1
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
731
732
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
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