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 }