k8s.io/kubernetes@v1.29.3/pkg/kubelet/lifecycle/handlers_test.go (about)

     1  /*
     2  Copyright 2014 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package lifecycle
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"io"
    23  	"net"
    24  	"net/http"
    25  	"net/http/httptest"
    26  	"reflect"
    27  	"strings"
    28  	"testing"
    29  	"time"
    30  
    31  	"github.com/google/go-cmp/cmp"
    32  	v1 "k8s.io/api/core/v1"
    33  	"k8s.io/apimachinery/pkg/types"
    34  	"k8s.io/apimachinery/pkg/util/intstr"
    35  	utilfeature "k8s.io/apiserver/pkg/util/feature"
    36  	"k8s.io/client-go/tools/record"
    37  	featuregatetesting "k8s.io/component-base/featuregate/testing"
    38  	"k8s.io/component-base/metrics/legacyregistry"
    39  	"k8s.io/component-base/metrics/testutil"
    40  	"k8s.io/kubernetes/pkg/features"
    41  	kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
    42  	"k8s.io/kubernetes/pkg/kubelet/metrics"
    43  	"k8s.io/kubernetes/pkg/kubelet/util/format"
    44  )
    45  
    46  func TestResolvePort(t *testing.T) {
    47  	for _, testCase := range []struct {
    48  		container  *v1.Container
    49  		stringPort string
    50  		expected   int
    51  	}{
    52  		{
    53  			stringPort: "foo",
    54  			container: &v1.Container{
    55  				Ports: []v1.ContainerPort{{Name: "foo", ContainerPort: int32(80)}},
    56  			},
    57  			expected: 80,
    58  		},
    59  		{
    60  			container:  &v1.Container{},
    61  			stringPort: "80",
    62  			expected:   80,
    63  		},
    64  		{
    65  			container: &v1.Container{
    66  				Ports: []v1.ContainerPort{
    67  					{Name: "bar", ContainerPort: int32(80)},
    68  				},
    69  			},
    70  			stringPort: "foo",
    71  			expected:   -1,
    72  		},
    73  	} {
    74  		port, err := resolvePort(intstr.FromString(testCase.stringPort), testCase.container)
    75  		if testCase.expected != -1 && err != nil {
    76  			t.Fatalf("unexpected error while resolving port: %s", err)
    77  		}
    78  		if testCase.expected == -1 && err == nil {
    79  			t.Errorf("expected error when a port fails to resolve")
    80  		}
    81  		if testCase.expected != port {
    82  			t.Errorf("failed to resolve port, expected %d, got %d", testCase.expected, port)
    83  		}
    84  	}
    85  }
    86  
    87  type fakeContainerCommandRunner struct {
    88  	Cmd []string
    89  	ID  kubecontainer.ContainerID
    90  	Err error
    91  	Msg string
    92  }
    93  
    94  func (f *fakeContainerCommandRunner) RunInContainer(_ context.Context, id kubecontainer.ContainerID, cmd []string, timeout time.Duration) ([]byte, error) {
    95  	f.Cmd = cmd
    96  	f.ID = id
    97  	return []byte(f.Msg), f.Err
    98  }
    99  
   100  func stubPodStatusProvider(podIP string) podStatusProvider {
   101  	return podStatusProviderFunc(func(uid types.UID, name, namespace string) (*kubecontainer.PodStatus, error) {
   102  		return &kubecontainer.PodStatus{
   103  			ID:        uid,
   104  			Name:      name,
   105  			Namespace: namespace,
   106  			IPs:       []string{podIP},
   107  		}, nil
   108  	})
   109  }
   110  
   111  type podStatusProviderFunc func(uid types.UID, name, namespace string) (*kubecontainer.PodStatus, error)
   112  
   113  func (f podStatusProviderFunc) GetPodStatus(_ context.Context, uid types.UID, name, namespace string) (*kubecontainer.PodStatus, error) {
   114  	return f(uid, name, namespace)
   115  }
   116  
   117  func TestRunHandlerExec(t *testing.T) {
   118  	ctx := context.Background()
   119  	fakeCommandRunner := fakeContainerCommandRunner{}
   120  	handlerRunner := NewHandlerRunner(&fakeHTTP{}, &fakeCommandRunner, nil, nil)
   121  
   122  	containerID := kubecontainer.ContainerID{Type: "test", ID: "abc1234"}
   123  	containerName := "containerFoo"
   124  
   125  	container := v1.Container{
   126  		Name: containerName,
   127  		Lifecycle: &v1.Lifecycle{
   128  			PostStart: &v1.LifecycleHandler{
   129  				Exec: &v1.ExecAction{
   130  					Command: []string{"ls", "-a"},
   131  				},
   132  			},
   133  		},
   134  	}
   135  
   136  	pod := v1.Pod{}
   137  	pod.ObjectMeta.Name = "podFoo"
   138  	pod.ObjectMeta.Namespace = "nsFoo"
   139  	pod.Spec.Containers = []v1.Container{container}
   140  	_, err := handlerRunner.Run(ctx, containerID, &pod, &container, container.Lifecycle.PostStart)
   141  	if err != nil {
   142  		t.Errorf("unexpected error: %v", err)
   143  	}
   144  	if fakeCommandRunner.ID != containerID ||
   145  		!reflect.DeepEqual(container.Lifecycle.PostStart.Exec.Command, fakeCommandRunner.Cmd) {
   146  		t.Errorf("unexpected commands: %v", fakeCommandRunner)
   147  	}
   148  }
   149  
   150  type fakeHTTP struct {
   151  	url     string
   152  	headers http.Header
   153  	err     error
   154  	resp    *http.Response
   155  }
   156  
   157  func (f *fakeHTTP) Do(req *http.Request) (*http.Response, error) {
   158  	f.url = req.URL.String()
   159  	f.headers = req.Header.Clone()
   160  	return f.resp, f.err
   161  }
   162  
   163  func TestRunHandlerHttp(t *testing.T) {
   164  	ctx := context.Background()
   165  	fakeHTTPGetter := fakeHTTP{}
   166  	fakePodStatusProvider := stubPodStatusProvider("127.0.0.1")
   167  	handlerRunner := NewHandlerRunner(&fakeHTTPGetter, &fakeContainerCommandRunner{}, fakePodStatusProvider, nil)
   168  
   169  	containerID := kubecontainer.ContainerID{Type: "test", ID: "abc1234"}
   170  	containerName := "containerFoo"
   171  
   172  	container := v1.Container{
   173  		Name: containerName,
   174  		Lifecycle: &v1.Lifecycle{
   175  			PostStart: &v1.LifecycleHandler{
   176  				HTTPGet: &v1.HTTPGetAction{
   177  					Host: "foo",
   178  					Port: intstr.FromInt32(8080),
   179  					Path: "bar",
   180  				},
   181  			},
   182  		},
   183  	}
   184  	pod := v1.Pod{}
   185  	pod.ObjectMeta.Name = "podFoo"
   186  	pod.ObjectMeta.Namespace = "nsFoo"
   187  	pod.ObjectMeta.UID = "foo-bar-quux"
   188  	pod.Spec.Containers = []v1.Container{container}
   189  	_, err := handlerRunner.Run(ctx, containerID, &pod, &container, container.Lifecycle.PostStart)
   190  
   191  	if err != nil {
   192  		t.Errorf("unexpected error: %v", err)
   193  	}
   194  	if fakeHTTPGetter.url != "http://foo:8080/bar" {
   195  		t.Errorf("unexpected url: %s", fakeHTTPGetter.url)
   196  	}
   197  }
   198  
   199  func TestRunHandlerHttpWithHeaders(t *testing.T) {
   200  	ctx := context.Background()
   201  	fakeHTTPDoer := fakeHTTP{}
   202  	fakePodStatusProvider := stubPodStatusProvider("127.0.0.1")
   203  
   204  	handlerRunner := NewHandlerRunner(&fakeHTTPDoer, &fakeContainerCommandRunner{}, fakePodStatusProvider, nil)
   205  
   206  	containerID := kubecontainer.ContainerID{Type: "test", ID: "abc1234"}
   207  	containerName := "containerFoo"
   208  
   209  	container := v1.Container{
   210  		Name: containerName,
   211  		Lifecycle: &v1.Lifecycle{
   212  			PostStart: &v1.LifecycleHandler{
   213  				HTTPGet: &v1.HTTPGetAction{
   214  					Host: "foo",
   215  					Port: intstr.FromInt32(8080),
   216  					Path: "/bar",
   217  					HTTPHeaders: []v1.HTTPHeader{
   218  						{Name: "Foo", Value: "bar"},
   219  					},
   220  				},
   221  			},
   222  		},
   223  	}
   224  	pod := v1.Pod{}
   225  	pod.ObjectMeta.Name = "podFoo"
   226  	pod.ObjectMeta.Namespace = "nsFoo"
   227  	pod.Spec.Containers = []v1.Container{container}
   228  	_, err := handlerRunner.Run(ctx, containerID, &pod, &container, container.Lifecycle.PostStart)
   229  
   230  	if err != nil {
   231  		t.Errorf("unexpected error: %v", err)
   232  	}
   233  	if fakeHTTPDoer.url != "http://foo:8080/bar" {
   234  		t.Errorf("unexpected url: %s", fakeHTTPDoer.url)
   235  	}
   236  	if fakeHTTPDoer.headers["Foo"][0] != "bar" {
   237  		t.Errorf("missing http header: %s", fakeHTTPDoer.headers)
   238  	}
   239  }
   240  
   241  func TestRunHandlerHttps(t *testing.T) {
   242  	ctx := context.Background()
   243  	fakeHTTPDoer := fakeHTTP{}
   244  	fakePodStatusProvider := stubPodStatusProvider("127.0.0.1")
   245  	handlerRunner := NewHandlerRunner(&fakeHTTPDoer, &fakeContainerCommandRunner{}, fakePodStatusProvider, nil)
   246  
   247  	containerID := kubecontainer.ContainerID{Type: "test", ID: "abc1234"}
   248  	containerName := "containerFoo"
   249  
   250  	container := v1.Container{
   251  		Name: containerName,
   252  		Lifecycle: &v1.Lifecycle{
   253  			PostStart: &v1.LifecycleHandler{
   254  				HTTPGet: &v1.HTTPGetAction{
   255  					Scheme: v1.URISchemeHTTPS,
   256  					Host:   "foo",
   257  					Path:   "bar",
   258  				},
   259  			},
   260  		},
   261  	}
   262  	pod := v1.Pod{}
   263  	pod.ObjectMeta.Name = "podFoo"
   264  	pod.ObjectMeta.Namespace = "nsFoo"
   265  	pod.Spec.Containers = []v1.Container{container}
   266  
   267  	t.Run("consistent", func(t *testing.T) {
   268  		container.Lifecycle.PostStart.HTTPGet.Port = intstr.FromString("70")
   269  		pod.Spec.Containers = []v1.Container{container}
   270  		_, err := handlerRunner.Run(ctx, containerID, &pod, &container, container.Lifecycle.PostStart)
   271  
   272  		if err != nil {
   273  			t.Errorf("unexpected error: %v", err)
   274  		}
   275  		if fakeHTTPDoer.url != "https://foo:70/bar" {
   276  			t.Errorf("unexpected url: %s", fakeHTTPDoer.url)
   277  		}
   278  	})
   279  
   280  	t.Run("inconsistent", func(t *testing.T) {
   281  		defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ConsistentHTTPGetHandlers, false)()
   282  		container.Lifecycle.PostStart.HTTPGet.Port = intstr.FromString("70")
   283  		pod.Spec.Containers = []v1.Container{container}
   284  		_, err := handlerRunner.Run(ctx, containerID, &pod, &container, container.Lifecycle.PostStart)
   285  
   286  		if err != nil {
   287  			t.Errorf("unexpected error: %v", err)
   288  		}
   289  		if fakeHTTPDoer.url != "http://foo:70/bar" {
   290  			t.Errorf("unexpected url: %q", fakeHTTPDoer.url)
   291  		}
   292  	})
   293  }
   294  
   295  func TestRunHandlerHTTPPort(t *testing.T) {
   296  	tests := []struct {
   297  		Name               string
   298  		FeatureGateEnabled bool
   299  		Port               intstr.IntOrString
   300  		ExpectError        bool
   301  		Expected           string
   302  	}{
   303  		{
   304  			Name:               "consistent/with port",
   305  			FeatureGateEnabled: true,
   306  			Port:               intstr.FromString("70"),
   307  			Expected:           "https://foo:70/bar",
   308  		}, {
   309  			Name:               "consistent/without port",
   310  			FeatureGateEnabled: true,
   311  			Port:               intstr.FromString(""),
   312  			ExpectError:        true,
   313  		}, {
   314  			Name:               "inconsistent/with port",
   315  			FeatureGateEnabled: false,
   316  			Port:               intstr.FromString("70"),
   317  			Expected:           "http://foo:70/bar",
   318  		}, {
   319  			Name:               "inconsistent/without port",
   320  			Port:               intstr.FromString(""),
   321  			FeatureGateEnabled: false,
   322  			Expected:           "http://foo:80/bar",
   323  		},
   324  	}
   325  
   326  	fakePodStatusProvider := stubPodStatusProvider("127.0.0.1")
   327  
   328  	containerID := kubecontainer.ContainerID{Type: "test", ID: "abc1234"}
   329  	containerName := "containerFoo"
   330  
   331  	container := v1.Container{
   332  		Name: containerName,
   333  		Lifecycle: &v1.Lifecycle{
   334  			PostStart: &v1.LifecycleHandler{
   335  				HTTPGet: &v1.HTTPGetAction{
   336  					Scheme: v1.URISchemeHTTPS,
   337  					Host:   "foo",
   338  					Port:   intstr.FromString("unexpected"),
   339  					Path:   "bar",
   340  				},
   341  			},
   342  		},
   343  	}
   344  	pod := v1.Pod{}
   345  	pod.ObjectMeta.Name = "podFoo"
   346  	pod.ObjectMeta.Namespace = "nsFoo"
   347  	pod.Spec.Containers = []v1.Container{container}
   348  
   349  	for _, tt := range tests {
   350  		t.Run(tt.Name, func(t *testing.T) {
   351  			ctx := context.Background()
   352  			defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ConsistentHTTPGetHandlers, tt.FeatureGateEnabled)()
   353  			fakeHTTPDoer := fakeHTTP{}
   354  			handlerRunner := NewHandlerRunner(&fakeHTTPDoer, &fakeContainerCommandRunner{}, fakePodStatusProvider, nil)
   355  
   356  			container.Lifecycle.PostStart.HTTPGet.Port = tt.Port
   357  			pod.Spec.Containers = []v1.Container{container}
   358  			_, err := handlerRunner.Run(ctx, containerID, &pod, &container, container.Lifecycle.PostStart)
   359  
   360  			if hasError := (err != nil); hasError != tt.ExpectError {
   361  				t.Errorf("unexpected error: %v", err)
   362  			}
   363  
   364  			if fakeHTTPDoer.url != tt.Expected {
   365  				t.Errorf("unexpected url: %s", fakeHTTPDoer.url)
   366  			}
   367  		})
   368  	}
   369  }
   370  
   371  func TestRunHTTPHandler(t *testing.T) {
   372  	type expected struct {
   373  		OldURL    string
   374  		OldHeader http.Header
   375  		NewURL    string
   376  		NewHeader http.Header
   377  	}
   378  
   379  	tests := []struct {
   380  		Name     string
   381  		PodIP    string
   382  		HTTPGet  *v1.HTTPGetAction
   383  		Expected expected
   384  	}{
   385  		{
   386  			Name:  "missing pod IP",
   387  			PodIP: "",
   388  			HTTPGet: &v1.HTTPGetAction{
   389  				Path:        "foo",
   390  				Port:        intstr.FromString("42"),
   391  				Host:        "example.test",
   392  				Scheme:      "http",
   393  				HTTPHeaders: []v1.HTTPHeader{},
   394  			},
   395  			Expected: expected{
   396  				OldURL:    "http://example.test:42/foo",
   397  				OldHeader: http.Header{},
   398  				NewURL:    "http://example.test:42/foo",
   399  				NewHeader: http.Header{
   400  					"Accept":     {"*/*"},
   401  					"User-Agent": {"kube-lifecycle/."},
   402  				},
   403  			},
   404  		}, {
   405  			Name:  "missing host",
   406  			PodIP: "233.252.0.1",
   407  			HTTPGet: &v1.HTTPGetAction{
   408  				Path:        "foo",
   409  				Port:        intstr.FromString("42"),
   410  				Scheme:      "http",
   411  				HTTPHeaders: []v1.HTTPHeader{},
   412  			},
   413  			Expected: expected{
   414  				OldURL:    "http://233.252.0.1:42/foo",
   415  				OldHeader: http.Header{},
   416  				NewURL:    "http://233.252.0.1:42/foo",
   417  				NewHeader: http.Header{
   418  					"Accept":     {"*/*"},
   419  					"User-Agent": {"kube-lifecycle/."},
   420  				},
   421  			},
   422  		}, {
   423  			Name:  "path with leading slash",
   424  			PodIP: "233.252.0.1",
   425  			HTTPGet: &v1.HTTPGetAction{
   426  				Path:        "/foo",
   427  				Port:        intstr.FromString("42"),
   428  				Scheme:      "http",
   429  				HTTPHeaders: []v1.HTTPHeader{},
   430  			},
   431  			Expected: expected{
   432  				OldURL:    "http://233.252.0.1:42//foo",
   433  				OldHeader: http.Header{},
   434  				NewURL:    "http://233.252.0.1:42/foo",
   435  				NewHeader: http.Header{
   436  					"Accept":     {"*/*"},
   437  					"User-Agent": {"kube-lifecycle/."},
   438  				},
   439  			},
   440  		}, {
   441  			Name:  "path without leading slash",
   442  			PodIP: "233.252.0.1",
   443  			HTTPGet: &v1.HTTPGetAction{
   444  				Path:        "foo",
   445  				Port:        intstr.FromString("42"),
   446  				Scheme:      "http",
   447  				HTTPHeaders: []v1.HTTPHeader{},
   448  			},
   449  			Expected: expected{
   450  				OldURL:    "http://233.252.0.1:42/foo",
   451  				OldHeader: http.Header{},
   452  				NewURL:    "http://233.252.0.1:42/foo",
   453  				NewHeader: http.Header{
   454  					"Accept":     {"*/*"},
   455  					"User-Agent": {"kube-lifecycle/."},
   456  				},
   457  			},
   458  		}, {
   459  			Name:  "port resolution",
   460  			PodIP: "233.252.0.1",
   461  			HTTPGet: &v1.HTTPGetAction{
   462  				Path:        "foo",
   463  				Port:        intstr.FromString("quux"),
   464  				Scheme:      "http",
   465  				HTTPHeaders: []v1.HTTPHeader{},
   466  			},
   467  			Expected: expected{
   468  				OldURL:    "http://233.252.0.1:8080/foo",
   469  				OldHeader: http.Header{},
   470  				NewURL:    "http://233.252.0.1:8080/foo",
   471  				NewHeader: http.Header{
   472  					"Accept":     {"*/*"},
   473  					"User-Agent": {"kube-lifecycle/."},
   474  				},
   475  			},
   476  		}, {
   477  			Name:  "https",
   478  			PodIP: "233.252.0.1",
   479  			HTTPGet: &v1.HTTPGetAction{
   480  				Path:        "foo",
   481  				Port:        intstr.FromString("4430"),
   482  				Scheme:      "https",
   483  				HTTPHeaders: []v1.HTTPHeader{},
   484  			},
   485  			Expected: expected{
   486  				OldURL:    "http://233.252.0.1:4430/foo",
   487  				OldHeader: http.Header{},
   488  				NewURL:    "https://233.252.0.1:4430/foo",
   489  				NewHeader: http.Header{
   490  					"Accept":     {"*/*"},
   491  					"User-Agent": {"kube-lifecycle/."},
   492  				},
   493  			},
   494  		}, {
   495  			Name:  "unknown scheme",
   496  			PodIP: "233.252.0.1",
   497  			HTTPGet: &v1.HTTPGetAction{
   498  				Path:        "foo",
   499  				Port:        intstr.FromString("80"),
   500  				Scheme:      "baz",
   501  				HTTPHeaders: []v1.HTTPHeader{},
   502  			},
   503  			Expected: expected{
   504  				OldURL:    "http://233.252.0.1:80/foo",
   505  				OldHeader: http.Header{},
   506  				NewURL:    "baz://233.252.0.1:80/foo",
   507  				NewHeader: http.Header{
   508  					"Accept":     {"*/*"},
   509  					"User-Agent": {"kube-lifecycle/."},
   510  				},
   511  			},
   512  		}, {
   513  			Name:  "query param",
   514  			PodIP: "233.252.0.1",
   515  			HTTPGet: &v1.HTTPGetAction{
   516  				Path:        "foo?k=v",
   517  				Port:        intstr.FromString("80"),
   518  				Scheme:      "http",
   519  				HTTPHeaders: []v1.HTTPHeader{},
   520  			},
   521  			Expected: expected{
   522  				OldURL:    "http://233.252.0.1:80/foo?k=v",
   523  				OldHeader: http.Header{},
   524  				NewURL:    "http://233.252.0.1:80/foo?k=v",
   525  				NewHeader: http.Header{
   526  					"Accept":     {"*/*"},
   527  					"User-Agent": {"kube-lifecycle/."},
   528  				},
   529  			},
   530  		}, {
   531  			Name:  "fragment",
   532  			PodIP: "233.252.0.1",
   533  			HTTPGet: &v1.HTTPGetAction{
   534  				Path:        "foo#frag",
   535  				Port:        intstr.FromString("80"),
   536  				Scheme:      "http",
   537  				HTTPHeaders: []v1.HTTPHeader{},
   538  			},
   539  			Expected: expected{
   540  				OldURL:    "http://233.252.0.1:80/foo#frag",
   541  				OldHeader: http.Header{},
   542  				NewURL:    "http://233.252.0.1:80/foo#frag",
   543  				NewHeader: http.Header{
   544  					"Accept":     {"*/*"},
   545  					"User-Agent": {"kube-lifecycle/."},
   546  				},
   547  			},
   548  		}, {
   549  			Name:  "headers",
   550  			PodIP: "233.252.0.1",
   551  			HTTPGet: &v1.HTTPGetAction{
   552  				Path:   "foo",
   553  				Port:   intstr.FromString("80"),
   554  				Scheme: "http",
   555  				HTTPHeaders: []v1.HTTPHeader{
   556  					{
   557  						Name:  "Foo",
   558  						Value: "bar",
   559  					},
   560  				},
   561  			},
   562  			Expected: expected{
   563  				OldURL:    "http://233.252.0.1:80/foo",
   564  				OldHeader: http.Header{},
   565  				NewURL:    "http://233.252.0.1:80/foo",
   566  				NewHeader: http.Header{
   567  					"Accept":     {"*/*"},
   568  					"Foo":        {"bar"},
   569  					"User-Agent": {"kube-lifecycle/."},
   570  				},
   571  			},
   572  		}, {
   573  			Name:  "host header",
   574  			PodIP: "233.252.0.1",
   575  			HTTPGet: &v1.HTTPGetAction{
   576  				Host:   "example.test",
   577  				Path:   "foo",
   578  				Port:   intstr.FromString("80"),
   579  				Scheme: "http",
   580  				HTTPHeaders: []v1.HTTPHeader{
   581  					{
   582  						Name:  "Host",
   583  						Value: "from.header",
   584  					},
   585  				},
   586  			},
   587  			Expected: expected{
   588  				OldURL:    "http://example.test:80/foo",
   589  				OldHeader: http.Header{},
   590  				NewURL:    "http://example.test:80/foo",
   591  				NewHeader: http.Header{
   592  					"Accept":     {"*/*"},
   593  					"User-Agent": {"kube-lifecycle/."},
   594  					"Host":       {"from.header"},
   595  				},
   596  			},
   597  		},
   598  	}
   599  
   600  	containerID := kubecontainer.ContainerID{Type: "test", ID: "abc1234"}
   601  	containerName := "containerFoo"
   602  
   603  	container := v1.Container{
   604  		Name: containerName,
   605  		Lifecycle: &v1.Lifecycle{
   606  			PostStart: &v1.LifecycleHandler{},
   607  		},
   608  		Ports: []v1.ContainerPort{
   609  			{
   610  				Name:          "quux",
   611  				ContainerPort: 8080,
   612  			},
   613  		},
   614  	}
   615  
   616  	pod := v1.Pod{}
   617  	pod.ObjectMeta.Name = "podFoo"
   618  	pod.ObjectMeta.Namespace = "nsFoo"
   619  	pod.Spec.Containers = []v1.Container{container}
   620  
   621  	for _, tt := range tests {
   622  		t.Run(tt.Name, func(t *testing.T) {
   623  			ctx := context.Background()
   624  			fakePodStatusProvider := stubPodStatusProvider(tt.PodIP)
   625  
   626  			container.Lifecycle.PostStart.HTTPGet = tt.HTTPGet
   627  			pod.Spec.Containers = []v1.Container{container}
   628  
   629  			verify := func(t *testing.T, expectedHeader http.Header, expectedURL string) {
   630  				fakeHTTPDoer := fakeHTTP{}
   631  				handlerRunner := NewHandlerRunner(&fakeHTTPDoer, &fakeContainerCommandRunner{}, fakePodStatusProvider, nil)
   632  
   633  				_, err := handlerRunner.Run(ctx, containerID, &pod, &container, container.Lifecycle.PostStart)
   634  				if err != nil {
   635  					t.Fatal(err)
   636  				}
   637  
   638  				if diff := cmp.Diff(expectedHeader, fakeHTTPDoer.headers); diff != "" {
   639  					t.Errorf("unexpected header (-want, +got)\n:%s", diff)
   640  				}
   641  				if fakeHTTPDoer.url != expectedURL {
   642  					t.Errorf("url = %v; want %v", fakeHTTPDoer.url, tt.Expected.NewURL)
   643  				}
   644  			}
   645  
   646  			t.Run("consistent", func(t *testing.T) {
   647  				defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ConsistentHTTPGetHandlers, true)()
   648  				verify(t, tt.Expected.NewHeader, tt.Expected.NewURL)
   649  			})
   650  
   651  			t.Run("inconsistent", func(t *testing.T) {
   652  				defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ConsistentHTTPGetHandlers, false)()
   653  				verify(t, tt.Expected.OldHeader, tt.Expected.OldURL)
   654  			})
   655  		})
   656  	}
   657  }
   658  
   659  func TestRunHandlerNil(t *testing.T) {
   660  	ctx := context.Background()
   661  	handlerRunner := NewHandlerRunner(&fakeHTTP{}, &fakeContainerCommandRunner{}, nil, nil)
   662  	containerID := kubecontainer.ContainerID{Type: "test", ID: "abc1234"}
   663  	podName := "podFoo"
   664  	podNamespace := "nsFoo"
   665  	containerName := "containerFoo"
   666  
   667  	container := v1.Container{
   668  		Name: containerName,
   669  		Lifecycle: &v1.Lifecycle{
   670  			PostStart: &v1.LifecycleHandler{},
   671  		},
   672  	}
   673  	pod := v1.Pod{}
   674  	pod.ObjectMeta.Name = podName
   675  	pod.ObjectMeta.Namespace = podNamespace
   676  	pod.Spec.Containers = []v1.Container{container}
   677  	_, err := handlerRunner.Run(ctx, containerID, &pod, &container, container.Lifecycle.PostStart)
   678  	if err == nil {
   679  		t.Errorf("expect error, but got nil")
   680  	}
   681  }
   682  
   683  func TestRunHandlerExecFailure(t *testing.T) {
   684  	ctx := context.Background()
   685  	expectedErr := fmt.Errorf("invalid command")
   686  	fakeCommandRunner := fakeContainerCommandRunner{Err: expectedErr, Msg: expectedErr.Error()}
   687  	handlerRunner := NewHandlerRunner(&fakeHTTP{}, &fakeCommandRunner, nil, nil)
   688  
   689  	containerID := kubecontainer.ContainerID{Type: "test", ID: "abc1234"}
   690  	containerName := "containerFoo"
   691  	command := []string{"ls", "--a"}
   692  
   693  	container := v1.Container{
   694  		Name: containerName,
   695  		Lifecycle: &v1.Lifecycle{
   696  			PostStart: &v1.LifecycleHandler{
   697  				Exec: &v1.ExecAction{
   698  					Command: command,
   699  				},
   700  			},
   701  		},
   702  	}
   703  
   704  	pod := v1.Pod{}
   705  	pod.ObjectMeta.Name = "podFoo"
   706  	pod.ObjectMeta.Namespace = "nsFoo"
   707  	pod.Spec.Containers = []v1.Container{container}
   708  	expectedErrMsg := fmt.Sprintf("Exec lifecycle hook (%s) for Container %q in Pod %q failed - error: %v, message: %q", command, containerName, format.Pod(&pod), expectedErr, expectedErr.Error())
   709  	msg, err := handlerRunner.Run(ctx, containerID, &pod, &container, container.Lifecycle.PostStart)
   710  	if err == nil {
   711  		t.Errorf("expected error: %v", expectedErr)
   712  	}
   713  	if msg != expectedErrMsg {
   714  		t.Errorf("unexpected error message: %q; expected %q", msg, expectedErrMsg)
   715  	}
   716  }
   717  
   718  func TestRunHandlerHttpFailure(t *testing.T) {
   719  	ctx := context.Background()
   720  	expectedErr := fmt.Errorf("fake http error")
   721  	expectedResp := http.Response{
   722  		Body: io.NopCloser(strings.NewReader(expectedErr.Error())),
   723  	}
   724  	fakeHTTPGetter := fakeHTTP{err: expectedErr, resp: &expectedResp}
   725  
   726  	fakePodStatusProvider := stubPodStatusProvider("127.0.0.1")
   727  
   728  	handlerRunner := NewHandlerRunner(&fakeHTTPGetter, &fakeContainerCommandRunner{}, fakePodStatusProvider, nil)
   729  
   730  	containerName := "containerFoo"
   731  	containerID := kubecontainer.ContainerID{Type: "test", ID: "abc1234"}
   732  	container := v1.Container{
   733  		Name: containerName,
   734  		Lifecycle: &v1.Lifecycle{
   735  			PostStart: &v1.LifecycleHandler{
   736  				HTTPGet: &v1.HTTPGetAction{
   737  					Host: "foo",
   738  					Port: intstr.FromInt32(8080),
   739  					Path: "bar",
   740  				},
   741  			},
   742  		},
   743  	}
   744  	pod := v1.Pod{}
   745  	pod.ObjectMeta.Name = "podFoo"
   746  	pod.ObjectMeta.Namespace = "nsFoo"
   747  	pod.Spec.Containers = []v1.Container{container}
   748  	expectedErrMsg := fmt.Sprintf("HTTP lifecycle hook (%s) for Container %q in Pod %q failed - error: %v", "bar", containerName, format.Pod(&pod), expectedErr)
   749  	msg, err := handlerRunner.Run(ctx, containerID, &pod, &container, container.Lifecycle.PostStart)
   750  	if err == nil {
   751  		t.Errorf("expected error: %v", expectedErr)
   752  	}
   753  	if msg != expectedErrMsg {
   754  		t.Errorf("unexpected error message: %q; expected %q", msg, expectedErrMsg)
   755  	}
   756  	if fakeHTTPGetter.url != "http://foo:8080/bar" {
   757  		t.Errorf("unexpected url: %s", fakeHTTPGetter.url)
   758  	}
   759  }
   760  
   761  func TestRunHandlerHttpsFailureFallback(t *testing.T) {
   762  	ctx := context.Background()
   763  	defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ConsistentHTTPGetHandlers, true)()
   764  
   765  	// Since prometheus' gatherer is global, other tests may have updated metrics already, so
   766  	// we need to reset them prior running this test.
   767  	// This also implies that we can't run this test in parallel with other tests.
   768  	metrics.Register()
   769  	legacyregistry.Reset()
   770  
   771  	var actualHeaders http.Header
   772  	srv := httptest.NewServer(http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) {
   773  		actualHeaders = r.Header.Clone()
   774  	}))
   775  	defer srv.Close()
   776  	_, port, err := net.SplitHostPort(srv.Listener.Addr().String())
   777  	if err != nil {
   778  		t.Fatal(err)
   779  	}
   780  
   781  	recorder := &record.FakeRecorder{Events: make(chan string, 10)}
   782  
   783  	fakePodStatusProvider := stubPodStatusProvider("127.0.0.1")
   784  
   785  	handlerRunner := NewHandlerRunner(srv.Client(), &fakeContainerCommandRunner{}, fakePodStatusProvider, recorder).(*handlerRunner)
   786  
   787  	containerName := "containerFoo"
   788  	containerID := kubecontainer.ContainerID{Type: "test", ID: "abc1234"}
   789  	container := v1.Container{
   790  		Name: containerName,
   791  		Lifecycle: &v1.Lifecycle{
   792  			PostStart: &v1.LifecycleHandler{
   793  				HTTPGet: &v1.HTTPGetAction{
   794  					// set the scheme to https to ensure it falls back to HTTP.
   795  					Scheme: "https",
   796  					Host:   "127.0.0.1",
   797  					Port:   intstr.FromString(port),
   798  					Path:   "bar",
   799  					HTTPHeaders: []v1.HTTPHeader{
   800  						{
   801  							Name:  "Authorization",
   802  							Value: "secret",
   803  						},
   804  					},
   805  				},
   806  			},
   807  		},
   808  	}
   809  	pod := v1.Pod{}
   810  	pod.ObjectMeta.Name = "podFoo"
   811  	pod.ObjectMeta.Namespace = "nsFoo"
   812  	pod.Spec.Containers = []v1.Container{container}
   813  	msg, err := handlerRunner.Run(ctx, containerID, &pod, &container, container.Lifecycle.PostStart)
   814  
   815  	if err != nil {
   816  		t.Errorf("unexpected error: %v", err)
   817  	}
   818  	if msg != "" {
   819  		t.Errorf("unexpected error message: %q", msg)
   820  	}
   821  	if actualHeaders.Get("Authorization") != "" {
   822  		t.Error("unexpected Authorization header")
   823  	}
   824  
   825  	expectedMetrics := `
   826  # HELP kubelet_lifecycle_handler_http_fallbacks_total [ALPHA] The number of times lifecycle handlers successfully fell back to http from https.
   827  # TYPE kubelet_lifecycle_handler_http_fallbacks_total counter
   828  kubelet_lifecycle_handler_http_fallbacks_total 1
   829  `
   830  
   831  	if err := testutil.GatherAndCompare(legacyregistry.DefaultGatherer, strings.NewReader(expectedMetrics), "kubelet_lifecycle_handler_http_fallbacks_total"); err != nil {
   832  		t.Fatal(err)
   833  	}
   834  
   835  	select {
   836  	case event := <-recorder.Events:
   837  		if !strings.Contains(event, "LifecycleHTTPFallback") {
   838  			t.Fatalf("expected LifecycleHTTPFallback event, got %q", event)
   839  		}
   840  	default:
   841  		t.Fatal("no event recorded")
   842  	}
   843  }
   844  
   845  func TestIsHTTPResponseError(t *testing.T) {
   846  	s := httptest.NewServer(http.HandlerFunc(func(_ http.ResponseWriter, _ *http.Request) {}))
   847  	defer s.Close()
   848  	req, err := http.NewRequest("GET", s.URL, nil)
   849  	if err != nil {
   850  		t.Fatal(err)
   851  	}
   852  	req.URL.Scheme = "https"
   853  	_, err = http.DefaultClient.Do(req)
   854  	if !isHTTPResponseError(err) {
   855  		t.Errorf("unexpected http response error: %v", err)
   856  	}
   857  }
   858  
   859  func TestRunSleepHandler(t *testing.T) {
   860  	handlerRunner := NewHandlerRunner(&fakeHTTP{}, &fakeContainerCommandRunner{}, nil, nil)
   861  	containerID := kubecontainer.ContainerID{Type: "test", ID: "abc1234"}
   862  	containerName := "containerFoo"
   863  	container := v1.Container{
   864  		Name: containerName,
   865  		Lifecycle: &v1.Lifecycle{
   866  			PreStop: &v1.LifecycleHandler{},
   867  		},
   868  	}
   869  	pod := v1.Pod{}
   870  	pod.ObjectMeta.Name = "podFoo"
   871  	pod.ObjectMeta.Namespace = "nsFoo"
   872  	pod.Spec.Containers = []v1.Container{container}
   873  
   874  	tests := []struct {
   875  		name                          string
   876  		sleepSeconds                  int64
   877  		terminationGracePeriodSeconds int64
   878  		expectErr                     bool
   879  		expectedErr                   string
   880  	}{
   881  		{
   882  			name:                          "valid seconds",
   883  			sleepSeconds:                  5,
   884  			terminationGracePeriodSeconds: 30,
   885  		},
   886  		{
   887  			name:                          "longer than TerminationGracePeriodSeconds",
   888  			sleepSeconds:                  3,
   889  			terminationGracePeriodSeconds: 2,
   890  			expectErr:                     true,
   891  			expectedErr:                   "container terminated before sleep hook finished",
   892  		},
   893  	}
   894  
   895  	for _, tt := range tests {
   896  		t.Run(tt.name, func(t *testing.T) {
   897  			defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodLifecycleSleepAction, true)()
   898  
   899  			pod.Spec.Containers[0].Lifecycle.PreStop.Sleep = &v1.SleepAction{Seconds: tt.sleepSeconds}
   900  			ctx, cancel := context.WithTimeout(context.Background(), time.Duration(tt.terminationGracePeriodSeconds)*time.Second)
   901  			defer cancel()
   902  
   903  			_, err := handlerRunner.Run(ctx, containerID, &pod, &container, container.Lifecycle.PreStop)
   904  
   905  			if !tt.expectErr && err != nil {
   906  				t.Errorf("unexpected success")
   907  			}
   908  			if tt.expectErr && err.Error() != tt.expectedErr {
   909  				t.Errorf("%s: expected error want %s, got %s", tt.name, tt.expectedErr, err.Error())
   910  			}
   911  		})
   912  	}
   913  }