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  }