k8s.io/kubernetes@v1.29.3/pkg/controller/deployment/util/deployment_util_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 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 // generateRS creates a replica set, with the input deployment's template as its template 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 // generateDeployment creates a deployment, with the input image as its template 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 // Run 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 // Test the same case in reverse order 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 // RSs are getting filtered correctly by rs.spec.replicas 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 // old replica set: spec.replicas=1, status.replicas=1, status.availableReplicas=1 745 // new replica set: spec.replicas=1, status.replicas=1, status.availableReplicas=0 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 // Set of simple tests for annotation related util functions 1024 func TestAnnotationUtils(t *testing.T) { 1025 1026 //Setup 1027 tDeployment := generateDeployment("nginx") 1028 tRS := generateRS(tDeployment) 1029 tDeployment.Annotations[RevisionAnnotation] = "1" 1030 1031 //Test Case 1: Check if anotations are copied properly from deployment to RS 1032 t.Run("SetNewReplicaSetAnnotations", func(t *testing.T) { 1033 _, ctx := ktesting.NewTestContext(t) 1034 1035 //Try to set the increment revision from 11 through 20 1036 for i := 10; i < 20; i++ { 1037 1038 nextRevision := fmt.Sprintf("%d", i+1) 1039 SetNewReplicaSetAnnotations(ctx, &tDeployment, &tRS, nextRevision, true, 5) 1040 //Now the ReplicaSets Revision Annotation should be i+1 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 //Test Case 2: Check if annotations are set properly 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 //Test Case 3: Check if annotations reflect deployments state 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 //Tear Down 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 }