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  }