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