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 }