sigs.k8s.io/cluster-api@v1.6.3/exp/internal/controllers/machinepool_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 controllers
    18  
    19  import (
    20  	"fmt"
    21  	"testing"
    22  	"time"
    23  
    24  	"github.com/go-logr/logr"
    25  	. "github.com/onsi/gomega"
    26  	corev1 "k8s.io/api/core/v1"
    27  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    28  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    29  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    30  	"k8s.io/apimachinery/pkg/types"
    31  	"k8s.io/client-go/tools/record"
    32  	utilfeature "k8s.io/component-base/featuregate/testing"
    33  	"k8s.io/utils/pointer"
    34  	ctrl "sigs.k8s.io/controller-runtime"
    35  	"sigs.k8s.io/controller-runtime/pkg/client"
    36  	"sigs.k8s.io/controller-runtime/pkg/client/fake"
    37  	"sigs.k8s.io/controller-runtime/pkg/log"
    38  
    39  	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
    40  	"sigs.k8s.io/cluster-api/controllers/external"
    41  	"sigs.k8s.io/cluster-api/controllers/remote"
    42  	expv1 "sigs.k8s.io/cluster-api/exp/api/v1beta1"
    43  	"sigs.k8s.io/cluster-api/feature"
    44  	"sigs.k8s.io/cluster-api/internal/test/builder"
    45  	"sigs.k8s.io/cluster-api/internal/util/ssa"
    46  	"sigs.k8s.io/cluster-api/util/kubeconfig"
    47  	"sigs.k8s.io/cluster-api/util/labels/format"
    48  )
    49  
    50  const (
    51  	clusterName    = "test-cluster"
    52  	wrongNamespace = "wrong-namespace"
    53  )
    54  
    55  func TestReconcileMachinePoolPhases(t *testing.T) {
    56  	deletionTimestamp := metav1.Now()
    57  
    58  	var defaultKubeconfigSecret *corev1.Secret
    59  	defaultCluster := &clusterv1.Cluster{
    60  		ObjectMeta: metav1.ObjectMeta{
    61  			Name:      clusterName,
    62  			Namespace: metav1.NamespaceDefault,
    63  		},
    64  	}
    65  
    66  	defaultMachinePool := expv1.MachinePool{
    67  		ObjectMeta: metav1.ObjectMeta{
    68  			Name:      "machinepool-test",
    69  			Namespace: metav1.NamespaceDefault,
    70  		},
    71  		Spec: expv1.MachinePoolSpec{
    72  			ClusterName: defaultCluster.Name,
    73  			Replicas:    pointer.Int32(1),
    74  			Template: clusterv1.MachineTemplateSpec{
    75  				Spec: clusterv1.MachineSpec{
    76  					Bootstrap: clusterv1.Bootstrap{
    77  						ConfigRef: &corev1.ObjectReference{
    78  							APIVersion: builder.BootstrapGroupVersion.String(),
    79  							Kind:       builder.TestBootstrapConfigKind,
    80  							Name:       "bootstrap-config1",
    81  						},
    82  					},
    83  					InfrastructureRef: corev1.ObjectReference{
    84  						APIVersion: builder.InfrastructureGroupVersion.String(),
    85  						Kind:       builder.TestInfrastructureMachineTemplateKind,
    86  						Name:       "infra-config1",
    87  					},
    88  				},
    89  			},
    90  		},
    91  	}
    92  
    93  	defaultBootstrap := &unstructured.Unstructured{
    94  		Object: map[string]interface{}{
    95  			"kind":       builder.TestBootstrapConfigKind,
    96  			"apiVersion": builder.BootstrapGroupVersion.String(),
    97  			"metadata": map[string]interface{}{
    98  				"name":      "bootstrap-config1",
    99  				"namespace": metav1.NamespaceDefault,
   100  			},
   101  			"spec":   map[string]interface{}{},
   102  			"status": map[string]interface{}{},
   103  		},
   104  	}
   105  
   106  	defaultInfra := &unstructured.Unstructured{
   107  		Object: map[string]interface{}{
   108  			"kind":       builder.TestInfrastructureMachineTemplateKind,
   109  			"apiVersion": builder.InfrastructureGroupVersion.String(),
   110  			"metadata": map[string]interface{}{
   111  				"name":      "infra-config1",
   112  				"namespace": metav1.NamespaceDefault,
   113  			},
   114  			"spec": map[string]interface{}{
   115  				"providerIDList": []interface{}{
   116  					"test://id-1",
   117  				},
   118  			},
   119  			"status": map[string]interface{}{},
   120  		},
   121  	}
   122  
   123  	t.Run("Should set OwnerReference and cluster name label on external objects", func(t *testing.T) {
   124  		g := NewWithT(t)
   125  
   126  		defaultKubeconfigSecret = kubeconfig.GenerateSecret(defaultCluster, kubeconfig.FromEnvTestConfig(env.Config, defaultCluster))
   127  		machinepool := defaultMachinePool.DeepCopy()
   128  		bootstrapConfig := defaultBootstrap.DeepCopy()
   129  		infraConfig := defaultInfra.DeepCopy()
   130  
   131  		r := &MachinePoolReconciler{
   132  			Client: fake.NewClientBuilder().WithObjects(defaultCluster, defaultKubeconfigSecret, machinepool, bootstrapConfig, infraConfig, builder.TestBootstrapConfigCRD, builder.TestInfrastructureMachineTemplateCRD).Build(),
   133  		}
   134  
   135  		res, err := r.reconcile(ctx, defaultCluster, machinepool)
   136  		g.Expect(err).ToNot(HaveOccurred())
   137  		g.Expect(res.Requeue).To(BeFalse())
   138  
   139  		r.reconcilePhase(machinepool)
   140  
   141  		g.Expect(r.Client.Get(ctx, types.NamespacedName{Name: bootstrapConfig.GetName(), Namespace: bootstrapConfig.GetNamespace()}, bootstrapConfig)).To(Succeed())
   142  
   143  		g.Expect(bootstrapConfig.GetOwnerReferences()).To(HaveLen(1))
   144  		g.Expect(bootstrapConfig.GetLabels()[clusterv1.ClusterNameLabel]).To(BeEquivalentTo(clusterName))
   145  
   146  		g.Expect(r.Client.Get(ctx, types.NamespacedName{Name: infraConfig.GetName(), Namespace: infraConfig.GetNamespace()}, infraConfig)).To(Succeed())
   147  
   148  		g.Expect(infraConfig.GetOwnerReferences()).To(HaveLen(1))
   149  		g.Expect(infraConfig.GetLabels()[clusterv1.ClusterNameLabel]).To(BeEquivalentTo(clusterName))
   150  	})
   151  
   152  	t.Run("Should set `Pending` with a new MachinePool", func(t *testing.T) {
   153  		g := NewWithT(t)
   154  
   155  		defaultKubeconfigSecret = kubeconfig.GenerateSecret(defaultCluster, kubeconfig.FromEnvTestConfig(env.Config, defaultCluster))
   156  		machinepool := defaultMachinePool.DeepCopy()
   157  		bootstrapConfig := defaultBootstrap.DeepCopy()
   158  		infraConfig := defaultInfra.DeepCopy()
   159  
   160  		r := &MachinePoolReconciler{
   161  			Client: fake.NewClientBuilder().WithObjects(defaultCluster, defaultKubeconfigSecret, machinepool, bootstrapConfig, infraConfig, builder.TestBootstrapConfigCRD, builder.TestInfrastructureMachineTemplateCRD).Build(),
   162  		}
   163  
   164  		res, err := r.reconcile(ctx, defaultCluster, machinepool)
   165  		g.Expect(err).ToNot(HaveOccurred())
   166  		g.Expect(res.Requeue).To(BeFalse())
   167  
   168  		r.reconcilePhase(machinepool)
   169  		g.Expect(machinepool.Status.GetTypedPhase()).To(Equal(expv1.MachinePoolPhasePending))
   170  	})
   171  
   172  	t.Run("Should set `Provisioning` when bootstrap is ready", func(t *testing.T) {
   173  		g := NewWithT(t)
   174  
   175  		defaultKubeconfigSecret = kubeconfig.GenerateSecret(defaultCluster, kubeconfig.FromEnvTestConfig(env.Config, defaultCluster))
   176  		machinepool := defaultMachinePool.DeepCopy()
   177  		bootstrapConfig := defaultBootstrap.DeepCopy()
   178  		infraConfig := defaultInfra.DeepCopy()
   179  
   180  		// Set bootstrap ready.
   181  		err := unstructured.SetNestedField(bootstrapConfig.Object, true, "status", "ready")
   182  		g.Expect(err).ToNot(HaveOccurred())
   183  
   184  		err = unstructured.SetNestedField(bootstrapConfig.Object, "secret-data", "status", "dataSecretName")
   185  		g.Expect(err).ToNot(HaveOccurred())
   186  
   187  		r := &MachinePoolReconciler{
   188  			Client: fake.NewClientBuilder().WithObjects(defaultCluster, defaultKubeconfigSecret, machinepool, bootstrapConfig, infraConfig, builder.TestBootstrapConfigCRD, builder.TestInfrastructureMachineTemplateCRD).Build(),
   189  		}
   190  
   191  		res, err := r.reconcile(ctx, defaultCluster, machinepool)
   192  		g.Expect(err).ToNot(HaveOccurred())
   193  		g.Expect(res.Requeue).To(BeFalse())
   194  
   195  		r.reconcilePhase(machinepool)
   196  		g.Expect(machinepool.Status.GetTypedPhase()).To(Equal(expv1.MachinePoolPhaseProvisioning))
   197  	})
   198  
   199  	t.Run("Should set `Running` when bootstrap and infra is ready", func(t *testing.T) {
   200  		g := NewWithT(t)
   201  
   202  		defaultKubeconfigSecret = kubeconfig.GenerateSecret(defaultCluster, kubeconfig.FromEnvTestConfig(env.Config, defaultCluster))
   203  		machinepool := defaultMachinePool.DeepCopy()
   204  		bootstrapConfig := defaultBootstrap.DeepCopy()
   205  		infraConfig := defaultInfra.DeepCopy()
   206  
   207  		// Set bootstrap ready.
   208  		err := unstructured.SetNestedField(bootstrapConfig.Object, true, "status", "ready")
   209  		g.Expect(err).ToNot(HaveOccurred())
   210  
   211  		err = unstructured.SetNestedField(bootstrapConfig.Object, "secret-data", "status", "dataSecretName")
   212  		g.Expect(err).ToNot(HaveOccurred())
   213  
   214  		// Set infra ready.
   215  		err = unstructured.SetNestedField(infraConfig.Object, true, "status", "ready")
   216  		g.Expect(err).ToNot(HaveOccurred())
   217  
   218  		err = unstructured.SetNestedField(infraConfig.Object, int64(1), "status", "replicas")
   219  		g.Expect(err).ToNot(HaveOccurred())
   220  
   221  		err = unstructured.SetNestedStringSlice(infraConfig.Object, []string{"test://machinepool-test-node"}, "spec", "providerIDList")
   222  		g.Expect(err).ToNot(HaveOccurred())
   223  
   224  		err = unstructured.SetNestedField(infraConfig.Object, "us-east-2a", "spec", "failureDomain")
   225  		g.Expect(err).ToNot(HaveOccurred())
   226  
   227  		// Set NodeRef.
   228  		machinepool.Status.NodeRefs = []corev1.ObjectReference{{Kind: "Node", Name: "machinepool-test-node"}}
   229  
   230  		fakeClient := fake.NewClientBuilder().WithObjects(defaultCluster, defaultKubeconfigSecret, machinepool, bootstrapConfig, infraConfig, builder.TestBootstrapConfigCRD, builder.TestInfrastructureMachineTemplateCRD).Build()
   231  		r := &MachinePoolReconciler{
   232  			Client:  fakeClient,
   233  			Tracker: remote.NewTestClusterCacheTracker(logr.New(log.NullLogSink{}), fakeClient, fakeClient.Scheme(), client.ObjectKey{Name: defaultCluster.Name, Namespace: defaultCluster.Namespace}),
   234  		}
   235  
   236  		res, err := r.reconcile(ctx, defaultCluster, machinepool)
   237  		g.Expect(err).ToNot(HaveOccurred())
   238  		g.Expect(res.Requeue).To(BeFalse())
   239  
   240  		// Set ReadyReplicas
   241  		machinepool.Status.ReadyReplicas = 1
   242  
   243  		r.reconcilePhase(machinepool)
   244  		g.Expect(machinepool.Status.GetTypedPhase()).To(Equal(expv1.MachinePoolPhaseRunning))
   245  	})
   246  
   247  	t.Run("Should set `Running` when bootstrap, infra, and ready replicas equals spec replicas", func(t *testing.T) {
   248  		g := NewWithT(t)
   249  
   250  		defaultKubeconfigSecret = kubeconfig.GenerateSecret(defaultCluster, kubeconfig.FromEnvTestConfig(env.Config, defaultCluster))
   251  		machinepool := defaultMachinePool.DeepCopy()
   252  		bootstrapConfig := defaultBootstrap.DeepCopy()
   253  		infraConfig := defaultInfra.DeepCopy()
   254  
   255  		// Set bootstrap ready.
   256  		err := unstructured.SetNestedField(bootstrapConfig.Object, true, "status", "ready")
   257  		g.Expect(err).ToNot(HaveOccurred())
   258  
   259  		err = unstructured.SetNestedField(bootstrapConfig.Object, "secret-data", "status", "dataSecretName")
   260  		g.Expect(err).ToNot(HaveOccurred())
   261  
   262  		// Set infra ready.
   263  		err = unstructured.SetNestedStringSlice(infraConfig.Object, []string{"test://id-1"}, "spec", "providerIDList")
   264  		g.Expect(err).ToNot(HaveOccurred())
   265  
   266  		err = unstructured.SetNestedField(infraConfig.Object, true, "status", "ready")
   267  		g.Expect(err).ToNot(HaveOccurred())
   268  
   269  		err = unstructured.SetNestedField(infraConfig.Object, int64(1), "status", "replicas")
   270  		g.Expect(err).ToNot(HaveOccurred())
   271  
   272  		err = unstructured.SetNestedField(infraConfig.Object, []interface{}{
   273  			map[string]interface{}{
   274  				"type":    "InternalIP",
   275  				"address": "10.0.0.1",
   276  			},
   277  			map[string]interface{}{
   278  				"type":    "InternalIP",
   279  				"address": "10.0.0.2",
   280  			},
   281  		}, "addresses")
   282  		g.Expect(err).ToNot(HaveOccurred())
   283  
   284  		// Set NodeRef.
   285  		machinepool.Status.NodeRefs = []corev1.ObjectReference{{Kind: "Node", Name: "machinepool-test-node"}}
   286  
   287  		fakeClient := fake.NewClientBuilder().WithObjects(defaultCluster, defaultKubeconfigSecret, machinepool, bootstrapConfig, infraConfig, builder.TestBootstrapConfigCRD, builder.TestInfrastructureMachineTemplateCRD).Build()
   288  		r := &MachinePoolReconciler{
   289  			Client:  fakeClient,
   290  			Tracker: remote.NewTestClusterCacheTracker(logr.New(log.NullLogSink{}), fakeClient, fakeClient.Scheme(), client.ObjectKey{Name: defaultCluster.Name, Namespace: defaultCluster.Namespace}),
   291  		}
   292  
   293  		res, err := r.reconcile(ctx, defaultCluster, machinepool)
   294  		g.Expect(err).ToNot(HaveOccurred())
   295  		g.Expect(res.Requeue).To(BeFalse())
   296  
   297  		// Set ReadyReplicas
   298  		machinepool.Status.ReadyReplicas = 1
   299  
   300  		r.reconcilePhase(machinepool)
   301  		g.Expect(machinepool.Status.GetTypedPhase()).To(Equal(expv1.MachinePoolPhaseRunning))
   302  	})
   303  
   304  	t.Run("Should set `Provisioned` when there is a NodeRef but infra is not ready ", func(t *testing.T) {
   305  		g := NewWithT(t)
   306  
   307  		defaultKubeconfigSecret = kubeconfig.GenerateSecret(defaultCluster, kubeconfig.FromEnvTestConfig(env.Config, defaultCluster))
   308  		machinepool := defaultMachinePool.DeepCopy()
   309  		bootstrapConfig := defaultBootstrap.DeepCopy()
   310  		infraConfig := defaultInfra.DeepCopy()
   311  
   312  		// Set bootstrap ready.
   313  		err := unstructured.SetNestedField(bootstrapConfig.Object, true, "status", "ready")
   314  		g.Expect(err).ToNot(HaveOccurred())
   315  
   316  		err = unstructured.SetNestedField(bootstrapConfig.Object, "secret-data", "status", "dataSecretName")
   317  		g.Expect(err).ToNot(HaveOccurred())
   318  
   319  		// Set NodeRef.
   320  		machinepool.Status.NodeRefs = []corev1.ObjectReference{{Kind: "Node", Name: "machinepool-test-node"}}
   321  
   322  		r := &MachinePoolReconciler{
   323  			Client: fake.NewClientBuilder().WithObjects(defaultCluster, defaultKubeconfigSecret, machinepool, bootstrapConfig, infraConfig, builder.TestBootstrapConfigCRD, builder.TestInfrastructureMachineTemplateCRD).Build(),
   324  		}
   325  
   326  		res, err := r.reconcile(ctx, defaultCluster, machinepool)
   327  		g.Expect(err).ToNot(HaveOccurred())
   328  		g.Expect(res.Requeue).To(BeFalse())
   329  
   330  		r.reconcilePhase(machinepool)
   331  		g.Expect(machinepool.Status.GetTypedPhase()).To(Equal(expv1.MachinePoolPhaseProvisioned))
   332  	})
   333  
   334  	t.Run("Should set `ScalingUp` when infra is scaling up", func(t *testing.T) {
   335  		g := NewWithT(t)
   336  
   337  		defaultKubeconfigSecret = kubeconfig.GenerateSecret(defaultCluster, kubeconfig.FromEnvTestConfig(env.Config, defaultCluster))
   338  		machinepool := defaultMachinePool.DeepCopy()
   339  		bootstrapConfig := defaultBootstrap.DeepCopy()
   340  		infraConfig := defaultInfra.DeepCopy()
   341  
   342  		// Set bootstrap ready.
   343  		err := unstructured.SetNestedField(bootstrapConfig.Object, true, "status", "ready")
   344  		g.Expect(err).ToNot(HaveOccurred())
   345  
   346  		err = unstructured.SetNestedField(bootstrapConfig.Object, "secret-data", "status", "dataSecretName")
   347  		g.Expect(err).ToNot(HaveOccurred())
   348  
   349  		// Set infra ready.
   350  		err = unstructured.SetNestedStringSlice(infraConfig.Object, []string{"test://id-1"}, "spec", "providerIDList")
   351  		g.Expect(err).ToNot(HaveOccurred())
   352  
   353  		err = unstructured.SetNestedField(infraConfig.Object, true, "status", "ready")
   354  		g.Expect(err).ToNot(HaveOccurred())
   355  
   356  		err = unstructured.SetNestedField(infraConfig.Object, int64(1), "status", "replicas")
   357  		g.Expect(err).ToNot(HaveOccurred())
   358  
   359  		// Set NodeRef.
   360  		machinepool.Status.NodeRefs = []corev1.ObjectReference{{Kind: "Node", Name: "machinepool-test-node"}}
   361  
   362  		fakeClient := fake.NewClientBuilder().WithObjects(defaultCluster, defaultKubeconfigSecret, machinepool, bootstrapConfig, infraConfig, builder.TestBootstrapConfigCRD, builder.TestInfrastructureMachineTemplateCRD).Build()
   363  		r := &MachinePoolReconciler{
   364  			Client:  fakeClient,
   365  			Tracker: remote.NewTestClusterCacheTracker(logr.New(log.NullLogSink{}), fakeClient, fakeClient.Scheme(), client.ObjectKey{Name: defaultCluster.Name, Namespace: defaultCluster.Namespace}),
   366  		}
   367  
   368  		res, err := r.reconcile(ctx, defaultCluster, machinepool)
   369  		g.Expect(err).ToNot(HaveOccurred())
   370  		g.Expect(res.Requeue).To(BeFalse())
   371  
   372  		// Set ReadyReplicas
   373  		machinepool.Status.ReadyReplicas = 1
   374  
   375  		// Scale up
   376  		machinepool.Spec.Replicas = pointer.Int32(5)
   377  
   378  		r.reconcilePhase(machinepool)
   379  		g.Expect(machinepool.Status.GetTypedPhase()).To(Equal(expv1.MachinePoolPhaseScalingUp))
   380  	})
   381  
   382  	t.Run("Should set `ScalingDown` when infra is scaling down", func(t *testing.T) {
   383  		g := NewWithT(t)
   384  
   385  		defaultKubeconfigSecret = kubeconfig.GenerateSecret(defaultCluster, kubeconfig.FromEnvTestConfig(env.Config, defaultCluster))
   386  		machinepool := defaultMachinePool.DeepCopy()
   387  		bootstrapConfig := defaultBootstrap.DeepCopy()
   388  		infraConfig := defaultInfra.DeepCopy()
   389  
   390  		// Set bootstrap ready.
   391  		err := unstructured.SetNestedField(bootstrapConfig.Object, true, "status", "ready")
   392  		g.Expect(err).ToNot(HaveOccurred())
   393  
   394  		err = unstructured.SetNestedField(bootstrapConfig.Object, "secret-data", "status", "dataSecretName")
   395  		g.Expect(err).ToNot(HaveOccurred())
   396  
   397  		// Set infra ready.
   398  		err = unstructured.SetNestedStringSlice(infraConfig.Object, []string{"test://id-1"}, "spec", "providerIDList")
   399  		g.Expect(err).ToNot(HaveOccurred())
   400  
   401  		err = unstructured.SetNestedField(infraConfig.Object, true, "status", "ready")
   402  		g.Expect(err).ToNot(HaveOccurred())
   403  
   404  		err = unstructured.SetNestedField(infraConfig.Object, int64(4), "status", "replicas")
   405  		g.Expect(err).ToNot(HaveOccurred())
   406  
   407  		machinepool.Spec.Replicas = pointer.Int32(4)
   408  
   409  		// Set NodeRef.
   410  		machinepool.Status.NodeRefs = []corev1.ObjectReference{
   411  			{Kind: "Node", Name: "machinepool-test-node-0"},
   412  			{Kind: "Node", Name: "machinepool-test-node-1"},
   413  			{Kind: "Node", Name: "machinepool-test-node-2"},
   414  			{Kind: "Node", Name: "machinepool-test-node-3"},
   415  		}
   416  
   417  		fakeClient := fake.NewClientBuilder().WithObjects(defaultCluster, defaultKubeconfigSecret, machinepool, bootstrapConfig, infraConfig, builder.TestBootstrapConfigCRD, builder.TestInfrastructureMachineTemplateCRD).Build()
   418  		r := &MachinePoolReconciler{
   419  			Client:  fakeClient,
   420  			Tracker: remote.NewTestClusterCacheTracker(logr.New(log.NullLogSink{}), fakeClient, fakeClient.Scheme(), client.ObjectKey{Name: defaultCluster.Name, Namespace: defaultCluster.Namespace}),
   421  		}
   422  
   423  		res, err := r.reconcile(ctx, defaultCluster, machinepool)
   424  		g.Expect(err).ToNot(HaveOccurred())
   425  		g.Expect(res.Requeue).To(BeFalse())
   426  
   427  		// Set ReadyReplicas
   428  		machinepool.Status.ReadyReplicas = 4
   429  
   430  		// Scale down
   431  		machinepool.Spec.Replicas = pointer.Int32(1)
   432  
   433  		r.reconcilePhase(machinepool)
   434  		g.Expect(machinepool.Status.GetTypedPhase()).To(Equal(expv1.MachinePoolPhaseScalingDown))
   435  	})
   436  
   437  	t.Run("Should set `Deleting` when MachinePool is being deleted", func(t *testing.T) {
   438  		g := NewWithT(t)
   439  
   440  		defaultKubeconfigSecret = kubeconfig.GenerateSecret(defaultCluster, kubeconfig.FromEnvTestConfig(env.Config, defaultCluster))
   441  		machinepool := defaultMachinePool.DeepCopy()
   442  		bootstrapConfig := defaultBootstrap.DeepCopy()
   443  		infraConfig := defaultInfra.DeepCopy()
   444  
   445  		// Set bootstrap ready.
   446  		err := unstructured.SetNestedField(bootstrapConfig.Object, true, "status", "ready")
   447  		g.Expect(err).ToNot(HaveOccurred())
   448  
   449  		err = unstructured.SetNestedField(bootstrapConfig.Object, "secret-data", "status", "dataSecretName")
   450  		g.Expect(err).ToNot(HaveOccurred())
   451  
   452  		// Set infra ready.
   453  		err = unstructured.SetNestedStringSlice(infraConfig.Object, []string{"test://id-1"}, "spec", "providerIDList")
   454  		g.Expect(err).ToNot(HaveOccurred())
   455  
   456  		err = unstructured.SetNestedField(infraConfig.Object, true, "status", "ready")
   457  		g.Expect(err).ToNot(HaveOccurred())
   458  
   459  		err = unstructured.SetNestedField(infraConfig.Object, []interface{}{
   460  			map[string]interface{}{
   461  				"type":    "InternalIP",
   462  				"address": "10.0.0.1",
   463  			},
   464  			map[string]interface{}{
   465  				"type":    "InternalIP",
   466  				"address": "10.0.0.2",
   467  			},
   468  		}, "addresses")
   469  		g.Expect(err).ToNot(HaveOccurred())
   470  
   471  		// Set NodeRef.
   472  		machinepool.Status.NodeRefs = []corev1.ObjectReference{{Kind: "Node", Name: "machinepool-test-node"}}
   473  
   474  		// Set Deletion Timestamp.
   475  		machinepool.SetDeletionTimestamp(&deletionTimestamp)
   476  		machinepool.Finalizers = []string{expv1.MachinePoolFinalizer}
   477  
   478  		r := &MachinePoolReconciler{
   479  			Client: fake.NewClientBuilder().WithObjects(defaultCluster, defaultKubeconfigSecret, machinepool, bootstrapConfig, infraConfig, builder.TestBootstrapConfigCRD, builder.TestInfrastructureMachineTemplateCRD).Build(),
   480  		}
   481  
   482  		res, err := r.reconcile(ctx, defaultCluster, machinepool)
   483  		g.Expect(err).ToNot(HaveOccurred())
   484  		g.Expect(res.Requeue).To(BeFalse())
   485  
   486  		r.reconcilePhase(machinepool)
   487  		g.Expect(machinepool.Status.GetTypedPhase()).To(Equal(expv1.MachinePoolPhaseDeleting))
   488  	})
   489  }
   490  
   491  func TestReconcileMachinePoolBootstrap(t *testing.T) {
   492  	defaultMachinePool := expv1.MachinePool{
   493  		ObjectMeta: metav1.ObjectMeta{
   494  			Name:      "machinepool-test",
   495  			Namespace: metav1.NamespaceDefault,
   496  			Labels: map[string]string{
   497  				clusterv1.ClusterNameLabel: clusterName,
   498  			},
   499  		},
   500  		Spec: expv1.MachinePoolSpec{
   501  			Template: clusterv1.MachineTemplateSpec{
   502  				Spec: clusterv1.MachineSpec{
   503  					Bootstrap: clusterv1.Bootstrap{
   504  						ConfigRef: &corev1.ObjectReference{
   505  							APIVersion: builder.BootstrapGroupVersion.String(),
   506  							Kind:       builder.TestBootstrapConfigKind,
   507  							Name:       "bootstrap-config1",
   508  						},
   509  					},
   510  				},
   511  			},
   512  		},
   513  	}
   514  
   515  	defaultCluster := &clusterv1.Cluster{
   516  		ObjectMeta: metav1.ObjectMeta{
   517  			Name:      clusterName,
   518  			Namespace: metav1.NamespaceDefault,
   519  		},
   520  	}
   521  
   522  	testCases := []struct {
   523  		name            string
   524  		bootstrapConfig map[string]interface{}
   525  		machinepool     *expv1.MachinePool
   526  		expectError     bool
   527  		expectResult    ctrl.Result
   528  		expected        func(g *WithT, m *expv1.MachinePool)
   529  	}{
   530  		{
   531  			name: "new machinepool, bootstrap config ready with data",
   532  			bootstrapConfig: map[string]interface{}{
   533  				"kind":       builder.TestBootstrapConfigKind,
   534  				"apiVersion": builder.BootstrapGroupVersion.String(),
   535  				"metadata": map[string]interface{}{
   536  					"name":      "bootstrap-config1",
   537  					"namespace": metav1.NamespaceDefault,
   538  				},
   539  				"spec": map[string]interface{}{},
   540  				"status": map[string]interface{}{
   541  					"ready":          true,
   542  					"dataSecretName": "secret-data",
   543  				},
   544  			},
   545  			expectError: false,
   546  			expected: func(g *WithT, m *expv1.MachinePool) {
   547  				g.Expect(m.Status.BootstrapReady).To(BeTrue())
   548  				g.Expect(m.Spec.Template.Spec.Bootstrap.DataSecretName).ToNot(BeNil())
   549  				g.Expect(*m.Spec.Template.Spec.Bootstrap.DataSecretName).To(ContainSubstring("secret-data"))
   550  			},
   551  		},
   552  		{
   553  			name: "new machinepool, bootstrap config ready with no data",
   554  			bootstrapConfig: map[string]interface{}{
   555  				"kind":       builder.TestBootstrapConfigKind,
   556  				"apiVersion": builder.BootstrapGroupVersion.String(),
   557  				"metadata": map[string]interface{}{
   558  					"name":      "bootstrap-config1",
   559  					"namespace": metav1.NamespaceDefault,
   560  				},
   561  				"spec": map[string]interface{}{},
   562  				"status": map[string]interface{}{
   563  					"ready": true,
   564  				},
   565  			},
   566  			expectError: true,
   567  			expected: func(g *WithT, m *expv1.MachinePool) {
   568  				g.Expect(m.Status.BootstrapReady).To(BeFalse())
   569  				g.Expect(m.Spec.Template.Spec.Bootstrap.DataSecretName).To(BeNil())
   570  			},
   571  		},
   572  		{
   573  			name: "new machinepool, bootstrap config not ready",
   574  			bootstrapConfig: map[string]interface{}{
   575  				"kind":       builder.TestBootstrapConfigKind,
   576  				"apiVersion": builder.BootstrapGroupVersion.String(),
   577  				"metadata": map[string]interface{}{
   578  					"name":      "bootstrap-config1",
   579  					"namespace": metav1.NamespaceDefault,
   580  				},
   581  				"spec":   map[string]interface{}{},
   582  				"status": map[string]interface{}{},
   583  			},
   584  			expectError:  false,
   585  			expectResult: ctrl.Result{},
   586  			expected: func(g *WithT, m *expv1.MachinePool) {
   587  				g.Expect(m.Status.BootstrapReady).To(BeFalse())
   588  			},
   589  		},
   590  		{
   591  			name: "new machinepool, bootstrap config is not found",
   592  			bootstrapConfig: map[string]interface{}{
   593  				"kind":       builder.TestBootstrapConfigKind,
   594  				"apiVersion": builder.BootstrapGroupVersion.String(),
   595  				"metadata": map[string]interface{}{
   596  					"name":      "bootstrap-config1",
   597  					"namespace": wrongNamespace,
   598  				},
   599  				"spec":   map[string]interface{}{},
   600  				"status": map[string]interface{}{},
   601  			},
   602  			expectError: true,
   603  			expected: func(g *WithT, m *expv1.MachinePool) {
   604  				g.Expect(m.Status.BootstrapReady).To(BeFalse())
   605  			},
   606  		},
   607  		{
   608  			name: "new machinepool, no bootstrap config or data",
   609  			bootstrapConfig: map[string]interface{}{
   610  				"kind":       builder.TestBootstrapConfigKind,
   611  				"apiVersion": builder.BootstrapGroupVersion.String(),
   612  				"metadata": map[string]interface{}{
   613  					"name":      "bootstrap-config1",
   614  					"namespace": wrongNamespace,
   615  				},
   616  				"spec":   map[string]interface{}{},
   617  				"status": map[string]interface{}{},
   618  			},
   619  			expectError: true,
   620  		},
   621  		{
   622  			name: "existing machinepool with config ref, update data secret name",
   623  			bootstrapConfig: map[string]interface{}{
   624  				"kind":       builder.TestBootstrapConfigKind,
   625  				"apiVersion": builder.BootstrapGroupVersion.String(),
   626  				"metadata": map[string]interface{}{
   627  					"name":      "bootstrap-config1",
   628  					"namespace": metav1.NamespaceDefault,
   629  				},
   630  				"spec": map[string]interface{}{},
   631  				"status": map[string]interface{}{
   632  					"ready":          true,
   633  					"dataSecretName": "secret-data",
   634  				},
   635  			},
   636  			machinepool: &expv1.MachinePool{
   637  				ObjectMeta: metav1.ObjectMeta{
   638  					Name:      "bootstrap-test-existing",
   639  					Namespace: metav1.NamespaceDefault,
   640  				},
   641  				Spec: expv1.MachinePoolSpec{
   642  					Template: clusterv1.MachineTemplateSpec{
   643  						Spec: clusterv1.MachineSpec{
   644  							Bootstrap: clusterv1.Bootstrap{
   645  								ConfigRef: &corev1.ObjectReference{
   646  									APIVersion: builder.BootstrapGroupVersion.String(),
   647  									Kind:       builder.TestBootstrapConfigKind,
   648  									Name:       "bootstrap-config1",
   649  								},
   650  								DataSecretName: pointer.String("data"),
   651  							},
   652  						},
   653  					},
   654  				},
   655  				Status: expv1.MachinePoolStatus{
   656  					BootstrapReady: true,
   657  				},
   658  			},
   659  			expectError: false,
   660  			expected: func(g *WithT, m *expv1.MachinePool) {
   661  				g.Expect(m.Status.BootstrapReady).To(BeTrue())
   662  				g.Expect(*m.Spec.Template.Spec.Bootstrap.DataSecretName).To(Equal("secret-data"))
   663  			},
   664  		},
   665  		{
   666  			name: "existing machinepool without config ref, do not update data secret name",
   667  			bootstrapConfig: map[string]interface{}{
   668  				"kind":       builder.TestBootstrapConfigKind,
   669  				"apiVersion": builder.BootstrapGroupVersion.String(),
   670  				"metadata": map[string]interface{}{
   671  					"name":      "bootstrap-config1",
   672  					"namespace": metav1.NamespaceDefault,
   673  				},
   674  				"spec": map[string]interface{}{},
   675  				"status": map[string]interface{}{
   676  					"ready":          true,
   677  					"dataSecretName": "secret-data",
   678  				},
   679  			},
   680  			machinepool: &expv1.MachinePool{
   681  				ObjectMeta: metav1.ObjectMeta{
   682  					Name:      "bootstrap-test-existing",
   683  					Namespace: metav1.NamespaceDefault,
   684  				},
   685  				Spec: expv1.MachinePoolSpec{
   686  					Template: clusterv1.MachineTemplateSpec{
   687  						Spec: clusterv1.MachineSpec{
   688  							Bootstrap: clusterv1.Bootstrap{
   689  								DataSecretName: pointer.String("data"),
   690  							},
   691  						},
   692  					},
   693  				},
   694  				Status: expv1.MachinePoolStatus{
   695  					BootstrapReady: true,
   696  				},
   697  			},
   698  			expectError: false,
   699  			expected: func(g *WithT, m *expv1.MachinePool) {
   700  				g.Expect(m.Status.BootstrapReady).To(BeTrue())
   701  				g.Expect(*m.Spec.Template.Spec.Bootstrap.DataSecretName).To(Equal("data"))
   702  			},
   703  		},
   704  		{
   705  			name: "existing machinepool, bootstrap provider is not ready",
   706  			bootstrapConfig: map[string]interface{}{
   707  				"kind":       builder.TestBootstrapConfigKind,
   708  				"apiVersion": builder.BootstrapGroupVersion.String(),
   709  				"metadata": map[string]interface{}{
   710  					"name":      "bootstrap-config1",
   711  					"namespace": metav1.NamespaceDefault,
   712  				},
   713  				"spec": map[string]interface{}{},
   714  				"status": map[string]interface{}{
   715  					"ready": false,
   716  					"data":  "#!/bin/bash ... data",
   717  				},
   718  			},
   719  			machinepool: &expv1.MachinePool{
   720  				ObjectMeta: metav1.ObjectMeta{
   721  					Name:      "bootstrap-test-existing",
   722  					Namespace: metav1.NamespaceDefault,
   723  				},
   724  				Spec: expv1.MachinePoolSpec{
   725  					Template: clusterv1.MachineTemplateSpec{
   726  						Spec: clusterv1.MachineSpec{
   727  							Bootstrap: clusterv1.Bootstrap{
   728  								ConfigRef: &corev1.ObjectReference{
   729  									APIVersion: builder.BootstrapGroupVersion.String(),
   730  									Kind:       builder.TestBootstrapConfigKind,
   731  									Name:       "bootstrap-config1",
   732  								},
   733  								DataSecretName: pointer.String("data"),
   734  							},
   735  						},
   736  					},
   737  				},
   738  				Status: expv1.MachinePoolStatus{
   739  					BootstrapReady: false,
   740  				},
   741  			},
   742  			expectError:  false,
   743  			expectResult: ctrl.Result{},
   744  			expected: func(g *WithT, m *expv1.MachinePool) {
   745  				g.Expect(m.Status.BootstrapReady).To(BeFalse())
   746  			},
   747  		},
   748  	}
   749  
   750  	for _, tc := range testCases {
   751  		t.Run(tc.name, func(t *testing.T) {
   752  			g := NewWithT(t)
   753  			if tc.machinepool == nil {
   754  				tc.machinepool = defaultMachinePool.DeepCopy()
   755  			}
   756  
   757  			bootstrapConfig := &unstructured.Unstructured{Object: tc.bootstrapConfig}
   758  			r := &MachinePoolReconciler{
   759  				Client: fake.NewClientBuilder().WithObjects(tc.machinepool, bootstrapConfig, builder.TestBootstrapConfigCRD, builder.TestInfrastructureMachineTemplateCRD).Build(),
   760  			}
   761  
   762  			res, err := r.reconcileBootstrap(ctx, defaultCluster, tc.machinepool)
   763  			g.Expect(res).To(BeComparableTo(tc.expectResult))
   764  			if tc.expectError {
   765  				g.Expect(err).To(HaveOccurred())
   766  			} else {
   767  				g.Expect(err).ToNot(HaveOccurred())
   768  			}
   769  
   770  			if tc.expected != nil {
   771  				tc.expected(g, tc.machinepool)
   772  			}
   773  		})
   774  	}
   775  }
   776  
   777  func TestReconcileMachinePoolInfrastructure(t *testing.T) {
   778  	defaultMachinePool := expv1.MachinePool{
   779  		ObjectMeta: metav1.ObjectMeta{
   780  			Name:      "machinepool-test",
   781  			Namespace: metav1.NamespaceDefault,
   782  			Labels: map[string]string{
   783  				clusterv1.ClusterNameLabel: clusterName,
   784  			},
   785  		},
   786  		Spec: expv1.MachinePoolSpec{
   787  			Replicas: pointer.Int32(1),
   788  			Template: clusterv1.MachineTemplateSpec{
   789  				Spec: clusterv1.MachineSpec{
   790  					Bootstrap: clusterv1.Bootstrap{
   791  						ConfigRef: &corev1.ObjectReference{
   792  							APIVersion: builder.BootstrapGroupVersion.String(),
   793  							Kind:       builder.TestBootstrapConfigKind,
   794  							Name:       "bootstrap-config1",
   795  						},
   796  					},
   797  					InfrastructureRef: corev1.ObjectReference{
   798  						APIVersion: builder.InfrastructureGroupVersion.String(),
   799  						Kind:       builder.TestInfrastructureMachineTemplateKind,
   800  						Name:       "infra-config1",
   801  					},
   802  				},
   803  			},
   804  		},
   805  	}
   806  
   807  	defaultCluster := &clusterv1.Cluster{
   808  		ObjectMeta: metav1.ObjectMeta{
   809  			Name:      clusterName,
   810  			Namespace: metav1.NamespaceDefault,
   811  		},
   812  	}
   813  
   814  	testCases := []struct {
   815  		name               string
   816  		bootstrapConfig    map[string]interface{}
   817  		infraConfig        map[string]interface{}
   818  		machinepool        *expv1.MachinePool
   819  		expectError        bool
   820  		expectChanged      bool
   821  		expectRequeueAfter bool
   822  		expected           func(g *WithT, m *expv1.MachinePool)
   823  	}{
   824  		{
   825  			name: "new machinepool, infrastructure config ready",
   826  			infraConfig: map[string]interface{}{
   827  				"kind":       builder.TestInfrastructureMachineTemplateKind,
   828  				"apiVersion": builder.InfrastructureGroupVersion.String(),
   829  				"metadata": map[string]interface{}{
   830  					"name":      "infra-config1",
   831  					"namespace": metav1.NamespaceDefault,
   832  				},
   833  				"spec": map[string]interface{}{
   834  					"providerIDList": []interface{}{
   835  						"test://id-1",
   836  					},
   837  				},
   838  				"status": map[string]interface{}{
   839  					"ready": true,
   840  					"addresses": []interface{}{
   841  						map[string]interface{}{
   842  							"type":    "InternalIP",
   843  							"address": "10.0.0.1",
   844  						},
   845  						map[string]interface{}{
   846  							"type":    "InternalIP",
   847  							"address": "10.0.0.2",
   848  						},
   849  					},
   850  				},
   851  			},
   852  			expectError:   false,
   853  			expectChanged: true,
   854  			expected: func(g *WithT, m *expv1.MachinePool) {
   855  				g.Expect(m.Status.InfrastructureReady).To(BeTrue())
   856  			},
   857  		},
   858  		{
   859  			name: "ready bootstrap, infra, and nodeRef, machinepool is running, infra object is deleted, expect failed",
   860  			machinepool: &expv1.MachinePool{
   861  				ObjectMeta: metav1.ObjectMeta{
   862  					Name:      "machinepool-test",
   863  					Namespace: metav1.NamespaceDefault,
   864  				},
   865  				Spec: expv1.MachinePoolSpec{
   866  					Replicas: pointer.Int32(1),
   867  					Template: clusterv1.MachineTemplateSpec{
   868  						Spec: clusterv1.MachineSpec{
   869  							Bootstrap: clusterv1.Bootstrap{
   870  								ConfigRef: &corev1.ObjectReference{
   871  									APIVersion: builder.BootstrapGroupVersion.String(),
   872  									Kind:       builder.TestBootstrapConfigKind,
   873  									Name:       "bootstrap-config1",
   874  								},
   875  							},
   876  							InfrastructureRef: corev1.ObjectReference{
   877  								APIVersion: builder.InfrastructureGroupVersion.String(),
   878  								Kind:       builder.TestInfrastructureMachineTemplateKind,
   879  								Name:       "infra-config1",
   880  							},
   881  						},
   882  					},
   883  				},
   884  				Status: expv1.MachinePoolStatus{
   885  					BootstrapReady:      true,
   886  					InfrastructureReady: true,
   887  					NodeRefs:            []corev1.ObjectReference{{Kind: "Node", Name: "machinepool-test-node"}},
   888  				},
   889  			},
   890  			bootstrapConfig: map[string]interface{}{
   891  				"kind":       builder.TestBootstrapConfigKind,
   892  				"apiVersion": builder.BootstrapGroupVersion.String(),
   893  				"metadata": map[string]interface{}{
   894  					"name":      "bootstrap-config1",
   895  					"namespace": metav1.NamespaceDefault,
   896  				},
   897  				"spec": map[string]interface{}{},
   898  				"status": map[string]interface{}{
   899  					"ready":          true,
   900  					"dataSecretName": "secret-data",
   901  				},
   902  			},
   903  			infraConfig: map[string]interface{}{
   904  				"kind":       builder.TestInfrastructureMachineTemplateKind,
   905  				"apiVersion": builder.InfrastructureGroupVersion.String(),
   906  				"metadata":   map[string]interface{}{},
   907  			},
   908  			expectError:        true,
   909  			expectRequeueAfter: false,
   910  			expected: func(g *WithT, m *expv1.MachinePool) {
   911  				g.Expect(m.Status.InfrastructureReady).To(BeTrue())
   912  				g.Expect(m.Status.FailureMessage).ToNot(BeNil())
   913  				g.Expect(m.Status.FailureReason).ToNot(BeNil())
   914  				g.Expect(m.Status.GetTypedPhase()).To(Equal(expv1.MachinePoolPhaseFailed))
   915  			},
   916  		},
   917  		{
   918  			name: "infrastructure ref is paused",
   919  			infraConfig: map[string]interface{}{
   920  				"kind":       builder.TestInfrastructureMachineTemplateKind,
   921  				"apiVersion": builder.InfrastructureGroupVersion.String(),
   922  				"metadata": map[string]interface{}{
   923  					"name":      "infra-config1",
   924  					"namespace": metav1.NamespaceDefault,
   925  					"annotations": map[string]interface{}{
   926  						"cluster.x-k8s.io/paused": "true",
   927  					},
   928  				},
   929  				"spec": map[string]interface{}{
   930  					"providerIDList": []interface{}{
   931  						"test://id-1",
   932  					},
   933  				},
   934  				"status": map[string]interface{}{
   935  					"ready": true,
   936  					"addresses": []interface{}{
   937  						map[string]interface{}{
   938  							"type":    "InternalIP",
   939  							"address": "10.0.0.1",
   940  						},
   941  						map[string]interface{}{
   942  							"type":    "InternalIP",
   943  							"address": "10.0.0.2",
   944  						},
   945  					},
   946  				},
   947  			},
   948  			expectError:   false,
   949  			expectChanged: false,
   950  			expected: func(g *WithT, m *expv1.MachinePool) {
   951  				g.Expect(m.Status.InfrastructureReady).To(BeFalse())
   952  			},
   953  		},
   954  		{
   955  			name: "ready bootstrap, infra, and nodeRef, machinepool is running, replicas 0, providerIDList not set",
   956  			machinepool: &expv1.MachinePool{
   957  				ObjectMeta: metav1.ObjectMeta{
   958  					Name:      "machinepool-test",
   959  					Namespace: metav1.NamespaceDefault,
   960  				},
   961  				Spec: expv1.MachinePoolSpec{
   962  					Replicas: pointer.Int32(0),
   963  					Template: clusterv1.MachineTemplateSpec{
   964  						Spec: clusterv1.MachineSpec{
   965  							Bootstrap: clusterv1.Bootstrap{
   966  								ConfigRef: &corev1.ObjectReference{
   967  									APIVersion: builder.BootstrapGroupVersion.String(),
   968  									Kind:       builder.TestBootstrapConfigKind,
   969  									Name:       "bootstrap-config1",
   970  								},
   971  							},
   972  							InfrastructureRef: corev1.ObjectReference{
   973  								APIVersion: builder.InfrastructureGroupVersion.String(),
   974  								Kind:       builder.TestInfrastructureMachineTemplateKind,
   975  								Name:       "infra-config1",
   976  							},
   977  						},
   978  					},
   979  				},
   980  				Status: expv1.MachinePoolStatus{
   981  					BootstrapReady:      true,
   982  					InfrastructureReady: true,
   983  					NodeRefs:            []corev1.ObjectReference{{Kind: "Node", Name: "machinepool-test-node"}},
   984  				},
   985  			},
   986  			bootstrapConfig: map[string]interface{}{
   987  				"kind":       builder.TestBootstrapConfigKind,
   988  				"apiVersion": builder.BootstrapGroupVersion.String(),
   989  				"metadata": map[string]interface{}{
   990  					"name":      "bootstrap-config1",
   991  					"namespace": metav1.NamespaceDefault,
   992  				},
   993  				"spec": map[string]interface{}{},
   994  				"status": map[string]interface{}{
   995  					"ready":          true,
   996  					"dataSecretName": "secret-data",
   997  				},
   998  			},
   999  			infraConfig: map[string]interface{}{
  1000  				"kind":       builder.TestInfrastructureMachineTemplateKind,
  1001  				"apiVersion": builder.InfrastructureGroupVersion.String(),
  1002  				"metadata": map[string]interface{}{
  1003  					"name":      "infra-config1",
  1004  					"namespace": metav1.NamespaceDefault,
  1005  				},
  1006  				"spec": map[string]interface{}{
  1007  					"providerIDList": []interface{}{},
  1008  				},
  1009  				"status": map[string]interface{}{
  1010  					"ready": true,
  1011  					"addresses": []interface{}{
  1012  						map[string]interface{}{
  1013  							"type":    "InternalIP",
  1014  							"address": "10.0.0.1",
  1015  						},
  1016  						map[string]interface{}{
  1017  							"type":    "InternalIP",
  1018  							"address": "10.0.0.2",
  1019  						},
  1020  					},
  1021  				},
  1022  			},
  1023  			expectError:        false,
  1024  			expectRequeueAfter: false,
  1025  			expected: func(g *WithT, m *expv1.MachinePool) {
  1026  				g.Expect(m.Status.InfrastructureReady).To(BeTrue())
  1027  				g.Expect(m.Status.ReadyReplicas).To(Equal(int32(0)))
  1028  				g.Expect(m.Status.AvailableReplicas).To(Equal(int32(0)))
  1029  				g.Expect(m.Status.UnavailableReplicas).To(Equal(int32(0)))
  1030  				g.Expect(m.Status.FailureMessage).To(BeNil())
  1031  				g.Expect(m.Status.FailureReason).To(BeNil())
  1032  				g.Expect(m.Status.GetTypedPhase()).To(Equal(expv1.MachinePoolPhaseRunning))
  1033  			},
  1034  		},
  1035  	}
  1036  
  1037  	for _, tc := range testCases {
  1038  		t.Run(tc.name, func(t *testing.T) {
  1039  			g := NewWithT(t)
  1040  
  1041  			if tc.machinepool == nil {
  1042  				tc.machinepool = defaultMachinePool.DeepCopy()
  1043  			}
  1044  
  1045  			infraConfig := &unstructured.Unstructured{Object: tc.infraConfig}
  1046  			r := &MachinePoolReconciler{
  1047  				Client: fake.NewClientBuilder().WithObjects(tc.machinepool, infraConfig, builder.TestBootstrapConfigCRD, builder.TestInfrastructureMachineTemplateCRD).Build(),
  1048  			}
  1049  
  1050  			res, err := r.reconcileInfrastructure(ctx, defaultCluster, tc.machinepool)
  1051  			if tc.expectRequeueAfter {
  1052  				g.Expect(res.RequeueAfter).To(BeNumerically(">=", 0))
  1053  			} else {
  1054  				g.Expect(res.RequeueAfter).To(Equal(time.Duration(0)))
  1055  			}
  1056  			r.reconcilePhase(tc.machinepool)
  1057  			if tc.expectError {
  1058  				g.Expect(err).To(HaveOccurred())
  1059  			} else {
  1060  				g.Expect(err).ToNot(HaveOccurred())
  1061  			}
  1062  
  1063  			if tc.expected != nil {
  1064  				tc.expected(g, tc.machinepool)
  1065  			}
  1066  		})
  1067  	}
  1068  }
  1069  
  1070  func TestReconcileMachinePoolMachines(t *testing.T) {
  1071  	t.Run("Reconcile MachinePool Machines", func(t *testing.T) {
  1072  		// NOTE: MachinePool feature flag is disabled by default, thus preventing to create or update MachinePool.
  1073  		// Enabling the feature flag temporarily for this test.
  1074  		defer utilfeature.SetFeatureGateDuringTest(t, feature.Gates, feature.MachinePool, true)()
  1075  
  1076  		g := NewWithT(t)
  1077  
  1078  		ns, err := env.CreateNamespace(ctx, "test-machinepool-machines")
  1079  		g.Expect(err).ToNot(HaveOccurred())
  1080  
  1081  		cluster := builder.Cluster(ns.Name, clusterName).Build()
  1082  		g.Expect(env.Create(ctx, cluster)).To(Succeed())
  1083  
  1084  		t.Run("Should do nothing if machines already exist", func(t *testing.T) {
  1085  			machinePool := getMachinePool(2, "machinepool-test-1", clusterName, ns.Name)
  1086  			g.Expect(env.Create(ctx, &machinePool)).To(Succeed())
  1087  
  1088  			infraMachines := getInfraMachines(2, machinePool.Name, clusterName, ns.Name)
  1089  			for i := range infraMachines {
  1090  				g.Expect(env.Create(ctx, &infraMachines[i])).To(Succeed())
  1091  			}
  1092  
  1093  			machines := getMachines(2, machinePool.Name, clusterName, ns.Name)
  1094  			for i := range machines {
  1095  				g.Expect(env.Create(ctx, &machines[i])).To(Succeed())
  1096  			}
  1097  
  1098  			infraConfig := map[string]interface{}{
  1099  				"kind":       builder.GenericInfrastructureMachinePoolKind,
  1100  				"apiVersion": "infrastructure.cluster.x-k8s.io/v1beta1",
  1101  				"metadata": map[string]interface{}{
  1102  					"name":      "infra-config1",
  1103  					"namespace": ns.Name,
  1104  				},
  1105  				"spec": map[string]interface{}{
  1106  					"providerIDList": []interface{}{
  1107  						"test://id-1",
  1108  					},
  1109  				},
  1110  				"status": map[string]interface{}{
  1111  					"ready": true,
  1112  					"addresses": []interface{}{
  1113  						map[string]interface{}{
  1114  							"type":    "InternalIP",
  1115  							"address": "10.0.0.1",
  1116  						},
  1117  						map[string]interface{}{
  1118  							"type":    "InternalIP",
  1119  							"address": "10.0.0.2",
  1120  						},
  1121  					},
  1122  					"infrastructureMachineKind": builder.GenericInfrastructureMachineKind,
  1123  				},
  1124  			}
  1125  			g.Expect(env.Create(ctx, &unstructured.Unstructured{Object: infraConfig})).To(Succeed())
  1126  
  1127  			r := &MachinePoolReconciler{
  1128  				Client:   env,
  1129  				ssaCache: ssa.NewCache(),
  1130  			}
  1131  
  1132  			err = r.reconcileMachines(ctx, &machinePool, &unstructured.Unstructured{Object: infraConfig})
  1133  			r.reconcilePhase(&machinePool)
  1134  			g.Expect(err).ToNot(HaveOccurred())
  1135  
  1136  			machineList := &clusterv1.MachineList{}
  1137  			labels := map[string]string{
  1138  				clusterv1.ClusterNameLabel:     clusterName,
  1139  				clusterv1.MachinePoolNameLabel: machinePool.Name,
  1140  			}
  1141  			g.Expect(env.GetAPIReader().List(ctx, machineList, client.InNamespace(cluster.Namespace), client.MatchingLabels(labels))).To(Succeed())
  1142  			g.Expect(machineList.Items).To(HaveLen(2))
  1143  			for i := range machineList.Items {
  1144  				machine := &machineList.Items[i]
  1145  				_, err := external.Get(ctx, r.Client, &machine.Spec.InfrastructureRef, machine.Namespace)
  1146  				g.Expect(err).ToNot(HaveOccurred())
  1147  			}
  1148  		})
  1149  
  1150  		t.Run("Should create two machines if two infra machines exist", func(t *testing.T) {
  1151  			machinePool := getMachinePool(2, "machinepool-test-2", clusterName, ns.Name)
  1152  			g.Expect(env.Create(ctx, &machinePool)).To(Succeed())
  1153  
  1154  			infraMachines := getInfraMachines(2, machinePool.Name, clusterName, ns.Name)
  1155  			for i := range infraMachines {
  1156  				g.Expect(env.Create(ctx, &infraMachines[i])).To(Succeed())
  1157  			}
  1158  
  1159  			infraConfig := map[string]interface{}{
  1160  				"kind":       builder.GenericInfrastructureMachinePoolKind,
  1161  				"apiVersion": "infrastructure.cluster.x-k8s.io/v1beta1",
  1162  				"metadata": map[string]interface{}{
  1163  					"name":      "infra-config2",
  1164  					"namespace": ns.Name,
  1165  				},
  1166  				"spec": map[string]interface{}{
  1167  					"providerIDList": []interface{}{
  1168  						"test://id-1",
  1169  					},
  1170  				},
  1171  				"status": map[string]interface{}{
  1172  					"ready": true,
  1173  					"addresses": []interface{}{
  1174  						map[string]interface{}{
  1175  							"type":    "InternalIP",
  1176  							"address": "10.0.0.1",
  1177  						},
  1178  						map[string]interface{}{
  1179  							"type":    "InternalIP",
  1180  							"address": "10.0.0.2",
  1181  						},
  1182  					},
  1183  					"infrastructureMachineKind": builder.GenericInfrastructureMachineKind,
  1184  				},
  1185  			}
  1186  			g.Expect(env.Create(ctx, &unstructured.Unstructured{Object: infraConfig})).To(Succeed())
  1187  
  1188  			r := &MachinePoolReconciler{
  1189  				Client:   env,
  1190  				ssaCache: ssa.NewCache(),
  1191  			}
  1192  
  1193  			err = r.reconcileMachines(ctx, &machinePool, &unstructured.Unstructured{Object: infraConfig})
  1194  			r.reconcilePhase(&machinePool)
  1195  			g.Expect(err).ToNot(HaveOccurred())
  1196  
  1197  			machineList := &clusterv1.MachineList{}
  1198  			labels := map[string]string{
  1199  				clusterv1.ClusterNameLabel:     clusterName,
  1200  				clusterv1.MachinePoolNameLabel: machinePool.Name,
  1201  			}
  1202  			g.Expect(env.GetAPIReader().List(ctx, machineList, client.InNamespace(cluster.Namespace), client.MatchingLabels(labels))).To(Succeed())
  1203  			g.Expect(machineList.Items).To(HaveLen(2))
  1204  			for i := range machineList.Items {
  1205  				machine := &machineList.Items[i]
  1206  				_, err := external.Get(ctx, r.Client, &machine.Spec.InfrastructureRef, machine.Namespace)
  1207  				g.Expect(err).ToNot(HaveOccurred())
  1208  			}
  1209  		})
  1210  
  1211  		t.Run("Should do nothing if machinepool does not support machinepool machines", func(t *testing.T) {
  1212  			machinePool := getMachinePool(2, "machinepool-test-3", clusterName, ns.Name)
  1213  			g.Expect(env.Create(ctx, &machinePool)).To(Succeed())
  1214  
  1215  			infraConfig := map[string]interface{}{
  1216  				"kind":       builder.GenericInfrastructureMachinePoolKind,
  1217  				"apiVersion": "infrastructure.cluster.x-k8s.io/v1beta1",
  1218  				"metadata": map[string]interface{}{
  1219  					"name":      "infra-config3",
  1220  					"namespace": ns.Name,
  1221  				},
  1222  				"spec": map[string]interface{}{
  1223  					"providerIDList": []interface{}{
  1224  						"test://id-1",
  1225  					},
  1226  				},
  1227  				"status": map[string]interface{}{
  1228  					"ready": true,
  1229  					"addresses": []interface{}{
  1230  						map[string]interface{}{
  1231  							"type":    "InternalIP",
  1232  							"address": "10.0.0.1",
  1233  						},
  1234  						map[string]interface{}{
  1235  							"type":    "InternalIP",
  1236  							"address": "10.0.0.2",
  1237  						},
  1238  					},
  1239  				},
  1240  			}
  1241  			g.Expect(env.Create(ctx, &unstructured.Unstructured{Object: infraConfig})).To(Succeed())
  1242  
  1243  			r := &MachinePoolReconciler{
  1244  				Client:   env,
  1245  				ssaCache: ssa.NewCache(),
  1246  			}
  1247  
  1248  			err = r.reconcileMachines(ctx, &machinePool, &unstructured.Unstructured{Object: infraConfig})
  1249  			r.reconcilePhase(&machinePool)
  1250  			g.Expect(err).ToNot(HaveOccurred())
  1251  
  1252  			machineList := &clusterv1.MachineList{}
  1253  			labels := map[string]string{
  1254  				clusterv1.ClusterNameLabel:     clusterName,
  1255  				clusterv1.MachinePoolNameLabel: machinePool.Name,
  1256  			}
  1257  			g.Expect(env.GetAPIReader().List(ctx, machineList, client.InNamespace(cluster.Namespace), client.MatchingLabels(labels))).To(Succeed())
  1258  			g.Expect(machineList.Items).To(BeEmpty())
  1259  		})
  1260  	})
  1261  }
  1262  
  1263  func TestInfraMachineToMachinePoolMapper(t *testing.T) {
  1264  	machinePool1 := expv1.MachinePool{
  1265  		ObjectMeta: metav1.ObjectMeta{
  1266  			Name:      "machinepool-1",
  1267  			Namespace: metav1.NamespaceDefault,
  1268  			Labels: map[string]string{
  1269  				clusterv1.ClusterNameLabel: clusterName,
  1270  			},
  1271  		},
  1272  	}
  1273  
  1274  	machinePool2 := expv1.MachinePool{
  1275  		ObjectMeta: metav1.ObjectMeta{
  1276  			Name:      "machinepool-2",
  1277  			Namespace: "other-namespace",
  1278  			Labels: map[string]string{
  1279  				clusterv1.ClusterNameLabel: clusterName,
  1280  			},
  1281  		},
  1282  	}
  1283  
  1284  	machinePool3 := expv1.MachinePool{
  1285  		ObjectMeta: metav1.ObjectMeta{
  1286  			Name:      "machinepool-3",
  1287  			Namespace: metav1.NamespaceDefault,
  1288  			Labels: map[string]string{
  1289  				clusterv1.ClusterNameLabel: "other-cluster",
  1290  			},
  1291  		},
  1292  	}
  1293  
  1294  	machinePoolLongName := expv1.MachinePool{
  1295  		ObjectMeta: metav1.ObjectMeta{
  1296  			Name:      "machinepool-very-very-very-very-very-very-very-very-very-very-very-very-very-very-very-very-very-very-very-very-long", // Use a name longer than 64 characters to trigger a hash
  1297  			Namespace: metav1.NamespaceDefault,
  1298  			Labels: map[string]string{
  1299  				clusterv1.ClusterNameLabel: "other-cluster",
  1300  			},
  1301  		},
  1302  	}
  1303  
  1304  	infraMachine1 := unstructured.Unstructured{
  1305  		Object: map[string]interface{}{
  1306  			"kind":       "InfrastructureMachine",
  1307  			"apiVersion": "infrastructure.cluster.x-k8s.io/v1beta1",
  1308  			"metadata": map[string]interface{}{
  1309  				"name":      "infra-machine1",
  1310  				"namespace": metav1.NamespaceDefault,
  1311  				"labels": map[string]interface{}{
  1312  					clusterv1.ClusterNameLabel:     clusterName,
  1313  					clusterv1.MachinePoolNameLabel: format.MustFormatValue(machinePool1.Name),
  1314  				},
  1315  			},
  1316  		},
  1317  	}
  1318  
  1319  	infraMachine2 := unstructured.Unstructured{
  1320  		Object: map[string]interface{}{
  1321  			"kind":       "InfrastructureMachine",
  1322  			"apiVersion": "infrastructure.cluster.x-k8s.io/v1beta1",
  1323  			"metadata": map[string]interface{}{
  1324  				"name":      "infra-machine2",
  1325  				"namespace": metav1.NamespaceDefault,
  1326  				"labels": map[string]interface{}{
  1327  					clusterv1.ClusterNameLabel:     "other-cluster",
  1328  					clusterv1.MachinePoolNameLabel: format.MustFormatValue(machinePoolLongName.Name),
  1329  				},
  1330  			},
  1331  		},
  1332  	}
  1333  
  1334  	infraMachine3 := unstructured.Unstructured{
  1335  		Object: map[string]interface{}{
  1336  			"kind":       "InfrastructureMachine",
  1337  			"apiVersion": "infrastructure.cluster.x-k8s.io/v1beta1",
  1338  			"metadata": map[string]interface{}{
  1339  				"name":      "infra-machine3",
  1340  				"namespace": metav1.NamespaceDefault,
  1341  				"labels": map[string]interface{}{
  1342  					clusterv1.ClusterNameLabel:     "other-cluster",
  1343  					clusterv1.MachinePoolNameLabel: format.MustFormatValue("missing-machinepool"),
  1344  				},
  1345  			},
  1346  		},
  1347  	}
  1348  
  1349  	testCases := []struct {
  1350  		name                string
  1351  		infraMachine        *unstructured.Unstructured
  1352  		machinepools        []expv1.MachinePool
  1353  		expectedMachinePool *expv1.MachinePool
  1354  	}{
  1355  		{
  1356  			name:         "match machinePool name with label value",
  1357  			infraMachine: &infraMachine1,
  1358  			machinepools: []expv1.MachinePool{
  1359  				machinePool1,
  1360  				machinePool2,
  1361  				machinePool3,
  1362  				machinePoolLongName,
  1363  			},
  1364  			expectedMachinePool: &machinePool1,
  1365  		},
  1366  		{
  1367  			name:         "match hash of machinePool name with label hash",
  1368  			infraMachine: &infraMachine2,
  1369  			machinepools: []expv1.MachinePool{
  1370  				machinePool1,
  1371  				machinePool2,
  1372  				machinePool3,
  1373  				machinePoolLongName,
  1374  			},
  1375  			expectedMachinePool: &machinePoolLongName,
  1376  		},
  1377  		{
  1378  			name:         "return nil if no machinePool matches",
  1379  			infraMachine: &infraMachine3,
  1380  			machinepools: []expv1.MachinePool{
  1381  				machinePool1,
  1382  				machinePool2,
  1383  				machinePool3,
  1384  				machinePoolLongName,
  1385  			},
  1386  			expectedMachinePool: nil,
  1387  		},
  1388  	}
  1389  
  1390  	for _, tc := range testCases {
  1391  		t.Run(tc.name, func(t *testing.T) {
  1392  			g := NewWithT(t)
  1393  
  1394  			objs := []client.Object{tc.infraMachine.DeepCopy()}
  1395  
  1396  			for _, mp := range tc.machinepools {
  1397  				objs = append(objs, mp.DeepCopy())
  1398  			}
  1399  
  1400  			r := &MachinePoolReconciler{
  1401  				Client: fake.NewClientBuilder().WithObjects(objs...).Build(),
  1402  			}
  1403  
  1404  			result := r.infraMachineToMachinePoolMapper(ctx, tc.infraMachine)
  1405  			if tc.expectedMachinePool == nil {
  1406  				g.Expect(result).To(BeNil())
  1407  			} else {
  1408  				g.Expect(result).To(HaveLen(1))
  1409  				g.Expect(result[0].Name).To(Equal(tc.expectedMachinePool.Name))
  1410  				g.Expect(result[0].Namespace).To(Equal(tc.expectedMachinePool.Namespace))
  1411  			}
  1412  		})
  1413  	}
  1414  }
  1415  
  1416  func TestReconcileMachinePoolScaleToFromZero(t *testing.T) {
  1417  	g := NewWithT(t)
  1418  
  1419  	ns, err := env.CreateNamespace(ctx, "machinepool-scale-zero")
  1420  	g.Expect(err).ToNot(HaveOccurred())
  1421  
  1422  	// Set up cluster to test against.
  1423  	testCluster := &clusterv1.Cluster{
  1424  		ObjectMeta: metav1.ObjectMeta{
  1425  			GenerateName: "machinepool-scale-zero-",
  1426  			Namespace:    ns.Name,
  1427  		},
  1428  	}
  1429  	g.Expect(env.CreateAndWait(ctx, testCluster)).To(Succeed())
  1430  	g.Expect(env.CreateKubeconfigSecret(ctx, testCluster)).To(Succeed())
  1431  	defer func(do ...client.Object) {
  1432  		g.Expect(env.CleanupAndWait(ctx, do...)).To(Succeed())
  1433  	}(testCluster)
  1434  
  1435  	defaultMachinePool := expv1.MachinePool{
  1436  		ObjectMeta: metav1.ObjectMeta{
  1437  			Name:      "machinepool-test",
  1438  			Namespace: ns.Name,
  1439  		},
  1440  		Spec: expv1.MachinePoolSpec{
  1441  			ClusterName: testCluster.Name,
  1442  			Template: clusterv1.MachineTemplateSpec{
  1443  				Spec: clusterv1.MachineSpec{
  1444  					Bootstrap: clusterv1.Bootstrap{
  1445  						ConfigRef: &corev1.ObjectReference{
  1446  							APIVersion: builder.BootstrapGroupVersion.String(),
  1447  							Kind:       builder.TestBootstrapConfigKind,
  1448  							Name:       "bootstrap-config1",
  1449  						},
  1450  					},
  1451  					InfrastructureRef: corev1.ObjectReference{
  1452  						APIVersion: builder.InfrastructureGroupVersion.String(),
  1453  						Kind:       builder.TestInfrastructureMachineTemplateKind,
  1454  						Name:       "infra-config1",
  1455  					},
  1456  				},
  1457  			},
  1458  		},
  1459  		Status: expv1.MachinePoolStatus{},
  1460  	}
  1461  
  1462  	defaultBootstrap := &unstructured.Unstructured{
  1463  		Object: map[string]interface{}{
  1464  			"kind":       builder.TestBootstrapConfigKind,
  1465  			"apiVersion": builder.BootstrapGroupVersion.String(),
  1466  			"metadata": map[string]interface{}{
  1467  				"name":      "bootstrap-config1",
  1468  				"namespace": ns.Name,
  1469  			},
  1470  			"spec": map[string]interface{}{},
  1471  			"status": map[string]interface{}{
  1472  				"ready":          true,
  1473  				"dataSecretName": "secret-data",
  1474  			},
  1475  		},
  1476  	}
  1477  
  1478  	defaultInfra := &unstructured.Unstructured{
  1479  		Object: map[string]interface{}{
  1480  			"kind":       builder.TestInfrastructureMachineTemplateKind,
  1481  			"apiVersion": builder.InfrastructureGroupVersion.String(),
  1482  			"metadata": map[string]interface{}{
  1483  				"name":      "infra-config1",
  1484  				"namespace": ns.Name,
  1485  			},
  1486  			"spec": map[string]interface{}{},
  1487  			"status": map[string]interface{}{
  1488  				"ready": true,
  1489  			},
  1490  		},
  1491  	}
  1492  
  1493  	t.Run("Should set `ScalingDown` when scaling to zero", func(t *testing.T) {
  1494  		g := NewWithT(t)
  1495  
  1496  		node := &corev1.Node{
  1497  			ObjectMeta: metav1.ObjectMeta{
  1498  				Name: "machinepool-test-node",
  1499  			},
  1500  			Spec: corev1.NodeSpec{
  1501  				ProviderID: "test://machinepool-test-node",
  1502  			},
  1503  			Status: corev1.NodeStatus{
  1504  				Conditions: []corev1.NodeCondition{
  1505  					{Type: corev1.NodeReady, Status: corev1.ConditionTrue},
  1506  				},
  1507  			},
  1508  		}
  1509  		g.Expect(env.CreateAndWait(ctx, node)).To(Succeed())
  1510  		defer func(do ...client.Object) {
  1511  			g.Expect(env.CleanupAndWait(ctx, do...)).To(Succeed())
  1512  		}(node)
  1513  
  1514  		kubeconfigSecret := kubeconfig.GenerateSecret(testCluster, kubeconfig.FromEnvTestConfig(env.Config, testCluster))
  1515  		machinepool := defaultMachinePool.DeepCopy()
  1516  		bootstrapConfig := defaultBootstrap.DeepCopy()
  1517  		infraConfig := defaultInfra.DeepCopy()
  1518  
  1519  		// Setup prerequisites - a running MachinePool with one instance and user sets Replicas to 0
  1520  
  1521  		// set replicas to 0
  1522  		machinepool.Spec.Replicas = pointer.Int32(0)
  1523  
  1524  		// set nodeRefs to one instance
  1525  		machinepool.Status.NodeRefs = []corev1.ObjectReference{{Kind: "Node", Name: "machinepool-test-node"}}
  1526  
  1527  		// set infra providerIDList
  1528  		err = unstructured.SetNestedStringSlice(infraConfig.Object, []string{"test://machinepool-test-node"}, "spec", "providerIDList")
  1529  		g.Expect(err).ToNot(HaveOccurred())
  1530  
  1531  		// set infra replicas
  1532  		err = unstructured.SetNestedField(infraConfig.Object, int64(1), "status", "replicas")
  1533  		g.Expect(err).ToNot(HaveOccurred())
  1534  
  1535  		fakeClient := fake.NewClientBuilder().WithObjects(testCluster, kubeconfigSecret, machinepool, bootstrapConfig, infraConfig, builder.TestBootstrapConfigCRD, builder.TestInfrastructureMachineTemplateCRD).Build()
  1536  		r := &MachinePoolReconciler{
  1537  			Client:   fakeClient,
  1538  			Tracker:  remote.NewTestClusterCacheTracker(logr.New(log.NullLogSink{}), env.GetClient(), env.GetClient().Scheme(), client.ObjectKey{Name: testCluster.Name, Namespace: testCluster.Namespace}),
  1539  			recorder: record.NewFakeRecorder(32),
  1540  		}
  1541  
  1542  		res, err := r.reconcile(ctx, testCluster, machinepool)
  1543  		g.Expect(err).ToNot(HaveOccurred())
  1544  		g.Expect(res.Requeue).To(BeFalse())
  1545  
  1546  		r.reconcilePhase(machinepool)
  1547  
  1548  		g.Expect(machinepool.Status.GetTypedPhase()).To(Equal(expv1.MachinePoolPhaseScalingDown))
  1549  
  1550  		delNode := &corev1.Node{}
  1551  		g.Expect(env.Get(ctx, client.ObjectKeyFromObject(node), delNode)).To(Succeed())
  1552  	})
  1553  
  1554  	t.Run("Should delete retired nodes when scaled to zero", func(t *testing.T) {
  1555  		g := NewWithT(t)
  1556  
  1557  		node := &corev1.Node{
  1558  			ObjectMeta: metav1.ObjectMeta{
  1559  				Name: "machinepool-test-node",
  1560  			},
  1561  			Spec: corev1.NodeSpec{
  1562  				ProviderID: "test://machinepool-test-node",
  1563  			},
  1564  			Status: corev1.NodeStatus{
  1565  				Conditions: []corev1.NodeCondition{
  1566  					{Type: corev1.NodeReady, Status: corev1.ConditionTrue},
  1567  				},
  1568  			},
  1569  		}
  1570  		g.Expect(env.CreateAndWait(ctx, node)).To(Succeed())
  1571  		defer func(do ...client.Object) {
  1572  			g.Expect(env.CleanupAndWait(ctx, do...)).To(Succeed())
  1573  		}(node)
  1574  
  1575  		kubeconfigSecret := kubeconfig.GenerateSecret(testCluster, kubeconfig.FromEnvTestConfig(env.Config, testCluster))
  1576  		machinepool := defaultMachinePool.DeepCopy()
  1577  		bootstrapConfig := defaultBootstrap.DeepCopy()
  1578  		infraConfig := defaultInfra.DeepCopy()
  1579  
  1580  		// Setup prerequisites - a running MachinePool with one instance and user sets Replicas to 0
  1581  
  1582  		// set replicas to 0
  1583  		machinepool.Spec.Replicas = pointer.Int32(0)
  1584  
  1585  		// set nodeRefs to one instance
  1586  		machinepool.Status.NodeRefs = []corev1.ObjectReference{{Kind: "Node", Name: "machinepool-test-node"}}
  1587  
  1588  		// set infra replicas
  1589  		err = unstructured.SetNestedField(infraConfig.Object, int64(0), "status", "replicas")
  1590  		g.Expect(err).ToNot(HaveOccurred())
  1591  
  1592  		fakeClient := fake.NewClientBuilder().WithObjects(testCluster, kubeconfigSecret, machinepool, bootstrapConfig, infraConfig, builder.TestBootstrapConfigCRD, builder.TestInfrastructureMachineTemplateCRD).Build()
  1593  		r := &MachinePoolReconciler{
  1594  			Client:   fakeClient,
  1595  			Tracker:  remote.NewTestClusterCacheTracker(logr.New(log.NullLogSink{}), env.GetClient(), env.GetClient().Scheme(), client.ObjectKey{Name: testCluster.Name, Namespace: testCluster.Namespace}),
  1596  			recorder: record.NewFakeRecorder(32),
  1597  		}
  1598  
  1599  		res, err := r.reconcile(ctx, testCluster, machinepool)
  1600  		g.Expect(err).ToNot(HaveOccurred())
  1601  		g.Expect(res.Requeue).To(BeFalse())
  1602  
  1603  		r.reconcilePhase(machinepool)
  1604  		g.Expect(machinepool.Status.GetTypedPhase()).To(Equal(expv1.MachinePoolPhaseRunning))
  1605  
  1606  		delNode := &corev1.Node{}
  1607  		err = env.GetAPIReader().Get(ctx, client.ObjectKeyFromObject(node), delNode)
  1608  		g.Expect(err).To(HaveOccurred())
  1609  		g.Expect(apierrors.IsNotFound(err)).To(BeTrue())
  1610  	})
  1611  
  1612  	t.Run("Should set `Running` when scaled to zero", func(t *testing.T) {
  1613  		g := NewWithT(t)
  1614  
  1615  		kubeconfigSecret := kubeconfig.GenerateSecret(testCluster, kubeconfig.FromEnvTestConfig(env.Config, testCluster))
  1616  		machinepool := defaultMachinePool.DeepCopy()
  1617  		bootstrapConfig := defaultBootstrap.DeepCopy()
  1618  		infraConfig := defaultInfra.DeepCopy()
  1619  
  1620  		// Setup prerequisites - a running MachinePool with no instances and replicas set to 0
  1621  
  1622  		// set replicas to 0
  1623  		machinepool.Spec.Replicas = pointer.Int32(0)
  1624  
  1625  		// set nodeRefs to no instance
  1626  		machinepool.Status.NodeRefs = []corev1.ObjectReference{}
  1627  
  1628  		// set infra replicas
  1629  		err := unstructured.SetNestedField(infraConfig.Object, int64(0), "status", "replicas")
  1630  		g.Expect(err).ToNot(HaveOccurred())
  1631  
  1632  		r := &MachinePoolReconciler{
  1633  			Client:   fake.NewClientBuilder().WithObjects(testCluster, kubeconfigSecret, machinepool, bootstrapConfig, infraConfig, builder.TestBootstrapConfigCRD, builder.TestInfrastructureMachineTemplateCRD).Build(),
  1634  			recorder: record.NewFakeRecorder(32),
  1635  		}
  1636  
  1637  		res, err := r.reconcile(ctx, testCluster, machinepool)
  1638  		g.Expect(err).ToNot(HaveOccurred())
  1639  		g.Expect(res.Requeue).To(BeFalse())
  1640  
  1641  		r.reconcilePhase(machinepool)
  1642  
  1643  		g.Expect(machinepool.Status.GetTypedPhase()).To(Equal(expv1.MachinePoolPhaseRunning))
  1644  	})
  1645  
  1646  	t.Run("Should set `ScalingUp` when scaling from zero to one", func(t *testing.T) {
  1647  		g := NewWithT(t)
  1648  
  1649  		kubeconfigSecret := kubeconfig.GenerateSecret(testCluster, kubeconfig.FromEnvTestConfig(env.Config, testCluster))
  1650  		machinepool := defaultMachinePool.DeepCopy()
  1651  		bootstrapConfig := defaultBootstrap.DeepCopy()
  1652  		infraConfig := defaultInfra.DeepCopy()
  1653  
  1654  		// Setup prerequisites - a running MachinePool with no instances and replicas set to 1
  1655  
  1656  		// set replicas to 1
  1657  		machinepool.Spec.Replicas = pointer.Int32(1)
  1658  
  1659  		// set nodeRefs to no instance
  1660  		machinepool.Status.NodeRefs = []corev1.ObjectReference{}
  1661  
  1662  		// set infra replicas
  1663  		err := unstructured.SetNestedField(infraConfig.Object, int64(0), "status", "replicas")
  1664  		g.Expect(err).ToNot(HaveOccurred())
  1665  
  1666  		r := &MachinePoolReconciler{
  1667  			Client:   fake.NewClientBuilder().WithObjects(testCluster, kubeconfigSecret, machinepool, bootstrapConfig, infraConfig, builder.TestBootstrapConfigCRD, builder.TestInfrastructureMachineTemplateCRD).Build(),
  1668  			recorder: record.NewFakeRecorder(32),
  1669  		}
  1670  
  1671  		res, err := r.reconcile(ctx, testCluster, machinepool)
  1672  		g.Expect(err).ToNot(HaveOccurred())
  1673  		g.Expect(res.Requeue).To(BeFalse())
  1674  
  1675  		r.reconcilePhase(machinepool)
  1676  
  1677  		g.Expect(machinepool.Status.GetTypedPhase()).To(Equal(expv1.MachinePoolPhaseScalingUp))
  1678  	})
  1679  
  1680  	t.Run("Should set `Running` when scaled from zero to one", func(t *testing.T) {
  1681  		g := NewWithT(t)
  1682  
  1683  		node := &corev1.Node{
  1684  			ObjectMeta: metav1.ObjectMeta{
  1685  				Name: "machinepool-test-node",
  1686  			},
  1687  			Spec: corev1.NodeSpec{
  1688  				ProviderID: "test://machinepool-test-node",
  1689  			},
  1690  			Status: corev1.NodeStatus{
  1691  				Conditions: []corev1.NodeCondition{
  1692  					{Type: corev1.NodeReady, Status: corev1.ConditionTrue},
  1693  				},
  1694  			},
  1695  		}
  1696  		g.Expect(env.CreateAndWait(ctx, node)).To(Succeed())
  1697  		defer func(do ...client.Object) {
  1698  			g.Expect(env.CleanupAndWait(ctx, do...)).To(Succeed())
  1699  		}(node)
  1700  
  1701  		kubeconfigSecret := kubeconfig.GenerateSecret(testCluster, kubeconfig.FromEnvTestConfig(env.Config, testCluster))
  1702  		machinepool := defaultMachinePool.DeepCopy()
  1703  		bootstrapConfig := defaultBootstrap.DeepCopy()
  1704  		infraConfig := defaultInfra.DeepCopy()
  1705  
  1706  		// Setup prerequisites - a running MachinePool with no refs but providerIDList and replicas set to 1
  1707  
  1708  		// set replicas to 1
  1709  		machinepool.Spec.Replicas = pointer.Int32(1)
  1710  
  1711  		// set nodeRefs to no instance
  1712  		machinepool.Status.NodeRefs = []corev1.ObjectReference{}
  1713  
  1714  		// set infra providerIDList
  1715  		err = unstructured.SetNestedStringSlice(infraConfig.Object, []string{"test://machinepool-test-node"}, "spec", "providerIDList")
  1716  		g.Expect(err).ToNot(HaveOccurred())
  1717  
  1718  		// set infra replicas
  1719  		err = unstructured.SetNestedField(infraConfig.Object, int64(1), "status", "replicas")
  1720  		g.Expect(err).ToNot(HaveOccurred())
  1721  
  1722  		fakeClient := fake.NewClientBuilder().WithObjects(testCluster, kubeconfigSecret, machinepool, bootstrapConfig, infraConfig, builder.TestBootstrapConfigCRD, builder.TestInfrastructureMachineTemplateCRD).Build()
  1723  		r := &MachinePoolReconciler{
  1724  			Client:   fakeClient,
  1725  			Tracker:  remote.NewTestClusterCacheTracker(logr.New(log.NullLogSink{}), env.GetClient(), env.GetClient().Scheme(), client.ObjectKey{Name: testCluster.Name, Namespace: testCluster.Namespace}),
  1726  			recorder: record.NewFakeRecorder(32),
  1727  		}
  1728  
  1729  		res, err := r.reconcile(ctx, testCluster, machinepool)
  1730  		g.Expect(err).ToNot(HaveOccurred())
  1731  		g.Expect(res.Requeue).To(BeFalse())
  1732  
  1733  		r.reconcilePhase(machinepool)
  1734  
  1735  		g.Expect(machinepool.Status.GetTypedPhase()).To(Equal(expv1.MachinePoolPhaseRunning))
  1736  
  1737  		delNode := &corev1.Node{}
  1738  		g.Expect(env.Get(ctx, client.ObjectKeyFromObject(node), delNode)).To(Succeed())
  1739  	})
  1740  }
  1741  
  1742  func getInfraMachines(replicas int, mpName, clusterName, nsName string) []unstructured.Unstructured {
  1743  	infraMachines := make([]unstructured.Unstructured, replicas)
  1744  	for i := 0; i < replicas; i++ {
  1745  		infraMachines[i] = unstructured.Unstructured{
  1746  			Object: map[string]interface{}{
  1747  				"kind":       builder.GenericInfrastructureMachineKind,
  1748  				"apiVersion": "infrastructure.cluster.x-k8s.io/v1beta1",
  1749  				"metadata": map[string]interface{}{
  1750  					"name":      fmt.Sprintf("%s-infra-%d", mpName, i),
  1751  					"namespace": nsName,
  1752  					"labels": map[string]interface{}{
  1753  						clusterv1.ClusterNameLabel:     clusterName,
  1754  						clusterv1.MachinePoolNameLabel: mpName,
  1755  					},
  1756  				},
  1757  			},
  1758  		}
  1759  	}
  1760  	return infraMachines
  1761  }
  1762  
  1763  func getMachines(replicas int, mpName, clusterName, nsName string) []clusterv1.Machine {
  1764  	machines := make([]clusterv1.Machine, replicas)
  1765  	for i := 0; i < replicas; i++ {
  1766  		machines[i] = clusterv1.Machine{
  1767  			ObjectMeta: metav1.ObjectMeta{
  1768  				Name:      fmt.Sprintf("%s-machine-%d", mpName, i),
  1769  				Namespace: nsName,
  1770  				Labels: map[string]string{
  1771  					clusterv1.ClusterNameLabel:     clusterName,
  1772  					clusterv1.MachinePoolNameLabel: mpName,
  1773  				},
  1774  			},
  1775  			Spec: clusterv1.MachineSpec{
  1776  				ClusterName: clusterName,
  1777  				Bootstrap: clusterv1.Bootstrap{
  1778  					ConfigRef: &corev1.ObjectReference{
  1779  						APIVersion: builder.BootstrapGroupVersion.String(),
  1780  						Kind:       builder.GenericBootstrapConfigKind,
  1781  						Name:       fmt.Sprintf("bootstrap-config-%d", i),
  1782  					},
  1783  				},
  1784  				InfrastructureRef: corev1.ObjectReference{
  1785  					APIVersion: builder.InfrastructureGroupVersion.String(),
  1786  					Kind:       builder.GenericInfrastructureMachineKind,
  1787  					Name:       fmt.Sprintf("%s-infra-%d", mpName, i),
  1788  				},
  1789  			},
  1790  		}
  1791  	}
  1792  	return machines
  1793  }
  1794  
  1795  func getMachinePool(replicas int, mpName, clusterName, nsName string) expv1.MachinePool {
  1796  	return expv1.MachinePool{
  1797  		ObjectMeta: metav1.ObjectMeta{
  1798  			Name:      mpName,
  1799  			Namespace: nsName,
  1800  			Labels: map[string]string{
  1801  				clusterv1.ClusterNameLabel: clusterName,
  1802  			},
  1803  		},
  1804  		Spec: expv1.MachinePoolSpec{
  1805  			ClusterName: clusterName,
  1806  			Replicas:    pointer.Int32(int32(replicas)),
  1807  			Template: clusterv1.MachineTemplateSpec{
  1808  				Spec: clusterv1.MachineSpec{
  1809  					ClusterName: clusterName,
  1810  					Bootstrap: clusterv1.Bootstrap{
  1811  						ConfigRef: &corev1.ObjectReference{
  1812  							APIVersion: builder.BootstrapGroupVersion.String(),
  1813  							Kind:       builder.GenericBootstrapConfigKind,
  1814  							Name:       "bootstrap-config1",
  1815  						},
  1816  					},
  1817  					InfrastructureRef: corev1.ObjectReference{
  1818  						APIVersion: builder.InfrastructureGroupVersion.String(),
  1819  						Kind:       builder.GenericInfrastructureMachineKind,
  1820  						Name:       "infra-config1",
  1821  					},
  1822  				},
  1823  			},
  1824  		},
  1825  	}
  1826  }