sigs.k8s.io/cluster-api@v1.7.1/internal/controllers/machineset/machineset_controller_test.go (about)

     1  /*
     2  Copyright 2018 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 machineset
    18  
    19  import (
    20  	"testing"
    21  	"time"
    22  
    23  	. "github.com/onsi/gomega"
    24  	corev1 "k8s.io/api/core/v1"
    25  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    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  	utilfeature "k8s.io/component-base/featuregate/testing"
    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/feature"
    38  	"sigs.k8s.io/cluster-api/internal/contract"
    39  	"sigs.k8s.io/cluster-api/internal/test/builder"
    40  	"sigs.k8s.io/cluster-api/internal/util/ssa"
    41  	"sigs.k8s.io/cluster-api/util"
    42  	"sigs.k8s.io/cluster-api/util/conditions"
    43  	"sigs.k8s.io/cluster-api/util/patch"
    44  )
    45  
    46  var _ reconcile.Reconciler = &Reconciler{}
    47  
    48  func TestMachineSetReconciler(t *testing.T) {
    49  	setup := func(t *testing.T, g *WithT) (*corev1.Namespace, *clusterv1.Cluster) {
    50  		t.Helper()
    51  
    52  		t.Log("Creating the namespace")
    53  		ns, err := env.CreateNamespace(ctx, "test-machine-set-reconciler")
    54  		g.Expect(err).ToNot(HaveOccurred())
    55  
    56  		t.Log("Creating the Cluster")
    57  		cluster := &clusterv1.Cluster{ObjectMeta: metav1.ObjectMeta{Namespace: ns.Name, Name: testClusterName}}
    58  		g.Expect(env.Create(ctx, cluster)).To(Succeed())
    59  
    60  		t.Log("Creating the Cluster Kubeconfig Secret")
    61  		g.Expect(env.CreateKubeconfigSecret(ctx, cluster)).To(Succeed())
    62  
    63  		return ns, cluster
    64  	}
    65  
    66  	teardown := func(t *testing.T, g *WithT, ns *corev1.Namespace, cluster *clusterv1.Cluster) {
    67  		t.Helper()
    68  
    69  		t.Log("Deleting the Cluster")
    70  		g.Expect(env.Delete(ctx, cluster)).To(Succeed())
    71  		t.Log("Deleting the namespace")
    72  		g.Expect(env.Delete(ctx, ns)).To(Succeed())
    73  	}
    74  
    75  	t.Run("Should reconcile a MachineSet", func(t *testing.T) {
    76  		g := NewWithT(t)
    77  		namespace, testCluster := setup(t, g)
    78  		defer teardown(t, g, namespace, testCluster)
    79  
    80  		duration10m := &metav1.Duration{Duration: 10 * time.Minute}
    81  		duration5m := &metav1.Duration{Duration: 5 * time.Minute}
    82  		replicas := int32(2)
    83  		version := "v1.14.2"
    84  		instance := &clusterv1.MachineSet{
    85  			ObjectMeta: metav1.ObjectMeta{
    86  				GenerateName: "ms-",
    87  				Namespace:    namespace.Name,
    88  				Labels: map[string]string{
    89  					"label-1": "true",
    90  				},
    91  			},
    92  			Spec: clusterv1.MachineSetSpec{
    93  				ClusterName: testCluster.Name,
    94  				Replicas:    &replicas,
    95  				Selector: metav1.LabelSelector{
    96  					MatchLabels: map[string]string{
    97  						"label-1": "true",
    98  					},
    99  				},
   100  				Template: clusterv1.MachineTemplateSpec{
   101  					ObjectMeta: clusterv1.ObjectMeta{
   102  						Labels: map[string]string{
   103  							"label-1": "true",
   104  						},
   105  						Annotations: map[string]string{
   106  							"annotation-1": "true",
   107  							"precedence":   "MachineSet",
   108  						},
   109  					},
   110  					Spec: clusterv1.MachineSpec{
   111  						ClusterName: testCluster.Name,
   112  						Version:     &version,
   113  						Bootstrap: clusterv1.Bootstrap{
   114  							ConfigRef: &corev1.ObjectReference{
   115  								APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1",
   116  								Kind:       "GenericBootstrapConfigTemplate",
   117  								Name:       "ms-template",
   118  							},
   119  						},
   120  						InfrastructureRef: corev1.ObjectReference{
   121  							APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1",
   122  							Kind:       "GenericInfrastructureMachineTemplate",
   123  							Name:       "ms-template",
   124  						},
   125  						NodeDrainTimeout:        duration10m,
   126  						NodeDeletionTimeout:     duration10m,
   127  						NodeVolumeDetachTimeout: duration10m,
   128  					},
   129  				},
   130  			},
   131  		}
   132  
   133  		// Create bootstrap template resource.
   134  		bootstrapResource := map[string]interface{}{
   135  			"kind":       "GenericBootstrapConfig",
   136  			"apiVersion": "bootstrap.cluster.x-k8s.io/v1beta1",
   137  			"metadata": map[string]interface{}{
   138  				"annotations": map[string]interface{}{
   139  					"precedence": "GenericBootstrapConfig",
   140  				},
   141  			},
   142  		}
   143  		bootstrapTmpl := &unstructured.Unstructured{
   144  			Object: map[string]interface{}{
   145  				"spec": map[string]interface{}{
   146  					"template": bootstrapResource,
   147  				},
   148  			},
   149  		}
   150  		bootstrapTmpl.SetKind("GenericBootstrapConfigTemplate")
   151  		bootstrapTmpl.SetAPIVersion("bootstrap.cluster.x-k8s.io/v1beta1")
   152  		bootstrapTmpl.SetName("ms-template")
   153  		bootstrapTmpl.SetNamespace(namespace.Name)
   154  		g.Expect(env.Create(ctx, bootstrapTmpl)).To(Succeed())
   155  
   156  		// Create infrastructure template resource.
   157  		infraResource := map[string]interface{}{
   158  			"kind":       "GenericInfrastructureMachine",
   159  			"apiVersion": "infrastructure.cluster.x-k8s.io/v1beta1",
   160  			"metadata": map[string]interface{}{
   161  				"annotations": map[string]interface{}{
   162  					"precedence": "GenericInfrastructureMachineTemplate",
   163  				},
   164  			},
   165  			"spec": map[string]interface{}{
   166  				"size": "3xlarge",
   167  			},
   168  		}
   169  		infraTmpl := &unstructured.Unstructured{
   170  			Object: map[string]interface{}{
   171  				"spec": map[string]interface{}{
   172  					"template": infraResource,
   173  				},
   174  			},
   175  		}
   176  		infraTmpl.SetKind("GenericInfrastructureMachineTemplate")
   177  		infraTmpl.SetAPIVersion("infrastructure.cluster.x-k8s.io/v1beta1")
   178  		infraTmpl.SetName("ms-template")
   179  		infraTmpl.SetNamespace(namespace.Name)
   180  		g.Expect(env.Create(ctx, infraTmpl)).To(Succeed())
   181  
   182  		// Create the MachineSet.
   183  		g.Expect(env.Create(ctx, instance)).To(Succeed())
   184  		defer func() {
   185  			g.Expect(env.Delete(ctx, instance)).To(Succeed())
   186  		}()
   187  
   188  		t.Log("Verifying the linked bootstrap template has a cluster owner reference")
   189  		g.Eventually(func() bool {
   190  			obj, err := external.Get(ctx, env, instance.Spec.Template.Spec.Bootstrap.ConfigRef, instance.Namespace)
   191  			if err != nil {
   192  				return false
   193  			}
   194  
   195  			return util.HasOwnerRef(obj.GetOwnerReferences(), metav1.OwnerReference{
   196  				APIVersion: clusterv1.GroupVersion.String(),
   197  				Kind:       "Cluster",
   198  				Name:       testCluster.Name,
   199  				UID:        testCluster.UID,
   200  			})
   201  		}, timeout).Should(BeTrue())
   202  
   203  		t.Log("Verifying the linked infrastructure template has a cluster owner reference")
   204  		g.Eventually(func() bool {
   205  			obj, err := external.Get(ctx, env, &instance.Spec.Template.Spec.InfrastructureRef, instance.Namespace)
   206  			if err != nil {
   207  				return false
   208  			}
   209  
   210  			return util.HasOwnerRef(obj.GetOwnerReferences(), metav1.OwnerReference{
   211  				APIVersion: clusterv1.GroupVersion.String(),
   212  				Kind:       "Cluster",
   213  				Name:       testCluster.Name,
   214  				UID:        testCluster.UID,
   215  			})
   216  		}, timeout).Should(BeTrue())
   217  
   218  		machines := &clusterv1.MachineList{}
   219  
   220  		// Verify that we have 2 replicas.
   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(replicas))
   227  
   228  		t.Log("Creating a InfrastructureMachine for each Machine")
   229  		infraMachines := &unstructured.UnstructuredList{}
   230  		infraMachines.SetAPIVersion("infrastructure.cluster.x-k8s.io/v1beta1")
   231  		infraMachines.SetKind("GenericInfrastructureMachine")
   232  		g.Eventually(func() int {
   233  			if err := env.List(ctx, infraMachines, client.InNamespace(namespace.Name)); err != nil {
   234  				return -1
   235  			}
   236  			return len(machines.Items)
   237  		}, timeout).Should(BeEquivalentTo(replicas))
   238  		for _, im := range infraMachines.Items {
   239  			g.Expect(im.GetAnnotations()).To(HaveKeyWithValue("annotation-1", "true"), "have annotations of MachineTemplate applied")
   240  			g.Expect(im.GetAnnotations()).To(HaveKeyWithValue("precedence", "MachineSet"), "the annotations from the MachineSpec template to overwrite the infrastructure template ones")
   241  			g.Expect(im.GetLabels()).To(HaveKeyWithValue("label-1", "true"), "have labels of MachineTemplate applied")
   242  		}
   243  		g.Eventually(func() bool {
   244  			g.Expect(env.List(ctx, infraMachines, client.InNamespace(namespace.Name))).To(Succeed())
   245  			// The Machine reconciler should remove the ownerReference to the MachineSet on the InfrastructureMachine.
   246  			hasMSOwnerRef := false
   247  			hasMachineOwnerRef := false
   248  			for _, im := range infraMachines.Items {
   249  				for _, o := range im.GetOwnerReferences() {
   250  					if o.Kind == machineSetKind.Kind {
   251  						hasMSOwnerRef = true
   252  					}
   253  					if o.Kind == "Machine" {
   254  						hasMachineOwnerRef = true
   255  					}
   256  				}
   257  			}
   258  			return !hasMSOwnerRef && hasMachineOwnerRef
   259  		}, timeout).Should(BeTrue(), "infraMachine should not have ownerRef to MachineSet")
   260  
   261  		t.Log("Creating a BootstrapConfig for each Machine")
   262  		bootstrapConfigs := &unstructured.UnstructuredList{}
   263  		bootstrapConfigs.SetAPIVersion("bootstrap.cluster.x-k8s.io/v1beta1")
   264  		bootstrapConfigs.SetKind("GenericBootstrapConfig")
   265  		g.Eventually(func() int {
   266  			if err := env.List(ctx, bootstrapConfigs, client.InNamespace(namespace.Name)); err != nil {
   267  				return -1
   268  			}
   269  			return len(machines.Items)
   270  		}, timeout).Should(BeEquivalentTo(replicas))
   271  		for _, im := range bootstrapConfigs.Items {
   272  			g.Expect(im.GetAnnotations()).To(HaveKeyWithValue("annotation-1", "true"), "have annotations of MachineTemplate applied")
   273  			g.Expect(im.GetAnnotations()).To(HaveKeyWithValue("precedence", "MachineSet"), "the annotations from the MachineSpec template to overwrite the bootstrap config template ones")
   274  			g.Expect(im.GetLabels()).To(HaveKeyWithValue("label-1", "true"), "have labels of MachineTemplate applied")
   275  		}
   276  		g.Eventually(func() bool {
   277  			g.Expect(env.List(ctx, bootstrapConfigs, client.InNamespace(namespace.Name))).To(Succeed())
   278  			// The Machine reconciler should remove the ownerReference to the MachineSet on the Bootstrap object.
   279  			hasMSOwnerRef := false
   280  			hasMachineOwnerRef := false
   281  			for _, im := range bootstrapConfigs.Items {
   282  				for _, o := range im.GetOwnerReferences() {
   283  					if o.Kind == machineSetKind.Kind {
   284  						hasMSOwnerRef = true
   285  					}
   286  					if o.Kind == "Machine" {
   287  						hasMachineOwnerRef = true
   288  					}
   289  				}
   290  			}
   291  			return !hasMSOwnerRef && hasMachineOwnerRef
   292  		}, timeout).Should(BeTrue(), "bootstrap should not have ownerRef to MachineSet")
   293  
   294  		// Set the infrastructure reference as ready.
   295  		for _, m := range machines.Items {
   296  			fakeBootstrapRefReady(*m.Spec.Bootstrap.ConfigRef, bootstrapResource, g)
   297  			fakeInfrastructureRefReady(m.Spec.InfrastructureRef, infraResource, g)
   298  		}
   299  
   300  		// Verify that in-place mutable fields propagate from MachineSet to Machines.
   301  		t.Log("Updating NodeDrainTimeout on MachineSet")
   302  		patchHelper, err := patch.NewHelper(instance, env)
   303  		g.Expect(err).ToNot(HaveOccurred())
   304  		instance.Spec.Template.Spec.NodeDrainTimeout = duration5m
   305  		g.Expect(patchHelper.Patch(ctx, instance)).Should(Succeed())
   306  
   307  		t.Log("Verifying new NodeDrainTimeout value is set on Machines")
   308  		g.Eventually(func() bool {
   309  			if err := env.List(ctx, machines, client.InNamespace(namespace.Name)); err != nil {
   310  				return false
   311  			}
   312  			// All the machines should have the new NodeDrainTimeoutValue
   313  			for _, m := range machines.Items {
   314  				if m.Spec.NodeDrainTimeout == nil {
   315  					return false
   316  				}
   317  				if m.Spec.NodeDrainTimeout.Duration != duration5m.Duration {
   318  					return false
   319  				}
   320  			}
   321  			return true
   322  		}, timeout).Should(BeTrue(), "machine should have the updated NodeDrainTimeout value")
   323  
   324  		// Try to delete 1 machine and check the MachineSet scales back up.
   325  		machineToBeDeleted := machines.Items[0]
   326  		g.Expect(env.Delete(ctx, &machineToBeDeleted)).To(Succeed())
   327  
   328  		// Verify that the Machine has been deleted.
   329  		g.Eventually(func() bool {
   330  			key := client.ObjectKey{Name: machineToBeDeleted.Name, Namespace: machineToBeDeleted.Namespace}
   331  			if err := env.Get(ctx, key, &machineToBeDeleted); apierrors.IsNotFound(err) || !machineToBeDeleted.DeletionTimestamp.IsZero() {
   332  				return true
   333  			}
   334  			return false
   335  		}, timeout).Should(BeTrue())
   336  
   337  		// Verify that we have 2 replicas.
   338  		g.Eventually(func() (ready int) {
   339  			if err := env.List(ctx, machines, client.InNamespace(namespace.Name)); err != nil {
   340  				return -1
   341  			}
   342  			for _, m := range machines.Items {
   343  				if !m.DeletionTimestamp.IsZero() {
   344  					continue
   345  				}
   346  				ready++
   347  			}
   348  			return
   349  		}, timeout*3).Should(BeEquivalentTo(replicas))
   350  
   351  		// Verify that each machine has the desired kubelet version,
   352  		// create a fake node in Ready state, update NodeRef, and wait for a reconciliation request.
   353  		for i := 0; i < len(machines.Items); i++ {
   354  			m := machines.Items[i]
   355  			if !m.DeletionTimestamp.IsZero() {
   356  				// Skip deleted Machines
   357  				continue
   358  			}
   359  
   360  			g.Expect(m.Spec.Version).ToNot(BeNil())
   361  			g.Expect(*m.Spec.Version).To(BeEquivalentTo("v1.14.2"))
   362  			fakeBootstrapRefReady(*m.Spec.Bootstrap.ConfigRef, bootstrapResource, g)
   363  			providerID := fakeInfrastructureRefReady(m.Spec.InfrastructureRef, infraResource, g)
   364  			fakeMachineNodeRef(&m, providerID, g)
   365  		}
   366  
   367  		// Verify that all Machines are Ready.
   368  		g.Eventually(func() int32 {
   369  			key := client.ObjectKey{Name: instance.Name, Namespace: instance.Namespace}
   370  			if err := env.Get(ctx, key, instance); err != nil {
   371  				return -1
   372  			}
   373  			return instance.Status.AvailableReplicas
   374  		}, timeout).Should(BeEquivalentTo(replicas))
   375  
   376  		t.Log("Verifying MachineSet has MachinesCreatedCondition")
   377  		g.Eventually(func() bool {
   378  			key := client.ObjectKey{Name: instance.Name, Namespace: instance.Namespace}
   379  			if err := env.Get(ctx, key, instance); err != nil {
   380  				return false
   381  			}
   382  			return conditions.IsTrue(instance, clusterv1.MachinesCreatedCondition)
   383  		}, timeout).Should(BeTrue())
   384  
   385  		t.Log("Verifying MachineSet has ResizedCondition")
   386  		g.Eventually(func() bool {
   387  			key := client.ObjectKey{Name: instance.Name, Namespace: instance.Namespace}
   388  			if err := env.Get(ctx, key, instance); err != nil {
   389  				return false
   390  			}
   391  			return conditions.IsTrue(instance, clusterv1.ResizedCondition)
   392  		}, timeout).Should(BeTrue())
   393  
   394  		t.Log("Verifying MachineSet has MachinesReadyCondition")
   395  		g.Eventually(func() bool {
   396  			key := client.ObjectKey{Name: instance.Name, Namespace: instance.Namespace}
   397  			if err := env.Get(ctx, key, instance); err != nil {
   398  				return false
   399  			}
   400  			return conditions.IsTrue(instance, clusterv1.MachinesReadyCondition)
   401  		}, timeout).Should(BeTrue())
   402  
   403  		// Validate that the controller set the cluster name label in selector.
   404  		g.Expect(instance.Status.Selector).To(ContainSubstring(testCluster.Name))
   405  	})
   406  }
   407  
   408  func TestMachineSetOwnerReference(t *testing.T) {
   409  	testCluster := &clusterv1.Cluster{
   410  		TypeMeta:   metav1.TypeMeta{Kind: "Cluster", APIVersion: clusterv1.GroupVersion.String()},
   411  		ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceDefault, Name: testClusterName},
   412  	}
   413  
   414  	ms1 := newMachineSet("machineset1", "valid-cluster", int32(0))
   415  	ms2 := newMachineSet("machineset2", "invalid-cluster", int32(0))
   416  	ms3 := newMachineSet("machineset3", "valid-cluster", int32(0))
   417  	ms3.OwnerReferences = []metav1.OwnerReference{
   418  		{
   419  			APIVersion: clusterv1.GroupVersion.String(),
   420  			Kind:       "MachineDeployment",
   421  			Name:       "valid-machinedeployment",
   422  		},
   423  	}
   424  
   425  	testCases := []struct {
   426  		name               string
   427  		request            reconcile.Request
   428  		ms                 *clusterv1.MachineSet
   429  		expectReconcileErr bool
   430  		expectedOR         []metav1.OwnerReference
   431  	}{
   432  		{
   433  			name: "should add cluster owner reference to machine set",
   434  			request: reconcile.Request{
   435  				NamespacedName: util.ObjectKey(ms1),
   436  			},
   437  			ms: ms1,
   438  			expectedOR: []metav1.OwnerReference{
   439  				{
   440  					APIVersion: testCluster.APIVersion,
   441  					Kind:       testCluster.Kind,
   442  					Name:       testCluster.Name,
   443  					UID:        testCluster.UID,
   444  				},
   445  			},
   446  		},
   447  		{
   448  			name: "should not add cluster owner reference if machine is owned by a machine deployment",
   449  			request: reconcile.Request{
   450  				NamespacedName: util.ObjectKey(ms3),
   451  			},
   452  			ms: ms3,
   453  			expectedOR: []metav1.OwnerReference{
   454  				{
   455  					APIVersion: clusterv1.GroupVersion.String(),
   456  					Kind:       "MachineDeployment",
   457  					Name:       "valid-machinedeployment",
   458  				},
   459  			},
   460  		},
   461  	}
   462  
   463  	for _, tc := range testCases {
   464  		t.Run(tc.name, func(t *testing.T) {
   465  			g := NewWithT(t)
   466  
   467  			c := fake.NewClientBuilder().WithObjects(
   468  				testCluster,
   469  				ms1,
   470  				ms2,
   471  				ms3,
   472  			).WithStatusSubresource(&clusterv1.MachineSet{}).Build()
   473  			msr := &Reconciler{
   474  				Client:                    c,
   475  				UnstructuredCachingClient: c,
   476  				recorder:                  record.NewFakeRecorder(32),
   477  			}
   478  
   479  			_, err := msr.Reconcile(ctx, tc.request)
   480  			if tc.expectReconcileErr {
   481  				g.Expect(err).To(HaveOccurred())
   482  			} else {
   483  				g.Expect(err).ToNot(HaveOccurred())
   484  			}
   485  
   486  			key := client.ObjectKey{Namespace: tc.ms.Namespace, Name: tc.ms.Name}
   487  			var actual clusterv1.MachineSet
   488  			if len(tc.expectedOR) > 0 {
   489  				g.Expect(msr.Client.Get(ctx, key, &actual)).To(Succeed())
   490  				g.Expect(actual.OwnerReferences).To(BeComparableTo(tc.expectedOR))
   491  			} else {
   492  				g.Expect(actual.OwnerReferences).To(BeEmpty())
   493  			}
   494  		})
   495  	}
   496  }
   497  
   498  func TestMachineSetReconcile(t *testing.T) {
   499  	testCluster := &clusterv1.Cluster{
   500  		ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceDefault, Name: testClusterName},
   501  	}
   502  
   503  	t.Run("ignore machine sets marked for deletion", func(t *testing.T) {
   504  		g := NewWithT(t)
   505  
   506  		dt := metav1.Now()
   507  		ms := &clusterv1.MachineSet{
   508  			ObjectMeta: metav1.ObjectMeta{
   509  				Name:              "machineset1",
   510  				Namespace:         metav1.NamespaceDefault,
   511  				DeletionTimestamp: &dt,
   512  				Finalizers:        []string{"block-deletion"},
   513  			},
   514  			Spec: clusterv1.MachineSetSpec{
   515  				ClusterName: testClusterName,
   516  			},
   517  		}
   518  		request := reconcile.Request{
   519  			NamespacedName: util.ObjectKey(ms),
   520  		}
   521  
   522  		c := fake.NewClientBuilder().WithObjects(testCluster, ms).WithStatusSubresource(&clusterv1.MachineSet{}).Build()
   523  		msr := &Reconciler{
   524  			Client:                    c,
   525  			UnstructuredCachingClient: c,
   526  			recorder:                  record.NewFakeRecorder(32),
   527  		}
   528  		result, err := msr.Reconcile(ctx, request)
   529  		g.Expect(err).ToNot(HaveOccurred())
   530  		g.Expect(result).To(BeComparableTo(reconcile.Result{}))
   531  	})
   532  
   533  	t.Run("records event if reconcile fails", func(t *testing.T) {
   534  		g := NewWithT(t)
   535  
   536  		ms := newMachineSet("machineset1", testClusterName, int32(0))
   537  		ms.Spec.Selector.MatchLabels = map[string]string{
   538  			"--$-invalid": "true",
   539  		}
   540  
   541  		request := reconcile.Request{
   542  			NamespacedName: util.ObjectKey(ms),
   543  		}
   544  
   545  		rec := record.NewFakeRecorder(32)
   546  		c := fake.NewClientBuilder().WithObjects(testCluster, ms).WithStatusSubresource(&clusterv1.MachineSet{}).Build()
   547  		msr := &Reconciler{
   548  			Client:                    c,
   549  			UnstructuredCachingClient: c,
   550  			recorder:                  rec,
   551  		}
   552  		_, _ = msr.Reconcile(ctx, request)
   553  		g.Eventually(rec.Events).Should(Receive())
   554  	})
   555  
   556  	t.Run("reconcile successfully when labels are missing", func(t *testing.T) {
   557  		g := NewWithT(t)
   558  
   559  		ms := newMachineSet("machineset1", testClusterName, int32(0))
   560  		ms.Labels = nil
   561  		ms.Spec.Selector.MatchLabels = nil
   562  		ms.Spec.Template.Labels = nil
   563  
   564  		request := reconcile.Request{
   565  			NamespacedName: util.ObjectKey(ms),
   566  		}
   567  
   568  		rec := record.NewFakeRecorder(32)
   569  		c := fake.NewClientBuilder().WithObjects(testCluster, ms).WithStatusSubresource(&clusterv1.MachineSet{}).Build()
   570  		msr := &Reconciler{
   571  			Client:                    c,
   572  			UnstructuredCachingClient: c,
   573  			recorder:                  rec,
   574  		}
   575  		_, err := msr.Reconcile(ctx, request)
   576  		g.Expect(err).ToNot(HaveOccurred())
   577  	})
   578  }
   579  
   580  func TestMachineSetToMachines(t *testing.T) {
   581  	machineSetList := []client.Object{
   582  		&clusterv1.MachineSet{
   583  			ObjectMeta: metav1.ObjectMeta{
   584  				Name:      "withMatchingLabels",
   585  				Namespace: metav1.NamespaceDefault,
   586  			},
   587  			Spec: clusterv1.MachineSetSpec{
   588  				Selector: metav1.LabelSelector{
   589  					MatchLabels: map[string]string{
   590  						"foo":                      "bar",
   591  						clusterv1.ClusterNameLabel: testClusterName,
   592  					},
   593  				},
   594  			},
   595  		},
   596  	}
   597  	controller := true
   598  	m := clusterv1.Machine{
   599  		ObjectMeta: metav1.ObjectMeta{
   600  			Name:      "withOwnerRef",
   601  			Namespace: metav1.NamespaceDefault,
   602  			Labels: map[string]string{
   603  				clusterv1.ClusterNameLabel: testClusterName,
   604  			},
   605  			OwnerReferences: []metav1.OwnerReference{
   606  				{
   607  					Name:       "Owner",
   608  					Kind:       machineSetKind.Kind,
   609  					Controller: &controller,
   610  				},
   611  			},
   612  		},
   613  	}
   614  	m2 := clusterv1.Machine{
   615  		ObjectMeta: metav1.ObjectMeta{
   616  			Name:      "noOwnerRefNoLabels",
   617  			Namespace: metav1.NamespaceDefault,
   618  			Labels: map[string]string{
   619  				clusterv1.ClusterNameLabel: testClusterName,
   620  			},
   621  		},
   622  	}
   623  	m3 := clusterv1.Machine{
   624  		ObjectMeta: metav1.ObjectMeta{
   625  			Name:      "withMatchingLabels",
   626  			Namespace: metav1.NamespaceDefault,
   627  			Labels: map[string]string{
   628  				"foo":                      "bar",
   629  				clusterv1.ClusterNameLabel: testClusterName,
   630  			},
   631  		},
   632  	}
   633  	testsCases := []struct {
   634  		name      string
   635  		mapObject client.Object
   636  		expected  []reconcile.Request
   637  	}{
   638  		{
   639  			name:      "should return empty request when controller is set",
   640  			mapObject: &m,
   641  			expected:  []reconcile.Request{},
   642  		},
   643  		{
   644  			name:      "should return nil if machine has no owner reference",
   645  			mapObject: &m2,
   646  			expected:  nil,
   647  		},
   648  		{
   649  			name:      "should return request if machine set's labels matches machine's labels",
   650  			mapObject: &m3,
   651  			expected: []reconcile.Request{
   652  				{NamespacedName: client.ObjectKey{Namespace: metav1.NamespaceDefault, Name: "withMatchingLabels"}},
   653  			},
   654  		},
   655  	}
   656  
   657  	c := fake.NewClientBuilder().WithObjects(append(machineSetList, &m, &m2, &m3)...).Build()
   658  	r := &Reconciler{
   659  		Client:                    c,
   660  		UnstructuredCachingClient: c,
   661  	}
   662  
   663  	for _, tc := range testsCases {
   664  		t.Run(tc.name, func(t *testing.T) {
   665  			gs := NewWithT(t)
   666  
   667  			got := r.MachineToMachineSets(ctx, tc.mapObject)
   668  			gs.Expect(got).To(BeComparableTo(tc.expected))
   669  		})
   670  	}
   671  }
   672  
   673  func TestShouldExcludeMachine(t *testing.T) {
   674  	controller := true
   675  	testCases := []struct {
   676  		machineSet clusterv1.MachineSet
   677  		machine    clusterv1.Machine
   678  		expected   bool
   679  	}{
   680  		{
   681  			machineSet: clusterv1.MachineSet{
   682  				ObjectMeta: metav1.ObjectMeta{UID: "1"},
   683  			},
   684  			machine: clusterv1.Machine{
   685  				ObjectMeta: metav1.ObjectMeta{
   686  					Name:      "withNoMatchingOwnerRef",
   687  					Namespace: metav1.NamespaceDefault,
   688  					OwnerReferences: []metav1.OwnerReference{
   689  						{
   690  							Name:       "Owner",
   691  							Kind:       machineSetKind.Kind,
   692  							Controller: &controller,
   693  							UID:        "not-1",
   694  						},
   695  					},
   696  				},
   697  			},
   698  			expected: true,
   699  		},
   700  		{
   701  			machineSet: clusterv1.MachineSet{
   702  				ObjectMeta: metav1.ObjectMeta{UID: "1"},
   703  			},
   704  			machine: clusterv1.Machine{
   705  				ObjectMeta: metav1.ObjectMeta{
   706  					Name:      "withMatchingOwnerRef",
   707  					Namespace: metav1.NamespaceDefault,
   708  					OwnerReferences: []metav1.OwnerReference{
   709  						{
   710  							Name:       "Owner",
   711  							Kind:       machineSetKind.Kind,
   712  							Controller: &controller,
   713  							UID:        "1",
   714  						},
   715  					},
   716  				},
   717  			},
   718  			expected: false,
   719  		},
   720  		{
   721  			machineSet: clusterv1.MachineSet{
   722  				Spec: clusterv1.MachineSetSpec{
   723  					Selector: metav1.LabelSelector{
   724  						MatchLabels: map[string]string{
   725  							"foo": "bar",
   726  						},
   727  					},
   728  				},
   729  			},
   730  			machine: clusterv1.Machine{
   731  				ObjectMeta: metav1.ObjectMeta{
   732  					Name:      "withMatchingLabels",
   733  					Namespace: metav1.NamespaceDefault,
   734  					Labels: map[string]string{
   735  						"foo": "bar",
   736  					},
   737  				},
   738  			},
   739  			expected: false,
   740  		},
   741  		{
   742  			machineSet: clusterv1.MachineSet{},
   743  			machine: clusterv1.Machine{
   744  				ObjectMeta: metav1.ObjectMeta{
   745  					Name:              "withDeletionTimestamp",
   746  					Namespace:         metav1.NamespaceDefault,
   747  					DeletionTimestamp: &metav1.Time{Time: time.Now()},
   748  					Labels: map[string]string{
   749  						"foo": "bar",
   750  					},
   751  				},
   752  			},
   753  			expected: false,
   754  		},
   755  	}
   756  
   757  	for i := range testCases {
   758  		tc := testCases[i]
   759  		g := NewWithT(t)
   760  
   761  		got := shouldExcludeMachine(&tc.machineSet, &tc.machine)
   762  
   763  		g.Expect(got).To(Equal(tc.expected))
   764  	}
   765  }
   766  
   767  func TestAdoptOrphan(t *testing.T) {
   768  	g := NewWithT(t)
   769  
   770  	m := clusterv1.Machine{
   771  		ObjectMeta: metav1.ObjectMeta{
   772  			Name: "orphanMachine",
   773  		},
   774  	}
   775  	ms := clusterv1.MachineSet{
   776  		ObjectMeta: metav1.ObjectMeta{
   777  			Name: "adoptOrphanMachine",
   778  		},
   779  	}
   780  	controller := true
   781  	blockOwnerDeletion := true
   782  	testCases := []struct {
   783  		machineSet clusterv1.MachineSet
   784  		machine    clusterv1.Machine
   785  		expected   []metav1.OwnerReference
   786  	}{
   787  		{
   788  			machine:    m,
   789  			machineSet: ms,
   790  			expected: []metav1.OwnerReference{
   791  				{
   792  					APIVersion:         clusterv1.GroupVersion.String(),
   793  					Kind:               machineSetKind.Kind,
   794  					Name:               "adoptOrphanMachine",
   795  					UID:                "",
   796  					Controller:         &controller,
   797  					BlockOwnerDeletion: &blockOwnerDeletion,
   798  				},
   799  			},
   800  		},
   801  	}
   802  
   803  	c := fake.NewClientBuilder().WithObjects(&m).Build()
   804  	r := &Reconciler{
   805  		Client:                    c,
   806  		UnstructuredCachingClient: c,
   807  	}
   808  	for i := range testCases {
   809  		tc := testCases[i]
   810  		g.Expect(r.adoptOrphan(ctx, tc.machineSet.DeepCopy(), tc.machine.DeepCopy())).To(Succeed())
   811  
   812  		key := client.ObjectKey{Namespace: tc.machine.Namespace, Name: tc.machine.Name}
   813  		g.Expect(r.Client.Get(ctx, key, &tc.machine)).To(Succeed())
   814  
   815  		got := tc.machine.GetOwnerReferences()
   816  		g.Expect(got).To(BeComparableTo(tc.expected))
   817  	}
   818  }
   819  
   820  func newMachineSet(name, cluster string, replicas int32) *clusterv1.MachineSet {
   821  	return &clusterv1.MachineSet{
   822  		ObjectMeta: metav1.ObjectMeta{
   823  			Name:      name,
   824  			Namespace: metav1.NamespaceDefault,
   825  			Labels: map[string]string{
   826  				clusterv1.ClusterNameLabel: cluster,
   827  			},
   828  		},
   829  		Spec: clusterv1.MachineSetSpec{
   830  			ClusterName: testClusterName,
   831  			Replicas:    &replicas,
   832  			Template: clusterv1.MachineTemplateSpec{
   833  				ObjectMeta: clusterv1.ObjectMeta{
   834  					Labels: map[string]string{
   835  						clusterv1.ClusterNameLabel: cluster,
   836  					},
   837  				},
   838  			},
   839  			Selector: metav1.LabelSelector{
   840  				MatchLabels: map[string]string{
   841  					clusterv1.ClusterNameLabel: cluster,
   842  				},
   843  			},
   844  		},
   845  	}
   846  }
   847  
   848  func TestMachineSetReconcile_MachinesCreatedConditionFalseOnBadInfraRef(t *testing.T) {
   849  	g := NewWithT(t)
   850  	replicas := int32(1)
   851  	version := "v1.21.0"
   852  	cluster := &clusterv1.Cluster{
   853  		ObjectMeta: metav1.ObjectMeta{
   854  			Name:      "foo",
   855  			Namespace: metav1.NamespaceDefault,
   856  		},
   857  	}
   858  
   859  	ms := &clusterv1.MachineSet{
   860  		ObjectMeta: metav1.ObjectMeta{
   861  			Name:      "ms-foo",
   862  			Namespace: metav1.NamespaceDefault,
   863  			Labels: map[string]string{
   864  				clusterv1.ClusterNameLabel: cluster.Name,
   865  			},
   866  		},
   867  		Spec: clusterv1.MachineSetSpec{
   868  			ClusterName: cluster.ObjectMeta.Name,
   869  			Replicas:    &replicas,
   870  			Template: clusterv1.MachineTemplateSpec{
   871  				ObjectMeta: clusterv1.ObjectMeta{
   872  					Labels: map[string]string{
   873  						clusterv1.ClusterNameLabel: cluster.Name,
   874  					},
   875  				},
   876  				Spec: clusterv1.MachineSpec{
   877  					InfrastructureRef: corev1.ObjectReference{
   878  						Kind:       builder.GenericInfrastructureMachineTemplateCRD.Kind,
   879  						APIVersion: builder.GenericInfrastructureMachineTemplateCRD.APIVersion,
   880  						// Try to break Infra Cloning
   881  						Name:      "something_invalid",
   882  						Namespace: cluster.Namespace,
   883  					},
   884  					Version: &version,
   885  				},
   886  			},
   887  			Selector: metav1.LabelSelector{
   888  				MatchLabels: map[string]string{
   889  					clusterv1.ClusterNameLabel: cluster.Name,
   890  				},
   891  			},
   892  		},
   893  	}
   894  
   895  	key := util.ObjectKey(ms)
   896  	request := reconcile.Request{
   897  		NamespacedName: key,
   898  	}
   899  	fakeClient := fake.NewClientBuilder().WithObjects(cluster, ms, builder.GenericInfrastructureMachineTemplateCRD.DeepCopy()).WithStatusSubresource(&clusterv1.MachineSet{}).Build()
   900  
   901  	msr := &Reconciler{
   902  		Client:                    fakeClient,
   903  		UnstructuredCachingClient: fakeClient,
   904  		recorder:                  record.NewFakeRecorder(32),
   905  	}
   906  	_, err := msr.Reconcile(ctx, request)
   907  	g.Expect(err).To(HaveOccurred())
   908  	g.Expect(fakeClient.Get(ctx, key, ms)).To(Succeed())
   909  	gotCond := conditions.Get(ms, clusterv1.MachinesCreatedCondition)
   910  	g.Expect(gotCond).ToNot(BeNil())
   911  	g.Expect(gotCond.Status).To(Equal(corev1.ConditionFalse))
   912  	g.Expect(gotCond.Reason).To(Equal(clusterv1.InfrastructureTemplateCloningFailedReason))
   913  }
   914  
   915  func TestMachineSetReconciler_updateStatusResizedCondition(t *testing.T) {
   916  	cluster := &clusterv1.Cluster{
   917  		ObjectMeta: metav1.ObjectMeta{
   918  			Name:      "foo",
   919  			Namespace: metav1.NamespaceDefault,
   920  		},
   921  	}
   922  
   923  	testCases := []struct {
   924  		name            string
   925  		machineSet      *clusterv1.MachineSet
   926  		machines        []*clusterv1.Machine
   927  		expectedReason  string
   928  		expectedMessage string
   929  	}{
   930  		{
   931  			name:            "MachineSet should have ResizedCondition=false on scale up",
   932  			machineSet:      newMachineSet("ms-scale-up", cluster.Name, int32(1)),
   933  			machines:        []*clusterv1.Machine{},
   934  			expectedReason:  clusterv1.ScalingUpReason,
   935  			expectedMessage: "Scaling up MachineSet to 1 replicas (actual 0)",
   936  		},
   937  		{
   938  			name:       "MachineSet should have ResizedCondition=false on scale down",
   939  			machineSet: newMachineSet("ms-scale-down", cluster.Name, int32(0)),
   940  			machines: []*clusterv1.Machine{{
   941  				ObjectMeta: metav1.ObjectMeta{
   942  					Name:      "machine-a",
   943  					Namespace: metav1.NamespaceDefault,
   944  					Labels: map[string]string{
   945  						clusterv1.ClusterNameLabel: cluster.Name,
   946  					},
   947  				},
   948  			},
   949  			},
   950  			expectedReason:  clusterv1.ScalingDownReason,
   951  			expectedMessage: "Scaling down MachineSet to 0 replicas (actual 1)",
   952  		},
   953  	}
   954  
   955  	for _, tc := range testCases {
   956  		t.Run(tc.name, func(t *testing.T) {
   957  			g := NewWithT(t)
   958  
   959  			c := fake.NewClientBuilder().WithObjects().Build()
   960  			msr := &Reconciler{
   961  				Client:                    c,
   962  				UnstructuredCachingClient: c,
   963  				recorder:                  record.NewFakeRecorder(32),
   964  			}
   965  			err := msr.updateStatus(ctx, cluster, tc.machineSet, tc.machines)
   966  			g.Expect(err).ToNot(HaveOccurred())
   967  			gotCond := conditions.Get(tc.machineSet, clusterv1.ResizedCondition)
   968  			g.Expect(gotCond).ToNot(BeNil())
   969  			g.Expect(gotCond.Status).To(Equal(corev1.ConditionFalse))
   970  			g.Expect(gotCond.Reason).To(Equal(tc.expectedReason))
   971  			g.Expect(gotCond.Message).To(Equal(tc.expectedMessage))
   972  		})
   973  	}
   974  }
   975  
   976  func TestMachineSetReconciler_syncMachines(t *testing.T) {
   977  	setup := func(t *testing.T, g *WithT) (*corev1.Namespace, *clusterv1.Cluster) {
   978  		t.Helper()
   979  
   980  		t.Log("Creating the namespace")
   981  		ns, err := env.CreateNamespace(ctx, "test-machine-set-reconciler-sync-machines")
   982  		g.Expect(err).ToNot(HaveOccurred())
   983  
   984  		t.Log("Creating the Cluster")
   985  		cluster := &clusterv1.Cluster{ObjectMeta: metav1.ObjectMeta{Namespace: ns.Name, Name: testClusterName}}
   986  		g.Expect(env.Create(ctx, cluster)).To(Succeed())
   987  
   988  		t.Log("Creating the Cluster Kubeconfig Secret")
   989  		g.Expect(env.CreateKubeconfigSecret(ctx, cluster)).To(Succeed())
   990  
   991  		return ns, cluster
   992  	}
   993  
   994  	teardown := func(t *testing.T, g *WithT, ns *corev1.Namespace, cluster *clusterv1.Cluster) {
   995  		t.Helper()
   996  
   997  		t.Log("Deleting the Cluster")
   998  		g.Expect(env.Delete(ctx, cluster)).To(Succeed())
   999  		t.Log("Deleting the namespace")
  1000  		g.Expect(env.Delete(ctx, ns)).To(Succeed())
  1001  	}
  1002  
  1003  	g := NewWithT(t)
  1004  	namespace, testCluster := setup(t, g)
  1005  	defer teardown(t, g, namespace, testCluster)
  1006  
  1007  	classicManager := "manager"
  1008  	replicas := int32(2)
  1009  	version := "v1.25.3"
  1010  	duration10s := &metav1.Duration{Duration: 10 * time.Second}
  1011  	ms := &clusterv1.MachineSet{
  1012  		ObjectMeta: metav1.ObjectMeta{
  1013  			UID:       "abc-123-ms-uid",
  1014  			Name:      "ms-1",
  1015  			Namespace: namespace.Name,
  1016  			Labels: map[string]string{
  1017  				"label-1":                            "true",
  1018  				clusterv1.MachineDeploymentNameLabel: "md-1",
  1019  			},
  1020  		},
  1021  		Spec: clusterv1.MachineSetSpec{
  1022  			ClusterName: testCluster.Name,
  1023  			Replicas:    &replicas,
  1024  			Selector: metav1.LabelSelector{
  1025  				MatchLabels: map[string]string{
  1026  					"preserved-label": "preserved-value",
  1027  				},
  1028  			},
  1029  			Template: clusterv1.MachineTemplateSpec{
  1030  				ObjectMeta: clusterv1.ObjectMeta{
  1031  					Labels: map[string]string{
  1032  						"preserved-label": "preserved-value", // Label will be preserved while testing in-place mutation.
  1033  						"dropped-label":   "dropped-value",   // Label will be dropped while testing in-place mutation.
  1034  						"modified-label":  "modified-value",  // Label value will be modified while testing in-place mutation.
  1035  					},
  1036  					Annotations: map[string]string{
  1037  						"preserved-annotation": "preserved-value", // Annotation will be preserved while testing in-place mutation.
  1038  						"dropped-annotation":   "dropped-value",   // Annotation will be dropped while testing in-place mutation.
  1039  						"modified-annotation":  "modified-value",  // Annotation value will be modified while testing in-place mutation.
  1040  					},
  1041  				},
  1042  				Spec: clusterv1.MachineSpec{
  1043  					ClusterName: testCluster.Name,
  1044  					Version:     &version,
  1045  					Bootstrap: clusterv1.Bootstrap{
  1046  						ConfigRef: &corev1.ObjectReference{
  1047  							APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1",
  1048  							Kind:       "GenericBootstrapConfigTemplate",
  1049  							Name:       "ms-template",
  1050  						},
  1051  					},
  1052  					InfrastructureRef: corev1.ObjectReference{
  1053  						APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1",
  1054  						Kind:       "GenericInfrastructureMachineTemplate",
  1055  						Name:       "ms-template",
  1056  					},
  1057  				},
  1058  			},
  1059  		},
  1060  	}
  1061  
  1062  	infraMachineSpec := map[string]interface{}{
  1063  		"infra-field": "infra-value",
  1064  	}
  1065  	infraMachine := &unstructured.Unstructured{
  1066  		Object: map[string]interface{}{
  1067  			"kind":       "GenericInfrastructureMachine",
  1068  			"apiVersion": "infrastructure.cluster.x-k8s.io/v1beta1",
  1069  			"metadata": map[string]interface{}{
  1070  				"name":      "infra-machine-1",
  1071  				"namespace": namespace.Name,
  1072  				"labels": map[string]string{
  1073  					"preserved-label": "preserved-value",
  1074  					"dropped-label":   "dropped-value",
  1075  					"modified-label":  "modified-value",
  1076  				},
  1077  				"annotations": map[string]string{
  1078  					"preserved-annotation": "preserved-value",
  1079  					"dropped-annotation":   "dropped-value",
  1080  					"modified-annotation":  "modified-value",
  1081  				},
  1082  			},
  1083  			"spec": infraMachineSpec,
  1084  		},
  1085  	}
  1086  	g.Expect(env.Create(ctx, infraMachine, client.FieldOwner(classicManager))).To(Succeed())
  1087  
  1088  	bootstrapConfigSpec := map[string]interface{}{
  1089  		"bootstrap-field": "bootstrap-value",
  1090  	}
  1091  	bootstrapConfig := &unstructured.Unstructured{
  1092  		Object: map[string]interface{}{
  1093  			"kind":       "GenericBootstrapConfig",
  1094  			"apiVersion": "bootstrap.cluster.x-k8s.io/v1beta1",
  1095  			"metadata": map[string]interface{}{
  1096  				"name":      "bootstrap-config-1",
  1097  				"namespace": namespace.Name,
  1098  				"labels": map[string]string{
  1099  					"preserved-label": "preserved-value",
  1100  					"dropped-label":   "dropped-value",
  1101  					"modified-label":  "modified-value",
  1102  				},
  1103  				"annotations": map[string]string{
  1104  					"preserved-annotation": "preserved-value",
  1105  					"dropped-annotation":   "dropped-value",
  1106  					"modified-annotation":  "modified-value",
  1107  				},
  1108  			},
  1109  			"spec": bootstrapConfigSpec,
  1110  		},
  1111  	}
  1112  	g.Expect(env.Create(ctx, bootstrapConfig, client.FieldOwner(classicManager))).To(Succeed())
  1113  
  1114  	inPlaceMutatingMachine := &clusterv1.Machine{
  1115  		TypeMeta: metav1.TypeMeta{
  1116  			APIVersion: clusterv1.GroupVersion.String(),
  1117  			Kind:       "Machine",
  1118  		},
  1119  		ObjectMeta: metav1.ObjectMeta{
  1120  			UID:       "abc-123-uid",
  1121  			Name:      "in-place-mutating-machine",
  1122  			Namespace: namespace.Name,
  1123  			Labels: map[string]string{
  1124  				"preserved-label": "preserved-value",
  1125  				"dropped-label":   "dropped-value",
  1126  				"modified-label":  "modified-value",
  1127  			},
  1128  			Annotations: map[string]string{
  1129  				"preserved-annotation": "preserved-value",
  1130  				"dropped-annotation":   "dropped-value",
  1131  				"modified-annotation":  "modified-value",
  1132  			},
  1133  		},
  1134  		Spec: clusterv1.MachineSpec{
  1135  			ClusterName: testClusterName,
  1136  			InfrastructureRef: corev1.ObjectReference{
  1137  				Namespace:  infraMachine.GetNamespace(),
  1138  				Name:       infraMachine.GetName(),
  1139  				UID:        infraMachine.GetUID(),
  1140  				APIVersion: infraMachine.GetAPIVersion(),
  1141  				Kind:       infraMachine.GetKind(),
  1142  			},
  1143  			Bootstrap: clusterv1.Bootstrap{
  1144  				ConfigRef: &corev1.ObjectReference{
  1145  					Namespace:  bootstrapConfig.GetNamespace(),
  1146  					Name:       bootstrapConfig.GetName(),
  1147  					UID:        bootstrapConfig.GetUID(),
  1148  					APIVersion: bootstrapConfig.GetAPIVersion(),
  1149  					Kind:       bootstrapConfig.GetKind(),
  1150  				},
  1151  			},
  1152  		},
  1153  	}
  1154  	g.Expect(env.Create(ctx, inPlaceMutatingMachine, client.FieldOwner(classicManager))).To(Succeed())
  1155  
  1156  	deletingMachine := &clusterv1.Machine{
  1157  		TypeMeta: metav1.TypeMeta{
  1158  			APIVersion: clusterv1.GroupVersion.String(),
  1159  			Kind:       "Machine",
  1160  		},
  1161  		ObjectMeta: metav1.ObjectMeta{
  1162  			UID:         "abc-123-uid",
  1163  			Name:        "deleting-machine",
  1164  			Namespace:   namespace.Name,
  1165  			Labels:      map[string]string{},
  1166  			Annotations: map[string]string{},
  1167  			Finalizers:  []string{"testing-finalizer"},
  1168  		},
  1169  		Spec: clusterv1.MachineSpec{
  1170  			ClusterName: testClusterName,
  1171  			InfrastructureRef: corev1.ObjectReference{
  1172  				Namespace: namespace.Name,
  1173  			},
  1174  			Bootstrap: clusterv1.Bootstrap{
  1175  				DataSecretName: ptr.To("machine-bootstrap-secret"),
  1176  			},
  1177  		},
  1178  	}
  1179  	g.Expect(env.Create(ctx, deletingMachine, client.FieldOwner(classicManager))).To(Succeed())
  1180  	// Delete the machine to put it in the deleting state
  1181  	g.Expect(env.Delete(ctx, deletingMachine)).To(Succeed())
  1182  	// Wait till the machine is marked for deletion
  1183  	g.Eventually(func() bool {
  1184  		if err := env.Get(ctx, client.ObjectKeyFromObject(deletingMachine), deletingMachine); err != nil {
  1185  			return false
  1186  		}
  1187  		return !deletingMachine.DeletionTimestamp.IsZero()
  1188  	}, timeout).Should(BeTrue())
  1189  
  1190  	machines := []*clusterv1.Machine{inPlaceMutatingMachine, deletingMachine}
  1191  
  1192  	//
  1193  	// Verify Managed Fields
  1194  	//
  1195  
  1196  	// Run syncMachines to clean up managed fields and have proper field ownership
  1197  	// for Machines, InfrastructureMachines and BootstrapConfigs.
  1198  	reconciler := &Reconciler{
  1199  		Client:                    env,
  1200  		UnstructuredCachingClient: env,
  1201  		ssaCache:                  ssa.NewCache(),
  1202  	}
  1203  	g.Expect(reconciler.syncMachines(ctx, ms, machines)).To(Succeed())
  1204  
  1205  	// The inPlaceMutatingMachine should have cleaned up managed fields.
  1206  	updatedInPlaceMutatingMachine := inPlaceMutatingMachine.DeepCopy()
  1207  	g.Expect(env.GetAPIReader().Get(ctx, client.ObjectKeyFromObject(updatedInPlaceMutatingMachine), updatedInPlaceMutatingMachine)).To(Succeed())
  1208  	// Verify ManagedFields
  1209  	g.Expect(updatedInPlaceMutatingMachine.ManagedFields).Should(
  1210  		ContainElement(ssa.MatchManagedFieldsEntry(machineSetManagerName, metav1.ManagedFieldsOperationApply)),
  1211  		"in-place mutable machine should contain an entry for SSA manager",
  1212  	)
  1213  	g.Expect(updatedInPlaceMutatingMachine.ManagedFields).ShouldNot(
  1214  		ContainElement(ssa.MatchManagedFieldsEntry(classicManager, metav1.ManagedFieldsOperationUpdate)),
  1215  		"in-place mutable machine should not contain an entry for old manager",
  1216  	)
  1217  
  1218  	// The InfrastructureMachine should have ownership of "labels" and "annotations" transferred to
  1219  	// "capi-machineset" manager.
  1220  	updatedInfraMachine := infraMachine.DeepCopy()
  1221  	g.Expect(env.GetAPIReader().Get(ctx, client.ObjectKeyFromObject(updatedInfraMachine), updatedInfraMachine)).To(Succeed())
  1222  
  1223  	// Verify ManagedFields
  1224  	g.Expect(updatedInfraMachine.GetManagedFields()).Should(
  1225  		ssa.MatchFieldOwnership(machineSetManagerName, metav1.ManagedFieldsOperationApply, contract.Path{"f:metadata", "f:labels"}))
  1226  	g.Expect(updatedInfraMachine.GetManagedFields()).Should(
  1227  		ssa.MatchFieldOwnership(machineSetManagerName, metav1.ManagedFieldsOperationApply, contract.Path{"f:metadata", "f:annotations"}))
  1228  	g.Expect(updatedInfraMachine.GetManagedFields()).ShouldNot(
  1229  		ssa.MatchFieldOwnership(classicManager, metav1.ManagedFieldsOperationUpdate, contract.Path{"f:metadata", "f:labels"}))
  1230  	g.Expect(updatedInfraMachine.GetManagedFields()).ShouldNot(
  1231  		ssa.MatchFieldOwnership(classicManager, metav1.ManagedFieldsOperationUpdate, contract.Path{"f:metadata", "f:annotations"}))
  1232  	g.Expect(updatedInfraMachine.GetManagedFields()).Should(
  1233  		ssa.MatchFieldOwnership(classicManager, metav1.ManagedFieldsOperationUpdate, contract.Path{"f:spec"}))
  1234  
  1235  	// The BootstrapConfig should have ownership of "labels" and "annotations" transferred to
  1236  	// "capi-machineset" manager.
  1237  	updatedBootstrapConfig := bootstrapConfig.DeepCopy()
  1238  	g.Expect(env.GetAPIReader().Get(ctx, client.ObjectKeyFromObject(updatedBootstrapConfig), updatedBootstrapConfig)).To(Succeed())
  1239  
  1240  	// Verify ManagedFields
  1241  	g.Expect(updatedBootstrapConfig.GetManagedFields()).Should(
  1242  		ssa.MatchFieldOwnership(machineSetManagerName, metav1.ManagedFieldsOperationApply, contract.Path{"f:metadata", "f:labels"}))
  1243  	g.Expect(updatedBootstrapConfig.GetManagedFields()).Should(
  1244  		ssa.MatchFieldOwnership(machineSetManagerName, metav1.ManagedFieldsOperationApply, contract.Path{"f:metadata", "f:annotations"}))
  1245  	g.Expect(updatedBootstrapConfig.GetManagedFields()).ShouldNot(
  1246  		ssa.MatchFieldOwnership(classicManager, metav1.ManagedFieldsOperationUpdate, contract.Path{"f:metadata", "f:labels"}))
  1247  	g.Expect(updatedBootstrapConfig.GetManagedFields()).ShouldNot(
  1248  		ssa.MatchFieldOwnership(classicManager, metav1.ManagedFieldsOperationUpdate, contract.Path{"f:metadata", "f:annotations"}))
  1249  	g.Expect(updatedBootstrapConfig.GetManagedFields()).Should(
  1250  		ssa.MatchFieldOwnership(classicManager, metav1.ManagedFieldsOperationUpdate, contract.Path{"f:spec"}))
  1251  
  1252  	//
  1253  	// Verify In-place mutating fields
  1254  	//
  1255  
  1256  	// Update the MachineSet and verify the in-mutating fields are propagated.
  1257  	ms.Spec.Template.Labels = map[string]string{
  1258  		"preserved-label": "preserved-value",  // Keep the label and value as is
  1259  		"modified-label":  "modified-value-2", // Modify the value of the label
  1260  		// Drop "dropped-label"
  1261  	}
  1262  	expectedLabels := map[string]string{
  1263  		"preserved-label":                    "preserved-value",
  1264  		"modified-label":                     "modified-value-2",
  1265  		clusterv1.MachineSetNameLabel:        ms.Name,
  1266  		clusterv1.MachineDeploymentNameLabel: "md-1",
  1267  		clusterv1.ClusterNameLabel:           testClusterName, // This label is added by the Machine controller.
  1268  	}
  1269  	ms.Spec.Template.Annotations = map[string]string{
  1270  		"preserved-annotation": "preserved-value",  // Keep the annotation and value as is
  1271  		"modified-annotation":  "modified-value-2", // Modify the value of the annotation
  1272  		// Drop "dropped-annotation"
  1273  	}
  1274  	ms.Spec.Template.Spec.NodeDrainTimeout = duration10s
  1275  	ms.Spec.Template.Spec.NodeDeletionTimeout = duration10s
  1276  	ms.Spec.Template.Spec.NodeVolumeDetachTimeout = duration10s
  1277  	g.Expect(reconciler.syncMachines(ctx, ms, []*clusterv1.Machine{updatedInPlaceMutatingMachine, deletingMachine})).To(Succeed())
  1278  
  1279  	// Verify in-place mutable fields are updated on the Machine.
  1280  	updatedInPlaceMutatingMachine = inPlaceMutatingMachine.DeepCopy()
  1281  	g.Eventually(func(g Gomega) {
  1282  		g.Expect(env.GetAPIReader().Get(ctx, client.ObjectKeyFromObject(updatedInPlaceMutatingMachine), updatedInPlaceMutatingMachine)).To(Succeed())
  1283  		// Verify Labels
  1284  		g.Expect(updatedInPlaceMutatingMachine.Labels).Should(Equal(expectedLabels))
  1285  		// Verify Annotations
  1286  		g.Expect(updatedInPlaceMutatingMachine.Annotations).Should(Equal(ms.Spec.Template.Annotations))
  1287  		// Verify Node timeout values
  1288  		g.Expect(updatedInPlaceMutatingMachine.Spec.NodeDrainTimeout).Should(And(
  1289  			Not(BeNil()),
  1290  			HaveValue(Equal(*ms.Spec.Template.Spec.NodeDrainTimeout)),
  1291  		))
  1292  		g.Expect(updatedInPlaceMutatingMachine.Spec.NodeDeletionTimeout).Should(And(
  1293  			Not(BeNil()),
  1294  			HaveValue(Equal(*ms.Spec.Template.Spec.NodeDeletionTimeout)),
  1295  		))
  1296  		g.Expect(updatedInPlaceMutatingMachine.Spec.NodeVolumeDetachTimeout).Should(And(
  1297  			Not(BeNil()),
  1298  			HaveValue(Equal(*ms.Spec.Template.Spec.NodeVolumeDetachTimeout)),
  1299  		))
  1300  	}, timeout).Should(Succeed())
  1301  
  1302  	// Verify in-place mutable fields are updated on InfrastructureMachine
  1303  	updatedInfraMachine = infraMachine.DeepCopy()
  1304  	g.Eventually(func(g Gomega) {
  1305  		g.Expect(env.GetAPIReader().Get(ctx, client.ObjectKeyFromObject(updatedInfraMachine), updatedInfraMachine)).To(Succeed())
  1306  		// Verify Labels
  1307  		g.Expect(updatedInfraMachine.GetLabels()).Should(Equal(expectedLabels))
  1308  		// Verify Annotations
  1309  		g.Expect(updatedInfraMachine.GetAnnotations()).Should(Equal(ms.Spec.Template.Annotations))
  1310  		// Verify spec remains the same
  1311  		g.Expect(updatedInfraMachine.Object).Should(HaveKeyWithValue("spec", infraMachineSpec))
  1312  	}, timeout).Should(Succeed())
  1313  
  1314  	// Verify in-place mutable fields are updated on the BootstrapConfig.
  1315  	updatedBootstrapConfig = bootstrapConfig.DeepCopy()
  1316  	g.Eventually(func(g Gomega) {
  1317  		g.Expect(env.GetAPIReader().Get(ctx, client.ObjectKeyFromObject(updatedBootstrapConfig), updatedBootstrapConfig)).To(Succeed())
  1318  		// Verify Labels
  1319  		g.Expect(updatedBootstrapConfig.GetLabels()).Should(Equal(expectedLabels))
  1320  		// Verify Annotations
  1321  		g.Expect(updatedBootstrapConfig.GetAnnotations()).Should(Equal(ms.Spec.Template.Annotations))
  1322  		// Verify spec remains the same
  1323  		g.Expect(updatedBootstrapConfig.Object).Should(HaveKeyWithValue("spec", bootstrapConfigSpec))
  1324  	}, timeout).Should(Succeed())
  1325  
  1326  	// Wait to ensure Machine is not updated.
  1327  	// Verify that the machine stays the same consistently.
  1328  	g.Consistently(func(g Gomega) {
  1329  		// The deleting machine should not change.
  1330  		updatedDeletingMachine := deletingMachine.DeepCopy()
  1331  		g.Expect(env.GetAPIReader().Get(ctx, client.ObjectKeyFromObject(updatedDeletingMachine), updatedDeletingMachine)).To(Succeed())
  1332  
  1333  		// Verify ManagedFields
  1334  		g.Expect(updatedDeletingMachine.ManagedFields).ShouldNot(
  1335  			ContainElement(ssa.MatchManagedFieldsEntry(machineSetManagerName, metav1.ManagedFieldsOperationApply)),
  1336  			"deleting machine should not contain an entry for SSA manager",
  1337  		)
  1338  		g.Expect(updatedDeletingMachine.ManagedFields).Should(
  1339  			ContainElement(ssa.MatchManagedFieldsEntry("manager", metav1.ManagedFieldsOperationUpdate)),
  1340  			"in-place mutable machine should still contain an entry for old manager",
  1341  		)
  1342  
  1343  		// Verify in-place mutable fields are still the same.
  1344  		g.Expect(updatedDeletingMachine.Labels).Should(Equal(deletingMachine.Labels))
  1345  		g.Expect(updatedDeletingMachine.Annotations).Should(Equal(deletingMachine.Annotations))
  1346  		g.Expect(updatedDeletingMachine.Spec.NodeDrainTimeout).Should(Equal(deletingMachine.Spec.NodeDrainTimeout))
  1347  		g.Expect(updatedDeletingMachine.Spec.NodeDeletionTimeout).Should(Equal(deletingMachine.Spec.NodeDeletionTimeout))
  1348  		g.Expect(updatedDeletingMachine.Spec.NodeVolumeDetachTimeout).Should(Equal(deletingMachine.Spec.NodeVolumeDetachTimeout))
  1349  	}, 5*time.Second).Should(Succeed())
  1350  }
  1351  
  1352  func TestMachineSetReconciler_reconcileUnhealthyMachines(t *testing.T) {
  1353  	t.Run("should delete unhealthy machines if preflight checks pass", func(t *testing.T) {
  1354  		defer utilfeature.SetFeatureGateDuringTest(t, feature.Gates, feature.MachineSetPreflightChecks, true)()
  1355  
  1356  		g := NewWithT(t)
  1357  
  1358  		controlPlaneStable := builder.ControlPlane("default", "cp1").
  1359  			WithVersion("v1.26.2").
  1360  			WithStatusFields(map[string]interface{}{
  1361  				"status.version": "v1.26.2",
  1362  			}).
  1363  			Build()
  1364  		cluster := &clusterv1.Cluster{
  1365  			ObjectMeta: metav1.ObjectMeta{
  1366  				Name:      "test-cluster",
  1367  				Namespace: "default",
  1368  			},
  1369  			Spec: clusterv1.ClusterSpec{
  1370  				ControlPlaneRef: contract.ObjToRef(controlPlaneStable),
  1371  			},
  1372  		}
  1373  		machineSet := &clusterv1.MachineSet{}
  1374  
  1375  		unhealthyMachine := &clusterv1.Machine{
  1376  			ObjectMeta: metav1.ObjectMeta{
  1377  				Name:      "unhealthy-machine",
  1378  				Namespace: "default",
  1379  			},
  1380  			Status: clusterv1.MachineStatus{
  1381  				Conditions: []clusterv1.Condition{
  1382  					{
  1383  						Type:   clusterv1.MachineOwnerRemediatedCondition,
  1384  						Status: corev1.ConditionFalse,
  1385  					},
  1386  				},
  1387  			},
  1388  		}
  1389  		healthyMachine := &clusterv1.Machine{
  1390  			ObjectMeta: metav1.ObjectMeta{
  1391  				Name:      "healthy-machine",
  1392  				Namespace: "default",
  1393  			},
  1394  		}
  1395  
  1396  		machines := []*clusterv1.Machine{unhealthyMachine, healthyMachine}
  1397  
  1398  		fakeClient := fake.NewClientBuilder().WithObjects(controlPlaneStable, unhealthyMachine, healthyMachine).Build()
  1399  		r := &Reconciler{
  1400  			Client:                    fakeClient,
  1401  			UnstructuredCachingClient: fakeClient,
  1402  		}
  1403  		_, err := r.reconcileUnhealthyMachines(ctx, cluster, machineSet, machines)
  1404  		g.Expect(err).ToNot(HaveOccurred())
  1405  		// Verify the unhealthy machine is deleted.
  1406  		m := &clusterv1.Machine{}
  1407  		err = r.Client.Get(ctx, client.ObjectKeyFromObject(unhealthyMachine), m)
  1408  		g.Expect(apierrors.IsNotFound(err)).To(BeTrue())
  1409  		// Verify the healthy machine is not deleted.
  1410  		m = &clusterv1.Machine{}
  1411  		g.Expect(r.Client.Get(ctx, client.ObjectKeyFromObject(healthyMachine), m)).Should(Succeed())
  1412  	})
  1413  
  1414  	t.Run("should update the unhealthy machine MachineOwnerRemediated condition if preflight checks did not pass", func(t *testing.T) {
  1415  		defer utilfeature.SetFeatureGateDuringTest(t, feature.Gates, feature.MachineSetPreflightChecks, true)()
  1416  
  1417  		g := NewWithT(t)
  1418  
  1419  		// An upgrading control plane should cause the preflight checks to not pass.
  1420  		controlPlaneUpgrading := builder.ControlPlane("default", "cp1").
  1421  			WithVersion("v1.26.2").
  1422  			WithStatusFields(map[string]interface{}{
  1423  				"status.version": "v1.25.2",
  1424  			}).
  1425  			Build()
  1426  		cluster := &clusterv1.Cluster{
  1427  			ObjectMeta: metav1.ObjectMeta{
  1428  				Name:      "test-cluster",
  1429  				Namespace: "default",
  1430  			},
  1431  			Spec: clusterv1.ClusterSpec{
  1432  				ControlPlaneRef: contract.ObjToRef(controlPlaneUpgrading),
  1433  			},
  1434  		}
  1435  		machineSet := &clusterv1.MachineSet{}
  1436  
  1437  		unhealthyMachine := &clusterv1.Machine{
  1438  			ObjectMeta: metav1.ObjectMeta{
  1439  				Name:      "unhealthy-machine",
  1440  				Namespace: "default",
  1441  			},
  1442  			Status: clusterv1.MachineStatus{
  1443  				Conditions: []clusterv1.Condition{
  1444  					{
  1445  						Type:   clusterv1.MachineOwnerRemediatedCondition,
  1446  						Status: corev1.ConditionFalse,
  1447  					},
  1448  				},
  1449  			},
  1450  		}
  1451  		healthyMachine := &clusterv1.Machine{
  1452  			ObjectMeta: metav1.ObjectMeta{
  1453  				Name:      "healthy-machine",
  1454  				Namespace: "default",
  1455  			},
  1456  		}
  1457  
  1458  		machines := []*clusterv1.Machine{unhealthyMachine, healthyMachine}
  1459  		fakeClient := fake.NewClientBuilder().WithObjects(controlPlaneUpgrading, unhealthyMachine, healthyMachine).WithStatusSubresource(&clusterv1.Machine{}).Build()
  1460  		r := &Reconciler{
  1461  			Client:                    fakeClient,
  1462  			UnstructuredCachingClient: fakeClient,
  1463  		}
  1464  		_, err := r.reconcileUnhealthyMachines(ctx, cluster, machineSet, machines)
  1465  		g.Expect(err).ToNot(HaveOccurred())
  1466  
  1467  		// Verify the unhealthy machine has the updated condition.
  1468  		condition := clusterv1.MachineOwnerRemediatedCondition
  1469  		m := &clusterv1.Machine{}
  1470  		g.Expect(r.Client.Get(ctx, client.ObjectKeyFromObject(unhealthyMachine), m)).To(Succeed())
  1471  		g.Expect(conditions.Has(m, condition)).
  1472  			To(BeTrue(), "Machine should have the %s condition set", condition)
  1473  		machineOwnerRemediatedCondition := conditions.Get(m, condition)
  1474  		g.Expect(machineOwnerRemediatedCondition.Status).
  1475  			To(Equal(corev1.ConditionFalse), "%s condition status should be false", condition)
  1476  		g.Expect(machineOwnerRemediatedCondition.Reason).
  1477  			To(Equal(clusterv1.WaitingForRemediationReason), "%s condition should have reason %s", condition, clusterv1.WaitingForRemediationReason)
  1478  
  1479  		// Verify the healthy machine continues to not have the MachineOwnerRemediated condition.
  1480  		m = &clusterv1.Machine{}
  1481  		g.Expect(r.Client.Get(ctx, client.ObjectKeyFromObject(healthyMachine), m)).To(Succeed())
  1482  		g.Expect(conditions.Has(m, condition)).
  1483  			To(BeFalse(), "Machine should not have the %s condition set", condition)
  1484  	})
  1485  }
  1486  
  1487  func TestMachineSetReconciler_syncReplicas(t *testing.T) {
  1488  	t.Run("should hold off on creating new machines when preflight checks do not pass", func(t *testing.T) {
  1489  		defer utilfeature.SetFeatureGateDuringTest(t, feature.Gates, feature.MachineSetPreflightChecks, true)()
  1490  
  1491  		g := NewWithT(t)
  1492  
  1493  		// An upgrading control plane should cause the preflight checks to not pass.
  1494  		controlPlaneUpgrading := builder.ControlPlane("default", "test-cp").
  1495  			WithVersion("v1.26.2").
  1496  			WithStatusFields(map[string]interface{}{
  1497  				"status.version": "v1.25.2",
  1498  			}).
  1499  			Build()
  1500  		cluster := &clusterv1.Cluster{
  1501  			ObjectMeta: metav1.ObjectMeta{
  1502  				Name:      "test-cluster",
  1503  				Namespace: "default",
  1504  			},
  1505  			Spec: clusterv1.ClusterSpec{
  1506  				ControlPlaneRef: contract.ObjToRef(controlPlaneUpgrading),
  1507  			},
  1508  		}
  1509  		machineSet := &clusterv1.MachineSet{
  1510  			ObjectMeta: metav1.ObjectMeta{
  1511  				Name:      "test-machineset",
  1512  				Namespace: "default",
  1513  			},
  1514  			Spec: clusterv1.MachineSetSpec{
  1515  				Replicas: ptr.To[int32](1),
  1516  			},
  1517  		}
  1518  
  1519  		fakeClient := fake.NewClientBuilder().WithObjects(controlPlaneUpgrading, machineSet).WithStatusSubresource(&clusterv1.MachineSet{}).Build()
  1520  		r := &Reconciler{
  1521  			Client:                    fakeClient,
  1522  			UnstructuredCachingClient: fakeClient,
  1523  		}
  1524  		result, err := r.syncReplicas(ctx, cluster, machineSet, nil)
  1525  		g.Expect(err).ToNot(HaveOccurred())
  1526  		g.Expect(result.IsZero()).To(BeFalse(), "syncReplicas should not return a 'zero' result")
  1527  
  1528  		// Verify the proper condition is set on the MachineSet.
  1529  		condition := clusterv1.MachinesCreatedCondition
  1530  		g.Expect(conditions.Has(machineSet, condition)).
  1531  			To(BeTrue(), "MachineSet should have the %s condition set", condition)
  1532  		machinesCreatedCondition := conditions.Get(machineSet, condition)
  1533  		g.Expect(machinesCreatedCondition.Status).
  1534  			To(Equal(corev1.ConditionFalse), "%s condition status should be %s", condition, corev1.ConditionFalse)
  1535  		g.Expect(machinesCreatedCondition.Reason).
  1536  			To(Equal(clusterv1.PreflightCheckFailedReason), "%s condition reason should be %s", condition, clusterv1.PreflightCheckFailedReason)
  1537  
  1538  		// Verify no new Machines are created.
  1539  		machineList := &clusterv1.MachineList{}
  1540  		g.Expect(r.Client.List(ctx, machineList)).To(Succeed())
  1541  		g.Expect(machineList.Items).To(BeEmpty(), "There should not be any machines")
  1542  	})
  1543  }
  1544  
  1545  func TestComputeDesiredMachine(t *testing.T) {
  1546  	duration5s := &metav1.Duration{Duration: 5 * time.Second}
  1547  	duration10s := &metav1.Duration{Duration: 10 * time.Second}
  1548  
  1549  	infraRef := corev1.ObjectReference{
  1550  		Kind:       "GenericInfrastructureMachineTemplate",
  1551  		Name:       "infra-template-1",
  1552  		APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1",
  1553  	}
  1554  	bootstrapRef := corev1.ObjectReference{
  1555  		Kind:       "GenericBootstrapConfigTemplate",
  1556  		Name:       "bootstrap-template-1",
  1557  		APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1",
  1558  	}
  1559  
  1560  	ms := &clusterv1.MachineSet{
  1561  		ObjectMeta: metav1.ObjectMeta{
  1562  			Namespace: "default",
  1563  			Name:      "ms1",
  1564  			Labels: map[string]string{
  1565  				clusterv1.MachineDeploymentNameLabel: "md1",
  1566  			},
  1567  		},
  1568  		Spec: clusterv1.MachineSetSpec{
  1569  			ClusterName:     "test-cluster",
  1570  			Replicas:        ptr.To[int32](3),
  1571  			MinReadySeconds: 10,
  1572  			Selector: metav1.LabelSelector{
  1573  				MatchLabels: map[string]string{"k1": "v1"},
  1574  			},
  1575  			Template: clusterv1.MachineTemplateSpec{
  1576  				ObjectMeta: clusterv1.ObjectMeta{
  1577  					Labels:      map[string]string{"machine-label1": "machine-value1"},
  1578  					Annotations: map[string]string{"machine-annotation1": "machine-value1"},
  1579  				},
  1580  				Spec: clusterv1.MachineSpec{
  1581  					Version:           ptr.To("v1.25.3"),
  1582  					InfrastructureRef: infraRef,
  1583  					Bootstrap: clusterv1.Bootstrap{
  1584  						ConfigRef: &bootstrapRef,
  1585  					},
  1586  					NodeDrainTimeout:        duration10s,
  1587  					NodeVolumeDetachTimeout: duration10s,
  1588  					NodeDeletionTimeout:     duration10s,
  1589  				},
  1590  			},
  1591  		},
  1592  	}
  1593  
  1594  	skeletonMachine := &clusterv1.Machine{
  1595  		ObjectMeta: metav1.ObjectMeta{
  1596  			Namespace: "default",
  1597  			Labels: map[string]string{
  1598  				"machine-label1":                     "machine-value1",
  1599  				clusterv1.MachineSetNameLabel:        "ms1",
  1600  				clusterv1.MachineDeploymentNameLabel: "md1",
  1601  			},
  1602  			Annotations: map[string]string{"machine-annotation1": "machine-value1"},
  1603  			Finalizers:  []string{clusterv1.MachineFinalizer},
  1604  		},
  1605  		Spec: clusterv1.MachineSpec{
  1606  			ClusterName:             "test-cluster",
  1607  			Version:                 ptr.To("v1.25.3"),
  1608  			NodeDrainTimeout:        duration10s,
  1609  			NodeVolumeDetachTimeout: duration10s,
  1610  			NodeDeletionTimeout:     duration10s,
  1611  		},
  1612  	}
  1613  
  1614  	// Creating a new Machine
  1615  	expectedNewMachine := skeletonMachine.DeepCopy()
  1616  
  1617  	// Updating an existing Machine
  1618  	existingMachine := skeletonMachine.DeepCopy()
  1619  	existingMachine.Name = "exiting-machine-1"
  1620  	existingMachine.UID = "abc-123-existing-machine-1"
  1621  	existingMachine.Labels = nil
  1622  	existingMachine.Annotations = nil
  1623  	existingMachine.Spec.InfrastructureRef = corev1.ObjectReference{
  1624  		Kind:       "GenericInfrastructureMachine",
  1625  		Name:       "infra-machine-1",
  1626  		APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1",
  1627  	}
  1628  	existingMachine.Spec.Bootstrap.ConfigRef = &corev1.ObjectReference{
  1629  		Kind:       "GenericBootstrapConfig",
  1630  		Name:       "bootstrap-config-1",
  1631  		APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1",
  1632  	}
  1633  	existingMachine.Spec.NodeDrainTimeout = duration5s
  1634  	existingMachine.Spec.NodeDeletionTimeout = duration5s
  1635  	existingMachine.Spec.NodeVolumeDetachTimeout = duration5s
  1636  
  1637  	expectedUpdatedMachine := skeletonMachine.DeepCopy()
  1638  	expectedUpdatedMachine.Name = existingMachine.Name
  1639  	expectedUpdatedMachine.UID = existingMachine.UID
  1640  	expectedUpdatedMachine.Spec.InfrastructureRef = *existingMachine.Spec.InfrastructureRef.DeepCopy()
  1641  	expectedUpdatedMachine.Spec.Bootstrap.ConfigRef = existingMachine.Spec.Bootstrap.ConfigRef.DeepCopy()
  1642  
  1643  	tests := []struct {
  1644  		name            string
  1645  		existingMachine *clusterv1.Machine
  1646  		want            *clusterv1.Machine
  1647  	}{
  1648  		{
  1649  			name:            "creating a new Machine",
  1650  			existingMachine: nil,
  1651  			want:            expectedNewMachine,
  1652  		},
  1653  		{
  1654  			name:            "updating an existing Machine",
  1655  			existingMachine: existingMachine,
  1656  			want:            expectedUpdatedMachine,
  1657  		},
  1658  	}
  1659  
  1660  	for _, tt := range tests {
  1661  		t.Run(tt.name, func(t *testing.T) {
  1662  			g := NewWithT(t)
  1663  			got := (&Reconciler{}).computeDesiredMachine(ms, tt.existingMachine)
  1664  			assertMachine(g, got, tt.want)
  1665  		})
  1666  	}
  1667  }
  1668  
  1669  func assertMachine(g *WithT, actualMachine *clusterv1.Machine, expectedMachine *clusterv1.Machine) {
  1670  	// Check Name
  1671  	if expectedMachine.Name != "" {
  1672  		g.Expect(actualMachine.Name).Should(Equal(expectedMachine.Name))
  1673  	}
  1674  	// Check UID
  1675  	if expectedMachine.UID != "" {
  1676  		g.Expect(actualMachine.UID).Should(Equal(expectedMachine.UID))
  1677  	}
  1678  	// Check Namespace
  1679  	g.Expect(actualMachine.Namespace).Should(Equal(expectedMachine.Namespace))
  1680  	// Check Labels
  1681  	for k, v := range expectedMachine.Labels {
  1682  		g.Expect(actualMachine.Labels).Should(HaveKeyWithValue(k, v))
  1683  	}
  1684  	// Check Annotations
  1685  	for k, v := range expectedMachine.Annotations {
  1686  		g.Expect(actualMachine.Annotations).Should(HaveKeyWithValue(k, v))
  1687  	}
  1688  	// Check Spec
  1689  	g.Expect(actualMachine.Spec).Should(BeComparableTo(expectedMachine.Spec))
  1690  	// Check Finalizer
  1691  	if expectedMachine.Finalizers != nil {
  1692  		g.Expect(actualMachine.Finalizers).Should(Equal(expectedMachine.Finalizers))
  1693  	}
  1694  }