k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/kubelet/kubelet_test.go (about) 1 /* 2 Copyright 2014 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 kubelet 18 19 import ( 20 "context" 21 "crypto/tls" 22 "fmt" 23 "net" 24 "os" 25 "path/filepath" 26 "reflect" 27 goruntime "runtime" 28 "sort" 29 "strconv" 30 "strings" 31 "testing" 32 "time" 33 34 sdktrace "go.opentelemetry.io/otel/sdk/trace" 35 "go.opentelemetry.io/otel/sdk/trace/tracetest" 36 oteltrace "go.opentelemetry.io/otel/trace" 37 38 cadvisorapi "github.com/google/cadvisor/info/v1" 39 cadvisorapiv2 "github.com/google/cadvisor/info/v2" 40 "github.com/stretchr/testify/assert" 41 "github.com/stretchr/testify/require" 42 "go.uber.org/mock/gomock" 43 core "k8s.io/client-go/testing" 44 "k8s.io/mount-utils" 45 46 v1 "k8s.io/api/core/v1" 47 "k8s.io/apimachinery/pkg/api/resource" 48 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 49 "k8s.io/apimachinery/pkg/labels" 50 "k8s.io/apimachinery/pkg/types" 51 "k8s.io/apimachinery/pkg/util/sets" 52 "k8s.io/apimachinery/pkg/util/wait" 53 utilfeature "k8s.io/apiserver/pkg/util/feature" 54 "k8s.io/client-go/kubernetes/fake" 55 "k8s.io/client-go/tools/record" 56 "k8s.io/client-go/util/flowcontrol" 57 featuregatetesting "k8s.io/component-base/featuregate/testing" 58 internalapi "k8s.io/cri-api/pkg/apis" 59 runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1" 60 remote "k8s.io/cri-client/pkg" 61 fakeremote "k8s.io/cri-client/pkg/fake" 62 "k8s.io/klog/v2" 63 "k8s.io/klog/v2/ktesting" 64 "k8s.io/kubernetes/pkg/features" 65 kubeletconfiginternal "k8s.io/kubernetes/pkg/kubelet/apis/config" 66 cadvisortest "k8s.io/kubernetes/pkg/kubelet/cadvisor/testing" 67 "k8s.io/kubernetes/pkg/kubelet/clustertrustbundle" 68 "k8s.io/kubernetes/pkg/kubelet/cm" 69 "k8s.io/kubernetes/pkg/kubelet/config" 70 "k8s.io/kubernetes/pkg/kubelet/configmap" 71 kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" 72 containertest "k8s.io/kubernetes/pkg/kubelet/container/testing" 73 "k8s.io/kubernetes/pkg/kubelet/eviction" 74 "k8s.io/kubernetes/pkg/kubelet/images" 75 "k8s.io/kubernetes/pkg/kubelet/kuberuntime" 76 "k8s.io/kubernetes/pkg/kubelet/lifecycle" 77 "k8s.io/kubernetes/pkg/kubelet/logs" 78 "k8s.io/kubernetes/pkg/kubelet/network/dns" 79 "k8s.io/kubernetes/pkg/kubelet/nodeshutdown" 80 "k8s.io/kubernetes/pkg/kubelet/pleg" 81 "k8s.io/kubernetes/pkg/kubelet/pluginmanager" 82 kubepod "k8s.io/kubernetes/pkg/kubelet/pod" 83 podtest "k8s.io/kubernetes/pkg/kubelet/pod/testing" 84 proberesults "k8s.io/kubernetes/pkg/kubelet/prober/results" 85 probetest "k8s.io/kubernetes/pkg/kubelet/prober/testing" 86 "k8s.io/kubernetes/pkg/kubelet/secret" 87 "k8s.io/kubernetes/pkg/kubelet/server" 88 serverstats "k8s.io/kubernetes/pkg/kubelet/server/stats" 89 "k8s.io/kubernetes/pkg/kubelet/stats" 90 "k8s.io/kubernetes/pkg/kubelet/status" 91 "k8s.io/kubernetes/pkg/kubelet/status/state" 92 statustest "k8s.io/kubernetes/pkg/kubelet/status/testing" 93 "k8s.io/kubernetes/pkg/kubelet/token" 94 kubetypes "k8s.io/kubernetes/pkg/kubelet/types" 95 kubeletutil "k8s.io/kubernetes/pkg/kubelet/util" 96 "k8s.io/kubernetes/pkg/kubelet/util/queue" 97 kubeletvolume "k8s.io/kubernetes/pkg/kubelet/volumemanager" 98 schedulerframework "k8s.io/kubernetes/pkg/scheduler/framework" 99 "k8s.io/kubernetes/pkg/util/oom" 100 "k8s.io/kubernetes/pkg/volume" 101 _ "k8s.io/kubernetes/pkg/volume/hostpath" 102 volumesecret "k8s.io/kubernetes/pkg/volume/secret" 103 volumetest "k8s.io/kubernetes/pkg/volume/testing" 104 "k8s.io/kubernetes/pkg/volume/util" 105 "k8s.io/kubernetes/pkg/volume/util/hostutil" 106 "k8s.io/kubernetes/pkg/volume/util/subpath" 107 "k8s.io/utils/clock" 108 testingclock "k8s.io/utils/clock/testing" 109 utilpointer "k8s.io/utils/pointer" 110 ) 111 112 func init() { 113 } 114 115 const ( 116 testKubeletHostname = "127.0.0.1" 117 testKubeletHostIP = "127.0.0.1" 118 testKubeletHostIPv6 = "::1" 119 120 // TODO(harry) any global place for these two? 121 // Reasonable size range of all container images. 90%ile of images on dockerhub drops into this range. 122 minImgSize int64 = 23 * 1024 * 1024 123 maxImgSize int64 = 1000 * 1024 * 1024 124 ) 125 126 // fakeImageGCManager is a fake image gc manager for testing. It will return image 127 // list from fake runtime directly instead of caching it. 128 type fakeImageGCManager struct { 129 fakeImageService kubecontainer.ImageService 130 images.ImageGCManager 131 } 132 133 func (f *fakeImageGCManager) GetImageList() ([]kubecontainer.Image, error) { 134 return f.fakeImageService.ListImages(context.Background()) 135 } 136 137 type TestKubelet struct { 138 kubelet *Kubelet 139 fakeRuntime *containertest.FakeRuntime 140 fakeContainerManager *cm.FakeContainerManager 141 fakeKubeClient *fake.Clientset 142 fakeMirrorClient *podtest.FakeMirrorClient 143 fakeClock *testingclock.FakeClock 144 mounter mount.Interface 145 volumePlugin *volumetest.FakeVolumePlugin 146 } 147 148 func (tk *TestKubelet) Cleanup() { 149 if tk.kubelet != nil { 150 os.RemoveAll(tk.kubelet.rootDirectory) 151 tk.kubelet = nil 152 } 153 } 154 155 // newTestKubelet returns test kubelet with two images. 156 func newTestKubelet(t *testing.T, controllerAttachDetachEnabled bool) *TestKubelet { 157 imageList := []kubecontainer.Image{ 158 { 159 ID: "abc", 160 RepoTags: []string{"registry.k8s.io:v1", "registry.k8s.io:v2"}, 161 Size: 123, 162 }, 163 { 164 ID: "efg", 165 RepoTags: []string{"registry.k8s.io:v3", "registry.k8s.io:v4"}, 166 Size: 456, 167 }, 168 } 169 return newTestKubeletWithImageList(t, imageList, controllerAttachDetachEnabled, true /*initFakeVolumePlugin*/, true /*localStorageCapacityIsolation*/) 170 } 171 172 func newTestKubeletWithImageList( 173 t *testing.T, 174 imageList []kubecontainer.Image, 175 controllerAttachDetachEnabled bool, 176 initFakeVolumePlugin bool, 177 localStorageCapacityIsolation bool, 178 ) *TestKubelet { 179 logger, _ := ktesting.NewTestContext(t) 180 181 fakeRuntime := &containertest.FakeRuntime{ 182 ImageList: imageList, 183 // Set ready conditions by default. 184 RuntimeStatus: &kubecontainer.RuntimeStatus{ 185 Conditions: []kubecontainer.RuntimeCondition{ 186 {Type: "RuntimeReady", Status: true}, 187 {Type: "NetworkReady", Status: true}, 188 }, 189 }, 190 VersionInfo: "1.5.0", 191 RuntimeType: "test", 192 T: t, 193 } 194 195 fakeRecorder := &record.FakeRecorder{} 196 fakeKubeClient := &fake.Clientset{} 197 kubelet := &Kubelet{} 198 kubelet.recorder = fakeRecorder 199 kubelet.kubeClient = fakeKubeClient 200 kubelet.heartbeatClient = fakeKubeClient 201 kubelet.os = &containertest.FakeOS{} 202 kubelet.mounter = mount.NewFakeMounter(nil) 203 kubelet.hostutil = hostutil.NewFakeHostUtil(nil) 204 kubelet.subpather = &subpath.FakeSubpath{} 205 206 kubelet.hostname = testKubeletHostname 207 kubelet.nodeName = types.NodeName(testKubeletHostname) 208 kubelet.runtimeState = newRuntimeState(maxWaitForContainerRuntime) 209 kubelet.runtimeState.setNetworkState(nil) 210 kubelet.rootDirectory = t.TempDir() 211 kubelet.podLogsDirectory = t.TempDir() 212 kubelet.sourcesReady = config.NewSourcesReady(func(_ sets.String) bool { return true }) 213 kubelet.serviceLister = testServiceLister{} 214 kubelet.serviceHasSynced = func() bool { return true } 215 kubelet.nodeHasSynced = func() bool { return true } 216 kubelet.nodeLister = testNodeLister{ 217 nodes: []*v1.Node{ 218 { 219 ObjectMeta: metav1.ObjectMeta{ 220 Name: string(kubelet.nodeName), 221 }, 222 Status: v1.NodeStatus{ 223 Conditions: []v1.NodeCondition{ 224 { 225 Type: v1.NodeReady, 226 Status: v1.ConditionTrue, 227 Reason: "Ready", 228 Message: "Node ready", 229 }, 230 }, 231 Addresses: []v1.NodeAddress{ 232 { 233 Type: v1.NodeInternalIP, 234 Address: testKubeletHostIP, 235 }, 236 { 237 Type: v1.NodeInternalIP, 238 Address: testKubeletHostIPv6, 239 }, 240 }, 241 VolumesAttached: []v1.AttachedVolume{ 242 { 243 Name: "fake/fake-device", 244 DevicePath: "fake/path", 245 }, 246 }, 247 }, 248 }, 249 }, 250 } 251 kubelet.recorder = fakeRecorder 252 if err := kubelet.setupDataDirs(); err != nil { 253 t.Fatalf("can't initialize kubelet data dirs: %v", err) 254 } 255 kubelet.daemonEndpoints = &v1.NodeDaemonEndpoints{} 256 257 kubelet.cadvisor = &cadvisortest.Fake{} 258 machineInfo, _ := kubelet.cadvisor.MachineInfo() 259 kubelet.setCachedMachineInfo(machineInfo) 260 kubelet.tracer = oteltrace.NewNoopTracerProvider().Tracer("") 261 262 fakeMirrorClient := podtest.NewFakeMirrorClient() 263 secretManager := secret.NewSimpleSecretManager(kubelet.kubeClient) 264 kubelet.secretManager = secretManager 265 configMapManager := configmap.NewSimpleConfigMapManager(kubelet.kubeClient) 266 kubelet.configMapManager = configMapManager 267 kubelet.mirrorPodClient = fakeMirrorClient 268 kubelet.podManager = kubepod.NewBasicPodManager() 269 podStartupLatencyTracker := kubeletutil.NewPodStartupLatencyTracker() 270 kubelet.statusManager = status.NewManager(fakeKubeClient, kubelet.podManager, &statustest.FakePodDeletionSafetyProvider{}, podStartupLatencyTracker, kubelet.getRootDir()) 271 kubelet.nodeStartupLatencyTracker = kubeletutil.NewNodeStartupLatencyTracker() 272 273 kubelet.containerRuntime = fakeRuntime 274 kubelet.runtimeCache = containertest.NewFakeRuntimeCache(kubelet.containerRuntime) 275 kubelet.reasonCache = NewReasonCache() 276 kubelet.podCache = containertest.NewFakeCache(kubelet.containerRuntime) 277 kubelet.podWorkers = &fakePodWorkers{ 278 syncPodFn: kubelet.SyncPod, 279 cache: kubelet.podCache, 280 t: t, 281 } 282 283 kubelet.probeManager = probetest.FakeManager{} 284 kubelet.livenessManager = proberesults.NewManager() 285 kubelet.readinessManager = proberesults.NewManager() 286 kubelet.startupManager = proberesults.NewManager() 287 288 fakeContainerManager := cm.NewFakeContainerManager() 289 kubelet.containerManager = fakeContainerManager 290 fakeNodeRef := &v1.ObjectReference{ 291 Kind: "Node", 292 Name: testKubeletHostname, 293 UID: types.UID(testKubeletHostname), 294 Namespace: "", 295 } 296 297 volumeStatsAggPeriod := time.Second * 10 298 kubelet.resourceAnalyzer = serverstats.NewResourceAnalyzer(kubelet, volumeStatsAggPeriod, kubelet.recorder) 299 300 fakeHostStatsProvider := stats.NewFakeHostStatsProvider() 301 302 kubelet.StatsProvider = stats.NewCadvisorStatsProvider( 303 kubelet.cadvisor, 304 kubelet.resourceAnalyzer, 305 kubelet.podManager, 306 kubelet.runtimeCache, 307 fakeRuntime, 308 kubelet.statusManager, 309 fakeHostStatsProvider, 310 ) 311 fakeImageGCPolicy := images.ImageGCPolicy{ 312 HighThresholdPercent: 90, 313 LowThresholdPercent: 80, 314 } 315 imageGCManager, err := images.NewImageGCManager(fakeRuntime, kubelet.StatsProvider, fakeRecorder, fakeNodeRef, fakeImageGCPolicy, oteltrace.NewNoopTracerProvider()) 316 assert.NoError(t, err) 317 kubelet.imageManager = &fakeImageGCManager{ 318 fakeImageService: fakeRuntime, 319 ImageGCManager: imageGCManager, 320 } 321 kubelet.containerLogManager = logs.NewStubContainerLogManager() 322 containerGCPolicy := kubecontainer.GCPolicy{ 323 MinAge: time.Duration(0), 324 MaxPerPodContainer: 1, 325 MaxContainers: -1, 326 } 327 containerGC, err := kubecontainer.NewContainerGC(fakeRuntime, containerGCPolicy, kubelet.sourcesReady) 328 assert.NoError(t, err) 329 kubelet.containerGC = containerGC 330 331 fakeClock := testingclock.NewFakeClock(time.Now()) 332 kubelet.backOff = flowcontrol.NewBackOff(time.Second, time.Minute) 333 kubelet.backOff.Clock = fakeClock 334 kubelet.resyncInterval = 10 * time.Second 335 kubelet.workQueue = queue.NewBasicWorkQueue(fakeClock) 336 // Relist period does not affect the tests. 337 kubelet.pleg = pleg.NewGenericPLEG(fakeRuntime, make(chan *pleg.PodLifecycleEvent, 100), &pleg.RelistDuration{RelistPeriod: time.Hour, RelistThreshold: genericPlegRelistThreshold}, kubelet.podCache, clock.RealClock{}) 338 kubelet.clock = fakeClock 339 340 nodeRef := &v1.ObjectReference{ 341 Kind: "Node", 342 Name: string(kubelet.nodeName), 343 UID: types.UID(kubelet.nodeName), 344 Namespace: "", 345 } 346 // setup eviction manager 347 evictionManager, evictionAdmitHandler := eviction.NewManager(kubelet.resourceAnalyzer, eviction.Config{}, 348 killPodNow(kubelet.podWorkers, fakeRecorder), kubelet.imageManager, kubelet.containerGC, fakeRecorder, nodeRef, kubelet.clock, kubelet.supportLocalStorageCapacityIsolation()) 349 350 kubelet.evictionManager = evictionManager 351 kubelet.admitHandlers.AddPodAdmitHandler(evictionAdmitHandler) 352 353 // setup shutdown manager 354 shutdownManager, shutdownAdmitHandler := nodeshutdown.NewManager(&nodeshutdown.Config{ 355 Logger: logger, 356 ProbeManager: kubelet.probeManager, 357 Recorder: fakeRecorder, 358 NodeRef: nodeRef, 359 GetPodsFunc: kubelet.podManager.GetPods, 360 KillPodFunc: killPodNow(kubelet.podWorkers, fakeRecorder), 361 SyncNodeStatusFunc: func() {}, 362 ShutdownGracePeriodRequested: 0, 363 ShutdownGracePeriodCriticalPods: 0, 364 }) 365 kubelet.shutdownManager = shutdownManager 366 kubelet.admitHandlers.AddPodAdmitHandler(shutdownAdmitHandler) 367 368 // Add this as cleanup predicate pod admitter 369 kubelet.admitHandlers.AddPodAdmitHandler(lifecycle.NewPredicateAdmitHandler(kubelet.getNodeAnyWay, lifecycle.NewAdmissionFailureHandlerStub(), kubelet.containerManager.UpdatePluginResources)) 370 371 allPlugins := []volume.VolumePlugin{} 372 plug := &volumetest.FakeVolumePlugin{PluginName: "fake", Host: nil} 373 if initFakeVolumePlugin { 374 allPlugins = append(allPlugins, plug) 375 } else { 376 allPlugins = append(allPlugins, volumesecret.ProbeVolumePlugins()...) 377 } 378 379 var prober volume.DynamicPluginProber // TODO (#51147) inject mock 380 kubelet.volumePluginMgr, err = 381 NewInitializedVolumePluginMgr(kubelet, kubelet.secretManager, kubelet.configMapManager, token.NewManager(kubelet.kubeClient), &clustertrustbundle.NoopManager{}, allPlugins, prober) 382 require.NoError(t, err, "Failed to initialize VolumePluginMgr") 383 384 kubelet.volumeManager = kubeletvolume.NewVolumeManager( 385 controllerAttachDetachEnabled, 386 kubelet.nodeName, 387 kubelet.podManager, 388 kubelet.podWorkers, 389 fakeKubeClient, 390 kubelet.volumePluginMgr, 391 fakeRuntime, 392 kubelet.mounter, 393 kubelet.hostutil, 394 kubelet.getPodsDir(), 395 kubelet.recorder, 396 volumetest.NewBlockVolumePathHandler()) 397 398 kubelet.pluginManager = pluginmanager.NewPluginManager( 399 kubelet.getPluginsRegistrationDir(), /* sockDir */ 400 kubelet.recorder, 401 ) 402 kubelet.setNodeStatusFuncs = kubelet.defaultNodeStatusFuncs() 403 404 // enable active deadline handler 405 activeDeadlineHandler, err := newActiveDeadlineHandler(kubelet.statusManager, kubelet.recorder, kubelet.clock) 406 require.NoError(t, err, "Can't initialize active deadline handler") 407 408 kubelet.AddPodSyncLoopHandler(activeDeadlineHandler) 409 kubelet.AddPodSyncHandler(activeDeadlineHandler) 410 kubelet.kubeletConfiguration.LocalStorageCapacityIsolation = localStorageCapacityIsolation 411 return &TestKubelet{kubelet, fakeRuntime, fakeContainerManager, fakeKubeClient, fakeMirrorClient, fakeClock, nil, plug} 412 } 413 414 func newTestPods(count int) []*v1.Pod { 415 pods := make([]*v1.Pod, count) 416 for i := 0; i < count; i++ { 417 pods[i] = &v1.Pod{ 418 Spec: v1.PodSpec{ 419 HostNetwork: true, 420 }, 421 ObjectMeta: metav1.ObjectMeta{ 422 UID: types.UID(strconv.Itoa(10000 + i)), 423 Name: fmt.Sprintf("pod%d", i), 424 }, 425 } 426 } 427 return pods 428 } 429 430 func TestSyncLoopAbort(t *testing.T) { 431 ctx := context.Background() 432 testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) 433 defer testKubelet.Cleanup() 434 kubelet := testKubelet.kubelet 435 kubelet.runtimeState.setRuntimeSync(time.Now()) 436 // The syncLoop waits on time.After(resyncInterval), set it really big so that we don't race for 437 // the channel close 438 kubelet.resyncInterval = time.Second * 30 439 440 ch := make(chan kubetypes.PodUpdate) 441 close(ch) 442 443 // sanity check (also prevent this test from hanging in the next step) 444 ok := kubelet.syncLoopIteration(ctx, ch, kubelet, make(chan time.Time), make(chan time.Time), make(chan *pleg.PodLifecycleEvent, 1)) 445 require.False(t, ok, "Expected syncLoopIteration to return !ok since update chan was closed") 446 447 // this should terminate immediately; if it hangs then the syncLoopIteration isn't aborting properly 448 kubelet.syncLoop(ctx, ch, kubelet) 449 } 450 451 func TestSyncPodsStartPod(t *testing.T) { 452 testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) 453 defer testKubelet.Cleanup() 454 kubelet := testKubelet.kubelet 455 fakeRuntime := testKubelet.fakeRuntime 456 pods := []*v1.Pod{ 457 podWithUIDNameNsSpec("12345678", "foo", "new", v1.PodSpec{ 458 Containers: []v1.Container{ 459 {Name: "bar"}, 460 }, 461 }), 462 } 463 kubelet.podManager.SetPods(pods) 464 kubelet.HandlePodSyncs(pods) 465 fakeRuntime.AssertStartedPods([]string{string(pods[0].UID)}) 466 } 467 468 func TestHandlePodCleanupsPerQOS(t *testing.T) { 469 ctx := context.Background() 470 testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) 471 defer testKubelet.Cleanup() 472 473 pod := &kubecontainer.Pod{ 474 ID: "12345678", 475 Name: "foo", 476 Namespace: "new", 477 Containers: []*kubecontainer.Container{ 478 {Name: "bar"}, 479 }, 480 } 481 482 fakeRuntime := testKubelet.fakeRuntime 483 fakeContainerManager := testKubelet.fakeContainerManager 484 fakeContainerManager.PodContainerManager.AddPodFromCgroups(pod) // add pod to mock cgroup 485 fakeRuntime.PodList = []*containertest.FakePod{ 486 {Pod: pod}, 487 } 488 kubelet := testKubelet.kubelet 489 kubelet.cgroupsPerQOS = true // enable cgroupsPerQOS to turn on the cgroups cleanup 490 491 // HandlePodCleanups gets called every 2 seconds within the Kubelet's 492 // housekeeping routine. This test registers the pod, removes the unwanted pod, then calls into 493 // HandlePodCleanups a few more times. We should only see one Destroy() event. podKiller runs 494 // within a goroutine so a two second delay should be enough time to 495 // mark the pod as killed (within this test case). 496 497 kubelet.HandlePodCleanups(ctx) 498 499 // assert that unwanted pods were killed 500 if actual, expected := kubelet.podWorkers.(*fakePodWorkers).triggeredDeletion, []types.UID{"12345678"}; !reflect.DeepEqual(actual, expected) { 501 t.Fatalf("expected %v to be deleted, got %v", expected, actual) 502 } 503 fakeRuntime.AssertKilledPods([]string(nil)) 504 505 // simulate Runtime.KillPod 506 fakeRuntime.PodList = nil 507 508 kubelet.HandlePodCleanups(ctx) 509 kubelet.HandlePodCleanups(ctx) 510 kubelet.HandlePodCleanups(ctx) 511 512 destroyCount := 0 513 err := wait.Poll(100*time.Millisecond, 10*time.Second, func() (bool, error) { 514 fakeContainerManager.PodContainerManager.Lock() 515 defer fakeContainerManager.PodContainerManager.Unlock() 516 destroyCount = 0 517 for _, functionName := range fakeContainerManager.PodContainerManager.CalledFunctions { 518 if functionName == "Destroy" { 519 destroyCount = destroyCount + 1 520 } 521 } 522 return destroyCount >= 1, nil 523 }) 524 525 assert.NoError(t, err, "wait should not return error") 526 // housekeeping can get called multiple times. The cgroup Destroy() is 527 // done within a goroutine and can get called multiple times, so the 528 // Destroy() count in not deterministic on the actual number. 529 // https://github.com/kubernetes/kubernetes/blob/29fdbb065b5e0d195299eb2d260b975cbc554673/pkg/kubelet/kubelet_pods.go#L2006 530 assert.True(t, destroyCount >= 1, "Expect 1 or more destroys") 531 } 532 533 func TestDispatchWorkOfCompletedPod(t *testing.T) { 534 testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) 535 defer testKubelet.Cleanup() 536 kubelet := testKubelet.kubelet 537 var got bool 538 kubelet.podWorkers = &fakePodWorkers{ 539 syncPodFn: func(ctx context.Context, updateType kubetypes.SyncPodType, pod, mirrorPod *v1.Pod, podStatus *kubecontainer.PodStatus) (bool, error) { 540 got = true 541 return false, nil 542 }, 543 cache: kubelet.podCache, 544 t: t, 545 } 546 now := metav1.NewTime(time.Now()) 547 pods := []*v1.Pod{ 548 { 549 ObjectMeta: metav1.ObjectMeta{ 550 UID: "1", 551 Name: "completed-pod1", 552 Namespace: "ns", 553 Annotations: make(map[string]string), 554 }, 555 Status: v1.PodStatus{ 556 Phase: v1.PodFailed, 557 ContainerStatuses: []v1.ContainerStatus{ 558 { 559 State: v1.ContainerState{ 560 Terminated: &v1.ContainerStateTerminated{}, 561 }, 562 }, 563 }, 564 }, 565 }, 566 { 567 ObjectMeta: metav1.ObjectMeta{ 568 UID: "2", 569 Name: "completed-pod2", 570 Namespace: "ns", 571 Annotations: make(map[string]string), 572 }, 573 Status: v1.PodStatus{ 574 Phase: v1.PodSucceeded, 575 ContainerStatuses: []v1.ContainerStatus{ 576 { 577 State: v1.ContainerState{ 578 Terminated: &v1.ContainerStateTerminated{}, 579 }, 580 }, 581 }, 582 }, 583 }, 584 { 585 ObjectMeta: metav1.ObjectMeta{ 586 UID: "3", 587 Name: "completed-pod3", 588 Namespace: "ns", 589 Annotations: make(map[string]string), 590 DeletionTimestamp: &now, 591 }, 592 Status: v1.PodStatus{ 593 ContainerStatuses: []v1.ContainerStatus{ 594 { 595 State: v1.ContainerState{ 596 Terminated: &v1.ContainerStateTerminated{}, 597 }, 598 }, 599 }, 600 }, 601 }, 602 } 603 for _, pod := range pods { 604 kubelet.podWorkers.UpdatePod(UpdatePodOptions{ 605 Pod: pod, 606 UpdateType: kubetypes.SyncPodSync, 607 StartTime: time.Now(), 608 }) 609 if !got { 610 t.Errorf("Should not skip completed pod %q", pod.Name) 611 } 612 got = false 613 } 614 } 615 616 func TestDispatchWorkOfActivePod(t *testing.T) { 617 testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) 618 defer testKubelet.Cleanup() 619 kubelet := testKubelet.kubelet 620 var got bool 621 kubelet.podWorkers = &fakePodWorkers{ 622 syncPodFn: func(ctx context.Context, updateType kubetypes.SyncPodType, pod, mirrorPod *v1.Pod, podStatus *kubecontainer.PodStatus) (bool, error) { 623 got = true 624 return false, nil 625 }, 626 cache: kubelet.podCache, 627 t: t, 628 } 629 pods := []*v1.Pod{ 630 { 631 ObjectMeta: metav1.ObjectMeta{ 632 UID: "1", 633 Name: "active-pod1", 634 Namespace: "ns", 635 Annotations: make(map[string]string), 636 }, 637 Status: v1.PodStatus{ 638 Phase: v1.PodRunning, 639 }, 640 }, 641 { 642 ObjectMeta: metav1.ObjectMeta{ 643 UID: "2", 644 Name: "active-pod2", 645 Namespace: "ns", 646 Annotations: make(map[string]string), 647 }, 648 Status: v1.PodStatus{ 649 Phase: v1.PodFailed, 650 ContainerStatuses: []v1.ContainerStatus{ 651 { 652 State: v1.ContainerState{ 653 Running: &v1.ContainerStateRunning{}, 654 }, 655 }, 656 }, 657 }, 658 }, 659 } 660 661 for _, pod := range pods { 662 kubelet.podWorkers.UpdatePod(UpdatePodOptions{ 663 Pod: pod, 664 UpdateType: kubetypes.SyncPodSync, 665 StartTime: time.Now(), 666 }) 667 if !got { 668 t.Errorf("Should not skip active pod %q", pod.Name) 669 } 670 got = false 671 } 672 } 673 674 func TestHandlePodCleanups(t *testing.T) { 675 ctx := context.Background() 676 testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) 677 defer testKubelet.Cleanup() 678 679 pod := &kubecontainer.Pod{ 680 ID: "12345678", 681 Name: "foo", 682 Namespace: "new", 683 Containers: []*kubecontainer.Container{ 684 {Name: "bar"}, 685 }, 686 } 687 688 fakeRuntime := testKubelet.fakeRuntime 689 fakeRuntime.PodList = []*containertest.FakePod{ 690 {Pod: pod}, 691 } 692 kubelet := testKubelet.kubelet 693 694 kubelet.HandlePodCleanups(ctx) 695 696 // assert that unwanted pods were queued to kill 697 if actual, expected := kubelet.podWorkers.(*fakePodWorkers).triggeredDeletion, []types.UID{"12345678"}; !reflect.DeepEqual(actual, expected) { 698 t.Fatalf("expected %v to be deleted, got %v", expected, actual) 699 } 700 fakeRuntime.AssertKilledPods([]string(nil)) 701 } 702 703 func TestHandlePodRemovesWhenSourcesAreReady(t *testing.T) { 704 if testing.Short() { 705 t.Skip("skipping test in short mode.") 706 } 707 708 ready := false 709 710 testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) 711 defer testKubelet.Cleanup() 712 713 fakePod := &kubecontainer.Pod{ 714 ID: "1", 715 Name: "foo", 716 Namespace: "new", 717 Containers: []*kubecontainer.Container{ 718 {Name: "bar"}, 719 }, 720 } 721 722 pods := []*v1.Pod{ 723 podWithUIDNameNs("1", "foo", "new"), 724 } 725 726 fakeRuntime := testKubelet.fakeRuntime 727 fakeRuntime.PodList = []*containertest.FakePod{ 728 {Pod: fakePod}, 729 } 730 kubelet := testKubelet.kubelet 731 kubelet.sourcesReady = config.NewSourcesReady(func(_ sets.String) bool { return ready }) 732 733 kubelet.HandlePodRemoves(pods) 734 time.Sleep(2 * time.Second) 735 736 // Sources are not ready yet. Don't remove any pods. 737 if expect, actual := []types.UID(nil), kubelet.podWorkers.(*fakePodWorkers).triggeredDeletion; !reflect.DeepEqual(expect, actual) { 738 t.Fatalf("expected %v kills, got %v", expect, actual) 739 } 740 741 ready = true 742 kubelet.HandlePodRemoves(pods) 743 time.Sleep(2 * time.Second) 744 745 // Sources are ready. Remove unwanted pods. 746 if expect, actual := []types.UID{"1"}, kubelet.podWorkers.(*fakePodWorkers).triggeredDeletion; !reflect.DeepEqual(expect, actual) { 747 t.Fatalf("expected %v kills, got %v", expect, actual) 748 } 749 } 750 751 type testNodeLister struct { 752 nodes []*v1.Node 753 } 754 755 func (nl testNodeLister) Get(name string) (*v1.Node, error) { 756 for _, node := range nl.nodes { 757 if node.Name == name { 758 return node, nil 759 } 760 } 761 return nil, fmt.Errorf("Node with name: %s does not exist", name) 762 } 763 764 func (nl testNodeLister) List(_ labels.Selector) (ret []*v1.Node, err error) { 765 return nl.nodes, nil 766 } 767 768 func checkPodStatus(t *testing.T, kl *Kubelet, pod *v1.Pod, phase v1.PodPhase) { 769 t.Helper() 770 status, found := kl.statusManager.GetPodStatus(pod.UID) 771 require.True(t, found, "Status of pod %q is not found in the status map", pod.UID) 772 require.Equal(t, phase, status.Phase) 773 } 774 775 // Tests that we handle port conflicts correctly by setting the failed status in status map. 776 func TestHandlePortConflicts(t *testing.T) { 777 testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) 778 defer testKubelet.Cleanup() 779 kl := testKubelet.kubelet 780 781 kl.nodeLister = testNodeLister{nodes: []*v1.Node{ 782 { 783 ObjectMeta: metav1.ObjectMeta{Name: string(kl.nodeName)}, 784 Status: v1.NodeStatus{ 785 Allocatable: v1.ResourceList{ 786 v1.ResourcePods: *resource.NewQuantity(110, resource.DecimalSI), 787 }, 788 }, 789 }, 790 }} 791 792 recorder := record.NewFakeRecorder(20) 793 nodeRef := &v1.ObjectReference{ 794 Kind: "Node", 795 Name: "testNode", 796 UID: types.UID("testNode"), 797 Namespace: "", 798 } 799 testClusterDNSDomain := "TEST" 800 kl.dnsConfigurer = dns.NewConfigurer(recorder, nodeRef, nil, nil, testClusterDNSDomain, "") 801 802 spec := v1.PodSpec{NodeName: string(kl.nodeName), Containers: []v1.Container{{Ports: []v1.ContainerPort{{HostPort: 80}}}}} 803 pods := []*v1.Pod{ 804 podWithUIDNameNsSpec("123456789", "newpod", "foo", spec), 805 podWithUIDNameNsSpec("987654321", "oldpod", "foo", spec), 806 } 807 // Make sure the Pods are in the reverse order of creation time. 808 pods[1].CreationTimestamp = metav1.NewTime(time.Now()) 809 pods[0].CreationTimestamp = metav1.NewTime(time.Now().Add(1 * time.Second)) 810 // The newer pod should be rejected. 811 notfittingPod := pods[0] 812 fittingPod := pods[1] 813 kl.podWorkers.(*fakePodWorkers).running = map[types.UID]bool{ 814 pods[0].UID: true, 815 pods[1].UID: true, 816 } 817 818 kl.HandlePodAdditions(pods) 819 820 // Check pod status stored in the status map. 821 checkPodStatus(t, kl, notfittingPod, v1.PodFailed) 822 checkPodStatus(t, kl, fittingPod, v1.PodPending) 823 } 824 825 // Tests that we handle host name conflicts correctly by setting the failed status in status map. 826 func TestHandleHostNameConflicts(t *testing.T) { 827 testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) 828 defer testKubelet.Cleanup() 829 kl := testKubelet.kubelet 830 831 kl.nodeLister = testNodeLister{nodes: []*v1.Node{ 832 { 833 ObjectMeta: metav1.ObjectMeta{Name: "127.0.0.1"}, 834 Status: v1.NodeStatus{ 835 Allocatable: v1.ResourceList{ 836 v1.ResourcePods: *resource.NewQuantity(110, resource.DecimalSI), 837 }, 838 }, 839 }, 840 }} 841 842 recorder := record.NewFakeRecorder(20) 843 nodeRef := &v1.ObjectReference{ 844 Kind: "Node", 845 Name: "testNode", 846 UID: types.UID("testNode"), 847 Namespace: "", 848 } 849 testClusterDNSDomain := "TEST" 850 kl.dnsConfigurer = dns.NewConfigurer(recorder, nodeRef, nil, nil, testClusterDNSDomain, "") 851 852 // default NodeName in test is 127.0.0.1 853 pods := []*v1.Pod{ 854 podWithUIDNameNsSpec("123456789", "notfittingpod", "foo", v1.PodSpec{NodeName: "127.0.0.2"}), 855 podWithUIDNameNsSpec("987654321", "fittingpod", "foo", v1.PodSpec{NodeName: "127.0.0.1"}), 856 } 857 858 notfittingPod := pods[0] 859 fittingPod := pods[1] 860 861 kl.HandlePodAdditions(pods) 862 863 // Check pod status stored in the status map. 864 checkPodStatus(t, kl, notfittingPod, v1.PodFailed) 865 checkPodStatus(t, kl, fittingPod, v1.PodPending) 866 } 867 868 // Tests that we handle not matching labels selector correctly by setting the failed status in status map. 869 func TestHandleNodeSelector(t *testing.T) { 870 testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) 871 defer testKubelet.Cleanup() 872 kl := testKubelet.kubelet 873 nodes := []*v1.Node{ 874 { 875 ObjectMeta: metav1.ObjectMeta{Name: testKubeletHostname, Labels: map[string]string{"key": "B"}}, 876 Status: v1.NodeStatus{ 877 Allocatable: v1.ResourceList{ 878 v1.ResourcePods: *resource.NewQuantity(110, resource.DecimalSI), 879 }, 880 }, 881 }, 882 } 883 kl.nodeLister = testNodeLister{nodes: nodes} 884 885 recorder := record.NewFakeRecorder(20) 886 nodeRef := &v1.ObjectReference{ 887 Kind: "Node", 888 Name: "testNode", 889 UID: types.UID("testNode"), 890 Namespace: "", 891 } 892 testClusterDNSDomain := "TEST" 893 kl.dnsConfigurer = dns.NewConfigurer(recorder, nodeRef, nil, nil, testClusterDNSDomain, "") 894 895 pods := []*v1.Pod{ 896 podWithUIDNameNsSpec("123456789", "podA", "foo", v1.PodSpec{NodeSelector: map[string]string{"key": "A"}}), 897 podWithUIDNameNsSpec("987654321", "podB", "foo", v1.PodSpec{NodeSelector: map[string]string{"key": "B"}}), 898 } 899 // The first pod should be rejected. 900 notfittingPod := pods[0] 901 fittingPod := pods[1] 902 903 kl.HandlePodAdditions(pods) 904 905 // Check pod status stored in the status map. 906 checkPodStatus(t, kl, notfittingPod, v1.PodFailed) 907 checkPodStatus(t, kl, fittingPod, v1.PodPending) 908 } 909 910 // Tests that we handle not matching labels selector correctly by setting the failed status in status map. 911 func TestHandleNodeSelectorBasedOnOS(t *testing.T) { 912 tests := []struct { 913 name string 914 nodeLabels map[string]string 915 podSelector map[string]string 916 podStatus v1.PodPhase 917 }{ 918 { 919 name: "correct OS label, wrong pod selector, admission denied", 920 nodeLabels: map[string]string{v1.LabelOSStable: goruntime.GOOS, v1.LabelArchStable: goruntime.GOARCH}, 921 podSelector: map[string]string{v1.LabelOSStable: "dummyOS"}, 922 podStatus: v1.PodFailed, 923 }, 924 { 925 name: "correct OS label, correct pod selector, admission denied", 926 nodeLabels: map[string]string{v1.LabelOSStable: goruntime.GOOS, v1.LabelArchStable: goruntime.GOARCH}, 927 podSelector: map[string]string{v1.LabelOSStable: goruntime.GOOS}, 928 podStatus: v1.PodPending, 929 }, 930 { 931 // Expect no patching to happen, label B should be preserved and can be used for nodeAffinity. 932 name: "new node label, correct pod selector, admitted", 933 nodeLabels: map[string]string{v1.LabelOSStable: goruntime.GOOS, v1.LabelArchStable: goruntime.GOARCH, "key": "B"}, 934 podSelector: map[string]string{"key": "B"}, 935 podStatus: v1.PodPending, 936 }, 937 } 938 for _, test := range tests { 939 t.Run(test.name, func(t *testing.T) { 940 testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) 941 defer testKubelet.Cleanup() 942 kl := testKubelet.kubelet 943 nodes := []*v1.Node{ 944 { 945 ObjectMeta: metav1.ObjectMeta{Name: testKubeletHostname, Labels: test.nodeLabels}, 946 Status: v1.NodeStatus{ 947 Allocatable: v1.ResourceList{ 948 v1.ResourcePods: *resource.NewQuantity(110, resource.DecimalSI), 949 }, 950 }, 951 }, 952 } 953 kl.nodeLister = testNodeLister{nodes: nodes} 954 955 recorder := record.NewFakeRecorder(20) 956 nodeRef := &v1.ObjectReference{ 957 Kind: "Node", 958 Name: "testNode", 959 UID: types.UID("testNode"), 960 Namespace: "", 961 } 962 testClusterDNSDomain := "TEST" 963 kl.dnsConfigurer = dns.NewConfigurer(recorder, nodeRef, nil, nil, testClusterDNSDomain, "") 964 965 pod := podWithUIDNameNsSpec("123456789", "podA", "foo", v1.PodSpec{NodeSelector: test.podSelector}) 966 967 kl.HandlePodAdditions([]*v1.Pod{pod}) 968 969 // Check pod status stored in the status map. 970 checkPodStatus(t, kl, pod, test.podStatus) 971 }) 972 } 973 } 974 975 // Tests that we handle exceeded resources correctly by setting the failed status in status map. 976 func TestHandleMemExceeded(t *testing.T) { 977 testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) 978 defer testKubelet.Cleanup() 979 kl := testKubelet.kubelet 980 nodes := []*v1.Node{ 981 {ObjectMeta: metav1.ObjectMeta{Name: testKubeletHostname}, 982 Status: v1.NodeStatus{Capacity: v1.ResourceList{}, Allocatable: v1.ResourceList{ 983 v1.ResourceCPU: *resource.NewMilliQuantity(10, resource.DecimalSI), 984 v1.ResourceMemory: *resource.NewQuantity(100, resource.BinarySI), 985 v1.ResourcePods: *resource.NewQuantity(40, resource.DecimalSI), 986 }}}, 987 } 988 kl.nodeLister = testNodeLister{nodes: nodes} 989 990 recorder := record.NewFakeRecorder(20) 991 nodeRef := &v1.ObjectReference{ 992 Kind: "Node", 993 Name: "testNode", 994 UID: types.UID("testNode"), 995 Namespace: "", 996 } 997 testClusterDNSDomain := "TEST" 998 kl.dnsConfigurer = dns.NewConfigurer(recorder, nodeRef, nil, nil, testClusterDNSDomain, "") 999 1000 spec := v1.PodSpec{NodeName: string(kl.nodeName), 1001 Containers: []v1.Container{{Resources: v1.ResourceRequirements{ 1002 Requests: v1.ResourceList{ 1003 v1.ResourceMemory: resource.MustParse("90"), 1004 }, 1005 }}}, 1006 } 1007 pods := []*v1.Pod{ 1008 podWithUIDNameNsSpec("123456789", "newpod", "foo", spec), 1009 podWithUIDNameNsSpec("987654321", "oldpod", "foo", spec), 1010 } 1011 // Make sure the Pods are in the reverse order of creation time. 1012 pods[1].CreationTimestamp = metav1.NewTime(time.Now()) 1013 pods[0].CreationTimestamp = metav1.NewTime(time.Now().Add(1 * time.Second)) 1014 // The newer pod should be rejected. 1015 notfittingPod := pods[0] 1016 fittingPod := pods[1] 1017 kl.podWorkers.(*fakePodWorkers).running = map[types.UID]bool{ 1018 pods[0].UID: true, 1019 pods[1].UID: true, 1020 } 1021 1022 kl.HandlePodAdditions(pods) 1023 1024 // Check pod status stored in the status map. 1025 checkPodStatus(t, kl, notfittingPod, v1.PodFailed) 1026 checkPodStatus(t, kl, fittingPod, v1.PodPending) 1027 } 1028 1029 // Tests that we handle result of interface UpdatePluginResources correctly 1030 // by setting corresponding status in status map. 1031 func TestHandlePluginResources(t *testing.T) { 1032 testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) 1033 defer testKubelet.Cleanup() 1034 kl := testKubelet.kubelet 1035 1036 adjustedResource := v1.ResourceName("domain1.com/adjustedResource") 1037 emptyResource := v1.ResourceName("domain2.com/emptyResource") 1038 missingResource := v1.ResourceName("domain2.com/missingResource") 1039 failedResource := v1.ResourceName("domain2.com/failedResource") 1040 resourceQuantity0 := *resource.NewQuantity(int64(0), resource.DecimalSI) 1041 resourceQuantity1 := *resource.NewQuantity(int64(1), resource.DecimalSI) 1042 resourceQuantity2 := *resource.NewQuantity(int64(2), resource.DecimalSI) 1043 resourceQuantityInvalid := *resource.NewQuantity(int64(-1), resource.DecimalSI) 1044 allowedPodQuantity := *resource.NewQuantity(int64(10), resource.DecimalSI) 1045 nodes := []*v1.Node{ 1046 {ObjectMeta: metav1.ObjectMeta{Name: testKubeletHostname}, 1047 Status: v1.NodeStatus{Capacity: v1.ResourceList{}, Allocatable: v1.ResourceList{ 1048 adjustedResource: resourceQuantity1, 1049 emptyResource: resourceQuantity0, 1050 v1.ResourcePods: allowedPodQuantity, 1051 }}}, 1052 } 1053 kl.nodeLister = testNodeLister{nodes: nodes} 1054 1055 updatePluginResourcesFunc := func(node *schedulerframework.NodeInfo, attrs *lifecycle.PodAdmitAttributes) error { 1056 // Maps from resourceName to the value we use to set node.allocatableResource[resourceName]. 1057 // A resource with invalid value (< 0) causes the function to return an error 1058 // to emulate resource Allocation failure. 1059 // Resources not contained in this map will have their node.allocatableResource 1060 // quantity unchanged. 1061 updateResourceMap := map[v1.ResourceName]resource.Quantity{ 1062 adjustedResource: resourceQuantity2, 1063 emptyResource: resourceQuantity0, 1064 failedResource: resourceQuantityInvalid, 1065 } 1066 pod := attrs.Pod 1067 newAllocatableResource := node.Allocatable.Clone() 1068 for _, container := range pod.Spec.Containers { 1069 for resource := range container.Resources.Requests { 1070 newQuantity, exist := updateResourceMap[resource] 1071 if !exist { 1072 continue 1073 } 1074 if newQuantity.Value() < 0 { 1075 return fmt.Errorf("Allocation failed") 1076 } 1077 newAllocatableResource.ScalarResources[resource] = newQuantity.Value() 1078 } 1079 } 1080 node.Allocatable = newAllocatableResource 1081 return nil 1082 } 1083 1084 // add updatePluginResourcesFunc to admission handler, to test it's behavior. 1085 kl.admitHandlers = lifecycle.PodAdmitHandlers{} 1086 kl.admitHandlers.AddPodAdmitHandler(lifecycle.NewPredicateAdmitHandler(kl.getNodeAnyWay, lifecycle.NewAdmissionFailureHandlerStub(), updatePluginResourcesFunc)) 1087 1088 recorder := record.NewFakeRecorder(20) 1089 nodeRef := &v1.ObjectReference{ 1090 Kind: "Node", 1091 Name: "testNode", 1092 UID: types.UID("testNode"), 1093 Namespace: "", 1094 } 1095 testClusterDNSDomain := "TEST" 1096 kl.dnsConfigurer = dns.NewConfigurer(recorder, nodeRef, nil, nil, testClusterDNSDomain, "") 1097 1098 // pod requiring adjustedResource can be successfully allocated because updatePluginResourcesFunc 1099 // adjusts node.allocatableResource for this resource to a sufficient value. 1100 fittingPodSpec := v1.PodSpec{NodeName: string(kl.nodeName), 1101 Containers: []v1.Container{{Resources: v1.ResourceRequirements{ 1102 Limits: v1.ResourceList{ 1103 adjustedResource: resourceQuantity2, 1104 }, 1105 Requests: v1.ResourceList{ 1106 adjustedResource: resourceQuantity2, 1107 }, 1108 }}}, 1109 } 1110 // pod requiring emptyResource (extended resources with 0 allocatable) will 1111 // not pass PredicateAdmit. 1112 emptyPodSpec := v1.PodSpec{NodeName: string(kl.nodeName), 1113 Containers: []v1.Container{{Resources: v1.ResourceRequirements{ 1114 Limits: v1.ResourceList{ 1115 emptyResource: resourceQuantity2, 1116 }, 1117 Requests: v1.ResourceList{ 1118 emptyResource: resourceQuantity2, 1119 }, 1120 }}}, 1121 } 1122 // pod requiring missingResource will pass PredicateAdmit. 1123 // 1124 // Extended resources missing in node status are ignored in PredicateAdmit. 1125 // This is required to support extended resources that are not managed by 1126 // device plugin, such as cluster-level resources. 1127 missingPodSpec := v1.PodSpec{NodeName: string(kl.nodeName), 1128 Containers: []v1.Container{{Resources: v1.ResourceRequirements{ 1129 Limits: v1.ResourceList{ 1130 missingResource: resourceQuantity2, 1131 }, 1132 Requests: v1.ResourceList{ 1133 missingResource: resourceQuantity2, 1134 }, 1135 }}}, 1136 } 1137 // pod requiring failedResource will fail with the resource failed to be allocated. 1138 failedPodSpec := v1.PodSpec{NodeName: string(kl.nodeName), 1139 Containers: []v1.Container{{Resources: v1.ResourceRequirements{ 1140 Limits: v1.ResourceList{ 1141 failedResource: resourceQuantity1, 1142 }, 1143 Requests: v1.ResourceList{ 1144 failedResource: resourceQuantity1, 1145 }, 1146 }}}, 1147 } 1148 1149 fittingPod := podWithUIDNameNsSpec("1", "fittingpod", "foo", fittingPodSpec) 1150 emptyPod := podWithUIDNameNsSpec("2", "emptypod", "foo", emptyPodSpec) 1151 missingPod := podWithUIDNameNsSpec("3", "missingpod", "foo", missingPodSpec) 1152 failedPod := podWithUIDNameNsSpec("4", "failedpod", "foo", failedPodSpec) 1153 1154 kl.HandlePodAdditions([]*v1.Pod{fittingPod, emptyPod, missingPod, failedPod}) 1155 1156 // Check pod status stored in the status map. 1157 checkPodStatus(t, kl, fittingPod, v1.PodPending) 1158 checkPodStatus(t, kl, emptyPod, v1.PodFailed) 1159 checkPodStatus(t, kl, missingPod, v1.PodPending) 1160 checkPodStatus(t, kl, failedPod, v1.PodFailed) 1161 } 1162 1163 // TODO(filipg): This test should be removed once StatusSyncer can do garbage collection without external signal. 1164 func TestPurgingObsoleteStatusMapEntries(t *testing.T) { 1165 ctx := context.Background() 1166 testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) 1167 defer testKubelet.Cleanup() 1168 1169 kl := testKubelet.kubelet 1170 pods := []*v1.Pod{ 1171 {ObjectMeta: metav1.ObjectMeta{Name: "pod1", UID: "1234"}, Spec: v1.PodSpec{Containers: []v1.Container{{Ports: []v1.ContainerPort{{HostPort: 80}}}}}}, 1172 {ObjectMeta: metav1.ObjectMeta{Name: "pod2", UID: "4567"}, Spec: v1.PodSpec{Containers: []v1.Container{{Ports: []v1.ContainerPort{{HostPort: 80}}}}}}, 1173 } 1174 podToTest := pods[1] 1175 // Run once to populate the status map. 1176 kl.HandlePodAdditions(pods) 1177 if _, found := kl.statusManager.GetPodStatus(podToTest.UID); !found { 1178 t.Fatalf("expected to have status cached for pod2") 1179 } 1180 // Sync with empty pods so that the entry in status map will be removed. 1181 kl.podManager.SetPods([]*v1.Pod{}) 1182 kl.HandlePodCleanups(ctx) 1183 if _, found := kl.statusManager.GetPodStatus(podToTest.UID); found { 1184 t.Fatalf("expected to not have status cached for pod2") 1185 } 1186 } 1187 1188 func TestValidateContainerLogStatus(t *testing.T) { 1189 testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) 1190 defer testKubelet.Cleanup() 1191 kubelet := testKubelet.kubelet 1192 containerName := "x" 1193 testCases := []struct { 1194 statuses []v1.ContainerStatus 1195 success bool // whether getting logs for the container should succeed. 1196 pSuccess bool // whether getting logs for the previous container should succeed. 1197 }{ 1198 { 1199 statuses: []v1.ContainerStatus{ 1200 { 1201 Name: containerName, 1202 State: v1.ContainerState{ 1203 Running: &v1.ContainerStateRunning{}, 1204 }, 1205 LastTerminationState: v1.ContainerState{ 1206 Terminated: &v1.ContainerStateTerminated{ContainerID: "docker://fakeid"}, 1207 }, 1208 }, 1209 }, 1210 success: true, 1211 pSuccess: true, 1212 }, 1213 { 1214 statuses: []v1.ContainerStatus{ 1215 { 1216 Name: containerName, 1217 State: v1.ContainerState{ 1218 Running: &v1.ContainerStateRunning{}, 1219 }, 1220 }, 1221 }, 1222 success: true, 1223 pSuccess: false, 1224 }, 1225 { 1226 statuses: []v1.ContainerStatus{ 1227 { 1228 Name: containerName, 1229 State: v1.ContainerState{ 1230 Terminated: &v1.ContainerStateTerminated{}, 1231 }, 1232 }, 1233 }, 1234 success: false, 1235 pSuccess: false, 1236 }, 1237 { 1238 statuses: []v1.ContainerStatus{ 1239 { 1240 Name: containerName, 1241 State: v1.ContainerState{ 1242 Terminated: &v1.ContainerStateTerminated{ContainerID: "docker://fakeid"}, 1243 }, 1244 }, 1245 }, 1246 success: true, 1247 pSuccess: false, 1248 }, 1249 { 1250 statuses: []v1.ContainerStatus{ 1251 { 1252 Name: containerName, 1253 State: v1.ContainerState{ 1254 Terminated: &v1.ContainerStateTerminated{}, 1255 }, 1256 LastTerminationState: v1.ContainerState{ 1257 Terminated: &v1.ContainerStateTerminated{}, 1258 }, 1259 }, 1260 }, 1261 success: false, 1262 pSuccess: false, 1263 }, 1264 { 1265 statuses: []v1.ContainerStatus{ 1266 { 1267 Name: containerName, 1268 State: v1.ContainerState{ 1269 Terminated: &v1.ContainerStateTerminated{}, 1270 }, 1271 LastTerminationState: v1.ContainerState{ 1272 Terminated: &v1.ContainerStateTerminated{ContainerID: "docker://fakeid"}, 1273 }, 1274 }, 1275 }, 1276 success: true, 1277 pSuccess: true, 1278 }, 1279 { 1280 statuses: []v1.ContainerStatus{ 1281 { 1282 Name: containerName, 1283 State: v1.ContainerState{ 1284 Waiting: &v1.ContainerStateWaiting{}, 1285 }, 1286 }, 1287 }, 1288 success: false, 1289 pSuccess: false, 1290 }, 1291 { 1292 statuses: []v1.ContainerStatus{ 1293 { 1294 Name: containerName, 1295 State: v1.ContainerState{Waiting: &v1.ContainerStateWaiting{Reason: "ErrImagePull"}}, 1296 }, 1297 }, 1298 success: false, 1299 pSuccess: false, 1300 }, 1301 { 1302 statuses: []v1.ContainerStatus{ 1303 { 1304 Name: containerName, 1305 State: v1.ContainerState{Waiting: &v1.ContainerStateWaiting{Reason: "ErrImagePullBackOff"}}, 1306 }, 1307 }, 1308 success: false, 1309 pSuccess: false, 1310 }, 1311 } 1312 1313 for i, tc := range testCases { 1314 // Access the log of the most recent container 1315 previous := false 1316 podStatus := &v1.PodStatus{ContainerStatuses: tc.statuses} 1317 _, err := kubelet.validateContainerLogStatus("podName", podStatus, containerName, previous) 1318 if !tc.success { 1319 assert.Error(t, err, fmt.Sprintf("[case %d] error", i)) 1320 } else { 1321 assert.NoError(t, err, "[case %d] error", i) 1322 } 1323 // Access the log of the previous, terminated container 1324 previous = true 1325 _, err = kubelet.validateContainerLogStatus("podName", podStatus, containerName, previous) 1326 if !tc.pSuccess { 1327 assert.Error(t, err, fmt.Sprintf("[case %d] error", i)) 1328 } else { 1329 assert.NoError(t, err, "[case %d] error", i) 1330 } 1331 // Access the log of a container that's not in the pod 1332 _, err = kubelet.validateContainerLogStatus("podName", podStatus, "blah", false) 1333 assert.Error(t, err, fmt.Sprintf("[case %d] invalid container name should cause an error", i)) 1334 } 1335 } 1336 1337 func TestCreateMirrorPod(t *testing.T) { 1338 tests := []struct { 1339 name string 1340 updateType kubetypes.SyncPodType 1341 }{ 1342 { 1343 name: "SyncPodCreate", 1344 updateType: kubetypes.SyncPodCreate, 1345 }, 1346 { 1347 name: "SyncPodUpdate", 1348 updateType: kubetypes.SyncPodUpdate, 1349 }, 1350 } 1351 for _, tt := range tests { 1352 tt := tt 1353 t.Run(tt.name, func(t *testing.T) { 1354 testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) 1355 defer testKubelet.Cleanup() 1356 1357 kl := testKubelet.kubelet 1358 manager := testKubelet.fakeMirrorClient 1359 pod := podWithUIDNameNs("12345678", "bar", "foo") 1360 pod.Annotations[kubetypes.ConfigSourceAnnotationKey] = "file" 1361 pods := []*v1.Pod{pod} 1362 kl.podManager.SetPods(pods) 1363 isTerminal, err := kl.SyncPod(context.Background(), tt.updateType, pod, nil, &kubecontainer.PodStatus{}) 1364 assert.NoError(t, err) 1365 if isTerminal { 1366 t.Fatalf("pod should not be terminal: %#v", pod) 1367 } 1368 podFullName := kubecontainer.GetPodFullName(pod) 1369 assert.True(t, manager.HasPod(podFullName), "Expected mirror pod %q to be created", podFullName) 1370 assert.Equal(t, 1, manager.NumOfPods(), "Expected only 1 mirror pod %q, got %+v", podFullName, manager.GetPods()) 1371 }) 1372 } 1373 } 1374 1375 func TestDeleteOutdatedMirrorPod(t *testing.T) { 1376 testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) 1377 defer testKubelet.Cleanup() 1378 1379 kl := testKubelet.kubelet 1380 manager := testKubelet.fakeMirrorClient 1381 pod := podWithUIDNameNsSpec("12345678", "foo", "ns", v1.PodSpec{ 1382 Containers: []v1.Container{ 1383 {Name: "1234", Image: "foo"}, 1384 }, 1385 }) 1386 pod.Annotations[kubetypes.ConfigSourceAnnotationKey] = "file" 1387 1388 // Mirror pod has an outdated spec. 1389 mirrorPod := podWithUIDNameNsSpec("11111111", "foo", "ns", v1.PodSpec{ 1390 Containers: []v1.Container{ 1391 {Name: "1234", Image: "bar"}, 1392 }, 1393 }) 1394 mirrorPod.Annotations[kubetypes.ConfigSourceAnnotationKey] = "api" 1395 mirrorPod.Annotations[kubetypes.ConfigMirrorAnnotationKey] = "mirror" 1396 1397 pods := []*v1.Pod{pod, mirrorPod} 1398 kl.podManager.SetPods(pods) 1399 isTerminal, err := kl.SyncPod(context.Background(), kubetypes.SyncPodUpdate, pod, mirrorPod, &kubecontainer.PodStatus{}) 1400 assert.NoError(t, err) 1401 if isTerminal { 1402 t.Fatalf("pod should not be terminal: %#v", pod) 1403 } 1404 name := kubecontainer.GetPodFullName(pod) 1405 creates, deletes := manager.GetCounts(name) 1406 if creates != 1 || deletes != 1 { 1407 t.Errorf("expected 1 creation and 1 deletion of %q, got %d, %d", name, creates, deletes) 1408 } 1409 } 1410 1411 func TestDeleteOrphanedMirrorPods(t *testing.T) { 1412 ctx := context.Background() 1413 testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) 1414 defer testKubelet.Cleanup() 1415 1416 kl := testKubelet.kubelet 1417 manager := testKubelet.fakeMirrorClient 1418 orphanPods := []*v1.Pod{ 1419 { 1420 ObjectMeta: metav1.ObjectMeta{ 1421 UID: "12345678", 1422 Name: "pod1", 1423 Namespace: "ns", 1424 Annotations: map[string]string{ 1425 kubetypes.ConfigSourceAnnotationKey: "api", 1426 kubetypes.ConfigMirrorAnnotationKey: "mirror", 1427 }, 1428 }, 1429 }, 1430 { 1431 ObjectMeta: metav1.ObjectMeta{ 1432 UID: "12345679", 1433 Name: "pod2", 1434 Namespace: "ns", 1435 Annotations: map[string]string{ 1436 kubetypes.ConfigSourceAnnotationKey: "api", 1437 kubetypes.ConfigMirrorAnnotationKey: "mirror", 1438 }, 1439 }, 1440 }, 1441 { 1442 ObjectMeta: metav1.ObjectMeta{ 1443 UID: "12345670", 1444 Name: "pod3", 1445 Namespace: "ns", 1446 Annotations: map[string]string{ 1447 kubetypes.ConfigSourceAnnotationKey: "api", 1448 kubetypes.ConfigMirrorAnnotationKey: "mirror", 1449 }, 1450 }, 1451 }, 1452 } 1453 1454 kl.podManager.SetPods(orphanPods) 1455 1456 // a static pod that is terminating will not be deleted 1457 kl.podWorkers.(*fakePodWorkers).terminatingStaticPods = map[string]bool{ 1458 kubecontainer.GetPodFullName(orphanPods[2]): true, 1459 } 1460 1461 // Sync with an empty pod list to delete all mirror pods. 1462 kl.HandlePodCleanups(ctx) 1463 assert.Len(t, manager.GetPods(), 0, "Expected 0 mirror pods") 1464 for i, pod := range orphanPods { 1465 name := kubecontainer.GetPodFullName(pod) 1466 creates, deletes := manager.GetCounts(name) 1467 switch i { 1468 case 2: 1469 if creates != 0 || deletes != 0 { 1470 t.Errorf("expected 0 creation and 0 deletion of %q, got %d, %d", name, creates, deletes) 1471 } 1472 default: 1473 if creates != 0 || deletes != 1 { 1474 t.Errorf("expected 0 creation and one deletion of %q, got %d, %d", name, creates, deletes) 1475 } 1476 } 1477 } 1478 } 1479 1480 func TestNetworkErrorsWithoutHostNetwork(t *testing.T) { 1481 testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) 1482 defer testKubelet.Cleanup() 1483 kubelet := testKubelet.kubelet 1484 1485 kubelet.runtimeState.setNetworkState(fmt.Errorf("simulated network error")) 1486 1487 pod := podWithUIDNameNsSpec("12345678", "hostnetwork", "new", v1.PodSpec{ 1488 HostNetwork: false, 1489 1490 Containers: []v1.Container{ 1491 {Name: "foo"}, 1492 }, 1493 }) 1494 1495 kubelet.podManager.SetPods([]*v1.Pod{pod}) 1496 isTerminal, err := kubelet.SyncPod(context.Background(), kubetypes.SyncPodUpdate, pod, nil, &kubecontainer.PodStatus{}) 1497 assert.Error(t, err, "expected pod with hostNetwork=false to fail when network in error") 1498 if isTerminal { 1499 t.Fatalf("pod should not be terminal: %#v", pod) 1500 } 1501 1502 pod.Annotations[kubetypes.ConfigSourceAnnotationKey] = kubetypes.FileSource 1503 pod.Spec.HostNetwork = true 1504 isTerminal, err = kubelet.SyncPod(context.Background(), kubetypes.SyncPodUpdate, pod, nil, &kubecontainer.PodStatus{}) 1505 assert.NoError(t, err, "expected pod with hostNetwork=true to succeed when network in error") 1506 if isTerminal { 1507 t.Fatalf("pod should not be terminal: %#v", pod) 1508 } 1509 } 1510 1511 func TestFilterOutInactivePods(t *testing.T) { 1512 testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) 1513 defer testKubelet.Cleanup() 1514 kubelet := testKubelet.kubelet 1515 pods := newTestPods(8) 1516 now := metav1.NewTime(time.Now()) 1517 1518 // terminal pods are excluded 1519 pods[0].Status.Phase = v1.PodFailed 1520 pods[1].Status.Phase = v1.PodSucceeded 1521 1522 // deleted pod is included unless it's known to be terminated 1523 pods[2].Status.Phase = v1.PodRunning 1524 pods[2].DeletionTimestamp = &now 1525 pods[2].Status.ContainerStatuses = []v1.ContainerStatus{ 1526 {State: v1.ContainerState{ 1527 Running: &v1.ContainerStateRunning{ 1528 StartedAt: now, 1529 }, 1530 }}, 1531 } 1532 1533 // pending and running pods are included 1534 pods[3].Status.Phase = v1.PodPending 1535 pods[4].Status.Phase = v1.PodRunning 1536 1537 // pod that is running but has been rejected by admission is excluded 1538 pods[5].Status.Phase = v1.PodRunning 1539 kubelet.statusManager.SetPodStatus(pods[5], v1.PodStatus{Phase: v1.PodFailed}) 1540 1541 // pod that is running according to the api but is known terminated is excluded 1542 pods[6].Status.Phase = v1.PodRunning 1543 kubelet.podWorkers.(*fakePodWorkers).terminated = map[types.UID]bool{ 1544 pods[6].UID: true, 1545 } 1546 1547 // pod that is failed but still terminating is included (it may still be consuming 1548 // resources) 1549 pods[7].Status.Phase = v1.PodFailed 1550 kubelet.podWorkers.(*fakePodWorkers).terminationRequested = map[types.UID]bool{ 1551 pods[7].UID: true, 1552 } 1553 1554 expected := []*v1.Pod{pods[2], pods[3], pods[4], pods[7]} 1555 kubelet.podManager.SetPods(pods) 1556 actual := kubelet.filterOutInactivePods(pods) 1557 assert.Equal(t, expected, actual) 1558 } 1559 1560 func TestCheckpointContainer(t *testing.T) { 1561 testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) 1562 defer testKubelet.Cleanup() 1563 kubelet := testKubelet.kubelet 1564 1565 fakeRuntime := testKubelet.fakeRuntime 1566 containerID := kubecontainer.ContainerID{ 1567 Type: "test", 1568 ID: "abc1234", 1569 } 1570 1571 fakePod := &containertest.FakePod{ 1572 Pod: &kubecontainer.Pod{ 1573 ID: "12345678", 1574 Name: "podFoo", 1575 Namespace: "nsFoo", 1576 Containers: []*kubecontainer.Container{ 1577 { 1578 Name: "containerFoo", 1579 ID: containerID, 1580 }, 1581 }, 1582 }, 1583 } 1584 1585 fakeRuntime.PodList = []*containertest.FakePod{fakePod} 1586 wrongContainerName := "wrongContainerName" 1587 1588 tests := []struct { 1589 name string 1590 containerName string 1591 checkpointLocation string 1592 expectedStatus error 1593 expectedLocation string 1594 }{ 1595 { 1596 name: "Checkpoint with wrong container name", 1597 containerName: wrongContainerName, 1598 checkpointLocation: "", 1599 expectedStatus: fmt.Errorf("container %s not found", wrongContainerName), 1600 expectedLocation: "", 1601 }, 1602 { 1603 name: "Checkpoint with default checkpoint location", 1604 containerName: fakePod.Pod.Containers[0].Name, 1605 checkpointLocation: "", 1606 expectedStatus: nil, 1607 expectedLocation: filepath.Join( 1608 kubelet.getCheckpointsDir(), 1609 fmt.Sprintf( 1610 "checkpoint-%s_%s-%s", 1611 fakePod.Pod.Name, 1612 fakePod.Pod.Namespace, 1613 fakePod.Pod.Containers[0].Name, 1614 ), 1615 ), 1616 }, 1617 { 1618 name: "Checkpoint with ignored location", 1619 containerName: fakePod.Pod.Containers[0].Name, 1620 checkpointLocation: "somethingThatWillBeIgnored", 1621 expectedStatus: nil, 1622 expectedLocation: filepath.Join( 1623 kubelet.getCheckpointsDir(), 1624 fmt.Sprintf( 1625 "checkpoint-%s_%s-%s", 1626 fakePod.Pod.Name, 1627 fakePod.Pod.Namespace, 1628 fakePod.Pod.Containers[0].Name, 1629 ), 1630 ), 1631 }, 1632 } 1633 1634 for _, test := range tests { 1635 t.Run(test.name, func(t *testing.T) { 1636 ctx := context.Background() 1637 options := &runtimeapi.CheckpointContainerRequest{} 1638 if test.checkpointLocation != "" { 1639 options.Location = test.checkpointLocation 1640 } 1641 status := kubelet.CheckpointContainer( 1642 ctx, 1643 fakePod.Pod.ID, 1644 fmt.Sprintf( 1645 "%s_%s", 1646 fakePod.Pod.Name, 1647 fakePod.Pod.Namespace, 1648 ), 1649 test.containerName, 1650 options, 1651 ) 1652 require.Equal(t, test.expectedStatus, status) 1653 1654 if status != nil { 1655 return 1656 } 1657 1658 require.True( 1659 t, 1660 strings.HasPrefix( 1661 options.Location, 1662 test.expectedLocation, 1663 ), 1664 ) 1665 require.Equal( 1666 t, 1667 options.ContainerId, 1668 containerID.ID, 1669 ) 1670 1671 }) 1672 } 1673 } 1674 1675 func TestSyncPodsSetStatusToFailedForPodsThatRunTooLong(t *testing.T) { 1676 testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) 1677 defer testKubelet.Cleanup() 1678 fakeRuntime := testKubelet.fakeRuntime 1679 kubelet := testKubelet.kubelet 1680 1681 now := metav1.Now() 1682 startTime := metav1.NewTime(now.Time.Add(-1 * time.Minute)) 1683 exceededActiveDeadlineSeconds := int64(30) 1684 1685 pods := []*v1.Pod{ 1686 { 1687 ObjectMeta: metav1.ObjectMeta{ 1688 UID: "12345678", 1689 Name: "bar", 1690 Namespace: "new", 1691 }, 1692 Spec: v1.PodSpec{ 1693 Containers: []v1.Container{ 1694 {Name: "foo"}, 1695 }, 1696 ActiveDeadlineSeconds: &exceededActiveDeadlineSeconds, 1697 }, 1698 Status: v1.PodStatus{ 1699 StartTime: &startTime, 1700 }, 1701 }, 1702 } 1703 1704 fakeRuntime.PodList = []*containertest.FakePod{ 1705 {Pod: &kubecontainer.Pod{ 1706 ID: "12345678", 1707 Name: "bar", 1708 Namespace: "new", 1709 Containers: []*kubecontainer.Container{ 1710 {Name: "foo"}, 1711 }, 1712 }}, 1713 } 1714 1715 // Let the pod worker sets the status to fail after this sync. 1716 kubelet.HandlePodUpdates(pods) 1717 status, found := kubelet.statusManager.GetPodStatus(pods[0].UID) 1718 assert.True(t, found, "expected to found status for pod %q", pods[0].UID) 1719 assert.Equal(t, v1.PodFailed, status.Phase) 1720 // check pod status contains ContainerStatuses, etc. 1721 assert.NotNil(t, status.ContainerStatuses) 1722 } 1723 1724 func TestSyncPodsDoesNotSetPodsThatDidNotRunTooLongToFailed(t *testing.T) { 1725 testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) 1726 defer testKubelet.Cleanup() 1727 fakeRuntime := testKubelet.fakeRuntime 1728 1729 kubelet := testKubelet.kubelet 1730 1731 now := metav1.Now() 1732 startTime := metav1.NewTime(now.Time.Add(-1 * time.Minute)) 1733 exceededActiveDeadlineSeconds := int64(300) 1734 1735 pods := []*v1.Pod{ 1736 { 1737 ObjectMeta: metav1.ObjectMeta{ 1738 UID: "12345678", 1739 Name: "bar", 1740 Namespace: "new", 1741 }, 1742 Spec: v1.PodSpec{ 1743 Containers: []v1.Container{ 1744 {Name: "foo"}, 1745 }, 1746 ActiveDeadlineSeconds: &exceededActiveDeadlineSeconds, 1747 }, 1748 Status: v1.PodStatus{ 1749 StartTime: &startTime, 1750 }, 1751 }, 1752 } 1753 1754 fakeRuntime.PodList = []*containertest.FakePod{ 1755 {Pod: &kubecontainer.Pod{ 1756 ID: "12345678", 1757 Name: "bar", 1758 Namespace: "new", 1759 Containers: []*kubecontainer.Container{ 1760 {Name: "foo"}, 1761 }, 1762 }}, 1763 } 1764 1765 kubelet.podManager.SetPods(pods) 1766 kubelet.HandlePodUpdates(pods) 1767 status, found := kubelet.statusManager.GetPodStatus(pods[0].UID) 1768 assert.True(t, found, "expected to found status for pod %q", pods[0].UID) 1769 assert.NotEqual(t, v1.PodFailed, status.Phase) 1770 } 1771 1772 func podWithUIDNameNs(uid types.UID, name, namespace string) *v1.Pod { 1773 return &v1.Pod{ 1774 ObjectMeta: metav1.ObjectMeta{ 1775 UID: uid, 1776 Name: name, 1777 Namespace: namespace, 1778 Annotations: map[string]string{}, 1779 }, 1780 } 1781 } 1782 1783 func podWithUIDNameNsSpec(uid types.UID, name, namespace string, spec v1.PodSpec) *v1.Pod { 1784 pod := podWithUIDNameNs(uid, name, namespace) 1785 pod.Spec = spec 1786 return pod 1787 } 1788 1789 func TestDeletePodDirsForDeletedPods(t *testing.T) { 1790 ctx := context.Background() 1791 testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) 1792 defer testKubelet.Cleanup() 1793 kl := testKubelet.kubelet 1794 pods := []*v1.Pod{ 1795 podWithUIDNameNs("12345678", "pod1", "ns"), 1796 podWithUIDNameNs("12345679", "pod2", "ns"), 1797 } 1798 1799 kl.podManager.SetPods(pods) 1800 // Sync to create pod directories. 1801 kl.HandlePodSyncs(kl.podManager.GetPods()) 1802 for i := range pods { 1803 assert.True(t, dirExists(kl.getPodDir(pods[i].UID)), "Expected directory to exist for pod %d", i) 1804 } 1805 1806 // Pod 1 has been deleted and no longer exists. 1807 kl.podManager.SetPods([]*v1.Pod{pods[0]}) 1808 kl.HandlePodCleanups(ctx) 1809 assert.True(t, dirExists(kl.getPodDir(pods[0].UID)), "Expected directory to exist for pod 0") 1810 assert.False(t, dirExists(kl.getPodDir(pods[1].UID)), "Expected directory to be deleted for pod 1") 1811 } 1812 1813 func syncAndVerifyPodDir(t *testing.T, testKubelet *TestKubelet, pods []*v1.Pod, podsToCheck []*v1.Pod, shouldExist bool) { 1814 ctx := context.Background() 1815 t.Helper() 1816 kl := testKubelet.kubelet 1817 1818 kl.podManager.SetPods(pods) 1819 kl.HandlePodSyncs(pods) 1820 kl.HandlePodCleanups(ctx) 1821 for i, pod := range podsToCheck { 1822 exist := dirExists(kl.getPodDir(pod.UID)) 1823 assert.Equal(t, shouldExist, exist, "directory of pod %d", i) 1824 } 1825 } 1826 1827 func TestDoesNotDeletePodDirsForTerminatedPods(t *testing.T) { 1828 testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) 1829 defer testKubelet.Cleanup() 1830 kl := testKubelet.kubelet 1831 pods := []*v1.Pod{ 1832 podWithUIDNameNs("12345678", "pod1", "ns"), 1833 podWithUIDNameNs("12345679", "pod2", "ns"), 1834 podWithUIDNameNs("12345680", "pod3", "ns"), 1835 } 1836 syncAndVerifyPodDir(t, testKubelet, pods, pods, true) 1837 // Pod 1 failed, and pod 2 succeeded. None of the pod directories should be 1838 // deleted. 1839 kl.statusManager.SetPodStatus(pods[1], v1.PodStatus{Phase: v1.PodFailed}) 1840 kl.statusManager.SetPodStatus(pods[2], v1.PodStatus{Phase: v1.PodSucceeded}) 1841 syncAndVerifyPodDir(t, testKubelet, pods, pods, true) 1842 } 1843 1844 func TestDoesNotDeletePodDirsIfContainerIsRunning(t *testing.T) { 1845 testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) 1846 defer testKubelet.Cleanup() 1847 runningPod := &kubecontainer.Pod{ 1848 ID: "12345678", 1849 Name: "pod1", 1850 Namespace: "ns", 1851 } 1852 apiPod := podWithUIDNameNs(runningPod.ID, runningPod.Name, runningPod.Namespace) 1853 1854 // Sync once to create pod directory; confirm that the pod directory has 1855 // already been created. 1856 pods := []*v1.Pod{apiPod} 1857 testKubelet.kubelet.podWorkers.(*fakePodWorkers).running = map[types.UID]bool{apiPod.UID: true} 1858 syncAndVerifyPodDir(t, testKubelet, pods, []*v1.Pod{apiPod}, true) 1859 1860 // Pretend the pod is deleted from apiserver, but is still active on the node. 1861 // The pod directory should not be removed. 1862 pods = []*v1.Pod{} 1863 testKubelet.fakeRuntime.PodList = []*containertest.FakePod{{Pod: runningPod, NetnsPath: ""}} 1864 syncAndVerifyPodDir(t, testKubelet, pods, []*v1.Pod{apiPod}, true) 1865 1866 // The pod is deleted and also not active on the node. The pod directory 1867 // should be removed. 1868 pods = []*v1.Pod{} 1869 testKubelet.fakeRuntime.PodList = []*containertest.FakePod{} 1870 testKubelet.kubelet.podWorkers.(*fakePodWorkers).running = nil 1871 syncAndVerifyPodDir(t, testKubelet, pods, []*v1.Pod{apiPod}, false) 1872 } 1873 1874 func TestGetPodsToSync(t *testing.T) { 1875 testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) 1876 defer testKubelet.Cleanup() 1877 kubelet := testKubelet.kubelet 1878 clock := testKubelet.fakeClock 1879 pods := newTestPods(5) 1880 1881 exceededActiveDeadlineSeconds := int64(30) 1882 notYetActiveDeadlineSeconds := int64(120) 1883 startTime := metav1.NewTime(clock.Now()) 1884 pods[0].Status.StartTime = &startTime 1885 pods[0].Spec.ActiveDeadlineSeconds = &exceededActiveDeadlineSeconds 1886 pods[1].Status.StartTime = &startTime 1887 pods[1].Spec.ActiveDeadlineSeconds = ¬YetActiveDeadlineSeconds 1888 pods[2].Status.StartTime = &startTime 1889 pods[2].Spec.ActiveDeadlineSeconds = &exceededActiveDeadlineSeconds 1890 1891 kubelet.podManager.SetPods(pods) 1892 kubelet.workQueue.Enqueue(pods[2].UID, 0) 1893 kubelet.workQueue.Enqueue(pods[3].UID, 30*time.Second) 1894 kubelet.workQueue.Enqueue(pods[4].UID, 2*time.Minute) 1895 1896 clock.Step(1 * time.Minute) 1897 1898 expected := []*v1.Pod{pods[2], pods[3], pods[0]} 1899 podsToSync := kubelet.getPodsToSync() 1900 sort.Sort(podsByUID(expected)) 1901 sort.Sort(podsByUID(podsToSync)) 1902 assert.Equal(t, expected, podsToSync) 1903 } 1904 1905 func TestGenerateAPIPodStatusWithSortedContainers(t *testing.T) { 1906 testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) 1907 defer testKubelet.Cleanup() 1908 kubelet := testKubelet.kubelet 1909 numContainers := 10 1910 expectedOrder := []string{} 1911 cStatuses := []*kubecontainer.Status{} 1912 specContainerList := []v1.Container{} 1913 for i := 0; i < numContainers; i++ { 1914 id := fmt.Sprintf("%v", i) 1915 containerName := fmt.Sprintf("%vcontainer", id) 1916 expectedOrder = append(expectedOrder, containerName) 1917 cStatus := &kubecontainer.Status{ 1918 ID: kubecontainer.BuildContainerID("test", id), 1919 Name: containerName, 1920 } 1921 // Rearrange container statuses 1922 if i%2 == 0 { 1923 cStatuses = append(cStatuses, cStatus) 1924 } else { 1925 cStatuses = append([]*kubecontainer.Status{cStatus}, cStatuses...) 1926 } 1927 specContainerList = append(specContainerList, v1.Container{Name: containerName}) 1928 } 1929 pod := podWithUIDNameNs("uid1", "foo", "test") 1930 pod.Spec = v1.PodSpec{ 1931 Containers: specContainerList, 1932 } 1933 1934 status := &kubecontainer.PodStatus{ 1935 ID: pod.UID, 1936 Name: pod.Name, 1937 Namespace: pod.Namespace, 1938 ContainerStatuses: cStatuses, 1939 } 1940 for i := 0; i < 5; i++ { 1941 apiStatus := kubelet.generateAPIPodStatus(pod, status, false) 1942 for i, c := range apiStatus.ContainerStatuses { 1943 if expectedOrder[i] != c.Name { 1944 t.Fatalf("Container status not sorted, expected %v at index %d, but found %v", expectedOrder[i], i, c.Name) 1945 } 1946 } 1947 } 1948 } 1949 1950 func verifyContainerStatuses(t *testing.T, statuses []v1.ContainerStatus, expectedState, expectedLastTerminationState map[string]v1.ContainerState, message string) { 1951 for _, s := range statuses { 1952 assert.Equal(t, expectedState[s.Name], s.State, "%s: state", message) 1953 assert.Equal(t, expectedLastTerminationState[s.Name], s.LastTerminationState, "%s: last terminated state", message) 1954 } 1955 } 1956 1957 // Test generateAPIPodStatus with different reason cache and old api pod status. 1958 func TestGenerateAPIPodStatusWithReasonCache(t *testing.T) { 1959 // The following waiting reason and message are generated in convertStatusToAPIStatus() 1960 testTimestamp := time.Unix(123456789, 987654321) 1961 testErrorReason := fmt.Errorf("test-error") 1962 emptyContainerID := (&kubecontainer.ContainerID{}).String() 1963 testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) 1964 defer testKubelet.Cleanup() 1965 kubelet := testKubelet.kubelet 1966 pod := podWithUIDNameNs("12345678", "foo", "new") 1967 pod.Spec = v1.PodSpec{RestartPolicy: v1.RestartPolicyOnFailure} 1968 1969 podStatus := &kubecontainer.PodStatus{ 1970 ID: pod.UID, 1971 Name: pod.Name, 1972 Namespace: pod.Namespace, 1973 } 1974 tests := []struct { 1975 containers []v1.Container 1976 statuses []*kubecontainer.Status 1977 reasons map[string]error 1978 oldStatuses []v1.ContainerStatus 1979 expectedState map[string]v1.ContainerState 1980 // Only set expectedInitState when it is different from expectedState 1981 expectedInitState map[string]v1.ContainerState 1982 expectedLastTerminationState map[string]v1.ContainerState 1983 }{ 1984 // For container with no historical record, State should be Waiting, LastTerminationState should be retrieved from 1985 // old status from apiserver. 1986 { 1987 containers: []v1.Container{{Name: "without-old-record"}, {Name: "with-old-record"}}, 1988 statuses: []*kubecontainer.Status{}, 1989 reasons: map[string]error{}, 1990 oldStatuses: []v1.ContainerStatus{{ 1991 Name: "with-old-record", 1992 LastTerminationState: v1.ContainerState{Terminated: &v1.ContainerStateTerminated{}}, 1993 }}, 1994 expectedState: map[string]v1.ContainerState{ 1995 "without-old-record": {Waiting: &v1.ContainerStateWaiting{ 1996 Reason: ContainerCreating, 1997 }}, 1998 "with-old-record": {Waiting: &v1.ContainerStateWaiting{ 1999 Reason: ContainerCreating, 2000 }}, 2001 }, 2002 expectedInitState: map[string]v1.ContainerState{ 2003 "without-old-record": {Waiting: &v1.ContainerStateWaiting{ 2004 Reason: PodInitializing, 2005 }}, 2006 "with-old-record": {Waiting: &v1.ContainerStateWaiting{ 2007 Reason: PodInitializing, 2008 }}, 2009 }, 2010 expectedLastTerminationState: map[string]v1.ContainerState{ 2011 "with-old-record": {Terminated: &v1.ContainerStateTerminated{}}, 2012 }, 2013 }, 2014 // For running container, State should be Running, LastTerminationState should be retrieved from latest terminated status. 2015 { 2016 containers: []v1.Container{{Name: "running"}}, 2017 statuses: []*kubecontainer.Status{ 2018 { 2019 Name: "running", 2020 State: kubecontainer.ContainerStateRunning, 2021 StartedAt: testTimestamp, 2022 }, 2023 { 2024 Name: "running", 2025 State: kubecontainer.ContainerStateExited, 2026 ExitCode: 1, 2027 }, 2028 }, 2029 reasons: map[string]error{}, 2030 oldStatuses: []v1.ContainerStatus{}, 2031 expectedState: map[string]v1.ContainerState{ 2032 "running": {Running: &v1.ContainerStateRunning{ 2033 StartedAt: metav1.NewTime(testTimestamp), 2034 }}, 2035 }, 2036 expectedLastTerminationState: map[string]v1.ContainerState{ 2037 "running": {Terminated: &v1.ContainerStateTerminated{ 2038 ExitCode: 1, 2039 ContainerID: emptyContainerID, 2040 }}, 2041 }, 2042 }, 2043 // For terminated container: 2044 // * If there is no recent start error record, State should be Terminated, LastTerminationState should be retrieved from 2045 // second latest terminated status; 2046 // * If there is recent start error record, State should be Waiting, LastTerminationState should be retrieved from latest 2047 // terminated status; 2048 // * If ExitCode = 0, restart policy is RestartPolicyOnFailure, the container shouldn't be restarted. No matter there is 2049 // recent start error or not, State should be Terminated, LastTerminationState should be retrieved from second latest 2050 // terminated status. 2051 { 2052 containers: []v1.Container{{Name: "without-reason"}, {Name: "with-reason"}}, 2053 statuses: []*kubecontainer.Status{ 2054 { 2055 Name: "without-reason", 2056 State: kubecontainer.ContainerStateExited, 2057 ExitCode: 1, 2058 }, 2059 { 2060 Name: "with-reason", 2061 State: kubecontainer.ContainerStateExited, 2062 ExitCode: 2, 2063 }, 2064 { 2065 Name: "without-reason", 2066 State: kubecontainer.ContainerStateExited, 2067 ExitCode: 3, 2068 }, 2069 { 2070 Name: "with-reason", 2071 State: kubecontainer.ContainerStateExited, 2072 ExitCode: 4, 2073 }, 2074 { 2075 Name: "succeed", 2076 State: kubecontainer.ContainerStateExited, 2077 ExitCode: 0, 2078 }, 2079 { 2080 Name: "succeed", 2081 State: kubecontainer.ContainerStateExited, 2082 ExitCode: 5, 2083 }, 2084 }, 2085 reasons: map[string]error{"with-reason": testErrorReason, "succeed": testErrorReason}, 2086 oldStatuses: []v1.ContainerStatus{}, 2087 expectedState: map[string]v1.ContainerState{ 2088 "without-reason": {Terminated: &v1.ContainerStateTerminated{ 2089 ExitCode: 1, 2090 ContainerID: emptyContainerID, 2091 }}, 2092 "with-reason": {Waiting: &v1.ContainerStateWaiting{Reason: testErrorReason.Error()}}, 2093 "succeed": {Terminated: &v1.ContainerStateTerminated{ 2094 ExitCode: 0, 2095 ContainerID: emptyContainerID, 2096 }}, 2097 }, 2098 expectedLastTerminationState: map[string]v1.ContainerState{ 2099 "without-reason": {Terminated: &v1.ContainerStateTerminated{ 2100 ExitCode: 3, 2101 ContainerID: emptyContainerID, 2102 }}, 2103 "with-reason": {Terminated: &v1.ContainerStateTerminated{ 2104 ExitCode: 2, 2105 ContainerID: emptyContainerID, 2106 }}, 2107 "succeed": {Terminated: &v1.ContainerStateTerminated{ 2108 ExitCode: 5, 2109 ContainerID: emptyContainerID, 2110 }}, 2111 }, 2112 }, 2113 // For Unknown Container Status: 2114 // * In certain situations a container can be running and fail to retrieve the status which results in 2115 // * a transition to the Unknown state. Prior to this fix, a container would make an invalid transition 2116 // * from Running->Waiting. This test validates the correct behavior of transitioning from Running->Terminated. 2117 { 2118 containers: []v1.Container{{Name: "unknown"}}, 2119 statuses: []*kubecontainer.Status{ 2120 { 2121 Name: "unknown", 2122 State: kubecontainer.ContainerStateUnknown, 2123 }, 2124 { 2125 Name: "unknown", 2126 State: kubecontainer.ContainerStateRunning, 2127 }, 2128 }, 2129 reasons: map[string]error{}, 2130 oldStatuses: []v1.ContainerStatus{{ 2131 Name: "unknown", 2132 State: v1.ContainerState{Running: &v1.ContainerStateRunning{}}, 2133 }}, 2134 expectedState: map[string]v1.ContainerState{ 2135 "unknown": {Terminated: &v1.ContainerStateTerminated{ 2136 ExitCode: 137, 2137 Message: "The container could not be located when the pod was terminated", 2138 Reason: "ContainerStatusUnknown", 2139 }}, 2140 }, 2141 expectedLastTerminationState: map[string]v1.ContainerState{ 2142 "unknown": {Running: &v1.ContainerStateRunning{}}, 2143 }, 2144 }, 2145 } 2146 2147 for i, test := range tests { 2148 kubelet.reasonCache = NewReasonCache() 2149 for n, e := range test.reasons { 2150 kubelet.reasonCache.add(pod.UID, n, e, "") 2151 } 2152 pod.Spec.Containers = test.containers 2153 pod.Status.ContainerStatuses = test.oldStatuses 2154 podStatus.ContainerStatuses = test.statuses 2155 apiStatus := kubelet.generateAPIPodStatus(pod, podStatus, false) 2156 verifyContainerStatuses(t, apiStatus.ContainerStatuses, test.expectedState, test.expectedLastTerminationState, fmt.Sprintf("case %d", i)) 2157 } 2158 2159 // Everything should be the same for init containers 2160 for i, test := range tests { 2161 kubelet.reasonCache = NewReasonCache() 2162 for n, e := range test.reasons { 2163 kubelet.reasonCache.add(pod.UID, n, e, "") 2164 } 2165 pod.Spec.InitContainers = test.containers 2166 pod.Status.InitContainerStatuses = test.oldStatuses 2167 podStatus.ContainerStatuses = test.statuses 2168 apiStatus := kubelet.generateAPIPodStatus(pod, podStatus, false) 2169 expectedState := test.expectedState 2170 if test.expectedInitState != nil { 2171 expectedState = test.expectedInitState 2172 } 2173 verifyContainerStatuses(t, apiStatus.InitContainerStatuses, expectedState, test.expectedLastTerminationState, fmt.Sprintf("case %d", i)) 2174 } 2175 } 2176 2177 // Test generateAPIPodStatus with different restart policies. 2178 func TestGenerateAPIPodStatusWithDifferentRestartPolicies(t *testing.T) { 2179 testErrorReason := fmt.Errorf("test-error") 2180 emptyContainerID := (&kubecontainer.ContainerID{}).String() 2181 testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) 2182 defer testKubelet.Cleanup() 2183 kubelet := testKubelet.kubelet 2184 pod := podWithUIDNameNs("12345678", "foo", "new") 2185 containers := []v1.Container{{Name: "succeed"}, {Name: "failed"}} 2186 podStatus := &kubecontainer.PodStatus{ 2187 ID: pod.UID, 2188 Name: pod.Name, 2189 Namespace: pod.Namespace, 2190 ContainerStatuses: []*kubecontainer.Status{ 2191 { 2192 Name: "succeed", 2193 State: kubecontainer.ContainerStateExited, 2194 ExitCode: 0, 2195 }, 2196 { 2197 Name: "failed", 2198 State: kubecontainer.ContainerStateExited, 2199 ExitCode: 1, 2200 }, 2201 { 2202 Name: "succeed", 2203 State: kubecontainer.ContainerStateExited, 2204 ExitCode: 2, 2205 }, 2206 { 2207 Name: "failed", 2208 State: kubecontainer.ContainerStateExited, 2209 ExitCode: 3, 2210 }, 2211 }, 2212 } 2213 kubelet.reasonCache.add(pod.UID, "succeed", testErrorReason, "") 2214 kubelet.reasonCache.add(pod.UID, "failed", testErrorReason, "") 2215 for c, test := range []struct { 2216 restartPolicy v1.RestartPolicy 2217 expectedState map[string]v1.ContainerState 2218 expectedLastTerminationState map[string]v1.ContainerState 2219 // Only set expectedInitState when it is different from expectedState 2220 expectedInitState map[string]v1.ContainerState 2221 // Only set expectedInitLastTerminationState when it is different from expectedLastTerminationState 2222 expectedInitLastTerminationState map[string]v1.ContainerState 2223 }{ 2224 { 2225 restartPolicy: v1.RestartPolicyNever, 2226 expectedState: map[string]v1.ContainerState{ 2227 "succeed": {Terminated: &v1.ContainerStateTerminated{ 2228 ExitCode: 0, 2229 ContainerID: emptyContainerID, 2230 }}, 2231 "failed": {Terminated: &v1.ContainerStateTerminated{ 2232 ExitCode: 1, 2233 ContainerID: emptyContainerID, 2234 }}, 2235 }, 2236 expectedLastTerminationState: map[string]v1.ContainerState{ 2237 "succeed": {Terminated: &v1.ContainerStateTerminated{ 2238 ExitCode: 2, 2239 ContainerID: emptyContainerID, 2240 }}, 2241 "failed": {Terminated: &v1.ContainerStateTerminated{ 2242 ExitCode: 3, 2243 ContainerID: emptyContainerID, 2244 }}, 2245 }, 2246 }, 2247 { 2248 restartPolicy: v1.RestartPolicyOnFailure, 2249 expectedState: map[string]v1.ContainerState{ 2250 "succeed": {Terminated: &v1.ContainerStateTerminated{ 2251 ExitCode: 0, 2252 ContainerID: emptyContainerID, 2253 }}, 2254 "failed": {Waiting: &v1.ContainerStateWaiting{Reason: testErrorReason.Error()}}, 2255 }, 2256 expectedLastTerminationState: map[string]v1.ContainerState{ 2257 "succeed": {Terminated: &v1.ContainerStateTerminated{ 2258 ExitCode: 2, 2259 ContainerID: emptyContainerID, 2260 }}, 2261 "failed": {Terminated: &v1.ContainerStateTerminated{ 2262 ExitCode: 1, 2263 ContainerID: emptyContainerID, 2264 }}, 2265 }, 2266 }, 2267 { 2268 restartPolicy: v1.RestartPolicyAlways, 2269 expectedState: map[string]v1.ContainerState{ 2270 "succeed": {Waiting: &v1.ContainerStateWaiting{Reason: testErrorReason.Error()}}, 2271 "failed": {Waiting: &v1.ContainerStateWaiting{Reason: testErrorReason.Error()}}, 2272 }, 2273 expectedLastTerminationState: map[string]v1.ContainerState{ 2274 "succeed": {Terminated: &v1.ContainerStateTerminated{ 2275 ExitCode: 0, 2276 ContainerID: emptyContainerID, 2277 }}, 2278 "failed": {Terminated: &v1.ContainerStateTerminated{ 2279 ExitCode: 1, 2280 ContainerID: emptyContainerID, 2281 }}, 2282 }, 2283 // If the init container is terminated with exit code 0, it won't be restarted even when the 2284 // restart policy is RestartAlways. 2285 expectedInitState: map[string]v1.ContainerState{ 2286 "succeed": {Terminated: &v1.ContainerStateTerminated{ 2287 ExitCode: 0, 2288 ContainerID: emptyContainerID, 2289 }}, 2290 "failed": {Waiting: &v1.ContainerStateWaiting{Reason: testErrorReason.Error()}}, 2291 }, 2292 expectedInitLastTerminationState: map[string]v1.ContainerState{ 2293 "succeed": {Terminated: &v1.ContainerStateTerminated{ 2294 ExitCode: 2, 2295 ContainerID: emptyContainerID, 2296 }}, 2297 "failed": {Terminated: &v1.ContainerStateTerminated{ 2298 ExitCode: 1, 2299 ContainerID: emptyContainerID, 2300 }}, 2301 }, 2302 }, 2303 } { 2304 pod.Spec.RestartPolicy = test.restartPolicy 2305 // Test normal containers 2306 pod.Spec.Containers = containers 2307 apiStatus := kubelet.generateAPIPodStatus(pod, podStatus, false) 2308 expectedState, expectedLastTerminationState := test.expectedState, test.expectedLastTerminationState 2309 verifyContainerStatuses(t, apiStatus.ContainerStatuses, expectedState, expectedLastTerminationState, fmt.Sprintf("case %d", c)) 2310 pod.Spec.Containers = nil 2311 2312 // Test init containers 2313 pod.Spec.InitContainers = containers 2314 apiStatus = kubelet.generateAPIPodStatus(pod, podStatus, false) 2315 if test.expectedInitState != nil { 2316 expectedState = test.expectedInitState 2317 } 2318 if test.expectedInitLastTerminationState != nil { 2319 expectedLastTerminationState = test.expectedInitLastTerminationState 2320 } 2321 verifyContainerStatuses(t, apiStatus.InitContainerStatuses, expectedState, expectedLastTerminationState, fmt.Sprintf("case %d", c)) 2322 pod.Spec.InitContainers = nil 2323 } 2324 } 2325 2326 // testPodAdmitHandler is a lifecycle.PodAdmitHandler for testing. 2327 type testPodAdmitHandler struct { 2328 // list of pods to reject. 2329 podsToReject []*v1.Pod 2330 } 2331 2332 // Admit rejects all pods in the podsToReject list with a matching UID. 2333 func (a *testPodAdmitHandler) Admit(attrs *lifecycle.PodAdmitAttributes) lifecycle.PodAdmitResult { 2334 for _, podToReject := range a.podsToReject { 2335 if podToReject.UID == attrs.Pod.UID { 2336 return lifecycle.PodAdmitResult{Admit: false, Reason: "Rejected", Message: "Pod is rejected"} 2337 } 2338 } 2339 return lifecycle.PodAdmitResult{Admit: true} 2340 } 2341 2342 // Test verifies that the kubelet invokes an admission handler during HandlePodAdditions. 2343 func TestHandlePodAdditionsInvokesPodAdmitHandlers(t *testing.T) { 2344 testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) 2345 defer testKubelet.Cleanup() 2346 kl := testKubelet.kubelet 2347 kl.nodeLister = testNodeLister{nodes: []*v1.Node{ 2348 { 2349 ObjectMeta: metav1.ObjectMeta{Name: string(kl.nodeName)}, 2350 Status: v1.NodeStatus{ 2351 Allocatable: v1.ResourceList{ 2352 v1.ResourcePods: *resource.NewQuantity(110, resource.DecimalSI), 2353 }, 2354 }, 2355 }, 2356 }} 2357 2358 pods := []*v1.Pod{ 2359 { 2360 ObjectMeta: metav1.ObjectMeta{ 2361 UID: "123456789", 2362 Name: "podA", 2363 Namespace: "foo", 2364 }, 2365 }, 2366 { 2367 ObjectMeta: metav1.ObjectMeta{ 2368 UID: "987654321", 2369 Name: "podB", 2370 Namespace: "foo", 2371 }, 2372 }, 2373 } 2374 podToReject := pods[0] 2375 podToAdmit := pods[1] 2376 podsToReject := []*v1.Pod{podToReject} 2377 2378 kl.admitHandlers.AddPodAdmitHandler(&testPodAdmitHandler{podsToReject: podsToReject}) 2379 2380 kl.HandlePodAdditions(pods) 2381 2382 // Check pod status stored in the status map. 2383 checkPodStatus(t, kl, podToReject, v1.PodFailed) 2384 checkPodStatus(t, kl, podToAdmit, v1.PodPending) 2385 } 2386 2387 func TestPodResourceAllocationReset(t *testing.T) { 2388 featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.InPlacePodVerticalScaling, true) 2389 testKubelet := newTestKubelet(t, false) 2390 defer testKubelet.Cleanup() 2391 kubelet := testKubelet.kubelet 2392 kubelet.statusManager = status.NewFakeManager() 2393 2394 nodes := []*v1.Node{ 2395 { 2396 ObjectMeta: metav1.ObjectMeta{Name: testKubeletHostname}, 2397 Status: v1.NodeStatus{ 2398 Capacity: v1.ResourceList{ 2399 v1.ResourceCPU: resource.MustParse("8"), 2400 v1.ResourceMemory: resource.MustParse("8Gi"), 2401 }, 2402 Allocatable: v1.ResourceList{ 2403 v1.ResourceCPU: resource.MustParse("4"), 2404 v1.ResourceMemory: resource.MustParse("4Gi"), 2405 v1.ResourcePods: *resource.NewQuantity(40, resource.DecimalSI), 2406 }, 2407 }, 2408 }, 2409 } 2410 kubelet.nodeLister = testNodeLister{nodes: nodes} 2411 2412 cpu500m := resource.MustParse("500m") 2413 cpu800m := resource.MustParse("800m") 2414 mem500M := resource.MustParse("500Mi") 2415 mem800M := resource.MustParse("800Mi") 2416 cpu500mMem500MPodSpec := &v1.PodSpec{ 2417 Containers: []v1.Container{ 2418 { 2419 Name: "c1", 2420 Resources: v1.ResourceRequirements{ 2421 Requests: v1.ResourceList{v1.ResourceCPU: cpu500m, v1.ResourceMemory: mem500M}, 2422 }, 2423 }, 2424 }, 2425 } 2426 cpu800mMem800MPodSpec := cpu500mMem500MPodSpec.DeepCopy() 2427 cpu800mMem800MPodSpec.Containers[0].Resources.Requests = v1.ResourceList{v1.ResourceCPU: cpu800m, v1.ResourceMemory: mem800M} 2428 cpu800mPodSpec := cpu500mMem500MPodSpec.DeepCopy() 2429 cpu800mPodSpec.Containers[0].Resources.Requests = v1.ResourceList{v1.ResourceCPU: cpu800m} 2430 mem800MPodSpec := cpu500mMem500MPodSpec.DeepCopy() 2431 mem800MPodSpec.Containers[0].Resources.Requests = v1.ResourceList{v1.ResourceMemory: mem800M} 2432 2433 cpu500mPodSpec := cpu500mMem500MPodSpec.DeepCopy() 2434 cpu500mPodSpec.Containers[0].Resources.Requests = v1.ResourceList{v1.ResourceCPU: cpu500m} 2435 mem500MPodSpec := cpu500mMem500MPodSpec.DeepCopy() 2436 mem500MPodSpec.Containers[0].Resources.Requests = v1.ResourceList{v1.ResourceMemory: mem500M} 2437 emptyPodSpec := cpu500mMem500MPodSpec.DeepCopy() 2438 emptyPodSpec.Containers[0].Resources.Requests = v1.ResourceList{} 2439 2440 tests := []struct { 2441 name string 2442 pod *v1.Pod 2443 existingPodAllocation *v1.Pod 2444 expectedPodResourceAllocation state.PodResourceAllocation 2445 }{ 2446 { 2447 name: "Having both memory and cpu, resource allocation not exists", 2448 pod: podWithUIDNameNsSpec("1", "pod1", "foo", *cpu500mMem500MPodSpec), 2449 expectedPodResourceAllocation: state.PodResourceAllocation{ 2450 "1": map[string]v1.ResourceList{ 2451 cpu500mMem500MPodSpec.Containers[0].Name: cpu500mMem500MPodSpec.Containers[0].Resources.Requests, 2452 }, 2453 }, 2454 }, 2455 { 2456 name: "Having both memory and cpu, resource allocation exists", 2457 pod: podWithUIDNameNsSpec("2", "pod2", "foo", *cpu500mMem500MPodSpec), 2458 existingPodAllocation: podWithUIDNameNsSpec("2", "pod2", "foo", *cpu500mMem500MPodSpec), 2459 expectedPodResourceAllocation: state.PodResourceAllocation{ 2460 "2": map[string]v1.ResourceList{ 2461 cpu500mMem500MPodSpec.Containers[0].Name: cpu500mMem500MPodSpec.Containers[0].Resources.Requests, 2462 }, 2463 }, 2464 }, 2465 { 2466 name: "Having both memory and cpu, resource allocation exists (with different value)", 2467 pod: podWithUIDNameNsSpec("3", "pod3", "foo", *cpu500mMem500MPodSpec), 2468 existingPodAllocation: podWithUIDNameNsSpec("3", "pod3", "foo", *cpu800mMem800MPodSpec), 2469 expectedPodResourceAllocation: state.PodResourceAllocation{ 2470 "3": map[string]v1.ResourceList{ 2471 cpu800mMem800MPodSpec.Containers[0].Name: cpu800mMem800MPodSpec.Containers[0].Resources.Requests, 2472 }, 2473 }, 2474 }, 2475 { 2476 name: "Only has cpu, resource allocation not exists", 2477 pod: podWithUIDNameNsSpec("4", "pod5", "foo", *cpu500mPodSpec), 2478 expectedPodResourceAllocation: state.PodResourceAllocation{ 2479 "4": map[string]v1.ResourceList{ 2480 cpu500mPodSpec.Containers[0].Name: cpu500mPodSpec.Containers[0].Resources.Requests, 2481 }, 2482 }, 2483 }, 2484 { 2485 name: "Only has cpu, resource allocation exists", 2486 pod: podWithUIDNameNsSpec("5", "pod5", "foo", *cpu500mPodSpec), 2487 existingPodAllocation: podWithUIDNameNsSpec("5", "pod5", "foo", *cpu500mPodSpec), 2488 expectedPodResourceAllocation: state.PodResourceAllocation{ 2489 "5": map[string]v1.ResourceList{ 2490 cpu500mPodSpec.Containers[0].Name: cpu500mPodSpec.Containers[0].Resources.Requests, 2491 }, 2492 }, 2493 }, 2494 { 2495 name: "Only has cpu, resource allocation exists (with different value)", 2496 pod: podWithUIDNameNsSpec("6", "pod6", "foo", *cpu500mPodSpec), 2497 existingPodAllocation: podWithUIDNameNsSpec("6", "pod6", "foo", *cpu800mPodSpec), 2498 expectedPodResourceAllocation: state.PodResourceAllocation{ 2499 "6": map[string]v1.ResourceList{ 2500 cpu800mPodSpec.Containers[0].Name: cpu800mPodSpec.Containers[0].Resources.Requests, 2501 }, 2502 }, 2503 }, 2504 { 2505 name: "Only has memory, resource allocation not exists", 2506 pod: podWithUIDNameNsSpec("7", "pod7", "foo", *mem500MPodSpec), 2507 expectedPodResourceAllocation: state.PodResourceAllocation{ 2508 "7": map[string]v1.ResourceList{ 2509 mem500MPodSpec.Containers[0].Name: mem500MPodSpec.Containers[0].Resources.Requests, 2510 }, 2511 }, 2512 }, 2513 { 2514 name: "Only has memory, resource allocation exists", 2515 pod: podWithUIDNameNsSpec("8", "pod8", "foo", *mem500MPodSpec), 2516 existingPodAllocation: podWithUIDNameNsSpec("8", "pod8", "foo", *mem500MPodSpec), 2517 expectedPodResourceAllocation: state.PodResourceAllocation{ 2518 "8": map[string]v1.ResourceList{ 2519 mem500MPodSpec.Containers[0].Name: mem500MPodSpec.Containers[0].Resources.Requests, 2520 }, 2521 }, 2522 }, 2523 { 2524 name: "Only has memory, resource allocation exists (with different value)", 2525 pod: podWithUIDNameNsSpec("9", "pod9", "foo", *mem500MPodSpec), 2526 existingPodAllocation: podWithUIDNameNsSpec("9", "pod9", "foo", *mem800MPodSpec), 2527 expectedPodResourceAllocation: state.PodResourceAllocation{ 2528 "9": map[string]v1.ResourceList{ 2529 mem800MPodSpec.Containers[0].Name: mem800MPodSpec.Containers[0].Resources.Requests, 2530 }, 2531 }, 2532 }, 2533 { 2534 name: "No CPU and memory, resource allocation not exists", 2535 pod: podWithUIDNameNsSpec("10", "pod10", "foo", *emptyPodSpec), 2536 expectedPodResourceAllocation: state.PodResourceAllocation{ 2537 "10": map[string]v1.ResourceList{ 2538 emptyPodSpec.Containers[0].Name: emptyPodSpec.Containers[0].Resources.Requests, 2539 }, 2540 }, 2541 }, 2542 { 2543 name: "No CPU and memory, resource allocation exists", 2544 pod: podWithUIDNameNsSpec("11", "pod11", "foo", *emptyPodSpec), 2545 existingPodAllocation: podWithUIDNameNsSpec("11", "pod11", "foo", *emptyPodSpec), 2546 expectedPodResourceAllocation: state.PodResourceAllocation{ 2547 "11": map[string]v1.ResourceList{ 2548 emptyPodSpec.Containers[0].Name: emptyPodSpec.Containers[0].Resources.Requests, 2549 }, 2550 }, 2551 }, 2552 } 2553 for _, tc := range tests { 2554 if tc.existingPodAllocation != nil { 2555 // when kubelet restarts, AllocatedResources has already existed before adding pod 2556 err := kubelet.statusManager.SetPodAllocation(tc.existingPodAllocation) 2557 if err != nil { 2558 t.Fatalf("failed to set pod allocation: %v", err) 2559 } 2560 } 2561 kubelet.HandlePodAdditions([]*v1.Pod{tc.pod}) 2562 2563 allocatedResources, found := kubelet.statusManager.GetContainerResourceAllocation(string(tc.pod.UID), tc.pod.Spec.Containers[0].Name) 2564 if !found { 2565 t.Fatalf("resource allocation should exist: (pod: %#v, container: %s)", tc.pod, tc.pod.Spec.Containers[0].Name) 2566 } 2567 assert.Equal(t, tc.expectedPodResourceAllocation[string(tc.pod.UID)][tc.pod.Spec.Containers[0].Name], allocatedResources, tc.name) 2568 } 2569 } 2570 2571 func TestHandlePodResourcesResize(t *testing.T) { 2572 featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.InPlacePodVerticalScaling, true) 2573 testKubelet := newTestKubelet(t, false) 2574 defer testKubelet.Cleanup() 2575 kubelet := testKubelet.kubelet 2576 kubelet.statusManager = status.NewFakeManager() 2577 2578 cpu500m := resource.MustParse("500m") 2579 cpu1000m := resource.MustParse("1") 2580 cpu1500m := resource.MustParse("1500m") 2581 cpu2500m := resource.MustParse("2500m") 2582 cpu5000m := resource.MustParse("5000m") 2583 mem500M := resource.MustParse("500Mi") 2584 mem1000M := resource.MustParse("1Gi") 2585 mem1500M := resource.MustParse("1500Mi") 2586 mem2500M := resource.MustParse("2500Mi") 2587 mem4500M := resource.MustParse("4500Mi") 2588 2589 nodes := []*v1.Node{ 2590 { 2591 ObjectMeta: metav1.ObjectMeta{Name: testKubeletHostname}, 2592 Status: v1.NodeStatus{ 2593 Capacity: v1.ResourceList{ 2594 v1.ResourceCPU: resource.MustParse("8"), 2595 v1.ResourceMemory: resource.MustParse("8Gi"), 2596 }, 2597 Allocatable: v1.ResourceList{ 2598 v1.ResourceCPU: resource.MustParse("4"), 2599 v1.ResourceMemory: resource.MustParse("4Gi"), 2600 v1.ResourcePods: *resource.NewQuantity(40, resource.DecimalSI), 2601 }, 2602 }, 2603 }, 2604 } 2605 kubelet.nodeLister = testNodeLister{nodes: nodes} 2606 2607 testPod1 := &v1.Pod{ 2608 ObjectMeta: metav1.ObjectMeta{ 2609 UID: "1111", 2610 Name: "pod1", 2611 Namespace: "ns1", 2612 }, 2613 Spec: v1.PodSpec{ 2614 Containers: []v1.Container{ 2615 { 2616 Name: "c1", 2617 Image: "i1", 2618 Resources: v1.ResourceRequirements{ 2619 Requests: v1.ResourceList{v1.ResourceCPU: cpu1000m, v1.ResourceMemory: mem1000M}, 2620 }, 2621 }, 2622 }, 2623 }, 2624 Status: v1.PodStatus{ 2625 Phase: v1.PodRunning, 2626 ContainerStatuses: []v1.ContainerStatus{ 2627 { 2628 Name: "c1", 2629 AllocatedResources: v1.ResourceList{v1.ResourceCPU: cpu1000m, v1.ResourceMemory: mem1000M}, 2630 Resources: &v1.ResourceRequirements{}, 2631 }, 2632 }, 2633 }, 2634 } 2635 testPod2 := testPod1.DeepCopy() 2636 testPod2.UID = "2222" 2637 testPod2.Name = "pod2" 2638 testPod2.Namespace = "ns2" 2639 testPod3 := testPod1.DeepCopy() 2640 testPod3.UID = "3333" 2641 testPod3.Name = "pod3" 2642 testPod3.Namespace = "ns2" 2643 2644 testKubelet.fakeKubeClient = fake.NewSimpleClientset(testPod1, testPod2, testPod3) 2645 kubelet.kubeClient = testKubelet.fakeKubeClient 2646 defer testKubelet.fakeKubeClient.ClearActions() 2647 kubelet.podManager.AddPod(testPod1) 2648 kubelet.podManager.AddPod(testPod2) 2649 kubelet.podManager.AddPod(testPod3) 2650 kubelet.podWorkers.(*fakePodWorkers).running = map[types.UID]bool{ 2651 testPod1.UID: true, 2652 testPod2.UID: true, 2653 testPod3.UID: true, 2654 } 2655 defer kubelet.podManager.RemovePod(testPod3) 2656 defer kubelet.podManager.RemovePod(testPod2) 2657 defer kubelet.podManager.RemovePod(testPod1) 2658 2659 tests := []struct { 2660 name string 2661 pod *v1.Pod 2662 newRequests v1.ResourceList 2663 expectedAllocations v1.ResourceList 2664 expectedResize v1.PodResizeStatus 2665 }{ 2666 { 2667 name: "Request CPU and memory decrease - expect InProgress", 2668 pod: testPod2, 2669 newRequests: v1.ResourceList{v1.ResourceCPU: cpu500m, v1.ResourceMemory: mem500M}, 2670 expectedAllocations: v1.ResourceList{v1.ResourceCPU: cpu500m, v1.ResourceMemory: mem500M}, 2671 expectedResize: v1.PodResizeStatusInProgress, 2672 }, 2673 { 2674 name: "Request CPU increase, memory decrease - expect InProgress", 2675 pod: testPod2, 2676 newRequests: v1.ResourceList{v1.ResourceCPU: cpu1500m, v1.ResourceMemory: mem500M}, 2677 expectedAllocations: v1.ResourceList{v1.ResourceCPU: cpu1500m, v1.ResourceMemory: mem500M}, 2678 expectedResize: v1.PodResizeStatusInProgress, 2679 }, 2680 { 2681 name: "Request CPU decrease, memory increase - expect InProgress", 2682 pod: testPod2, 2683 newRequests: v1.ResourceList{v1.ResourceCPU: cpu500m, v1.ResourceMemory: mem1500M}, 2684 expectedAllocations: v1.ResourceList{v1.ResourceCPU: cpu500m, v1.ResourceMemory: mem1500M}, 2685 expectedResize: v1.PodResizeStatusInProgress, 2686 }, 2687 { 2688 name: "Request CPU and memory increase beyond current capacity - expect Deferred", 2689 pod: testPod2, 2690 newRequests: v1.ResourceList{v1.ResourceCPU: cpu2500m, v1.ResourceMemory: mem2500M}, 2691 expectedAllocations: v1.ResourceList{v1.ResourceCPU: cpu1000m, v1.ResourceMemory: mem1000M}, 2692 expectedResize: v1.PodResizeStatusDeferred, 2693 }, 2694 { 2695 name: "Request CPU decrease and memory increase beyond current capacity - expect Deferred", 2696 pod: testPod2, 2697 newRequests: v1.ResourceList{v1.ResourceCPU: cpu500m, v1.ResourceMemory: mem2500M}, 2698 expectedAllocations: v1.ResourceList{v1.ResourceCPU: cpu1000m, v1.ResourceMemory: mem1000M}, 2699 expectedResize: v1.PodResizeStatusDeferred, 2700 }, 2701 { 2702 name: "Request memory increase beyond node capacity - expect Infeasible", 2703 pod: testPod2, 2704 newRequests: v1.ResourceList{v1.ResourceCPU: cpu1000m, v1.ResourceMemory: mem4500M}, 2705 expectedAllocations: v1.ResourceList{v1.ResourceCPU: cpu1000m, v1.ResourceMemory: mem1000M}, 2706 expectedResize: v1.PodResizeStatusInfeasible, 2707 }, 2708 { 2709 name: "Request CPU increase beyond node capacity - expect Infeasible", 2710 pod: testPod2, 2711 newRequests: v1.ResourceList{v1.ResourceCPU: cpu5000m, v1.ResourceMemory: mem1000M}, 2712 expectedAllocations: v1.ResourceList{v1.ResourceCPU: cpu1000m, v1.ResourceMemory: mem1000M}, 2713 expectedResize: v1.PodResizeStatusInfeasible, 2714 }, 2715 } 2716 2717 for _, tt := range tests { 2718 tt.pod.Spec.Containers[0].Resources.Requests = tt.newRequests 2719 tt.pod.Status.ContainerStatuses[0].AllocatedResources = v1.ResourceList{v1.ResourceCPU: cpu1000m, v1.ResourceMemory: mem1000M} 2720 kubelet.handlePodResourcesResize(tt.pod) 2721 updatedPod, found := kubelet.podManager.GetPodByName(tt.pod.Namespace, tt.pod.Name) 2722 assert.True(t, found, "expected to find pod %s", tt.pod.Name) 2723 assert.Equal(t, tt.expectedAllocations, updatedPod.Status.ContainerStatuses[0].AllocatedResources, tt.name) 2724 assert.Equal(t, tt.expectedResize, updatedPod.Status.Resize, tt.name) 2725 testKubelet.fakeKubeClient.ClearActions() 2726 } 2727 } 2728 2729 // testPodSyncLoopHandler is a lifecycle.PodSyncLoopHandler that is used for testing. 2730 type testPodSyncLoopHandler struct { 2731 // list of pods to sync 2732 podsToSync []*v1.Pod 2733 } 2734 2735 // ShouldSync evaluates if the pod should be synced from the kubelet. 2736 func (a *testPodSyncLoopHandler) ShouldSync(pod *v1.Pod) bool { 2737 for _, podToSync := range a.podsToSync { 2738 if podToSync.UID == pod.UID { 2739 return true 2740 } 2741 } 2742 return false 2743 } 2744 2745 // TestGetPodsToSyncInvokesPodSyncLoopHandlers ensures that the get pods to sync routine invokes the handler. 2746 func TestGetPodsToSyncInvokesPodSyncLoopHandlers(t *testing.T) { 2747 testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) 2748 defer testKubelet.Cleanup() 2749 kubelet := testKubelet.kubelet 2750 pods := newTestPods(5) 2751 expected := []*v1.Pod{pods[0]} 2752 kubelet.AddPodSyncLoopHandler(&testPodSyncLoopHandler{expected}) 2753 kubelet.podManager.SetPods(pods) 2754 2755 podsToSync := kubelet.getPodsToSync() 2756 sort.Sort(podsByUID(expected)) 2757 sort.Sort(podsByUID(podsToSync)) 2758 assert.Equal(t, expected, podsToSync) 2759 } 2760 2761 // testPodSyncHandler is a lifecycle.PodSyncHandler that is used for testing. 2762 type testPodSyncHandler struct { 2763 // list of pods to evict. 2764 podsToEvict []*v1.Pod 2765 // the reason for the eviction 2766 reason string 2767 // the message for the eviction 2768 message string 2769 } 2770 2771 // ShouldEvict evaluates if the pod should be evicted from the kubelet. 2772 func (a *testPodSyncHandler) ShouldEvict(pod *v1.Pod) lifecycle.ShouldEvictResponse { 2773 for _, podToEvict := range a.podsToEvict { 2774 if podToEvict.UID == pod.UID { 2775 return lifecycle.ShouldEvictResponse{Evict: true, Reason: a.reason, Message: a.message} 2776 } 2777 } 2778 return lifecycle.ShouldEvictResponse{Evict: false} 2779 } 2780 2781 // TestGenerateAPIPodStatusInvokesPodSyncHandlers invokes the handlers and reports the proper status 2782 func TestGenerateAPIPodStatusInvokesPodSyncHandlers(t *testing.T) { 2783 testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) 2784 defer testKubelet.Cleanup() 2785 kubelet := testKubelet.kubelet 2786 pod := newTestPods(1)[0] 2787 podsToEvict := []*v1.Pod{pod} 2788 kubelet.AddPodSyncHandler(&testPodSyncHandler{podsToEvict, "Evicted", "because"}) 2789 status := &kubecontainer.PodStatus{ 2790 ID: pod.UID, 2791 Name: pod.Name, 2792 Namespace: pod.Namespace, 2793 } 2794 apiStatus := kubelet.generateAPIPodStatus(pod, status, false) 2795 require.Equal(t, v1.PodFailed, apiStatus.Phase) 2796 require.Equal(t, "Evicted", apiStatus.Reason) 2797 require.Equal(t, "because", apiStatus.Message) 2798 } 2799 2800 func TestSyncTerminatingPodKillPod(t *testing.T) { 2801 testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) 2802 defer testKubelet.Cleanup() 2803 kl := testKubelet.kubelet 2804 pod := &v1.Pod{ 2805 ObjectMeta: metav1.ObjectMeta{ 2806 UID: "12345678", 2807 Name: "bar", 2808 Namespace: "foo", 2809 }, 2810 } 2811 pods := []*v1.Pod{pod} 2812 kl.podManager.SetPods(pods) 2813 podStatus := &kubecontainer.PodStatus{ID: pod.UID} 2814 gracePeriodOverride := int64(0) 2815 err := kl.SyncTerminatingPod(context.Background(), pod, podStatus, &gracePeriodOverride, func(podStatus *v1.PodStatus) { 2816 podStatus.Phase = v1.PodFailed 2817 podStatus.Reason = "reason" 2818 podStatus.Message = "message" 2819 }) 2820 require.NoError(t, err) 2821 2822 // Check pod status stored in the status map. 2823 checkPodStatus(t, kl, pod, v1.PodFailed) 2824 } 2825 2826 func TestSyncLabels(t *testing.T) { 2827 tests := []struct { 2828 name string 2829 existingNode *v1.Node 2830 isPatchingNeeded bool 2831 }{ 2832 { 2833 name: "no labels", 2834 existingNode: &v1.Node{ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{}}}, 2835 isPatchingNeeded: true, 2836 }, 2837 { 2838 name: "wrong labels", 2839 existingNode: &v1.Node{ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{v1.LabelOSStable: "dummyOS", v1.LabelArchStable: "dummyArch"}}}, 2840 isPatchingNeeded: true, 2841 }, 2842 { 2843 name: "correct labels", 2844 existingNode: &v1.Node{ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{v1.LabelOSStable: goruntime.GOOS, v1.LabelArchStable: goruntime.GOARCH}}}, 2845 isPatchingNeeded: false, 2846 }, 2847 { 2848 name: "partially correct labels", 2849 existingNode: &v1.Node{ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{v1.LabelOSStable: goruntime.GOOS, v1.LabelArchStable: "dummyArch"}}}, 2850 isPatchingNeeded: true, 2851 }, 2852 } 2853 2854 for _, test := range tests { 2855 t.Run(test.name, func(t *testing.T) { 2856 testKubelet := newTestKubelet(t, false) 2857 defer testKubelet.Cleanup() 2858 kl := testKubelet.kubelet 2859 kubeClient := testKubelet.fakeKubeClient 2860 2861 test.existingNode.Name = string(kl.nodeName) 2862 2863 kl.nodeLister = testNodeLister{nodes: []*v1.Node{test.existingNode}} 2864 go func() { kl.syncNodeStatus() }() 2865 2866 err := retryWithExponentialBackOff( 2867 100*time.Millisecond, 2868 func() (bool, error) { 2869 var savedNode *v1.Node 2870 if test.isPatchingNeeded { 2871 actions := kubeClient.Actions() 2872 if len(actions) == 0 { 2873 t.Logf("No action yet") 2874 return false, nil 2875 } 2876 for _, action := range actions { 2877 if action.GetVerb() == "patch" { 2878 var ( 2879 err error 2880 patchAction = action.(core.PatchActionImpl) 2881 patchContent = patchAction.GetPatch() 2882 ) 2883 savedNode, err = applyNodeStatusPatch(test.existingNode, patchContent) 2884 if err != nil { 2885 t.Logf("node patching failed, %v", err) 2886 return false, nil 2887 } 2888 } 2889 } 2890 } else { 2891 savedNode = test.existingNode 2892 } 2893 if savedNode == nil || savedNode.Labels == nil { 2894 t.Logf("savedNode.Labels should not be nil") 2895 return false, nil 2896 } 2897 val, ok := savedNode.Labels[v1.LabelOSStable] 2898 if !ok { 2899 t.Logf("expected kubernetes.io/os label to be present") 2900 return false, nil 2901 } 2902 if val != goruntime.GOOS { 2903 t.Logf("expected kubernetes.io/os to match runtime.GOOS but got %v", val) 2904 return false, nil 2905 } 2906 val, ok = savedNode.Labels[v1.LabelArchStable] 2907 if !ok { 2908 t.Logf("expected kubernetes.io/arch label to be present") 2909 return false, nil 2910 } 2911 if val != goruntime.GOARCH { 2912 t.Logf("expected kubernetes.io/arch to match runtime.GOARCH but got %v", val) 2913 return false, nil 2914 } 2915 return true, nil 2916 }, 2917 ) 2918 if err != nil { 2919 t.Fatalf("expected labels to be reconciled but it failed with %v", err) 2920 } 2921 }) 2922 } 2923 } 2924 2925 func waitForVolumeUnmount( 2926 volumeManager kubeletvolume.VolumeManager, 2927 pod *v1.Pod) error { 2928 var podVolumes kubecontainer.VolumeMap 2929 err := retryWithExponentialBackOff( 2930 time.Duration(50*time.Millisecond), 2931 func() (bool, error) { 2932 // Verify volumes detached 2933 podVolumes = volumeManager.GetMountedVolumesForPod( 2934 util.GetUniquePodName(pod)) 2935 2936 if len(podVolumes) != 0 { 2937 return false, nil 2938 } 2939 2940 return true, nil 2941 }, 2942 ) 2943 2944 if err != nil { 2945 return fmt.Errorf( 2946 "Expected volumes to be unmounted. But some volumes are still mounted: %#v", podVolumes) 2947 } 2948 2949 return nil 2950 } 2951 2952 func waitForVolumeDetach( 2953 volumeName v1.UniqueVolumeName, 2954 volumeManager kubeletvolume.VolumeManager) error { 2955 attachedVolumes := []v1.UniqueVolumeName{} 2956 err := retryWithExponentialBackOff( 2957 time.Duration(50*time.Millisecond), 2958 func() (bool, error) { 2959 // Verify volumes detached 2960 volumeAttached := volumeManager.VolumeIsAttached(volumeName) 2961 return !volumeAttached, nil 2962 }, 2963 ) 2964 2965 if err != nil { 2966 return fmt.Errorf( 2967 "Expected volumes to be detached. But some volumes are still attached: %#v", attachedVolumes) 2968 } 2969 2970 return nil 2971 } 2972 2973 func retryWithExponentialBackOff(initialDuration time.Duration, fn wait.ConditionFunc) error { 2974 backoff := wait.Backoff{ 2975 Duration: initialDuration, 2976 Factor: 3, 2977 Jitter: 0, 2978 Steps: 6, 2979 } 2980 return wait.ExponentialBackoff(backoff, fn) 2981 } 2982 2983 func simulateVolumeInUseUpdate( 2984 volumeName v1.UniqueVolumeName, 2985 stopCh <-chan struct{}, 2986 volumeManager kubeletvolume.VolumeManager) { 2987 ticker := time.NewTicker(100 * time.Millisecond) 2988 defer ticker.Stop() 2989 for { 2990 select { 2991 case <-ticker.C: 2992 volumeManager.MarkVolumesAsReportedInUse( 2993 []v1.UniqueVolumeName{volumeName}) 2994 case <-stopCh: 2995 return 2996 } 2997 } 2998 } 2999 3000 func runVolumeManager(kubelet *Kubelet) chan struct{} { 3001 stopCh := make(chan struct{}) 3002 go kubelet.volumeManager.Run(kubelet.sourcesReady, stopCh) 3003 return stopCh 3004 } 3005 3006 // dirExists returns true if the path exists and represents a directory. 3007 func dirExists(path string) bool { 3008 s, err := os.Stat(path) 3009 if err != nil { 3010 return false 3011 } 3012 return s.IsDir() 3013 } 3014 3015 // Sort pods by UID. 3016 type podsByUID []*v1.Pod 3017 3018 func (p podsByUID) Len() int { return len(p) } 3019 func (p podsByUID) Swap(i, j int) { p[i], p[j] = p[j], p[i] } 3020 func (p podsByUID) Less(i, j int) bool { return p[i].UID < p[j].UID } 3021 3022 // createAndStartFakeRemoteRuntime creates and starts fakeremote.RemoteRuntime. 3023 // It returns the RemoteRuntime, endpoint on success. 3024 // Users should call fakeRuntime.Stop() to cleanup the server. 3025 func createAndStartFakeRemoteRuntime(t *testing.T) (*fakeremote.RemoteRuntime, string) { 3026 endpoint, err := fakeremote.GenerateEndpoint() 3027 require.NoError(t, err) 3028 3029 fakeRuntime := fakeremote.NewFakeRemoteRuntime() 3030 fakeRuntime.Start(endpoint) 3031 3032 return fakeRuntime, endpoint 3033 } 3034 3035 func createRemoteRuntimeService(endpoint string, t *testing.T, tp oteltrace.TracerProvider) internalapi.RuntimeService { 3036 logger := klog.Background() 3037 runtimeService, err := remote.NewRemoteRuntimeService(endpoint, 15*time.Second, tp, &logger) 3038 require.NoError(t, err) 3039 return runtimeService 3040 } 3041 3042 func TestNewMainKubeletStandAlone(t *testing.T) { 3043 tempDir, err := os.MkdirTemp("", "logs") 3044 ContainerLogsDir = tempDir 3045 assert.NoError(t, err) 3046 defer os.RemoveAll(ContainerLogsDir) 3047 kubeCfg := &kubeletconfiginternal.KubeletConfiguration{ 3048 SyncFrequency: metav1.Duration{Duration: time.Minute}, 3049 ConfigMapAndSecretChangeDetectionStrategy: kubeletconfiginternal.WatchChangeDetectionStrategy, 3050 ContainerLogMaxSize: "10Mi", 3051 ContainerLogMaxFiles: 5, 3052 MemoryThrottlingFactor: utilpointer.Float64(0), 3053 } 3054 var prober volume.DynamicPluginProber 3055 tp := oteltrace.NewNoopTracerProvider() 3056 mockCtrl := gomock.NewController(t) 3057 defer mockCtrl.Finish() 3058 cadvisor := cadvisortest.NewMockInterface(mockCtrl) 3059 cadvisor.EXPECT().MachineInfo().Return(&cadvisorapi.MachineInfo{}, nil).AnyTimes() 3060 cadvisor.EXPECT().ImagesFsInfo().Return(cadvisorapiv2.FsInfo{ 3061 Usage: 400, 3062 Capacity: 1000, 3063 Available: 600, 3064 }, nil).AnyTimes() 3065 tlsOptions := &server.TLSOptions{ 3066 Config: &tls.Config{ 3067 MinVersion: 0, 3068 }, 3069 } 3070 fakeRuntime, endpoint := createAndStartFakeRemoteRuntime(t) 3071 defer func() { 3072 fakeRuntime.Stop() 3073 }() 3074 fakeRecorder := &record.FakeRecorder{} 3075 rtSvc := createRemoteRuntimeService(endpoint, t, oteltrace.NewNoopTracerProvider()) 3076 kubeDep := &Dependencies{ 3077 Auth: nil, 3078 CAdvisorInterface: cadvisor, 3079 Cloud: nil, 3080 ContainerManager: cm.NewStubContainerManager(), 3081 KubeClient: nil, // standalone mode 3082 HeartbeatClient: nil, 3083 EventClient: nil, 3084 TracerProvider: tp, 3085 HostUtil: hostutil.NewFakeHostUtil(nil), 3086 Mounter: mount.NewFakeMounter(nil), 3087 Recorder: fakeRecorder, 3088 RemoteRuntimeService: rtSvc, 3089 RemoteImageService: fakeRuntime.ImageService, 3090 Subpather: &subpath.FakeSubpath{}, 3091 OOMAdjuster: oom.NewOOMAdjuster(), 3092 OSInterface: kubecontainer.RealOS{}, 3093 DynamicPluginProber: prober, 3094 TLSOptions: tlsOptions, 3095 } 3096 crOptions := &config.ContainerRuntimeOptions{} 3097 3098 testMainKubelet, err := NewMainKubelet( 3099 kubeCfg, 3100 kubeDep, 3101 crOptions, 3102 "hostname", 3103 false, 3104 "hostname", 3105 []net.IP{}, 3106 "", 3107 "external", 3108 "/tmp/cert", 3109 "/tmp/rootdir", 3110 tempDir, 3111 "", 3112 "", 3113 false, 3114 []v1.Taint{}, 3115 []string{}, 3116 "", 3117 false, 3118 false, 3119 metav1.Duration{Duration: time.Minute}, 3120 1024, 3121 110, 3122 true, 3123 map[string]string{}, 3124 1024, 3125 false, 3126 ) 3127 assert.NoError(t, err, "NewMainKubelet should succeed") 3128 assert.NotNil(t, testMainKubelet, "testMainKubelet should not be nil") 3129 3130 testMainKubelet.BirthCry() 3131 testMainKubelet.StartGarbageCollection() 3132 // Nil pointer panic can be reproduced if configmap manager is not nil. 3133 // See https://github.com/kubernetes/kubernetes/issues/113492 3134 // pod := &v1.Pod{ 3135 // ObjectMeta: metav1.ObjectMeta{ 3136 // UID: "12345678", 3137 // Name: "bar", 3138 // Namespace: "foo", 3139 // }, 3140 // Spec: v1.PodSpec{ 3141 // Containers: []v1.Container{{ 3142 // EnvFrom: []v1.EnvFromSource{{ 3143 // ConfigMapRef: &v1.ConfigMapEnvSource{ 3144 // LocalObjectReference: v1.LocalObjectReference{Name: "config-map"}}}, 3145 // }}}, 3146 // Volumes: []v1.Volume{{ 3147 // VolumeSource: v1.VolumeSource{ 3148 // ConfigMap: &v1.ConfigMapVolumeSource{ 3149 // LocalObjectReference: v1.LocalObjectReference{ 3150 // Name: "config-map"}}}}}, 3151 // }, 3152 // } 3153 // testMainKubelet.configMapManager.RegisterPod(pod) 3154 // testMainKubelet.secretManager.RegisterPod(pod) 3155 assert.Nil(t, testMainKubelet.configMapManager, "configmap manager should be nil if kubelet is in standalone mode") 3156 assert.Nil(t, testMainKubelet.secretManager, "secret manager should be nil if kubelet is in standalone mode") 3157 } 3158 3159 func TestSyncPodSpans(t *testing.T) { 3160 testKubelet := newTestKubelet(t, false) 3161 kubelet := testKubelet.kubelet 3162 3163 recorder := record.NewFakeRecorder(20) 3164 nodeRef := &v1.ObjectReference{ 3165 Kind: "Node", 3166 Name: "testNode", 3167 UID: types.UID("testNode"), 3168 Namespace: "", 3169 } 3170 kubelet.dnsConfigurer = dns.NewConfigurer(recorder, nodeRef, nil, nil, "TEST", "") 3171 3172 kubeCfg := &kubeletconfiginternal.KubeletConfiguration{ 3173 SyncFrequency: metav1.Duration{Duration: time.Minute}, 3174 ConfigMapAndSecretChangeDetectionStrategy: kubeletconfiginternal.WatchChangeDetectionStrategy, 3175 ContainerLogMaxSize: "10Mi", 3176 ContainerLogMaxFiles: 5, 3177 MemoryThrottlingFactor: utilpointer.Float64(0), 3178 } 3179 3180 exp := tracetest.NewInMemoryExporter() 3181 tp := sdktrace.NewTracerProvider( 3182 sdktrace.WithSyncer(exp), 3183 ) 3184 kubelet.tracer = tp.Tracer(instrumentationScope) 3185 3186 fakeRuntime, endpoint := createAndStartFakeRemoteRuntime(t) 3187 defer func() { 3188 fakeRuntime.Stop() 3189 }() 3190 runtimeSvc := createRemoteRuntimeService(endpoint, t, tp) 3191 kubelet.runtimeService = runtimeSvc 3192 3193 fakeRuntime.ImageService.SetFakeImageSize(100) 3194 fakeRuntime.ImageService.SetFakeImages([]string{"test:latest"}) 3195 logger := klog.Background() 3196 imageSvc, err := remote.NewRemoteImageService(endpoint, 15*time.Second, tp, &logger) 3197 assert.NoError(t, err) 3198 3199 kubelet.containerRuntime, err = kuberuntime.NewKubeGenericRuntimeManager( 3200 kubelet.recorder, 3201 kubelet.livenessManager, 3202 kubelet.readinessManager, 3203 kubelet.startupManager, 3204 kubelet.rootDirectory, 3205 kubelet.podLogsDirectory, 3206 kubelet.machineInfo, 3207 kubelet.podWorkers, 3208 kubelet.os, 3209 kubelet, 3210 nil, 3211 kubelet.backOff, 3212 kubeCfg.SerializeImagePulls, 3213 kubeCfg.MaxParallelImagePulls, 3214 float32(kubeCfg.RegistryPullQPS), 3215 int(kubeCfg.RegistryBurst), 3216 "", 3217 "", 3218 kubeCfg.CPUCFSQuota, 3219 kubeCfg.CPUCFSQuotaPeriod, 3220 runtimeSvc, 3221 imageSvc, 3222 kubelet.containerManager, 3223 kubelet.containerLogManager, 3224 kubelet.runtimeClassManager, 3225 false, 3226 kubeCfg.MemorySwap.SwapBehavior, 3227 kubelet.containerManager.GetNodeAllocatableAbsolute, 3228 *kubeCfg.MemoryThrottlingFactor, 3229 kubeletutil.NewPodStartupLatencyTracker(), 3230 tp, 3231 ) 3232 assert.NoError(t, err) 3233 3234 pod := podWithUIDNameNsSpec("12345678", "foo", "new", v1.PodSpec{ 3235 Containers: []v1.Container{ 3236 { 3237 Name: "bar", 3238 Image: "test:latest", 3239 ImagePullPolicy: v1.PullAlways, 3240 }, 3241 }, 3242 EnableServiceLinks: utilpointer.Bool(false), 3243 }) 3244 3245 _, err = kubelet.SyncPod(context.Background(), kubetypes.SyncPodCreate, pod, nil, &kubecontainer.PodStatus{}) 3246 require.NoError(t, err) 3247 3248 require.NoError(t, err) 3249 assert.NotEmpty(t, exp.GetSpans()) 3250 3251 // find root span for syncPod 3252 var rootSpan *tracetest.SpanStub 3253 spans := exp.GetSpans() 3254 for i, span := range spans { 3255 if span.Name == "syncPod" { 3256 rootSpan = &spans[i] 3257 break 3258 } 3259 } 3260 assert.NotNil(t, rootSpan) 3261 3262 imageServiceSpans := make([]tracetest.SpanStub, 0) 3263 runtimeServiceSpans := make([]tracetest.SpanStub, 0) 3264 for _, span := range exp.GetSpans() { 3265 if span.SpanContext.TraceID() == rootSpan.SpanContext.TraceID() { 3266 switch { 3267 case strings.HasPrefix(span.Name, "runtime.v1.ImageService"): 3268 imageServiceSpans = append(imageServiceSpans, span) 3269 case strings.HasPrefix(span.Name, "runtime.v1.RuntimeService"): 3270 runtimeServiceSpans = append(runtimeServiceSpans, span) 3271 } 3272 } 3273 } 3274 assert.NotEmpty(t, imageServiceSpans, "syncPod trace should have image service spans") 3275 assert.NotEmpty(t, runtimeServiceSpans, "syncPod trace should have runtime service spans") 3276 3277 for _, span := range imageServiceSpans { 3278 assert.Equal(t, span.Parent.SpanID(), rootSpan.SpanContext.SpanID(), fmt.Sprintf("image service span %s %s should be child of root span", span.Name, span.Parent.SpanID())) 3279 } 3280 3281 for _, span := range runtimeServiceSpans { 3282 assert.Equal(t, span.Parent.SpanID(), rootSpan.SpanContext.SpanID(), fmt.Sprintf("runtime service span %s %s should be child of root span", span.Name, span.Parent.SpanID())) 3283 } 3284 }