k8s.io/kubernetes@v1.29.3/pkg/kubelet/kuberuntime/kuberuntime_gc_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 "testing" 24 "time" 25 26 "github.com/golang/mock/gomock" 27 "github.com/stretchr/testify/assert" 28 v1 "k8s.io/api/core/v1" 29 "k8s.io/apimachinery/pkg/types" 30 runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1" 31 kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" 32 containertest "k8s.io/kubernetes/pkg/kubelet/container/testing" 33 ) 34 35 func TestSandboxGC(t *testing.T) { 36 fakeRuntime, _, m, err := createTestRuntimeManager() 37 assert.NoError(t, err) 38 39 podStateProvider := m.containerGC.podStateProvider.(*fakePodStateProvider) 40 makeGCSandbox := func(pod *v1.Pod, attempt uint32, state runtimeapi.PodSandboxState, hasRunningContainers, isTerminating bool, createdAt int64) sandboxTemplate { 41 return sandboxTemplate{ 42 pod: pod, 43 state: state, 44 attempt: attempt, 45 createdAt: createdAt, 46 running: hasRunningContainers, 47 terminating: isTerminating, 48 } 49 } 50 51 pods := []*v1.Pod{ 52 makeTestPod("foo1", "new", "1234", []v1.Container{ 53 makeTestContainer("bar1", "busybox"), 54 makeTestContainer("bar2", "busybox"), 55 }), 56 makeTestPod("foo2", "new", "5678", []v1.Container{ 57 makeTestContainer("bar3", "busybox"), 58 }), 59 makeTestPod("deleted", "new", "9012", []v1.Container{ 60 makeTestContainer("bar4", "busybox"), 61 }), 62 } 63 64 for _, test := range []struct { 65 description string // description of the test case 66 sandboxes []sandboxTemplate // templates of sandboxes 67 containers []containerTemplate // templates of containers 68 remain []int // template indexes of remaining sandboxes 69 evictTerminatingPods bool 70 }{ 71 { 72 description: "notready sandboxes without containers for deleted pods should be garbage collected.", 73 sandboxes: []sandboxTemplate{ 74 makeGCSandbox(pods[2], 0, runtimeapi.PodSandboxState_SANDBOX_NOTREADY, false, false, 0), 75 }, 76 containers: []containerTemplate{}, 77 remain: []int{}, 78 evictTerminatingPods: false, 79 }, 80 { 81 description: "ready sandboxes without containers for deleted pods should not be garbage collected.", 82 sandboxes: []sandboxTemplate{ 83 makeGCSandbox(pods[2], 0, runtimeapi.PodSandboxState_SANDBOX_READY, false, false, 0), 84 }, 85 containers: []containerTemplate{}, 86 remain: []int{0}, 87 evictTerminatingPods: false, 88 }, 89 { 90 description: "sandboxes for existing pods should not be garbage collected.", 91 sandboxes: []sandboxTemplate{ 92 makeGCSandbox(pods[0], 0, runtimeapi.PodSandboxState_SANDBOX_READY, true, false, 0), 93 makeGCSandbox(pods[1], 0, runtimeapi.PodSandboxState_SANDBOX_NOTREADY, true, false, 0), 94 }, 95 containers: []containerTemplate{}, 96 remain: []int{0, 1}, 97 evictTerminatingPods: false, 98 }, 99 { 100 description: "older exited sandboxes without containers for existing pods should be garbage collected if there are more than one exited sandboxes.", 101 sandboxes: []sandboxTemplate{ 102 makeGCSandbox(pods[0], 1, runtimeapi.PodSandboxState_SANDBOX_NOTREADY, true, false, 1), 103 makeGCSandbox(pods[0], 0, runtimeapi.PodSandboxState_SANDBOX_NOTREADY, true, false, 0), 104 }, 105 containers: []containerTemplate{}, 106 remain: []int{0}, 107 evictTerminatingPods: false, 108 }, 109 { 110 description: "older exited sandboxes with containers for existing pods should not be garbage collected even if there are more than one exited sandboxes.", 111 sandboxes: []sandboxTemplate{ 112 makeGCSandbox(pods[0], 1, runtimeapi.PodSandboxState_SANDBOX_NOTREADY, true, false, 1), 113 makeGCSandbox(pods[0], 0, runtimeapi.PodSandboxState_SANDBOX_NOTREADY, true, false, 0), 114 }, 115 containers: []containerTemplate{ 116 {pod: pods[0], container: &pods[0].Spec.Containers[0], sandboxAttempt: 0, state: runtimeapi.ContainerState_CONTAINER_EXITED}, 117 }, 118 remain: []int{0, 1}, 119 evictTerminatingPods: false, 120 }, 121 { 122 description: "non-running sandboxes for existing pods should be garbage collected if evictTerminatingPods is set.", 123 sandboxes: []sandboxTemplate{ 124 makeGCSandbox(pods[0], 0, runtimeapi.PodSandboxState_SANDBOX_READY, true, true, 0), 125 makeGCSandbox(pods[1], 0, runtimeapi.PodSandboxState_SANDBOX_NOTREADY, true, true, 0), 126 }, 127 containers: []containerTemplate{}, 128 remain: []int{0}, 129 evictTerminatingPods: true, 130 }, 131 { 132 description: "sandbox with containers should not be garbage collected.", 133 sandboxes: []sandboxTemplate{ 134 makeGCSandbox(pods[0], 0, runtimeapi.PodSandboxState_SANDBOX_NOTREADY, false, false, 0), 135 }, 136 containers: []containerTemplate{ 137 {pod: pods[0], container: &pods[0].Spec.Containers[0], state: runtimeapi.ContainerState_CONTAINER_EXITED}, 138 }, 139 remain: []int{0}, 140 evictTerminatingPods: false, 141 }, 142 { 143 description: "multiple sandboxes should be handled properly.", 144 sandboxes: []sandboxTemplate{ 145 // running sandbox. 146 makeGCSandbox(pods[0], 1, runtimeapi.PodSandboxState_SANDBOX_READY, true, false, 1), 147 // exited sandbox without containers. 148 makeGCSandbox(pods[0], 0, runtimeapi.PodSandboxState_SANDBOX_NOTREADY, true, false, 0), 149 // exited sandbox with containers. 150 makeGCSandbox(pods[1], 1, runtimeapi.PodSandboxState_SANDBOX_NOTREADY, true, false, 1), 151 // exited sandbox without containers. 152 makeGCSandbox(pods[1], 0, runtimeapi.PodSandboxState_SANDBOX_NOTREADY, true, false, 0), 153 // exited sandbox without containers for deleted pods. 154 makeGCSandbox(pods[2], 0, runtimeapi.PodSandboxState_SANDBOX_NOTREADY, false, true, 0), 155 }, 156 containers: []containerTemplate{ 157 {pod: pods[1], container: &pods[1].Spec.Containers[0], sandboxAttempt: 1, state: runtimeapi.ContainerState_CONTAINER_EXITED}, 158 }, 159 remain: []int{0, 2}, 160 evictTerminatingPods: false, 161 }, 162 } { 163 t.Run(test.description, func(t *testing.T) { 164 ctx := context.Background() 165 podStateProvider.removed = make(map[types.UID]struct{}) 166 podStateProvider.terminated = make(map[types.UID]struct{}) 167 fakeSandboxes := makeFakePodSandboxes(t, m, test.sandboxes) 168 fakeContainers := makeFakeContainers(t, m, test.containers) 169 for _, s := range test.sandboxes { 170 if !s.running && s.pod.Name == "deleted" { 171 podStateProvider.removed[s.pod.UID] = struct{}{} 172 } 173 if s.terminating { 174 podStateProvider.terminated[s.pod.UID] = struct{}{} 175 } 176 } 177 fakeRuntime.SetFakeSandboxes(fakeSandboxes) 178 fakeRuntime.SetFakeContainers(fakeContainers) 179 180 err := m.containerGC.evictSandboxes(ctx, test.evictTerminatingPods) 181 assert.NoError(t, err) 182 realRemain, err := fakeRuntime.ListPodSandbox(ctx, nil) 183 assert.NoError(t, err) 184 assert.Len(t, realRemain, len(test.remain)) 185 for _, remain := range test.remain { 186 resp, err := fakeRuntime.PodSandboxStatus(ctx, fakeSandboxes[remain].Id, false) 187 assert.NoError(t, err) 188 assert.Equal(t, &fakeSandboxes[remain].PodSandboxStatus, resp.Status) 189 } 190 }) 191 } 192 } 193 194 func makeGCContainer(podName, containerName string, attempt int, createdAt int64, state runtimeapi.ContainerState) containerTemplate { 195 container := makeTestContainer(containerName, "test-image") 196 pod := makeTestPod(podName, "test-ns", podName, []v1.Container{container}) 197 return containerTemplate{ 198 pod: pod, 199 container: &container, 200 attempt: attempt, 201 createdAt: createdAt, 202 state: state, 203 } 204 } 205 206 func TestContainerGC(t *testing.T) { 207 fakeRuntime, _, m, err := createTestRuntimeManager() 208 assert.NoError(t, err) 209 210 podStateProvider := m.containerGC.podStateProvider.(*fakePodStateProvider) 211 defaultGCPolicy := kubecontainer.GCPolicy{MinAge: time.Hour, MaxPerPodContainer: 2, MaxContainers: 6} 212 213 for _, test := range []struct { 214 description string // description of the test case 215 containers []containerTemplate // templates of containers 216 policy *kubecontainer.GCPolicy // container gc policy 217 remain []int // template indexes of remaining containers 218 evictTerminatingPods bool 219 allSourcesReady bool 220 }{ 221 { 222 description: "all containers should be removed when max container limit is 0", 223 containers: []containerTemplate{ 224 makeGCContainer("foo", "bar", 0, 0, runtimeapi.ContainerState_CONTAINER_EXITED), 225 }, 226 policy: &kubecontainer.GCPolicy{MinAge: time.Minute, MaxPerPodContainer: 1, MaxContainers: 0}, 227 remain: []int{}, 228 evictTerminatingPods: false, 229 allSourcesReady: true, 230 }, 231 { 232 description: "max containers should be complied when no max per pod container limit is set", 233 containers: []containerTemplate{ 234 makeGCContainer("foo", "bar", 4, 4, runtimeapi.ContainerState_CONTAINER_EXITED), 235 makeGCContainer("foo", "bar", 3, 3, runtimeapi.ContainerState_CONTAINER_EXITED), 236 makeGCContainer("foo", "bar", 2, 2, runtimeapi.ContainerState_CONTAINER_EXITED), 237 makeGCContainer("foo", "bar", 1, 1, runtimeapi.ContainerState_CONTAINER_EXITED), 238 makeGCContainer("foo", "bar", 0, 0, runtimeapi.ContainerState_CONTAINER_EXITED), 239 }, 240 policy: &kubecontainer.GCPolicy{MinAge: time.Minute, MaxPerPodContainer: -1, MaxContainers: 4}, 241 remain: []int{0, 1, 2, 3}, 242 evictTerminatingPods: false, 243 allSourcesReady: true, 244 }, 245 { 246 description: "no containers should be removed if both max container and per pod container limits are not set", 247 containers: []containerTemplate{ 248 makeGCContainer("foo", "bar", 2, 2, runtimeapi.ContainerState_CONTAINER_EXITED), 249 makeGCContainer("foo", "bar", 1, 1, runtimeapi.ContainerState_CONTAINER_EXITED), 250 makeGCContainer("foo", "bar", 0, 0, runtimeapi.ContainerState_CONTAINER_EXITED), 251 }, 252 policy: &kubecontainer.GCPolicy{MinAge: time.Minute, MaxPerPodContainer: -1, MaxContainers: -1}, 253 remain: []int{0, 1, 2}, 254 evictTerminatingPods: false, 255 allSourcesReady: true, 256 }, 257 { 258 description: "recently started containers should not be removed", 259 containers: []containerTemplate{ 260 makeGCContainer("foo", "bar", 2, time.Now().UnixNano(), runtimeapi.ContainerState_CONTAINER_EXITED), 261 makeGCContainer("foo", "bar", 1, time.Now().UnixNano(), runtimeapi.ContainerState_CONTAINER_EXITED), 262 makeGCContainer("foo", "bar", 0, time.Now().UnixNano(), runtimeapi.ContainerState_CONTAINER_EXITED), 263 }, 264 remain: []int{0, 1, 2}, 265 evictTerminatingPods: false, 266 allSourcesReady: true, 267 }, 268 { 269 description: "oldest containers should be removed when per pod container limit exceeded", 270 containers: []containerTemplate{ 271 makeGCContainer("foo", "bar", 2, 2, runtimeapi.ContainerState_CONTAINER_EXITED), 272 makeGCContainer("foo", "bar", 1, 1, runtimeapi.ContainerState_CONTAINER_EXITED), 273 makeGCContainer("foo", "bar", 0, 0, runtimeapi.ContainerState_CONTAINER_EXITED), 274 }, 275 remain: []int{0, 1}, 276 evictTerminatingPods: false, 277 allSourcesReady: true, 278 }, 279 { 280 description: "running containers should not be removed", 281 containers: []containerTemplate{ 282 makeGCContainer("foo", "bar", 2, 2, runtimeapi.ContainerState_CONTAINER_EXITED), 283 makeGCContainer("foo", "bar", 1, 1, runtimeapi.ContainerState_CONTAINER_EXITED), 284 makeGCContainer("foo", "bar", 0, 0, runtimeapi.ContainerState_CONTAINER_RUNNING), 285 }, 286 remain: []int{0, 1, 2}, 287 evictTerminatingPods: false, 288 allSourcesReady: true, 289 }, 290 { 291 description: "no containers should be removed when limits are not exceeded", 292 containers: []containerTemplate{ 293 makeGCContainer("foo", "bar", 1, 1, runtimeapi.ContainerState_CONTAINER_EXITED), 294 makeGCContainer("foo", "bar", 0, 0, runtimeapi.ContainerState_CONTAINER_EXITED), 295 }, 296 remain: []int{0, 1}, 297 evictTerminatingPods: false, 298 allSourcesReady: true, 299 }, 300 { 301 description: "max container count should apply per (UID, container) pair", 302 containers: []containerTemplate{ 303 makeGCContainer("foo", "bar", 2, 2, runtimeapi.ContainerState_CONTAINER_EXITED), 304 makeGCContainer("foo", "bar", 1, 1, runtimeapi.ContainerState_CONTAINER_EXITED), 305 makeGCContainer("foo", "bar", 0, 0, runtimeapi.ContainerState_CONTAINER_EXITED), 306 makeGCContainer("foo1", "baz", 2, 2, runtimeapi.ContainerState_CONTAINER_EXITED), 307 makeGCContainer("foo1", "baz", 1, 1, runtimeapi.ContainerState_CONTAINER_EXITED), 308 makeGCContainer("foo1", "baz", 0, 0, runtimeapi.ContainerState_CONTAINER_EXITED), 309 makeGCContainer("foo2", "bar", 2, 2, runtimeapi.ContainerState_CONTAINER_EXITED), 310 makeGCContainer("foo2", "bar", 1, 1, runtimeapi.ContainerState_CONTAINER_EXITED), 311 makeGCContainer("foo2", "bar", 0, 0, runtimeapi.ContainerState_CONTAINER_EXITED), 312 }, 313 remain: []int{0, 1, 3, 4, 6, 7}, 314 evictTerminatingPods: false, 315 allSourcesReady: true, 316 }, 317 { 318 description: "max limit should apply and try to keep from every pod", 319 containers: []containerTemplate{ 320 makeGCContainer("foo", "bar", 1, 1, runtimeapi.ContainerState_CONTAINER_EXITED), 321 makeGCContainer("foo", "bar", 0, 0, runtimeapi.ContainerState_CONTAINER_EXITED), 322 makeGCContainer("foo1", "bar1", 1, 1, runtimeapi.ContainerState_CONTAINER_EXITED), 323 makeGCContainer("foo1", "bar1", 0, 0, runtimeapi.ContainerState_CONTAINER_EXITED), 324 makeGCContainer("foo2", "bar2", 1, 1, runtimeapi.ContainerState_CONTAINER_EXITED), 325 makeGCContainer("foo2", "bar2", 0, 0, runtimeapi.ContainerState_CONTAINER_EXITED), 326 makeGCContainer("foo3", "bar3", 1, 1, runtimeapi.ContainerState_CONTAINER_EXITED), 327 makeGCContainer("foo3", "bar3", 0, 0, runtimeapi.ContainerState_CONTAINER_EXITED), 328 makeGCContainer("foo4", "bar4", 1, 1, runtimeapi.ContainerState_CONTAINER_EXITED), 329 makeGCContainer("foo4", "bar4", 0, 0, runtimeapi.ContainerState_CONTAINER_EXITED), 330 }, 331 remain: []int{0, 2, 4, 6, 8}, 332 evictTerminatingPods: false, 333 allSourcesReady: true, 334 }, 335 { 336 description: "oldest pods should be removed if limit exceeded", 337 containers: []containerTemplate{ 338 makeGCContainer("foo", "bar", 2, 2, runtimeapi.ContainerState_CONTAINER_EXITED), 339 makeGCContainer("foo", "bar", 1, 1, runtimeapi.ContainerState_CONTAINER_EXITED), 340 makeGCContainer("foo1", "bar1", 2, 2, runtimeapi.ContainerState_CONTAINER_EXITED), 341 makeGCContainer("foo1", "bar1", 1, 1, runtimeapi.ContainerState_CONTAINER_EXITED), 342 makeGCContainer("foo2", "bar2", 1, 1, runtimeapi.ContainerState_CONTAINER_EXITED), 343 makeGCContainer("foo3", "bar3", 0, 0, runtimeapi.ContainerState_CONTAINER_EXITED), 344 makeGCContainer("foo4", "bar4", 1, 1, runtimeapi.ContainerState_CONTAINER_EXITED), 345 makeGCContainer("foo5", "bar5", 0, 0, runtimeapi.ContainerState_CONTAINER_EXITED), 346 makeGCContainer("foo6", "bar6", 2, 2, runtimeapi.ContainerState_CONTAINER_EXITED), 347 makeGCContainer("foo7", "bar7", 1, 1, runtimeapi.ContainerState_CONTAINER_EXITED), 348 }, 349 remain: []int{0, 2, 4, 6, 8, 9}, 350 evictTerminatingPods: false, 351 allSourcesReady: true, 352 }, 353 { 354 description: "all non-running containers should be removed when evictTerminatingPods is set", 355 containers: []containerTemplate{ 356 makeGCContainer("foo", "bar", 2, 2, runtimeapi.ContainerState_CONTAINER_EXITED), 357 makeGCContainer("foo", "bar", 1, 1, runtimeapi.ContainerState_CONTAINER_EXITED), 358 makeGCContainer("foo1", "bar1", 2, 2, runtimeapi.ContainerState_CONTAINER_EXITED), 359 makeGCContainer("foo1", "bar1", 1, 1, runtimeapi.ContainerState_CONTAINER_EXITED), 360 makeGCContainer("running", "bar2", 1, 1, runtimeapi.ContainerState_CONTAINER_EXITED), 361 makeGCContainer("foo3", "bar3", 0, 0, runtimeapi.ContainerState_CONTAINER_RUNNING), 362 }, 363 remain: []int{4, 5}, 364 evictTerminatingPods: true, 365 allSourcesReady: true, 366 }, 367 { 368 description: "containers for deleted pods should be removed", 369 containers: []containerTemplate{ 370 makeGCContainer("foo", "bar", 1, 1, runtimeapi.ContainerState_CONTAINER_EXITED), 371 makeGCContainer("foo", "bar", 0, 0, runtimeapi.ContainerState_CONTAINER_EXITED), 372 // deleted pods still respect MinAge. 373 makeGCContainer("deleted", "bar1", 2, time.Now().UnixNano(), runtimeapi.ContainerState_CONTAINER_EXITED), 374 makeGCContainer("deleted", "bar1", 1, 1, runtimeapi.ContainerState_CONTAINER_EXITED), 375 makeGCContainer("deleted", "bar1", 0, 0, runtimeapi.ContainerState_CONTAINER_EXITED), 376 }, 377 remain: []int{0, 1, 2}, 378 evictTerminatingPods: false, 379 allSourcesReady: true, 380 }, 381 { 382 description: "containers for deleted pods may not be removed if allSourcesReady is set false ", 383 containers: []containerTemplate{ 384 makeGCContainer("deleted", "bar1", 0, 0, runtimeapi.ContainerState_CONTAINER_EXITED), 385 }, 386 remain: []int{0}, 387 evictTerminatingPods: true, 388 allSourcesReady: false, 389 }, 390 } { 391 t.Run(test.description, func(t *testing.T) { 392 ctx := context.Background() 393 podStateProvider.removed = make(map[types.UID]struct{}) 394 podStateProvider.terminated = make(map[types.UID]struct{}) 395 fakeContainers := makeFakeContainers(t, m, test.containers) 396 for _, s := range test.containers { 397 if s.pod.Name == "deleted" { 398 podStateProvider.removed[s.pod.UID] = struct{}{} 399 } 400 if s.pod.Name != "running" { 401 podStateProvider.terminated[s.pod.UID] = struct{}{} 402 } 403 } 404 fakeRuntime.SetFakeContainers(fakeContainers) 405 406 if test.policy == nil { 407 test.policy = &defaultGCPolicy 408 } 409 err := m.containerGC.evictContainers(ctx, *test.policy, test.allSourcesReady, test.evictTerminatingPods) 410 assert.NoError(t, err) 411 realRemain, err := fakeRuntime.ListContainers(ctx, nil) 412 assert.NoError(t, err) 413 assert.Len(t, realRemain, len(test.remain)) 414 for _, remain := range test.remain { 415 resp, err := fakeRuntime.ContainerStatus(ctx, fakeContainers[remain].Id, false) 416 assert.NoError(t, err) 417 assert.Equal(t, &fakeContainers[remain].ContainerStatus, resp.Status) 418 } 419 }) 420 } 421 } 422 423 // Notice that legacy container symlink is not tested since it may be deprecated soon. 424 func TestPodLogDirectoryGC(t *testing.T) { 425 ctx := context.Background() 426 _, _, m, err := createTestRuntimeManager() 427 assert.NoError(t, err) 428 fakeOS := m.osInterface.(*containertest.FakeOS) 429 podStateProvider := m.containerGC.podStateProvider.(*fakePodStateProvider) 430 431 // pod log directories without corresponding pods should be removed. 432 files := []string{"123", "456", "789", "012", "name_namespace_321", "name_namespace_654"} 433 removed := []string{ 434 filepath.Join(podLogsRootDirectory, "789"), 435 filepath.Join(podLogsRootDirectory, "012"), 436 filepath.Join(podLogsRootDirectory, "name_namespace_654"), 437 } 438 podStateProvider.removed["012"] = struct{}{} 439 podStateProvider.removed["789"] = struct{}{} 440 podStateProvider.removed["654"] = struct{}{} 441 442 ctrl := gomock.NewController(t) 443 defer ctrl.Finish() 444 445 fakeOS.ReadDirFn = func(string) ([]os.DirEntry, error) { 446 var dirEntries []os.DirEntry 447 for _, file := range files { 448 mockDE := containertest.NewMockDirEntry(ctrl) 449 mockDE.EXPECT().Name().Return(file) 450 dirEntries = append(dirEntries, mockDE) 451 } 452 return dirEntries, nil 453 } 454 455 // allSourcesReady == true, pod log directories without corresponding pod should be removed. 456 err = m.containerGC.evictPodLogsDirectories(ctx, true) 457 assert.NoError(t, err) 458 assert.Equal(t, removed, fakeOS.Removes) 459 460 // allSourcesReady == false, pod log directories should not be removed. 461 fakeOS.Removes = []string{} 462 err = m.containerGC.evictPodLogsDirectories(ctx, false) 463 assert.NoError(t, err) 464 assert.Empty(t, fakeOS.Removes) 465 } 466 467 func TestUnknownStateContainerGC(t *testing.T) { 468 ctx := context.Background() 469 fakeRuntime, _, m, err := createTestRuntimeManager() 470 assert.NoError(t, err) 471 472 // podStateProvider := m.containerGC.podStateProvider.(*fakePodStateProvider) 473 defaultGCPolicy := kubecontainer.GCPolicy{MinAge: time.Hour, MaxPerPodContainer: 0, MaxContainers: 0} 474 475 fakeContainers := makeFakeContainers(t, m, []containerTemplate{ 476 makeGCContainer("foo", "bar", 0, 0, runtimeapi.ContainerState_CONTAINER_UNKNOWN), 477 }) 478 fakeRuntime.SetFakeContainers(fakeContainers) 479 480 err = m.containerGC.evictContainers(ctx, defaultGCPolicy, true, false) 481 assert.NoError(t, err) 482 483 assert.Contains(t, fakeRuntime.GetCalls(), "StopContainer", "RemoveContainer", 484 "container in unknown state should be stopped before being removed") 485 486 remain, err := fakeRuntime.ListContainers(ctx, nil) 487 assert.NoError(t, err) 488 assert.Empty(t, remain) 489 }