k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/kubelet/kuberuntime/kuberuntime_container_test.go (about) 1 /* 2 Copyright 2016 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 kuberuntime 18 19 import ( 20 "context" 21 "os" 22 "path/filepath" 23 "regexp" 24 goruntime "runtime" 25 "strings" 26 "testing" 27 "time" 28 29 "github.com/google/go-cmp/cmp" 30 "github.com/stretchr/testify/assert" 31 "github.com/stretchr/testify/require" 32 "k8s.io/apimachinery/pkg/api/resource" 33 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 34 "k8s.io/apimachinery/pkg/types" 35 "k8s.io/apimachinery/pkg/util/intstr" 36 utilfeature "k8s.io/apiserver/pkg/util/feature" 37 featuregatetesting "k8s.io/component-base/featuregate/testing" 38 39 v1 "k8s.io/api/core/v1" 40 runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1" 41 42 "k8s.io/kubernetes/pkg/features" 43 kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" 44 containertest "k8s.io/kubernetes/pkg/kubelet/container/testing" 45 "k8s.io/kubernetes/pkg/kubelet/lifecycle" 46 ) 47 48 // TestRemoveContainer tests removing the container and its corresponding container logs. 49 func TestRemoveContainer(t *testing.T) { 50 ctx := context.Background() 51 fakeRuntime, _, m, err := createTestRuntimeManager() 52 require.NoError(t, err) 53 pod := &v1.Pod{ 54 ObjectMeta: metav1.ObjectMeta{ 55 UID: "12345678", 56 Name: "bar", 57 Namespace: "new", 58 }, 59 Spec: v1.PodSpec{ 60 Containers: []v1.Container{ 61 { 62 Name: "foo", 63 Image: "busybox", 64 ImagePullPolicy: v1.PullIfNotPresent, 65 }, 66 }, 67 }, 68 } 69 70 // Create fake sandbox and container 71 _, fakeContainers := makeAndSetFakePod(t, m, fakeRuntime, pod) 72 assert.Equal(t, len(fakeContainers), 1) 73 74 containerID := fakeContainers[0].Id 75 fakeOS := m.osInterface.(*containertest.FakeOS) 76 fakeOS.GlobFn = func(pattern, path string) bool { 77 pattern = strings.Replace(pattern, "*", ".*", -1) 78 pattern = strings.Replace(pattern, "\\", "\\\\", -1) 79 return regexp.MustCompile(pattern).MatchString(path) 80 } 81 podLogsDirectory := "/var/log/pods" 82 expectedContainerLogPath := filepath.Join(podLogsDirectory, "new_bar_12345678", "foo", "0.log") 83 expectedContainerLogPathRotated := filepath.Join(podLogsDirectory, "new_bar_12345678", "foo", "0.log.20060102-150405") 84 expectedContainerLogSymlink := legacyLogSymlink(containerID, "foo", "bar", "new") 85 86 fakeOS.Create(expectedContainerLogPath) 87 fakeOS.Create(expectedContainerLogPathRotated) 88 89 err = m.removeContainer(ctx, containerID) 90 assert.NoError(t, err) 91 92 // Verify container log is removed. 93 // We could not predict the order of `fakeOS.Removes`, so we use `assert.ElementsMatch` here. 94 assert.ElementsMatch(t, 95 []string{expectedContainerLogSymlink, expectedContainerLogPath, expectedContainerLogPathRotated}, 96 fakeOS.Removes) 97 // Verify container is removed 98 assert.Contains(t, fakeRuntime.Called, "RemoveContainer") 99 containers, err := fakeRuntime.ListContainers(ctx, &runtimeapi.ContainerFilter{Id: containerID}) 100 assert.NoError(t, err) 101 assert.Empty(t, containers) 102 } 103 104 // TestKillContainer tests killing the container in a Pod. 105 func TestKillContainer(t *testing.T) { 106 _, _, m, _ := createTestRuntimeManager() 107 108 tests := []struct { 109 caseName string 110 pod *v1.Pod 111 containerID kubecontainer.ContainerID 112 containerName string 113 reason string 114 gracePeriodOverride int64 115 succeed bool 116 }{ 117 { 118 caseName: "Failed to find container in pods, expect to return error", 119 pod: &v1.Pod{ 120 ObjectMeta: metav1.ObjectMeta{UID: "pod1_id", Name: "pod1", Namespace: "default"}, 121 Spec: v1.PodSpec{Containers: []v1.Container{{Name: "empty_container"}}}, 122 }, 123 containerID: kubecontainer.ContainerID{Type: "docker", ID: "not_exist_container_id"}, 124 containerName: "not_exist_container", 125 reason: "unknown reason", 126 gracePeriodOverride: 0, 127 succeed: false, 128 }, 129 } 130 131 for _, test := range tests { 132 ctx := context.Background() 133 err := m.killContainer(ctx, test.pod, test.containerID, test.containerName, test.reason, "", &test.gracePeriodOverride, nil) 134 if test.succeed != (err == nil) { 135 t.Errorf("%s: expected %v, got %v (%v)", test.caseName, test.succeed, (err == nil), err) 136 } 137 } 138 } 139 140 // TestToKubeContainerStatus tests the converting the CRI container status to 141 // the internal type (i.e., toKubeContainerStatus()) for containers in 142 // different states. 143 func TestToKubeContainerStatus(t *testing.T) { 144 cid := &kubecontainer.ContainerID{Type: "testRuntime", ID: "dummyid"} 145 meta := &runtimeapi.ContainerMetadata{Name: "cname", Attempt: 3} 146 imageSpec := &runtimeapi.ImageSpec{Image: "fimage"} 147 var ( 148 createdAt int64 = 327 149 startedAt int64 = 999 150 finishedAt int64 = 1278 151 ) 152 153 for desc, test := range map[string]struct { 154 input *runtimeapi.ContainerStatus 155 expected *kubecontainer.Status 156 }{ 157 "created container": { 158 input: &runtimeapi.ContainerStatus{ 159 Id: cid.ID, 160 Metadata: meta, 161 Image: imageSpec, 162 State: runtimeapi.ContainerState_CONTAINER_CREATED, 163 CreatedAt: createdAt, 164 }, 165 expected: &kubecontainer.Status{ 166 ID: *cid, 167 Image: imageSpec.Image, 168 State: kubecontainer.ContainerStateCreated, 169 CreatedAt: time.Unix(0, createdAt), 170 }, 171 }, 172 "running container": { 173 input: &runtimeapi.ContainerStatus{ 174 Id: cid.ID, 175 Metadata: meta, 176 Image: imageSpec, 177 State: runtimeapi.ContainerState_CONTAINER_RUNNING, 178 CreatedAt: createdAt, 179 StartedAt: startedAt, 180 }, 181 expected: &kubecontainer.Status{ 182 ID: *cid, 183 Image: imageSpec.Image, 184 State: kubecontainer.ContainerStateRunning, 185 CreatedAt: time.Unix(0, createdAt), 186 StartedAt: time.Unix(0, startedAt), 187 }, 188 }, 189 "exited container": { 190 input: &runtimeapi.ContainerStatus{ 191 Id: cid.ID, 192 Metadata: meta, 193 Image: imageSpec, 194 State: runtimeapi.ContainerState_CONTAINER_EXITED, 195 CreatedAt: createdAt, 196 StartedAt: startedAt, 197 FinishedAt: finishedAt, 198 ExitCode: int32(121), 199 Reason: "GotKilled", 200 Message: "The container was killed", 201 }, 202 expected: &kubecontainer.Status{ 203 ID: *cid, 204 Image: imageSpec.Image, 205 State: kubecontainer.ContainerStateExited, 206 CreatedAt: time.Unix(0, createdAt), 207 StartedAt: time.Unix(0, startedAt), 208 FinishedAt: time.Unix(0, finishedAt), 209 ExitCode: 121, 210 Reason: "GotKilled", 211 Message: "The container was killed", 212 }, 213 }, 214 "unknown container": { 215 input: &runtimeapi.ContainerStatus{ 216 Id: cid.ID, 217 Metadata: meta, 218 Image: imageSpec, 219 State: runtimeapi.ContainerState_CONTAINER_UNKNOWN, 220 CreatedAt: createdAt, 221 StartedAt: startedAt, 222 }, 223 expected: &kubecontainer.Status{ 224 ID: *cid, 225 Image: imageSpec.Image, 226 State: kubecontainer.ContainerStateUnknown, 227 CreatedAt: time.Unix(0, createdAt), 228 StartedAt: time.Unix(0, startedAt), 229 }, 230 }, 231 } { 232 actual := toKubeContainerStatus(test.input, cid.Type) 233 assert.Equal(t, test.expected, actual, desc) 234 } 235 } 236 237 // TestToKubeContainerStatusWithResources tests the converting the CRI container status to 238 // the internal type (i.e., toKubeContainerStatus()) for containers that returns Resources. 239 func TestToKubeContainerStatusWithResources(t *testing.T) { 240 featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.InPlacePodVerticalScaling, true) 241 cid := &kubecontainer.ContainerID{Type: "testRuntime", ID: "dummyid"} 242 meta := &runtimeapi.ContainerMetadata{Name: "cname", Attempt: 3} 243 imageSpec := &runtimeapi.ImageSpec{Image: "fimage"} 244 var ( 245 createdAt int64 = 327 246 startedAt int64 = 999 247 ) 248 249 for desc, test := range map[string]struct { 250 input *runtimeapi.ContainerStatus 251 expected *kubecontainer.Status 252 skipOnWindows bool 253 }{ 254 "container reporting cpu and memory": { 255 input: &runtimeapi.ContainerStatus{ 256 Id: cid.ID, 257 Metadata: meta, 258 Image: imageSpec, 259 State: runtimeapi.ContainerState_CONTAINER_RUNNING, 260 CreatedAt: createdAt, 261 StartedAt: startedAt, 262 Resources: func() *runtimeapi.ContainerResources { 263 if goruntime.GOOS == "windows" { 264 return &runtimeapi.ContainerResources{ 265 Windows: &runtimeapi.WindowsContainerResources{ 266 CpuMaximum: 2500, 267 CpuCount: 1, 268 MemoryLimitInBytes: 524288000, 269 }, 270 } 271 } 272 return &runtimeapi.ContainerResources{ 273 Linux: &runtimeapi.LinuxContainerResources{ 274 CpuQuota: 25000, 275 CpuPeriod: 100000, 276 MemoryLimitInBytes: 524288000, 277 OomScoreAdj: -998, 278 }, 279 } 280 }(), 281 }, 282 expected: &kubecontainer.Status{ 283 ID: *cid, 284 Image: imageSpec.Image, 285 State: kubecontainer.ContainerStateRunning, 286 CreatedAt: time.Unix(0, createdAt), 287 StartedAt: time.Unix(0, startedAt), 288 Resources: &kubecontainer.ContainerResources{ 289 CPULimit: resource.NewMilliQuantity(250, resource.DecimalSI), 290 MemoryLimit: resource.NewQuantity(524288000, resource.BinarySI), 291 }, 292 }, 293 skipOnWindows: true, 294 }, 295 "container reporting cpu only": { 296 input: &runtimeapi.ContainerStatus{ 297 Id: cid.ID, 298 Metadata: meta, 299 Image: imageSpec, 300 State: runtimeapi.ContainerState_CONTAINER_RUNNING, 301 CreatedAt: createdAt, 302 StartedAt: startedAt, 303 Resources: func() *runtimeapi.ContainerResources { 304 if goruntime.GOOS == "windows" { 305 return &runtimeapi.ContainerResources{ 306 Windows: &runtimeapi.WindowsContainerResources{ 307 CpuMaximum: 2500, 308 CpuCount: 2, 309 }, 310 } 311 } 312 return &runtimeapi.ContainerResources{ 313 Linux: &runtimeapi.LinuxContainerResources{ 314 CpuQuota: 50000, 315 CpuPeriod: 100000, 316 }, 317 } 318 }(), 319 }, 320 expected: &kubecontainer.Status{ 321 ID: *cid, 322 Image: imageSpec.Image, 323 State: kubecontainer.ContainerStateRunning, 324 CreatedAt: time.Unix(0, createdAt), 325 StartedAt: time.Unix(0, startedAt), 326 Resources: &kubecontainer.ContainerResources{ 327 CPULimit: resource.NewMilliQuantity(500, resource.DecimalSI), 328 }, 329 }, 330 }, 331 "container reporting memory only": { 332 input: &runtimeapi.ContainerStatus{ 333 Id: cid.ID, 334 Metadata: meta, 335 Image: imageSpec, 336 State: runtimeapi.ContainerState_CONTAINER_RUNNING, 337 CreatedAt: createdAt, 338 StartedAt: startedAt, 339 Resources: &runtimeapi.ContainerResources{ 340 Linux: &runtimeapi.LinuxContainerResources{ 341 MemoryLimitInBytes: 524288000, 342 OomScoreAdj: -998, 343 }, 344 Windows: &runtimeapi.WindowsContainerResources{ 345 MemoryLimitInBytes: 524288000, 346 }, 347 }, 348 }, 349 expected: &kubecontainer.Status{ 350 ID: *cid, 351 Image: imageSpec.Image, 352 State: kubecontainer.ContainerStateRunning, 353 CreatedAt: time.Unix(0, createdAt), 354 StartedAt: time.Unix(0, startedAt), 355 Resources: &kubecontainer.ContainerResources{ 356 MemoryLimit: resource.NewQuantity(524288000, resource.BinarySI), 357 }, 358 }, 359 }, 360 } { 361 t.Run(desc, func(t *testing.T) { 362 if test.skipOnWindows && goruntime.GOOS == "windows" { 363 // TODO: remove skip once the failing test has been fixed. 364 t.Skip("Skip failing test on Windows.") 365 } 366 actual := toKubeContainerStatus(test.input, cid.Type) 367 assert.Equal(t, test.expected, actual, desc) 368 }) 369 } 370 } 371 372 func testLifeCycleHook(t *testing.T, testPod *v1.Pod, testContainer *v1.Container) { 373 374 // Setup 375 fakeRuntime, _, m, _ := createTestRuntimeManager() 376 377 gracePeriod := int64(30) 378 cID := kubecontainer.ContainerID{ 379 Type: "docker", 380 ID: "foo", 381 } 382 383 cmdPostStart := &v1.Lifecycle{ 384 PostStart: &v1.LifecycleHandler{ 385 Exec: &v1.ExecAction{ 386 Command: []string{"PostStartCMD"}, 387 }, 388 }, 389 } 390 391 httpLifeCycle := &v1.Lifecycle{ 392 PreStop: &v1.LifecycleHandler{ 393 HTTPGet: &v1.HTTPGetAction{ 394 Host: "testHost.com", 395 Path: "/GracefulExit", 396 }, 397 }, 398 } 399 400 cmdLifeCycle := &v1.Lifecycle{ 401 PreStop: &v1.LifecycleHandler{ 402 Exec: &v1.ExecAction{ 403 Command: []string{"PreStopCMD"}, 404 }, 405 }, 406 } 407 408 fakeRunner := &containertest.FakeContainerCommandRunner{} 409 fakeHTTP := &fakeHTTP{} 410 fakePodStatusProvider := podStatusProviderFunc(func(uid types.UID, name, namespace string) (*kubecontainer.PodStatus, error) { 411 return &kubecontainer.PodStatus{ 412 ID: uid, 413 Name: name, 414 Namespace: namespace, 415 IPs: []string{ 416 "127.0.0.1", 417 }, 418 }, nil 419 }) 420 421 lcHanlder := lifecycle.NewHandlerRunner( 422 fakeHTTP, 423 fakeRunner, 424 fakePodStatusProvider, 425 nil) 426 427 m.runner = lcHanlder 428 429 // Configured and works as expected 430 t.Run("PreStop-CMDExec", func(t *testing.T) { 431 ctx := context.Background() 432 testContainer.Lifecycle = cmdLifeCycle 433 _ = m.killContainer(ctx, testPod, cID, "foo", "testKill", "", &gracePeriod, nil) 434 if fakeRunner.Cmd[0] != cmdLifeCycle.PreStop.Exec.Command[0] { 435 t.Errorf("CMD Prestop hook was not invoked") 436 } 437 }) 438 439 // Configured and working HTTP hook 440 t.Run("PreStop-HTTPGet", func(t *testing.T) { 441 t.Run("consistent", func(t *testing.T) { 442 ctx := context.Background() 443 defer func() { fakeHTTP.req = nil }() 444 httpLifeCycle.PreStop.HTTPGet.Port = intstr.FromInt32(80) 445 testContainer.Lifecycle = httpLifeCycle 446 _ = m.killContainer(ctx, testPod, cID, "foo", "testKill", "", &gracePeriod, nil) 447 if fakeHTTP.req == nil || !strings.Contains(fakeHTTP.req.URL.String(), httpLifeCycle.PreStop.HTTPGet.Host) { 448 t.Errorf("HTTP Prestop hook was not invoked") 449 } 450 }) 451 }) 452 453 // When there is no time to run PreStopHook 454 t.Run("PreStop-NoTimeToRun", func(t *testing.T) { 455 ctx := context.Background() 456 gracePeriodLocal := int64(0) 457 458 testPod.DeletionGracePeriodSeconds = &gracePeriodLocal 459 testPod.Spec.TerminationGracePeriodSeconds = &gracePeriodLocal 460 461 _ = m.killContainer(ctx, testPod, cID, "foo", "testKill", "", &gracePeriodLocal, nil) 462 if fakeHTTP.req != nil { 463 t.Errorf("HTTP Prestop hook Should not execute when gracePeriod is 0") 464 } 465 }) 466 467 // Post Start script 468 t.Run("PostStart-CmdExe", func(t *testing.T) { 469 ctx := context.Background() 470 // Fake all the things you need before trying to create a container 471 fakeSandBox, _ := makeAndSetFakePod(t, m, fakeRuntime, testPod) 472 fakeSandBoxConfig, _ := m.generatePodSandboxConfig(testPod, 0) 473 testContainer.Lifecycle = cmdPostStart 474 fakePodStatus := &kubecontainer.PodStatus{ 475 ContainerStatuses: []*kubecontainer.Status{ 476 { 477 ID: kubecontainer.ContainerID{ 478 Type: "docker", 479 ID: testContainer.Name, 480 }, 481 Name: testContainer.Name, 482 State: kubecontainer.ContainerStateCreated, 483 CreatedAt: time.Unix(0, time.Now().Unix()), 484 }, 485 }, 486 } 487 488 // Now try to create a container, which should in turn invoke PostStart Hook 489 _, err := m.startContainer(ctx, fakeSandBox.Id, fakeSandBoxConfig, containerStartSpec(testContainer), testPod, fakePodStatus, nil, "", []string{}) 490 if err != nil { 491 t.Errorf("startContainer error =%v", err) 492 } 493 if fakeRunner.Cmd[0] != cmdPostStart.PostStart.Exec.Command[0] { 494 t.Errorf("CMD PostStart hook was not invoked") 495 } 496 }) 497 } 498 499 func TestLifeCycleHook(t *testing.T) { 500 testPod := &v1.Pod{ 501 ObjectMeta: metav1.ObjectMeta{ 502 Name: "bar", 503 Namespace: "default", 504 }, 505 Spec: v1.PodSpec{ 506 Containers: []v1.Container{ 507 { 508 Name: "foo", 509 Image: "busybox", 510 ImagePullPolicy: v1.PullIfNotPresent, 511 Command: []string{"testCommand"}, 512 WorkingDir: "testWorkingDir", 513 }, 514 }, 515 }, 516 } 517 518 testLifeCycleHook(t, testPod, &testPod.Spec.Containers[0]) 519 } 520 521 func TestLifeCycleHookForRestartableInitContainer(t *testing.T) { 522 testPod := &v1.Pod{ 523 ObjectMeta: metav1.ObjectMeta{ 524 Name: "bar", 525 Namespace: "default", 526 }, 527 Spec: v1.PodSpec{ 528 InitContainers: []v1.Container{ 529 { 530 Name: "foo", 531 Image: "busybox", 532 ImagePullPolicy: v1.PullIfNotPresent, 533 Command: []string{"testCommand"}, 534 WorkingDir: "testWorkingDir", 535 RestartPolicy: &containerRestartPolicyAlways, 536 }, 537 }, 538 }, 539 } 540 541 testLifeCycleHook(t, testPod, &testPod.Spec.InitContainers[0]) 542 } 543 544 func TestStartSpec(t *testing.T) { 545 podStatus := &kubecontainer.PodStatus{ 546 ContainerStatuses: []*kubecontainer.Status{ 547 { 548 ID: kubecontainer.ContainerID{ 549 Type: "docker", 550 ID: "docker-something-something", 551 }, 552 Name: "target", 553 }, 554 }, 555 } 556 557 for _, tc := range []struct { 558 name string 559 spec *startSpec 560 want *kubecontainer.ContainerID 561 }{ 562 { 563 "Regular Container", 564 containerStartSpec(&v1.Container{ 565 Name: "test", 566 }), 567 nil, 568 }, 569 { 570 "Ephemeral Container w/o Target", 571 ephemeralContainerStartSpec(&v1.EphemeralContainer{ 572 EphemeralContainerCommon: v1.EphemeralContainerCommon{ 573 Name: "test", 574 }, 575 }), 576 nil, 577 }, 578 { 579 "Ephemeral Container w/ Target", 580 ephemeralContainerStartSpec(&v1.EphemeralContainer{ 581 EphemeralContainerCommon: v1.EphemeralContainerCommon{ 582 Name: "test", 583 }, 584 TargetContainerName: "target", 585 }), 586 &kubecontainer.ContainerID{ 587 Type: "docker", 588 ID: "docker-something-something", 589 }, 590 }, 591 } { 592 t.Run(tc.name, func(t *testing.T) { 593 if got, err := tc.spec.getTargetID(podStatus); err != nil { 594 t.Fatalf("%v: getTargetID got unexpected error: %v", t.Name(), err) 595 } else if diff := cmp.Diff(tc.want, got); diff != "" { 596 t.Errorf("%v: getTargetID got unexpected result. diff:\n%v", t.Name(), diff) 597 } 598 }) 599 } 600 } 601 602 func TestRestartCountByLogDir(t *testing.T) { 603 for _, tc := range []struct { 604 filenames []string 605 restartCount int 606 }{ 607 { 608 filenames: []string{"0.log.rotated-log"}, 609 restartCount: 1, 610 }, 611 { 612 filenames: []string{"0.log"}, 613 restartCount: 1, 614 }, 615 { 616 filenames: []string{"0.log", "1.log", "2.log"}, 617 restartCount: 3, 618 }, 619 { 620 filenames: []string{"0.log.rotated", "1.log", "2.log"}, 621 restartCount: 3, 622 }, 623 { 624 filenames: []string{"5.log.rotated", "6.log.rotated"}, 625 restartCount: 7, 626 }, 627 { 628 filenames: []string{"5.log.rotated", "6.log", "7.log"}, 629 restartCount: 8, 630 }, 631 // no restart count log files 632 { 633 filenames: []string{}, 634 restartCount: 0, 635 }, 636 { 637 filenames: []string{"a.log.rotated", "b.log.rotated", "12log.rotated"}, 638 restartCount: 0, 639 }, 640 // log extension twice 641 { 642 filenames: []string{"145.log.log.rotated"}, 643 restartCount: 146, 644 }, 645 // too big of the integer 646 { 647 filenames: []string{"92233720368547758089223372036854775808.log.rotated"}, 648 restartCount: 0, 649 }, 650 // mix of log files 651 { 652 filenames: []string{"9223372036854775808.log.rotated", "23.log", "23a.log", "1aaa.log.rotated", "2.log", "3.log.rotated"}, 653 restartCount: 24, 654 }, 655 // prefixed 656 { 657 filenames: []string{"rotated.23.log"}, 658 restartCount: 0, 659 }, 660 { 661 filenames: []string{"mylog42.log"}, 662 restartCount: 0, 663 }, 664 { 665 filenames: []string{"-42.log"}, 666 restartCount: 0, 667 }, 668 // same restart count multiple times 669 { 670 filenames: []string{"6.log", "6.log.rotated", "6.log.rotated.rotated"}, 671 restartCount: 7, 672 }, 673 } { 674 tempDirPath, err := os.MkdirTemp("", "test-restart-count-") 675 assert.NoError(t, err, "create tempdir error") 676 defer os.RemoveAll(tempDirPath) 677 for _, filename := range tc.filenames { 678 err = os.WriteFile(filepath.Join(tempDirPath, filename), []byte("a log line"), 0600) 679 assert.NoError(t, err, "could not write log file") 680 } 681 count, err := calcRestartCountByLogDir(tempDirPath) 682 if assert.NoError(t, err) { 683 assert.Equal(t, count, tc.restartCount, "count %v should equal restartCount %v", count, tc.restartCount) 684 } 685 } 686 } 687 688 func TestKillContainerGracePeriod(t *testing.T) { 689 690 shortGracePeriod := int64(10) 691 mediumGracePeriod := int64(30) 692 longGracePeriod := int64(60) 693 694 tests := []struct { 695 name string 696 pod *v1.Pod 697 reason containerKillReason 698 expectedGracePeriod int64 699 }{ 700 { 701 name: "default termination grace period", 702 pod: &v1.Pod{ 703 Spec: v1.PodSpec{Containers: []v1.Container{{Name: "foo"}}}, 704 }, 705 reason: reasonUnknown, 706 expectedGracePeriod: int64(2), 707 }, 708 { 709 name: "use pod termination grace period", 710 pod: &v1.Pod{ 711 Spec: v1.PodSpec{ 712 Containers: []v1.Container{{Name: "foo"}}, 713 TerminationGracePeriodSeconds: &longGracePeriod, 714 }, 715 }, 716 reason: reasonUnknown, 717 expectedGracePeriod: longGracePeriod, 718 }, 719 { 720 name: "liveness probe overrides pod termination grace period", 721 pod: &v1.Pod{ 722 Spec: v1.PodSpec{ 723 Containers: []v1.Container{{ 724 Name: "foo", LivenessProbe: &v1.Probe{TerminationGracePeriodSeconds: &shortGracePeriod}, 725 }}, 726 TerminationGracePeriodSeconds: &longGracePeriod, 727 }, 728 }, 729 reason: reasonLivenessProbe, 730 expectedGracePeriod: shortGracePeriod, 731 }, 732 { 733 name: "startup probe overrides pod termination grace period", 734 pod: &v1.Pod{ 735 Spec: v1.PodSpec{ 736 Containers: []v1.Container{{ 737 Name: "foo", StartupProbe: &v1.Probe{TerminationGracePeriodSeconds: &shortGracePeriod}, 738 }}, 739 TerminationGracePeriodSeconds: &longGracePeriod, 740 }, 741 }, 742 reason: reasonStartupProbe, 743 expectedGracePeriod: shortGracePeriod, 744 }, 745 { 746 name: "startup probe overrides pod termination grace period, probe period > pod period", 747 pod: &v1.Pod{ 748 Spec: v1.PodSpec{ 749 Containers: []v1.Container{{ 750 Name: "foo", StartupProbe: &v1.Probe{TerminationGracePeriodSeconds: &longGracePeriod}, 751 }}, 752 TerminationGracePeriodSeconds: &shortGracePeriod, 753 }, 754 }, 755 reason: reasonStartupProbe, 756 expectedGracePeriod: longGracePeriod, 757 }, 758 { 759 name: "liveness probe overrides pod termination grace period, probe period > pod period", 760 pod: &v1.Pod{ 761 Spec: v1.PodSpec{ 762 Containers: []v1.Container{{ 763 Name: "foo", LivenessProbe: &v1.Probe{TerminationGracePeriodSeconds: &longGracePeriod}, 764 }}, 765 TerminationGracePeriodSeconds: &shortGracePeriod, 766 }, 767 }, 768 reason: reasonLivenessProbe, 769 expectedGracePeriod: longGracePeriod, 770 }, 771 { 772 name: "non-liveness probe failure, use pod termination grace period", 773 pod: &v1.Pod{ 774 Spec: v1.PodSpec{ 775 Containers: []v1.Container{{ 776 Name: "foo", LivenessProbe: &v1.Probe{TerminationGracePeriodSeconds: &shortGracePeriod}, 777 }}, 778 TerminationGracePeriodSeconds: &longGracePeriod, 779 }, 780 }, 781 reason: reasonUnknown, 782 expectedGracePeriod: longGracePeriod, 783 }, 784 { 785 name: "non-startup probe failure, use pod termination grace period", 786 pod: &v1.Pod{ 787 Spec: v1.PodSpec{ 788 Containers: []v1.Container{{ 789 Name: "foo", StartupProbe: &v1.Probe{TerminationGracePeriodSeconds: &shortGracePeriod}, 790 }}, 791 TerminationGracePeriodSeconds: &longGracePeriod, 792 }, 793 }, 794 reason: reasonUnknown, 795 expectedGracePeriod: longGracePeriod, 796 }, 797 { 798 name: "all three grace periods set, use pod termination grace period", 799 pod: &v1.Pod{ 800 Spec: v1.PodSpec{ 801 Containers: []v1.Container{{ 802 Name: "foo", 803 StartupProbe: &v1.Probe{TerminationGracePeriodSeconds: &shortGracePeriod}, 804 LivenessProbe: &v1.Probe{TerminationGracePeriodSeconds: &mediumGracePeriod}, 805 }}, 806 TerminationGracePeriodSeconds: &longGracePeriod, 807 }, 808 }, 809 reason: reasonUnknown, 810 expectedGracePeriod: longGracePeriod, 811 }, 812 { 813 name: "all three grace periods set, use startup termination grace period", 814 pod: &v1.Pod{ 815 Spec: v1.PodSpec{ 816 Containers: []v1.Container{{ 817 Name: "foo", 818 StartupProbe: &v1.Probe{TerminationGracePeriodSeconds: &shortGracePeriod}, 819 LivenessProbe: &v1.Probe{TerminationGracePeriodSeconds: &mediumGracePeriod}, 820 }}, 821 TerminationGracePeriodSeconds: &longGracePeriod, 822 }, 823 }, 824 reason: reasonStartupProbe, 825 expectedGracePeriod: shortGracePeriod, 826 }, 827 { 828 name: "all three grace periods set, use liveness termination grace period", 829 pod: &v1.Pod{ 830 Spec: v1.PodSpec{ 831 Containers: []v1.Container{{ 832 Name: "foo", 833 StartupProbe: &v1.Probe{TerminationGracePeriodSeconds: &shortGracePeriod}, 834 LivenessProbe: &v1.Probe{TerminationGracePeriodSeconds: &mediumGracePeriod}, 835 }}, 836 TerminationGracePeriodSeconds: &longGracePeriod, 837 }, 838 }, 839 reason: reasonLivenessProbe, 840 expectedGracePeriod: mediumGracePeriod, 841 }, 842 } 843 844 for _, test := range tests { 845 t.Run(test.name, func(t *testing.T) { 846 actualGracePeriod := setTerminationGracePeriod(test.pod, &test.pod.Spec.Containers[0], "", kubecontainer.ContainerID{}, test.reason) 847 require.Equal(t, test.expectedGracePeriod, actualGracePeriod) 848 }) 849 } 850 } 851 852 // TestUpdateContainerResources tests updating a container in a Pod. 853 func TestUpdateContainerResources(t *testing.T) { 854 fakeRuntime, _, m, errCreate := createTestRuntimeManager() 855 require.NoError(t, errCreate) 856 pod := &v1.Pod{ 857 ObjectMeta: metav1.ObjectMeta{ 858 UID: "12345678", 859 Name: "bar", 860 Namespace: "new", 861 }, 862 Spec: v1.PodSpec{ 863 Containers: []v1.Container{ 864 { 865 Name: "foo", 866 Image: "busybox", 867 ImagePullPolicy: v1.PullIfNotPresent, 868 }, 869 }, 870 }, 871 } 872 873 // Create fake sandbox and container 874 _, fakeContainers := makeAndSetFakePod(t, m, fakeRuntime, pod) 875 assert.Equal(t, len(fakeContainers), 1) 876 877 ctx := context.Background() 878 cStatus, err := m.getPodContainerStatuses(ctx, pod.UID, pod.Name, pod.Namespace) 879 assert.NoError(t, err) 880 containerID := cStatus[0].ID 881 882 err = m.updateContainerResources(pod, &pod.Spec.Containers[0], containerID) 883 assert.NoError(t, err) 884 885 // Verify container is updated 886 assert.Contains(t, fakeRuntime.Called, "UpdateContainerResources") 887 }