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