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  }