sigs.k8s.io/cluster-api@v1.7.1/internal/controllers/machinedeployment/machinedeployment_controller_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 "testing" 22 "time" 23 24 . "github.com/onsi/gomega" 25 corev1 "k8s.io/api/core/v1" 26 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 27 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 28 "k8s.io/client-go/tools/record" 29 "k8s.io/client-go/util/retry" 30 "k8s.io/utils/ptr" 31 "sigs.k8s.io/controller-runtime/pkg/client" 32 "sigs.k8s.io/controller-runtime/pkg/client/fake" 33 "sigs.k8s.io/controller-runtime/pkg/reconcile" 34 35 clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" 36 "sigs.k8s.io/cluster-api/controllers/external" 37 "sigs.k8s.io/cluster-api/internal/util/ssa" 38 "sigs.k8s.io/cluster-api/util" 39 "sigs.k8s.io/cluster-api/util/conditions" 40 "sigs.k8s.io/cluster-api/util/patch" 41 ) 42 43 const ( 44 machineDeploymentNamespace = "md-test" 45 ) 46 47 var _ reconcile.Reconciler = &Reconciler{} 48 49 func TestMachineDeploymentReconciler(t *testing.T) { 50 setup := func(t *testing.T, g *WithT) (*corev1.Namespace, *clusterv1.Cluster) { 51 t.Helper() 52 53 t.Log("Creating the namespace") 54 ns, err := env.CreateNamespace(ctx, machineDeploymentNamespace) 55 g.Expect(err).ToNot(HaveOccurred()) 56 57 t.Log("Creating the Cluster") 58 cluster := &clusterv1.Cluster{ObjectMeta: metav1.ObjectMeta{Namespace: ns.Name, Name: "test-cluster"}} 59 g.Expect(env.Create(ctx, cluster)).To(Succeed()) 60 61 t.Log("Creating the Cluster Kubeconfig Secret") 62 g.Expect(env.CreateKubeconfigSecret(ctx, cluster)).To(Succeed()) 63 64 return ns, cluster 65 } 66 67 teardown := func(t *testing.T, g *WithT, ns *corev1.Namespace, cluster *clusterv1.Cluster) { 68 t.Helper() 69 70 t.Log("Deleting the Cluster") 71 g.Expect(env.Delete(ctx, cluster)).To(Succeed()) 72 t.Log("Deleting the namespace") 73 g.Expect(env.Delete(ctx, ns)).To(Succeed()) 74 } 75 76 t.Run("Should reconcile a MachineDeployment", func(t *testing.T) { 77 g := NewWithT(t) 78 namespace, testCluster := setup(t, g) 79 defer teardown(t, g, namespace, testCluster) 80 81 labels := map[string]string{ 82 "foo": "bar", 83 clusterv1.ClusterNameLabel: testCluster.Name, 84 } 85 version := "v1.10.3" 86 deployment := &clusterv1.MachineDeployment{ 87 ObjectMeta: metav1.ObjectMeta{ 88 GenerateName: "md-", 89 Namespace: namespace.Name, 90 Labels: map[string]string{ 91 clusterv1.ClusterNameLabel: testCluster.Name, 92 }, 93 }, 94 Spec: clusterv1.MachineDeploymentSpec{ 95 ClusterName: testCluster.Name, 96 MinReadySeconds: ptr.To[int32](0), 97 Replicas: ptr.To[int32](2), 98 RevisionHistoryLimit: ptr.To[int32](0), 99 Selector: metav1.LabelSelector{ 100 // We're using the same labels for spec.selector and spec.template.labels. 101 // The labels are later changed and we will use the initial labels later to 102 // verify that all original MachineSets have been deleted. 103 MatchLabels: labels, 104 }, 105 Strategy: &clusterv1.MachineDeploymentStrategy{ 106 Type: clusterv1.RollingUpdateMachineDeploymentStrategyType, 107 RollingUpdate: &clusterv1.MachineRollingUpdateDeployment{ 108 MaxUnavailable: intOrStrPtr(0), 109 MaxSurge: intOrStrPtr(1), 110 DeletePolicy: ptr.To("Oldest"), 111 }, 112 }, 113 Template: clusterv1.MachineTemplateSpec{ 114 ObjectMeta: clusterv1.ObjectMeta{ 115 Labels: labels, 116 }, 117 Spec: clusterv1.MachineSpec{ 118 ClusterName: testCluster.Name, 119 Version: &version, 120 InfrastructureRef: corev1.ObjectReference{ 121 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 122 Kind: "GenericInfrastructureMachineTemplate", 123 Name: "md-template", 124 }, 125 Bootstrap: clusterv1.Bootstrap{ 126 DataSecretName: ptr.To("data-secret-name"), 127 }, 128 }, 129 }, 130 }, 131 } 132 msListOpts := []client.ListOption{ 133 client.InNamespace(namespace.Name), 134 client.MatchingLabels(labels), 135 } 136 137 // Create infrastructure template resource. 138 infraResource := map[string]interface{}{ 139 "kind": "GenericInfrastructureMachine", 140 "apiVersion": "infrastructure.cluster.x-k8s.io/v1beta1", 141 "metadata": map[string]interface{}{}, 142 "spec": map[string]interface{}{ 143 "size": "3xlarge", 144 }, 145 } 146 infraTmpl := &unstructured.Unstructured{ 147 Object: map[string]interface{}{ 148 "kind": "GenericInfrastructureMachineTemplate", 149 "apiVersion": "infrastructure.cluster.x-k8s.io/v1beta1", 150 "metadata": map[string]interface{}{ 151 "name": "md-template", 152 "namespace": namespace.Name, 153 }, 154 "spec": map[string]interface{}{ 155 "template": infraResource, 156 }, 157 }, 158 } 159 t.Log("Creating the infrastructure template") 160 g.Expect(env.Create(ctx, infraTmpl)).To(Succeed()) 161 162 // Create the MachineDeployment object and expect Reconcile to be called. 163 t.Log("Creating the MachineDeployment") 164 g.Expect(env.Create(ctx, deployment)).To(Succeed()) 165 defer func() { 166 t.Log("Deleting the MachineDeployment") 167 g.Expect(env.Delete(ctx, deployment)).To(Succeed()) 168 }() 169 170 t.Log("Verifying the MachineDeployment has a cluster label and ownerRef") 171 g.Eventually(func() bool { 172 key := client.ObjectKey{Name: deployment.Name, Namespace: deployment.Namespace} 173 if err := env.Get(ctx, key, deployment); err != nil { 174 return false 175 } 176 if len(deployment.Labels) == 0 || deployment.Labels[clusterv1.ClusterNameLabel] != testCluster.Name { 177 return false 178 } 179 if len(deployment.OwnerReferences) == 0 || deployment.OwnerReferences[0].Name != testCluster.Name { 180 return false 181 } 182 return true 183 }, timeout).Should(BeTrue()) 184 185 // Verify that the MachineSet was created. 186 t.Log("Verifying the MachineSet was created") 187 machineSets := &clusterv1.MachineSetList{} 188 g.Eventually(func() int { 189 if err := env.List(ctx, machineSets, msListOpts...); err != nil { 190 return -1 191 } 192 return len(machineSets.Items) 193 }, timeout).Should(BeEquivalentTo(1)) 194 195 t.Log("Verifying that the deployment's deletePolicy was propagated to the machineset") 196 g.Expect(machineSets.Items[0].Spec.DeletePolicy).To(Equal("Oldest")) 197 198 t.Log("Verifying the linked infrastructure template has a cluster owner reference") 199 g.Eventually(func() bool { 200 obj, err := external.Get(ctx, env, &deployment.Spec.Template.Spec.InfrastructureRef, deployment.Namespace) 201 if err != nil { 202 return false 203 } 204 205 return util.HasOwnerRef(obj.GetOwnerReferences(), metav1.OwnerReference{ 206 APIVersion: clusterv1.GroupVersion.String(), 207 Kind: "Cluster", 208 Name: testCluster.Name, 209 UID: testCluster.UID, 210 }) 211 }, timeout).Should(BeTrue()) 212 213 t.Log("Verify MachineSet has expected replicas and version") 214 firstMachineSet := machineSets.Items[0] 215 g.Expect(*firstMachineSet.Spec.Replicas).To(BeEquivalentTo(2)) 216 g.Expect(*firstMachineSet.Spec.Template.Spec.Version).To(BeEquivalentTo("v1.10.3")) 217 218 t.Log("Verify MachineSet has expected ClusterNameLabel and MachineDeploymentNameLabel") 219 g.Expect(firstMachineSet.Labels[clusterv1.ClusterNameLabel]).To(Equal(testCluster.Name)) 220 g.Expect(firstMachineSet.Labels[clusterv1.MachineDeploymentNameLabel]).To(Equal(deployment.Name)) 221 222 t.Log("Verify expected number of Machines are created") 223 machines := &clusterv1.MachineList{} 224 g.Eventually(func() int { 225 if err := env.List(ctx, machines, client.InNamespace(namespace.Name)); err != nil { 226 return -1 227 } 228 return len(machines.Items) 229 }, timeout).Should(BeEquivalentTo(*deployment.Spec.Replicas)) 230 231 t.Log("Verify Machines have expected ClusterNameLabel, MachineDeploymentNameLabel and MachineSetNameLabel") 232 for _, m := range machines.Items { 233 g.Expect(m.Labels[clusterv1.ClusterNameLabel]).To(Equal(testCluster.Name)) 234 g.Expect(m.Labels[clusterv1.MachineDeploymentNameLabel]).To(Equal(deployment.Name)) 235 g.Expect(m.Labels[clusterv1.MachineSetNameLabel]).To(Equal(firstMachineSet.Name)) 236 } 237 238 // 239 // Delete firstMachineSet and expect Reconcile to be called to replace it. 240 // 241 t.Log("Deleting the initial MachineSet") 242 g.Expect(env.Delete(ctx, &firstMachineSet)).To(Succeed()) 243 g.Eventually(func() bool { 244 if err := env.List(ctx, machineSets, msListOpts...); err != nil { 245 return false 246 } 247 for _, ms := range machineSets.Items { 248 if ms.UID == firstMachineSet.UID { 249 return false 250 } 251 } 252 return len(machineSets.Items) > 0 253 }, timeout).Should(BeTrue()) 254 255 // 256 // Scale the MachineDeployment and expect Reconcile to be called. 257 // 258 secondMachineSet := machineSets.Items[0] 259 t.Log("Scaling the MachineDeployment to 3 replicas") 260 desiredMachineDeploymentReplicas := int32(3) 261 modifyFunc := func(d *clusterv1.MachineDeployment) { 262 d.Spec.Replicas = ptr.To[int32](desiredMachineDeploymentReplicas) 263 } 264 g.Expect(updateMachineDeployment(ctx, env, deployment, modifyFunc)).To(Succeed()) 265 g.Eventually(func() int { 266 key := client.ObjectKey{Name: secondMachineSet.Name, Namespace: secondMachineSet.Namespace} 267 if err := env.Get(ctx, key, &secondMachineSet); err != nil { 268 return -1 269 } 270 return int(*secondMachineSet.Spec.Replicas) 271 }, timeout).Should(BeEquivalentTo(desiredMachineDeploymentReplicas)) 272 273 // 274 // Update the InfraStructureRef of the MachineDeployment, expect Reconcile to be called and a new MachineSet to appear. 275 // 276 277 t.Log("Updating the InfrastructureRef on the MachineDeployment") 278 // Create the InfrastructureTemplate 279 // Create infrastructure template resource. 280 infraTmpl2 := &unstructured.Unstructured{ 281 Object: map[string]interface{}{ 282 "kind": "GenericInfrastructureMachineTemplate", 283 "apiVersion": "infrastructure.cluster.x-k8s.io/v1beta1", 284 "metadata": map[string]interface{}{ 285 "name": "md-template-2", 286 "namespace": namespace.Name, 287 }, 288 "spec": map[string]interface{}{ 289 "template": map[string]interface{}{ 290 "kind": "GenericInfrastructureMachine", 291 "apiVersion": "infrastructure.cluster.x-k8s.io/v1beta1", 292 "metadata": map[string]interface{}{}, 293 "spec": map[string]interface{}{ 294 "size": "5xlarge", 295 }, 296 }, 297 }, 298 }, 299 } 300 t.Log("Creating the infrastructure template") 301 g.Expect(env.Create(ctx, infraTmpl2)).To(Succeed()) 302 303 infraTmpl2Ref := corev1.ObjectReference{ 304 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 305 Kind: "GenericInfrastructureMachineTemplate", 306 Name: "md-template-2", 307 } 308 modifyFunc = func(d *clusterv1.MachineDeployment) { d.Spec.Template.Spec.InfrastructureRef = infraTmpl2Ref } 309 g.Expect(updateMachineDeployment(ctx, env, deployment, modifyFunc)).To(Succeed()) 310 g.Eventually(func() int { 311 if err := env.List(ctx, machineSets, msListOpts...); err != nil { 312 return -1 313 } 314 return len(machineSets.Items) 315 }, timeout).Should(BeEquivalentTo(2)) 316 317 // Update the Labels of the MachineDeployment, expect Reconcile to be called and the MachineSet to be updated in-place. 318 t.Log("Setting a label on the MachineDeployment") 319 modifyFunc = func(d *clusterv1.MachineDeployment) { d.Spec.Template.Labels["updated"] = "true" } 320 g.Expect(updateMachineDeployment(ctx, env, deployment, modifyFunc)).To(Succeed()) 321 g.Eventually(func(g Gomega) { 322 g.Expect(env.List(ctx, machineSets, msListOpts...)).To(Succeed()) 323 // Verify we still only have 2 MachineSets. 324 g.Expect(machineSets.Items).To(HaveLen(2)) 325 // Verify that the new MachineSet gets the updated labels. 326 g.Expect(machineSets.Items[0].Spec.Template.Labels).To(HaveKeyWithValue("updated", "true")) 327 // Verify that the old MachineSet does not get the updated labels. 328 g.Expect(machineSets.Items[1].Spec.Template.Labels).ShouldNot(HaveKeyWithValue("updated", "true")) 329 }, timeout).Should(Succeed()) 330 331 // Update the NodeDrainTimout, NodeDeletionTimeout, NodeVolumeDetachTimeout of the MachineDeployment, 332 // expect the Reconcile to be called and the MachineSet to be updated in-place. 333 t.Log("Setting NodeDrainTimout, NodeDeletionTimeout, NodeVolumeDetachTimeout on the MachineDeployment") 334 duration10s := metav1.Duration{Duration: 10 * time.Second} 335 modifyFunc = func(d *clusterv1.MachineDeployment) { 336 d.Spec.Template.Spec.NodeDrainTimeout = &duration10s 337 d.Spec.Template.Spec.NodeDeletionTimeout = &duration10s 338 d.Spec.Template.Spec.NodeVolumeDetachTimeout = &duration10s 339 } 340 g.Expect(updateMachineDeployment(ctx, env, deployment, modifyFunc)).To(Succeed()) 341 g.Eventually(func(g Gomega) { 342 g.Expect(env.List(ctx, machineSets, msListOpts...)).Should(Succeed()) 343 // Verify we still only have 2 MachineSets. 344 g.Expect(machineSets.Items).To(HaveLen(2)) 345 // Verify the NodeDrainTimeout value is updated 346 g.Expect(machineSets.Items[0].Spec.Template.Spec.NodeDrainTimeout).Should(And( 347 Not(BeNil()), 348 HaveValue(Equal(duration10s)), 349 ), "NodeDrainTimout value does not match expected") 350 // Verify the NodeDeletionTimeout value is updated 351 g.Expect(machineSets.Items[0].Spec.Template.Spec.NodeDeletionTimeout).Should(And( 352 Not(BeNil()), 353 HaveValue(Equal(duration10s)), 354 ), "NodeDeletionTimeout value does not match expected") 355 // Verify the NodeVolumeDetachTimeout value is updated 356 g.Expect(machineSets.Items[0].Spec.Template.Spec.NodeVolumeDetachTimeout).Should(And( 357 Not(BeNil()), 358 HaveValue(Equal(duration10s)), 359 ), "NodeVolumeDetachTimeout value does not match expected") 360 361 // Verify that the old machine set keeps the old values. 362 g.Expect(machineSets.Items[1].Spec.Template.Spec.NodeDrainTimeout).Should(BeNil()) 363 g.Expect(machineSets.Items[1].Spec.Template.Spec.NodeDeletionTimeout).Should(BeNil()) 364 g.Expect(machineSets.Items[1].Spec.Template.Spec.NodeVolumeDetachTimeout).Should(BeNil()) 365 }).Should(Succeed()) 366 367 // Update the DeletePolicy of the MachineDeployment, 368 // expect the Reconcile to be called and the MachineSet to be updated in-place. 369 t.Log("Updating deletePolicy on the MachineDeployment") 370 modifyFunc = func(d *clusterv1.MachineDeployment) { 371 d.Spec.Strategy.RollingUpdate.DeletePolicy = ptr.To("Newest") 372 } 373 g.Expect(updateMachineDeployment(ctx, env, deployment, modifyFunc)).To(Succeed()) 374 g.Eventually(func(g Gomega) { 375 g.Expect(env.List(ctx, machineSets, msListOpts...)).Should(Succeed()) 376 // Verify we still only have 2 MachineSets. 377 g.Expect(machineSets.Items).To(HaveLen(2)) 378 // Verify the DeletePolicy value is updated 379 g.Expect(machineSets.Items[0].Spec.DeletePolicy).Should(Equal("Newest")) 380 381 // Verify that the old machine set retains its delete policy 382 g.Expect(machineSets.Items[1].Spec.DeletePolicy).To(Equal("Oldest")) 383 }).Should(Succeed()) 384 385 // Verify that all the MachineSets have the expected OwnerRef. 386 t.Log("Verifying MachineSet owner references") 387 g.Eventually(func() bool { 388 if err := env.List(ctx, machineSets, msListOpts...); err != nil { 389 return false 390 } 391 for i := 0; i < len(machineSets.Items); i++ { 392 ms := machineSets.Items[0] 393 if !metav1.IsControlledBy(&ms, deployment) || metav1.GetControllerOf(&ms).Kind != "MachineDeployment" { 394 return false 395 } 396 } 397 return true 398 }, timeout).Should(BeTrue()) 399 400 t.Log("Locating the newest MachineSet") 401 var newestMachineSet *clusterv1.MachineSet 402 for i := range machineSets.Items { 403 ms := &machineSets.Items[i] 404 if ms.UID != secondMachineSet.UID { 405 newestMachineSet = ms 406 break 407 } 408 } 409 g.Expect(newestMachineSet).NotTo(BeNil()) 410 411 t.Log("Verifying the initial MachineSet is deleted") 412 g.Eventually(func() int { 413 // Set the all non-deleted machines as ready with a NodeRef, so the MachineSet controller can proceed 414 // to properly set AvailableReplicas. 415 foundMachines := &clusterv1.MachineList{} 416 g.Expect(env.List(ctx, foundMachines, client.InNamespace(namespace.Name))).To(Succeed()) 417 for i := 0; i < len(foundMachines.Items); i++ { 418 m := foundMachines.Items[i] 419 // Skip over deleted Machines 420 if !m.DeletionTimestamp.IsZero() { 421 continue 422 } 423 // Skip over Machines controlled by other (previous) MachineSets 424 if !metav1.IsControlledBy(&m, newestMachineSet) { 425 continue 426 } 427 providerID := fakeInfrastructureRefReady(m.Spec.InfrastructureRef, infraResource, g) 428 fakeMachineNodeRef(&m, providerID, g) 429 } 430 431 if err := env.List(ctx, machineSets, msListOpts...); err != nil { 432 return -1 433 } 434 return len(machineSets.Items) 435 }, timeout*3).Should(BeEquivalentTo(1)) 436 437 t.Log("Verifying new MachineSet has desired number of replicas") 438 g.Eventually(func() bool { 439 g.Expect(env.List(ctx, machineSets, msListOpts...)).Should(Succeed()) 440 newms := machineSets.Items[0] 441 // Set the all non-deleted machines as ready with a NodeRef, so the MachineSet controller can proceed 442 // to properly set AvailableReplicas. 443 foundMachines := &clusterv1.MachineList{} 444 g.Expect(env.List(ctx, foundMachines, client.InNamespace(namespace.Name))).To(Succeed()) 445 for i := 0; i < len(foundMachines.Items); i++ { 446 m := foundMachines.Items[i] 447 if !m.DeletionTimestamp.IsZero() { 448 continue 449 } 450 // Skip over Machines controlled by other (previous) MachineSets 451 if !metav1.IsControlledBy(&m, &newms) { 452 continue 453 } 454 providerID := fakeInfrastructureRefReady(m.Spec.InfrastructureRef, infraResource, g) 455 fakeMachineNodeRef(&m, providerID, g) 456 } 457 458 return newms.Status.Replicas == desiredMachineDeploymentReplicas 459 }, timeout*5).Should(BeTrue()) 460 461 t.Log("Verifying MachineDeployment has correct Conditions") 462 g.Eventually(func() bool { 463 key := client.ObjectKey{Name: deployment.Name, Namespace: deployment.Namespace} 464 g.Expect(env.Get(ctx, key, deployment)).To(Succeed()) 465 return conditions.IsTrue(deployment, clusterv1.MachineDeploymentAvailableCondition) 466 }, timeout).Should(BeTrue()) 467 468 // Validate that the controller set the cluster name label in selector. 469 g.Expect(deployment.Status.Selector).To(ContainSubstring(testCluster.Name)) 470 }) 471 } 472 473 func TestMachineDeploymentReconciler_CleanUpManagedFieldsForSSAAdoption(t *testing.T) { 474 setup := func(t *testing.T, g *WithT) (*corev1.Namespace, *clusterv1.Cluster) { 475 t.Helper() 476 477 t.Log("Creating the namespace") 478 ns, err := env.CreateNamespace(ctx, machineDeploymentNamespace) 479 g.Expect(err).ToNot(HaveOccurred()) 480 481 t.Log("Creating the Cluster") 482 cluster := &clusterv1.Cluster{ObjectMeta: metav1.ObjectMeta{Namespace: ns.Name, Name: "test-cluster"}} 483 g.Expect(env.Create(ctx, cluster)).To(Succeed()) 484 485 t.Log("Creating the Cluster Kubeconfig Secret") 486 g.Expect(env.CreateKubeconfigSecret(ctx, cluster)).To(Succeed()) 487 488 return ns, cluster 489 } 490 491 teardown := func(t *testing.T, g *WithT, ns *corev1.Namespace, cluster *clusterv1.Cluster) { 492 t.Helper() 493 494 t.Log("Deleting the Cluster") 495 g.Expect(env.Delete(ctx, cluster)).To(Succeed()) 496 t.Log("Deleting the namespace") 497 g.Expect(env.Delete(ctx, ns)).To(Succeed()) 498 } 499 500 g := NewWithT(t) 501 namespace, testCluster := setup(t, g) 502 defer teardown(t, g, namespace, testCluster) 503 504 labels := map[string]string{ 505 "foo": "bar", 506 clusterv1.ClusterNameLabel: testCluster.Name, 507 } 508 version := "v1.10.3" 509 deployment := &clusterv1.MachineDeployment{ 510 ObjectMeta: metav1.ObjectMeta{ 511 GenerateName: "md-", 512 Namespace: namespace.Name, 513 Labels: map[string]string{ 514 clusterv1.ClusterNameLabel: testCluster.Name, 515 }, 516 }, 517 Spec: clusterv1.MachineDeploymentSpec{ 518 Paused: true, // Set this to true as we do not want to test the other parts of the reconciler in this test. 519 ClusterName: testCluster.Name, 520 MinReadySeconds: ptr.To[int32](0), 521 Replicas: ptr.To[int32](2), 522 RevisionHistoryLimit: ptr.To[int32](0), 523 Selector: metav1.LabelSelector{ 524 // We're using the same labels for spec.selector and spec.template.labels. 525 MatchLabels: labels, 526 }, 527 Strategy: &clusterv1.MachineDeploymentStrategy{ 528 Type: clusterv1.RollingUpdateMachineDeploymentStrategyType, 529 RollingUpdate: &clusterv1.MachineRollingUpdateDeployment{ 530 MaxUnavailable: intOrStrPtr(0), 531 MaxSurge: intOrStrPtr(1), 532 DeletePolicy: ptr.To("Oldest"), 533 }, 534 }, 535 Template: clusterv1.MachineTemplateSpec{ 536 ObjectMeta: clusterv1.ObjectMeta{ 537 Labels: labels, 538 }, 539 Spec: clusterv1.MachineSpec{ 540 ClusterName: testCluster.Name, 541 Version: &version, 542 InfrastructureRef: corev1.ObjectReference{ 543 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 544 Kind: "GenericInfrastructureMachineTemplate", 545 Name: "md-template", 546 }, 547 Bootstrap: clusterv1.Bootstrap{ 548 DataSecretName: ptr.To("data-secret-name"), 549 }, 550 }, 551 }, 552 }, 553 } 554 msListOpts := []client.ListOption{ 555 client.InNamespace(namespace.Name), 556 client.MatchingLabels(labels), 557 } 558 559 // Create infrastructure template resource. 560 infraResource := map[string]interface{}{ 561 "kind": "GenericInfrastructureMachine", 562 "apiVersion": "infrastructure.cluster.x-k8s.io/v1beta1", 563 "metadata": map[string]interface{}{}, 564 "spec": map[string]interface{}{ 565 "size": "3xlarge", 566 }, 567 } 568 infraTmpl := &unstructured.Unstructured{ 569 Object: map[string]interface{}{ 570 "kind": "GenericInfrastructureMachineTemplate", 571 "apiVersion": "infrastructure.cluster.x-k8s.io/v1beta1", 572 "metadata": map[string]interface{}{ 573 "name": "md-template", 574 "namespace": namespace.Name, 575 }, 576 "spec": map[string]interface{}{ 577 "template": infraResource, 578 }, 579 }, 580 } 581 t.Log("Creating the infrastructure template") 582 g.Expect(env.Create(ctx, infraTmpl)).To(Succeed()) 583 584 // Create the MachineDeployment object and expect Reconcile to be called. 585 t.Log("Creating the MachineDeployment") 586 g.Expect(env.Create(ctx, deployment)).To(Succeed()) 587 588 // Create a MachineSet for the MachineDeployment. 589 classicManagerMS := &clusterv1.MachineSet{ 590 TypeMeta: metav1.TypeMeta{ 591 Kind: "MachineSet", 592 APIVersion: clusterv1.GroupVersion.String(), 593 }, 594 ObjectMeta: metav1.ObjectMeta{ 595 Name: deployment.Name + "-" + "classic-ms", 596 Namespace: testCluster.Namespace, 597 Labels: labels, 598 }, 599 Spec: clusterv1.MachineSetSpec{ 600 ClusterName: testCluster.Name, 601 Replicas: ptr.To[int32](0), 602 MinReadySeconds: 0, 603 Selector: metav1.LabelSelector{ 604 MatchLabels: labels, 605 }, 606 Template: clusterv1.MachineTemplateSpec{ 607 ObjectMeta: clusterv1.ObjectMeta{ 608 Labels: labels, 609 }, 610 Spec: clusterv1.MachineSpec{ 611 ClusterName: testCluster.Name, 612 InfrastructureRef: corev1.ObjectReference{ 613 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 614 Kind: "GenericInfrastructureMachineTemplate", 615 Name: "md-template", 616 }, 617 Bootstrap: clusterv1.Bootstrap{ 618 DataSecretName: ptr.To("data-secret-name"), 619 }, 620 Version: &version, 621 }, 622 }, 623 }, 624 } 625 ssaManagerMS := classicManagerMS.DeepCopy() 626 ssaManagerMS.Name = deployment.Name + "-" + "ssa-ms" 627 628 // Create one using the "old manager". 629 g.Expect(env.Create(ctx, classicManagerMS, client.FieldOwner("manager"))).To(Succeed()) 630 631 // Create one using SSA. 632 g.Expect(env.Patch(ctx, ssaManagerMS, client.Apply, client.FieldOwner(machineDeploymentManagerName), client.ForceOwnership)).To(Succeed()) 633 634 // Verify that for both the MachineSets the ManagedFields are updated. 635 g.Eventually(func(g Gomega) { 636 machineSets := &clusterv1.MachineSetList{} 637 g.Expect(env.List(ctx, machineSets, msListOpts...)).To(Succeed()) 638 639 g.Expect(machineSets.Items).To(HaveLen(2)) 640 for _, ms := range machineSets.Items { 641 // Verify the ManagedFields are updated. 642 g.Expect(ms.GetManagedFields()).Should( 643 ContainElement(ssa.MatchManagedFieldsEntry(machineDeploymentManagerName, metav1.ManagedFieldsOperationApply))) 644 g.Expect(ms.GetManagedFields()).ShouldNot( 645 ContainElement(ssa.MatchManagedFieldsEntry("manager", metav1.ManagedFieldsOperationUpdate))) 646 } 647 }).Should(Succeed()) 648 } 649 650 func TestMachineSetToDeployments(t *testing.T) { 651 g := NewWithT(t) 652 653 machineDeployment := &clusterv1.MachineDeployment{ 654 ObjectMeta: metav1.ObjectMeta{ 655 Name: "withMatchingLabels", 656 Namespace: metav1.NamespaceDefault, 657 }, 658 Spec: clusterv1.MachineDeploymentSpec{ 659 Selector: metav1.LabelSelector{ 660 MatchLabels: map[string]string{ 661 "foo": "bar", 662 clusterv1.ClusterNameLabel: "test-cluster", 663 }, 664 }, 665 }, 666 } 667 668 machineDeplopymentList := []client.Object{machineDeployment} 669 670 ms1 := clusterv1.MachineSet{ 671 TypeMeta: metav1.TypeMeta{ 672 Kind: "MachineSet", 673 }, 674 ObjectMeta: metav1.ObjectMeta{ 675 Name: "withOwnerRef", 676 Namespace: metav1.NamespaceDefault, 677 OwnerReferences: []metav1.OwnerReference{ 678 *metav1.NewControllerRef(machineDeployment, machineDeploymentKind), 679 }, 680 Labels: map[string]string{ 681 clusterv1.ClusterNameLabel: "test-cluster", 682 }, 683 }, 684 } 685 ms2 := clusterv1.MachineSet{ 686 TypeMeta: metav1.TypeMeta{ 687 Kind: "MachineSet", 688 }, 689 ObjectMeta: metav1.ObjectMeta{ 690 Name: "noOwnerRefNoLabels", 691 Namespace: metav1.NamespaceDefault, 692 Labels: map[string]string{ 693 clusterv1.ClusterNameLabel: "test-cluster", 694 }, 695 }, 696 } 697 ms3 := clusterv1.MachineSet{ 698 TypeMeta: metav1.TypeMeta{ 699 Kind: "MachineSet", 700 }, 701 ObjectMeta: metav1.ObjectMeta{ 702 Name: "withMatchingLabels", 703 Namespace: metav1.NamespaceDefault, 704 Labels: map[string]string{ 705 "foo": "bar", 706 clusterv1.ClusterNameLabel: "test-cluster", 707 }, 708 }, 709 } 710 711 testsCases := []struct { 712 machineSet clusterv1.MachineSet 713 mapObject client.Object 714 expected []reconcile.Request 715 }{ 716 { 717 machineSet: ms1, 718 mapObject: &ms1, 719 expected: []reconcile.Request{}, 720 }, 721 { 722 machineSet: ms2, 723 mapObject: &ms2, 724 expected: nil, 725 }, 726 { 727 machineSet: ms3, 728 mapObject: &ms3, 729 expected: []reconcile.Request{ 730 {NamespacedName: client.ObjectKey{Namespace: metav1.NamespaceDefault, Name: "withMatchingLabels"}}, 731 }, 732 }, 733 } 734 735 r := &Reconciler{ 736 Client: fake.NewClientBuilder().WithObjects(machineDeplopymentList...).Build(), 737 recorder: record.NewFakeRecorder(32), 738 } 739 740 for _, tc := range testsCases { 741 got := r.MachineSetToDeployments(ctx, tc.mapObject) 742 g.Expect(got).To(BeComparableTo(tc.expected)) 743 } 744 } 745 746 func TestGetMachineDeploymentsForMachineSet(t *testing.T) { 747 g := NewWithT(t) 748 749 machineDeployment := &clusterv1.MachineDeployment{ 750 ObjectMeta: metav1.ObjectMeta{ 751 Name: "withLabels", 752 Namespace: metav1.NamespaceDefault, 753 }, 754 Spec: clusterv1.MachineDeploymentSpec{ 755 Selector: metav1.LabelSelector{ 756 MatchLabels: map[string]string{ 757 "foo": "bar", 758 }, 759 }, 760 }, 761 } 762 machineDeploymentList := []client.Object{machineDeployment} 763 764 ms1 := clusterv1.MachineSet{ 765 TypeMeta: metav1.TypeMeta{ 766 Kind: "MachineSet", 767 }, 768 ObjectMeta: metav1.ObjectMeta{ 769 Name: "NoMatchingLabels", 770 Namespace: metav1.NamespaceDefault, 771 }, 772 } 773 ms2 := clusterv1.MachineSet{ 774 TypeMeta: metav1.TypeMeta{ 775 Kind: "MachineSet", 776 }, 777 ObjectMeta: metav1.ObjectMeta{ 778 Name: "withMatchingLabels", 779 Namespace: metav1.NamespaceDefault, 780 Labels: map[string]string{ 781 "foo": "bar", 782 }, 783 }, 784 } 785 786 testCases := []struct { 787 machineSet clusterv1.MachineSet 788 expected []client.Object 789 }{ 790 { 791 machineSet: ms1, 792 expected: nil, 793 }, 794 { 795 machineSet: ms2, 796 expected: []client.Object{machineDeployment}, 797 }, 798 } 799 800 r := &Reconciler{ 801 Client: fake.NewClientBuilder().WithObjects(append(machineDeploymentList, &ms1, &ms2)...).Build(), 802 recorder: record.NewFakeRecorder(32), 803 } 804 805 for i := range testCases { 806 tc := testCases[i] 807 var got []client.Object 808 for _, x := range r.getMachineDeploymentsForMachineSet(ctx, &tc.machineSet) { 809 got = append(got, x) 810 } 811 g.Expect(got).To(BeComparableTo(tc.expected)) 812 } 813 } 814 815 func TestGetMachineSetsForDeployment(t *testing.T) { 816 machineDeployment1 := clusterv1.MachineDeployment{ 817 ObjectMeta: metav1.ObjectMeta{ 818 Name: "withMatchingOwnerRefAndLabels", 819 Namespace: metav1.NamespaceDefault, 820 UID: "UID", 821 }, 822 Spec: clusterv1.MachineDeploymentSpec{ 823 Selector: metav1.LabelSelector{ 824 MatchLabels: map[string]string{ 825 "foo": "bar", 826 }, 827 }, 828 }, 829 } 830 machineDeployment2 := clusterv1.MachineDeployment{ 831 ObjectMeta: metav1.ObjectMeta{ 832 Name: "withNoMatchingOwnerRef", 833 Namespace: metav1.NamespaceDefault, 834 UID: "unMatchingUID", 835 }, 836 Spec: clusterv1.MachineDeploymentSpec{ 837 Selector: metav1.LabelSelector{ 838 MatchLabels: map[string]string{ 839 "foo": "bar2", 840 }, 841 }, 842 }, 843 } 844 machineDeployment3 := clusterv1.MachineDeployment{ 845 ObjectMeta: metav1.ObjectMeta{ 846 Name: "withMatchingOwnerRefAndNoMatchingLabels", 847 Namespace: metav1.NamespaceDefault, 848 UID: "UID3", 849 }, 850 Spec: clusterv1.MachineDeploymentSpec{ 851 Selector: metav1.LabelSelector{ 852 MatchLabels: map[string]string{ 853 "foo": "bar", 854 }, 855 }, 856 }, 857 } 858 859 ms1 := clusterv1.MachineSet{ 860 TypeMeta: metav1.TypeMeta{ 861 Kind: "MachineSet", 862 }, 863 ObjectMeta: metav1.ObjectMeta{ 864 Name: "withNoOwnerRefShouldBeAdopted2", 865 Namespace: metav1.NamespaceDefault, 866 Labels: map[string]string{ 867 "foo": "bar2", 868 }, 869 }, 870 } 871 ms2 := clusterv1.MachineSet{ 872 TypeMeta: metav1.TypeMeta{ 873 Kind: "MachineSet", 874 }, 875 ObjectMeta: metav1.ObjectMeta{ 876 Name: "withOwnerRefAndLabels", 877 Namespace: metav1.NamespaceDefault, 878 OwnerReferences: []metav1.OwnerReference{ 879 *metav1.NewControllerRef(&machineDeployment1, machineDeploymentKind), 880 }, 881 Labels: map[string]string{ 882 "foo": "bar", 883 }, 884 }, 885 } 886 ms3 := clusterv1.MachineSet{ 887 TypeMeta: metav1.TypeMeta{ 888 Kind: "MachineSet", 889 }, 890 ObjectMeta: metav1.ObjectMeta{ 891 Name: "withNoOwnerRefShouldBeAdopted1", 892 Namespace: metav1.NamespaceDefault, 893 Labels: map[string]string{ 894 "foo": "bar", 895 }, 896 }, 897 } 898 ms4 := clusterv1.MachineSet{ 899 TypeMeta: metav1.TypeMeta{ 900 Kind: "MachineSet", 901 }, 902 ObjectMeta: metav1.ObjectMeta{ 903 Name: "withNoOwnerRefNoMatch", 904 Namespace: metav1.NamespaceDefault, 905 Labels: map[string]string{ 906 "foo": "nomatch", 907 }, 908 }, 909 } 910 ms5 := clusterv1.MachineSet{ 911 TypeMeta: metav1.TypeMeta{ 912 Kind: "MachineSet", 913 }, 914 ObjectMeta: metav1.ObjectMeta{ 915 Name: "withOwnerRefAndNoMatchLabels", 916 Namespace: metav1.NamespaceDefault, 917 OwnerReferences: []metav1.OwnerReference{ 918 *metav1.NewControllerRef(&machineDeployment3, machineDeploymentKind), 919 }, 920 Labels: map[string]string{ 921 "foo": "nomatch", 922 }, 923 }, 924 } 925 machineSetList := []client.Object{ 926 &ms1, 927 &ms2, 928 &ms3, 929 &ms4, 930 &ms5, 931 } 932 933 testCases := []struct { 934 name string 935 machineDeployment clusterv1.MachineDeployment 936 expected []*clusterv1.MachineSet 937 }{ 938 { 939 name: "matching ownerRef and labels", 940 machineDeployment: machineDeployment1, 941 expected: []*clusterv1.MachineSet{&ms3, &ms2}, 942 }, 943 { 944 name: "no matching ownerRef, matching labels", 945 machineDeployment: machineDeployment2, 946 expected: []*clusterv1.MachineSet{&ms1}, 947 }, 948 { 949 name: "matching ownerRef, mismatch labels", 950 machineDeployment: machineDeployment3, 951 expected: []*clusterv1.MachineSet{&ms3, &ms5}, 952 }, 953 } 954 955 for i := range testCases { 956 tc := testCases[i] 957 t.Run(tc.name, func(t *testing.T) { 958 g := NewWithT(t) 959 960 r := &Reconciler{ 961 Client: fake.NewClientBuilder().WithObjects(machineSetList...).Build(), 962 recorder: record.NewFakeRecorder(32), 963 } 964 965 got, err := r.getMachineSetsForDeployment(ctx, &tc.machineDeployment) 966 g.Expect(err).ToNot(HaveOccurred()) 967 g.Expect(got).To(HaveLen(len(tc.expected))) 968 969 for idx, res := range got { 970 g.Expect(res.Name).To(Equal(tc.expected[idx].Name)) 971 g.Expect(res.Namespace).To(Equal(tc.expected[idx].Namespace)) 972 } 973 }) 974 } 975 } 976 977 // We have this as standalone variant to be able to use it from the tests. 978 func updateMachineDeployment(ctx context.Context, c client.Client, md *clusterv1.MachineDeployment, modify func(*clusterv1.MachineDeployment)) error { 979 mdObjectKey := util.ObjectKey(md) 980 return retry.RetryOnConflict(retry.DefaultBackoff, func() error { 981 // Note: We intentionally don't re-use the passed in MachineDeployment md here as that would 982 // overwrite any local changes we might have previously made to the MachineDeployment with the version 983 // we get here from the apiserver. 984 md := &clusterv1.MachineDeployment{} 985 if err := c.Get(ctx, mdObjectKey, md); err != nil { 986 return err 987 } 988 patchHelper, err := patch.NewHelper(md, c) 989 if err != nil { 990 return err 991 } 992 modify(md) 993 return patchHelper.Patch(ctx, md) 994 }) 995 }