sigs.k8s.io/cluster-api@v1.7.1/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/ptr"
    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(Equal(m.Name))
   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(Equal(m.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 := ptr.To("fd1")
   529  		createdMachine, err := (&KubeadmControlPlaneReconciler{}).computeDesiredMachine(
   530  			kcp, cluster,
   531  			failureDomain, nil,
   532  		)
   533  		g.Expect(err).ToNot(HaveOccurred())
   534  
   535  		expectedMachineSpec := clusterv1.MachineSpec{
   536  			ClusterName:             cluster.Name,
   537  			Version:                 ptr.To(kcp.Spec.Version),
   538  			FailureDomain:           failureDomain,
   539  			NodeDrainTimeout:        kcp.Spec.MachineTemplate.NodeDrainTimeout,
   540  			NodeDeletionTimeout:     kcp.Spec.MachineTemplate.NodeDeletionTimeout,
   541  			NodeVolumeDetachTimeout: kcp.Spec.MachineTemplate.NodeVolumeDetachTimeout,
   542  		}
   543  		g.Expect(createdMachine.Name).To(HavePrefix(kcp.Name))
   544  		g.Expect(createdMachine.Namespace).To(Equal(kcp.Namespace))
   545  		g.Expect(createdMachine.OwnerReferences).To(HaveLen(1))
   546  		g.Expect(createdMachine.OwnerReferences).To(ContainElement(*metav1.NewControllerRef(kcp, controlplanev1.GroupVersion.WithKind("KubeadmControlPlane"))))
   547  		g.Expect(createdMachine.Spec).To(BeComparableTo(expectedMachineSpec))
   548  
   549  		// Verify that the machineTemplate.ObjectMeta has been propagated to the Machine.
   550  		// Verify labels.
   551  		expectedLabels := map[string]string{}
   552  		for k, v := range kcpMachineTemplateObjectMeta.Labels {
   553  			expectedLabels[k] = v
   554  		}
   555  		expectedLabels[clusterv1.ClusterNameLabel] = cluster.Name
   556  		expectedLabels[clusterv1.MachineControlPlaneLabel] = ""
   557  		expectedLabels[clusterv1.MachineControlPlaneNameLabel] = kcp.Name
   558  		g.Expect(createdMachine.Labels).To(Equal(expectedLabels))
   559  		// Verify annotations.
   560  		expectedAnnotations := map[string]string{}
   561  		for k, v := range kcpMachineTemplateObjectMeta.Annotations {
   562  			expectedAnnotations[k] = v
   563  		}
   564  		expectedAnnotations[controlplanev1.KubeadmClusterConfigurationAnnotation] = clusterConfigurationString
   565  		g.Expect(createdMachine.Annotations).To(Equal(expectedAnnotations))
   566  
   567  		// Verify that machineTemplate.ObjectMeta in KCP has not been modified.
   568  		g.Expect(kcp.Spec.MachineTemplate.ObjectMeta.Labels).To(Equal(kcpMachineTemplateObjectMetaCopy.Labels))
   569  		g.Expect(kcp.Spec.MachineTemplate.ObjectMeta.Annotations).To(Equal(kcpMachineTemplateObjectMetaCopy.Annotations))
   570  	})
   571  
   572  	t.Run("should return the correct Machine object when updating an existing Machine", func(t *testing.T) {
   573  		g := NewWithT(t)
   574  
   575  		machineName := "existing-machine"
   576  		machineUID := types.UID("abc-123-existing-machine")
   577  		// Use different ClusterConfiguration string than the information present in KCP
   578  		// to verify that for an existing machine we do not override this information.
   579  		existingClusterConfigurationString := "existing-cluster-configuration-information"
   580  		remediationData := "remediation-data"
   581  		failureDomain := ptr.To("fd-1")
   582  		machineVersion := ptr.To("v1.25.3")
   583  		existingMachine := &clusterv1.Machine{
   584  			ObjectMeta: metav1.ObjectMeta{
   585  				Name: machineName,
   586  				UID:  machineUID,
   587  				Annotations: map[string]string{
   588  					controlplanev1.KubeadmClusterConfigurationAnnotation: existingClusterConfigurationString,
   589  					controlplanev1.RemediationForAnnotation:              remediationData,
   590  				},
   591  			},
   592  			Spec: clusterv1.MachineSpec{
   593  				Version:                 machineVersion,
   594  				FailureDomain:           failureDomain,
   595  				NodeDrainTimeout:        duration10s,
   596  				NodeDeletionTimeout:     duration10s,
   597  				NodeVolumeDetachTimeout: duration10s,
   598  				Bootstrap: clusterv1.Bootstrap{
   599  					ConfigRef: bootstrapRef,
   600  				},
   601  				InfrastructureRef: *infraRef,
   602  			},
   603  		}
   604  
   605  		updatedMachine, err := (&KubeadmControlPlaneReconciler{}).computeDesiredMachine(
   606  			kcp, cluster,
   607  			existingMachine.Spec.FailureDomain, existingMachine,
   608  		)
   609  		g.Expect(err).ToNot(HaveOccurred())
   610  
   611  		expectedMachineSpec := clusterv1.MachineSpec{
   612  			ClusterName: cluster.Name,
   613  			Version:     machineVersion, // Should use the Machine version and not the version from KCP.
   614  			Bootstrap: clusterv1.Bootstrap{
   615  				ConfigRef: bootstrapRef,
   616  			},
   617  			InfrastructureRef:       *infraRef,
   618  			FailureDomain:           failureDomain,
   619  			NodeDrainTimeout:        kcp.Spec.MachineTemplate.NodeDrainTimeout,
   620  			NodeDeletionTimeout:     kcp.Spec.MachineTemplate.NodeDeletionTimeout,
   621  			NodeVolumeDetachTimeout: kcp.Spec.MachineTemplate.NodeVolumeDetachTimeout,
   622  		}
   623  		g.Expect(updatedMachine.Namespace).To(Equal(kcp.Namespace))
   624  		g.Expect(updatedMachine.OwnerReferences).To(HaveLen(1))
   625  		g.Expect(updatedMachine.OwnerReferences).To(ContainElement(*metav1.NewControllerRef(kcp, controlplanev1.GroupVersion.WithKind("KubeadmControlPlane"))))
   626  		g.Expect(updatedMachine.Spec).To(BeComparableTo(expectedMachineSpec))
   627  
   628  		// Verify the Name and UID of the Machine remain unchanged
   629  		g.Expect(updatedMachine.Name).To(Equal(machineName))
   630  		g.Expect(updatedMachine.UID).To(Equal(machineUID))
   631  
   632  		// Verify that the machineTemplate.ObjectMeta has been propagated to the Machine.
   633  		// Verify labels.
   634  		expectedLabels := map[string]string{}
   635  		for k, v := range kcpMachineTemplateObjectMeta.Labels {
   636  			expectedLabels[k] = v
   637  		}
   638  		expectedLabels[clusterv1.ClusterNameLabel] = cluster.Name
   639  		expectedLabels[clusterv1.MachineControlPlaneLabel] = ""
   640  		expectedLabels[clusterv1.MachineControlPlaneNameLabel] = kcp.Name
   641  		g.Expect(updatedMachine.Labels).To(Equal(expectedLabels))
   642  		// Verify annotations.
   643  		expectedAnnotations := map[string]string{}
   644  		for k, v := range kcpMachineTemplateObjectMeta.Annotations {
   645  			expectedAnnotations[k] = v
   646  		}
   647  		expectedAnnotations[controlplanev1.KubeadmClusterConfigurationAnnotation] = existingClusterConfigurationString
   648  		expectedAnnotations[controlplanev1.RemediationForAnnotation] = remediationData
   649  		g.Expect(updatedMachine.Annotations).To(Equal(expectedAnnotations))
   650  
   651  		// Verify that machineTemplate.ObjectMeta in KCP has not been modified.
   652  		g.Expect(kcp.Spec.MachineTemplate.ObjectMeta.Labels).To(Equal(kcpMachineTemplateObjectMetaCopy.Labels))
   653  		g.Expect(kcp.Spec.MachineTemplate.ObjectMeta.Annotations).To(Equal(kcpMachineTemplateObjectMetaCopy.Annotations))
   654  	})
   655  }
   656  
   657  func TestKubeadmControlPlaneReconciler_generateKubeadmConfig(t *testing.T) {
   658  	g := NewWithT(t)
   659  	fakeClient := newFakeClient()
   660  
   661  	cluster := &clusterv1.Cluster{
   662  		ObjectMeta: metav1.ObjectMeta{
   663  			Name:      "testCluster",
   664  			Namespace: metav1.NamespaceDefault,
   665  		},
   666  	}
   667  
   668  	kcp := &controlplanev1.KubeadmControlPlane{
   669  		ObjectMeta: metav1.ObjectMeta{
   670  			Name:      "testControlPlane",
   671  			Namespace: cluster.Namespace,
   672  		},
   673  	}
   674  
   675  	spec := bootstrapv1.KubeadmConfigSpec{}
   676  	expectedReferenceKind := "KubeadmConfig"
   677  	expectedReferenceAPIVersion := bootstrapv1.GroupVersion.String()
   678  	expectedOwner := metav1.OwnerReference{
   679  		Kind:       "KubeadmControlPlane",
   680  		APIVersion: controlplanev1.GroupVersion.String(),
   681  		Name:       kcp.Name,
   682  	}
   683  
   684  	r := &KubeadmControlPlaneReconciler{
   685  		Client:              fakeClient,
   686  		SecretCachingClient: fakeClient,
   687  		recorder:            record.NewFakeRecorder(32),
   688  	}
   689  
   690  	got, err := r.generateKubeadmConfig(ctx, kcp, cluster, spec.DeepCopy(), "kubeadmconfig-name")
   691  	g.Expect(err).ToNot(HaveOccurred())
   692  	g.Expect(got).NotTo(BeNil())
   693  	g.Expect(got.Name).To(Equal("kubeadmconfig-name"))
   694  	g.Expect(got.Namespace).To(Equal(kcp.Namespace))
   695  	g.Expect(got.Kind).To(Equal(expectedReferenceKind))
   696  	g.Expect(got.APIVersion).To(Equal(expectedReferenceAPIVersion))
   697  
   698  	bootstrapConfig := &bootstrapv1.KubeadmConfig{}
   699  	key := client.ObjectKey{Name: got.Name, Namespace: got.Namespace}
   700  	g.Expect(fakeClient.Get(ctx, key, bootstrapConfig)).To(Succeed())
   701  	g.Expect(bootstrapConfig.OwnerReferences).To(HaveLen(1))
   702  	g.Expect(bootstrapConfig.OwnerReferences).To(ContainElement(expectedOwner))
   703  	g.Expect(bootstrapConfig.Spec).To(BeComparableTo(spec))
   704  }
   705  
   706  func TestKubeadmControlPlaneReconciler_adoptKubeconfigSecret(t *testing.T) {
   707  	g := NewWithT(t)
   708  	otherOwner := metav1.OwnerReference{
   709  		Name:               "testcontroller",
   710  		UID:                "5",
   711  		Kind:               "OtherController",
   712  		APIVersion:         clusterv1.GroupVersion.String(),
   713  		Controller:         ptr.To(true),
   714  		BlockOwnerDeletion: ptr.To(true),
   715  	}
   716  
   717  	// A KubeadmConfig secret created by CAPI controllers with no owner references.
   718  	capiKubeadmConfigSecretNoOwner := kubeconfig.GenerateSecretWithOwner(
   719  		client.ObjectKey{Name: "test1", Namespace: metav1.NamespaceDefault},
   720  		[]byte{},
   721  		metav1.OwnerReference{})
   722  	capiKubeadmConfigSecretNoOwner.OwnerReferences = []metav1.OwnerReference{}
   723  
   724  	// A KubeadmConfig secret created by CAPI controllers with a non-KCP owner reference.
   725  	capiKubeadmConfigSecretOtherOwner := capiKubeadmConfigSecretNoOwner.DeepCopy()
   726  	capiKubeadmConfigSecretOtherOwner.OwnerReferences = []metav1.OwnerReference{otherOwner}
   727  
   728  	// A user provided KubeadmConfig secret with no owner reference.
   729  	userProvidedKubeadmConfigSecretNoOwner := kubeconfig.GenerateSecretWithOwner(
   730  		client.ObjectKey{Name: "test1", Namespace: metav1.NamespaceDefault},
   731  		[]byte{},
   732  		metav1.OwnerReference{})
   733  	userProvidedKubeadmConfigSecretNoOwner.Type = corev1.SecretTypeOpaque
   734  
   735  	// A user provided KubeadmConfig with a non-KCP owner reference.
   736  	userProvidedKubeadmConfigSecretOtherOwner := userProvidedKubeadmConfigSecretNoOwner.DeepCopy()
   737  	userProvidedKubeadmConfigSecretOtherOwner.OwnerReferences = []metav1.OwnerReference{otherOwner}
   738  
   739  	kcp := &controlplanev1.KubeadmControlPlane{
   740  		TypeMeta: metav1.TypeMeta{
   741  			Kind:       "KubeadmControlPlane",
   742  			APIVersion: controlplanev1.GroupVersion.String(),
   743  		},
   744  		ObjectMeta: metav1.ObjectMeta{
   745  			Name:      "testControlPlane",
   746  			Namespace: metav1.NamespaceDefault,
   747  		},
   748  	}
   749  	tests := []struct {
   750  		name             string
   751  		configSecret     *corev1.Secret
   752  		expectedOwnerRef metav1.OwnerReference
   753  	}{
   754  		{
   755  			name:         "add KCP owner reference on kubeconfig secret generated by CAPI",
   756  			configSecret: capiKubeadmConfigSecretNoOwner,
   757  			expectedOwnerRef: metav1.OwnerReference{
   758  				Name:               kcp.Name,
   759  				UID:                kcp.UID,
   760  				Kind:               kcp.Kind,
   761  				APIVersion:         kcp.APIVersion,
   762  				Controller:         ptr.To(true),
   763  				BlockOwnerDeletion: ptr.To(true),
   764  			},
   765  		},
   766  		{
   767  			name:         "replace owner reference with KCP on kubeconfig secret generated by CAPI with other owner",
   768  			configSecret: capiKubeadmConfigSecretOtherOwner,
   769  			expectedOwnerRef: metav1.OwnerReference{
   770  				Name:               kcp.Name,
   771  				UID:                kcp.UID,
   772  				Kind:               kcp.Kind,
   773  				APIVersion:         kcp.APIVersion,
   774  				Controller:         ptr.To(true),
   775  				BlockOwnerDeletion: ptr.To(true),
   776  			},
   777  		},
   778  		{
   779  			name:             "don't add ownerReference on kubeconfig secret provided by user",
   780  			configSecret:     userProvidedKubeadmConfigSecretNoOwner,
   781  			expectedOwnerRef: metav1.OwnerReference{},
   782  		},
   783  		{
   784  			name:             "don't replace ownerReference on kubeconfig secret provided by user",
   785  			configSecret:     userProvidedKubeadmConfigSecretOtherOwner,
   786  			expectedOwnerRef: otherOwner,
   787  		},
   788  	}
   789  	for _, tt := range tests {
   790  		t.Run(tt.name, func(*testing.T) {
   791  			fakeClient := newFakeClient(kcp, tt.configSecret)
   792  			r := &KubeadmControlPlaneReconciler{
   793  				Client:              fakeClient,
   794  				SecretCachingClient: fakeClient,
   795  			}
   796  			g.Expect(r.adoptKubeconfigSecret(ctx, tt.configSecret, kcp)).To(Succeed())
   797  			actualSecret := &corev1.Secret{}
   798  			g.Expect(fakeClient.Get(ctx, client.ObjectKey{Namespace: tt.configSecret.Namespace, Name: tt.configSecret.Name}, actualSecret)).To(Succeed())
   799  			g.Expect(actualSecret.GetOwnerReferences()).To(ConsistOf(tt.expectedOwnerRef))
   800  		})
   801  	}
   802  }