sigs.k8s.io/cluster-api@v1.7.1/internal/controllers/machine/machine_controller_phases_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 machine
    18  
    19  import (
    20  	"fmt"
    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/utils/ptr"
    29  	ctrl "sigs.k8s.io/controller-runtime"
    30  	"sigs.k8s.io/controller-runtime/pkg/client"
    31  	"sigs.k8s.io/controller-runtime/pkg/client/fake"
    32  
    33  	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
    34  	"sigs.k8s.io/cluster-api/internal/test/builder"
    35  	"sigs.k8s.io/cluster-api/util"
    36  	"sigs.k8s.io/cluster-api/util/conditions"
    37  	"sigs.k8s.io/cluster-api/util/kubeconfig"
    38  )
    39  
    40  func init() {
    41  	externalReadyWait = 1 * time.Second
    42  }
    43  
    44  func TestReconcileMachinePhases(t *testing.T) {
    45  	var defaultKubeconfigSecret *corev1.Secret
    46  	defaultCluster := &clusterv1.Cluster{
    47  		ObjectMeta: metav1.ObjectMeta{
    48  			Name:      "test-cluster",
    49  			Namespace: metav1.NamespaceDefault,
    50  		},
    51  	}
    52  
    53  	defaultMachine := clusterv1.Machine{
    54  		ObjectMeta: metav1.ObjectMeta{
    55  			Name:      "machine-test",
    56  			Namespace: metav1.NamespaceDefault,
    57  			Labels: map[string]string{
    58  				clusterv1.MachineControlPlaneLabel: "",
    59  			},
    60  		},
    61  		Spec: clusterv1.MachineSpec{
    62  			ClusterName: defaultCluster.Name,
    63  			Bootstrap: clusterv1.Bootstrap{
    64  				ConfigRef: &corev1.ObjectReference{
    65  					APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1",
    66  					Kind:       "GenericBootstrapConfig",
    67  					Name:       "bootstrap-config1",
    68  				},
    69  			},
    70  			InfrastructureRef: corev1.ObjectReference{
    71  				APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1",
    72  				Kind:       "GenericInfrastructureMachine",
    73  				Name:       "infra-config1",
    74  			},
    75  		},
    76  	}
    77  
    78  	defaultBootstrap := &unstructured.Unstructured{
    79  		Object: map[string]interface{}{
    80  			"kind":       "GenericBootstrapConfig",
    81  			"apiVersion": "bootstrap.cluster.x-k8s.io/v1beta1",
    82  			"metadata": map[string]interface{}{
    83  				"name":      "bootstrap-config1",
    84  				"namespace": metav1.NamespaceDefault,
    85  			},
    86  			"spec":   map[string]interface{}{},
    87  			"status": map[string]interface{}{},
    88  		},
    89  	}
    90  
    91  	defaultInfra := &unstructured.Unstructured{
    92  		Object: map[string]interface{}{
    93  			"kind":       "GenericInfrastructureMachine",
    94  			"apiVersion": "infrastructure.cluster.x-k8s.io/v1beta1",
    95  			"metadata": map[string]interface{}{
    96  				"name":      "infra-config1",
    97  				"namespace": metav1.NamespaceDefault,
    98  			},
    99  			"spec":   map[string]interface{}{},
   100  			"status": map[string]interface{}{},
   101  		},
   102  	}
   103  
   104  	t.Run("Should set OwnerReference and cluster name label on external objects", func(t *testing.T) {
   105  		g := NewWithT(t)
   106  
   107  		ns, err := env.CreateNamespace(ctx, "test-reconcile-machine-phases")
   108  		g.Expect(err).ToNot(HaveOccurred())
   109  		defer func() {
   110  			g.Expect(env.Cleanup(ctx, ns)).To(Succeed())
   111  		}()
   112  
   113  		cluster := defaultCluster.DeepCopy()
   114  		cluster.Namespace = ns.Name
   115  
   116  		bootstrapConfig := defaultBootstrap.DeepCopy()
   117  		bootstrapConfig.SetNamespace(ns.Name)
   118  		infraMachine := defaultInfra.DeepCopy()
   119  		infraMachine.SetNamespace(ns.Name)
   120  		machine := defaultMachine.DeepCopy()
   121  		machine.Namespace = ns.Name
   122  
   123  		g.Expect(env.Create(ctx, cluster)).To(Succeed())
   124  		defaultKubeconfigSecret = kubeconfig.GenerateSecret(cluster, kubeconfig.FromEnvTestConfig(env.Config, cluster))
   125  		g.Expect(env.Create(ctx, defaultKubeconfigSecret)).To(Succeed())
   126  
   127  		g.Expect(env.Create(ctx, bootstrapConfig)).To(Succeed())
   128  		g.Expect(env.Create(ctx, infraMachine)).To(Succeed())
   129  		g.Expect(env.Create(ctx, machine)).To(Succeed())
   130  
   131  		// Wait until BootstrapConfig has the ownerReference.
   132  		g.Eventually(func(g Gomega) bool {
   133  			if err := env.Get(ctx, client.ObjectKeyFromObject(bootstrapConfig), bootstrapConfig); err != nil {
   134  				return false
   135  			}
   136  			g.Expect(bootstrapConfig.GetOwnerReferences()).To(HaveLen(1))
   137  			g.Expect(bootstrapConfig.GetLabels()[clusterv1.ClusterNameLabel]).To(BeEquivalentTo("test-cluster"))
   138  			return true
   139  		}, 10*time.Second).Should(BeTrue())
   140  
   141  		// Wait until InfraMachine has the ownerReference.
   142  		g.Eventually(func(g Gomega) bool {
   143  			if err := env.Get(ctx, client.ObjectKeyFromObject(infraMachine), infraMachine); err != nil {
   144  				return false
   145  			}
   146  			g.Expect(infraMachine.GetOwnerReferences()).To(HaveLen(1))
   147  			g.Expect(infraMachine.GetLabels()[clusterv1.ClusterNameLabel]).To(BeEquivalentTo("test-cluster"))
   148  			return true
   149  		}, 10*time.Second).Should(BeTrue())
   150  	})
   151  
   152  	t.Run("Should set `Pending` with a new Machine", func(t *testing.T) {
   153  		g := NewWithT(t)
   154  
   155  		ns, err := env.CreateNamespace(ctx, "test-reconcile-machine-phases")
   156  		g.Expect(err).ToNot(HaveOccurred())
   157  		defer func() {
   158  			g.Expect(env.Cleanup(ctx, ns)).To(Succeed())
   159  		}()
   160  
   161  		cluster := defaultCluster.DeepCopy()
   162  		cluster.Namespace = ns.Name
   163  
   164  		bootstrapConfig := defaultBootstrap.DeepCopy()
   165  		bootstrapConfig.SetNamespace(ns.Name)
   166  		infraMachine := defaultInfra.DeepCopy()
   167  		infraMachine.SetNamespace(ns.Name)
   168  		machine := defaultMachine.DeepCopy()
   169  		machine.Namespace = ns.Name
   170  
   171  		g.Expect(env.Create(ctx, cluster)).To(Succeed())
   172  		defaultKubeconfigSecret = kubeconfig.GenerateSecret(cluster, kubeconfig.FromEnvTestConfig(env.Config, cluster))
   173  		g.Expect(env.Create(ctx, defaultKubeconfigSecret)).To(Succeed())
   174  
   175  		g.Expect(env.Create(ctx, bootstrapConfig)).To(Succeed())
   176  		g.Expect(env.Create(ctx, infraMachine)).To(Succeed())
   177  		g.Expect(env.Create(ctx, machine)).To(Succeed())
   178  
   179  		// Wait until Machine was reconciled.
   180  		g.Eventually(func(g Gomega) bool {
   181  			if err := env.Get(ctx, client.ObjectKeyFromObject(machine), machine); err != nil {
   182  				return false
   183  			}
   184  			g.Expect(machine.Status.GetTypedPhase()).To(Equal(clusterv1.MachinePhasePending))
   185  			// LastUpdated should be set as the phase changes
   186  			g.Expect(machine.Status.LastUpdated).NotTo(BeNil())
   187  			return true
   188  		}, 10*time.Second).Should(BeTrue())
   189  	})
   190  
   191  	t.Run("Should set `Provisioning` when bootstrap is ready", func(t *testing.T) {
   192  		g := NewWithT(t)
   193  
   194  		ns, err := env.CreateNamespace(ctx, "test-reconcile-machine-phases")
   195  		g.Expect(err).ToNot(HaveOccurred())
   196  		defer func() {
   197  			g.Expect(env.Cleanup(ctx, ns)).To(Succeed())
   198  		}()
   199  
   200  		cluster := defaultCluster.DeepCopy()
   201  		cluster.Namespace = ns.Name
   202  
   203  		bootstrapConfig := defaultBootstrap.DeepCopy()
   204  		bootstrapConfig.SetNamespace(ns.Name)
   205  		infraMachine := defaultInfra.DeepCopy()
   206  		infraMachine.SetNamespace(ns.Name)
   207  		machine := defaultMachine.DeepCopy()
   208  		machine.Namespace = ns.Name
   209  
   210  		g.Expect(env.Create(ctx, cluster)).To(Succeed())
   211  		defaultKubeconfigSecret = kubeconfig.GenerateSecret(cluster, kubeconfig.FromEnvTestConfig(env.Config, cluster))
   212  		g.Expect(env.Create(ctx, defaultKubeconfigSecret)).To(Succeed())
   213  
   214  		g.Expect(env.Create(ctx, bootstrapConfig)).To(Succeed())
   215  		g.Expect(env.Create(ctx, infraMachine)).To(Succeed())
   216  		// We have to subtract 2 seconds, because .status.lastUpdated does not contain miliseconds.
   217  		preUpdate := time.Now().Add(-2 * time.Second)
   218  		g.Expect(env.Create(ctx, machine)).To(Succeed())
   219  
   220  		// Set the LastUpdated to be able to verify it is updated when the phase changes
   221  		modifiedMachine := machine.DeepCopy()
   222  		g.Expect(env.Status().Patch(ctx, modifiedMachine, client.MergeFrom(machine))).To(Succeed())
   223  
   224  		// Set bootstrap ready.
   225  		modifiedBootstrapConfig := bootstrapConfig.DeepCopy()
   226  		g.Expect(unstructured.SetNestedField(modifiedBootstrapConfig.Object, true, "status", "ready")).To(Succeed())
   227  		g.Expect(unstructured.SetNestedField(modifiedBootstrapConfig.Object, "secret-data", "status", "dataSecretName")).To(Succeed())
   228  		g.Expect(env.Status().Patch(ctx, modifiedBootstrapConfig, client.MergeFrom(bootstrapConfig))).To(Succeed())
   229  
   230  		// Wait until Machine was reconciled.
   231  		g.Eventually(func(g Gomega) bool {
   232  			if err := env.Get(ctx, client.ObjectKeyFromObject(machine), machine); err != nil {
   233  				return false
   234  			}
   235  			g.Expect(machine.Status.GetTypedPhase()).To(Equal(clusterv1.MachinePhaseProvisioning))
   236  			// Verify that the LastUpdated timestamp was updated
   237  			g.Expect(machine.Status.LastUpdated).NotTo(BeNil())
   238  			g.Expect(machine.Status.LastUpdated.After(preUpdate)).To(BeTrue())
   239  			return true
   240  		}, 10*time.Second).Should(BeTrue())
   241  	})
   242  
   243  	t.Run("Should set `Running` when bootstrap and infra is ready", func(t *testing.T) {
   244  		g := NewWithT(t)
   245  
   246  		ns, err := env.CreateNamespace(ctx, "test-reconcile-machine-phases")
   247  		g.Expect(err).ToNot(HaveOccurred())
   248  		defer func() {
   249  			g.Expect(env.Cleanup(ctx, ns)).To(Succeed())
   250  		}()
   251  
   252  		nodeProviderID := fmt.Sprintf("test://%s", util.RandomString(6))
   253  
   254  		cluster := defaultCluster.DeepCopy()
   255  		cluster.Namespace = ns.Name
   256  
   257  		bootstrapConfig := defaultBootstrap.DeepCopy()
   258  		bootstrapConfig.SetNamespace(ns.Name)
   259  		infraMachine := defaultInfra.DeepCopy()
   260  		infraMachine.SetNamespace(ns.Name)
   261  		g.Expect(unstructured.SetNestedField(infraMachine.Object, nodeProviderID, "spec", "providerID")).To(Succeed())
   262  		g.Expect(unstructured.SetNestedField(infraMachine.Object, "us-east-2a", "spec", "failureDomain")).To(Succeed())
   263  		machine := defaultMachine.DeepCopy()
   264  		machine.Namespace = ns.Name
   265  
   266  		// Create Node.
   267  		node := &corev1.Node{
   268  			ObjectMeta: metav1.ObjectMeta{
   269  				GenerateName: "machine-test-node-",
   270  			},
   271  			Spec: corev1.NodeSpec{ProviderID: nodeProviderID},
   272  		}
   273  		g.Expect(env.Create(ctx, node)).To(Succeed())
   274  		defer func() {
   275  			g.Expect(env.Cleanup(ctx, node)).To(Succeed())
   276  		}()
   277  
   278  		g.Expect(env.Create(ctx, cluster)).To(Succeed())
   279  		defaultKubeconfigSecret = kubeconfig.GenerateSecret(cluster, kubeconfig.FromEnvTestConfig(env.Config, cluster))
   280  		g.Expect(env.Create(ctx, defaultKubeconfigSecret)).To(Succeed())
   281  
   282  		g.Expect(env.Create(ctx, bootstrapConfig)).To(Succeed())
   283  		g.Expect(env.Create(ctx, infraMachine)).To(Succeed())
   284  		// We have to subtract 2 seconds, because .status.lastUpdated does not contain miliseconds.
   285  		preUpdate := time.Now().Add(-2 * time.Second)
   286  		g.Expect(env.Create(ctx, machine)).To(Succeed())
   287  
   288  		modifiedMachine := machine.DeepCopy()
   289  		// Set NodeRef.
   290  		machine.Status.NodeRef = &corev1.ObjectReference{Kind: "Node", Name: node.Name}
   291  		g.Expect(env.Status().Patch(ctx, modifiedMachine, client.MergeFrom(machine))).To(Succeed())
   292  
   293  		// Set bootstrap ready.
   294  		modifiedBootstrapConfig := bootstrapConfig.DeepCopy()
   295  		g.Expect(unstructured.SetNestedField(modifiedBootstrapConfig.Object, true, "status", "ready")).To(Succeed())
   296  		g.Expect(unstructured.SetNestedField(modifiedBootstrapConfig.Object, "secret-data", "status", "dataSecretName")).To(Succeed())
   297  		g.Expect(env.Status().Patch(ctx, modifiedBootstrapConfig, client.MergeFrom(bootstrapConfig))).To(Succeed())
   298  
   299  		// Set infra ready.
   300  		modifiedInfraMachine := infraMachine.DeepCopy()
   301  		g.Expect(unstructured.SetNestedField(modifiedInfraMachine.Object, true, "status", "ready")).To(Succeed())
   302  		g.Expect(unstructured.SetNestedField(modifiedInfraMachine.Object, []interface{}{
   303  			map[string]interface{}{
   304  				"type":    "InternalIP",
   305  				"address": "10.0.0.1",
   306  			},
   307  			map[string]interface{}{
   308  				"type":    "InternalIP",
   309  				"address": "10.0.0.2",
   310  			},
   311  		}, "status", "addresses")).To(Succeed())
   312  		g.Expect(env.Status().Patch(ctx, modifiedInfraMachine, client.MergeFrom(infraMachine))).To(Succeed())
   313  
   314  		// Wait until Machine was reconciled.
   315  		g.Eventually(func(g Gomega) bool {
   316  			if err := env.Get(ctx, client.ObjectKeyFromObject(machine), machine); err != nil {
   317  				return false
   318  			}
   319  			g.Expect(machine.Status.Addresses).To(HaveLen(2))
   320  			g.Expect(*machine.Spec.FailureDomain).To(Equal("us-east-2a"))
   321  			g.Expect(machine.Status.GetTypedPhase()).To(Equal(clusterv1.MachinePhaseRunning))
   322  			// Verify that the LastUpdated timestamp was updated
   323  			g.Expect(machine.Status.LastUpdated).NotTo(BeNil())
   324  			g.Expect(machine.Status.LastUpdated.After(preUpdate)).To(BeTrue())
   325  			return true
   326  		}, 10*time.Second).Should(BeTrue())
   327  	})
   328  
   329  	t.Run("Should set `Running` when bootstrap and infra is ready with no Status.Addresses", func(t *testing.T) {
   330  		g := NewWithT(t)
   331  
   332  		ns, err := env.CreateNamespace(ctx, "test-reconcile-machine-phases")
   333  		g.Expect(err).ToNot(HaveOccurred())
   334  		defer func() {
   335  			g.Expect(env.Cleanup(ctx, ns)).To(Succeed())
   336  		}()
   337  
   338  		nodeProviderID := fmt.Sprintf("test://%s", util.RandomString(6))
   339  
   340  		cluster := defaultCluster.DeepCopy()
   341  		cluster.Namespace = ns.Name
   342  
   343  		bootstrapConfig := defaultBootstrap.DeepCopy()
   344  		bootstrapConfig.SetNamespace(ns.Name)
   345  		infraMachine := defaultInfra.DeepCopy()
   346  		infraMachine.SetNamespace(ns.Name)
   347  		g.Expect(unstructured.SetNestedField(infraMachine.Object, nodeProviderID, "spec", "providerID")).To(Succeed())
   348  		machine := defaultMachine.DeepCopy()
   349  		machine.Namespace = ns.Name
   350  
   351  		// Create Node.
   352  		node := &corev1.Node{
   353  			ObjectMeta: metav1.ObjectMeta{
   354  				GenerateName: "machine-test-node-",
   355  			},
   356  			Spec: corev1.NodeSpec{ProviderID: nodeProviderID},
   357  		}
   358  		g.Expect(env.Create(ctx, node)).To(Succeed())
   359  		defer func() {
   360  			g.Expect(env.Cleanup(ctx, node)).To(Succeed())
   361  		}()
   362  
   363  		g.Expect(env.Create(ctx, cluster)).To(Succeed())
   364  		defaultKubeconfigSecret = kubeconfig.GenerateSecret(cluster, kubeconfig.FromEnvTestConfig(env.Config, cluster))
   365  		g.Expect(env.Create(ctx, defaultKubeconfigSecret)).To(Succeed())
   366  
   367  		g.Expect(env.Create(ctx, bootstrapConfig)).To(Succeed())
   368  		g.Expect(env.Create(ctx, infraMachine)).To(Succeed())
   369  		// We have to subtract 2 seconds, because .status.lastUpdated does not contain miliseconds.
   370  		preUpdate := time.Now().Add(-2 * time.Second)
   371  		g.Expect(env.Create(ctx, machine)).To(Succeed())
   372  
   373  		modifiedMachine := machine.DeepCopy()
   374  		// Set NodeRef.
   375  		machine.Status.NodeRef = &corev1.ObjectReference{Kind: "Node", Name: node.Name}
   376  		g.Expect(env.Status().Patch(ctx, modifiedMachine, client.MergeFrom(machine))).To(Succeed())
   377  
   378  		// Set bootstrap ready.
   379  		modifiedBootstrapConfig := bootstrapConfig.DeepCopy()
   380  		g.Expect(unstructured.SetNestedField(modifiedBootstrapConfig.Object, true, "status", "ready")).To(Succeed())
   381  		g.Expect(unstructured.SetNestedField(modifiedBootstrapConfig.Object, "secret-data", "status", "dataSecretName")).To(Succeed())
   382  		g.Expect(env.Status().Patch(ctx, modifiedBootstrapConfig, client.MergeFrom(bootstrapConfig))).To(Succeed())
   383  
   384  		// Set infra ready.
   385  		modifiedInfraMachine := infraMachine.DeepCopy()
   386  		g.Expect(unstructured.SetNestedField(modifiedInfraMachine.Object, true, "status", "ready")).To(Succeed())
   387  		g.Expect(env.Status().Patch(ctx, modifiedInfraMachine, client.MergeFrom(infraMachine))).To(Succeed())
   388  
   389  		// Wait until Machine was reconciled.
   390  		g.Eventually(func(g Gomega) bool {
   391  			if err := env.Get(ctx, client.ObjectKeyFromObject(machine), machine); err != nil {
   392  				return false
   393  			}
   394  			g.Expect(machine.Status.GetTypedPhase()).To(Equal(clusterv1.MachinePhaseRunning))
   395  			g.Expect(machine.Status.Addresses).To(BeEmpty())
   396  			// Verify that the LastUpdated timestamp was updated
   397  			g.Expect(machine.Status.LastUpdated).NotTo(BeNil())
   398  			g.Expect(machine.Status.LastUpdated.After(preUpdate)).To(BeTrue())
   399  			return true
   400  		}, 10*time.Second).Should(BeTrue())
   401  	})
   402  
   403  	t.Run("Should set `Running` when bootstrap, infra, and NodeRef is ready", func(t *testing.T) {
   404  		g := NewWithT(t)
   405  
   406  		ns, err := env.CreateNamespace(ctx, "test-reconcile-machine-phases")
   407  		g.Expect(err).ToNot(HaveOccurred())
   408  		defer func() {
   409  			g.Expect(env.Cleanup(ctx, ns)).To(Succeed())
   410  		}()
   411  
   412  		nodeProviderID := fmt.Sprintf("test://%s", util.RandomString(6))
   413  
   414  		cluster := defaultCluster.DeepCopy()
   415  		cluster.Namespace = ns.Name
   416  
   417  		bootstrapConfig := defaultBootstrap.DeepCopy()
   418  		bootstrapConfig.SetNamespace(ns.Name)
   419  		infraMachine := defaultInfra.DeepCopy()
   420  		infraMachine.SetNamespace(ns.Name)
   421  		g.Expect(unstructured.SetNestedField(infraMachine.Object, nodeProviderID, "spec", "providerID")).To(Succeed())
   422  		machine := defaultMachine.DeepCopy()
   423  		machine.Namespace = ns.Name
   424  
   425  		// Create Node.
   426  		node := &corev1.Node{
   427  			ObjectMeta: metav1.ObjectMeta{
   428  				GenerateName: "machine-test-node-",
   429  			},
   430  			Spec: corev1.NodeSpec{ProviderID: nodeProviderID},
   431  		}
   432  		g.Expect(env.Create(ctx, node)).To(Succeed())
   433  		defer func() {
   434  			g.Expect(env.Cleanup(ctx, node)).To(Succeed())
   435  		}()
   436  
   437  		g.Expect(env.Create(ctx, cluster)).To(Succeed())
   438  		defaultKubeconfigSecret = kubeconfig.GenerateSecret(cluster, kubeconfig.FromEnvTestConfig(env.Config, cluster))
   439  		g.Expect(env.Create(ctx, defaultKubeconfigSecret)).To(Succeed())
   440  
   441  		g.Expect(env.Create(ctx, bootstrapConfig)).To(Succeed())
   442  		g.Expect(env.Create(ctx, infraMachine)).To(Succeed())
   443  		// We have to subtract 2 seconds, because .status.lastUpdated does not contain miliseconds.
   444  		preUpdate := time.Now().Add(-2 * time.Second)
   445  		g.Expect(env.Create(ctx, machine)).To(Succeed())
   446  
   447  		modifiedMachine := machine.DeepCopy()
   448  		// Set NodeRef.
   449  		machine.Status.NodeRef = &corev1.ObjectReference{Kind: "Node", Name: node.Name}
   450  		g.Expect(env.Status().Patch(ctx, modifiedMachine, client.MergeFrom(machine))).To(Succeed())
   451  
   452  		// Set bootstrap ready.
   453  		modifiedBootstrapConfig := bootstrapConfig.DeepCopy()
   454  		g.Expect(unstructured.SetNestedField(modifiedBootstrapConfig.Object, true, "status", "ready")).To(Succeed())
   455  		g.Expect(unstructured.SetNestedField(modifiedBootstrapConfig.Object, "secret-data", "status", "dataSecretName")).To(Succeed())
   456  		g.Expect(env.Status().Patch(ctx, modifiedBootstrapConfig, client.MergeFrom(bootstrapConfig))).To(Succeed())
   457  
   458  		// Set infra ready.
   459  		modifiedInfraMachine := infraMachine.DeepCopy()
   460  		g.Expect(unstructured.SetNestedField(modifiedInfraMachine.Object, true, "status", "ready")).To(Succeed())
   461  		g.Expect(env.Status().Patch(ctx, modifiedInfraMachine, client.MergeFrom(infraMachine))).To(Succeed())
   462  
   463  		// Wait until Machine was reconciled.
   464  		g.Eventually(func(g Gomega) bool {
   465  			if err := env.Get(ctx, client.ObjectKeyFromObject(machine), machine); err != nil {
   466  				return false
   467  			}
   468  			g.Expect(machine.Status.GetTypedPhase()).To(Equal(clusterv1.MachinePhaseRunning))
   469  			// Verify that the LastUpdated timestamp was updated
   470  			g.Expect(machine.Status.LastUpdated).NotTo(BeNil())
   471  			g.Expect(machine.Status.LastUpdated.After(preUpdate)).To(BeTrue())
   472  			return true
   473  		}, 10*time.Second).Should(BeTrue())
   474  	})
   475  
   476  	t.Run("Should set `Provisioned` when there is a ProviderID and there is no Node", func(t *testing.T) {
   477  		g := NewWithT(t)
   478  
   479  		ns, err := env.CreateNamespace(ctx, "test-reconcile-machine-phases")
   480  		g.Expect(err).ToNot(HaveOccurred())
   481  		defer func() {
   482  			g.Expect(env.Cleanup(ctx, ns)).To(Succeed())
   483  		}()
   484  
   485  		nodeProviderID := fmt.Sprintf("test://%s", util.RandomString(6))
   486  
   487  		cluster := defaultCluster.DeepCopy()
   488  		cluster.Namespace = ns.Name
   489  
   490  		bootstrapConfig := defaultBootstrap.DeepCopy()
   491  		bootstrapConfig.SetNamespace(ns.Name)
   492  		infraMachine := defaultInfra.DeepCopy()
   493  		infraMachine.SetNamespace(ns.Name)
   494  		g.Expect(unstructured.SetNestedField(infraMachine.Object, nodeProviderID, "spec", "providerID")).To(Succeed())
   495  		machine := defaultMachine.DeepCopy()
   496  		machine.Namespace = ns.Name
   497  		// Set Machine ProviderID.
   498  		machine.Spec.ProviderID = ptr.To(nodeProviderID)
   499  
   500  		g.Expect(env.Create(ctx, cluster)).To(Succeed())
   501  		defaultKubeconfigSecret = kubeconfig.GenerateSecret(cluster, kubeconfig.FromEnvTestConfig(env.Config, cluster))
   502  		g.Expect(env.Create(ctx, defaultKubeconfigSecret)).To(Succeed())
   503  
   504  		g.Expect(env.Create(ctx, bootstrapConfig)).To(Succeed())
   505  		g.Expect(env.Create(ctx, infraMachine)).To(Succeed())
   506  		// We have to subtract 2 seconds, because .status.lastUpdated does not contain miliseconds.
   507  		preUpdate := time.Now().Add(-2 * time.Second)
   508  		g.Expect(env.Create(ctx, machine)).To(Succeed())
   509  
   510  		// Set bootstrap ready.
   511  		modifiedBootstrapConfig := bootstrapConfig.DeepCopy()
   512  		g.Expect(unstructured.SetNestedField(modifiedBootstrapConfig.Object, true, "status", "ready")).To(Succeed())
   513  		g.Expect(unstructured.SetNestedField(modifiedBootstrapConfig.Object, "secret-data", "status", "dataSecretName")).To(Succeed())
   514  		g.Expect(env.Status().Patch(ctx, modifiedBootstrapConfig, client.MergeFrom(bootstrapConfig))).To(Succeed())
   515  
   516  		// Set infra ready.
   517  		modifiedInfraMachine := infraMachine.DeepCopy()
   518  		g.Expect(unstructured.SetNestedField(modifiedInfraMachine.Object, true, "status", "ready")).To(Succeed())
   519  		g.Expect(env.Status().Patch(ctx, modifiedInfraMachine, client.MergeFrom(infraMachine))).To(Succeed())
   520  
   521  		// Wait until Machine was reconciled.
   522  		g.Eventually(func(g Gomega) bool {
   523  			if err := env.Get(ctx, client.ObjectKeyFromObject(machine), machine); err != nil {
   524  				return false
   525  			}
   526  			g.Expect(machine.Status.GetTypedPhase()).To(Equal(clusterv1.MachinePhaseProvisioned))
   527  			// Verify that the LastUpdated timestamp was updated
   528  			g.Expect(machine.Status.LastUpdated).NotTo(BeNil())
   529  			g.Expect(machine.Status.LastUpdated.After(preUpdate)).To(BeTrue())
   530  			return true
   531  		}, 10*time.Second).Should(BeTrue())
   532  	})
   533  
   534  	t.Run("Should set `Deleting` when Machine is being deleted", func(t *testing.T) {
   535  		g := NewWithT(t)
   536  
   537  		ns, err := env.CreateNamespace(ctx, "test-reconcile-machine-phases")
   538  		g.Expect(err).ToNot(HaveOccurred())
   539  		defer func() {
   540  			g.Expect(env.Cleanup(ctx, ns)).To(Succeed())
   541  		}()
   542  
   543  		nodeProviderID := fmt.Sprintf("test://%s", util.RandomString(6))
   544  
   545  		cluster := defaultCluster.DeepCopy()
   546  		cluster.Namespace = ns.Name
   547  
   548  		bootstrapConfig := defaultBootstrap.DeepCopy()
   549  		bootstrapConfig.SetNamespace(ns.Name)
   550  		infraMachine := defaultInfra.DeepCopy()
   551  		infraMachine.SetNamespace(ns.Name)
   552  		g.Expect(unstructured.SetNestedField(infraMachine.Object, nodeProviderID, "spec", "providerID")).To(Succeed())
   553  		machine := defaultMachine.DeepCopy()
   554  		machine.Namespace = ns.Name
   555  
   556  		// Create Node.
   557  		node := &corev1.Node{
   558  			ObjectMeta: metav1.ObjectMeta{
   559  				GenerateName: "machine-test-node-",
   560  			},
   561  			Spec: corev1.NodeSpec{ProviderID: nodeProviderID},
   562  		}
   563  		g.Expect(env.Create(ctx, node)).To(Succeed())
   564  		defer func() {
   565  			g.Expect(env.Cleanup(ctx, node)).To(Succeed())
   566  		}()
   567  
   568  		g.Expect(env.Create(ctx, cluster)).To(Succeed())
   569  		defaultKubeconfigSecret = kubeconfig.GenerateSecret(cluster, kubeconfig.FromEnvTestConfig(env.Config, cluster))
   570  		g.Expect(env.Create(ctx, defaultKubeconfigSecret)).To(Succeed())
   571  
   572  		g.Expect(env.Create(ctx, bootstrapConfig)).To(Succeed())
   573  		g.Expect(env.Create(ctx, infraMachine)).To(Succeed())
   574  		// We have to subtract 2 seconds, because .status.lastUpdated does not contain miliseconds.
   575  		preUpdate := time.Now().Add(-2 * time.Second)
   576  		g.Expect(env.Create(ctx, machine)).To(Succeed())
   577  
   578  		// Set bootstrap ready.
   579  		modifiedBootstrapConfig := bootstrapConfig.DeepCopy()
   580  		g.Expect(unstructured.SetNestedField(modifiedBootstrapConfig.Object, true, "status", "ready")).To(Succeed())
   581  		g.Expect(unstructured.SetNestedField(modifiedBootstrapConfig.Object, "secret-data", "status", "dataSecretName")).To(Succeed())
   582  		g.Expect(env.Status().Patch(ctx, modifiedBootstrapConfig, client.MergeFrom(bootstrapConfig))).To(Succeed())
   583  
   584  		// Set infra ready.
   585  		modifiedInfraMachine := infraMachine.DeepCopy()
   586  		g.Expect(unstructured.SetNestedField(modifiedInfraMachine.Object, true, "status", "ready")).To(Succeed())
   587  		g.Expect(env.Status().Patch(ctx, modifiedInfraMachine, client.MergeFrom(infraMachine))).To(Succeed())
   588  
   589  		// Wait until the Machine has the Machine finalizer
   590  		g.Eventually(func() []string {
   591  			if err := env.Get(ctx, client.ObjectKeyFromObject(machine), machine); err != nil {
   592  				return nil
   593  			}
   594  			return machine.Finalizers
   595  		}, 10*time.Second).Should(HaveLen(1))
   596  
   597  		modifiedMachine := machine.DeepCopy()
   598  		// Set NodeRef.
   599  		machine.Status.NodeRef = &corev1.ObjectReference{Kind: "Node", Name: node.Name}
   600  		g.Expect(env.Status().Patch(ctx, modifiedMachine, client.MergeFrom(machine))).To(Succeed())
   601  
   602  		modifiedMachine = machine.DeepCopy()
   603  		// Set finalizer so we can check the Machine later, otherwise it would be already gone.
   604  		modifiedMachine.Finalizers = append(modifiedMachine.Finalizers, "test")
   605  		g.Expect(env.Patch(ctx, modifiedMachine, client.MergeFrom(machine))).To(Succeed())
   606  
   607  		// Delete Machine
   608  		g.Expect(env.Delete(ctx, machine)).To(Succeed())
   609  
   610  		// Wait until Machine was reconciled.
   611  		g.Eventually(func(g Gomega) bool {
   612  			if err := env.Get(ctx, client.ObjectKeyFromObject(machine), machine); err != nil {
   613  				return false
   614  			}
   615  			g.Expect(machine.Status.GetTypedPhase()).To(Equal(clusterv1.MachinePhaseDeleting))
   616  			nodeHealthyCondition := conditions.Get(machine, clusterv1.MachineNodeHealthyCondition)
   617  			g.Expect(nodeHealthyCondition.Status).To(Equal(corev1.ConditionFalse))
   618  			g.Expect(nodeHealthyCondition.Reason).To(Equal(clusterv1.DeletingReason))
   619  			// Verify that the LastUpdated timestamp was updated
   620  			g.Expect(machine.Status.LastUpdated).NotTo(BeNil())
   621  			g.Expect(machine.Status.LastUpdated.After(preUpdate)).To(BeTrue())
   622  			return true
   623  		}, 10*time.Second).Should(BeTrue())
   624  	})
   625  }
   626  
   627  func TestReconcileBootstrap(t *testing.T) {
   628  	defaultMachine := clusterv1.Machine{
   629  		ObjectMeta: metav1.ObjectMeta{
   630  			Name:      "machine-test",
   631  			Namespace: metav1.NamespaceDefault,
   632  			Labels: map[string]string{
   633  				clusterv1.ClusterNameLabel: "test-cluster",
   634  			},
   635  		},
   636  		Spec: clusterv1.MachineSpec{
   637  			Bootstrap: clusterv1.Bootstrap{
   638  				ConfigRef: &corev1.ObjectReference{
   639  					APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1",
   640  					Kind:       "GenericBootstrapConfig",
   641  					Name:       "bootstrap-config1",
   642  				},
   643  			},
   644  		},
   645  	}
   646  
   647  	defaultCluster := &clusterv1.Cluster{
   648  		ObjectMeta: metav1.ObjectMeta{
   649  			Name:      "test-cluster",
   650  			Namespace: metav1.NamespaceDefault,
   651  		},
   652  	}
   653  
   654  	testCases := []struct {
   655  		name            string
   656  		bootstrapConfig map[string]interface{}
   657  		machine         *clusterv1.Machine
   658  		expectResult    ctrl.Result
   659  		expectError     bool
   660  		expected        func(g *WithT, m *clusterv1.Machine)
   661  	}{
   662  		{
   663  			name: "new machine, bootstrap config ready with data",
   664  			bootstrapConfig: map[string]interface{}{
   665  				"kind":       "GenericBootstrapConfig",
   666  				"apiVersion": "bootstrap.cluster.x-k8s.io/v1beta1",
   667  				"metadata": map[string]interface{}{
   668  					"name":      "bootstrap-config1",
   669  					"namespace": metav1.NamespaceDefault,
   670  				},
   671  				"spec": map[string]interface{}{},
   672  				"status": map[string]interface{}{
   673  					"ready":          true,
   674  					"dataSecretName": "secret-data",
   675  				},
   676  			},
   677  			expectResult: ctrl.Result{},
   678  			expectError:  false,
   679  			expected: func(g *WithT, m *clusterv1.Machine) {
   680  				g.Expect(m.Status.BootstrapReady).To(BeTrue())
   681  				g.Expect(m.Spec.Bootstrap.DataSecretName).NotTo(BeNil())
   682  				g.Expect(*m.Spec.Bootstrap.DataSecretName).To(ContainSubstring("secret-data"))
   683  			},
   684  		},
   685  		{
   686  			name: "new machine, bootstrap config ready with no data",
   687  			bootstrapConfig: map[string]interface{}{
   688  				"kind":       "GenericBootstrapConfig",
   689  				"apiVersion": "bootstrap.cluster.x-k8s.io/v1beta1",
   690  				"metadata": map[string]interface{}{
   691  					"name":      "bootstrap-config1",
   692  					"namespace": metav1.NamespaceDefault,
   693  				},
   694  				"spec": map[string]interface{}{},
   695  				"status": map[string]interface{}{
   696  					"ready": true,
   697  				},
   698  			},
   699  			expectResult: ctrl.Result{},
   700  			expectError:  true,
   701  			expected: func(g *WithT, m *clusterv1.Machine) {
   702  				g.Expect(m.Status.BootstrapReady).To(BeFalse())
   703  				g.Expect(m.Spec.Bootstrap.DataSecretName).To(BeNil())
   704  			},
   705  		},
   706  		{
   707  			name: "new machine, bootstrap config not ready",
   708  			bootstrapConfig: map[string]interface{}{
   709  				"kind":       "GenericBootstrapConfig",
   710  				"apiVersion": "bootstrap.cluster.x-k8s.io/v1beta1",
   711  				"metadata": map[string]interface{}{
   712  					"name":      "bootstrap-config1",
   713  					"namespace": metav1.NamespaceDefault,
   714  				},
   715  				"spec":   map[string]interface{}{},
   716  				"status": map[string]interface{}{},
   717  			},
   718  			expectResult: ctrl.Result{},
   719  			expectError:  false,
   720  			expected: func(g *WithT, m *clusterv1.Machine) {
   721  				g.Expect(m.Status.BootstrapReady).To(BeFalse())
   722  			},
   723  		},
   724  		{
   725  			name: "new machine, bootstrap config is not found",
   726  			bootstrapConfig: map[string]interface{}{
   727  				"kind":       "GenericBootstrapConfig",
   728  				"apiVersion": "bootstrap.cluster.x-k8s.io/v1beta1",
   729  				"metadata": map[string]interface{}{
   730  					"name":      "bootstrap-config1",
   731  					"namespace": "wrong-namespace",
   732  				},
   733  				"spec":   map[string]interface{}{},
   734  				"status": map[string]interface{}{},
   735  			},
   736  			expectResult: ctrl.Result{RequeueAfter: externalReadyWait},
   737  			expectError:  false,
   738  			expected: func(g *WithT, m *clusterv1.Machine) {
   739  				g.Expect(m.Status.BootstrapReady).To(BeFalse())
   740  			},
   741  		},
   742  		{
   743  			name: "new machine, no bootstrap config or data",
   744  			bootstrapConfig: map[string]interface{}{
   745  				"kind":       "GenericBootstrapConfig",
   746  				"apiVersion": "bootstrap.cluster.x-k8s.io/v1beta1",
   747  				"metadata": map[string]interface{}{
   748  					"name":      "bootstrap-config1",
   749  					"namespace": "wrong-namespace",
   750  				},
   751  				"spec":   map[string]interface{}{},
   752  				"status": map[string]interface{}{},
   753  			},
   754  			expectResult: ctrl.Result{RequeueAfter: externalReadyWait},
   755  			expectError:  false,
   756  		},
   757  		{
   758  			name: "existing machine, bootstrap data should not change",
   759  			bootstrapConfig: map[string]interface{}{
   760  				"kind":       "GenericBootstrapConfig",
   761  				"apiVersion": "bootstrap.cluster.x-k8s.io/v1beta1",
   762  				"metadata": map[string]interface{}{
   763  					"name":      "bootstrap-config1",
   764  					"namespace": metav1.NamespaceDefault,
   765  				},
   766  				"spec": map[string]interface{}{},
   767  				"status": map[string]interface{}{
   768  					"ready":          true,
   769  					"dataSecretName": "secret-data",
   770  				},
   771  			},
   772  			machine: &clusterv1.Machine{
   773  				ObjectMeta: metav1.ObjectMeta{
   774  					Name:      "bootstrap-test-existing",
   775  					Namespace: metav1.NamespaceDefault,
   776  				},
   777  				Spec: clusterv1.MachineSpec{
   778  					Bootstrap: clusterv1.Bootstrap{
   779  						ConfigRef: &corev1.ObjectReference{
   780  							APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1",
   781  							Kind:       "GenericBootstrapConfig",
   782  							Name:       "bootstrap-config1",
   783  						},
   784  						DataSecretName: ptr.To("secret-data"),
   785  					},
   786  				},
   787  				Status: clusterv1.MachineStatus{
   788  					BootstrapReady: true,
   789  				},
   790  			},
   791  			expectResult: ctrl.Result{},
   792  			expectError:  false,
   793  			expected: func(g *WithT, m *clusterv1.Machine) {
   794  				g.Expect(m.Status.BootstrapReady).To(BeTrue())
   795  				g.Expect(*m.Spec.Bootstrap.DataSecretName).To(BeEquivalentTo("secret-data"))
   796  			},
   797  		},
   798  		{
   799  			name: "existing machine, bootstrap provider is not ready, and ownerref updated",
   800  			bootstrapConfig: map[string]interface{}{
   801  				"kind":       "GenericBootstrapConfig",
   802  				"apiVersion": "bootstrap.cluster.x-k8s.io/v1beta1",
   803  				"metadata": map[string]interface{}{
   804  					"name":      "bootstrap-config1",
   805  					"namespace": metav1.NamespaceDefault,
   806  					"ownerReferences": []interface{}{
   807  						map[string]interface{}{
   808  							"apiVersion": clusterv1.GroupVersion.String(),
   809  							"kind":       "MachineSet",
   810  							"name":       "ms",
   811  							"uid":        "1",
   812  							"controller": true,
   813  						},
   814  					},
   815  				},
   816  				"spec": map[string]interface{}{},
   817  				"status": map[string]interface{}{
   818  					"ready": false,
   819  				},
   820  			},
   821  			machine: &clusterv1.Machine{
   822  				ObjectMeta: metav1.ObjectMeta{
   823  					Name:      "bootstrap-test-existing",
   824  					Namespace: metav1.NamespaceDefault,
   825  				},
   826  				Spec: clusterv1.MachineSpec{
   827  					Bootstrap: clusterv1.Bootstrap{
   828  						ConfigRef: &corev1.ObjectReference{
   829  							APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1",
   830  							Kind:       "GenericBootstrapConfig",
   831  							Name:       "bootstrap-config1",
   832  						},
   833  					},
   834  				},
   835  				Status: clusterv1.MachineStatus{
   836  					BootstrapReady: true,
   837  				},
   838  			},
   839  			expectResult: ctrl.Result{},
   840  			expectError:  false,
   841  			expected: func(g *WithT, m *clusterv1.Machine) {
   842  				g.Expect(m.GetOwnerReferences()).NotTo(ContainRefOfGroupKind("cluster.x-k8s.io", "MachineSet"))
   843  			},
   844  		},
   845  		{
   846  			name: "existing machine, machineset owner and version v1alpha2, and ownerref updated",
   847  			bootstrapConfig: map[string]interface{}{
   848  				"kind":       "GenericBootstrapConfig",
   849  				"apiVersion": "bootstrap.cluster.x-k8s.io/v1beta1",
   850  				"metadata": map[string]interface{}{
   851  					"name":      "bootstrap-config1",
   852  					"namespace": metav1.NamespaceDefault,
   853  					"ownerReferences": []interface{}{
   854  						map[string]interface{}{
   855  							"apiVersion": "cluster.x-k8s.io/v1alpha2",
   856  							"kind":       "MachineSet",
   857  							"name":       "ms",
   858  							"uid":        "1",
   859  							"controller": true,
   860  						},
   861  					},
   862  				},
   863  				"spec": map[string]interface{}{},
   864  				"status": map[string]interface{}{
   865  					"ready": true,
   866  				},
   867  			},
   868  			machine: &clusterv1.Machine{
   869  				ObjectMeta: metav1.ObjectMeta{
   870  					Name:      "bootstrap-test-existing",
   871  					Namespace: metav1.NamespaceDefault,
   872  				},
   873  				Spec: clusterv1.MachineSpec{
   874  					Bootstrap: clusterv1.Bootstrap{
   875  						ConfigRef: &corev1.ObjectReference{
   876  							APIVersion: "bootstrap.cluster.x-k8s.io/v1alpha2",
   877  							Kind:       "GenericBootstrapConfig",
   878  							Name:       "bootstrap-config1",
   879  						},
   880  					},
   881  				},
   882  				Status: clusterv1.MachineStatus{
   883  					BootstrapReady: true,
   884  				},
   885  			},
   886  			expectResult: ctrl.Result{},
   887  			expectError:  true,
   888  			expected: func(g *WithT, m *clusterv1.Machine) {
   889  				g.Expect(m.GetOwnerReferences()).NotTo(ContainRefOfGroupKind("cluster.x-k8s.io", "MachineSet"))
   890  			},
   891  		},
   892  	}
   893  
   894  	for _, tc := range testCases {
   895  		t.Run(tc.name, func(t *testing.T) {
   896  			g := NewWithT(t)
   897  
   898  			if tc.machine == nil {
   899  				tc.machine = defaultMachine.DeepCopy()
   900  			}
   901  
   902  			bootstrapConfig := &unstructured.Unstructured{Object: tc.bootstrapConfig}
   903  			c := fake.NewClientBuilder().
   904  				WithObjects(tc.machine,
   905  					builder.GenericBootstrapConfigCRD.DeepCopy(),
   906  					builder.GenericInfrastructureMachineCRD.DeepCopy(),
   907  					bootstrapConfig,
   908  				).Build()
   909  			r := &Reconciler{
   910  				Client:                    c,
   911  				UnstructuredCachingClient: c,
   912  			}
   913  
   914  			s := &scope{cluster: defaultCluster, machine: tc.machine}
   915  			res, err := r.reconcileBootstrap(ctx, s)
   916  			g.Expect(res).To(BeComparableTo(tc.expectResult))
   917  			if tc.expectError {
   918  				g.Expect(err).To(HaveOccurred())
   919  			} else {
   920  				g.Expect(err).ToNot(HaveOccurred())
   921  			}
   922  
   923  			if tc.expected != nil {
   924  				tc.expected(g, tc.machine)
   925  			}
   926  		})
   927  	}
   928  }
   929  
   930  func TestReconcileInfrastructure(t *testing.T) {
   931  	defaultMachine := clusterv1.Machine{
   932  		ObjectMeta: metav1.ObjectMeta{
   933  			Name:      "machine-test",
   934  			Namespace: metav1.NamespaceDefault,
   935  			Labels: map[string]string{
   936  				clusterv1.ClusterNameLabel: "test-cluster",
   937  			},
   938  		},
   939  		Spec: clusterv1.MachineSpec{
   940  			Bootstrap: clusterv1.Bootstrap{
   941  				ConfigRef: &corev1.ObjectReference{
   942  					APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1",
   943  					Kind:       "GenericBootstrapConfig",
   944  					Name:       "bootstrap-config1",
   945  				},
   946  			},
   947  			InfrastructureRef: corev1.ObjectReference{
   948  				APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1",
   949  				Kind:       "GenericInfrastructureMachine",
   950  				Name:       "infra-config1",
   951  			},
   952  		},
   953  	}
   954  
   955  	defaultCluster := &clusterv1.Cluster{
   956  		ObjectMeta: metav1.ObjectMeta{
   957  			Name:      "test-cluster",
   958  			Namespace: metav1.NamespaceDefault,
   959  		},
   960  	}
   961  
   962  	testCases := []struct {
   963  		name            string
   964  		bootstrapConfig map[string]interface{}
   965  		infraConfig     map[string]interface{}
   966  		machine         *clusterv1.Machine
   967  		expectResult    ctrl.Result
   968  		expectError     bool
   969  		expectChanged   bool
   970  		expected        func(g *WithT, m *clusterv1.Machine)
   971  	}{
   972  		{
   973  			name: "new machine, infrastructure config ready",
   974  			infraConfig: map[string]interface{}{
   975  				"kind":       "GenericInfrastructureMachine",
   976  				"apiVersion": "infrastructure.cluster.x-k8s.io/v1beta1",
   977  				"metadata": map[string]interface{}{
   978  					"name":      "infra-config1",
   979  					"namespace": metav1.NamespaceDefault,
   980  					"ownerReferences": []interface{}{
   981  						map[string]interface{}{
   982  							"apiVersion": clusterv1.GroupVersion.String(),
   983  							"kind":       "MachineSet",
   984  							"name":       "ms",
   985  							"uid":        "1",
   986  							"controller": true,
   987  						},
   988  					},
   989  				},
   990  				"spec": map[string]interface{}{
   991  					"providerID": "test://id-1",
   992  				},
   993  				"status": map[string]interface{}{
   994  					"ready": true,
   995  					"addresses": []interface{}{
   996  						map[string]interface{}{
   997  							"type":    "InternalIP",
   998  							"address": "10.0.0.1",
   999  						},
  1000  						map[string]interface{}{
  1001  							"type":    "InternalIP",
  1002  							"address": "10.0.0.2",
  1003  						},
  1004  					},
  1005  				},
  1006  			},
  1007  			expectResult:  ctrl.Result{},
  1008  			expectError:   false,
  1009  			expectChanged: true,
  1010  			expected: func(g *WithT, m *clusterv1.Machine) {
  1011  				g.Expect(m.Status.InfrastructureReady).To(BeTrue())
  1012  				g.Expect(m.GetOwnerReferences()).NotTo(ContainRefOfGroupKind("cluster.x-k8s.io", "MachineSet"))
  1013  			},
  1014  		},
  1015  		{
  1016  			name: "ready bootstrap, infra, and nodeRef, machine is running, infra object is deleted, expect failed",
  1017  			machine: &clusterv1.Machine{
  1018  				ObjectMeta: metav1.ObjectMeta{
  1019  					Name:      "machine-test",
  1020  					Namespace: metav1.NamespaceDefault,
  1021  				},
  1022  				Spec: clusterv1.MachineSpec{
  1023  					Bootstrap: clusterv1.Bootstrap{
  1024  						ConfigRef: &corev1.ObjectReference{
  1025  							APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1",
  1026  							Kind:       "GenericBootstrapConfig",
  1027  							Name:       "bootstrap-config1",
  1028  						},
  1029  					},
  1030  					InfrastructureRef: corev1.ObjectReference{
  1031  						APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1",
  1032  						Kind:       "GenericInfrastructureMachine",
  1033  						Name:       "infra-config1",
  1034  					},
  1035  				},
  1036  				Status: clusterv1.MachineStatus{
  1037  					BootstrapReady:      true,
  1038  					InfrastructureReady: true,
  1039  					NodeRef:             &corev1.ObjectReference{Kind: "Node", Name: "machine-test-node"},
  1040  				},
  1041  			},
  1042  			bootstrapConfig: map[string]interface{}{
  1043  				"kind":       "GenericBootstrapConfig",
  1044  				"apiVersion": "bootstrap.cluster.x-k8s.io/v1beta1",
  1045  				"metadata": map[string]interface{}{
  1046  					"name":      "bootstrap-config1",
  1047  					"namespace": metav1.NamespaceDefault,
  1048  				},
  1049  				"spec": map[string]interface{}{},
  1050  				"status": map[string]interface{}{
  1051  					"ready":          true,
  1052  					"dataSecretName": "secret-data",
  1053  				},
  1054  			},
  1055  			infraConfig: map[string]interface{}{
  1056  				"kind":       "GenericInfrastructureMachine",
  1057  				"apiVersion": "infrastructure.cluster.x-k8s.io/v1beta1",
  1058  				"metadata":   map[string]interface{}{},
  1059  			},
  1060  			expectResult: ctrl.Result{},
  1061  			expectError:  true,
  1062  			expected: func(g *WithT, m *clusterv1.Machine) {
  1063  				g.Expect(m.Status.InfrastructureReady).To(BeTrue())
  1064  				g.Expect(m.Status.FailureMessage).NotTo(BeNil())
  1065  				g.Expect(m.Status.FailureReason).NotTo(BeNil())
  1066  				g.Expect(m.Status.GetTypedPhase()).To(Equal(clusterv1.MachinePhaseFailed))
  1067  			},
  1068  		},
  1069  		{
  1070  			name: "infrastructure ref is paused",
  1071  			infraConfig: map[string]interface{}{
  1072  				"kind":       "GenericInfrastructureMachine",
  1073  				"apiVersion": "infrastructure.cluster.x-k8s.io/v1beta1",
  1074  				"metadata": map[string]interface{}{
  1075  					"name":      "infra-config1",
  1076  					"namespace": metav1.NamespaceDefault,
  1077  					"annotations": map[string]interface{}{
  1078  						"cluster.x-k8s.io/paused": "true",
  1079  					},
  1080  				},
  1081  				"spec": map[string]interface{}{
  1082  					"providerID": "test://id-1",
  1083  				},
  1084  				"status": map[string]interface{}{
  1085  					"ready": true,
  1086  					"addresses": []interface{}{
  1087  						map[string]interface{}{
  1088  							"type":    "InternalIP",
  1089  							"address": "10.0.0.1",
  1090  						},
  1091  						map[string]interface{}{
  1092  							"type":    "InternalIP",
  1093  							"address": "10.0.0.2",
  1094  						},
  1095  					},
  1096  				},
  1097  			},
  1098  			expectResult:  ctrl.Result{},
  1099  			expectError:   false,
  1100  			expectChanged: false,
  1101  			expected: func(g *WithT, m *clusterv1.Machine) {
  1102  				g.Expect(m.Status.InfrastructureReady).To(BeFalse())
  1103  			},
  1104  		},
  1105  	}
  1106  
  1107  	for _, tc := range testCases {
  1108  		t.Run(tc.name, func(t *testing.T) {
  1109  			g := NewWithT(t)
  1110  
  1111  			if tc.machine == nil {
  1112  				tc.machine = defaultMachine.DeepCopy()
  1113  			}
  1114  
  1115  			infraConfig := &unstructured.Unstructured{Object: tc.infraConfig}
  1116  			c := fake.NewClientBuilder().
  1117  				WithObjects(tc.machine,
  1118  					builder.GenericBootstrapConfigCRD.DeepCopy(),
  1119  					builder.GenericInfrastructureMachineCRD.DeepCopy(),
  1120  					infraConfig,
  1121  				).Build()
  1122  			r := &Reconciler{
  1123  				Client:                    c,
  1124  				UnstructuredCachingClient: c,
  1125  			}
  1126  			s := &scope{cluster: defaultCluster, machine: tc.machine}
  1127  			result, err := r.reconcileInfrastructure(ctx, s)
  1128  			r.reconcilePhase(ctx, tc.machine)
  1129  			g.Expect(result).To(BeComparableTo(tc.expectResult))
  1130  			if tc.expectError {
  1131  				g.Expect(err).To(HaveOccurred())
  1132  			} else {
  1133  				g.Expect(err).ToNot(HaveOccurred())
  1134  			}
  1135  
  1136  			if tc.expected != nil {
  1137  				tc.expected(g, tc.machine)
  1138  			}
  1139  		})
  1140  	}
  1141  }
  1142  
  1143  func TestReconcileCertificateExpiry(t *testing.T) {
  1144  	fakeTimeString := "2020-01-01T00:00:00Z"
  1145  	fakeTime, _ := time.Parse(time.RFC3339, fakeTimeString)
  1146  	fakeMetaTime := &metav1.Time{Time: fakeTime}
  1147  
  1148  	fakeTimeString2 := "2020-02-02T00:00:00Z"
  1149  	fakeTime2, _ := time.Parse(time.RFC3339, fakeTimeString2)
  1150  	fakeMetaTime2 := &metav1.Time{Time: fakeTime2}
  1151  
  1152  	bootstrapConfigWithExpiry := &unstructured.Unstructured{
  1153  		Object: map[string]interface{}{
  1154  			"kind":       "GenericBootstrapConfig",
  1155  			"apiVersion": "bootstrap.cluster.x-k8s.io/v1beta1",
  1156  			"metadata": map[string]interface{}{
  1157  				"name":      "bootstrap-config-with-expiry",
  1158  				"namespace": metav1.NamespaceDefault,
  1159  				"annotations": map[string]interface{}{
  1160  					clusterv1.MachineCertificatesExpiryDateAnnotation: fakeTimeString,
  1161  				},
  1162  			},
  1163  			"spec": map[string]interface{}{},
  1164  			"status": map[string]interface{}{
  1165  				"ready":          true,
  1166  				"dataSecretName": "secret-data",
  1167  			},
  1168  		},
  1169  	}
  1170  
  1171  	bootstrapConfigWithoutExpiry := &unstructured.Unstructured{
  1172  		Object: map[string]interface{}{
  1173  			"kind":       "GenericBootstrapConfig",
  1174  			"apiVersion": "bootstrap.cluster.x-k8s.io/v1beta1",
  1175  			"metadata": map[string]interface{}{
  1176  				"name":      "bootstrap-config-without-expiry",
  1177  				"namespace": metav1.NamespaceDefault,
  1178  			},
  1179  			"spec": map[string]interface{}{},
  1180  			"status": map[string]interface{}{
  1181  				"ready":          true,
  1182  				"dataSecretName": "secret-data",
  1183  			},
  1184  		},
  1185  	}
  1186  
  1187  	tests := []struct {
  1188  		name            string
  1189  		machine         *clusterv1.Machine
  1190  		bootstrapConfig *unstructured.Unstructured
  1191  		expected        func(g *WithT, m *clusterv1.Machine)
  1192  	}{
  1193  		{
  1194  			name: "worker machine with certificate expiry annotation should not update expiry date",
  1195  			machine: &clusterv1.Machine{
  1196  				ObjectMeta: metav1.ObjectMeta{
  1197  					Name:      "bootstrap-test-existing",
  1198  					Namespace: metav1.NamespaceDefault,
  1199  					Annotations: map[string]string{
  1200  						clusterv1.MachineCertificatesExpiryDateAnnotation: fakeTimeString,
  1201  					},
  1202  				},
  1203  				Spec: clusterv1.MachineSpec{
  1204  					Bootstrap: clusterv1.Bootstrap{},
  1205  				},
  1206  			},
  1207  			expected: func(g *WithT, m *clusterv1.Machine) {
  1208  				g.Expect(m.Status.CertificatesExpiryDate).To(BeNil())
  1209  			},
  1210  		},
  1211  		{
  1212  			name: "control plane machine with no bootstrap config and no certificate expiry annotation should not set expiry date",
  1213  			machine: &clusterv1.Machine{
  1214  				ObjectMeta: metav1.ObjectMeta{
  1215  					Name:      "bootstrap-test-existing",
  1216  					Namespace: metav1.NamespaceDefault,
  1217  					Labels: map[string]string{
  1218  						clusterv1.MachineControlPlaneLabel: "",
  1219  					},
  1220  				},
  1221  				Spec: clusterv1.MachineSpec{
  1222  					Bootstrap: clusterv1.Bootstrap{},
  1223  				},
  1224  			},
  1225  			expected: func(g *WithT, m *clusterv1.Machine) {
  1226  				g.Expect(m.Status.CertificatesExpiryDate).To(BeNil())
  1227  			},
  1228  		},
  1229  		{
  1230  			name: "control plane machine with bootstrap config and no certificate expiry annotation should not set expiry date",
  1231  			machine: &clusterv1.Machine{
  1232  				ObjectMeta: metav1.ObjectMeta{
  1233  					Name:      "bootstrap-test-existing",
  1234  					Namespace: metav1.NamespaceDefault,
  1235  					Labels: map[string]string{
  1236  						clusterv1.MachineControlPlaneLabel: "",
  1237  					},
  1238  				},
  1239  				Spec: clusterv1.MachineSpec{
  1240  					Bootstrap: clusterv1.Bootstrap{
  1241  						ConfigRef: &corev1.ObjectReference{
  1242  							APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1",
  1243  							Kind:       "GenericBootstrapConfig",
  1244  							Name:       "bootstrap-config-without-expiry",
  1245  						},
  1246  					},
  1247  				},
  1248  			},
  1249  			bootstrapConfig: bootstrapConfigWithoutExpiry,
  1250  			expected: func(g *WithT, m *clusterv1.Machine) {
  1251  				g.Expect(m.Status.CertificatesExpiryDate).To(BeNil())
  1252  			},
  1253  		},
  1254  		{
  1255  			name: "control plane machine with certificate expiry annotation in bootstrap config should set expiry date",
  1256  			machine: &clusterv1.Machine{
  1257  				ObjectMeta: metav1.ObjectMeta{
  1258  					Name:      "bootstrap-test-existing",
  1259  					Namespace: metav1.NamespaceDefault,
  1260  					Labels: map[string]string{
  1261  						clusterv1.MachineControlPlaneLabel: "",
  1262  					},
  1263  				},
  1264  				Spec: clusterv1.MachineSpec{
  1265  					Bootstrap: clusterv1.Bootstrap{
  1266  						ConfigRef: &corev1.ObjectReference{
  1267  							APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1",
  1268  							Kind:       "GenericBootstrapConfig",
  1269  							Name:       "bootstrap-config-with-expiry",
  1270  						},
  1271  					},
  1272  				},
  1273  			},
  1274  			bootstrapConfig: bootstrapConfigWithExpiry,
  1275  			expected: func(g *WithT, m *clusterv1.Machine) {
  1276  				g.Expect(m.Status.CertificatesExpiryDate).To(Equal(fakeMetaTime))
  1277  			},
  1278  		},
  1279  		{
  1280  			name: "control plane machine with certificate expiry annotation and no certificate expiry annotation on bootstrap config should set expiry date",
  1281  			machine: &clusterv1.Machine{
  1282  				ObjectMeta: metav1.ObjectMeta{
  1283  					Name:      "bootstrap-test-existing",
  1284  					Namespace: metav1.NamespaceDefault,
  1285  					Labels: map[string]string{
  1286  						clusterv1.MachineControlPlaneLabel: "",
  1287  					},
  1288  					Annotations: map[string]string{
  1289  						clusterv1.MachineCertificatesExpiryDateAnnotation: fakeTimeString,
  1290  					},
  1291  				},
  1292  				Spec: clusterv1.MachineSpec{
  1293  					Bootstrap: clusterv1.Bootstrap{
  1294  						ConfigRef: &corev1.ObjectReference{
  1295  							APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1",
  1296  							Kind:       "GenericBootstrapConfig",
  1297  							Name:       "bootstrap-config-without-expiry",
  1298  						},
  1299  					},
  1300  				},
  1301  			},
  1302  			bootstrapConfig: bootstrapConfigWithoutExpiry,
  1303  			expected: func(g *WithT, m *clusterv1.Machine) {
  1304  				g.Expect(m.Status.CertificatesExpiryDate).To(Equal(fakeMetaTime))
  1305  			},
  1306  		},
  1307  		{
  1308  			name: "control plane machine with certificate expiry annotation in machine should take precedence over bootstrap config and should set expiry date",
  1309  			machine: &clusterv1.Machine{
  1310  				ObjectMeta: metav1.ObjectMeta{
  1311  					Name:      "bootstrap-test-existing",
  1312  					Namespace: metav1.NamespaceDefault,
  1313  					Labels: map[string]string{
  1314  						clusterv1.MachineControlPlaneLabel: "",
  1315  					},
  1316  					Annotations: map[string]string{
  1317  						clusterv1.MachineCertificatesExpiryDateAnnotation: fakeTimeString2,
  1318  					},
  1319  				},
  1320  				Spec: clusterv1.MachineSpec{
  1321  					Bootstrap: clusterv1.Bootstrap{
  1322  						ConfigRef: &corev1.ObjectReference{
  1323  							APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1",
  1324  							Kind:       "GenericBootstrapConfig",
  1325  							Name:       "bootstrap-config-with-expiry",
  1326  						},
  1327  					},
  1328  				},
  1329  			},
  1330  			bootstrapConfig: bootstrapConfigWithExpiry,
  1331  			expected: func(g *WithT, m *clusterv1.Machine) {
  1332  				g.Expect(m.Status.CertificatesExpiryDate).To(Equal(fakeMetaTime2))
  1333  			},
  1334  		},
  1335  		{
  1336  			name: "reset certificates expiry information in machine status if the information is not available on the machine and the bootstrap config",
  1337  			machine: &clusterv1.Machine{
  1338  				ObjectMeta: metav1.ObjectMeta{
  1339  					Name:      "bootstrap-test-existing",
  1340  					Namespace: metav1.NamespaceDefault,
  1341  					Labels: map[string]string{
  1342  						clusterv1.MachineControlPlaneLabel: "",
  1343  					},
  1344  				},
  1345  				Spec: clusterv1.MachineSpec{
  1346  					Bootstrap: clusterv1.Bootstrap{
  1347  						ConfigRef: &corev1.ObjectReference{
  1348  							APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1",
  1349  							Kind:       "GenericBootstrapConfig",
  1350  							Name:       "bootstrap-config-without-expiry",
  1351  						},
  1352  					},
  1353  				},
  1354  				Status: clusterv1.MachineStatus{
  1355  					CertificatesExpiryDate: fakeMetaTime,
  1356  				},
  1357  			},
  1358  			bootstrapConfig: bootstrapConfigWithoutExpiry,
  1359  			expected: func(g *WithT, m *clusterv1.Machine) {
  1360  				g.Expect(m.Status.CertificatesExpiryDate).To(BeNil())
  1361  			},
  1362  		},
  1363  	}
  1364  
  1365  	for _, tc := range tests {
  1366  		t.Run(tc.name, func(t *testing.T) {
  1367  			g := NewWithT(t)
  1368  
  1369  			r := &Reconciler{}
  1370  			s := &scope{machine: tc.machine, bootstrapConfig: tc.bootstrapConfig}
  1371  			_, _ = r.reconcileCertificateExpiry(ctx, s)
  1372  			if tc.expected != nil {
  1373  				tc.expected(g, tc.machine)
  1374  			}
  1375  		})
  1376  	}
  1377  }