...

Source file src/k8s.io/kubernetes/pkg/kubelet/kuberuntime/kuberuntime_container_test.go

Documentation: k8s.io/kubernetes/pkg/kubelet/kuberuntime

     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  	defer 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  	}{
   253  		"container reporting cpu and memory": {
   254  			input: &runtimeapi.ContainerStatus{
   255  				Id:        cid.ID,
   256  				Metadata:  meta,
   257  				Image:     imageSpec,
   258  				State:     runtimeapi.ContainerState_CONTAINER_RUNNING,
   259  				CreatedAt: createdAt,
   260  				StartedAt: startedAt,
   261  				Resources: func() *runtimeapi.ContainerResources {
   262  					if goruntime.GOOS == "windows" {
   263  						return &runtimeapi.ContainerResources{
   264  							Windows: &runtimeapi.WindowsContainerResources{
   265  								CpuMaximum:         2500,
   266  								CpuCount:           1,
   267  								MemoryLimitInBytes: 524288000,
   268  							},
   269  						}
   270  					}
   271  					return &runtimeapi.ContainerResources{
   272  						Linux: &runtimeapi.LinuxContainerResources{
   273  							CpuQuota:           25000,
   274  							CpuPeriod:          100000,
   275  							MemoryLimitInBytes: 524288000,
   276  							OomScoreAdj:        -998,
   277  						},
   278  					}
   279  				}(),
   280  			},
   281  			expected: &kubecontainer.Status{
   282  				ID:        *cid,
   283  				Image:     imageSpec.Image,
   284  				State:     kubecontainer.ContainerStateRunning,
   285  				CreatedAt: time.Unix(0, createdAt),
   286  				StartedAt: time.Unix(0, startedAt),
   287  				Resources: &kubecontainer.ContainerResources{
   288  					CPULimit:    resource.NewMilliQuantity(250, resource.DecimalSI),
   289  					MemoryLimit: resource.NewQuantity(524288000, resource.BinarySI),
   290  				},
   291  			},
   292  		},
   293  		"container reporting cpu only": {
   294  			input: &runtimeapi.ContainerStatus{
   295  				Id:        cid.ID,
   296  				Metadata:  meta,
   297  				Image:     imageSpec,
   298  				State:     runtimeapi.ContainerState_CONTAINER_RUNNING,
   299  				CreatedAt: createdAt,
   300  				StartedAt: startedAt,
   301  				Resources: func() *runtimeapi.ContainerResources {
   302  					if goruntime.GOOS == "windows" {
   303  						return &runtimeapi.ContainerResources{
   304  							Windows: &runtimeapi.WindowsContainerResources{
   305  								CpuMaximum: 2500,
   306  								CpuCount:   2,
   307  							},
   308  						}
   309  					}
   310  					return &runtimeapi.ContainerResources{
   311  						Linux: &runtimeapi.LinuxContainerResources{
   312  							CpuQuota:  50000,
   313  							CpuPeriod: 100000,
   314  						},
   315  					}
   316  				}(),
   317  			},
   318  			expected: &kubecontainer.Status{
   319  				ID:        *cid,
   320  				Image:     imageSpec.Image,
   321  				State:     kubecontainer.ContainerStateRunning,
   322  				CreatedAt: time.Unix(0, createdAt),
   323  				StartedAt: time.Unix(0, startedAt),
   324  				Resources: &kubecontainer.ContainerResources{
   325  					CPULimit: resource.NewMilliQuantity(500, resource.DecimalSI),
   326  				},
   327  			},
   328  		},
   329  		"container reporting memory only": {
   330  			input: &runtimeapi.ContainerStatus{
   331  				Id:        cid.ID,
   332  				Metadata:  meta,
   333  				Image:     imageSpec,
   334  				State:     runtimeapi.ContainerState_CONTAINER_RUNNING,
   335  				CreatedAt: createdAt,
   336  				StartedAt: startedAt,
   337  				Resources: &runtimeapi.ContainerResources{
   338  					Linux: &runtimeapi.LinuxContainerResources{
   339  						MemoryLimitInBytes: 524288000,
   340  						OomScoreAdj:        -998,
   341  					},
   342  					Windows: &runtimeapi.WindowsContainerResources{
   343  						MemoryLimitInBytes: 524288000,
   344  					},
   345  				},
   346  			},
   347  			expected: &kubecontainer.Status{
   348  				ID:        *cid,
   349  				Image:     imageSpec.Image,
   350  				State:     kubecontainer.ContainerStateRunning,
   351  				CreatedAt: time.Unix(0, createdAt),
   352  				StartedAt: time.Unix(0, startedAt),
   353  				Resources: &kubecontainer.ContainerResources{
   354  					MemoryLimit: resource.NewQuantity(524288000, resource.BinarySI),
   355  				},
   356  			},
   357  		},
   358  	} {
   359  		t.Run(desc, func(t *testing.T) {
   360  			actual := toKubeContainerStatus(test.input, cid.Type)
   361  			assert.Equal(t, test.expected, actual, desc)
   362  		})
   363  	}
   364  }
   365  
   366  func testLifeCycleHook(t *testing.T, testPod *v1.Pod, testContainer *v1.Container) {
   367  
   368  	// Setup
   369  	fakeRuntime, _, m, _ := createTestRuntimeManager()
   370  
   371  	gracePeriod := int64(30)
   372  	cID := kubecontainer.ContainerID{
   373  		Type: "docker",
   374  		ID:   "foo",
   375  	}
   376  
   377  	cmdPostStart := &v1.Lifecycle{
   378  		PostStart: &v1.LifecycleHandler{
   379  			Exec: &v1.ExecAction{
   380  				Command: []string{"PostStartCMD"},
   381  			},
   382  		},
   383  	}
   384  
   385  	httpLifeCycle := &v1.Lifecycle{
   386  		PreStop: &v1.LifecycleHandler{
   387  			HTTPGet: &v1.HTTPGetAction{
   388  				Host: "testHost.com",
   389  				Path: "/GracefulExit",
   390  			},
   391  		},
   392  	}
   393  
   394  	cmdLifeCycle := &v1.Lifecycle{
   395  		PreStop: &v1.LifecycleHandler{
   396  			Exec: &v1.ExecAction{
   397  				Command: []string{"PreStopCMD"},
   398  			},
   399  		},
   400  	}
   401  
   402  	fakeRunner := &containertest.FakeContainerCommandRunner{}
   403  	fakeHTTP := &fakeHTTP{}
   404  	fakePodStatusProvider := podStatusProviderFunc(func(uid types.UID, name, namespace string) (*kubecontainer.PodStatus, error) {
   405  		return &kubecontainer.PodStatus{
   406  			ID:        uid,
   407  			Name:      name,
   408  			Namespace: namespace,
   409  			IPs: []string{
   410  				"127.0.0.1",
   411  			},
   412  		}, nil
   413  	})
   414  
   415  	lcHanlder := lifecycle.NewHandlerRunner(
   416  		fakeHTTP,
   417  		fakeRunner,
   418  		fakePodStatusProvider,
   419  		nil)
   420  
   421  	m.runner = lcHanlder
   422  
   423  	// Configured and works as expected
   424  	t.Run("PreStop-CMDExec", func(t *testing.T) {
   425  		ctx := context.Background()
   426  		testContainer.Lifecycle = cmdLifeCycle
   427  		_ = m.killContainer(ctx, testPod, cID, "foo", "testKill", "", &gracePeriod, nil)
   428  		if fakeRunner.Cmd[0] != cmdLifeCycle.PreStop.Exec.Command[0] {
   429  			t.Errorf("CMD Prestop hook was not invoked")
   430  		}
   431  	})
   432  
   433  	// Configured and working HTTP hook
   434  	t.Run("PreStop-HTTPGet", func(t *testing.T) {
   435  		t.Run("consistent", func(t *testing.T) {
   436  			ctx := context.Background()
   437  			defer func() { fakeHTTP.req = nil }()
   438  			httpLifeCycle.PreStop.HTTPGet.Port = intstr.FromInt32(80)
   439  			testContainer.Lifecycle = httpLifeCycle
   440  			_ = m.killContainer(ctx, testPod, cID, "foo", "testKill", "", &gracePeriod, nil)
   441  			if fakeHTTP.req == nil || !strings.Contains(fakeHTTP.req.URL.String(), httpLifeCycle.PreStop.HTTPGet.Host) {
   442  				t.Errorf("HTTP Prestop hook was not invoked")
   443  			}
   444  		})
   445  	})
   446  
   447  	// When there is no time to run PreStopHook
   448  	t.Run("PreStop-NoTimeToRun", func(t *testing.T) {
   449  		ctx := context.Background()
   450  		gracePeriodLocal := int64(0)
   451  
   452  		testPod.DeletionGracePeriodSeconds = &gracePeriodLocal
   453  		testPod.Spec.TerminationGracePeriodSeconds = &gracePeriodLocal
   454  
   455  		_ = m.killContainer(ctx, testPod, cID, "foo", "testKill", "", &gracePeriodLocal, nil)
   456  		if fakeHTTP.req != nil {
   457  			t.Errorf("HTTP Prestop hook Should not execute when gracePeriod is 0")
   458  		}
   459  	})
   460  
   461  	// Post Start script
   462  	t.Run("PostStart-CmdExe", func(t *testing.T) {
   463  		ctx := context.Background()
   464  		// Fake all the things you need before trying to create a container
   465  		fakeSandBox, _ := makeAndSetFakePod(t, m, fakeRuntime, testPod)
   466  		fakeSandBoxConfig, _ := m.generatePodSandboxConfig(testPod, 0)
   467  		testContainer.Lifecycle = cmdPostStart
   468  		fakePodStatus := &kubecontainer.PodStatus{
   469  			ContainerStatuses: []*kubecontainer.Status{
   470  				{
   471  					ID: kubecontainer.ContainerID{
   472  						Type: "docker",
   473  						ID:   testContainer.Name,
   474  					},
   475  					Name:      testContainer.Name,
   476  					State:     kubecontainer.ContainerStateCreated,
   477  					CreatedAt: time.Unix(0, time.Now().Unix()),
   478  				},
   479  			},
   480  		}
   481  
   482  		// Now try to create a container, which should in turn invoke PostStart Hook
   483  		_, err := m.startContainer(ctx, fakeSandBox.Id, fakeSandBoxConfig, containerStartSpec(testContainer), testPod, fakePodStatus, nil, "", []string{})
   484  		if err != nil {
   485  			t.Errorf("startContainer error =%v", err)
   486  		}
   487  		if fakeRunner.Cmd[0] != cmdPostStart.PostStart.Exec.Command[0] {
   488  			t.Errorf("CMD PostStart hook was not invoked")
   489  		}
   490  	})
   491  }
   492  
   493  func TestLifeCycleHook(t *testing.T) {
   494  	testPod := &v1.Pod{
   495  		ObjectMeta: metav1.ObjectMeta{
   496  			Name:      "bar",
   497  			Namespace: "default",
   498  		},
   499  		Spec: v1.PodSpec{
   500  			Containers: []v1.Container{
   501  				{
   502  					Name:            "foo",
   503  					Image:           "busybox",
   504  					ImagePullPolicy: v1.PullIfNotPresent,
   505  					Command:         []string{"testCommand"},
   506  					WorkingDir:      "testWorkingDir",
   507  				},
   508  			},
   509  		},
   510  	}
   511  
   512  	testLifeCycleHook(t, testPod, &testPod.Spec.Containers[0])
   513  }
   514  
   515  func TestLifeCycleHookForRestartableInitContainer(t *testing.T) {
   516  	testPod := &v1.Pod{
   517  		ObjectMeta: metav1.ObjectMeta{
   518  			Name:      "bar",
   519  			Namespace: "default",
   520  		},
   521  		Spec: v1.PodSpec{
   522  			InitContainers: []v1.Container{
   523  				{
   524  					Name:            "foo",
   525  					Image:           "busybox",
   526  					ImagePullPolicy: v1.PullIfNotPresent,
   527  					Command:         []string{"testCommand"},
   528  					WorkingDir:      "testWorkingDir",
   529  					RestartPolicy:   &containerRestartPolicyAlways,
   530  				},
   531  			},
   532  		},
   533  	}
   534  
   535  	testLifeCycleHook(t, testPod, &testPod.Spec.InitContainers[0])
   536  }
   537  
   538  func TestStartSpec(t *testing.T) {
   539  	podStatus := &kubecontainer.PodStatus{
   540  		ContainerStatuses: []*kubecontainer.Status{
   541  			{
   542  				ID: kubecontainer.ContainerID{
   543  					Type: "docker",
   544  					ID:   "docker-something-something",
   545  				},
   546  				Name: "target",
   547  			},
   548  		},
   549  	}
   550  
   551  	for _, tc := range []struct {
   552  		name string
   553  		spec *startSpec
   554  		want *kubecontainer.ContainerID
   555  	}{
   556  		{
   557  			"Regular Container",
   558  			containerStartSpec(&v1.Container{
   559  				Name: "test",
   560  			}),
   561  			nil,
   562  		},
   563  		{
   564  			"Ephemeral Container w/o Target",
   565  			ephemeralContainerStartSpec(&v1.EphemeralContainer{
   566  				EphemeralContainerCommon: v1.EphemeralContainerCommon{
   567  					Name: "test",
   568  				},
   569  			}),
   570  			nil,
   571  		},
   572  		{
   573  			"Ephemeral Container w/ Target",
   574  			ephemeralContainerStartSpec(&v1.EphemeralContainer{
   575  				EphemeralContainerCommon: v1.EphemeralContainerCommon{
   576  					Name: "test",
   577  				},
   578  				TargetContainerName: "target",
   579  			}),
   580  			&kubecontainer.ContainerID{
   581  				Type: "docker",
   582  				ID:   "docker-something-something",
   583  			},
   584  		},
   585  	} {
   586  		t.Run(tc.name, func(t *testing.T) {
   587  			if got, err := tc.spec.getTargetID(podStatus); err != nil {
   588  				t.Fatalf("%v: getTargetID got unexpected error: %v", t.Name(), err)
   589  			} else if diff := cmp.Diff(tc.want, got); diff != "" {
   590  				t.Errorf("%v: getTargetID got unexpected result. diff:\n%v", t.Name(), diff)
   591  			}
   592  		})
   593  	}
   594  }
   595  
   596  func TestRestartCountByLogDir(t *testing.T) {
   597  	for _, tc := range []struct {
   598  		filenames    []string
   599  		restartCount int
   600  	}{
   601  		{
   602  			filenames:    []string{"0.log.rotated-log"},
   603  			restartCount: 1,
   604  		},
   605  		{
   606  			filenames:    []string{"0.log"},
   607  			restartCount: 1,
   608  		},
   609  		{
   610  			filenames:    []string{"0.log", "1.log", "2.log"},
   611  			restartCount: 3,
   612  		},
   613  		{
   614  			filenames:    []string{"0.log.rotated", "1.log", "2.log"},
   615  			restartCount: 3,
   616  		},
   617  		{
   618  			filenames:    []string{"5.log.rotated", "6.log.rotated"},
   619  			restartCount: 7,
   620  		},
   621  		{
   622  			filenames:    []string{"5.log.rotated", "6.log", "7.log"},
   623  			restartCount: 8,
   624  		},
   625  		// no restart count log files
   626  		{
   627  			filenames:    []string{},
   628  			restartCount: 0,
   629  		},
   630  		{
   631  			filenames:    []string{"a.log.rotated", "b.log.rotated", "12log.rotated"},
   632  			restartCount: 0,
   633  		},
   634  		// log extension twice
   635  		{
   636  			filenames:    []string{"145.log.log.rotated"},
   637  			restartCount: 146,
   638  		},
   639  		// too big of the integer
   640  		{
   641  			filenames:    []string{"92233720368547758089223372036854775808.log.rotated"},
   642  			restartCount: 0,
   643  		},
   644  		// mix of log files
   645  		{
   646  			filenames:    []string{"9223372036854775808.log.rotated", "23.log", "23a.log", "1aaa.log.rotated", "2.log", "3.log.rotated"},
   647  			restartCount: 24,
   648  		},
   649  		// prefixed
   650  		{
   651  			filenames:    []string{"rotated.23.log"},
   652  			restartCount: 0,
   653  		},
   654  		{
   655  			filenames:    []string{"mylog42.log"},
   656  			restartCount: 0,
   657  		},
   658  		{
   659  			filenames:    []string{"-42.log"},
   660  			restartCount: 0,
   661  		},
   662  		// same restart count multiple times
   663  		{
   664  			filenames:    []string{"6.log", "6.log.rotated", "6.log.rotated.rotated"},
   665  			restartCount: 7,
   666  		},
   667  	} {
   668  		tempDirPath, err := os.MkdirTemp("", "test-restart-count-")
   669  		assert.NoError(t, err, "create tempdir error")
   670  		defer os.RemoveAll(tempDirPath)
   671  		for _, filename := range tc.filenames {
   672  			err = os.WriteFile(filepath.Join(tempDirPath, filename), []byte("a log line"), 0600)
   673  			assert.NoError(t, err, "could not write log file")
   674  		}
   675  		count, err := calcRestartCountByLogDir(tempDirPath)
   676  		if assert.NoError(t, err) {
   677  			assert.Equal(t, count, tc.restartCount, "count %v should equal restartCount %v", count, tc.restartCount)
   678  		}
   679  	}
   680  }
   681  
   682  func TestKillContainerGracePeriod(t *testing.T) {
   683  
   684  	shortGracePeriod := int64(10)
   685  	mediumGracePeriod := int64(30)
   686  	longGracePeriod := int64(60)
   687  
   688  	tests := []struct {
   689  		name                string
   690  		pod                 *v1.Pod
   691  		reason              containerKillReason
   692  		expectedGracePeriod int64
   693  	}{
   694  		{
   695  			name: "default termination grace period",
   696  			pod: &v1.Pod{
   697  				Spec: v1.PodSpec{Containers: []v1.Container{{Name: "foo"}}},
   698  			},
   699  			reason:              reasonUnknown,
   700  			expectedGracePeriod: int64(2),
   701  		},
   702  		{
   703  			name: "use pod termination grace period",
   704  			pod: &v1.Pod{
   705  				Spec: v1.PodSpec{
   706  					Containers:                    []v1.Container{{Name: "foo"}},
   707  					TerminationGracePeriodSeconds: &longGracePeriod,
   708  				},
   709  			},
   710  			reason:              reasonUnknown,
   711  			expectedGracePeriod: longGracePeriod,
   712  		},
   713  		{
   714  			name: "liveness probe overrides pod termination grace period",
   715  			pod: &v1.Pod{
   716  				Spec: v1.PodSpec{
   717  					Containers: []v1.Container{{
   718  						Name: "foo", LivenessProbe: &v1.Probe{TerminationGracePeriodSeconds: &shortGracePeriod},
   719  					}},
   720  					TerminationGracePeriodSeconds: &longGracePeriod,
   721  				},
   722  			},
   723  			reason:              reasonLivenessProbe,
   724  			expectedGracePeriod: shortGracePeriod,
   725  		},
   726  		{
   727  			name: "startup probe overrides pod termination grace period",
   728  			pod: &v1.Pod{
   729  				Spec: v1.PodSpec{
   730  					Containers: []v1.Container{{
   731  						Name: "foo", StartupProbe: &v1.Probe{TerminationGracePeriodSeconds: &shortGracePeriod},
   732  					}},
   733  					TerminationGracePeriodSeconds: &longGracePeriod,
   734  				},
   735  			},
   736  			reason:              reasonStartupProbe,
   737  			expectedGracePeriod: shortGracePeriod,
   738  		},
   739  		{
   740  			name: "startup probe overrides pod termination grace period, probe period > pod period",
   741  			pod: &v1.Pod{
   742  				Spec: v1.PodSpec{
   743  					Containers: []v1.Container{{
   744  						Name: "foo", StartupProbe: &v1.Probe{TerminationGracePeriodSeconds: &longGracePeriod},
   745  					}},
   746  					TerminationGracePeriodSeconds: &shortGracePeriod,
   747  				},
   748  			},
   749  			reason:              reasonStartupProbe,
   750  			expectedGracePeriod: longGracePeriod,
   751  		},
   752  		{
   753  			name: "liveness probe overrides pod termination grace period, probe period > pod period",
   754  			pod: &v1.Pod{
   755  				Spec: v1.PodSpec{
   756  					Containers: []v1.Container{{
   757  						Name: "foo", LivenessProbe: &v1.Probe{TerminationGracePeriodSeconds: &longGracePeriod},
   758  					}},
   759  					TerminationGracePeriodSeconds: &shortGracePeriod,
   760  				},
   761  			},
   762  			reason:              reasonLivenessProbe,
   763  			expectedGracePeriod: longGracePeriod,
   764  		},
   765  		{
   766  			name: "non-liveness probe failure, use pod termination grace period",
   767  			pod: &v1.Pod{
   768  				Spec: v1.PodSpec{
   769  					Containers: []v1.Container{{
   770  						Name: "foo", LivenessProbe: &v1.Probe{TerminationGracePeriodSeconds: &shortGracePeriod},
   771  					}},
   772  					TerminationGracePeriodSeconds: &longGracePeriod,
   773  				},
   774  			},
   775  			reason:              reasonUnknown,
   776  			expectedGracePeriod: longGracePeriod,
   777  		},
   778  		{
   779  			name: "non-startup probe failure, use pod termination grace period",
   780  			pod: &v1.Pod{
   781  				Spec: v1.PodSpec{
   782  					Containers: []v1.Container{{
   783  						Name: "foo", StartupProbe: &v1.Probe{TerminationGracePeriodSeconds: &shortGracePeriod},
   784  					}},
   785  					TerminationGracePeriodSeconds: &longGracePeriod,
   786  				},
   787  			},
   788  			reason:              reasonUnknown,
   789  			expectedGracePeriod: longGracePeriod,
   790  		},
   791  		{
   792  			name: "all three grace periods set, use pod termination grace period",
   793  			pod: &v1.Pod{
   794  				Spec: v1.PodSpec{
   795  					Containers: []v1.Container{{
   796  						Name:          "foo",
   797  						StartupProbe:  &v1.Probe{TerminationGracePeriodSeconds: &shortGracePeriod},
   798  						LivenessProbe: &v1.Probe{TerminationGracePeriodSeconds: &mediumGracePeriod},
   799  					}},
   800  					TerminationGracePeriodSeconds: &longGracePeriod,
   801  				},
   802  			},
   803  			reason:              reasonUnknown,
   804  			expectedGracePeriod: longGracePeriod,
   805  		},
   806  		{
   807  			name: "all three grace periods set, use startup termination grace period",
   808  			pod: &v1.Pod{
   809  				Spec: v1.PodSpec{
   810  					Containers: []v1.Container{{
   811  						Name:          "foo",
   812  						StartupProbe:  &v1.Probe{TerminationGracePeriodSeconds: &shortGracePeriod},
   813  						LivenessProbe: &v1.Probe{TerminationGracePeriodSeconds: &mediumGracePeriod},
   814  					}},
   815  					TerminationGracePeriodSeconds: &longGracePeriod,
   816  				},
   817  			},
   818  			reason:              reasonStartupProbe,
   819  			expectedGracePeriod: shortGracePeriod,
   820  		},
   821  		{
   822  			name: "all three grace periods set, use liveness termination grace period",
   823  			pod: &v1.Pod{
   824  				Spec: v1.PodSpec{
   825  					Containers: []v1.Container{{
   826  						Name:          "foo",
   827  						StartupProbe:  &v1.Probe{TerminationGracePeriodSeconds: &shortGracePeriod},
   828  						LivenessProbe: &v1.Probe{TerminationGracePeriodSeconds: &mediumGracePeriod},
   829  					}},
   830  					TerminationGracePeriodSeconds: &longGracePeriod,
   831  				},
   832  			},
   833  			reason:              reasonLivenessProbe,
   834  			expectedGracePeriod: mediumGracePeriod,
   835  		},
   836  	}
   837  
   838  	for _, test := range tests {
   839  		t.Run(test.name, func(t *testing.T) {
   840  			actualGracePeriod := setTerminationGracePeriod(test.pod, &test.pod.Spec.Containers[0], "", kubecontainer.ContainerID{}, test.reason)
   841  			require.Equal(t, test.expectedGracePeriod, actualGracePeriod)
   842  		})
   843  	}
   844  }
   845  
   846  // TestUpdateContainerResources tests updating a container in a Pod.
   847  func TestUpdateContainerResources(t *testing.T) {
   848  	fakeRuntime, _, m, errCreate := createTestRuntimeManager()
   849  	require.NoError(t, errCreate)
   850  	pod := &v1.Pod{
   851  		ObjectMeta: metav1.ObjectMeta{
   852  			UID:       "12345678",
   853  			Name:      "bar",
   854  			Namespace: "new",
   855  		},
   856  		Spec: v1.PodSpec{
   857  			Containers: []v1.Container{
   858  				{
   859  					Name:            "foo",
   860  					Image:           "busybox",
   861  					ImagePullPolicy: v1.PullIfNotPresent,
   862  				},
   863  			},
   864  		},
   865  	}
   866  
   867  	// Create fake sandbox and container
   868  	_, fakeContainers := makeAndSetFakePod(t, m, fakeRuntime, pod)
   869  	assert.Equal(t, len(fakeContainers), 1)
   870  
   871  	ctx := context.Background()
   872  	cStatus, err := m.getPodContainerStatuses(ctx, pod.UID, pod.Name, pod.Namespace)
   873  	assert.NoError(t, err)
   874  	containerID := cStatus[0].ID
   875  
   876  	err = m.updateContainerResources(pod, &pod.Spec.Containers[0], containerID)
   877  	assert.NoError(t, err)
   878  
   879  	// Verify container is updated
   880  	assert.Contains(t, fakeRuntime.Called, "UpdateContainerResources")
   881  }
   882  

View as plain text