k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/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 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 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 }