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