1
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
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
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
179
180
181 adds, dels := 10, 30
182 rc := newReplicationController(1)
183
184
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
194
195 e.CreationObserved(logger, rcKey)
196 wg.Done()
197 }()
198 }
199 wg.Wait()
200
201
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
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
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
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 }
1211
View as plain text