sigs.k8s.io/cluster-api@v1.7.1/internal/controllers/machinedeployment/mdutil/util_test.go (about) 1 /* 2 Copyright 2018 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 mdutil 18 19 import ( 20 "fmt" 21 "math/rand" 22 "sort" 23 "strconv" 24 "testing" 25 "time" 26 27 . "github.com/onsi/gomega" 28 corev1 "k8s.io/api/core/v1" 29 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 30 "k8s.io/apimachinery/pkg/types" 31 "k8s.io/apimachinery/pkg/util/intstr" 32 "k8s.io/apiserver/pkg/storage/names" 33 "k8s.io/utils/ptr" 34 ctrl "sigs.k8s.io/controller-runtime" 35 36 clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" 37 ) 38 39 var ( 40 ctx = ctrl.SetupSignalHandler() 41 ) 42 43 func newDControllerRef(md *clusterv1.MachineDeployment) *metav1.OwnerReference { 44 isController := true 45 return &metav1.OwnerReference{ 46 APIVersion: "clusters/v1alpha", 47 Kind: "MachineDeployment", 48 Name: md.GetName(), 49 UID: md.GetUID(), 50 Controller: &isController, 51 } 52 } 53 54 // generateMS creates a machine set, with the input deployment's template as its template. 55 func generateMS(md clusterv1.MachineDeployment) clusterv1.MachineSet { 56 template := md.Spec.Template.DeepCopy() 57 return clusterv1.MachineSet{ 58 ObjectMeta: metav1.ObjectMeta{ 59 UID: randomUID(), 60 Name: names.SimpleNameGenerator.GenerateName("machineset"), 61 Labels: template.Labels, 62 OwnerReferences: []metav1.OwnerReference{*newDControllerRef(&md)}, 63 }, 64 Spec: clusterv1.MachineSetSpec{ 65 Replicas: new(int32), 66 Template: *template, 67 Selector: metav1.LabelSelector{MatchLabels: template.Labels}, 68 }, 69 } 70 } 71 72 func randomUID() types.UID { 73 return types.UID(strconv.FormatInt(rand.Int63(), 10)) //nolint:gosec 74 } 75 76 // generateDeployment creates a deployment, with the input image as its template. 77 func generateDeployment(image string) clusterv1.MachineDeployment { 78 machineLabels := map[string]string{"name": image} 79 return clusterv1.MachineDeployment{ 80 ObjectMeta: metav1.ObjectMeta{ 81 Name: image, 82 Annotations: make(map[string]string), 83 }, 84 Spec: clusterv1.MachineDeploymentSpec{ 85 Replicas: ptr.To[int32](3), 86 Selector: metav1.LabelSelector{MatchLabels: machineLabels}, 87 Template: clusterv1.MachineTemplateSpec{ 88 ObjectMeta: clusterv1.ObjectMeta{ 89 Labels: machineLabels, 90 }, 91 Spec: clusterv1.MachineSpec{ 92 NodeDrainTimeout: &metav1.Duration{Duration: 10 * time.Second}, 93 }, 94 }, 95 }, 96 } 97 } 98 99 func TestMachineSetsByDecreasingReplicas(t *testing.T) { 100 t0 := time.Now() 101 t1 := t0.Add(1 * time.Minute) 102 msAReplicas1T0 := &clusterv1.MachineSet{ 103 ObjectMeta: metav1.ObjectMeta{ 104 CreationTimestamp: metav1.Time{Time: t0}, 105 Name: "ms-a", 106 }, 107 Spec: clusterv1.MachineSetSpec{ 108 Replicas: ptr.To[int32](1), 109 }, 110 } 111 112 msAAReplicas3T0 := &clusterv1.MachineSet{ 113 ObjectMeta: metav1.ObjectMeta{ 114 CreationTimestamp: metav1.Time{Time: t0}, 115 Name: "ms-aa", 116 }, 117 Spec: clusterv1.MachineSetSpec{ 118 Replicas: ptr.To[int32](3), 119 }, 120 } 121 122 msBReplicas1T0 := &clusterv1.MachineSet{ 123 ObjectMeta: metav1.ObjectMeta{ 124 CreationTimestamp: metav1.Time{Time: t0}, 125 Name: "ms-b", 126 }, 127 Spec: clusterv1.MachineSetSpec{ 128 Replicas: ptr.To[int32](1), 129 }, 130 } 131 132 msAReplicas1T1 := &clusterv1.MachineSet{ 133 ObjectMeta: metav1.ObjectMeta{ 134 CreationTimestamp: metav1.Time{Time: t1}, 135 Name: "ms-a", 136 }, 137 Spec: clusterv1.MachineSetSpec{ 138 Replicas: ptr.To[int32](1), 139 }, 140 } 141 142 tests := []struct { 143 name string 144 machineSets []*clusterv1.MachineSet 145 want []*clusterv1.MachineSet 146 }{ 147 { 148 name: "machine set with higher replicas should be lower in the list", 149 machineSets: []*clusterv1.MachineSet{msAReplicas1T0, msAAReplicas3T0}, 150 want: []*clusterv1.MachineSet{msAAReplicas3T0, msAReplicas1T0}, 151 }, 152 { 153 name: "MachineSet created earlier should be lower in the list if replicas are the same", 154 machineSets: []*clusterv1.MachineSet{msAReplicas1T1, msAReplicas1T0}, 155 want: []*clusterv1.MachineSet{msAReplicas1T0, msAReplicas1T1}, 156 }, 157 { 158 name: "MachineSet with lower name should be lower in the list if the replicas and creationTimestamp are same", 159 machineSets: []*clusterv1.MachineSet{msBReplicas1T0, msAReplicas1T0}, 160 want: []*clusterv1.MachineSet{msAReplicas1T0, msBReplicas1T0}, 161 }, 162 } 163 164 for _, tt := range tests { 165 t.Run(tt.name, func(t *testing.T) { 166 // sort the machine sets and verify the sorted list 167 g := NewWithT(t) 168 sort.Sort(MachineSetsByDecreasingReplicas(tt.machineSets)) 169 g.Expect(tt.machineSets).To(BeComparableTo(tt.want)) 170 }) 171 } 172 } 173 174 func TestEqualMachineTemplate(t *testing.T) { 175 machineTemplate := &clusterv1.MachineTemplateSpec{ 176 ObjectMeta: clusterv1.ObjectMeta{ 177 Labels: map[string]string{"l1": "v1"}, 178 Annotations: map[string]string{"a1": "v1"}, 179 }, 180 Spec: clusterv1.MachineSpec{ 181 NodeDrainTimeout: &metav1.Duration{Duration: 10 * time.Second}, 182 NodeDeletionTimeout: &metav1.Duration{Duration: 10 * time.Second}, 183 NodeVolumeDetachTimeout: &metav1.Duration{Duration: 10 * time.Second}, 184 InfrastructureRef: corev1.ObjectReference{ 185 Name: "infra1", 186 Namespace: "default", 187 Kind: "InfrastructureMachine", 188 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 189 }, 190 Bootstrap: clusterv1.Bootstrap{ 191 ConfigRef: &corev1.ObjectReference{ 192 Name: "bootstrap1", 193 Namespace: "default", 194 Kind: "BootstrapConfig", 195 APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1", 196 }, 197 }, 198 }, 199 } 200 201 machineTemplateWithEmptyLabels := machineTemplate.DeepCopy() 202 machineTemplateWithEmptyLabels.Labels = map[string]string{} 203 204 machineTemplateWithDifferentLabels := machineTemplate.DeepCopy() 205 machineTemplateWithDifferentLabels.Labels = map[string]string{"l2": "v2"} 206 207 machineTemplateWithEmptyAnnotations := machineTemplate.DeepCopy() 208 machineTemplateWithEmptyAnnotations.Annotations = map[string]string{} 209 210 machineTemplateWithDifferentAnnotations := machineTemplate.DeepCopy() 211 machineTemplateWithDifferentAnnotations.Annotations = map[string]string{"a2": "v2"} 212 213 machineTemplateWithDifferentInPlaceMutableSpecFields := machineTemplate.DeepCopy() 214 machineTemplateWithDifferentInPlaceMutableSpecFields.Spec.NodeDrainTimeout = &metav1.Duration{Duration: 20 * time.Second} 215 machineTemplateWithDifferentInPlaceMutableSpecFields.Spec.NodeDeletionTimeout = &metav1.Duration{Duration: 20 * time.Second} 216 machineTemplateWithDifferentInPlaceMutableSpecFields.Spec.NodeVolumeDetachTimeout = &metav1.Duration{Duration: 20 * time.Second} 217 218 machineTemplateWithDifferentInfraRef := machineTemplate.DeepCopy() 219 machineTemplateWithDifferentInfraRef.Spec.InfrastructureRef.Name = "infra2" 220 221 machineTemplateWithDifferentInfraRefAPIVersion := machineTemplate.DeepCopy() 222 machineTemplateWithDifferentInfraRefAPIVersion.Spec.InfrastructureRef.APIVersion = "infrastructure.cluster.x-k8s.io/v1beta2" 223 224 machineTemplateWithDifferentBootstrapConfigRef := machineTemplate.DeepCopy() 225 machineTemplateWithDifferentBootstrapConfigRef.Spec.Bootstrap.ConfigRef.Name = "bootstrap2" 226 227 machineTemplateWithDifferentBootstrapConfigRefAPIVersion := machineTemplate.DeepCopy() 228 machineTemplateWithDifferentBootstrapConfigRefAPIVersion.Spec.Bootstrap.ConfigRef.APIVersion = "bootstrap.cluster.x-k8s.io/v1beta2" 229 230 tests := []struct { 231 Name string 232 Former, Latter *clusterv1.MachineTemplateSpec 233 Expected bool 234 }{ 235 { 236 Name: "Same spec, except latter does not have labels", 237 Former: machineTemplate, 238 Latter: machineTemplateWithEmptyLabels, 239 Expected: true, 240 }, 241 { 242 Name: "Same spec, except latter has different labels", 243 Former: machineTemplate, 244 Latter: machineTemplateWithDifferentLabels, 245 Expected: true, 246 }, 247 { 248 Name: "Same spec, except latter does not have annotations", 249 Former: machineTemplate, 250 Latter: machineTemplateWithEmptyAnnotations, 251 Expected: true, 252 }, 253 { 254 Name: "Same spec, except latter has different annotations", 255 Former: machineTemplate, 256 Latter: machineTemplateWithDifferentAnnotations, 257 Expected: true, 258 }, 259 { 260 Name: "Spec changes, latter has different in-place mutable spec fields", 261 Former: machineTemplate, 262 Latter: machineTemplateWithDifferentInPlaceMutableSpecFields, 263 Expected: true, 264 }, 265 { 266 Name: "Spec changes, latter has different InfrastructureRef", 267 Former: machineTemplate, 268 Latter: machineTemplateWithDifferentInfraRef, 269 Expected: false, 270 }, 271 { 272 Name: "Spec changes, latter has different Bootstrap.ConfigRef", 273 Former: machineTemplate, 274 Latter: machineTemplateWithDifferentBootstrapConfigRef, 275 Expected: false, 276 }, 277 { 278 Name: "Same spec, except latter has different InfrastructureRef APIVersion", 279 Former: machineTemplate, 280 Latter: machineTemplateWithDifferentInfraRefAPIVersion, 281 Expected: true, 282 }, 283 { 284 Name: "Same spec, except latter has different Bootstrap.ConfigRef APIVersion", 285 Former: machineTemplate, 286 Latter: machineTemplateWithDifferentBootstrapConfigRefAPIVersion, 287 Expected: true, 288 }, 289 } 290 291 for _, test := range tests { 292 t.Run(test.Name, func(t *testing.T) { 293 g := NewWithT(t) 294 295 runTest := func(t1, t2 *clusterv1.MachineTemplateSpec) { 296 // Run 297 equal := EqualMachineTemplate(t1, t2) 298 g.Expect(equal).To(Equal(test.Expected)) 299 g.Expect(t1.Labels).NotTo(BeNil()) 300 g.Expect(t2.Labels).NotTo(BeNil()) 301 } 302 303 runTest(test.Former, test.Latter) 304 // Test the same case in reverse order 305 runTest(test.Latter, test.Former) 306 }) 307 } 308 } 309 310 func TestFindNewMachineSet(t *testing.T) { 311 twoBeforeRolloutAfter := metav1.Now() 312 oneBeforeRolloutAfter := metav1.NewTime(twoBeforeRolloutAfter.Add(time.Minute)) 313 rolloutAfter := metav1.NewTime(oneBeforeRolloutAfter.Add(time.Minute)) 314 oneAfterRolloutAfter := metav1.NewTime(rolloutAfter.Add(time.Minute)) 315 twoAfterRolloutAfter := metav1.NewTime(oneAfterRolloutAfter.Add(time.Minute)) 316 317 deployment := generateDeployment("nginx") 318 deployment.Spec.RolloutAfter = &rolloutAfter 319 320 matchingMS := generateMS(deployment) 321 322 matchingMSHigherReplicas := generateMS(deployment) 323 matchingMSHigherReplicas.Spec.Replicas = ptr.To[int32](2) 324 325 matchingMSDiffersInPlaceMutableFields := generateMS(deployment) 326 matchingMSDiffersInPlaceMutableFields.Spec.Template.Spec.NodeDrainTimeout = &metav1.Duration{Duration: 20 * time.Second} 327 328 oldMS := generateMS(deployment) 329 oldMS.Spec.Template.Spec.InfrastructureRef.Name = "changed-infra-ref" 330 331 msCreatedTwoBeforeRolloutAfter := generateMS(deployment) 332 msCreatedTwoBeforeRolloutAfter.CreationTimestamp = twoBeforeRolloutAfter 333 334 msCreatedAfterRolloutAfter := generateMS(deployment) 335 msCreatedAfterRolloutAfter.CreationTimestamp = oneAfterRolloutAfter 336 337 tests := []struct { 338 Name string 339 deployment clusterv1.MachineDeployment 340 msList []*clusterv1.MachineSet 341 reconciliationTime *metav1.Time 342 expected *clusterv1.MachineSet 343 }{ 344 { 345 Name: "Get the MachineSet with the MachineTemplate that matches the intent of the MachineDeployment", 346 deployment: deployment, 347 msList: []*clusterv1.MachineSet{&oldMS, &matchingMS}, 348 expected: &matchingMS, 349 }, 350 { 351 Name: "Get the MachineSet with the higher replicas if multiple MachineSets match the desired intent on the MachineDeployment", 352 deployment: deployment, 353 msList: []*clusterv1.MachineSet{&oldMS, &matchingMS, &matchingMSHigherReplicas}, 354 expected: &matchingMSHigherReplicas, 355 }, 356 { 357 Name: "Get the MachineSet with the MachineTemplate that matches the desired intent on the MachineDeployment, except differs in in-place mutable fields", 358 deployment: deployment, 359 msList: []*clusterv1.MachineSet{&oldMS, &matchingMSDiffersInPlaceMutableFields}, 360 expected: &matchingMSDiffersInPlaceMutableFields, 361 }, 362 { 363 Name: "Get nil if no MachineSet matches the desired intent of the MachineDeployment", 364 deployment: deployment, 365 msList: []*clusterv1.MachineSet{&oldMS}, 366 expected: nil, 367 }, 368 { 369 Name: "Get the MachineSet if reconciliationTime < rolloutAfter", 370 deployment: deployment, 371 msList: []*clusterv1.MachineSet{&msCreatedTwoBeforeRolloutAfter}, 372 reconciliationTime: &oneBeforeRolloutAfter, 373 expected: &msCreatedTwoBeforeRolloutAfter, 374 }, 375 { 376 Name: "Get nil if reconciliationTime is > rolloutAfter and no MachineSet is created after rolloutAfter", 377 deployment: deployment, 378 msList: []*clusterv1.MachineSet{&msCreatedTwoBeforeRolloutAfter}, 379 reconciliationTime: &oneAfterRolloutAfter, 380 expected: nil, 381 }, 382 { 383 Name: "Get MachineSet created after RolloutAfter if reconciliationTime is > rolloutAfter", 384 deployment: deployment, 385 msList: []*clusterv1.MachineSet{&msCreatedAfterRolloutAfter, &msCreatedTwoBeforeRolloutAfter}, 386 reconciliationTime: &twoAfterRolloutAfter, 387 expected: &msCreatedAfterRolloutAfter, 388 }, 389 } 390 391 for i := range tests { 392 test := tests[i] 393 t.Run(test.Name, func(t *testing.T) { 394 g := NewWithT(t) 395 396 ms := FindNewMachineSet(&test.deployment, test.msList, test.reconciliationTime) 397 g.Expect(ms).To(BeComparableTo(test.expected)) 398 }) 399 } 400 } 401 402 func TestFindOldMachineSets(t *testing.T) { 403 twoBeforeRolloutAfter := metav1.Now() 404 oneBeforeRolloutAfter := metav1.NewTime(twoBeforeRolloutAfter.Add(time.Minute)) 405 rolloutAfter := metav1.NewTime(oneBeforeRolloutAfter.Add(time.Minute)) 406 oneAfterRolloutAfter := metav1.NewTime(rolloutAfter.Add(time.Minute)) 407 twoAfterRolloutAfter := metav1.NewTime(oneAfterRolloutAfter.Add(time.Minute)) 408 409 deployment := generateDeployment("nginx") 410 deployment.Spec.RolloutAfter = &rolloutAfter 411 412 newMS := generateMS(deployment) 413 newMS.Name = "aa" 414 newMS.Spec.Replicas = ptr.To[int32](1) 415 416 newMSHigherReplicas := generateMS(deployment) 417 newMSHigherReplicas.Spec.Replicas = ptr.To[int32](2) 418 419 newMSHigherName := generateMS(deployment) 420 newMSHigherName.Spec.Replicas = ptr.To[int32](1) 421 newMSHigherName.Name = "ab" 422 423 oldDeployment := generateDeployment("nginx") 424 oldDeployment.Spec.Template.Spec.InfrastructureRef.Name = "changed-infra-ref" 425 oldMS := generateMS(oldDeployment) 426 427 msCreatedTwoBeforeRolloutAfter := generateMS(deployment) 428 msCreatedTwoBeforeRolloutAfter.CreationTimestamp = twoBeforeRolloutAfter 429 430 msCreatedAfterRolloutAfter := generateMS(deployment) 431 msCreatedAfterRolloutAfter.CreationTimestamp = oneAfterRolloutAfter 432 433 tests := []struct { 434 Name string 435 deployment clusterv1.MachineDeployment 436 msList []*clusterv1.MachineSet 437 reconciliationTime *metav1.Time 438 expected []*clusterv1.MachineSet 439 }{ 440 { 441 Name: "Get old MachineSets", 442 deployment: deployment, 443 msList: []*clusterv1.MachineSet{&newMS, &oldMS}, 444 expected: []*clusterv1.MachineSet{&oldMS}, 445 }, 446 { 447 Name: "Get old MachineSets with no new MachineSet", 448 deployment: deployment, 449 msList: []*clusterv1.MachineSet{&oldMS}, 450 expected: []*clusterv1.MachineSet{&oldMS}, 451 }, 452 { 453 Name: "Get old MachineSets with two new MachineSets, only the MachineSet with higher replicas is seen as new MachineSet", 454 deployment: deployment, 455 msList: []*clusterv1.MachineSet{&oldMS, &newMS, &newMSHigherReplicas}, 456 expected: []*clusterv1.MachineSet{&oldMS, &newMS}, 457 }, 458 { 459 Name: "Get old MachineSets with two new MachineSets, when replicas are matching only the MachineSet with lower name is seen as new MachineSet", 460 deployment: deployment, 461 msList: []*clusterv1.MachineSet{&oldMS, &newMS, &newMSHigherName}, 462 expected: []*clusterv1.MachineSet{&oldMS, &newMSHigherName}, 463 }, 464 { 465 Name: "Get empty old MachineSets", 466 deployment: deployment, 467 msList: []*clusterv1.MachineSet{&newMS}, 468 expected: []*clusterv1.MachineSet{}, 469 }, 470 { 471 Name: "Get old MachineSets with new MachineSets, MachineSets created before rolloutAfter are considered new when reconciliationTime < rolloutAfter", 472 deployment: deployment, 473 msList: []*clusterv1.MachineSet{&msCreatedTwoBeforeRolloutAfter}, 474 reconciliationTime: &oneBeforeRolloutAfter, 475 expected: nil, 476 }, 477 { 478 Name: "Get old MachineSets with new MachineSets, MachineSets created after rolloutAfter are considered new when reconciliationTime > rolloutAfter", 479 deployment: deployment, 480 msList: []*clusterv1.MachineSet{&msCreatedTwoBeforeRolloutAfter, &msCreatedAfterRolloutAfter}, 481 reconciliationTime: &twoAfterRolloutAfter, 482 expected: []*clusterv1.MachineSet{&msCreatedTwoBeforeRolloutAfter}, 483 }, 484 } 485 486 for i := range tests { 487 test := tests[i] 488 t.Run(test.Name, func(t *testing.T) { 489 g := NewWithT(t) 490 491 allMS := FindOldMachineSets(&test.deployment, test.msList, test.reconciliationTime) 492 g.Expect(allMS).To(ConsistOf(test.expected)) 493 }) 494 } 495 } 496 497 func TestGetReplicaCountForMachineSets(t *testing.T) { 498 ms1 := generateMS(generateDeployment("foo")) 499 *(ms1.Spec.Replicas) = 1 500 ms1.Status.Replicas = 2 501 ms2 := generateMS(generateDeployment("bar")) 502 *(ms2.Spec.Replicas) = 5 503 ms2.Status.Replicas = 3 504 505 tests := []struct { 506 Name string 507 Sets []*clusterv1.MachineSet 508 ExpectedCount int32 509 ExpectedActual int32 510 ExpectedTotal int32 511 }{ 512 { 513 Name: "1:2 Replicas", 514 Sets: []*clusterv1.MachineSet{&ms1}, 515 ExpectedCount: 1, 516 ExpectedActual: 2, 517 ExpectedTotal: 2, 518 }, 519 { 520 Name: "6:5 Replicas", 521 Sets: []*clusterv1.MachineSet{&ms1, &ms2}, 522 ExpectedCount: 6, 523 ExpectedActual: 5, 524 ExpectedTotal: 7, 525 }, 526 } 527 528 for _, test := range tests { 529 t.Run(test.Name, func(t *testing.T) { 530 g := NewWithT(t) 531 532 g.Expect(GetReplicaCountForMachineSets(test.Sets)).To(Equal(test.ExpectedCount)) 533 g.Expect(GetActualReplicaCountForMachineSets(test.Sets)).To(Equal(test.ExpectedActual)) 534 g.Expect(TotalMachineSetsReplicaSum(test.Sets)).To(Equal(test.ExpectedTotal)) 535 }) 536 } 537 } 538 539 func TestResolveFenceposts(t *testing.T) { 540 tests := []struct { 541 maxSurge string 542 maxUnavailable string 543 desired int32 544 expectSurge int32 545 expectUnavailable int32 546 expectError bool 547 }{ 548 { 549 maxSurge: "0%", 550 maxUnavailable: "0%", 551 desired: 0, 552 expectSurge: 0, 553 expectUnavailable: 1, 554 expectError: false, 555 }, 556 { 557 maxSurge: "39%", 558 maxUnavailable: "39%", 559 desired: 10, 560 expectSurge: 4, 561 expectUnavailable: 3, 562 expectError: false, 563 }, 564 { 565 maxSurge: "oops", 566 maxUnavailable: "39%", 567 desired: 10, 568 expectSurge: 0, 569 expectUnavailable: 0, 570 expectError: true, 571 }, 572 { 573 maxSurge: "55%", 574 maxUnavailable: "urg", 575 desired: 10, 576 expectSurge: 0, 577 expectUnavailable: 0, 578 expectError: true, 579 }, 580 { 581 maxSurge: "5", 582 maxUnavailable: "1", 583 desired: 7, 584 expectSurge: 0, 585 expectUnavailable: 0, 586 expectError: true, 587 }, 588 } 589 590 for _, test := range tests { 591 t.Run("maxSurge="+test.maxSurge, func(t *testing.T) { 592 g := NewWithT(t) 593 594 maxSurge := intstr.FromString(test.maxSurge) 595 maxUnavail := intstr.FromString(test.maxUnavailable) 596 surge, unavail, err := ResolveFenceposts(&maxSurge, &maxUnavail, test.desired) 597 if test.expectError { 598 g.Expect(err).To(HaveOccurred()) 599 } else { 600 g.Expect(err).ToNot(HaveOccurred()) 601 } 602 g.Expect(surge).To(Equal(test.expectSurge)) 603 g.Expect(unavail).To(Equal(test.expectUnavailable)) 604 }) 605 } 606 } 607 608 func TestNewMSNewReplicas(t *testing.T) { 609 tests := []struct { 610 Name string 611 strategyType clusterv1.MachineDeploymentStrategyType 612 depReplicas int32 613 newMSReplicas int32 614 maxSurge int 615 expected int32 616 }{ 617 { 618 "can not scale up - to newMSReplicas", 619 clusterv1.RollingUpdateMachineDeploymentStrategyType, 620 1, 5, 1, 5, 621 }, 622 { 623 "scale up - to depReplicas", 624 clusterv1.RollingUpdateMachineDeploymentStrategyType, 625 6, 2, 10, 6, 626 }, 627 } 628 newDeployment := generateDeployment("nginx") 629 newRC := generateMS(newDeployment) 630 rs5 := generateMS(newDeployment) 631 *(rs5.Spec.Replicas) = 5 632 633 for _, test := range tests { 634 t.Run(test.Name, func(t *testing.T) { 635 g := NewWithT(t) 636 637 *(newDeployment.Spec.Replicas) = test.depReplicas 638 newDeployment.Spec.Strategy = &clusterv1.MachineDeploymentStrategy{Type: test.strategyType} 639 newDeployment.Spec.Strategy.RollingUpdate = &clusterv1.MachineRollingUpdateDeployment{ 640 MaxUnavailable: func(i int) *intstr.IntOrString { 641 x := intstr.FromInt(i) 642 return &x 643 }(1), 644 MaxSurge: func(i int) *intstr.IntOrString { 645 x := intstr.FromInt(i) 646 return &x 647 }(test.maxSurge), 648 } 649 *(newRC.Spec.Replicas) = test.newMSReplicas 650 ms, err := NewMSNewReplicas(&newDeployment, []*clusterv1.MachineSet{&rs5}, *newRC.Spec.Replicas) 651 g.Expect(err).ToNot(HaveOccurred()) 652 g.Expect(ms).To(Equal(test.expected)) 653 }) 654 } 655 } 656 657 func TestDeploymentComplete(t *testing.T) { 658 deployment := func(desired, current, updated, available, maxUnavailable, maxSurge int32) *clusterv1.MachineDeployment { 659 return &clusterv1.MachineDeployment{ 660 Spec: clusterv1.MachineDeploymentSpec{ 661 Replicas: &desired, 662 Strategy: &clusterv1.MachineDeploymentStrategy{ 663 RollingUpdate: &clusterv1.MachineRollingUpdateDeployment{ 664 MaxUnavailable: func(i int) *intstr.IntOrString { x := intstr.FromInt(i); return &x }(int(maxUnavailable)), 665 MaxSurge: func(i int) *intstr.IntOrString { x := intstr.FromInt(i); return &x }(int(maxSurge)), 666 }, 667 Type: clusterv1.RollingUpdateMachineDeploymentStrategyType, 668 }, 669 }, 670 Status: clusterv1.MachineDeploymentStatus{ 671 Replicas: current, 672 UpdatedReplicas: updated, 673 AvailableReplicas: available, 674 }, 675 } 676 } 677 678 tests := []struct { 679 name string 680 681 md *clusterv1.MachineDeployment 682 683 expected bool 684 }{ 685 { 686 name: "not complete: min but not all machines become available", 687 688 md: deployment(5, 5, 5, 4, 1, 0), 689 expected: false, 690 }, 691 { 692 name: "not complete: min availability is not honored", 693 694 md: deployment(5, 5, 5, 3, 1, 0), 695 expected: false, 696 }, 697 { 698 name: "complete", 699 700 md: deployment(5, 5, 5, 5, 0, 0), 701 expected: true, 702 }, 703 { 704 name: "not complete: all machines are available but not updated", 705 706 md: deployment(5, 5, 4, 5, 0, 0), 707 expected: false, 708 }, 709 { 710 name: "not complete: still running old machines", 711 712 // old machine set: spec.replicas=1, status.replicas=1, status.availableReplicas=1 713 // new machine set: spec.replicas=1, status.replicas=1, status.availableReplicas=0 714 md: deployment(1, 2, 1, 1, 0, 1), 715 expected: false, 716 }, 717 { 718 name: "not complete: one replica deployment never comes up", 719 720 md: deployment(1, 1, 1, 0, 1, 1), 721 expected: false, 722 }, 723 } 724 725 for i := range tests { 726 test := tests[i] 727 t.Run(test.name, func(t *testing.T) { 728 g := NewWithT(t) 729 730 g.Expect(DeploymentComplete(test.md, &test.md.Status)).To(Equal(test.expected)) 731 }) 732 } 733 } 734 735 func TestMaxUnavailable(t *testing.T) { 736 deployment := func(replicas int32, maxUnavailable intstr.IntOrString) clusterv1.MachineDeployment { 737 return clusterv1.MachineDeployment{ 738 Spec: clusterv1.MachineDeploymentSpec{ 739 Replicas: func(i int32) *int32 { return &i }(replicas), 740 Strategy: &clusterv1.MachineDeploymentStrategy{ 741 RollingUpdate: &clusterv1.MachineRollingUpdateDeployment{ 742 MaxSurge: func(i int) *intstr.IntOrString { x := intstr.FromInt(i); return &x }(int(1)), 743 MaxUnavailable: &maxUnavailable, 744 }, 745 Type: clusterv1.RollingUpdateMachineDeploymentStrategyType, 746 }, 747 }, 748 } 749 } 750 tests := []struct { 751 name string 752 deployment clusterv1.MachineDeployment 753 expected int32 754 }{ 755 { 756 name: "maxUnavailable less than replicas", 757 deployment: deployment(10, intstr.FromInt(5)), 758 expected: int32(5), 759 }, 760 { 761 name: "maxUnavailable equal replicas", 762 deployment: deployment(10, intstr.FromInt(10)), 763 expected: int32(10), 764 }, 765 { 766 name: "maxUnavailable greater than replicas", 767 deployment: deployment(5, intstr.FromInt(10)), 768 expected: int32(5), 769 }, 770 { 771 name: "maxUnavailable with replicas is 0", 772 deployment: deployment(0, intstr.FromInt(10)), 773 expected: int32(0), 774 }, 775 { 776 name: "maxUnavailable less than replicas with percents", 777 deployment: deployment(10, intstr.FromString("50%")), 778 expected: int32(5), 779 }, 780 { 781 name: "maxUnavailable equal replicas with percents", 782 deployment: deployment(10, intstr.FromString("100%")), 783 expected: int32(10), 784 }, 785 { 786 name: "maxUnavailable greater than replicas with percents", 787 deployment: deployment(5, intstr.FromString("100%")), 788 expected: int32(5), 789 }, 790 } 791 792 for _, test := range tests { 793 t.Run(test.name, func(t *testing.T) { 794 g := NewWithT(t) 795 796 g.Expect(MaxUnavailable(test.deployment)).To(Equal(test.expected)) 797 }) 798 } 799 } 800 801 // TestAnnotationUtils is a set of simple tests for annotation related util functions. 802 func TestAnnotationUtils(t *testing.T) { 803 // Setup 804 tDeployment := generateDeployment("nginx") 805 tDeployment.Spec.Replicas = ptr.To[int32](1) 806 tMS := generateMS(tDeployment) 807 808 // Test Case 1: Check if annotations are set properly 809 t.Run("SetReplicasAnnotations", func(t *testing.T) { 810 g := NewWithT(t) 811 812 g.Expect(SetReplicasAnnotations(&tMS, 10, 11)).To(BeTrue()) 813 g.Expect(tMS.Annotations).To(HaveKeyWithValue(clusterv1.DesiredReplicasAnnotation, "10")) 814 g.Expect(tMS.Annotations).To(HaveKeyWithValue(clusterv1.MaxReplicasAnnotation, "11")) 815 }) 816 817 // Test Case 2: Check if annotations reflect deployments state 818 tMS.Annotations[clusterv1.DesiredReplicasAnnotation] = "1" 819 tMS.Status.AvailableReplicas = 1 820 tMS.Spec.Replicas = new(int32) 821 *tMS.Spec.Replicas = 1 822 823 t.Run("IsSaturated", func(t *testing.T) { 824 g := NewWithT(t) 825 826 g.Expect(IsSaturated(&tDeployment, &tMS)).To(BeTrue()) 827 }) 828 } 829 830 func TestComputeMachineSetAnnotations(t *testing.T) { 831 deployment := generateDeployment("nginx") 832 deployment.Spec.Replicas = ptr.To[int32](3) 833 maxSurge := intstr.FromInt(1) 834 maxUnavailable := intstr.FromInt(0) 835 deployment.Spec.Strategy = &clusterv1.MachineDeploymentStrategy{ 836 Type: clusterv1.RollingUpdateMachineDeploymentStrategyType, 837 RollingUpdate: &clusterv1.MachineRollingUpdateDeployment{ 838 MaxSurge: &maxSurge, 839 MaxUnavailable: &maxUnavailable, 840 }, 841 } 842 deployment.Annotations = map[string]string{ 843 corev1.LastAppliedConfigAnnotation: "last-applied-configuration", 844 "key1": "value1", 845 } 846 847 tests := []struct { 848 name string 849 deployment *clusterv1.MachineDeployment 850 oldMSs []*clusterv1.MachineSet 851 ms *clusterv1.MachineSet 852 want map[string]string 853 wantErr bool 854 }{ 855 { 856 name: "Calculating annotations for a new MachineSet", 857 deployment: &deployment, 858 oldMSs: nil, 859 ms: nil, 860 want: map[string]string{ 861 "key1": "value1", 862 clusterv1.RevisionAnnotation: "1", 863 clusterv1.DesiredReplicasAnnotation: "3", 864 clusterv1.MaxReplicasAnnotation: "4", 865 }, 866 wantErr: false, 867 }, 868 { 869 name: "Calculating annotations for a new MachineSet - old MSs exist", 870 deployment: &deployment, 871 oldMSs: []*clusterv1.MachineSet{machineSetWithRevisionAndHistory("1", "")}, 872 ms: nil, 873 want: map[string]string{ 874 "key1": "value1", 875 clusterv1.RevisionAnnotation: "2", 876 clusterv1.DesiredReplicasAnnotation: "3", 877 clusterv1.MaxReplicasAnnotation: "4", 878 }, 879 wantErr: false, 880 }, 881 { 882 name: "Calculating annotations for a existing MachineSet", 883 deployment: &deployment, 884 oldMSs: nil, 885 ms: machineSetWithRevisionAndHistory("1", ""), 886 want: map[string]string{ 887 "key1": "value1", 888 clusterv1.RevisionAnnotation: "1", 889 clusterv1.DesiredReplicasAnnotation: "3", 890 clusterv1.MaxReplicasAnnotation: "4", 891 }, 892 wantErr: false, 893 }, 894 { 895 name: "Calculating annotations for a existing MachineSet - old MSs exist", 896 deployment: &deployment, 897 oldMSs: []*clusterv1.MachineSet{ 898 machineSetWithRevisionAndHistory("1", ""), 899 machineSetWithRevisionAndHistory("2", ""), 900 }, 901 ms: machineSetWithRevisionAndHistory("1", ""), 902 want: map[string]string{ 903 "key1": "value1", 904 clusterv1.RevisionAnnotation: "3", 905 clusterv1.RevisionHistoryAnnotation: "1", 906 clusterv1.DesiredReplicasAnnotation: "3", 907 clusterv1.MaxReplicasAnnotation: "4", 908 }, 909 wantErr: false, 910 }, 911 { 912 name: "Calculating annotations for a existing MachineSet - old MSs exist - existing revision is greater", 913 deployment: &deployment, 914 oldMSs: []*clusterv1.MachineSet{ 915 machineSetWithRevisionAndHistory("1", ""), 916 machineSetWithRevisionAndHistory("2", ""), 917 }, 918 ms: machineSetWithRevisionAndHistory("4", ""), 919 want: map[string]string{ 920 "key1": "value1", 921 clusterv1.RevisionAnnotation: "4", 922 clusterv1.DesiredReplicasAnnotation: "3", 923 clusterv1.MaxReplicasAnnotation: "4", 924 }, 925 wantErr: false, 926 }, 927 { 928 name: "Calculating annotations for a existing MachineSet - old MSs exist - ms already has revision history", 929 deployment: &deployment, 930 oldMSs: []*clusterv1.MachineSet{ 931 machineSetWithRevisionAndHistory("3", ""), 932 machineSetWithRevisionAndHistory("4", ""), 933 }, 934 ms: machineSetWithRevisionAndHistory("2", "1"), 935 want: map[string]string{ 936 "key1": "value1", 937 clusterv1.RevisionAnnotation: "5", 938 clusterv1.RevisionHistoryAnnotation: "1,2", 939 clusterv1.DesiredReplicasAnnotation: "3", 940 clusterv1.MaxReplicasAnnotation: "4", 941 }, 942 wantErr: false, 943 }, 944 } 945 946 for _, tt := range tests { 947 t.Run(tt.name, func(t *testing.T) { 948 g := NewWithT(t) 949 got, err := ComputeMachineSetAnnotations(ctx, tt.deployment, tt.oldMSs, tt.ms) 950 if tt.wantErr { 951 g.Expect(err).ShouldNot(HaveOccurred()) 952 } else { 953 g.Expect(err).ToNot(HaveOccurred()) 954 g.Expect(got).Should(Equal(tt.want)) 955 } 956 }) 957 } 958 } 959 960 func machineSetWithRevisionAndHistory(revision string, revisionHistory string) *clusterv1.MachineSet { 961 ms := &clusterv1.MachineSet{ 962 ObjectMeta: metav1.ObjectMeta{ 963 Annotations: map[string]string{ 964 clusterv1.RevisionAnnotation: revision, 965 }, 966 }, 967 } 968 if revisionHistory != "" { 969 ms.Annotations[clusterv1.RevisionHistoryAnnotation] = revisionHistory 970 } 971 return ms 972 } 973 974 func TestReplicasAnnotationsNeedUpdate(t *testing.T) { 975 desiredReplicas := fmt.Sprintf("%d", int32(10)) 976 maxReplicas := fmt.Sprintf("%d", int32(20)) 977 978 tests := []struct { 979 name string 980 machineSet *clusterv1.MachineSet 981 expected bool 982 }{ 983 { 984 name: "test Annotations nil", 985 machineSet: &clusterv1.MachineSet{ 986 ObjectMeta: metav1.ObjectMeta{Name: "hello", Namespace: metav1.NamespaceDefault}, 987 Spec: clusterv1.MachineSetSpec{ 988 Selector: metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}}, 989 }, 990 }, 991 expected: true, 992 }, 993 { 994 name: "test desiredReplicas update", 995 machineSet: &clusterv1.MachineSet{ 996 ObjectMeta: metav1.ObjectMeta{ 997 Name: "hello", 998 Namespace: metav1.NamespaceDefault, 999 Annotations: map[string]string{clusterv1.DesiredReplicasAnnotation: "8", clusterv1.MaxReplicasAnnotation: maxReplicas}, 1000 }, 1001 Spec: clusterv1.MachineSetSpec{ 1002 Selector: metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}}, 1003 }, 1004 }, 1005 expected: true, 1006 }, 1007 { 1008 name: "test maxReplicas update", 1009 machineSet: &clusterv1.MachineSet{ 1010 ObjectMeta: metav1.ObjectMeta{ 1011 Name: "hello", 1012 Namespace: metav1.NamespaceDefault, 1013 Annotations: map[string]string{clusterv1.DesiredReplicasAnnotation: desiredReplicas, clusterv1.MaxReplicasAnnotation: "16"}, 1014 }, 1015 Spec: clusterv1.MachineSetSpec{ 1016 Selector: metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}}, 1017 }, 1018 }, 1019 expected: true, 1020 }, 1021 { 1022 name: "test needn't update", 1023 machineSet: &clusterv1.MachineSet{ 1024 ObjectMeta: metav1.ObjectMeta{ 1025 Name: "hello", 1026 Namespace: metav1.NamespaceDefault, 1027 Annotations: map[string]string{clusterv1.DesiredReplicasAnnotation: desiredReplicas, clusterv1.MaxReplicasAnnotation: maxReplicas}, 1028 }, 1029 Spec: clusterv1.MachineSetSpec{ 1030 Selector: metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}}, 1031 }, 1032 }, 1033 expected: false, 1034 }, 1035 } 1036 1037 for _, test := range tests { 1038 t.Run(test.name, func(t *testing.T) { 1039 g := NewWithT(t) 1040 1041 g.Expect(ReplicasAnnotationsNeedUpdate(test.machineSet, 10, 20)).To(Equal(test.expected)) 1042 }) 1043 } 1044 }