1
16
17 package util
18
19 import (
20 "fmt"
21 "math"
22 "math/rand"
23 "reflect"
24 "sort"
25 "strconv"
26 "testing"
27 "time"
28
29 apps "k8s.io/api/apps/v1"
30 v1 "k8s.io/api/core/v1"
31 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
32 "k8s.io/apimachinery/pkg/types"
33 "k8s.io/apimachinery/pkg/util/intstr"
34 "k8s.io/apiserver/pkg/storage/names"
35 "k8s.io/client-go/informers"
36 "k8s.io/client-go/kubernetes/fake"
37 "k8s.io/klog/v2/ktesting"
38 "k8s.io/kubernetes/pkg/controller"
39 "k8s.io/utils/ptr"
40 )
41
42 func newDControllerRef(d *apps.Deployment) *metav1.OwnerReference {
43 isController := true
44 return &metav1.OwnerReference{
45 APIVersion: "apps/v1",
46 Kind: "Deployment",
47 Name: d.GetName(),
48 UID: d.GetUID(),
49 Controller: &isController,
50 }
51 }
52
53
54 func generateRS(deployment apps.Deployment) apps.ReplicaSet {
55 template := deployment.Spec.Template.DeepCopy()
56 return apps.ReplicaSet{
57 ObjectMeta: metav1.ObjectMeta{
58 UID: randomUID(),
59 Name: names.SimpleNameGenerator.GenerateName("replicaset"),
60 Labels: template.Labels,
61 OwnerReferences: []metav1.OwnerReference{*newDControllerRef(&deployment)},
62 },
63 Spec: apps.ReplicaSetSpec{
64 Replicas: new(int32),
65 Template: *template,
66 Selector: &metav1.LabelSelector{MatchLabels: template.Labels},
67 },
68 }
69 }
70
71 func randomUID() types.UID {
72 return types.UID(strconv.FormatInt(rand.Int63(), 10))
73 }
74
75
76 func generateDeployment(image string) apps.Deployment {
77 podLabels := map[string]string{"name": image}
78 terminationSec := int64(30)
79 enableServiceLinks := v1.DefaultEnableServiceLinks
80 return apps.Deployment{
81 ObjectMeta: metav1.ObjectMeta{
82 Name: image,
83 Annotations: make(map[string]string),
84 },
85 Spec: apps.DeploymentSpec{
86 Replicas: func(i int32) *int32 { return &i }(1),
87 Selector: &metav1.LabelSelector{MatchLabels: podLabels},
88 Template: v1.PodTemplateSpec{
89 ObjectMeta: metav1.ObjectMeta{
90 Labels: podLabels,
91 },
92 Spec: v1.PodSpec{
93 Containers: []v1.Container{
94 {
95 Name: image,
96 Image: image,
97 ImagePullPolicy: v1.PullAlways,
98 TerminationMessagePath: v1.TerminationMessagePathDefault,
99 },
100 },
101 DNSPolicy: v1.DNSClusterFirst,
102 TerminationGracePeriodSeconds: &terminationSec,
103 RestartPolicy: v1.RestartPolicyAlways,
104 SecurityContext: &v1.PodSecurityContext{},
105 EnableServiceLinks: &enableServiceLinks,
106 },
107 },
108 },
109 }
110 }
111
112 func generatePodTemplateSpec(name, nodeName string, annotations, labels map[string]string) v1.PodTemplateSpec {
113 return v1.PodTemplateSpec{
114 ObjectMeta: metav1.ObjectMeta{
115 Name: name,
116 Annotations: annotations,
117 Labels: labels,
118 },
119 Spec: v1.PodSpec{
120 NodeName: nodeName,
121 },
122 }
123 }
124
125 func TestEqualIgnoreHash(t *testing.T) {
126 tests := []struct {
127 Name string
128 former, latter v1.PodTemplateSpec
129 expected bool
130 }{
131 {
132 "Same spec, same labels",
133 generatePodTemplateSpec("foo", "foo-node", map[string]string{}, map[string]string{apps.DefaultDeploymentUniqueLabelKey: "value-1", "something": "else"}),
134 generatePodTemplateSpec("foo", "foo-node", map[string]string{}, map[string]string{apps.DefaultDeploymentUniqueLabelKey: "value-1", "something": "else"}),
135 true,
136 },
137 {
138 "Same spec, only pod-template-hash label value is different",
139 generatePodTemplateSpec("foo", "foo-node", map[string]string{}, map[string]string{apps.DefaultDeploymentUniqueLabelKey: "value-1", "something": "else"}),
140 generatePodTemplateSpec("foo", "foo-node", map[string]string{}, map[string]string{apps.DefaultDeploymentUniqueLabelKey: "value-2", "something": "else"}),
141 true,
142 },
143 {
144 "Same spec, the former doesn't have pod-template-hash label",
145 generatePodTemplateSpec("foo", "foo-node", map[string]string{}, map[string]string{"something": "else"}),
146 generatePodTemplateSpec("foo", "foo-node", map[string]string{}, map[string]string{apps.DefaultDeploymentUniqueLabelKey: "value-2", "something": "else"}),
147 true,
148 },
149 {
150 "Same spec, the label is different, the former doesn't have pod-template-hash label, same number of labels",
151 generatePodTemplateSpec("foo", "foo-node", map[string]string{}, map[string]string{"something": "else"}),
152 generatePodTemplateSpec("foo", "foo-node", map[string]string{}, map[string]string{apps.DefaultDeploymentUniqueLabelKey: "value-2"}),
153 false,
154 },
155 {
156 "Same spec, the label is different, the latter doesn't have pod-template-hash label, same number of labels",
157 generatePodTemplateSpec("foo", "foo-node", map[string]string{}, map[string]string{apps.DefaultDeploymentUniqueLabelKey: "value-1"}),
158 generatePodTemplateSpec("foo", "foo-node", map[string]string{}, map[string]string{"something": "else"}),
159 false,
160 },
161 {
162 "Same spec, the label is different, and the pod-template-hash label value is the same",
163 generatePodTemplateSpec("foo", "foo-node", map[string]string{}, map[string]string{apps.DefaultDeploymentUniqueLabelKey: "value-1"}),
164 generatePodTemplateSpec("foo", "foo-node", map[string]string{}, map[string]string{apps.DefaultDeploymentUniqueLabelKey: "value-1", "something": "else"}),
165 false,
166 },
167 {
168 "Different spec, same labels",
169 generatePodTemplateSpec("foo", "foo-node", map[string]string{"former": "value"}, map[string]string{apps.DefaultDeploymentUniqueLabelKey: "value-1", "something": "else"}),
170 generatePodTemplateSpec("foo", "foo-node", map[string]string{"latter": "value"}, map[string]string{apps.DefaultDeploymentUniqueLabelKey: "value-1", "something": "else"}),
171 false,
172 },
173 {
174 "Different spec, different pod-template-hash label value",
175 generatePodTemplateSpec("foo-1", "foo-node", map[string]string{}, map[string]string{apps.DefaultDeploymentUniqueLabelKey: "value-1", "something": "else"}),
176 generatePodTemplateSpec("foo-2", "foo-node", map[string]string{}, map[string]string{apps.DefaultDeploymentUniqueLabelKey: "value-2", "something": "else"}),
177 false,
178 },
179 {
180 "Different spec, the former doesn't have pod-template-hash label",
181 generatePodTemplateSpec("foo-1", "foo-node-1", map[string]string{}, map[string]string{"something": "else"}),
182 generatePodTemplateSpec("foo-2", "foo-node-2", map[string]string{}, map[string]string{apps.DefaultDeploymentUniqueLabelKey: "value-2", "something": "else"}),
183 false,
184 },
185 {
186 "Different spec, different labels",
187 generatePodTemplateSpec("foo", "foo-node-1", map[string]string{}, map[string]string{"something": "else"}),
188 generatePodTemplateSpec("foo", "foo-node-2", map[string]string{}, map[string]string{"nothing": "else"}),
189 false,
190 },
191 }
192
193 for _, test := range tests {
194 t.Run(test.Name, func(t *testing.T) {
195 runTest := func(t1, t2 *v1.PodTemplateSpec, reversed bool) {
196 reverseString := ""
197 if reversed {
198 reverseString = " (reverse order)"
199 }
200
201 equal := EqualIgnoreHash(t1, t2)
202 if equal != test.expected {
203 t.Errorf("%q%s: expected %v", test.Name, reverseString, test.expected)
204 return
205 }
206 if t1.Labels == nil || t2.Labels == nil {
207 t.Errorf("%q%s: unexpected labels becomes nil", test.Name, reverseString)
208 }
209 }
210
211 runTest(&test.former, &test.latter, false)
212
213 runTest(&test.latter, &test.former, true)
214 })
215 }
216 }
217
218 func TestFindNewReplicaSet(t *testing.T) {
219 now := metav1.Now()
220 later := metav1.Time{Time: now.Add(time.Minute)}
221
222 deployment := generateDeployment("nginx")
223 newRS := generateRS(deployment)
224 newRS.Labels[apps.DefaultDeploymentUniqueLabelKey] = "hash"
225 newRS.CreationTimestamp = later
226
227 newRSDup := generateRS(deployment)
228 newRSDup.Labels[apps.DefaultDeploymentUniqueLabelKey] = "different-hash"
229 newRSDup.CreationTimestamp = now
230
231 oldDeployment := generateDeployment("nginx")
232 oldDeployment.Spec.Template.Spec.Containers[0].Name = "nginx-old-1"
233 oldRS := generateRS(oldDeployment)
234 oldRS.Status.FullyLabeledReplicas = *(oldRS.Spec.Replicas)
235
236 tests := []struct {
237 Name string
238 deployment apps.Deployment
239 rsList []*apps.ReplicaSet
240 expected *apps.ReplicaSet
241 }{
242 {
243 Name: "Get new ReplicaSet with the same template as Deployment spec but different pod-template-hash value",
244 deployment: deployment,
245 rsList: []*apps.ReplicaSet{&newRS, &oldRS},
246 expected: &newRS,
247 },
248 {
249 Name: "Get the oldest new ReplicaSet when there are more than one ReplicaSet with the same template",
250 deployment: deployment,
251 rsList: []*apps.ReplicaSet{&newRS, &oldRS, &newRSDup},
252 expected: &newRSDup,
253 },
254 {
255 Name: "Get nil new ReplicaSet",
256 deployment: deployment,
257 rsList: []*apps.ReplicaSet{&oldRS},
258 expected: nil,
259 },
260 }
261
262 for _, test := range tests {
263 t.Run(test.Name, func(t *testing.T) {
264 if rs := FindNewReplicaSet(&test.deployment, test.rsList); !reflect.DeepEqual(rs, test.expected) {
265 t.Errorf("In test case %q, expected %#v, got %#v", test.Name, test.expected, rs)
266 }
267 })
268 }
269 }
270
271 func TestFindOldReplicaSets(t *testing.T) {
272 now := metav1.Now()
273 later := metav1.Time{Time: now.Add(time.Minute)}
274 before := metav1.Time{Time: now.Add(-time.Minute)}
275
276 deployment := generateDeployment("nginx")
277 newRS := generateRS(deployment)
278 *(newRS.Spec.Replicas) = 1
279 newRS.Labels[apps.DefaultDeploymentUniqueLabelKey] = "hash"
280 newRS.CreationTimestamp = later
281
282 newRSDup := generateRS(deployment)
283 newRSDup.Labels[apps.DefaultDeploymentUniqueLabelKey] = "different-hash"
284 newRSDup.CreationTimestamp = now
285
286 oldDeployment := generateDeployment("nginx")
287 oldDeployment.Spec.Template.Spec.Containers[0].Name = "nginx-old-1"
288 oldRS := generateRS(oldDeployment)
289 oldRS.Status.FullyLabeledReplicas = *(oldRS.Spec.Replicas)
290 oldRS.CreationTimestamp = before
291
292 tests := []struct {
293 Name string
294 deployment apps.Deployment
295 rsList []*apps.ReplicaSet
296 expected []*apps.ReplicaSet
297 expectedRequire []*apps.ReplicaSet
298 }{
299 {
300 Name: "Get old ReplicaSets",
301 deployment: deployment,
302 rsList: []*apps.ReplicaSet{&newRS, &oldRS},
303 expected: []*apps.ReplicaSet{&oldRS},
304 expectedRequire: nil,
305 },
306 {
307 Name: "Get old ReplicaSets with no new ReplicaSet",
308 deployment: deployment,
309 rsList: []*apps.ReplicaSet{&oldRS},
310 expected: []*apps.ReplicaSet{&oldRS},
311 expectedRequire: nil,
312 },
313 {
314 Name: "Get old ReplicaSets with two new ReplicaSets, only the oldest new ReplicaSet is seen as new ReplicaSet",
315 deployment: deployment,
316 rsList: []*apps.ReplicaSet{&oldRS, &newRS, &newRSDup},
317 expected: []*apps.ReplicaSet{&oldRS, &newRS},
318 expectedRequire: []*apps.ReplicaSet{&newRS},
319 },
320 {
321 Name: "Get empty old ReplicaSets",
322 deployment: deployment,
323 rsList: []*apps.ReplicaSet{&newRS},
324 expected: nil,
325 expectedRequire: nil,
326 },
327 }
328
329 for _, test := range tests {
330 t.Run(test.Name, func(t *testing.T) {
331 requireRS, allRS := FindOldReplicaSets(&test.deployment, test.rsList)
332 sort.Sort(controller.ReplicaSetsByCreationTimestamp(allRS))
333 sort.Sort(controller.ReplicaSetsByCreationTimestamp(test.expected))
334 if !reflect.DeepEqual(allRS, test.expected) {
335 t.Errorf("In test case %q, expected %#v, got %#v", test.Name, test.expected, allRS)
336 }
337
338 if !reflect.DeepEqual(requireRS, test.expectedRequire) {
339 t.Errorf("In test case %q, expected %#v, got %#v", test.Name, test.expectedRequire, requireRS)
340 }
341 })
342 }
343 }
344
345 func TestGetReplicaCountForReplicaSets(t *testing.T) {
346 rs1 := generateRS(generateDeployment("foo"))
347 *(rs1.Spec.Replicas) = 1
348 rs1.Status.Replicas = 2
349 rs2 := generateRS(generateDeployment("bar"))
350 *(rs2.Spec.Replicas) = 2
351 rs2.Status.Replicas = 3
352
353 tests := []struct {
354 Name string
355 sets []*apps.ReplicaSet
356 expectedCount int32
357 expectedActual int32
358 }{
359 {
360 "1:2 Replicas",
361 []*apps.ReplicaSet{&rs1},
362 1,
363 2,
364 },
365 {
366 "3:5 Replicas",
367 []*apps.ReplicaSet{&rs1, &rs2},
368 3,
369 5,
370 },
371 }
372
373 for _, test := range tests {
374 t.Run(test.Name, func(t *testing.T) {
375 rs := GetReplicaCountForReplicaSets(test.sets)
376 if rs != test.expectedCount {
377 t.Errorf("In test case %s, expectedCount %+v, got %+v", test.Name, test.expectedCount, rs)
378 }
379 rs = GetActualReplicaCountForReplicaSets(test.sets)
380 if rs != test.expectedActual {
381 t.Errorf("In test case %s, expectedActual %+v, got %+v", test.Name, test.expectedActual, rs)
382 }
383 })
384 }
385 }
386
387 func TestResolveFenceposts(t *testing.T) {
388 tests := []struct {
389 maxSurge *string
390 maxUnavailable *string
391 desired int32
392 expectSurge int32
393 expectUnavailable int32
394 expectError bool
395 }{
396 {
397 maxSurge: ptr.To("0%"),
398 maxUnavailable: ptr.To("0%"),
399 desired: 0,
400 expectSurge: 0,
401 expectUnavailable: 1,
402 expectError: false,
403 },
404 {
405 maxSurge: ptr.To("39%"),
406 maxUnavailable: ptr.To("39%"),
407 desired: 10,
408 expectSurge: 4,
409 expectUnavailable: 3,
410 expectError: false,
411 },
412 {
413 maxSurge: ptr.To("oops"),
414 maxUnavailable: ptr.To("39%"),
415 desired: 10,
416 expectSurge: 0,
417 expectUnavailable: 0,
418 expectError: true,
419 },
420 {
421 maxSurge: ptr.To("55%"),
422 maxUnavailable: ptr.To("urg"),
423 desired: 10,
424 expectSurge: 0,
425 expectUnavailable: 0,
426 expectError: true,
427 },
428 {
429 maxSurge: nil,
430 maxUnavailable: ptr.To("39%"),
431 desired: 10,
432 expectSurge: 0,
433 expectUnavailable: 3,
434 expectError: false,
435 },
436 {
437 maxSurge: ptr.To("39%"),
438 maxUnavailable: nil,
439 desired: 10,
440 expectSurge: 4,
441 expectUnavailable: 0,
442 expectError: false,
443 },
444 {
445 maxSurge: nil,
446 maxUnavailable: nil,
447 desired: 10,
448 expectSurge: 0,
449 expectUnavailable: 1,
450 expectError: false,
451 },
452 }
453
454 for num, test := range tests {
455 t.Run(fmt.Sprintf("%d", num), func(t *testing.T) {
456 var maxSurge, maxUnavail *intstr.IntOrString
457 if test.maxSurge != nil {
458 maxSurge = ptr.To(intstr.FromString(*test.maxSurge))
459 }
460 if test.maxUnavailable != nil {
461 maxUnavail = ptr.To(intstr.FromString(*test.maxUnavailable))
462 }
463 surge, unavail, err := ResolveFenceposts(maxSurge, maxUnavail, test.desired)
464 if err != nil && !test.expectError {
465 t.Errorf("unexpected error %v", err)
466 }
467 if err == nil && test.expectError {
468 t.Error("expected error")
469 }
470 if surge != test.expectSurge || unavail != test.expectUnavailable {
471 t.Errorf("#%v got %v:%v, want %v:%v", num, surge, unavail, test.expectSurge, test.expectUnavailable)
472 }
473 })
474 }
475 }
476
477 func TestNewRSNewReplicas(t *testing.T) {
478 tests := []struct {
479 Name string
480 strategyType apps.DeploymentStrategyType
481 depReplicas int32
482 newRSReplicas int32
483 maxSurge int32
484 expected int32
485 }{
486 {
487 "can not scale up - to newRSReplicas",
488 apps.RollingUpdateDeploymentStrategyType,
489 1, 5, 1, 5,
490 },
491 {
492 "scale up - to depReplicas",
493 apps.RollingUpdateDeploymentStrategyType,
494 6, 2, 10, 6,
495 },
496 {
497 "recreate - to depReplicas",
498 apps.RecreateDeploymentStrategyType,
499 3, 1, 1, 3,
500 },
501 }
502 newDeployment := generateDeployment("nginx")
503 newRC := generateRS(newDeployment)
504 rs5 := generateRS(newDeployment)
505 *(rs5.Spec.Replicas) = 5
506
507 for _, test := range tests {
508 t.Run(test.Name, func(t *testing.T) {
509 *(newDeployment.Spec.Replicas) = test.depReplicas
510 newDeployment.Spec.Strategy = apps.DeploymentStrategy{Type: test.strategyType}
511 newDeployment.Spec.Strategy.RollingUpdate = &apps.RollingUpdateDeployment{
512 MaxUnavailable: ptr.To(intstr.FromInt32(1)),
513 MaxSurge: ptr.To(intstr.FromInt32(test.maxSurge)),
514 }
515 *(newRC.Spec.Replicas) = test.newRSReplicas
516 rs, err := NewRSNewReplicas(&newDeployment, []*apps.ReplicaSet{&rs5}, &newRC)
517 if err != nil {
518 t.Errorf("In test case %s, got unexpected error %v", test.Name, err)
519 }
520 if rs != test.expected {
521 t.Errorf("In test case %s, expected %+v, got %+v", test.Name, test.expected, rs)
522 }
523 })
524 }
525 }
526
527 var (
528 condProgressing = func() apps.DeploymentCondition {
529 return apps.DeploymentCondition{
530 Type: apps.DeploymentProgressing,
531 Status: v1.ConditionFalse,
532 Reason: "ForSomeReason",
533 }
534 }
535
536 condProgressing2 = func() apps.DeploymentCondition {
537 return apps.DeploymentCondition{
538 Type: apps.DeploymentProgressing,
539 Status: v1.ConditionTrue,
540 Reason: "BecauseItIs",
541 }
542 }
543
544 condAvailable = func() apps.DeploymentCondition {
545 return apps.DeploymentCondition{
546 Type: apps.DeploymentAvailable,
547 Status: v1.ConditionTrue,
548 Reason: "AwesomeController",
549 }
550 }
551
552 status = func() *apps.DeploymentStatus {
553 return &apps.DeploymentStatus{
554 Conditions: []apps.DeploymentCondition{condProgressing(), condAvailable()},
555 }
556 }
557 )
558
559 func TestGetCondition(t *testing.T) {
560 exampleStatus := status()
561
562 tests := []struct {
563 name string
564
565 status apps.DeploymentStatus
566 condType apps.DeploymentConditionType
567
568 expected bool
569 }{
570 {
571 name: "condition exists",
572
573 status: *exampleStatus,
574 condType: apps.DeploymentAvailable,
575
576 expected: true,
577 },
578 {
579 name: "condition does not exist",
580
581 status: *exampleStatus,
582 condType: apps.DeploymentReplicaFailure,
583
584 expected: false,
585 },
586 }
587
588 for _, test := range tests {
589 t.Run(test.name, func(t *testing.T) {
590 cond := GetDeploymentCondition(test.status, test.condType)
591 exists := cond != nil
592 if exists != test.expected {
593 t.Errorf("%s: expected condition to exist: %t, got: %t", test.name, test.expected, exists)
594 }
595 })
596 }
597 }
598
599 func TestSetCondition(t *testing.T) {
600 tests := []struct {
601 name string
602
603 status *apps.DeploymentStatus
604 cond apps.DeploymentCondition
605
606 expectedStatus *apps.DeploymentStatus
607 }{
608 {
609 name: "set for the first time",
610
611 status: &apps.DeploymentStatus{},
612 cond: condAvailable(),
613
614 expectedStatus: &apps.DeploymentStatus{Conditions: []apps.DeploymentCondition{condAvailable()}},
615 },
616 {
617 name: "simple set",
618
619 status: &apps.DeploymentStatus{Conditions: []apps.DeploymentCondition{condProgressing()}},
620 cond: condAvailable(),
621
622 expectedStatus: status(),
623 },
624 {
625 name: "overwrite",
626
627 status: &apps.DeploymentStatus{Conditions: []apps.DeploymentCondition{condProgressing()}},
628 cond: condProgressing2(),
629
630 expectedStatus: &apps.DeploymentStatus{Conditions: []apps.DeploymentCondition{condProgressing2()}},
631 },
632 }
633
634 for _, test := range tests {
635 t.Run(test.name, func(t *testing.T) {
636 SetDeploymentCondition(test.status, test.cond)
637 if !reflect.DeepEqual(test.status, test.expectedStatus) {
638 t.Errorf("%s: expected status: %v, got: %v", test.name, test.expectedStatus, test.status)
639 }
640 })
641 }
642 }
643
644 func TestRemoveCondition(t *testing.T) {
645 tests := []struct {
646 name string
647
648 status *apps.DeploymentStatus
649 condType apps.DeploymentConditionType
650
651 expectedStatus *apps.DeploymentStatus
652 }{
653 {
654 name: "remove from empty status",
655
656 status: &apps.DeploymentStatus{},
657 condType: apps.DeploymentProgressing,
658
659 expectedStatus: &apps.DeploymentStatus{},
660 },
661 {
662 name: "simple remove",
663
664 status: &apps.DeploymentStatus{Conditions: []apps.DeploymentCondition{condProgressing()}},
665 condType: apps.DeploymentProgressing,
666
667 expectedStatus: &apps.DeploymentStatus{},
668 },
669 {
670 name: "doesn't remove anything",
671
672 status: status(),
673 condType: apps.DeploymentReplicaFailure,
674
675 expectedStatus: status(),
676 },
677 }
678
679 for _, test := range tests {
680 t.Run(test.name, func(t *testing.T) {
681 RemoveDeploymentCondition(test.status, test.condType)
682 if !reflect.DeepEqual(test.status, test.expectedStatus) {
683 t.Errorf("%s: expected status: %v, got: %v", test.name, test.expectedStatus, test.status)
684 }
685 })
686 }
687 }
688
689 func TestDeploymentComplete(t *testing.T) {
690 deployment := func(desired, current, updated, available, maxUnavailable, maxSurge int32) *apps.Deployment {
691 return &apps.Deployment{
692 Spec: apps.DeploymentSpec{
693 Replicas: &desired,
694 Strategy: apps.DeploymentStrategy{
695 RollingUpdate: &apps.RollingUpdateDeployment{
696 MaxUnavailable: ptr.To(intstr.FromInt32(maxUnavailable)),
697 MaxSurge: ptr.To(intstr.FromInt32(maxSurge)),
698 },
699 Type: apps.RollingUpdateDeploymentStrategyType,
700 },
701 },
702 Status: apps.DeploymentStatus{
703 Replicas: current,
704 UpdatedReplicas: updated,
705 AvailableReplicas: available,
706 },
707 }
708 }
709
710 tests := []struct {
711 name string
712
713 d *apps.Deployment
714
715 expected bool
716 }{
717 {
718 name: "not complete: min but not all pods become available",
719
720 d: deployment(5, 5, 5, 4, 1, 0),
721 expected: false,
722 },
723 {
724 name: "not complete: min availability is not honored",
725
726 d: deployment(5, 5, 5, 3, 1, 0),
727 expected: false,
728 },
729 {
730 name: "complete",
731
732 d: deployment(5, 5, 5, 5, 0, 0),
733 expected: true,
734 },
735 {
736 name: "not complete: all pods are available but not updated",
737
738 d: deployment(5, 5, 4, 5, 0, 0),
739 expected: false,
740 },
741 {
742 name: "not complete: still running old pods",
743
744
745
746 d: deployment(1, 2, 1, 1, 0, 1),
747 expected: false,
748 },
749 {
750 name: "not complete: one replica deployment never comes up",
751
752 d: deployment(1, 1, 1, 0, 1, 1),
753 expected: false,
754 },
755 }
756
757 for _, test := range tests {
758 t.Run(test.name, func(t *testing.T) {
759 if got, exp := DeploymentComplete(test.d, &test.d.Status), test.expected; got != exp {
760 t.Errorf("expected complete: %t, got: %t", exp, got)
761 }
762 })
763 }
764 }
765
766 func TestDeploymentProgressing(t *testing.T) {
767 deployment := func(current, updated, ready, available int32) *apps.Deployment {
768 return &apps.Deployment{
769 Status: apps.DeploymentStatus{
770 Replicas: current,
771 UpdatedReplicas: updated,
772 ReadyReplicas: ready,
773 AvailableReplicas: available,
774 },
775 }
776 }
777 newStatus := func(current, updated, ready, available int32) apps.DeploymentStatus {
778 return apps.DeploymentStatus{
779 Replicas: current,
780 UpdatedReplicas: updated,
781 ReadyReplicas: ready,
782 AvailableReplicas: available,
783 }
784 }
785
786 tests := []struct {
787 name string
788
789 d *apps.Deployment
790 newStatus apps.DeploymentStatus
791
792 expected bool
793 }{
794 {
795 name: "progressing: updated pods",
796
797 d: deployment(10, 4, 4, 4),
798 newStatus: newStatus(10, 6, 4, 4),
799
800 expected: true,
801 },
802 {
803 name: "not progressing",
804
805 d: deployment(10, 4, 4, 4),
806 newStatus: newStatus(10, 4, 4, 4),
807
808 expected: false,
809 },
810 {
811 name: "progressing: old pods removed",
812
813 d: deployment(10, 4, 6, 6),
814 newStatus: newStatus(8, 4, 6, 6),
815
816 expected: true,
817 },
818 {
819 name: "not progressing: less new pods",
820
821 d: deployment(10, 7, 3, 3),
822 newStatus: newStatus(10, 6, 3, 3),
823
824 expected: false,
825 },
826 {
827 name: "progressing: less overall but more new pods",
828
829 d: deployment(10, 4, 7, 7),
830 newStatus: newStatus(8, 8, 5, 5),
831
832 expected: true,
833 },
834 {
835 name: "progressing: more ready pods",
836
837 d: deployment(10, 10, 9, 8),
838 newStatus: newStatus(10, 10, 10, 8),
839
840 expected: true,
841 },
842 {
843 name: "progressing: more available pods",
844
845 d: deployment(10, 10, 10, 9),
846 newStatus: newStatus(10, 10, 10, 10),
847
848 expected: true,
849 },
850 }
851
852 for _, test := range tests {
853 t.Run(test.name, func(t *testing.T) {
854 if got, exp := DeploymentProgressing(test.d, &test.newStatus), test.expected; got != exp {
855 t.Errorf("expected progressing: %t, got: %t", exp, got)
856 }
857 })
858 }
859 }
860
861 func TestDeploymentTimedOut(t *testing.T) {
862 var (
863 null *int32
864 ten = int32(10)
865 infinite = int32(math.MaxInt32)
866 )
867
868 timeFn := func(min, sec int) time.Time {
869 return time.Date(2016, 1, 1, 0, min, sec, 0, time.UTC)
870 }
871 deployment := func(condType apps.DeploymentConditionType, status v1.ConditionStatus, reason string, pds *int32, from time.Time) apps.Deployment {
872 return apps.Deployment{
873 Spec: apps.DeploymentSpec{
874 ProgressDeadlineSeconds: pds,
875 },
876 Status: apps.DeploymentStatus{
877 Conditions: []apps.DeploymentCondition{
878 {
879 Type: condType,
880 Status: status,
881 Reason: reason,
882 LastUpdateTime: metav1.Time{Time: from},
883 },
884 },
885 },
886 }
887 }
888
889 tests := []struct {
890 name string
891
892 d apps.Deployment
893 nowFn func() time.Time
894
895 expected bool
896 }{
897 {
898 name: "nil progressDeadlineSeconds specified - no timeout",
899
900 d: deployment(apps.DeploymentProgressing, v1.ConditionTrue, "", null, timeFn(1, 9)),
901 nowFn: func() time.Time { return timeFn(1, 20) },
902 expected: false,
903 },
904 {
905 name: "infinite progressDeadlineSeconds specified - no timeout",
906
907 d: deployment(apps.DeploymentProgressing, v1.ConditionTrue, "", &infinite, timeFn(1, 9)),
908 nowFn: func() time.Time { return timeFn(1, 20) },
909 expected: false,
910 },
911 {
912 name: "progressDeadlineSeconds: 10s, now - started => 00:01:20 - 00:01:09 => 11s",
913
914 d: deployment(apps.DeploymentProgressing, v1.ConditionTrue, "", &ten, timeFn(1, 9)),
915 nowFn: func() time.Time { return timeFn(1, 20) },
916 expected: true,
917 },
918 {
919 name: "progressDeadlineSeconds: 10s, now - started => 00:01:20 - 00:01:11 => 9s",
920
921 d: deployment(apps.DeploymentProgressing, v1.ConditionTrue, "", &ten, timeFn(1, 11)),
922 nowFn: func() time.Time { return timeFn(1, 20) },
923 expected: false,
924 },
925 {
926 name: "previous status was a complete deployment",
927
928 d: deployment(apps.DeploymentProgressing, v1.ConditionTrue, NewRSAvailableReason, nil, time.Time{}),
929 expected: false,
930 },
931 }
932
933 for _, test := range tests {
934 t.Run(test.name, func(t *testing.T) {
935 nowFn = test.nowFn
936 _, ctx := ktesting.NewTestContext(t)
937 if got, exp := DeploymentTimedOut(ctx, &test.d, &test.d.Status), test.expected; got != exp {
938 t.Errorf("expected timeout: %t, got: %t", exp, got)
939 }
940 })
941 }
942 }
943
944 func TestMaxUnavailable(t *testing.T) {
945 deployment := func(replicas int32, maxUnavailable intstr.IntOrString) apps.Deployment {
946 return apps.Deployment{
947 Spec: apps.DeploymentSpec{
948 Replicas: func(i int32) *int32 { return &i }(replicas),
949 Strategy: apps.DeploymentStrategy{
950 RollingUpdate: &apps.RollingUpdateDeployment{
951 MaxSurge: ptr.To(intstr.FromInt32(1)),
952 MaxUnavailable: &maxUnavailable,
953 },
954 Type: apps.RollingUpdateDeploymentStrategyType,
955 },
956 },
957 }
958 }
959 tests := []struct {
960 name string
961 deployment apps.Deployment
962 expected int32
963 }{
964 {
965 name: "maxUnavailable less than replicas",
966 deployment: deployment(10, intstr.FromInt32(5)),
967 expected: int32(5),
968 },
969 {
970 name: "maxUnavailable equal replicas",
971 deployment: deployment(10, intstr.FromInt32(10)),
972 expected: int32(10),
973 },
974 {
975 name: "maxUnavailable greater than replicas",
976 deployment: deployment(5, intstr.FromInt32(10)),
977 expected: int32(5),
978 },
979 {
980 name: "maxUnavailable with replicas is 0",
981 deployment: deployment(0, intstr.FromInt32(10)),
982 expected: int32(0),
983 },
984 {
985 name: "maxUnavailable with Recreate deployment strategy",
986 deployment: apps.Deployment{
987 Spec: apps.DeploymentSpec{
988 Strategy: apps.DeploymentStrategy{
989 Type: apps.RecreateDeploymentStrategyType,
990 },
991 },
992 },
993 expected: int32(0),
994 },
995 {
996 name: "maxUnavailable less than replicas with percents",
997 deployment: deployment(10, intstr.FromString("50%")),
998 expected: int32(5),
999 },
1000 {
1001 name: "maxUnavailable equal replicas with percents",
1002 deployment: deployment(10, intstr.FromString("100%")),
1003 expected: int32(10),
1004 },
1005 {
1006 name: "maxUnavailable greater than replicas with percents",
1007 deployment: deployment(5, intstr.FromString("100%")),
1008 expected: int32(5),
1009 },
1010 }
1011
1012 for _, test := range tests {
1013 t.Log(test.name)
1014 t.Run(test.name, func(t *testing.T) {
1015 maxUnavailable := MaxUnavailable(test.deployment)
1016 if test.expected != maxUnavailable {
1017 t.Fatalf("expected:%v, got:%v", test.expected, maxUnavailable)
1018 }
1019 })
1020 }
1021 }
1022
1023
1024 func TestAnnotationUtils(t *testing.T) {
1025
1026
1027 tDeployment := generateDeployment("nginx")
1028 tRS := generateRS(tDeployment)
1029 tDeployment.Annotations[RevisionAnnotation] = "1"
1030
1031
1032 t.Run("SetNewReplicaSetAnnotations", func(t *testing.T) {
1033 _, ctx := ktesting.NewTestContext(t)
1034
1035
1036 for i := 10; i < 20; i++ {
1037
1038 nextRevision := fmt.Sprintf("%d", i+1)
1039 SetNewReplicaSetAnnotations(ctx, &tDeployment, &tRS, nextRevision, true, 5)
1040
1041
1042 if i >= 12 {
1043 expectedHistoryAnnotation := fmt.Sprintf("%d,%d", i-1, i)
1044 if tRS.Annotations[RevisionHistoryAnnotation] != expectedHistoryAnnotation {
1045 t.Errorf("Revision History Expected=%s Obtained=%s", expectedHistoryAnnotation, tRS.Annotations[RevisionHistoryAnnotation])
1046 }
1047 }
1048 if tRS.Annotations[RevisionAnnotation] != nextRevision {
1049 t.Errorf("Revision Expected=%s Obtained=%s", nextRevision, tRS.Annotations[RevisionAnnotation])
1050 }
1051 }
1052 })
1053
1054
1055 t.Run("SetReplicasAnnotations", func(t *testing.T) {
1056 updated := SetReplicasAnnotations(&tRS, 10, 11)
1057 if !updated {
1058 t.Errorf("SetReplicasAnnotations() failed")
1059 }
1060 value, ok := tRS.Annotations[DesiredReplicasAnnotation]
1061 if !ok {
1062 t.Errorf("SetReplicasAnnotations did not set DesiredReplicasAnnotation")
1063 }
1064 if value != "10" {
1065 t.Errorf("SetReplicasAnnotations did not set DesiredReplicasAnnotation correctly value=%s", value)
1066 }
1067 if value, ok = tRS.Annotations[MaxReplicasAnnotation]; !ok {
1068 t.Errorf("SetReplicasAnnotations did not set DesiredReplicasAnnotation")
1069 }
1070 if value != "11" {
1071 t.Errorf("SetReplicasAnnotations did not set MaxReplicasAnnotation correctly value=%s", value)
1072 }
1073 })
1074
1075
1076 tRS.Annotations[DesiredReplicasAnnotation] = "1"
1077 tRS.Status.AvailableReplicas = 1
1078 tRS.Spec.Replicas = new(int32)
1079 *tRS.Spec.Replicas = 1
1080
1081 t.Run("IsSaturated", func(t *testing.T) {
1082 saturated := IsSaturated(&tDeployment, &tRS)
1083 if !saturated {
1084 t.Errorf("SetReplicasAnnotations Expected=true Obtained=false")
1085 }
1086 })
1087
1088 }
1089
1090 func TestReplicasAnnotationsNeedUpdate(t *testing.T) {
1091
1092 desiredReplicas := fmt.Sprintf("%d", int32(10))
1093 maxReplicas := fmt.Sprintf("%d", int32(20))
1094
1095 tests := []struct {
1096 name string
1097 replicaSet *apps.ReplicaSet
1098 expected bool
1099 }{
1100 {
1101 name: "test Annotations nil",
1102 replicaSet: &apps.ReplicaSet{
1103 ObjectMeta: metav1.ObjectMeta{Name: "hello", Namespace: "test"},
1104 Spec: apps.ReplicaSetSpec{
1105 Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}},
1106 },
1107 },
1108 expected: true,
1109 },
1110 {
1111 name: "test desiredReplicas update",
1112 replicaSet: &apps.ReplicaSet{
1113 ObjectMeta: metav1.ObjectMeta{
1114 Name: "hello",
1115 Namespace: "test",
1116 Annotations: map[string]string{DesiredReplicasAnnotation: "8", MaxReplicasAnnotation: maxReplicas},
1117 },
1118 Spec: apps.ReplicaSetSpec{
1119 Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}},
1120 },
1121 },
1122 expected: true,
1123 },
1124 {
1125 name: "test maxReplicas update",
1126 replicaSet: &apps.ReplicaSet{
1127 ObjectMeta: metav1.ObjectMeta{
1128 Name: "hello",
1129 Namespace: "test",
1130 Annotations: map[string]string{DesiredReplicasAnnotation: desiredReplicas, MaxReplicasAnnotation: "16"},
1131 },
1132 Spec: apps.ReplicaSetSpec{
1133 Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}},
1134 },
1135 },
1136 expected: true,
1137 },
1138 {
1139 name: "test needn't update",
1140 replicaSet: &apps.ReplicaSet{
1141 ObjectMeta: metav1.ObjectMeta{
1142 Name: "hello",
1143 Namespace: "test",
1144 Annotations: map[string]string{DesiredReplicasAnnotation: desiredReplicas, MaxReplicasAnnotation: maxReplicas},
1145 },
1146 Spec: apps.ReplicaSetSpec{
1147 Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}},
1148 },
1149 },
1150 expected: false,
1151 },
1152 }
1153
1154 for i, test := range tests {
1155 t.Run(test.name, func(t *testing.T) {
1156 result := ReplicasAnnotationsNeedUpdate(test.replicaSet, 10, 20)
1157 if result != test.expected {
1158 t.Errorf("case[%d]:%s Expected %v, Got: %v", i, test.name, test.expected, result)
1159 }
1160 })
1161 }
1162 }
1163
1164 func TestGetDeploymentsForReplicaSet(t *testing.T) {
1165 fakeInformerFactory := informers.NewSharedInformerFactory(&fake.Clientset{}, 0*time.Second)
1166 var deployments []*apps.Deployment
1167 for i := 0; i < 3; i++ {
1168 deployment := &apps.Deployment{
1169 ObjectMeta: metav1.ObjectMeta{
1170 Name: fmt.Sprintf("deployment-%d", i),
1171 Namespace: "test",
1172 },
1173 Spec: apps.DeploymentSpec{
1174 Selector: &metav1.LabelSelector{
1175 MatchLabels: map[string]string{
1176 "app": fmt.Sprintf("test-%d", i),
1177 },
1178 },
1179 },
1180 }
1181 deployments = append(deployments, deployment)
1182 fakeInformerFactory.Apps().V1().Deployments().Informer().GetStore().Add(deployment)
1183 }
1184 var rss []*apps.ReplicaSet
1185 for i := 0; i < 5; i++ {
1186 rs := &apps.ReplicaSet{
1187 ObjectMeta: metav1.ObjectMeta{
1188 Namespace: "test",
1189 Name: fmt.Sprintf("test-replicaSet-%d", i),
1190 Labels: map[string]string{
1191 "app": fmt.Sprintf("test-%d", i),
1192 "label": fmt.Sprintf("label-%d", i),
1193 },
1194 },
1195 }
1196 rss = append(rss, rs)
1197 }
1198 tests := []struct {
1199 name string
1200 rs *apps.ReplicaSet
1201 err error
1202 expect []*apps.Deployment
1203 }{
1204 {
1205 name: "GetDeploymentsForReplicaSet for rs-0",
1206 rs: rss[0],
1207 expect: []*apps.Deployment{deployments[0]},
1208 },
1209 {
1210 name: "GetDeploymentsForReplicaSet for rs-1",
1211 rs: rss[1],
1212 expect: []*apps.Deployment{deployments[1]},
1213 },
1214 {
1215 name: "GetDeploymentsForReplicaSet for rs-2",
1216 rs: rss[2],
1217 expect: []*apps.Deployment{deployments[2]},
1218 },
1219 {
1220 name: "GetDeploymentsForReplicaSet for rs-3",
1221 rs: rss[3],
1222 err: fmt.Errorf("could not find deployments set for ReplicaSet %s in namespace %s with labels: %v", rss[3].Name, rss[3].Namespace, rss[3].Labels),
1223 },
1224 {
1225 name: "GetDeploymentsForReplicaSet for rs-4",
1226 rs: rss[4],
1227 err: fmt.Errorf("could not find deployments set for ReplicaSet %s in namespace %s with labels: %v", rss[4].Name, rss[4].Namespace, rss[4].Labels),
1228 },
1229 }
1230 for _, test := range tests {
1231 t.Run(test.name, func(t *testing.T) {
1232 get, err := GetDeploymentsForReplicaSet(fakeInformerFactory.Apps().V1().Deployments().Lister(), test.rs)
1233 if err != nil {
1234 if err.Error() != test.err.Error() {
1235 t.Errorf("Error from GetDeploymentsForReplicaSet: %v", err)
1236 }
1237 } else if !reflect.DeepEqual(get, test.expect) {
1238 t.Errorf("Expect deployments %v, but got %v", test.expect, get)
1239 }
1240 })
1241 }
1242
1243 }
1244
1245 func TestMinAvailable(t *testing.T) {
1246 maxSurge := ptr.To(intstr.FromInt32(1))
1247 deployment := func(replicas int32, maxUnavailable intstr.IntOrString) *apps.Deployment {
1248 return &apps.Deployment{
1249 Spec: apps.DeploymentSpec{
1250 Replicas: ptr.To(replicas),
1251 Strategy: apps.DeploymentStrategy{
1252 RollingUpdate: &apps.RollingUpdateDeployment{
1253 MaxSurge: maxSurge,
1254 MaxUnavailable: &maxUnavailable,
1255 },
1256 Type: apps.RollingUpdateDeploymentStrategyType,
1257 },
1258 },
1259 }
1260 }
1261 tests := []struct {
1262 name string
1263 deployment *apps.Deployment
1264 expected int32
1265 }{
1266 {
1267 name: "replicas greater than maxUnavailable",
1268 deployment: deployment(10, intstr.FromInt32(5)),
1269 expected: 5,
1270 },
1271 {
1272 name: "replicas equal maxUnavailable",
1273 deployment: deployment(10, intstr.FromInt32(10)),
1274 expected: 0,
1275 },
1276 {
1277 name: "replicas less than maxUnavailable",
1278 deployment: deployment(5, intstr.FromInt32(10)),
1279 expected: 0,
1280 },
1281 {
1282 name: "replicas is 0",
1283 deployment: deployment(0, intstr.FromInt32(10)),
1284 expected: 0,
1285 },
1286 {
1287 name: "minAvailable with Recreate deployment strategy",
1288 deployment: &apps.Deployment{
1289 Spec: apps.DeploymentSpec{
1290 Replicas: ptr.To[int32](10),
1291 Strategy: apps.DeploymentStrategy{
1292 Type: apps.RecreateDeploymentStrategyType,
1293 },
1294 },
1295 },
1296 expected: 0,
1297 },
1298 {
1299 name: "replicas greater than maxUnavailable with percents",
1300 deployment: deployment(10, intstr.FromString("60%")),
1301 expected: 4,
1302 },
1303 {
1304 name: "replicas equal maxUnavailable with percents",
1305 deployment: deployment(10, intstr.FromString("100%")),
1306 expected: int32(0),
1307 },
1308 {
1309 name: "replicas less than maxUnavailable with percents",
1310 deployment: deployment(5, intstr.FromString("100%")),
1311 expected: 0,
1312 },
1313 }
1314 for _, tt := range tests {
1315 t.Run(tt.name, func(t *testing.T) {
1316 if got := MinAvailable(tt.deployment); got != tt.expected {
1317 t.Errorf("MinAvailable() = %v, want %v", got, tt.expected)
1318 }
1319 })
1320 }
1321 }
1322
View as plain text