sigs.k8s.io/cluster-api@v1.7.1/internal/controllers/machinedeployment/machinedeployment_sync_test.go (about) 1 /* 2 Copyright 2019 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 machinedeployment 18 19 import ( 20 "context" 21 "fmt" 22 "strings" 23 "testing" 24 "time" 25 26 . "github.com/onsi/gomega" 27 "github.com/pkg/errors" 28 corev1 "k8s.io/api/core/v1" 29 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 30 "k8s.io/apimachinery/pkg/types" 31 apirand "k8s.io/apimachinery/pkg/util/rand" 32 "k8s.io/client-go/tools/record" 33 "k8s.io/utils/ptr" 34 "sigs.k8s.io/controller-runtime/pkg/client" 35 "sigs.k8s.io/controller-runtime/pkg/client/fake" 36 37 clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" 38 capierrors "sigs.k8s.io/cluster-api/errors" 39 "sigs.k8s.io/cluster-api/internal/controllers/machinedeployment/mdutil" 40 "sigs.k8s.io/cluster-api/util/conditions" 41 ) 42 43 func TestCalculateStatus(t *testing.T) { 44 msStatusError := capierrors.MachineSetStatusError("some failure") 45 46 var tests = map[string]struct { 47 machineSets []*clusterv1.MachineSet 48 newMachineSet *clusterv1.MachineSet 49 deployment *clusterv1.MachineDeployment 50 expectedStatus clusterv1.MachineDeploymentStatus 51 }{ 52 "all machines are running": { 53 machineSets: []*clusterv1.MachineSet{{ 54 Spec: clusterv1.MachineSetSpec{ 55 Replicas: ptr.To[int32](2), 56 }, 57 Status: clusterv1.MachineSetStatus{ 58 Selector: "", 59 AvailableReplicas: 2, 60 ReadyReplicas: 2, 61 Replicas: 2, 62 ObservedGeneration: 1, 63 }, 64 }}, 65 newMachineSet: &clusterv1.MachineSet{ 66 Spec: clusterv1.MachineSetSpec{ 67 Replicas: ptr.To[int32](2), 68 }, 69 Status: clusterv1.MachineSetStatus{ 70 Selector: "", 71 AvailableReplicas: 2, 72 ReadyReplicas: 2, 73 Replicas: 2, 74 ObservedGeneration: 1, 75 }, 76 }, 77 deployment: &clusterv1.MachineDeployment{ 78 ObjectMeta: metav1.ObjectMeta{ 79 Generation: 2, 80 }, 81 Spec: clusterv1.MachineDeploymentSpec{ 82 Replicas: ptr.To[int32](2), 83 }, 84 }, 85 expectedStatus: clusterv1.MachineDeploymentStatus{ 86 ObservedGeneration: 2, 87 Replicas: 2, 88 UpdatedReplicas: 2, 89 ReadyReplicas: 2, 90 AvailableReplicas: 2, 91 UnavailableReplicas: 0, 92 Phase: "Running", 93 }, 94 }, 95 "scaling up": { 96 machineSets: []*clusterv1.MachineSet{{ 97 Spec: clusterv1.MachineSetSpec{ 98 Replicas: ptr.To[int32](2), 99 }, 100 Status: clusterv1.MachineSetStatus{ 101 Selector: "", 102 AvailableReplicas: 1, 103 ReadyReplicas: 1, 104 Replicas: 2, 105 ObservedGeneration: 1, 106 }, 107 }}, 108 newMachineSet: &clusterv1.MachineSet{ 109 Spec: clusterv1.MachineSetSpec{ 110 Replicas: ptr.To[int32](2), 111 }, 112 Status: clusterv1.MachineSetStatus{ 113 Selector: "", 114 AvailableReplicas: 1, 115 ReadyReplicas: 1, 116 Replicas: 2, 117 ObservedGeneration: 1, 118 }, 119 }, 120 deployment: &clusterv1.MachineDeployment{ 121 ObjectMeta: metav1.ObjectMeta{ 122 Generation: 2, 123 }, 124 Spec: clusterv1.MachineDeploymentSpec{ 125 Replicas: ptr.To[int32](2), 126 }, 127 }, 128 expectedStatus: clusterv1.MachineDeploymentStatus{ 129 ObservedGeneration: 2, 130 Replicas: 2, 131 UpdatedReplicas: 2, 132 ReadyReplicas: 1, 133 AvailableReplicas: 1, 134 UnavailableReplicas: 1, 135 Phase: "ScalingUp", 136 }, 137 }, 138 "scaling down": { 139 machineSets: []*clusterv1.MachineSet{{ 140 Spec: clusterv1.MachineSetSpec{ 141 Replicas: ptr.To[int32](2), 142 }, 143 Status: clusterv1.MachineSetStatus{ 144 Selector: "", 145 AvailableReplicas: 3, 146 ReadyReplicas: 2, 147 Replicas: 2, 148 ObservedGeneration: 1, 149 }, 150 }}, 151 newMachineSet: &clusterv1.MachineSet{ 152 Spec: clusterv1.MachineSetSpec{ 153 Replicas: ptr.To[int32](2), 154 }, 155 Status: clusterv1.MachineSetStatus{ 156 Selector: "", 157 AvailableReplicas: 3, 158 ReadyReplicas: 2, 159 Replicas: 2, 160 ObservedGeneration: 1, 161 }, 162 }, 163 deployment: &clusterv1.MachineDeployment{ 164 ObjectMeta: metav1.ObjectMeta{ 165 Generation: 2, 166 }, 167 Spec: clusterv1.MachineDeploymentSpec{ 168 Replicas: ptr.To[int32](2), 169 }, 170 }, 171 expectedStatus: clusterv1.MachineDeploymentStatus{ 172 ObservedGeneration: 2, 173 Replicas: 2, 174 UpdatedReplicas: 2, 175 ReadyReplicas: 2, 176 AvailableReplicas: 3, 177 UnavailableReplicas: 0, 178 Phase: "ScalingDown", 179 }, 180 }, 181 "MachineSet failed": { 182 machineSets: []*clusterv1.MachineSet{{ 183 Spec: clusterv1.MachineSetSpec{ 184 Replicas: ptr.To[int32](2), 185 }, 186 Status: clusterv1.MachineSetStatus{ 187 Selector: "", 188 AvailableReplicas: 0, 189 ReadyReplicas: 0, 190 Replicas: 2, 191 ObservedGeneration: 1, 192 FailureReason: &msStatusError, 193 }, 194 }}, 195 newMachineSet: &clusterv1.MachineSet{ 196 Spec: clusterv1.MachineSetSpec{ 197 Replicas: ptr.To[int32](2), 198 }, 199 Status: clusterv1.MachineSetStatus{ 200 Selector: "", 201 AvailableReplicas: 0, 202 ReadyReplicas: 0, 203 Replicas: 2, 204 ObservedGeneration: 1, 205 }, 206 }, 207 deployment: &clusterv1.MachineDeployment{ 208 ObjectMeta: metav1.ObjectMeta{ 209 Generation: 2, 210 }, 211 Spec: clusterv1.MachineDeploymentSpec{ 212 Replicas: ptr.To[int32](2), 213 }, 214 }, 215 expectedStatus: clusterv1.MachineDeploymentStatus{ 216 ObservedGeneration: 2, 217 Replicas: 2, 218 UpdatedReplicas: 2, 219 ReadyReplicas: 0, 220 AvailableReplicas: 0, 221 UnavailableReplicas: 2, 222 Phase: "Failed", 223 }, 224 }, 225 } 226 227 for name, test := range tests { 228 t.Run(name, func(t *testing.T) { 229 g := NewWithT(t) 230 231 actualStatus := calculateStatus(test.machineSets, test.newMachineSet, test.deployment) 232 g.Expect(actualStatus).To(BeComparableTo(test.expectedStatus)) 233 }) 234 } 235 } 236 237 func TestScaleMachineSet(t *testing.T) { 238 testCases := []struct { 239 name string 240 machineDeployment *clusterv1.MachineDeployment 241 machineSet *clusterv1.MachineSet 242 newScale int32 243 error error 244 }{ 245 { 246 name: "It fails when new MachineSet has no replicas", 247 machineDeployment: &clusterv1.MachineDeployment{ 248 Spec: clusterv1.MachineDeploymentSpec{ 249 Replicas: ptr.To[int32](2), 250 }, 251 }, 252 machineSet: &clusterv1.MachineSet{ 253 ObjectMeta: metav1.ObjectMeta{ 254 Namespace: "foo", 255 Name: "bar", 256 }, 257 }, 258 error: errors.Errorf("spec.replicas for MachineSet foo/bar is nil, this is unexpected"), 259 }, 260 { 261 name: "It fails when new MachineDeployment has no replicas", 262 machineDeployment: &clusterv1.MachineDeployment{ 263 ObjectMeta: metav1.ObjectMeta{ 264 Namespace: "foo", 265 Name: "bar", 266 }, 267 Spec: clusterv1.MachineDeploymentSpec{}, 268 }, 269 machineSet: &clusterv1.MachineSet{ 270 ObjectMeta: metav1.ObjectMeta{ 271 Namespace: "foo", 272 Name: "bar", 273 }, 274 Spec: clusterv1.MachineSetSpec{ 275 Replicas: ptr.To[int32](2), 276 }, 277 }, 278 error: errors.Errorf("spec.replicas for MachineDeployment foo/bar is nil, this is unexpected"), 279 }, 280 { 281 name: "Scale up", 282 machineDeployment: &clusterv1.MachineDeployment{ 283 ObjectMeta: metav1.ObjectMeta{ 284 Namespace: "foo", 285 Name: "bar", 286 }, 287 Spec: clusterv1.MachineDeploymentSpec{ 288 Strategy: &clusterv1.MachineDeploymentStrategy{ 289 Type: clusterv1.RollingUpdateMachineDeploymentStrategyType, 290 RollingUpdate: &clusterv1.MachineRollingUpdateDeployment{ 291 MaxUnavailable: intOrStrPtr(0), 292 MaxSurge: intOrStrPtr(2), 293 }, 294 }, 295 Replicas: ptr.To[int32](2), 296 }, 297 }, 298 machineSet: &clusterv1.MachineSet{ 299 ObjectMeta: metav1.ObjectMeta{ 300 Namespace: "foo", 301 Name: "bar", 302 }, 303 Spec: clusterv1.MachineSetSpec{ 304 Replicas: ptr.To[int32](0), 305 }, 306 }, 307 newScale: 2, 308 }, 309 { 310 name: "Scale down", 311 machineDeployment: &clusterv1.MachineDeployment{ 312 ObjectMeta: metav1.ObjectMeta{ 313 Namespace: "foo", 314 Name: "bar", 315 }, 316 Spec: clusterv1.MachineDeploymentSpec{ 317 Strategy: &clusterv1.MachineDeploymentStrategy{ 318 Type: clusterv1.RollingUpdateMachineDeploymentStrategyType, 319 RollingUpdate: &clusterv1.MachineRollingUpdateDeployment{ 320 MaxUnavailable: intOrStrPtr(0), 321 MaxSurge: intOrStrPtr(2), 322 }, 323 }, 324 Replicas: ptr.To[int32](2), 325 }, 326 }, 327 machineSet: &clusterv1.MachineSet{ 328 ObjectMeta: metav1.ObjectMeta{ 329 Namespace: "foo", 330 Name: "bar", 331 }, 332 Spec: clusterv1.MachineSetSpec{ 333 Replicas: ptr.To[int32](4), 334 }, 335 }, 336 newScale: 2, 337 }, 338 { 339 name: "Same replicas does not scale", 340 machineDeployment: &clusterv1.MachineDeployment{ 341 ObjectMeta: metav1.ObjectMeta{ 342 Namespace: "foo", 343 Name: "bar", 344 }, 345 Spec: clusterv1.MachineDeploymentSpec{ 346 Strategy: &clusterv1.MachineDeploymentStrategy{ 347 Type: clusterv1.RollingUpdateMachineDeploymentStrategyType, 348 RollingUpdate: &clusterv1.MachineRollingUpdateDeployment{ 349 MaxUnavailable: intOrStrPtr(0), 350 MaxSurge: intOrStrPtr(2), 351 }, 352 }, 353 Replicas: ptr.To[int32](2), 354 }, 355 }, 356 machineSet: &clusterv1.MachineSet{ 357 ObjectMeta: metav1.ObjectMeta{ 358 Namespace: "foo", 359 Name: "bar", 360 }, 361 Spec: clusterv1.MachineSetSpec{ 362 Replicas: ptr.To[int32](2), 363 }, 364 }, 365 newScale: 2, 366 }, 367 } 368 for _, tc := range testCases { 369 t.Run(tc.name, func(t *testing.T) { 370 g := NewWithT(t) 371 372 resources := []client.Object{ 373 tc.machineDeployment, 374 tc.machineSet, 375 } 376 377 r := &Reconciler{ 378 Client: fake.NewClientBuilder().WithObjects(resources...).Build(), 379 recorder: record.NewFakeRecorder(32), 380 } 381 382 err := r.scaleMachineSet(context.Background(), tc.machineSet, tc.newScale, tc.machineDeployment) 383 if tc.error != nil { 384 g.Expect(err.Error()).To(BeEquivalentTo(tc.error.Error())) 385 return 386 } 387 388 g.Expect(err).ToNot(HaveOccurred()) 389 390 freshMachineSet := &clusterv1.MachineSet{} 391 err = r.Client.Get(ctx, client.ObjectKeyFromObject(tc.machineSet), freshMachineSet) 392 g.Expect(err).ToNot(HaveOccurred()) 393 394 g.Expect(*freshMachineSet.Spec.Replicas).To(BeEquivalentTo(tc.newScale)) 395 396 expectedMachineSetAnnotations := map[string]string{ 397 clusterv1.DesiredReplicasAnnotation: fmt.Sprintf("%d", *tc.machineDeployment.Spec.Replicas), 398 clusterv1.MaxReplicasAnnotation: fmt.Sprintf("%d", (*tc.machineDeployment.Spec.Replicas)+mdutil.MaxSurge(*tc.machineDeployment)), 399 } 400 g.Expect(freshMachineSet.GetAnnotations()).To(BeEquivalentTo(expectedMachineSetAnnotations)) 401 }) 402 } 403 } 404 405 func newTestMachineDeployment(pds *int32, replicas, statusReplicas, updatedReplicas, availableReplicas int32, conditions clusterv1.Conditions) *clusterv1.MachineDeployment { 406 d := &clusterv1.MachineDeployment{ 407 ObjectMeta: metav1.ObjectMeta{ 408 Name: "progress-test", 409 }, 410 Spec: clusterv1.MachineDeploymentSpec{ 411 ProgressDeadlineSeconds: pds, 412 Replicas: &replicas, 413 Strategy: &clusterv1.MachineDeploymentStrategy{ 414 Type: clusterv1.RollingUpdateMachineDeploymentStrategyType, 415 RollingUpdate: &clusterv1.MachineRollingUpdateDeployment{ 416 MaxUnavailable: intOrStrPtr(0), 417 MaxSurge: intOrStrPtr(1), 418 DeletePolicy: ptr.To("Oldest"), 419 }, 420 }, 421 }, 422 Status: clusterv1.MachineDeploymentStatus{ 423 Replicas: statusReplicas, 424 UpdatedReplicas: updatedReplicas, 425 AvailableReplicas: availableReplicas, 426 Conditions: conditions, 427 }, 428 } 429 return d 430 } 431 432 // helper to create MS with given availableReplicas. 433 func newTestMachinesetWithReplicas(name string, specReplicas, statusReplicas, availableReplicas int32, conditions clusterv1.Conditions) *clusterv1.MachineSet { 434 return &clusterv1.MachineSet{ 435 ObjectMeta: metav1.ObjectMeta{ 436 Name: name, 437 CreationTimestamp: metav1.Time{}, 438 Namespace: metav1.NamespaceDefault, 439 }, 440 Spec: clusterv1.MachineSetSpec{ 441 Replicas: ptr.To[int32](specReplicas), 442 }, 443 Status: clusterv1.MachineSetStatus{ 444 AvailableReplicas: availableReplicas, 445 Replicas: statusReplicas, 446 Conditions: conditions, 447 }, 448 } 449 } 450 451 func TestSyncDeploymentStatus(t *testing.T) { 452 pds := int32(60) 453 tests := []struct { 454 name string 455 d *clusterv1.MachineDeployment 456 oldMachineSets []*clusterv1.MachineSet 457 newMachineSet *clusterv1.MachineSet 458 expectedConditions []*clusterv1.Condition 459 }{ 460 { 461 name: "Deployment not available: MachineDeploymentAvailableCondition should exist and be false", 462 d: newTestMachineDeployment(&pds, 3, 2, 2, 2, clusterv1.Conditions{}), 463 oldMachineSets: []*clusterv1.MachineSet{}, 464 newMachineSet: newTestMachinesetWithReplicas("foo", 3, 2, 2, clusterv1.Conditions{}), 465 expectedConditions: []*clusterv1.Condition{ 466 { 467 Type: clusterv1.MachineDeploymentAvailableCondition, 468 Status: corev1.ConditionFalse, 469 Severity: clusterv1.ConditionSeverityWarning, 470 Reason: clusterv1.WaitingForAvailableMachinesReason, 471 }, 472 }, 473 }, 474 { 475 name: "Deployment Available: MachineDeploymentAvailableCondition should exist and be true", 476 d: newTestMachineDeployment(&pds, 3, 3, 3, 3, clusterv1.Conditions{}), 477 oldMachineSets: []*clusterv1.MachineSet{}, 478 newMachineSet: newTestMachinesetWithReplicas("foo", 3, 3, 3, clusterv1.Conditions{}), 479 expectedConditions: []*clusterv1.Condition{ 480 { 481 Type: clusterv1.MachineDeploymentAvailableCondition, 482 Status: corev1.ConditionTrue, 483 }, 484 }, 485 }, 486 { 487 name: "MachineSet exist: MachineSetReadyCondition should exist and mirror MachineSet Ready condition", 488 d: newTestMachineDeployment(&pds, 3, 3, 3, 3, clusterv1.Conditions{}), 489 oldMachineSets: []*clusterv1.MachineSet{}, 490 newMachineSet: newTestMachinesetWithReplicas("foo", 3, 3, 3, clusterv1.Conditions{ 491 { 492 Type: clusterv1.ReadyCondition, 493 Status: corev1.ConditionFalse, 494 Reason: "TestErrorResaon", 495 Message: "test error messsage", 496 }, 497 }), 498 expectedConditions: []*clusterv1.Condition{ 499 { 500 Type: clusterv1.MachineSetReadyCondition, 501 Status: corev1.ConditionFalse, 502 Reason: "TestErrorResaon", 503 Message: "test error messsage", 504 }, 505 }, 506 }, 507 { 508 name: "MachineSet doesn't exist: MachineSetReadyCondition should exist and be false", 509 d: newTestMachineDeployment(&pds, 3, 3, 3, 3, clusterv1.Conditions{}), 510 oldMachineSets: []*clusterv1.MachineSet{}, 511 newMachineSet: nil, 512 expectedConditions: []*clusterv1.Condition{ 513 { 514 Type: clusterv1.MachineSetReadyCondition, 515 Status: corev1.ConditionFalse, 516 Severity: clusterv1.ConditionSeverityInfo, 517 Reason: clusterv1.WaitingForMachineSetFallbackReason, 518 }, 519 }, 520 }, 521 } 522 523 for _, test := range tests { 524 t.Run(test.name, func(t *testing.T) { 525 g := NewWithT(t) 526 r := &Reconciler{ 527 Client: fake.NewClientBuilder().Build(), 528 recorder: record.NewFakeRecorder(32), 529 } 530 allMachineSets := append(test.oldMachineSets, test.newMachineSet) 531 err := r.syncDeploymentStatus(allMachineSets, test.newMachineSet, test.d) 532 g.Expect(err).ToNot(HaveOccurred()) 533 assertConditions(t, test.d, test.expectedConditions...) 534 }) 535 } 536 } 537 538 func TestComputeDesiredMachineSet(t *testing.T) { 539 duration5s := &metav1.Duration{Duration: 5 * time.Second} 540 duration10s := &metav1.Duration{Duration: 10 * time.Second} 541 542 infraRef := corev1.ObjectReference{ 543 Kind: "GenericInfrastructureMachineTemplate", 544 Name: "infra-template-1", 545 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 546 } 547 bootstrapRef := corev1.ObjectReference{ 548 Kind: "GenericBootstrapConfigTemplate", 549 Name: "bootstrap-template-1", 550 APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1", 551 } 552 553 deployment := &clusterv1.MachineDeployment{ 554 ObjectMeta: metav1.ObjectMeta{ 555 Namespace: "default", 556 Name: "md1", 557 Annotations: map[string]string{"top-level-annotation": "top-level-annotation-value"}, 558 }, 559 Spec: clusterv1.MachineDeploymentSpec{ 560 ClusterName: "test-cluster", 561 Replicas: ptr.To[int32](3), 562 MinReadySeconds: ptr.To[int32](10), 563 Strategy: &clusterv1.MachineDeploymentStrategy{ 564 Type: clusterv1.RollingUpdateMachineDeploymentStrategyType, 565 RollingUpdate: &clusterv1.MachineRollingUpdateDeployment{ 566 MaxSurge: intOrStrPtr(1), 567 DeletePolicy: ptr.To("Random"), 568 MaxUnavailable: intOrStrPtr(0), 569 }, 570 }, 571 Selector: metav1.LabelSelector{ 572 MatchLabels: map[string]string{"k1": "v1"}, 573 }, 574 Template: clusterv1.MachineTemplateSpec{ 575 ObjectMeta: clusterv1.ObjectMeta{ 576 Labels: map[string]string{"machine-label1": "machine-value1"}, 577 Annotations: map[string]string{"machine-annotation1": "machine-value1"}, 578 }, 579 Spec: clusterv1.MachineSpec{ 580 Version: ptr.To("v1.25.3"), 581 InfrastructureRef: infraRef, 582 Bootstrap: clusterv1.Bootstrap{ 583 ConfigRef: &bootstrapRef, 584 }, 585 NodeDrainTimeout: duration10s, 586 NodeVolumeDetachTimeout: duration10s, 587 NodeDeletionTimeout: duration10s, 588 }, 589 }, 590 }, 591 } 592 593 skeletonMSBasedOnMD := &clusterv1.MachineSet{ 594 ObjectMeta: metav1.ObjectMeta{ 595 Namespace: "default", 596 Labels: map[string]string{"machine-label1": "machine-value1"}, 597 Annotations: map[string]string{"top-level-annotation": "top-level-annotation-value"}, 598 }, 599 Spec: clusterv1.MachineSetSpec{ 600 ClusterName: "test-cluster", 601 Replicas: ptr.To[int32](3), 602 MinReadySeconds: 10, 603 DeletePolicy: string(clusterv1.RandomMachineSetDeletePolicy), 604 Selector: metav1.LabelSelector{MatchLabels: map[string]string{"k1": "v1"}}, 605 Template: *deployment.Spec.Template.DeepCopy(), 606 }, 607 } 608 609 t.Run("should compute a new MachineSet when no old MachineSets exist", func(t *testing.T) { 610 expectedMS := skeletonMSBasedOnMD.DeepCopy() 611 612 g := NewWithT(t) 613 actualMS, err := (&Reconciler{}).computeDesiredMachineSet(ctx, deployment, nil, nil) 614 g.Expect(err).ToNot(HaveOccurred()) 615 assertMachineSet(g, actualMS, expectedMS) 616 }) 617 618 t.Run("should compute a new MachineSet when old MachineSets exist", func(t *testing.T) { 619 oldMS := skeletonMSBasedOnMD.DeepCopy() 620 oldMS.Spec.Replicas = ptr.To[int32](2) 621 622 expectedMS := skeletonMSBasedOnMD.DeepCopy() 623 expectedMS.Spec.Replicas = ptr.To[int32](2) // 4 (maxsurge+replicas) - 2 (replicas of old ms) = 2 624 625 g := NewWithT(t) 626 actualMS, err := (&Reconciler{}).computeDesiredMachineSet(ctx, deployment, nil, []*clusterv1.MachineSet{oldMS}) 627 g.Expect(err).ToNot(HaveOccurred()) 628 assertMachineSet(g, actualMS, expectedMS) 629 }) 630 631 t.Run("should compute the updated MachineSet when no old MachineSets exists", func(t *testing.T) { 632 uniqueID := apirand.String(5) 633 existingMS := skeletonMSBasedOnMD.DeepCopy() 634 // computeDesiredMachineSet should retain the UID, name and the "machine-template-hash" label value 635 // of the existing machine. 636 // Other fields like labels, annotations, node timeout, etc are expected to change. 637 existingMSUID := types.UID("abc-123-uid") 638 existingMS.UID = existingMSUID 639 existingMS.Name = deployment.Name + "-" + uniqueID 640 existingMS.Labels = map[string]string{ 641 clusterv1.MachineDeploymentUniqueLabel: uniqueID, 642 "ms-label-1": "ms-value-1", 643 } 644 existingMS.Annotations = nil 645 existingMS.Spec.Template.Labels = map[string]string{ 646 clusterv1.MachineDeploymentUniqueLabel: uniqueID, 647 "ms-label-2": "ms-value-2", 648 } 649 existingMS.Spec.Template.Annotations = nil 650 existingMS.Spec.Template.Spec.NodeDrainTimeout = duration5s 651 existingMS.Spec.Template.Spec.NodeDeletionTimeout = duration5s 652 existingMS.Spec.Template.Spec.NodeVolumeDetachTimeout = duration5s 653 existingMS.Spec.DeletePolicy = string(clusterv1.NewestMachineSetDeletePolicy) 654 existingMS.Spec.MinReadySeconds = 0 655 656 expectedMS := skeletonMSBasedOnMD.DeepCopy() 657 expectedMS.UID = existingMSUID 658 expectedMS.Name = deployment.Name + "-" + uniqueID 659 expectedMS.Labels[clusterv1.MachineDeploymentUniqueLabel] = uniqueID 660 expectedMS.Spec.Template.Labels[clusterv1.MachineDeploymentUniqueLabel] = uniqueID 661 662 g := NewWithT(t) 663 actualMS, err := (&Reconciler{}).computeDesiredMachineSet(ctx, deployment, existingMS, nil) 664 g.Expect(err).ToNot(HaveOccurred()) 665 assertMachineSet(g, actualMS, expectedMS) 666 }) 667 668 t.Run("should compute the updated MachineSet when old MachineSets exist", func(t *testing.T) { 669 uniqueID := apirand.String(5) 670 existingMS := skeletonMSBasedOnMD.DeepCopy() 671 existingMSUID := types.UID("abc-123-uid") 672 existingMS.UID = existingMSUID 673 existingMS.Name = deployment.Name + "-" + uniqueID 674 existingMS.Labels = map[string]string{ 675 clusterv1.MachineDeploymentUniqueLabel: uniqueID, 676 "ms-label-1": "ms-value-1", 677 } 678 existingMS.Annotations = nil 679 existingMS.Spec.Template.Labels = map[string]string{ 680 clusterv1.MachineDeploymentUniqueLabel: uniqueID, 681 "ms-label-2": "ms-value-2", 682 } 683 existingMS.Spec.Template.Annotations = nil 684 existingMS.Spec.Template.Spec.NodeDrainTimeout = duration5s 685 existingMS.Spec.Template.Spec.NodeDeletionTimeout = duration5s 686 existingMS.Spec.Template.Spec.NodeVolumeDetachTimeout = duration5s 687 existingMS.Spec.DeletePolicy = string(clusterv1.NewestMachineSetDeletePolicy) 688 existingMS.Spec.MinReadySeconds = 0 689 690 oldMS := skeletonMSBasedOnMD.DeepCopy() 691 oldMS.Spec.Replicas = ptr.To[int32](2) 692 693 // Note: computeDesiredMachineSet does not modify the replicas on the updated MachineSet. 694 // Therefore, even though we have the old machineset with replicas 2 the updatedMS does not 695 // get modified replicas (2 = 4(maxsuge+spec.replica) - 2(oldMS replicas)). 696 // Nb. The final replicas of the MachineSet are calculated elsewhere. 697 expectedMS := skeletonMSBasedOnMD.DeepCopy() 698 expectedMS.UID = existingMSUID 699 expectedMS.Name = deployment.Name + "-" + uniqueID 700 expectedMS.Labels[clusterv1.MachineDeploymentUniqueLabel] = uniqueID 701 expectedMS.Spec.Template.Labels[clusterv1.MachineDeploymentUniqueLabel] = uniqueID 702 703 g := NewWithT(t) 704 actualMS, err := (&Reconciler{}).computeDesiredMachineSet(ctx, deployment, existingMS, []*clusterv1.MachineSet{oldMS}) 705 g.Expect(err).ToNot(HaveOccurred()) 706 assertMachineSet(g, actualMS, expectedMS) 707 }) 708 709 t.Run("should compute the updated MachineSet when no old MachineSets exists (", func(t *testing.T) { 710 // Set rollout strategy to "OnDelete". 711 deployment := deployment.DeepCopy() 712 deployment.Spec.Strategy = &clusterv1.MachineDeploymentStrategy{ 713 Type: clusterv1.OnDeleteMachineDeploymentStrategyType, 714 RollingUpdate: nil, 715 } 716 717 uniqueID := apirand.String(5) 718 existingMS := skeletonMSBasedOnMD.DeepCopy() 719 // computeDesiredMachineSet should retain the UID, name and the "machine-template-hash" label value 720 // of the existing machine. 721 // Other fields like labels, annotations, node timeout, etc are expected to change. 722 existingMSUID := types.UID("abc-123-uid") 723 existingMS.UID = existingMSUID 724 existingMS.Name = deployment.Name + "-" + uniqueID 725 existingMS.Labels = map[string]string{ 726 clusterv1.MachineDeploymentUniqueLabel: uniqueID, 727 "ms-label-1": "ms-value-1", 728 } 729 existingMS.Annotations = nil 730 existingMS.Spec.Template.Labels = map[string]string{ 731 clusterv1.MachineDeploymentUniqueLabel: uniqueID, 732 "ms-label-2": "ms-value-2", 733 } 734 existingMS.Spec.Template.Annotations = nil 735 existingMS.Spec.Template.Spec.NodeDrainTimeout = duration5s 736 existingMS.Spec.Template.Spec.NodeDeletionTimeout = duration5s 737 existingMS.Spec.Template.Spec.NodeVolumeDetachTimeout = duration5s 738 existingMS.Spec.DeletePolicy = string(clusterv1.NewestMachineSetDeletePolicy) 739 existingMS.Spec.MinReadySeconds = 0 740 741 expectedMS := skeletonMSBasedOnMD.DeepCopy() 742 expectedMS.UID = existingMSUID 743 expectedMS.Name = deployment.Name + "-" + uniqueID 744 expectedMS.Labels[clusterv1.MachineDeploymentUniqueLabel] = uniqueID 745 expectedMS.Spec.Template.Labels[clusterv1.MachineDeploymentUniqueLabel] = uniqueID 746 // DeletePolicy should be empty with rollout strategy "OnDelete". 747 expectedMS.Spec.DeletePolicy = "" 748 749 g := NewWithT(t) 750 actualMS, err := (&Reconciler{}).computeDesiredMachineSet(ctx, deployment, existingMS, nil) 751 g.Expect(err).ToNot(HaveOccurred()) 752 assertMachineSet(g, actualMS, expectedMS) 753 }) 754 } 755 756 func assertMachineSet(g *WithT, actualMS *clusterv1.MachineSet, expectedMS *clusterv1.MachineSet) { 757 // check UID 758 if expectedMS.UID != "" { 759 g.Expect(actualMS.UID).Should(Equal(expectedMS.UID)) 760 } 761 // Check Name 762 if expectedMS.Name != "" { 763 g.Expect(actualMS.Name).Should(Equal(expectedMS.Name)) 764 } 765 // Check Namespace 766 g.Expect(actualMS.Namespace).Should(Equal(expectedMS.Namespace)) 767 768 // Check Replicas 769 g.Expect(actualMS.Spec.Replicas).ShouldNot(BeNil()) 770 g.Expect(actualMS.Spec.Replicas).Should(HaveValue(Equal(*expectedMS.Spec.Replicas))) 771 772 // Check ClusterName 773 g.Expect(actualMS.Spec.ClusterName).Should(Equal(expectedMS.Spec.ClusterName)) 774 775 // Check Labels 776 for k, v := range expectedMS.Labels { 777 g.Expect(actualMS.Labels).Should(HaveKeyWithValue(k, v)) 778 } 779 for k, v := range expectedMS.Spec.Template.Labels { 780 g.Expect(actualMS.Spec.Template.Labels).Should(HaveKeyWithValue(k, v)) 781 } 782 // Verify that the labels also has the unique identifier key. 783 g.Expect(actualMS.Labels).Should(HaveKey(clusterv1.MachineDeploymentUniqueLabel)) 784 g.Expect(actualMS.Spec.Template.Labels).Should(HaveKey(clusterv1.MachineDeploymentUniqueLabel)) 785 786 // Check Annotations 787 // Note: More nuanced validation of the Revision annotation calculations are done when testing `ComputeMachineSetAnnotations`. 788 for k, v := range expectedMS.Annotations { 789 g.Expect(actualMS.Annotations).Should(HaveKeyWithValue(k, v)) 790 } 791 for k, v := range expectedMS.Spec.Template.Annotations { 792 g.Expect(actualMS.Spec.Template.Annotations).Should(HaveKeyWithValue(k, v)) 793 } 794 795 // Check MinReadySeconds 796 g.Expect(actualMS.Spec.MinReadySeconds).Should(Equal(expectedMS.Spec.MinReadySeconds)) 797 798 // Check DeletePolicy 799 g.Expect(actualMS.Spec.DeletePolicy).Should(Equal(expectedMS.Spec.DeletePolicy)) 800 801 // Check MachineTemplateSpec 802 g.Expect(actualMS.Spec.Template.Spec).Should(BeComparableTo(expectedMS.Spec.Template.Spec)) 803 } 804 805 // asserts the conditions set on the Getter object. 806 // TODO: replace this with util.condition.MatchConditions (or a new matcher in controller runtime komega). 807 func assertConditions(t *testing.T, from conditions.Getter, conditions ...*clusterv1.Condition) { 808 t.Helper() 809 810 for _, condition := range conditions { 811 assertCondition(t, from, condition) 812 } 813 } 814 815 // asserts whether a condition of type is set on the Getter object 816 // when the condition is true, asserting the reason/severity/message 817 // for the condition are avoided. 818 func assertCondition(t *testing.T, from conditions.Getter, condition *clusterv1.Condition) { 819 t.Helper() 820 821 g := NewWithT(t) 822 g.Expect(conditions.Has(from, condition.Type)).To(BeTrue()) 823 824 if condition.Status == corev1.ConditionTrue { 825 conditions.IsTrue(from, condition.Type) 826 } else { 827 conditionToBeAsserted := conditions.Get(from, condition.Type) 828 g.Expect(conditionToBeAsserted.Status).To(Equal(condition.Status)) 829 g.Expect(conditionToBeAsserted.Severity).To(Equal(condition.Severity)) 830 g.Expect(conditionToBeAsserted.Reason).To(Equal(condition.Reason)) 831 if condition.Message != "" { 832 g.Expect(conditionToBeAsserted.Message).To(Equal(condition.Message)) 833 } 834 } 835 } 836 837 func Test_computeNewMachineSetName(t *testing.T) { 838 tests := []struct { 839 base string 840 wantPrefix string 841 }{ 842 { 843 "a", 844 "a", 845 }, 846 { 847 fmt.Sprintf("%058d", 0), 848 fmt.Sprintf("%058d", 0), 849 }, 850 { 851 fmt.Sprintf("%059d", 0), 852 fmt.Sprintf("%058d", 0), 853 }, 854 { 855 fmt.Sprintf("%0100d", 0), 856 fmt.Sprintf("%058d", 0), 857 }, 858 } 859 for _, tt := range tests { 860 t.Run(fmt.Sprintf("base=%q, wantPrefix=%q", tt.base, tt.wantPrefix), func(t *testing.T) { 861 got, gotSuffix := computeNewMachineSetName(tt.base) 862 gotPrefix := strings.TrimSuffix(got, gotSuffix) 863 if gotPrefix != tt.wantPrefix { 864 t.Errorf("computeNewMachineSetName() = (%v, %v) wantPrefix %v", got, gotSuffix, tt.wantPrefix) 865 } 866 if len(got) > maxNameLength { 867 t.Errorf("expected %s to be of max length %d", got, maxNameLength) 868 } 869 }) 870 } 871 }