sigs.k8s.io/cluster-api@v1.6.3/controlplane/kubeadm/internal/controllers/helpers_test.go (about)

     1  /*
     2  Copyright 2020 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  	"testing"
    21  	"time"
    22  
    23  	. "github.com/onsi/gomega"
    24  	corev1 "k8s.io/api/core/v1"
    25  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    26  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    27  	"k8s.io/apimachinery/pkg/types"
    28  	"k8s.io/client-go/tools/record"
    29  	"k8s.io/utils/pointer"
    30  	ctrl "sigs.k8s.io/controller-runtime"
    31  	"sigs.k8s.io/controller-runtime/pkg/client"
    32  
    33  	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
    34  	bootstrapv1 "sigs.k8s.io/cluster-api/bootstrap/kubeadm/api/v1beta1"
    35  	"sigs.k8s.io/cluster-api/controllers/external"
    36  	controlplanev1 "sigs.k8s.io/cluster-api/controlplane/kubeadm/api/v1beta1"
    37  	"sigs.k8s.io/cluster-api/controlplane/kubeadm/internal"
    38  	"sigs.k8s.io/cluster-api/util/conditions"
    39  	"sigs.k8s.io/cluster-api/util/kubeconfig"
    40  	"sigs.k8s.io/cluster-api/util/secret"
    41  )
    42  
    43  func TestReconcileKubeconfigEmptyAPIEndpoints(t *testing.T) {
    44  	g := NewWithT(t)
    45  
    46  	cluster := &clusterv1.Cluster{
    47  		TypeMeta: metav1.TypeMeta{
    48  			Kind:       "Cluster",
    49  			APIVersion: clusterv1.GroupVersion.String(),
    50  		},
    51  		ObjectMeta: metav1.ObjectMeta{
    52  			Name:      "foo",
    53  			Namespace: metav1.NamespaceDefault,
    54  		},
    55  		Spec: clusterv1.ClusterSpec{
    56  			ControlPlaneEndpoint: clusterv1.APIEndpoint{},
    57  		},
    58  	}
    59  
    60  	kcp := &controlplanev1.KubeadmControlPlane{
    61  		TypeMeta: metav1.TypeMeta{
    62  			Kind:       "KubeadmControlPlane",
    63  			APIVersion: controlplanev1.GroupVersion.String(),
    64  		},
    65  		ObjectMeta: metav1.ObjectMeta{
    66  			Name:      "foo",
    67  			Namespace: metav1.NamespaceDefault,
    68  		},
    69  		Spec: controlplanev1.KubeadmControlPlaneSpec{
    70  			Version: "v1.16.6",
    71  		},
    72  	}
    73  	clusterName := client.ObjectKey{Namespace: metav1.NamespaceDefault, Name: "foo"}
    74  
    75  	fakeClient := newFakeClient(kcp.DeepCopy())
    76  	r := &KubeadmControlPlaneReconciler{
    77  		Client:              fakeClient,
    78  		SecretCachingClient: fakeClient,
    79  		recorder:            record.NewFakeRecorder(32),
    80  	}
    81  
    82  	controlPlane := &internal.ControlPlane{
    83  		KCP:     kcp,
    84  		Cluster: cluster,
    85  	}
    86  
    87  	result, err := r.reconcileKubeconfig(ctx, controlPlane)
    88  	g.Expect(err).ToNot(HaveOccurred())
    89  	g.Expect(result).To(BeZero())
    90  
    91  	kubeconfigSecret := &corev1.Secret{}
    92  	secretName := client.ObjectKey{
    93  		Namespace: metav1.NamespaceDefault,
    94  		Name:      secret.Name(clusterName.Name, secret.Kubeconfig),
    95  	}
    96  	g.Expect(r.Client.Get(ctx, secretName, kubeconfigSecret)).To(MatchError(ContainSubstring("not found")))
    97  }
    98  
    99  func TestReconcileKubeconfigMissingCACertificate(t *testing.T) {
   100  	g := NewWithT(t)
   101  
   102  	cluster := &clusterv1.Cluster{
   103  		TypeMeta: metav1.TypeMeta{
   104  			Kind:       "Cluster",
   105  			APIVersion: clusterv1.GroupVersion.String(),
   106  		},
   107  		ObjectMeta: metav1.ObjectMeta{
   108  			Name:      "foo",
   109  			Namespace: metav1.NamespaceDefault,
   110  		},
   111  		Spec: clusterv1.ClusterSpec{
   112  			ControlPlaneEndpoint: clusterv1.APIEndpoint{Host: "test.local", Port: 8443},
   113  		},
   114  	}
   115  
   116  	kcp := &controlplanev1.KubeadmControlPlane{
   117  		TypeMeta: metav1.TypeMeta{
   118  			Kind:       "KubeadmControlPlane",
   119  			APIVersion: controlplanev1.GroupVersion.String(),
   120  		},
   121  		ObjectMeta: metav1.ObjectMeta{
   122  			Name:      "foo",
   123  			Namespace: metav1.NamespaceDefault,
   124  		},
   125  		Spec: controlplanev1.KubeadmControlPlaneSpec{
   126  			Version: "v1.16.6",
   127  		},
   128  	}
   129  
   130  	fakeClient := newFakeClient(kcp.DeepCopy())
   131  	r := &KubeadmControlPlaneReconciler{
   132  		Client:              fakeClient,
   133  		SecretCachingClient: fakeClient,
   134  		recorder:            record.NewFakeRecorder(32),
   135  	}
   136  
   137  	controlPlane := &internal.ControlPlane{
   138  		KCP:     kcp,
   139  		Cluster: cluster,
   140  	}
   141  
   142  	result, err := r.reconcileKubeconfig(ctx, controlPlane)
   143  	g.Expect(err).ToNot(HaveOccurred())
   144  	g.Expect(result).To(BeComparableTo(ctrl.Result{RequeueAfter: dependentCertRequeueAfter}))
   145  
   146  	kubeconfigSecret := &corev1.Secret{}
   147  	secretName := client.ObjectKey{
   148  		Namespace: metav1.NamespaceDefault,
   149  		Name:      secret.Name(cluster.Name, secret.Kubeconfig),
   150  	}
   151  	g.Expect(r.Client.Get(ctx, secretName, kubeconfigSecret)).To(MatchError(ContainSubstring("not found")))
   152  }
   153  
   154  func TestReconcileKubeconfigSecretDoesNotAdoptsUserSecrets(t *testing.T) {
   155  	g := NewWithT(t)
   156  
   157  	cluster := &clusterv1.Cluster{
   158  		TypeMeta: metav1.TypeMeta{
   159  			Kind:       "Cluster",
   160  			APIVersion: clusterv1.GroupVersion.String(),
   161  		},
   162  		ObjectMeta: metav1.ObjectMeta{
   163  			Name:      "foo",
   164  			Namespace: metav1.NamespaceDefault,
   165  		},
   166  		Spec: clusterv1.ClusterSpec{
   167  			ControlPlaneEndpoint: clusterv1.APIEndpoint{Host: "test.local", Port: 8443},
   168  		},
   169  	}
   170  
   171  	kcp := &controlplanev1.KubeadmControlPlane{
   172  		TypeMeta: metav1.TypeMeta{
   173  			Kind:       "KubeadmControlPlane",
   174  			APIVersion: controlplanev1.GroupVersion.String(),
   175  		},
   176  		ObjectMeta: metav1.ObjectMeta{
   177  			Name:      "foo",
   178  			Namespace: metav1.NamespaceDefault,
   179  		},
   180  		Spec: controlplanev1.KubeadmControlPlaneSpec{
   181  			Version: "v1.16.6",
   182  		},
   183  	}
   184  
   185  	existingKubeconfigSecret := &corev1.Secret{
   186  		ObjectMeta: metav1.ObjectMeta{
   187  			Name:      secret.Name("foo", secret.Kubeconfig),
   188  			Namespace: metav1.NamespaceDefault,
   189  			Labels: map[string]string{
   190  				clusterv1.ClusterNameLabel: "foo",
   191  			},
   192  			OwnerReferences: []metav1.OwnerReference{},
   193  		},
   194  		Data: map[string][]byte{
   195  			secret.KubeconfigDataName: {},
   196  		},
   197  		// KCP identifies CAPI-created Secrets using the clusterv1.ClusterSecretType. Setting any other type allows
   198  		// the controllers to treat it as a user-provided Secret.
   199  		Type: corev1.SecretTypeOpaque,
   200  	}
   201  
   202  	fakeClient := newFakeClient(kcp.DeepCopy(), existingKubeconfigSecret.DeepCopy())
   203  	r := &KubeadmControlPlaneReconciler{
   204  		Client:              fakeClient,
   205  		SecretCachingClient: fakeClient,
   206  		recorder:            record.NewFakeRecorder(32),
   207  	}
   208  
   209  	controlPlane := &internal.ControlPlane{
   210  		KCP:     kcp,
   211  		Cluster: cluster,
   212  	}
   213  
   214  	result, err := r.reconcileKubeconfig(ctx, controlPlane)
   215  	g.Expect(err).To(Succeed())
   216  	g.Expect(result).To(BeZero())
   217  
   218  	kubeconfigSecret := &corev1.Secret{}
   219  	secretName := client.ObjectKey{
   220  		Namespace: metav1.NamespaceDefault,
   221  		Name:      secret.Name(cluster.Name, secret.Kubeconfig),
   222  	}
   223  	g.Expect(r.Client.Get(ctx, secretName, kubeconfigSecret)).To(Succeed())
   224  	g.Expect(kubeconfigSecret.Labels).To(Equal(existingKubeconfigSecret.Labels))
   225  	g.Expect(kubeconfigSecret.Data).To(Equal(existingKubeconfigSecret.Data))
   226  	g.Expect(kubeconfigSecret.OwnerReferences).ToNot(ContainElement(*metav1.NewControllerRef(kcp, controlplanev1.GroupVersion.WithKind("KubeadmControlPlane"))))
   227  }
   228  
   229  func TestKubeadmControlPlaneReconciler_reconcileKubeconfig(t *testing.T) {
   230  	g := NewWithT(t)
   231  
   232  	cluster := &clusterv1.Cluster{
   233  		TypeMeta: metav1.TypeMeta{
   234  			Kind:       "Cluster",
   235  			APIVersion: clusterv1.GroupVersion.String(),
   236  		},
   237  		ObjectMeta: metav1.ObjectMeta{
   238  			Name:      "foo",
   239  			Namespace: metav1.NamespaceDefault,
   240  		},
   241  		Spec: clusterv1.ClusterSpec{
   242  			ControlPlaneEndpoint: clusterv1.APIEndpoint{Host: "test.local", Port: 8443},
   243  		},
   244  	}
   245  
   246  	kcp := &controlplanev1.KubeadmControlPlane{
   247  		TypeMeta: metav1.TypeMeta{
   248  			Kind:       "KubeadmControlPlane",
   249  			APIVersion: controlplanev1.GroupVersion.String(),
   250  		},
   251  		ObjectMeta: metav1.ObjectMeta{
   252  			Name:      "foo",
   253  			Namespace: metav1.NamespaceDefault,
   254  		},
   255  		Spec: controlplanev1.KubeadmControlPlaneSpec{
   256  			Version: "v1.16.6",
   257  		},
   258  	}
   259  
   260  	clusterCerts := secret.NewCertificatesForInitialControlPlane(&bootstrapv1.ClusterConfiguration{})
   261  	g.Expect(clusterCerts.Generate()).To(Succeed())
   262  	caCert := clusterCerts.GetByPurpose(secret.ClusterCA)
   263  	existingCACertSecret := caCert.AsSecret(
   264  		client.ObjectKey{Namespace: metav1.NamespaceDefault, Name: "foo"},
   265  		*metav1.NewControllerRef(kcp, controlplanev1.GroupVersion.WithKind("KubeadmControlPlane")),
   266  	)
   267  
   268  	fakeClient := newFakeClient(kcp.DeepCopy(), existingCACertSecret.DeepCopy())
   269  	r := &KubeadmControlPlaneReconciler{
   270  		Client:              fakeClient,
   271  		SecretCachingClient: fakeClient,
   272  		recorder:            record.NewFakeRecorder(32),
   273  	}
   274  
   275  	controlPlane := &internal.ControlPlane{
   276  		KCP:     kcp,
   277  		Cluster: cluster,
   278  	}
   279  
   280  	result, err := r.reconcileKubeconfig(ctx, controlPlane)
   281  	g.Expect(err).ToNot(HaveOccurred())
   282  	g.Expect(result).To(BeComparableTo(ctrl.Result{}))
   283  
   284  	kubeconfigSecret := &corev1.Secret{}
   285  	secretName := client.ObjectKey{
   286  		Namespace: metav1.NamespaceDefault,
   287  		Name:      secret.Name(cluster.Name, secret.Kubeconfig),
   288  	}
   289  	g.Expect(r.Client.Get(ctx, secretName, kubeconfigSecret)).To(Succeed())
   290  	g.Expect(kubeconfigSecret.OwnerReferences).NotTo(BeEmpty())
   291  	g.Expect(kubeconfigSecret.OwnerReferences).To(ContainElement(*metav1.NewControllerRef(kcp, controlplanev1.GroupVersion.WithKind("KubeadmControlPlane"))))
   292  	g.Expect(kubeconfigSecret.Labels).To(HaveKeyWithValue(clusterv1.ClusterNameLabel, cluster.Name))
   293  }
   294  
   295  func TestCloneConfigsAndGenerateMachine(t *testing.T) {
   296  	setup := func(t *testing.T, g *WithT) *corev1.Namespace {
   297  		t.Helper()
   298  
   299  		t.Log("Creating the namespace")
   300  		ns, err := env.CreateNamespace(ctx, "test-applykubeadmconfig")
   301  		g.Expect(err).ToNot(HaveOccurred())
   302  
   303  		return ns
   304  	}
   305  
   306  	teardown := func(t *testing.T, g *WithT, ns *corev1.Namespace) {
   307  		t.Helper()
   308  
   309  		t.Log("Deleting the namespace")
   310  		g.Expect(env.Delete(ctx, ns)).To(Succeed())
   311  	}
   312  
   313  	g := NewWithT(t)
   314  	namespace := setup(t, g)
   315  	defer teardown(t, g, namespace)
   316  
   317  	cluster := &clusterv1.Cluster{
   318  		ObjectMeta: metav1.ObjectMeta{
   319  			Name:      "foo",
   320  			Namespace: namespace.Name,
   321  		},
   322  	}
   323  
   324  	genericInfrastructureMachineTemplate := &unstructured.Unstructured{
   325  		Object: map[string]interface{}{
   326  			"kind":       "GenericInfrastructureMachineTemplate",
   327  			"apiVersion": "infrastructure.cluster.x-k8s.io/v1beta1",
   328  			"metadata": map[string]interface{}{
   329  				"name":      "infra-foo",
   330  				"namespace": cluster.Namespace,
   331  			},
   332  			"spec": map[string]interface{}{
   333  				"template": map[string]interface{}{
   334  					"spec": map[string]interface{}{
   335  						"hello": "world",
   336  					},
   337  				},
   338  			},
   339  		},
   340  	}
   341  	g.Expect(env.Create(ctx, genericInfrastructureMachineTemplate)).To(Succeed())
   342  
   343  	kcp := &controlplanev1.KubeadmControlPlane{
   344  		ObjectMeta: metav1.ObjectMeta{
   345  			Name:      "kcp-foo",
   346  			Namespace: cluster.Namespace,
   347  			UID:       "abc-123-kcp-control-plane",
   348  		},
   349  		Spec: controlplanev1.KubeadmControlPlaneSpec{
   350  			MachineTemplate: controlplanev1.KubeadmControlPlaneMachineTemplate{
   351  				InfrastructureRef: corev1.ObjectReference{
   352  					Kind:       genericInfrastructureMachineTemplate.GetKind(),
   353  					APIVersion: genericInfrastructureMachineTemplate.GetAPIVersion(),
   354  					Name:       genericInfrastructureMachineTemplate.GetName(),
   355  					Namespace:  cluster.Namespace,
   356  				},
   357  			},
   358  			Version: "v1.16.6",
   359  		},
   360  	}
   361  
   362  	r := &KubeadmControlPlaneReconciler{
   363  		Client:              env,
   364  		SecretCachingClient: secretCachingClient,
   365  		recorder:            record.NewFakeRecorder(32),
   366  	}
   367  
   368  	bootstrapSpec := &bootstrapv1.KubeadmConfigSpec{
   369  		JoinConfiguration: &bootstrapv1.JoinConfiguration{},
   370  	}
   371  	g.Expect(r.cloneConfigsAndGenerateMachine(ctx, cluster, kcp, bootstrapSpec, nil)).To(Succeed())
   372  
   373  	machineList := &clusterv1.MachineList{}
   374  	g.Expect(env.GetAPIReader().List(ctx, machineList, client.InNamespace(cluster.Namespace))).To(Succeed())
   375  	g.Expect(machineList.Items).To(HaveLen(1))
   376  
   377  	for i := range machineList.Items {
   378  		m := machineList.Items[i]
   379  		g.Expect(m.Namespace).To(Equal(cluster.Namespace))
   380  		g.Expect(m.Name).NotTo(BeEmpty())
   381  		g.Expect(m.Name).To(HavePrefix(kcp.Name))
   382  
   383  		infraObj, err := external.Get(ctx, r.Client, &m.Spec.InfrastructureRef, m.Spec.InfrastructureRef.Namespace)
   384  		g.Expect(err).ToNot(HaveOccurred())
   385  		g.Expect(infraObj.GetAnnotations()).To(HaveKeyWithValue(clusterv1.TemplateClonedFromNameAnnotation, genericInfrastructureMachineTemplate.GetName()))
   386  		g.Expect(infraObj.GetAnnotations()).To(HaveKeyWithValue(clusterv1.TemplateClonedFromGroupKindAnnotation, genericInfrastructureMachineTemplate.GroupVersionKind().GroupKind().String()))
   387  
   388  		g.Expect(m.Spec.InfrastructureRef.Namespace).To(Equal(cluster.Namespace))
   389  		g.Expect(m.Spec.InfrastructureRef.Name).To(HavePrefix(genericInfrastructureMachineTemplate.GetName()))
   390  		g.Expect(m.Spec.InfrastructureRef.APIVersion).To(Equal(genericInfrastructureMachineTemplate.GetAPIVersion()))
   391  		g.Expect(m.Spec.InfrastructureRef.Kind).To(Equal("GenericInfrastructureMachine"))
   392  
   393  		g.Expect(m.Spec.Bootstrap.ConfigRef.Namespace).To(Equal(cluster.Namespace))
   394  		g.Expect(m.Spec.Bootstrap.ConfigRef.Name).To(HavePrefix(kcp.Name))
   395  		g.Expect(m.Spec.Bootstrap.ConfigRef.APIVersion).To(Equal(bootstrapv1.GroupVersion.String()))
   396  		g.Expect(m.Spec.Bootstrap.ConfigRef.Kind).To(Equal("KubeadmConfig"))
   397  	}
   398  }
   399  
   400  func TestCloneConfigsAndGenerateMachineFail(t *testing.T) {
   401  	g := NewWithT(t)
   402  
   403  	cluster := &clusterv1.Cluster{
   404  		ObjectMeta: metav1.ObjectMeta{
   405  			Name:      "foo",
   406  			Namespace: metav1.NamespaceDefault,
   407  		},
   408  	}
   409  
   410  	genericMachineTemplate := &unstructured.Unstructured{
   411  		Object: map[string]interface{}{
   412  			"kind":       "GenericMachineTemplate",
   413  			"apiVersion": "generic.io/v1",
   414  			"metadata": map[string]interface{}{
   415  				"name":      "infra-foo",
   416  				"namespace": cluster.Namespace,
   417  			},
   418  			"spec": map[string]interface{}{
   419  				"template": map[string]interface{}{
   420  					"spec": map[string]interface{}{
   421  						"hello": "world",
   422  					},
   423  				},
   424  			},
   425  		},
   426  	}
   427  
   428  	kcp := &controlplanev1.KubeadmControlPlane{
   429  		ObjectMeta: metav1.ObjectMeta{
   430  			Name:      "kcp-foo",
   431  			Namespace: cluster.Namespace,
   432  		},
   433  		Spec: controlplanev1.KubeadmControlPlaneSpec{
   434  			MachineTemplate: controlplanev1.KubeadmControlPlaneMachineTemplate{
   435  				InfrastructureRef: corev1.ObjectReference{
   436  					Kind:       genericMachineTemplate.GetKind(),
   437  					APIVersion: genericMachineTemplate.GetAPIVersion(),
   438  					Name:       genericMachineTemplate.GetName(),
   439  					Namespace:  cluster.Namespace,
   440  				},
   441  			},
   442  			Version: "v1.16.6",
   443  		},
   444  	}
   445  
   446  	fakeClient := newFakeClient(cluster.DeepCopy(), kcp.DeepCopy(), genericMachineTemplate.DeepCopy())
   447  
   448  	r := &KubeadmControlPlaneReconciler{
   449  		Client:              fakeClient,
   450  		SecretCachingClient: fakeClient,
   451  		recorder:            record.NewFakeRecorder(32),
   452  	}
   453  
   454  	bootstrapSpec := &bootstrapv1.KubeadmConfigSpec{
   455  		JoinConfiguration: &bootstrapv1.JoinConfiguration{},
   456  	}
   457  
   458  	// Try to break Infra Cloning
   459  	kcp.Spec.MachineTemplate.InfrastructureRef.Name = "something_invalid"
   460  	g.Expect(r.cloneConfigsAndGenerateMachine(ctx, cluster, kcp, bootstrapSpec, nil)).To(HaveOccurred())
   461  	g.Expect(&kcp.GetConditions()[0]).Should(conditions.HaveSameStateOf(&clusterv1.Condition{
   462  		Type:     controlplanev1.MachinesCreatedCondition,
   463  		Status:   corev1.ConditionFalse,
   464  		Severity: clusterv1.ConditionSeverityError,
   465  		Reason:   controlplanev1.InfrastructureTemplateCloningFailedReason,
   466  		Message:  "failed to retrieve GenericMachineTemplate external object \"default\"/\"something_invalid\": genericmachinetemplates.generic.io \"something_invalid\" not found",
   467  	}))
   468  }
   469  
   470  func TestKubeadmControlPlaneReconciler_computeDesiredMachine(t *testing.T) {
   471  	cluster := &clusterv1.Cluster{
   472  		ObjectMeta: metav1.ObjectMeta{
   473  			Name:      "testCluster",
   474  			Namespace: metav1.NamespaceDefault,
   475  		},
   476  	}
   477  
   478  	duration5s := &metav1.Duration{Duration: 5 * time.Second}
   479  	duration10s := &metav1.Duration{Duration: 10 * time.Second}
   480  
   481  	kcpMachineTemplateObjectMeta := clusterv1.ObjectMeta{
   482  		Labels: map[string]string{
   483  			"machineTemplateLabel": "machineTemplateLabelValue",
   484  		},
   485  		Annotations: map[string]string{
   486  			"machineTemplateAnnotation": "machineTemplateAnnotationValue",
   487  		},
   488  	}
   489  	kcpMachineTemplateObjectMetaCopy := kcpMachineTemplateObjectMeta.DeepCopy()
   490  	kcp := &controlplanev1.KubeadmControlPlane{
   491  		ObjectMeta: metav1.ObjectMeta{
   492  			Name:      "testControlPlane",
   493  			Namespace: cluster.Namespace,
   494  		},
   495  		Spec: controlplanev1.KubeadmControlPlaneSpec{
   496  			Version: "v1.16.6",
   497  			MachineTemplate: controlplanev1.KubeadmControlPlaneMachineTemplate{
   498  				ObjectMeta:              kcpMachineTemplateObjectMeta,
   499  				NodeDrainTimeout:        duration5s,
   500  				NodeDeletionTimeout:     duration5s,
   501  				NodeVolumeDetachTimeout: duration5s,
   502  			},
   503  			KubeadmConfigSpec: bootstrapv1.KubeadmConfigSpec{
   504  				ClusterConfiguration: &bootstrapv1.ClusterConfiguration{
   505  					ClusterName: "testCluster",
   506  				},
   507  			},
   508  		},
   509  	}
   510  	clusterConfigurationString := "{\"etcd\":{},\"networking\":{},\"apiServer\":{},\"controllerManager\":{},\"scheduler\":{},\"dns\":{},\"clusterName\":\"testCluster\"}"
   511  
   512  	infraRef := &corev1.ObjectReference{
   513  		Kind:       "InfraKind",
   514  		APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1",
   515  		Name:       "infra",
   516  		Namespace:  cluster.Namespace,
   517  	}
   518  	bootstrapRef := &corev1.ObjectReference{
   519  		Kind:       "BootstrapKind",
   520  		APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1",
   521  		Name:       "bootstrap",
   522  		Namespace:  cluster.Namespace,
   523  	}
   524  
   525  	t.Run("should return the correct Machine object when creating a new Machine", func(t *testing.T) {
   526  		g := NewWithT(t)
   527  
   528  		failureDomain := pointer.String("fd1")
   529  		createdMachine, err := (&KubeadmControlPlaneReconciler{}).computeDesiredMachine(
   530  			kcp, cluster,
   531  			infraRef, bootstrapRef,
   532  			failureDomain, nil,
   533  		)
   534  		g.Expect(err).ToNot(HaveOccurred())
   535  
   536  		expectedMachineSpec := clusterv1.MachineSpec{
   537  			ClusterName: cluster.Name,
   538  			Version:     pointer.String(kcp.Spec.Version),
   539  			Bootstrap: clusterv1.Bootstrap{
   540  				ConfigRef: bootstrapRef,
   541  			},
   542  			InfrastructureRef:       *infraRef,
   543  			FailureDomain:           failureDomain,
   544  			NodeDrainTimeout:        kcp.Spec.MachineTemplate.NodeDrainTimeout,
   545  			NodeDeletionTimeout:     kcp.Spec.MachineTemplate.NodeDeletionTimeout,
   546  			NodeVolumeDetachTimeout: kcp.Spec.MachineTemplate.NodeVolumeDetachTimeout,
   547  		}
   548  		g.Expect(createdMachine.Name).To(HavePrefix(kcp.Name))
   549  		g.Expect(createdMachine.Namespace).To(Equal(kcp.Namespace))
   550  		g.Expect(createdMachine.OwnerReferences).To(HaveLen(1))
   551  		g.Expect(createdMachine.OwnerReferences).To(ContainElement(*metav1.NewControllerRef(kcp, controlplanev1.GroupVersion.WithKind("KubeadmControlPlane"))))
   552  		g.Expect(createdMachine.Spec).To(BeComparableTo(expectedMachineSpec))
   553  
   554  		// Verify that the machineTemplate.ObjectMeta has been propagated to the Machine.
   555  		// Verify labels.
   556  		expectedLabels := map[string]string{}
   557  		for k, v := range kcpMachineTemplateObjectMeta.Labels {
   558  			expectedLabels[k] = v
   559  		}
   560  		expectedLabels[clusterv1.ClusterNameLabel] = cluster.Name
   561  		expectedLabels[clusterv1.MachineControlPlaneLabel] = ""
   562  		expectedLabels[clusterv1.MachineControlPlaneNameLabel] = kcp.Name
   563  		g.Expect(createdMachine.Labels).To(Equal(expectedLabels))
   564  		// Verify annotations.
   565  		expectedAnnotations := map[string]string{}
   566  		for k, v := range kcpMachineTemplateObjectMeta.Annotations {
   567  			expectedAnnotations[k] = v
   568  		}
   569  		expectedAnnotations[controlplanev1.KubeadmClusterConfigurationAnnotation] = clusterConfigurationString
   570  		g.Expect(createdMachine.Annotations).To(Equal(expectedAnnotations))
   571  
   572  		// Verify that machineTemplate.ObjectMeta in KCP has not been modified.
   573  		g.Expect(kcp.Spec.MachineTemplate.ObjectMeta.Labels).To(Equal(kcpMachineTemplateObjectMetaCopy.Labels))
   574  		g.Expect(kcp.Spec.MachineTemplate.ObjectMeta.Annotations).To(Equal(kcpMachineTemplateObjectMetaCopy.Annotations))
   575  	})
   576  
   577  	t.Run("should return the correct Machine object when updating an existing Machine", func(t *testing.T) {
   578  		g := NewWithT(t)
   579  
   580  		machineName := "existing-machine"
   581  		machineUID := types.UID("abc-123-existing-machine")
   582  		// Use different ClusterConfiguration string than the information present in KCP
   583  		// to verify that for an existing machine we do not override this information.
   584  		existingClusterConfigurationString := "existing-cluster-configuration-information"
   585  		remediationData := "remediation-data"
   586  		failureDomain := pointer.String("fd-1")
   587  		machineVersion := pointer.String("v1.25.3")
   588  		existingMachine := &clusterv1.Machine{
   589  			ObjectMeta: metav1.ObjectMeta{
   590  				Name: machineName,
   591  				UID:  machineUID,
   592  				Annotations: map[string]string{
   593  					controlplanev1.KubeadmClusterConfigurationAnnotation: existingClusterConfigurationString,
   594  					controlplanev1.RemediationForAnnotation:              remediationData,
   595  				},
   596  			},
   597  			Spec: clusterv1.MachineSpec{
   598  				Version:                 machineVersion,
   599  				FailureDomain:           failureDomain,
   600  				NodeDrainTimeout:        duration10s,
   601  				NodeDeletionTimeout:     duration10s,
   602  				NodeVolumeDetachTimeout: duration10s,
   603  			},
   604  		}
   605  
   606  		updatedMachine, err := (&KubeadmControlPlaneReconciler{}).computeDesiredMachine(
   607  			kcp, cluster,
   608  			infraRef, bootstrapRef,
   609  			existingMachine.Spec.FailureDomain, existingMachine,
   610  		)
   611  		g.Expect(err).ToNot(HaveOccurred())
   612  
   613  		expectedMachineSpec := clusterv1.MachineSpec{
   614  			ClusterName: cluster.Name,
   615  			Version:     machineVersion, // Should use the Machine version and not the version from KCP.
   616  			Bootstrap: clusterv1.Bootstrap{
   617  				ConfigRef: bootstrapRef,
   618  			},
   619  			InfrastructureRef:       *infraRef,
   620  			FailureDomain:           failureDomain,
   621  			NodeDrainTimeout:        kcp.Spec.MachineTemplate.NodeDrainTimeout,
   622  			NodeDeletionTimeout:     kcp.Spec.MachineTemplate.NodeDeletionTimeout,
   623  			NodeVolumeDetachTimeout: kcp.Spec.MachineTemplate.NodeVolumeDetachTimeout,
   624  		}
   625  		g.Expect(updatedMachine.Namespace).To(Equal(kcp.Namespace))
   626  		g.Expect(updatedMachine.OwnerReferences).To(HaveLen(1))
   627  		g.Expect(updatedMachine.OwnerReferences).To(ContainElement(*metav1.NewControllerRef(kcp, controlplanev1.GroupVersion.WithKind("KubeadmControlPlane"))))
   628  		g.Expect(updatedMachine.Spec).To(BeComparableTo(expectedMachineSpec))
   629  
   630  		// Verify the Name and UID of the Machine remain unchanged
   631  		g.Expect(updatedMachine.Name).To(Equal(machineName))
   632  		g.Expect(updatedMachine.UID).To(Equal(machineUID))
   633  
   634  		// Verify that the machineTemplate.ObjectMeta has been propagated to the Machine.
   635  		// Verify labels.
   636  		expectedLabels := map[string]string{}
   637  		for k, v := range kcpMachineTemplateObjectMeta.Labels {
   638  			expectedLabels[k] = v
   639  		}
   640  		expectedLabels[clusterv1.ClusterNameLabel] = cluster.Name
   641  		expectedLabels[clusterv1.MachineControlPlaneLabel] = ""
   642  		expectedLabels[clusterv1.MachineControlPlaneNameLabel] = kcp.Name
   643  		g.Expect(updatedMachine.Labels).To(Equal(expectedLabels))
   644  		// Verify annotations.
   645  		expectedAnnotations := map[string]string{}
   646  		for k, v := range kcpMachineTemplateObjectMeta.Annotations {
   647  			expectedAnnotations[k] = v
   648  		}
   649  		expectedAnnotations[controlplanev1.KubeadmClusterConfigurationAnnotation] = existingClusterConfigurationString
   650  		expectedAnnotations[controlplanev1.RemediationForAnnotation] = remediationData
   651  		g.Expect(updatedMachine.Annotations).To(Equal(expectedAnnotations))
   652  
   653  		// Verify that machineTemplate.ObjectMeta in KCP has not been modified.
   654  		g.Expect(kcp.Spec.MachineTemplate.ObjectMeta.Labels).To(Equal(kcpMachineTemplateObjectMetaCopy.Labels))
   655  		g.Expect(kcp.Spec.MachineTemplate.ObjectMeta.Annotations).To(Equal(kcpMachineTemplateObjectMetaCopy.Annotations))
   656  	})
   657  }
   658  
   659  func TestKubeadmControlPlaneReconciler_generateKubeadmConfig(t *testing.T) {
   660  	g := NewWithT(t)
   661  	fakeClient := newFakeClient()
   662  
   663  	cluster := &clusterv1.Cluster{
   664  		ObjectMeta: metav1.ObjectMeta{
   665  			Name:      "testCluster",
   666  			Namespace: metav1.NamespaceDefault,
   667  		},
   668  	}
   669  
   670  	kcp := &controlplanev1.KubeadmControlPlane{
   671  		ObjectMeta: metav1.ObjectMeta{
   672  			Name:      "testControlPlane",
   673  			Namespace: cluster.Namespace,
   674  		},
   675  	}
   676  
   677  	spec := bootstrapv1.KubeadmConfigSpec{}
   678  	expectedReferenceKind := "KubeadmConfig"
   679  	expectedReferenceAPIVersion := bootstrapv1.GroupVersion.String()
   680  	expectedOwner := metav1.OwnerReference{
   681  		Kind:       "KubeadmControlPlane",
   682  		APIVersion: controlplanev1.GroupVersion.String(),
   683  		Name:       kcp.Name,
   684  	}
   685  
   686  	r := &KubeadmControlPlaneReconciler{
   687  		Client:              fakeClient,
   688  		SecretCachingClient: fakeClient,
   689  		recorder:            record.NewFakeRecorder(32),
   690  	}
   691  
   692  	got, err := r.generateKubeadmConfig(ctx, kcp, cluster, spec.DeepCopy())
   693  	g.Expect(err).ToNot(HaveOccurred())
   694  	g.Expect(got).NotTo(BeNil())
   695  	g.Expect(got.Name).To(HavePrefix(kcp.Name))
   696  	g.Expect(got.Namespace).To(Equal(kcp.Namespace))
   697  	g.Expect(got.Kind).To(Equal(expectedReferenceKind))
   698  	g.Expect(got.APIVersion).To(Equal(expectedReferenceAPIVersion))
   699  
   700  	bootstrapConfig := &bootstrapv1.KubeadmConfig{}
   701  	key := client.ObjectKey{Name: got.Name, Namespace: got.Namespace}
   702  	g.Expect(fakeClient.Get(ctx, key, bootstrapConfig)).To(Succeed())
   703  	g.Expect(bootstrapConfig.OwnerReferences).To(HaveLen(1))
   704  	g.Expect(bootstrapConfig.OwnerReferences).To(ContainElement(expectedOwner))
   705  	g.Expect(bootstrapConfig.Spec).To(BeComparableTo(spec))
   706  }
   707  
   708  func TestKubeadmControlPlaneReconciler_adoptKubeconfigSecret(t *testing.T) {
   709  	g := NewWithT(t)
   710  	otherOwner := metav1.OwnerReference{
   711  		Name:               "testcontroller",
   712  		UID:                "5",
   713  		Kind:               "OtherController",
   714  		APIVersion:         clusterv1.GroupVersion.String(),
   715  		Controller:         pointer.Bool(true),
   716  		BlockOwnerDeletion: pointer.Bool(true),
   717  	}
   718  
   719  	// A KubeadmConfig secret created by CAPI controllers with no owner references.
   720  	capiKubeadmConfigSecretNoOwner := kubeconfig.GenerateSecretWithOwner(
   721  		client.ObjectKey{Name: "test1", Namespace: metav1.NamespaceDefault},
   722  		[]byte{},
   723  		metav1.OwnerReference{})
   724  	capiKubeadmConfigSecretNoOwner.OwnerReferences = []metav1.OwnerReference{}
   725  
   726  	// A KubeadmConfig secret created by CAPI controllers with a non-KCP owner reference.
   727  	capiKubeadmConfigSecretOtherOwner := capiKubeadmConfigSecretNoOwner.DeepCopy()
   728  	capiKubeadmConfigSecretOtherOwner.OwnerReferences = []metav1.OwnerReference{otherOwner}
   729  
   730  	// A user provided KubeadmConfig secret with no owner reference.
   731  	userProvidedKubeadmConfigSecretNoOwner := kubeconfig.GenerateSecretWithOwner(
   732  		client.ObjectKey{Name: "test1", Namespace: metav1.NamespaceDefault},
   733  		[]byte{},
   734  		metav1.OwnerReference{})
   735  	userProvidedKubeadmConfigSecretNoOwner.Type = corev1.SecretTypeOpaque
   736  
   737  	// A user provided KubeadmConfig with a non-KCP owner reference.
   738  	userProvidedKubeadmConfigSecretOtherOwner := userProvidedKubeadmConfigSecretNoOwner.DeepCopy()
   739  	userProvidedKubeadmConfigSecretOtherOwner.OwnerReferences = []metav1.OwnerReference{otherOwner}
   740  
   741  	kcp := &controlplanev1.KubeadmControlPlane{
   742  		TypeMeta: metav1.TypeMeta{
   743  			Kind:       "KubeadmControlPlane",
   744  			APIVersion: controlplanev1.GroupVersion.String(),
   745  		},
   746  		ObjectMeta: metav1.ObjectMeta{
   747  			Name:      "testControlPlane",
   748  			Namespace: metav1.NamespaceDefault,
   749  		},
   750  	}
   751  	tests := []struct {
   752  		name             string
   753  		configSecret     *corev1.Secret
   754  		expectedOwnerRef metav1.OwnerReference
   755  	}{
   756  		{
   757  			name:         "add KCP owner reference on kubeconfig secret generated by CAPI",
   758  			configSecret: capiKubeadmConfigSecretNoOwner,
   759  			expectedOwnerRef: metav1.OwnerReference{
   760  				Name:               kcp.Name,
   761  				UID:                kcp.UID,
   762  				Kind:               kcp.Kind,
   763  				APIVersion:         kcp.APIVersion,
   764  				Controller:         pointer.Bool(true),
   765  				BlockOwnerDeletion: pointer.Bool(true),
   766  			},
   767  		},
   768  		{
   769  			name:         "replace owner reference with KCP on kubeconfig secret generated by CAPI with other owner",
   770  			configSecret: capiKubeadmConfigSecretOtherOwner,
   771  			expectedOwnerRef: metav1.OwnerReference{
   772  				Name:               kcp.Name,
   773  				UID:                kcp.UID,
   774  				Kind:               kcp.Kind,
   775  				APIVersion:         kcp.APIVersion,
   776  				Controller:         pointer.Bool(true),
   777  				BlockOwnerDeletion: pointer.Bool(true),
   778  			},
   779  		},
   780  		{
   781  			name:             "don't add ownerReference on kubeconfig secret provided by user",
   782  			configSecret:     userProvidedKubeadmConfigSecretNoOwner,
   783  			expectedOwnerRef: metav1.OwnerReference{},
   784  		},
   785  		{
   786  			name:             "don't replace ownerReference on kubeconfig secret provided by user",
   787  			configSecret:     userProvidedKubeadmConfigSecretOtherOwner,
   788  			expectedOwnerRef: otherOwner,
   789  		},
   790  	}
   791  	for _, tt := range tests {
   792  		t.Run(tt.name, func(t *testing.T) {
   793  			fakeClient := newFakeClient(kcp, tt.configSecret)
   794  			r := &KubeadmControlPlaneReconciler{
   795  				Client:              fakeClient,
   796  				SecretCachingClient: fakeClient,
   797  			}
   798  			g.Expect(r.adoptKubeconfigSecret(ctx, tt.configSecret, kcp)).To(Succeed())
   799  			actualSecret := &corev1.Secret{}
   800  			g.Expect(fakeClient.Get(ctx, client.ObjectKey{Namespace: tt.configSecret.Namespace, Name: tt.configSecret.Name}, actualSecret)).To(Succeed())
   801  			g.Expect(actualSecret.GetOwnerReferences()).To(ConsistOf(tt.expectedOwnerRef))
   802  		})
   803  	}
   804  }