...

Source file src/k8s.io/kubernetes/pkg/kubelet/lifecycle/handlers_test.go

Documentation: k8s.io/kubernetes/pkg/kubelet/lifecycle

     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  
   281  func TestRunHandlerHTTPPort(t *testing.T) {
   282  	tests := []struct {
   283  		Name        string
   284  		Port        intstr.IntOrString
   285  		ExpectError bool
   286  		Expected    string
   287  	}{
   288  		{
   289  			Name:     "consistent/with port",
   290  			Port:     intstr.FromString("70"),
   291  			Expected: "https://foo:70/bar",
   292  		}, {
   293  			Name:        "consistent/without port",
   294  			Port:        intstr.FromString(""),
   295  			ExpectError: true,
   296  		},
   297  	}
   298  
   299  	fakePodStatusProvider := stubPodStatusProvider("127.0.0.1")
   300  
   301  	containerID := kubecontainer.ContainerID{Type: "test", ID: "abc1234"}
   302  	containerName := "containerFoo"
   303  
   304  	container := v1.Container{
   305  		Name: containerName,
   306  		Lifecycle: &v1.Lifecycle{
   307  			PostStart: &v1.LifecycleHandler{
   308  				HTTPGet: &v1.HTTPGetAction{
   309  					Scheme: v1.URISchemeHTTPS,
   310  					Host:   "foo",
   311  					Port:   intstr.FromString("unexpected"),
   312  					Path:   "bar",
   313  				},
   314  			},
   315  		},
   316  	}
   317  	pod := v1.Pod{}
   318  	pod.ObjectMeta.Name = "podFoo"
   319  	pod.ObjectMeta.Namespace = "nsFoo"
   320  	pod.Spec.Containers = []v1.Container{container}
   321  
   322  	for _, tt := range tests {
   323  		t.Run(tt.Name, func(t *testing.T) {
   324  			ctx := context.Background()
   325  			fakeHTTPDoer := fakeHTTP{}
   326  			handlerRunner := NewHandlerRunner(&fakeHTTPDoer, &fakeContainerCommandRunner{}, fakePodStatusProvider, nil)
   327  
   328  			container.Lifecycle.PostStart.HTTPGet.Port = tt.Port
   329  			pod.Spec.Containers = []v1.Container{container}
   330  			_, err := handlerRunner.Run(ctx, containerID, &pod, &container, container.Lifecycle.PostStart)
   331  
   332  			if hasError := (err != nil); hasError != tt.ExpectError {
   333  				t.Errorf("unexpected error: %v", err)
   334  			}
   335  
   336  			if fakeHTTPDoer.url != tt.Expected {
   337  				t.Errorf("unexpected url: %s", fakeHTTPDoer.url)
   338  			}
   339  		})
   340  	}
   341  }
   342  
   343  func TestRunHTTPHandler(t *testing.T) {
   344  	type expected struct {
   345  		OldURL    string
   346  		OldHeader http.Header
   347  		NewURL    string
   348  		NewHeader http.Header
   349  	}
   350  
   351  	tests := []struct {
   352  		Name     string
   353  		PodIP    string
   354  		HTTPGet  *v1.HTTPGetAction
   355  		Expected expected
   356  	}{
   357  		{
   358  			Name:  "missing pod IP",
   359  			PodIP: "",
   360  			HTTPGet: &v1.HTTPGetAction{
   361  				Path:        "foo",
   362  				Port:        intstr.FromString("42"),
   363  				Host:        "example.test",
   364  				Scheme:      "http",
   365  				HTTPHeaders: []v1.HTTPHeader{},
   366  			},
   367  			Expected: expected{
   368  				OldURL:    "http://example.test:42/foo",
   369  				OldHeader: http.Header{},
   370  				NewURL:    "http://example.test:42/foo",
   371  				NewHeader: http.Header{
   372  					"Accept":     {"*/*"},
   373  					"User-Agent": {"kube-lifecycle/."},
   374  				},
   375  			},
   376  		}, {
   377  			Name:  "missing host",
   378  			PodIP: "233.252.0.1",
   379  			HTTPGet: &v1.HTTPGetAction{
   380  				Path:        "foo",
   381  				Port:        intstr.FromString("42"),
   382  				Scheme:      "http",
   383  				HTTPHeaders: []v1.HTTPHeader{},
   384  			},
   385  			Expected: expected{
   386  				OldURL:    "http://233.252.0.1:42/foo",
   387  				OldHeader: http.Header{},
   388  				NewURL:    "http://233.252.0.1:42/foo",
   389  				NewHeader: http.Header{
   390  					"Accept":     {"*/*"},
   391  					"User-Agent": {"kube-lifecycle/."},
   392  				},
   393  			},
   394  		}, {
   395  			Name:  "path with leading slash",
   396  			PodIP: "233.252.0.1",
   397  			HTTPGet: &v1.HTTPGetAction{
   398  				Path:        "/foo",
   399  				Port:        intstr.FromString("42"),
   400  				Scheme:      "http",
   401  				HTTPHeaders: []v1.HTTPHeader{},
   402  			},
   403  			Expected: expected{
   404  				OldURL:    "http://233.252.0.1:42//foo",
   405  				OldHeader: http.Header{},
   406  				NewURL:    "http://233.252.0.1:42/foo",
   407  				NewHeader: http.Header{
   408  					"Accept":     {"*/*"},
   409  					"User-Agent": {"kube-lifecycle/."},
   410  				},
   411  			},
   412  		}, {
   413  			Name:  "path without leading slash",
   414  			PodIP: "233.252.0.1",
   415  			HTTPGet: &v1.HTTPGetAction{
   416  				Path:        "foo",
   417  				Port:        intstr.FromString("42"),
   418  				Scheme:      "http",
   419  				HTTPHeaders: []v1.HTTPHeader{},
   420  			},
   421  			Expected: expected{
   422  				OldURL:    "http://233.252.0.1:42/foo",
   423  				OldHeader: http.Header{},
   424  				NewURL:    "http://233.252.0.1:42/foo",
   425  				NewHeader: http.Header{
   426  					"Accept":     {"*/*"},
   427  					"User-Agent": {"kube-lifecycle/."},
   428  				},
   429  			},
   430  		}, {
   431  			Name:  "port resolution",
   432  			PodIP: "233.252.0.1",
   433  			HTTPGet: &v1.HTTPGetAction{
   434  				Path:        "foo",
   435  				Port:        intstr.FromString("quux"),
   436  				Scheme:      "http",
   437  				HTTPHeaders: []v1.HTTPHeader{},
   438  			},
   439  			Expected: expected{
   440  				OldURL:    "http://233.252.0.1:8080/foo",
   441  				OldHeader: http.Header{},
   442  				NewURL:    "http://233.252.0.1:8080/foo",
   443  				NewHeader: http.Header{
   444  					"Accept":     {"*/*"},
   445  					"User-Agent": {"kube-lifecycle/."},
   446  				},
   447  			},
   448  		}, {
   449  			Name:  "https",
   450  			PodIP: "233.252.0.1",
   451  			HTTPGet: &v1.HTTPGetAction{
   452  				Path:        "foo",
   453  				Port:        intstr.FromString("4430"),
   454  				Scheme:      "https",
   455  				HTTPHeaders: []v1.HTTPHeader{},
   456  			},
   457  			Expected: expected{
   458  				OldURL:    "http://233.252.0.1:4430/foo",
   459  				OldHeader: http.Header{},
   460  				NewURL:    "https://233.252.0.1:4430/foo",
   461  				NewHeader: http.Header{
   462  					"Accept":     {"*/*"},
   463  					"User-Agent": {"kube-lifecycle/."},
   464  				},
   465  			},
   466  		}, {
   467  			Name:  "unknown scheme",
   468  			PodIP: "233.252.0.1",
   469  			HTTPGet: &v1.HTTPGetAction{
   470  				Path:        "foo",
   471  				Port:        intstr.FromString("80"),
   472  				Scheme:      "baz",
   473  				HTTPHeaders: []v1.HTTPHeader{},
   474  			},
   475  			Expected: expected{
   476  				OldURL:    "http://233.252.0.1:80/foo",
   477  				OldHeader: http.Header{},
   478  				NewURL:    "baz://233.252.0.1:80/foo",
   479  				NewHeader: http.Header{
   480  					"Accept":     {"*/*"},
   481  					"User-Agent": {"kube-lifecycle/."},
   482  				},
   483  			},
   484  		}, {
   485  			Name:  "query param",
   486  			PodIP: "233.252.0.1",
   487  			HTTPGet: &v1.HTTPGetAction{
   488  				Path:        "foo?k=v",
   489  				Port:        intstr.FromString("80"),
   490  				Scheme:      "http",
   491  				HTTPHeaders: []v1.HTTPHeader{},
   492  			},
   493  			Expected: expected{
   494  				OldURL:    "http://233.252.0.1:80/foo?k=v",
   495  				OldHeader: http.Header{},
   496  				NewURL:    "http://233.252.0.1:80/foo?k=v",
   497  				NewHeader: http.Header{
   498  					"Accept":     {"*/*"},
   499  					"User-Agent": {"kube-lifecycle/."},
   500  				},
   501  			},
   502  		}, {
   503  			Name:  "fragment",
   504  			PodIP: "233.252.0.1",
   505  			HTTPGet: &v1.HTTPGetAction{
   506  				Path:        "foo#frag",
   507  				Port:        intstr.FromString("80"),
   508  				Scheme:      "http",
   509  				HTTPHeaders: []v1.HTTPHeader{},
   510  			},
   511  			Expected: expected{
   512  				OldURL:    "http://233.252.0.1:80/foo#frag",
   513  				OldHeader: http.Header{},
   514  				NewURL:    "http://233.252.0.1:80/foo#frag",
   515  				NewHeader: http.Header{
   516  					"Accept":     {"*/*"},
   517  					"User-Agent": {"kube-lifecycle/."},
   518  				},
   519  			},
   520  		}, {
   521  			Name:  "headers",
   522  			PodIP: "233.252.0.1",
   523  			HTTPGet: &v1.HTTPGetAction{
   524  				Path:   "foo",
   525  				Port:   intstr.FromString("80"),
   526  				Scheme: "http",
   527  				HTTPHeaders: []v1.HTTPHeader{
   528  					{
   529  						Name:  "Foo",
   530  						Value: "bar",
   531  					},
   532  				},
   533  			},
   534  			Expected: expected{
   535  				OldURL:    "http://233.252.0.1:80/foo",
   536  				OldHeader: http.Header{},
   537  				NewURL:    "http://233.252.0.1:80/foo",
   538  				NewHeader: http.Header{
   539  					"Accept":     {"*/*"},
   540  					"Foo":        {"bar"},
   541  					"User-Agent": {"kube-lifecycle/."},
   542  				},
   543  			},
   544  		}, {
   545  			Name:  "host header",
   546  			PodIP: "233.252.0.1",
   547  			HTTPGet: &v1.HTTPGetAction{
   548  				Host:   "example.test",
   549  				Path:   "foo",
   550  				Port:   intstr.FromString("80"),
   551  				Scheme: "http",
   552  				HTTPHeaders: []v1.HTTPHeader{
   553  					{
   554  						Name:  "Host",
   555  						Value: "from.header",
   556  					},
   557  				},
   558  			},
   559  			Expected: expected{
   560  				OldURL:    "http://example.test:80/foo",
   561  				OldHeader: http.Header{},
   562  				NewURL:    "http://example.test:80/foo",
   563  				NewHeader: http.Header{
   564  					"Accept":     {"*/*"},
   565  					"User-Agent": {"kube-lifecycle/."},
   566  					"Host":       {"from.header"},
   567  				},
   568  			},
   569  		},
   570  	}
   571  
   572  	containerID := kubecontainer.ContainerID{Type: "test", ID: "abc1234"}
   573  	containerName := "containerFoo"
   574  
   575  	container := v1.Container{
   576  		Name: containerName,
   577  		Lifecycle: &v1.Lifecycle{
   578  			PostStart: &v1.LifecycleHandler{},
   579  		},
   580  		Ports: []v1.ContainerPort{
   581  			{
   582  				Name:          "quux",
   583  				ContainerPort: 8080,
   584  			},
   585  		},
   586  	}
   587  
   588  	pod := v1.Pod{}
   589  	pod.ObjectMeta.Name = "podFoo"
   590  	pod.ObjectMeta.Namespace = "nsFoo"
   591  	pod.Spec.Containers = []v1.Container{container}
   592  
   593  	for _, tt := range tests {
   594  		t.Run(tt.Name, func(t *testing.T) {
   595  			ctx := context.Background()
   596  			fakePodStatusProvider := stubPodStatusProvider(tt.PodIP)
   597  
   598  			container.Lifecycle.PostStart.HTTPGet = tt.HTTPGet
   599  			pod.Spec.Containers = []v1.Container{container}
   600  
   601  			verify := func(t *testing.T, expectedHeader http.Header, expectedURL string) {
   602  				fakeHTTPDoer := fakeHTTP{}
   603  				handlerRunner := NewHandlerRunner(&fakeHTTPDoer, &fakeContainerCommandRunner{}, fakePodStatusProvider, nil)
   604  
   605  				_, err := handlerRunner.Run(ctx, containerID, &pod, &container, container.Lifecycle.PostStart)
   606  				if err != nil {
   607  					t.Fatal(err)
   608  				}
   609  
   610  				if diff := cmp.Diff(expectedHeader, fakeHTTPDoer.headers); diff != "" {
   611  					t.Errorf("unexpected header (-want, +got)\n:%s", diff)
   612  				}
   613  				if fakeHTTPDoer.url != expectedURL {
   614  					t.Errorf("url = %v; want %v", fakeHTTPDoer.url, tt.Expected.NewURL)
   615  				}
   616  			}
   617  
   618  			t.Run("consistent", func(t *testing.T) {
   619  				verify(t, tt.Expected.NewHeader, tt.Expected.NewURL)
   620  			})
   621  		})
   622  	}
   623  }
   624  
   625  func TestRunHandlerNil(t *testing.T) {
   626  	ctx := context.Background()
   627  	handlerRunner := NewHandlerRunner(&fakeHTTP{}, &fakeContainerCommandRunner{}, nil, nil)
   628  	containerID := kubecontainer.ContainerID{Type: "test", ID: "abc1234"}
   629  	podName := "podFoo"
   630  	podNamespace := "nsFoo"
   631  	containerName := "containerFoo"
   632  
   633  	container := v1.Container{
   634  		Name: containerName,
   635  		Lifecycle: &v1.Lifecycle{
   636  			PostStart: &v1.LifecycleHandler{},
   637  		},
   638  	}
   639  	pod := v1.Pod{}
   640  	pod.ObjectMeta.Name = podName
   641  	pod.ObjectMeta.Namespace = podNamespace
   642  	pod.Spec.Containers = []v1.Container{container}
   643  	_, err := handlerRunner.Run(ctx, containerID, &pod, &container, container.Lifecycle.PostStart)
   644  	if err == nil {
   645  		t.Errorf("expect error, but got nil")
   646  	}
   647  }
   648  
   649  func TestRunHandlerExecFailure(t *testing.T) {
   650  	ctx := context.Background()
   651  	expectedErr := fmt.Errorf("invalid command")
   652  	fakeCommandRunner := fakeContainerCommandRunner{Err: expectedErr, Msg: expectedErr.Error()}
   653  	handlerRunner := NewHandlerRunner(&fakeHTTP{}, &fakeCommandRunner, nil, nil)
   654  
   655  	containerID := kubecontainer.ContainerID{Type: "test", ID: "abc1234"}
   656  	containerName := "containerFoo"
   657  	command := []string{"ls", "--a"}
   658  
   659  	container := v1.Container{
   660  		Name: containerName,
   661  		Lifecycle: &v1.Lifecycle{
   662  			PostStart: &v1.LifecycleHandler{
   663  				Exec: &v1.ExecAction{
   664  					Command: command,
   665  				},
   666  			},
   667  		},
   668  	}
   669  
   670  	pod := v1.Pod{}
   671  	pod.ObjectMeta.Name = "podFoo"
   672  	pod.ObjectMeta.Namespace = "nsFoo"
   673  	pod.Spec.Containers = []v1.Container{container}
   674  	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())
   675  	msg, err := handlerRunner.Run(ctx, containerID, &pod, &container, container.Lifecycle.PostStart)
   676  	if err == nil {
   677  		t.Errorf("expected error: %v", expectedErr)
   678  	}
   679  	if msg != expectedErrMsg {
   680  		t.Errorf("unexpected error message: %q; expected %q", msg, expectedErrMsg)
   681  	}
   682  }
   683  
   684  func TestRunHandlerHttpFailure(t *testing.T) {
   685  	ctx := context.Background()
   686  	expectedErr := fmt.Errorf("fake http error")
   687  	expectedResp := http.Response{
   688  		Body: io.NopCloser(strings.NewReader(expectedErr.Error())),
   689  	}
   690  	fakeHTTPGetter := fakeHTTP{err: expectedErr, resp: &expectedResp}
   691  
   692  	fakePodStatusProvider := stubPodStatusProvider("127.0.0.1")
   693  
   694  	handlerRunner := NewHandlerRunner(&fakeHTTPGetter, &fakeContainerCommandRunner{}, fakePodStatusProvider, nil)
   695  
   696  	containerName := "containerFoo"
   697  	containerID := kubecontainer.ContainerID{Type: "test", ID: "abc1234"}
   698  	container := v1.Container{
   699  		Name: containerName,
   700  		Lifecycle: &v1.Lifecycle{
   701  			PostStart: &v1.LifecycleHandler{
   702  				HTTPGet: &v1.HTTPGetAction{
   703  					Host: "foo",
   704  					Port: intstr.FromInt32(8080),
   705  					Path: "bar",
   706  				},
   707  			},
   708  		},
   709  	}
   710  	pod := v1.Pod{}
   711  	pod.ObjectMeta.Name = "podFoo"
   712  	pod.ObjectMeta.Namespace = "nsFoo"
   713  	pod.Spec.Containers = []v1.Container{container}
   714  	expectedErrMsg := fmt.Sprintf("HTTP lifecycle hook (%s) for Container %q in Pod %q failed - error: %v", "bar", containerName, format.Pod(&pod), expectedErr)
   715  	msg, err := handlerRunner.Run(ctx, containerID, &pod, &container, container.Lifecycle.PostStart)
   716  	if err == nil {
   717  		t.Errorf("expected error: %v", expectedErr)
   718  	}
   719  	if msg != expectedErrMsg {
   720  		t.Errorf("unexpected error message: %q; expected %q", msg, expectedErrMsg)
   721  	}
   722  	if fakeHTTPGetter.url != "http://foo:8080/bar" {
   723  		t.Errorf("unexpected url: %s", fakeHTTPGetter.url)
   724  	}
   725  }
   726  
   727  func TestRunHandlerHttpsFailureFallback(t *testing.T) {
   728  	ctx := context.Background()
   729  
   730  	// Since prometheus' gatherer is global, other tests may have updated metrics already, so
   731  	// we need to reset them prior running this test.
   732  	// This also implies that we can't run this test in parallel with other tests.
   733  	metrics.Register()
   734  	legacyregistry.Reset()
   735  
   736  	var actualHeaders http.Header
   737  	srv := httptest.NewServer(http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) {
   738  		actualHeaders = r.Header.Clone()
   739  	}))
   740  	defer srv.Close()
   741  	_, port, err := net.SplitHostPort(srv.Listener.Addr().String())
   742  	if err != nil {
   743  		t.Fatal(err)
   744  	}
   745  
   746  	recorder := &record.FakeRecorder{Events: make(chan string, 10)}
   747  
   748  	fakePodStatusProvider := stubPodStatusProvider("127.0.0.1")
   749  
   750  	handlerRunner := NewHandlerRunner(srv.Client(), &fakeContainerCommandRunner{}, fakePodStatusProvider, recorder).(*handlerRunner)
   751  
   752  	containerName := "containerFoo"
   753  	containerID := kubecontainer.ContainerID{Type: "test", ID: "abc1234"}
   754  	container := v1.Container{
   755  		Name: containerName,
   756  		Lifecycle: &v1.Lifecycle{
   757  			PostStart: &v1.LifecycleHandler{
   758  				HTTPGet: &v1.HTTPGetAction{
   759  					// set the scheme to https to ensure it falls back to HTTP.
   760  					Scheme: "https",
   761  					Host:   "127.0.0.1",
   762  					Port:   intstr.FromString(port),
   763  					Path:   "bar",
   764  					HTTPHeaders: []v1.HTTPHeader{
   765  						{
   766  							Name:  "Authorization",
   767  							Value: "secret",
   768  						},
   769  					},
   770  				},
   771  			},
   772  		},
   773  	}
   774  	pod := v1.Pod{}
   775  	pod.ObjectMeta.Name = "podFoo"
   776  	pod.ObjectMeta.Namespace = "nsFoo"
   777  	pod.Spec.Containers = []v1.Container{container}
   778  	msg, err := handlerRunner.Run(ctx, containerID, &pod, &container, container.Lifecycle.PostStart)
   779  
   780  	if err != nil {
   781  		t.Errorf("unexpected error: %v", err)
   782  	}
   783  	if msg != "" {
   784  		t.Errorf("unexpected error message: %q", msg)
   785  	}
   786  	if actualHeaders.Get("Authorization") != "" {
   787  		t.Error("unexpected Authorization header")
   788  	}
   789  
   790  	expectedMetrics := `
   791  # HELP kubelet_lifecycle_handler_http_fallbacks_total [ALPHA] The number of times lifecycle handlers successfully fell back to http from https.
   792  # TYPE kubelet_lifecycle_handler_http_fallbacks_total counter
   793  kubelet_lifecycle_handler_http_fallbacks_total 1
   794  `
   795  
   796  	if err := testutil.GatherAndCompare(legacyregistry.DefaultGatherer, strings.NewReader(expectedMetrics), "kubelet_lifecycle_handler_http_fallbacks_total"); err != nil {
   797  		t.Fatal(err)
   798  	}
   799  
   800  	select {
   801  	case event := <-recorder.Events:
   802  		if !strings.Contains(event, "LifecycleHTTPFallback") {
   803  			t.Fatalf("expected LifecycleHTTPFallback event, got %q", event)
   804  		}
   805  	default:
   806  		t.Fatal("no event recorded")
   807  	}
   808  }
   809  
   810  func TestIsHTTPResponseError(t *testing.T) {
   811  	s := httptest.NewServer(http.HandlerFunc(func(_ http.ResponseWriter, _ *http.Request) {}))
   812  	defer s.Close()
   813  	req, err := http.NewRequest("GET", s.URL, nil)
   814  	if err != nil {
   815  		t.Fatal(err)
   816  	}
   817  	req.URL.Scheme = "https"
   818  	_, err = http.DefaultClient.Do(req)
   819  	if !isHTTPResponseError(err) {
   820  		t.Errorf("unexpected http response error: %v", err)
   821  	}
   822  }
   823  
   824  func TestRunSleepHandler(t *testing.T) {
   825  	handlerRunner := NewHandlerRunner(&fakeHTTP{}, &fakeContainerCommandRunner{}, nil, nil)
   826  	containerID := kubecontainer.ContainerID{Type: "test", ID: "abc1234"}
   827  	containerName := "containerFoo"
   828  	container := v1.Container{
   829  		Name: containerName,
   830  		Lifecycle: &v1.Lifecycle{
   831  			PreStop: &v1.LifecycleHandler{},
   832  		},
   833  	}
   834  	pod := v1.Pod{}
   835  	pod.ObjectMeta.Name = "podFoo"
   836  	pod.ObjectMeta.Namespace = "nsFoo"
   837  	pod.Spec.Containers = []v1.Container{container}
   838  
   839  	tests := []struct {
   840  		name                          string
   841  		sleepSeconds                  int64
   842  		terminationGracePeriodSeconds int64
   843  		expectErr                     bool
   844  		expectedErr                   string
   845  	}{
   846  		{
   847  			name:                          "valid seconds",
   848  			sleepSeconds:                  5,
   849  			terminationGracePeriodSeconds: 30,
   850  		},
   851  		{
   852  			name:                          "longer than TerminationGracePeriodSeconds",
   853  			sleepSeconds:                  3,
   854  			terminationGracePeriodSeconds: 2,
   855  			expectErr:                     true,
   856  			expectedErr:                   "container terminated before sleep hook finished",
   857  		},
   858  	}
   859  
   860  	for _, tt := range tests {
   861  		t.Run(tt.name, func(t *testing.T) {
   862  			defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodLifecycleSleepAction, true)()
   863  
   864  			pod.Spec.Containers[0].Lifecycle.PreStop.Sleep = &v1.SleepAction{Seconds: tt.sleepSeconds}
   865  			ctx, cancel := context.WithTimeout(context.Background(), time.Duration(tt.terminationGracePeriodSeconds)*time.Second)
   866  			defer cancel()
   867  
   868  			_, err := handlerRunner.Run(ctx, containerID, &pod, &container, container.Lifecycle.PreStop)
   869  
   870  			if !tt.expectErr && err != nil {
   871  				t.Errorf("unexpected success")
   872  			}
   873  			if tt.expectErr && err.Error() != tt.expectedErr {
   874  				t.Errorf("%s: expected error want %s, got %s", tt.name, tt.expectedErr, err.Error())
   875  			}
   876  		})
   877  	}
   878  }
   879  

View as plain text