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