k8s.io/kubernetes@v1.29.3/pkg/controller/controller_utils_test.go (about) 1 /* 2 Copyright 2015 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 controller 18 19 import ( 20 "context" 21 "encoding/json" 22 "fmt" 23 "math" 24 "math/rand" 25 "net/http/httptest" 26 "sort" 27 "sync" 28 "testing" 29 "time" 30 31 apps "k8s.io/api/apps/v1" 32 v1 "k8s.io/api/core/v1" 33 apiequality "k8s.io/apimachinery/pkg/api/equality" 34 apierrors "k8s.io/apimachinery/pkg/api/errors" 35 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 36 "k8s.io/apimachinery/pkg/runtime" 37 "k8s.io/apimachinery/pkg/runtime/schema" 38 "k8s.io/apimachinery/pkg/types" 39 "k8s.io/apimachinery/pkg/util/sets" 40 "k8s.io/apimachinery/pkg/util/uuid" 41 utilfeature "k8s.io/apiserver/pkg/util/feature" 42 clientset "k8s.io/client-go/kubernetes" 43 "k8s.io/client-go/kubernetes/fake" 44 clientscheme "k8s.io/client-go/kubernetes/scheme" 45 restclient "k8s.io/client-go/rest" 46 "k8s.io/client-go/tools/cache" 47 "k8s.io/client-go/tools/record" 48 utiltesting "k8s.io/client-go/util/testing" 49 featuregatetesting "k8s.io/component-base/featuregate/testing" 50 "k8s.io/kubernetes/pkg/apis/core" 51 _ "k8s.io/kubernetes/pkg/apis/core/install" 52 "k8s.io/kubernetes/pkg/controller/testutil" 53 "k8s.io/kubernetes/pkg/features" 54 "k8s.io/kubernetes/pkg/securitycontext" 55 "k8s.io/kubernetes/test/utils/ktesting" 56 testingclock "k8s.io/utils/clock/testing" 57 "k8s.io/utils/pointer" 58 59 "github.com/google/go-cmp/cmp" 60 "github.com/stretchr/testify/assert" 61 ) 62 63 // NewFakeControllerExpectationsLookup creates a fake store for PodExpectations. 64 func NewFakeControllerExpectationsLookup(ttl time.Duration) (*ControllerExpectations, *testingclock.FakeClock) { 65 fakeTime := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC) 66 fakeClock := testingclock.NewFakeClock(fakeTime) 67 ttlPolicy := &cache.TTLPolicy{TTL: ttl, Clock: fakeClock} 68 ttlStore := cache.NewFakeExpirationStore( 69 ExpKeyFunc, nil, ttlPolicy, fakeClock) 70 return &ControllerExpectations{ttlStore}, fakeClock 71 } 72 73 func newReplicationController(replicas int) *v1.ReplicationController { 74 rc := &v1.ReplicationController{ 75 TypeMeta: metav1.TypeMeta{APIVersion: "v1"}, 76 ObjectMeta: metav1.ObjectMeta{ 77 UID: uuid.NewUUID(), 78 Name: "foobar", 79 Namespace: metav1.NamespaceDefault, 80 ResourceVersion: "18", 81 }, 82 Spec: v1.ReplicationControllerSpec{ 83 Replicas: pointer.Int32(int32(replicas)), 84 Selector: map[string]string{"foo": "bar"}, 85 Template: &v1.PodTemplateSpec{ 86 ObjectMeta: metav1.ObjectMeta{ 87 Labels: map[string]string{ 88 "name": "foo", 89 "type": "production", 90 }, 91 }, 92 Spec: v1.PodSpec{ 93 Containers: []v1.Container{ 94 { 95 Image: "foo/bar", 96 TerminationMessagePath: v1.TerminationMessagePathDefault, 97 ImagePullPolicy: v1.PullIfNotPresent, 98 SecurityContext: securitycontext.ValidSecurityContextWithContainerDefaults(), 99 }, 100 }, 101 RestartPolicy: v1.RestartPolicyAlways, 102 DNSPolicy: v1.DNSDefault, 103 NodeSelector: map[string]string{ 104 "baz": "blah", 105 }, 106 }, 107 }, 108 }, 109 } 110 return rc 111 } 112 113 // create count pods with the given phase for the given rc (same selectors and namespace), and add them to the store. 114 func newPodList(store cache.Store, count int, status v1.PodPhase, rc *v1.ReplicationController) *v1.PodList { 115 pods := []v1.Pod{} 116 for i := 0; i < count; i++ { 117 newPod := v1.Pod{ 118 ObjectMeta: metav1.ObjectMeta{ 119 Name: fmt.Sprintf("pod%d", i), 120 Labels: rc.Spec.Selector, 121 Namespace: rc.Namespace, 122 }, 123 Status: v1.PodStatus{Phase: status}, 124 } 125 if store != nil { 126 store.Add(&newPod) 127 } 128 pods = append(pods, newPod) 129 } 130 return &v1.PodList{ 131 Items: pods, 132 } 133 } 134 135 func newReplicaSet(name string, replicas int, rsUuid types.UID) *apps.ReplicaSet { 136 return &apps.ReplicaSet{ 137 TypeMeta: metav1.TypeMeta{APIVersion: "v1"}, 138 ObjectMeta: metav1.ObjectMeta{ 139 UID: rsUuid, 140 Name: name, 141 Namespace: metav1.NamespaceDefault, 142 ResourceVersion: "18", 143 }, 144 Spec: apps.ReplicaSetSpec{ 145 Replicas: pointer.Int32(int32(replicas)), 146 Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}}, 147 Template: v1.PodTemplateSpec{ 148 ObjectMeta: metav1.ObjectMeta{ 149 Labels: map[string]string{ 150 "name": "foo", 151 "type": "production", 152 }, 153 }, 154 Spec: v1.PodSpec{ 155 Containers: []v1.Container{ 156 { 157 Image: "foo/bar", 158 TerminationMessagePath: v1.TerminationMessagePathDefault, 159 ImagePullPolicy: v1.PullIfNotPresent, 160 SecurityContext: securitycontext.ValidSecurityContextWithContainerDefaults(), 161 }, 162 }, 163 RestartPolicy: v1.RestartPolicyAlways, 164 DNSPolicy: v1.DNSDefault, 165 NodeSelector: map[string]string{ 166 "baz": "blah", 167 }, 168 }, 169 }, 170 }, 171 } 172 } 173 174 func TestControllerExpectations(t *testing.T) { 175 logger, _ := ktesting.NewTestContext(t) 176 ttl := 30 * time.Second 177 e, fakeClock := NewFakeControllerExpectationsLookup(ttl) 178 // In practice we can't really have add and delete expectations since we only either create or 179 // delete replicas in one rc pass, and the rc goes to sleep soon after until the expectations are 180 // either fulfilled or timeout. 181 adds, dels := 10, 30 182 rc := newReplicationController(1) 183 184 // RC fires off adds and deletes at apiserver, then sets expectations 185 rcKey, err := KeyFunc(rc) 186 assert.NoError(t, err, "Couldn't get key for object %#v: %v", rc, err) 187 188 e.SetExpectations(logger, rcKey, adds, dels) 189 var wg sync.WaitGroup 190 for i := 0; i < adds+1; i++ { 191 wg.Add(1) 192 go func() { 193 // In prod this can happen either because of a failed create by the rc 194 // or after having observed a create via informer 195 e.CreationObserved(logger, rcKey) 196 wg.Done() 197 }() 198 } 199 wg.Wait() 200 201 // There are still delete expectations 202 assert.False(t, e.SatisfiedExpectations(logger, rcKey), "Rc will sync before expectations are met") 203 204 for i := 0; i < dels+1; i++ { 205 wg.Add(1) 206 go func() { 207 e.DeletionObserved(logger, rcKey) 208 wg.Done() 209 }() 210 } 211 wg.Wait() 212 213 tests := []struct { 214 name string 215 expectationsToSet []int 216 expireExpectations bool 217 wantPodExpectations []int64 218 wantExpectationsSatisfied bool 219 }{ 220 { 221 name: "Expectations have been surpassed", 222 expireExpectations: false, 223 wantPodExpectations: []int64{int64(-1), int64(-1)}, 224 wantExpectationsSatisfied: true, 225 }, 226 { 227 name: "Old expectations are cleared because of ttl", 228 expectationsToSet: []int{1, 2}, 229 expireExpectations: true, 230 wantPodExpectations: []int64{int64(1), int64(2)}, 231 wantExpectationsSatisfied: false, 232 }, 233 } 234 235 for _, test := range tests { 236 t.Run(test.name, func(t *testing.T) { 237 if len(test.expectationsToSet) > 0 { 238 e.SetExpectations(logger, rcKey, test.expectationsToSet[0], test.expectationsToSet[1]) 239 } 240 podExp, exists, err := e.GetExpectations(rcKey) 241 assert.NoError(t, err, "Could not get expectations for rc, exists %v and err %v", exists, err) 242 assert.True(t, exists, "Could not get expectations for rc, exists %v and err %v", exists, err) 243 244 add, del := podExp.GetExpectations() 245 assert.Equal(t, test.wantPodExpectations[0], add, "Unexpected pod expectations %#v", podExp) 246 assert.Equal(t, test.wantPodExpectations[1], del, "Unexpected pod expectations %#v", podExp) 247 assert.Equal(t, test.wantExpectationsSatisfied, e.SatisfiedExpectations(logger, rcKey), "Expectations are met but the rc will not sync") 248 249 if test.expireExpectations { 250 fakeClock.Step(ttl + 1) 251 assert.True(t, e.SatisfiedExpectations(logger, rcKey), "Expectations should have expired but didn't") 252 } 253 }) 254 } 255 } 256 257 func TestUIDExpectations(t *testing.T) { 258 logger, _ := ktesting.NewTestContext(t) 259 uidExp := NewUIDTrackingControllerExpectations(NewControllerExpectations()) 260 type test struct { 261 name string 262 numReplicas int 263 } 264 265 shuffleTests := func(tests []test) { 266 for i := range tests { 267 j := rand.Intn(i + 1) 268 tests[i], tests[j] = tests[j], tests[i] 269 } 270 } 271 272 getRcDataFrom := func(test test) (string, []string) { 273 rc := newReplicationController(test.numReplicas) 274 275 rcName := fmt.Sprintf("rc-%v", test.numReplicas) 276 rc.Name = rcName 277 rc.Spec.Selector[rcName] = rcName 278 279 podList := newPodList(nil, 5, v1.PodRunning, rc) 280 rcKey, err := KeyFunc(rc) 281 if err != nil { 282 t.Fatalf("Couldn't get key for object %#v: %v", rc, err) 283 } 284 285 rcPodNames := []string{} 286 for i := range podList.Items { 287 p := &podList.Items[i] 288 p.Name = fmt.Sprintf("%v-%v", p.Name, rc.Name) 289 rcPodNames = append(rcPodNames, PodKey(p)) 290 } 291 uidExp.ExpectDeletions(logger, rcKey, rcPodNames) 292 293 return rcKey, rcPodNames 294 } 295 296 tests := []test{ 297 {name: "Replication controller with 2 replicas", numReplicas: 2}, 298 {name: "Replication controller with 1 replica", numReplicas: 1}, 299 {name: "Replication controller with no replicas", numReplicas: 0}, 300 {name: "Replication controller with 5 replicas", numReplicas: 5}, 301 } 302 303 shuffleTests(tests) 304 for _, test := range tests { 305 t.Run(test.name, func(t *testing.T) { 306 307 rcKey, rcPodNames := getRcDataFrom(test) 308 assert.False(t, uidExp.SatisfiedExpectations(logger, rcKey), 309 "Controller %v satisfied expectations before deletion", rcKey) 310 311 for _, p := range rcPodNames { 312 uidExp.DeletionObserved(logger, rcKey, p) 313 } 314 315 assert.True(t, uidExp.SatisfiedExpectations(logger, rcKey), 316 "Controller %v didn't satisfy expectations after deletion", rcKey) 317 318 uidExp.DeleteExpectations(logger, rcKey) 319 320 assert.Nil(t, uidExp.GetUIDs(rcKey), 321 "Failed to delete uid expectations for %v", rcKey) 322 }) 323 } 324 } 325 326 func TestCreatePodsWithGenerateName(t *testing.T) { 327 ns := metav1.NamespaceDefault 328 generateName := "hello-" 329 controllerSpec := newReplicationController(1) 330 controllerRef := metav1.NewControllerRef(controllerSpec, v1.SchemeGroupVersion.WithKind("ReplicationController")) 331 332 type test struct { 333 name string 334 podCreationFunc func(podControl RealPodControl) error 335 wantPod *v1.Pod 336 } 337 var tests = []test{ 338 { 339 name: "Create pod", 340 podCreationFunc: func(podControl RealPodControl) error { 341 return podControl.CreatePods(context.TODO(), ns, controllerSpec.Spec.Template, controllerSpec, controllerRef) 342 }, 343 wantPod: &v1.Pod{ 344 ObjectMeta: metav1.ObjectMeta{ 345 Labels: controllerSpec.Spec.Template.Labels, 346 GenerateName: fmt.Sprintf("%s-", controllerSpec.Name), 347 }, 348 Spec: controllerSpec.Spec.Template.Spec, 349 }, 350 }, 351 { 352 name: "Create pod with generate name", 353 podCreationFunc: func(podControl RealPodControl) error { 354 // Make sure createReplica sends a POST to the apiserver with a pod from the controllers pod template 355 return podControl.CreatePodsWithGenerateName(context.TODO(), ns, controllerSpec.Spec.Template, controllerSpec, controllerRef, generateName) 356 }, 357 wantPod: &v1.Pod{ 358 ObjectMeta: metav1.ObjectMeta{ 359 Labels: controllerSpec.Spec.Template.Labels, 360 GenerateName: generateName, 361 OwnerReferences: []metav1.OwnerReference{*controllerRef}, 362 }, 363 Spec: controllerSpec.Spec.Template.Spec, 364 }, 365 }, 366 } 367 368 for _, test := range tests { 369 t.Run(test.name, func(t *testing.T) { 370 body := runtime.EncodeOrDie(clientscheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "empty_pod"}}) 371 fakeHandler := utiltesting.FakeHandler{ 372 StatusCode: 200, 373 ResponseBody: string(body), 374 } 375 testServer := httptest.NewServer(&fakeHandler) 376 defer testServer.Close() 377 clientset := clientset.NewForConfigOrDie(&restclient.Config{Host: testServer.URL, ContentConfig: restclient.ContentConfig{GroupVersion: &schema.GroupVersion{Group: "", Version: "v1"}}}) 378 379 podControl := RealPodControl{ 380 KubeClient: clientset, 381 Recorder: &record.FakeRecorder{}, 382 } 383 384 err := test.podCreationFunc(podControl) 385 assert.NoError(t, err, "unexpected error: %v", err) 386 387 fakeHandler.ValidateRequest(t, "/api/v1/namespaces/default/pods", "POST", nil) 388 var actualPod = &v1.Pod{} 389 err = json.Unmarshal([]byte(fakeHandler.RequestBody), actualPod) 390 assert.NoError(t, err, "unexpected error: %v", err) 391 assert.True(t, apiequality.Semantic.DeepDerivative(test.wantPod, actualPod), 392 "Body: %s", fakeHandler.RequestBody) 393 }) 394 } 395 } 396 397 func TestDeletePodsAllowsMissing(t *testing.T) { 398 fakeClient := fake.NewSimpleClientset() 399 podControl := RealPodControl{ 400 KubeClient: fakeClient, 401 Recorder: &record.FakeRecorder{}, 402 } 403 404 controllerSpec := newReplicationController(1) 405 406 err := podControl.DeletePod(context.TODO(), "namespace-name", "podName", controllerSpec) 407 assert.True(t, apierrors.IsNotFound(err)) 408 } 409 410 func TestCountTerminatingPods(t *testing.T) { 411 now := metav1.Now() 412 413 // This rc is not needed by the test, only the newPodList to give the pods labels/a namespace. 414 rc := newReplicationController(0) 415 podList := newPodList(nil, 7, v1.PodRunning, rc) 416 podList.Items[0].Status.Phase = v1.PodSucceeded 417 podList.Items[1].Status.Phase = v1.PodFailed 418 podList.Items[2].Status.Phase = v1.PodPending 419 podList.Items[2].SetDeletionTimestamp(&now) 420 podList.Items[3].Status.Phase = v1.PodRunning 421 podList.Items[3].SetDeletionTimestamp(&now) 422 var podPointers []*v1.Pod 423 for i := range podList.Items { 424 podPointers = append(podPointers, &podList.Items[i]) 425 } 426 427 terminatingPods := CountTerminatingPods(podPointers) 428 429 assert.Equal(t, terminatingPods, int32(2)) 430 431 terminatingList := FilterTerminatingPods(podPointers) 432 assert.Equal(t, len(terminatingList), int(2)) 433 } 434 435 func TestActivePodFiltering(t *testing.T) { 436 logger, _ := ktesting.NewTestContext(t) 437 type podData struct { 438 podName string 439 podPhase v1.PodPhase 440 } 441 442 type test struct { 443 name string 444 pods []podData 445 wantPodNames []string 446 } 447 448 tests := []test{ 449 { 450 name: "Filters active pods", 451 pods: []podData{ 452 {podName: "pod-1", podPhase: v1.PodSucceeded}, 453 {podName: "pod-2", podPhase: v1.PodFailed}, 454 {podName: "pod-3"}, 455 {podName: "pod-4"}, 456 {podName: "pod-5"}, 457 }, 458 wantPodNames: []string{"pod-3", "pod-4", "pod-5"}, 459 }, 460 } 461 462 for _, test := range tests { 463 t.Run(test.name, func(t *testing.T) { 464 // This rc is not needed by the test, only the newPodList to give the pods labels/a namespace. 465 rc := newReplicationController(0) 466 podList := newPodList(nil, 5, v1.PodRunning, rc) 467 for idx, testPod := range test.pods { 468 podList.Items[idx].Name = testPod.podName 469 podList.Items[idx].Status.Phase = testPod.podPhase 470 } 471 472 var podPointers []*v1.Pod 473 for i := range podList.Items { 474 podPointers = append(podPointers, &podList.Items[i]) 475 } 476 got := FilterActivePods(logger, podPointers) 477 gotNames := sets.NewString() 478 for _, pod := range got { 479 gotNames.Insert(pod.Name) 480 } 481 482 if diff := cmp.Diff(test.wantPodNames, gotNames.List()); diff != "" { 483 t.Errorf("Active pod names (-want,+got):\n%s", diff) 484 } 485 }) 486 } 487 } 488 489 func TestSortingActivePods(t *testing.T) { 490 now := metav1.Now() 491 then := metav1.Time{Time: now.AddDate(0, -1, 0)} 492 493 tests := []struct { 494 name string 495 pods []v1.Pod 496 wantOrder []string 497 }{ 498 { 499 name: "Sorts by active pod", 500 pods: []v1.Pod{ 501 { 502 ObjectMeta: metav1.ObjectMeta{Name: "unscheduled"}, 503 Spec: v1.PodSpec{NodeName: ""}, 504 Status: v1.PodStatus{Phase: v1.PodPending}, 505 }, 506 { 507 ObjectMeta: metav1.ObjectMeta{Name: "scheduledButPending"}, 508 Spec: v1.PodSpec{NodeName: "bar"}, 509 Status: v1.PodStatus{Phase: v1.PodPending}, 510 }, 511 { 512 ObjectMeta: metav1.ObjectMeta{Name: "unknownPhase"}, 513 Spec: v1.PodSpec{NodeName: "foo"}, 514 Status: v1.PodStatus{Phase: v1.PodUnknown}, 515 }, 516 { 517 ObjectMeta: metav1.ObjectMeta{Name: "runningButNotReady"}, 518 Spec: v1.PodSpec{NodeName: "foo"}, 519 Status: v1.PodStatus{Phase: v1.PodRunning}, 520 }, 521 { 522 ObjectMeta: metav1.ObjectMeta{Name: "runningNoLastTransitionTime"}, 523 Spec: v1.PodSpec{NodeName: "foo"}, 524 Status: v1.PodStatus{ 525 Phase: v1.PodRunning, 526 Conditions: []v1.PodCondition{{Type: v1.PodReady, Status: v1.ConditionTrue}}, 527 ContainerStatuses: []v1.ContainerStatus{{RestartCount: 3}, {RestartCount: 0}}, 528 }, 529 }, 530 { 531 ObjectMeta: metav1.ObjectMeta{Name: "runningWithLastTransitionTime"}, 532 Spec: v1.PodSpec{NodeName: "foo"}, 533 Status: v1.PodStatus{ 534 Phase: v1.PodRunning, 535 Conditions: []v1.PodCondition{{Type: v1.PodReady, Status: v1.ConditionTrue, LastTransitionTime: now}}, 536 ContainerStatuses: []v1.ContainerStatus{{RestartCount: 3}, {RestartCount: 0}}, 537 }, 538 }, 539 { 540 ObjectMeta: metav1.ObjectMeta{Name: "runningLongerTime"}, 541 Spec: v1.PodSpec{NodeName: "foo"}, 542 Status: v1.PodStatus{ 543 Phase: v1.PodRunning, 544 Conditions: []v1.PodCondition{{Type: v1.PodReady, Status: v1.ConditionTrue, LastTransitionTime: then}}, 545 ContainerStatuses: []v1.ContainerStatus{{RestartCount: 3}, {RestartCount: 0}}, 546 }, 547 }, 548 { 549 ObjectMeta: metav1.ObjectMeta{Name: "lowerContainerRestartCount", CreationTimestamp: now}, 550 Spec: v1.PodSpec{NodeName: "foo"}, 551 Status: v1.PodStatus{ 552 Phase: v1.PodRunning, 553 Conditions: []v1.PodCondition{{Type: v1.PodReady, Status: v1.ConditionTrue, LastTransitionTime: then}}, 554 ContainerStatuses: []v1.ContainerStatus{{RestartCount: 2}, {RestartCount: 1}}, 555 }, 556 }, 557 { 558 ObjectMeta: metav1.ObjectMeta{Name: "oldest", CreationTimestamp: then}, 559 Spec: v1.PodSpec{NodeName: "foo"}, 560 Status: v1.PodStatus{ 561 Phase: v1.PodRunning, 562 Conditions: []v1.PodCondition{{Type: v1.PodReady, Status: v1.ConditionTrue, LastTransitionTime: then}}, 563 ContainerStatuses: []v1.ContainerStatus{{RestartCount: 2}, {RestartCount: 1}}, 564 }, 565 }, 566 }, 567 wantOrder: []string{ 568 "unscheduled", 569 "scheduledButPending", 570 "unknownPhase", 571 "runningButNotReady", 572 "runningNoLastTransitionTime", 573 "runningWithLastTransitionTime", 574 "runningLongerTime", 575 "lowerContainerRestartCount", 576 "oldest", 577 }, 578 }, 579 } 580 581 for _, test := range tests { 582 t.Run(test.name, func(t *testing.T) { 583 numPods := len(test.pods) 584 585 for i := 0; i < 20; i++ { 586 idx := rand.Perm(numPods) 587 randomizedPods := make([]*v1.Pod, numPods) 588 for j := 0; j < numPods; j++ { 589 randomizedPods[j] = &test.pods[idx[j]] 590 } 591 592 sort.Sort(ActivePods(randomizedPods)) 593 gotOrder := make([]string, len(randomizedPods)) 594 for i := range randomizedPods { 595 gotOrder[i] = randomizedPods[i].Name 596 } 597 598 if diff := cmp.Diff(test.wantOrder, gotOrder); diff != "" { 599 t.Errorf("Sorted active pod names (-want,+got):\n%s", diff) 600 } 601 } 602 }) 603 } 604 } 605 606 func TestSortingActivePodsWithRanks(t *testing.T) { 607 now := metav1.Now() 608 then1Month := metav1.Time{Time: now.AddDate(0, -1, 0)} 609 then2Hours := metav1.Time{Time: now.Add(-2 * time.Hour)} 610 then5Hours := metav1.Time{Time: now.Add(-5 * time.Hour)} 611 then8Hours := metav1.Time{Time: now.Add(-8 * time.Hour)} 612 zeroTime := metav1.Time{} 613 pod := func(podName, nodeName string, phase v1.PodPhase, ready bool, restarts int32, readySince metav1.Time, created metav1.Time, annotations map[string]string) *v1.Pod { 614 var conditions []v1.PodCondition 615 var containerStatuses []v1.ContainerStatus 616 if ready { 617 conditions = []v1.PodCondition{{Type: v1.PodReady, Status: v1.ConditionTrue, LastTransitionTime: readySince}} 618 containerStatuses = []v1.ContainerStatus{{RestartCount: restarts}} 619 } 620 return &v1.Pod{ 621 ObjectMeta: metav1.ObjectMeta{ 622 CreationTimestamp: created, 623 Name: podName, 624 Annotations: annotations, 625 }, 626 Spec: v1.PodSpec{NodeName: nodeName}, 627 Status: v1.PodStatus{ 628 Conditions: conditions, 629 ContainerStatuses: containerStatuses, 630 Phase: phase, 631 }, 632 } 633 } 634 var ( 635 unscheduledPod = pod("unscheduled", "", v1.PodPending, false, 0, zeroTime, zeroTime, nil) 636 scheduledPendingPod = pod("pending", "node", v1.PodPending, false, 0, zeroTime, zeroTime, nil) 637 unknownPhasePod = pod("unknown-phase", "node", v1.PodUnknown, false, 0, zeroTime, zeroTime, nil) 638 runningNotReadyPod = pod("not-ready", "node", v1.PodRunning, false, 0, zeroTime, zeroTime, nil) 639 runningReadyNoLastTransitionTimePod = pod("ready-no-last-transition-time", "node", v1.PodRunning, true, 0, zeroTime, zeroTime, nil) 640 runningReadyNow = pod("ready-now", "node", v1.PodRunning, true, 0, now, now, nil) 641 runningReadyThen = pod("ready-then", "node", v1.PodRunning, true, 0, then1Month, then1Month, nil) 642 runningReadyNowHighRestarts = pod("ready-high-restarts", "node", v1.PodRunning, true, 9001, now, now, nil) 643 runningReadyNowCreatedThen = pod("ready-now-created-then", "node", v1.PodRunning, true, 0, now, then1Month, nil) 644 lowPodDeletionCost = pod("low-deletion-cost", "node", v1.PodRunning, true, 0, now, then1Month, map[string]string{core.PodDeletionCost: "10"}) 645 highPodDeletionCost = pod("high-deletion-cost", "node", v1.PodRunning, true, 0, now, then1Month, map[string]string{core.PodDeletionCost: "100"}) 646 unscheduled5Hours = pod("unscheduled-5-hours", "", v1.PodPending, false, 0, then5Hours, then5Hours, nil) 647 unscheduled8Hours = pod("unscheduled-10-hours", "", v1.PodPending, false, 0, then8Hours, then8Hours, nil) 648 ready2Hours = pod("ready-2-hours", "", v1.PodRunning, true, 0, then2Hours, then1Month, nil) 649 ready5Hours = pod("ready-5-hours", "", v1.PodRunning, true, 0, then5Hours, then1Month, nil) 650 ready10Hours = pod("ready-10-hours", "", v1.PodRunning, true, 0, then8Hours, then1Month, nil) 651 ) 652 equalityTests := []struct { 653 p1 *v1.Pod 654 p2 *v1.Pod 655 disableLogarithmicScaleDown bool 656 }{ 657 {p1: unscheduledPod}, 658 {p1: scheduledPendingPod}, 659 {p1: unknownPhasePod}, 660 {p1: runningNotReadyPod}, 661 {p1: runningReadyNowCreatedThen}, 662 {p1: runningReadyNow}, 663 {p1: runningReadyThen}, 664 {p1: runningReadyNowHighRestarts}, 665 {p1: runningReadyNowCreatedThen}, 666 {p1: unscheduled5Hours, p2: unscheduled8Hours}, 667 {p1: ready5Hours, p2: ready10Hours}, 668 } 669 for i, test := range equalityTests { 670 t.Run(fmt.Sprintf("Equality tests %d", i), func(t *testing.T) { 671 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.LogarithmicScaleDown, !test.disableLogarithmicScaleDown)() 672 if test.p2 == nil { 673 test.p2 = test.p1 674 } 675 podsWithRanks := ActivePodsWithRanks{ 676 Pods: []*v1.Pod{test.p1, test.p2}, 677 Rank: []int{1, 1}, 678 Now: now, 679 } 680 if podsWithRanks.Less(0, 1) || podsWithRanks.Less(1, 0) { 681 t.Errorf("expected pod %q to be equivalent to %q", test.p1.Name, test.p2.Name) 682 } 683 }) 684 } 685 686 type podWithRank struct { 687 pod *v1.Pod 688 rank int 689 } 690 inequalityTests := []struct { 691 lesser, greater podWithRank 692 disablePodDeletioncost bool 693 disableLogarithmicScaleDown bool 694 }{ 695 {lesser: podWithRank{unscheduledPod, 1}, greater: podWithRank{scheduledPendingPod, 2}}, 696 {lesser: podWithRank{unscheduledPod, 2}, greater: podWithRank{scheduledPendingPod, 1}}, 697 {lesser: podWithRank{scheduledPendingPod, 1}, greater: podWithRank{unknownPhasePod, 2}}, 698 {lesser: podWithRank{unknownPhasePod, 1}, greater: podWithRank{runningNotReadyPod, 2}}, 699 {lesser: podWithRank{runningNotReadyPod, 1}, greater: podWithRank{runningReadyNoLastTransitionTimePod, 1}}, 700 {lesser: podWithRank{runningReadyNoLastTransitionTimePod, 1}, greater: podWithRank{runningReadyNow, 1}}, 701 {lesser: podWithRank{runningReadyNow, 2}, greater: podWithRank{runningReadyNoLastTransitionTimePod, 1}}, 702 {lesser: podWithRank{runningReadyNow, 1}, greater: podWithRank{runningReadyThen, 1}}, 703 {lesser: podWithRank{runningReadyNow, 2}, greater: podWithRank{runningReadyThen, 1}}, 704 {lesser: podWithRank{runningReadyNowHighRestarts, 1}, greater: podWithRank{runningReadyNow, 1}}, 705 {lesser: podWithRank{runningReadyNow, 2}, greater: podWithRank{runningReadyNowHighRestarts, 1}}, 706 {lesser: podWithRank{runningReadyNow, 1}, greater: podWithRank{runningReadyNowCreatedThen, 1}}, 707 {lesser: podWithRank{runningReadyNowCreatedThen, 2}, greater: podWithRank{runningReadyNow, 1}}, 708 {lesser: podWithRank{lowPodDeletionCost, 2}, greater: podWithRank{highPodDeletionCost, 1}}, 709 {lesser: podWithRank{highPodDeletionCost, 2}, greater: podWithRank{lowPodDeletionCost, 1}, disablePodDeletioncost: true}, 710 {lesser: podWithRank{ready2Hours, 1}, greater: podWithRank{ready5Hours, 1}}, 711 } 712 713 for i, test := range inequalityTests { 714 t.Run(fmt.Sprintf("Inequality tests %d", i), func(t *testing.T) { 715 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodDeletionCost, !test.disablePodDeletioncost)() 716 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.LogarithmicScaleDown, !test.disableLogarithmicScaleDown)() 717 718 podsWithRanks := ActivePodsWithRanks{ 719 Pods: []*v1.Pod{test.lesser.pod, test.greater.pod}, 720 Rank: []int{test.lesser.rank, test.greater.rank}, 721 Now: now, 722 } 723 if !podsWithRanks.Less(0, 1) { 724 t.Errorf("expected pod %q with rank %v to be less than %q with rank %v", podsWithRanks.Pods[0].Name, podsWithRanks.Rank[0], podsWithRanks.Pods[1].Name, podsWithRanks.Rank[1]) 725 } 726 if podsWithRanks.Less(1, 0) { 727 t.Errorf("expected pod %q with rank %v not to be less than %v with rank %v", podsWithRanks.Pods[1].Name, podsWithRanks.Rank[1], podsWithRanks.Pods[0].Name, podsWithRanks.Rank[0]) 728 } 729 }) 730 } 731 } 732 733 func TestActiveReplicaSetsFiltering(t *testing.T) { 734 735 rsUuid := uuid.NewUUID() 736 tests := []struct { 737 name string 738 replicaSets []*apps.ReplicaSet 739 wantReplicaSets []*apps.ReplicaSet 740 }{ 741 { 742 name: "Filters active replica sets", 743 replicaSets: []*apps.ReplicaSet{ 744 newReplicaSet("zero", 0, rsUuid), 745 nil, 746 newReplicaSet("foo", 1, rsUuid), 747 newReplicaSet("bar", 2, rsUuid), 748 }, 749 wantReplicaSets: []*apps.ReplicaSet{ 750 newReplicaSet("foo", 1, rsUuid), 751 newReplicaSet("bar", 2, rsUuid), 752 }, 753 }, 754 } 755 756 for _, test := range tests { 757 t.Run(test.name, func(t *testing.T) { 758 gotReplicaSets := FilterActiveReplicaSets(test.replicaSets) 759 760 if diff := cmp.Diff(test.wantReplicaSets, gotReplicaSets); diff != "" { 761 t.Errorf("Active replica set names (-want,+got):\n%s", diff) 762 } 763 }) 764 } 765 } 766 767 func TestComputeHash(t *testing.T) { 768 collisionCount := int32(1) 769 otherCollisionCount := int32(2) 770 maxCollisionCount := int32(math.MaxInt32) 771 tests := []struct { 772 name string 773 template *v1.PodTemplateSpec 774 collisionCount *int32 775 otherCollisionCount *int32 776 }{ 777 { 778 name: "simple", 779 template: &v1.PodTemplateSpec{}, 780 collisionCount: &collisionCount, 781 otherCollisionCount: &otherCollisionCount, 782 }, 783 { 784 name: "using math.MaxInt64", 785 template: &v1.PodTemplateSpec{}, 786 collisionCount: nil, 787 otherCollisionCount: &maxCollisionCount, 788 }, 789 } 790 791 for _, test := range tests { 792 hash := ComputeHash(test.template, test.collisionCount) 793 otherHash := ComputeHash(test.template, test.otherCollisionCount) 794 795 assert.NotEqual(t, hash, otherHash, "expected different hashes but got the same: %d", hash) 796 } 797 } 798 799 func TestRemoveTaintOffNode(t *testing.T) { 800 tests := []struct { 801 name string 802 nodeHandler *testutil.FakeNodeHandler 803 nodeName string 804 taintsToRemove []*v1.Taint 805 expectedTaints []v1.Taint 806 requestCount int 807 }{ 808 { 809 name: "remove one taint from node", 810 nodeHandler: &testutil.FakeNodeHandler{ 811 Existing: []*v1.Node{ 812 { 813 ObjectMeta: metav1.ObjectMeta{ 814 Name: "node1", 815 }, 816 Spec: v1.NodeSpec{ 817 Taints: []v1.Taint{ 818 {Key: "key1", Value: "value1", Effect: "NoSchedule"}, 819 {Key: "key2", Value: "value2", Effect: "NoExecute"}, 820 }, 821 }, 822 }, 823 }, 824 Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}), 825 }, 826 nodeName: "node1", 827 taintsToRemove: []*v1.Taint{ 828 {Key: "key2", Value: "value2", Effect: "NoExecute"}, 829 }, 830 expectedTaints: []v1.Taint{ 831 {Key: "key1", Value: "value1", Effect: "NoSchedule"}, 832 }, 833 requestCount: 4, 834 }, 835 { 836 name: "remove multiple taints from node", 837 nodeHandler: &testutil.FakeNodeHandler{ 838 Existing: []*v1.Node{ 839 { 840 ObjectMeta: metav1.ObjectMeta{ 841 Name: "node1", 842 }, 843 Spec: v1.NodeSpec{ 844 Taints: []v1.Taint{ 845 {Key: "key1", Value: "value1", Effect: "NoSchedule"}, 846 {Key: "key2", Value: "value2", Effect: "NoExecute"}, 847 {Key: "key3", Value: "value3", Effect: "NoSchedule"}, 848 {Key: "key4", Value: "value4", Effect: "NoExecute"}, 849 }, 850 }, 851 }, 852 }, 853 Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}), 854 }, 855 nodeName: "node1", 856 taintsToRemove: []*v1.Taint{ 857 {Key: "key2", Value: "value2", Effect: "NoExecute"}, 858 {Key: "key3", Value: "value3", Effect: "NoSchedule"}, 859 }, 860 expectedTaints: []v1.Taint{ 861 {Key: "key1", Value: "value1", Effect: "NoSchedule"}, 862 {Key: "key4", Value: "value4", Effect: "NoExecute"}, 863 }, 864 requestCount: 4, 865 }, 866 { 867 name: "remove no-exist taints from node", 868 nodeHandler: &testutil.FakeNodeHandler{ 869 Existing: []*v1.Node{ 870 { 871 ObjectMeta: metav1.ObjectMeta{ 872 Name: "node1", 873 }, 874 Spec: v1.NodeSpec{ 875 Taints: []v1.Taint{ 876 {Key: "key1", Value: "value1", Effect: "NoSchedule"}, 877 {Key: "key2", Value: "value2", Effect: "NoExecute"}, 878 }, 879 }, 880 }, 881 }, 882 Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}), 883 }, 884 nodeName: "node1", 885 taintsToRemove: []*v1.Taint{ 886 {Key: "key3", Value: "value3", Effect: "NoSchedule"}, 887 }, 888 expectedTaints: []v1.Taint{ 889 {Key: "key1", Value: "value1", Effect: "NoSchedule"}, 890 {Key: "key2", Value: "value2", Effect: "NoExecute"}, 891 }, 892 requestCount: 2, 893 }, 894 { 895 name: "remove taint from node without taints", 896 nodeHandler: &testutil.FakeNodeHandler{ 897 Existing: []*v1.Node{ 898 { 899 ObjectMeta: metav1.ObjectMeta{ 900 Name: "node1", 901 }, 902 }, 903 }, 904 Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}), 905 }, 906 nodeName: "node1", 907 taintsToRemove: []*v1.Taint{ 908 {Key: "key3", Value: "value3", Effect: "NoSchedule"}, 909 }, 910 expectedTaints: nil, 911 requestCount: 2, 912 }, 913 { 914 name: "remove empty taint list from node without taints", 915 nodeHandler: &testutil.FakeNodeHandler{ 916 Existing: []*v1.Node{ 917 { 918 ObjectMeta: metav1.ObjectMeta{ 919 Name: "node1", 920 }, 921 }, 922 }, 923 Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}), 924 }, 925 nodeName: "node1", 926 taintsToRemove: []*v1.Taint{}, 927 expectedTaints: nil, 928 requestCount: 2, 929 }, 930 { 931 name: "remove empty taint list from node", 932 nodeHandler: &testutil.FakeNodeHandler{ 933 Existing: []*v1.Node{ 934 { 935 ObjectMeta: metav1.ObjectMeta{ 936 Name: "node1", 937 }, 938 Spec: v1.NodeSpec{ 939 Taints: []v1.Taint{ 940 {Key: "key1", Value: "value1", Effect: "NoSchedule"}, 941 {Key: "key2", Value: "value2", Effect: "NoExecute"}, 942 }, 943 }, 944 }, 945 }, 946 Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}), 947 }, 948 nodeName: "node1", 949 taintsToRemove: []*v1.Taint{}, 950 expectedTaints: []v1.Taint{ 951 {Key: "key1", Value: "value1", Effect: "NoSchedule"}, 952 {Key: "key2", Value: "value2", Effect: "NoExecute"}, 953 }, 954 requestCount: 2, 955 }, 956 } 957 for _, test := range tests { 958 node, _ := test.nodeHandler.Get(context.TODO(), test.nodeName, metav1.GetOptions{}) 959 err := RemoveTaintOffNode(context.TODO(), test.nodeHandler, test.nodeName, node, test.taintsToRemove...) 960 assert.NoError(t, err, "%s: RemoveTaintOffNode() error = %v", test.name, err) 961 962 node, _ = test.nodeHandler.Get(context.TODO(), test.nodeName, metav1.GetOptions{}) 963 assert.EqualValues(t, test.expectedTaints, node.Spec.Taints, 964 "%s: failed to remove taint off node: expected %+v, got %+v", 965 test.name, test.expectedTaints, node.Spec.Taints) 966 967 assert.Equal(t, test.requestCount, test.nodeHandler.RequestCount, 968 "%s: unexpected request count: expected %+v, got %+v", 969 test.name, test.requestCount, test.nodeHandler.RequestCount) 970 } 971 } 972 973 func TestAddOrUpdateTaintOnNode(t *testing.T) { 974 tests := []struct { 975 name string 976 nodeHandler *testutil.FakeNodeHandler 977 nodeName string 978 taintsToAdd []*v1.Taint 979 expectedTaints []v1.Taint 980 requestCount int 981 expectedErr error 982 }{ 983 { 984 name: "add one taint on node", 985 nodeHandler: &testutil.FakeNodeHandler{ 986 Existing: []*v1.Node{ 987 { 988 ObjectMeta: metav1.ObjectMeta{ 989 Name: "node1", 990 }, 991 Spec: v1.NodeSpec{ 992 Taints: []v1.Taint{ 993 {Key: "key1", Value: "value1", Effect: "NoSchedule"}, 994 }, 995 }, 996 }, 997 }, 998 Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}), 999 }, 1000 nodeName: "node1", 1001 taintsToAdd: []*v1.Taint{ 1002 {Key: "key2", Value: "value2", Effect: "NoExecute"}, 1003 }, 1004 expectedTaints: []v1.Taint{ 1005 {Key: "key1", Value: "value1", Effect: "NoSchedule"}, 1006 {Key: "key2", Value: "value2", Effect: "NoExecute"}, 1007 }, 1008 requestCount: 3, 1009 }, 1010 { 1011 name: "add multiple taints to node", 1012 nodeHandler: &testutil.FakeNodeHandler{ 1013 Existing: []*v1.Node{ 1014 { 1015 ObjectMeta: metav1.ObjectMeta{ 1016 Name: "node1", 1017 }, 1018 Spec: v1.NodeSpec{ 1019 Taints: []v1.Taint{ 1020 {Key: "key1", Value: "value1", Effect: "NoSchedule"}, 1021 {Key: "key2", Value: "value2", Effect: "NoExecute"}, 1022 }, 1023 }, 1024 }, 1025 }, 1026 Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}), 1027 }, 1028 nodeName: "node1", 1029 taintsToAdd: []*v1.Taint{ 1030 {Key: "key3", Value: "value3", Effect: "NoSchedule"}, 1031 {Key: "key4", Value: "value4", Effect: "NoExecute"}, 1032 }, 1033 expectedTaints: []v1.Taint{ 1034 {Key: "key1", Value: "value1", Effect: "NoSchedule"}, 1035 {Key: "key2", Value: "value2", Effect: "NoExecute"}, 1036 {Key: "key3", Value: "value3", Effect: "NoSchedule"}, 1037 {Key: "key4", Value: "value4", Effect: "NoExecute"}, 1038 }, 1039 requestCount: 3, 1040 }, 1041 { 1042 name: "add exist taints to node", 1043 nodeHandler: &testutil.FakeNodeHandler{ 1044 Existing: []*v1.Node{ 1045 { 1046 ObjectMeta: metav1.ObjectMeta{ 1047 Name: "node1", 1048 }, 1049 Spec: v1.NodeSpec{ 1050 Taints: []v1.Taint{ 1051 {Key: "key1", Value: "value1", Effect: "NoSchedule"}, 1052 {Key: "key2", Value: "value2", Effect: "NoExecute"}, 1053 }, 1054 }, 1055 }, 1056 }, 1057 Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}), 1058 }, 1059 nodeName: "node1", 1060 taintsToAdd: []*v1.Taint{ 1061 {Key: "key2", Value: "value2", Effect: "NoExecute"}, 1062 }, 1063 expectedTaints: []v1.Taint{ 1064 {Key: "key1", Value: "value1", Effect: "NoSchedule"}, 1065 {Key: "key2", Value: "value2", Effect: "NoExecute"}, 1066 }, 1067 requestCount: 2, 1068 }, 1069 { 1070 name: "add taint to node without taints", 1071 nodeHandler: &testutil.FakeNodeHandler{ 1072 Existing: []*v1.Node{ 1073 { 1074 ObjectMeta: metav1.ObjectMeta{ 1075 Name: "node1", 1076 }, 1077 }, 1078 }, 1079 Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}), 1080 }, 1081 nodeName: "node1", 1082 taintsToAdd: []*v1.Taint{ 1083 {Key: "key3", Value: "value3", Effect: "NoSchedule"}, 1084 }, 1085 expectedTaints: []v1.Taint{ 1086 {Key: "key3", Value: "value3", Effect: "NoSchedule"}, 1087 }, 1088 requestCount: 3, 1089 }, 1090 { 1091 name: "add empty taint list to node without taints", 1092 nodeHandler: &testutil.FakeNodeHandler{ 1093 Existing: []*v1.Node{ 1094 { 1095 ObjectMeta: metav1.ObjectMeta{ 1096 Name: "node1", 1097 }, 1098 }, 1099 }, 1100 Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}), 1101 }, 1102 nodeName: "node1", 1103 taintsToAdd: []*v1.Taint{}, 1104 expectedTaints: nil, 1105 requestCount: 1, 1106 }, 1107 { 1108 name: "add empty taint list to node", 1109 nodeHandler: &testutil.FakeNodeHandler{ 1110 Existing: []*v1.Node{ 1111 { 1112 ObjectMeta: metav1.ObjectMeta{ 1113 Name: "node1", 1114 }, 1115 Spec: v1.NodeSpec{ 1116 Taints: []v1.Taint{ 1117 {Key: "key1", Value: "value1", Effect: "NoSchedule"}, 1118 {Key: "key2", Value: "value2", Effect: "NoExecute"}, 1119 }, 1120 }, 1121 }, 1122 }, 1123 Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}), 1124 }, 1125 nodeName: "node1", 1126 taintsToAdd: []*v1.Taint{}, 1127 expectedTaints: []v1.Taint{ 1128 {Key: "key1", Value: "value1", Effect: "NoSchedule"}, 1129 {Key: "key2", Value: "value2", Effect: "NoExecute"}, 1130 }, 1131 requestCount: 1, 1132 }, 1133 { 1134 name: "add taint to changed node", 1135 nodeHandler: &testutil.FakeNodeHandler{ 1136 Existing: []*v1.Node{ 1137 { 1138 ObjectMeta: metav1.ObjectMeta{ 1139 Name: "node1", 1140 ResourceVersion: "1", 1141 }, 1142 Spec: v1.NodeSpec{ 1143 Taints: []v1.Taint{ 1144 {Key: "key1", Value: "value1", Effect: "NoSchedule"}, 1145 }, 1146 }, 1147 }, 1148 }, 1149 Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}), 1150 AsyncCalls: []func(*testutil.FakeNodeHandler){func(m *testutil.FakeNodeHandler) { 1151 if len(m.UpdatedNodes) == 0 { 1152 m.UpdatedNodes = append(m.UpdatedNodes, &v1.Node{ 1153 ObjectMeta: metav1.ObjectMeta{ 1154 Name: "node1", 1155 ResourceVersion: "2", 1156 }, 1157 Spec: v1.NodeSpec{ 1158 Taints: []v1.Taint{}, 1159 }}) 1160 } 1161 }}, 1162 }, 1163 nodeName: "node1", 1164 taintsToAdd: []*v1.Taint{{Key: "key2", Value: "value2", Effect: "NoExecute"}}, 1165 expectedTaints: []v1.Taint{ 1166 {Key: "key2", Value: "value2", Effect: "NoExecute"}, 1167 }, 1168 requestCount: 5, 1169 }, 1170 { 1171 name: "add taint to non-exist node", 1172 nodeHandler: &testutil.FakeNodeHandler{ 1173 Existing: []*v1.Node{ 1174 { 1175 ObjectMeta: metav1.ObjectMeta{ 1176 Name: "node1", 1177 ResourceVersion: "1", 1178 }, 1179 Spec: v1.NodeSpec{ 1180 Taints: []v1.Taint{ 1181 {Key: "key1", Value: "value1", Effect: "NoSchedule"}, 1182 }, 1183 }, 1184 }, 1185 }, 1186 Clientset: fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*testutil.NewPod("pod0", "node0")}}), 1187 }, 1188 nodeName: "node2", 1189 taintsToAdd: []*v1.Taint{{Key: "key2", Value: "value2", Effect: "NoExecute"}}, 1190 expectedErr: apierrors.NewNotFound(schema.GroupResource{Resource: "nodes"}, "node2"), 1191 }, 1192 } 1193 for _, test := range tests { 1194 err := AddOrUpdateTaintOnNode(context.TODO(), test.nodeHandler, test.nodeName, test.taintsToAdd...) 1195 if test.expectedErr != nil { 1196 assert.Equal(t, test.expectedErr, err, "AddOrUpdateTaintOnNode get unexpected error") 1197 continue 1198 } 1199 assert.NoError(t, err, "%s: AddOrUpdateTaintOnNode() error = %v", test.name, err) 1200 1201 node, _ := test.nodeHandler.Get(context.TODO(), test.nodeName, metav1.GetOptions{}) 1202 assert.EqualValues(t, test.expectedTaints, node.Spec.Taints, 1203 "%s: failed to add taint to node: expected %+v, got %+v", 1204 test.name, test.expectedTaints, node.Spec.Taints) 1205 1206 assert.Equal(t, test.requestCount, test.nodeHandler.RequestCount, 1207 "%s: unexpected request count: expected %+v, got %+v", 1208 test.name, test.requestCount, test.nodeHandler.RequestCount) 1209 } 1210 }