sigs.k8s.io/cluster-api@v1.7.1/internal/controllers/machine/machine_controller_phases_test.go (about) 1 /* 2 Copyright 2019 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package machine 18 19 import ( 20 "fmt" 21 "testing" 22 "time" 23 24 . "github.com/onsi/gomega" 25 corev1 "k8s.io/api/core/v1" 26 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 27 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 28 "k8s.io/utils/ptr" 29 ctrl "sigs.k8s.io/controller-runtime" 30 "sigs.k8s.io/controller-runtime/pkg/client" 31 "sigs.k8s.io/controller-runtime/pkg/client/fake" 32 33 clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" 34 "sigs.k8s.io/cluster-api/internal/test/builder" 35 "sigs.k8s.io/cluster-api/util" 36 "sigs.k8s.io/cluster-api/util/conditions" 37 "sigs.k8s.io/cluster-api/util/kubeconfig" 38 ) 39 40 func init() { 41 externalReadyWait = 1 * time.Second 42 } 43 44 func TestReconcileMachinePhases(t *testing.T) { 45 var defaultKubeconfigSecret *corev1.Secret 46 defaultCluster := &clusterv1.Cluster{ 47 ObjectMeta: metav1.ObjectMeta{ 48 Name: "test-cluster", 49 Namespace: metav1.NamespaceDefault, 50 }, 51 } 52 53 defaultMachine := clusterv1.Machine{ 54 ObjectMeta: metav1.ObjectMeta{ 55 Name: "machine-test", 56 Namespace: metav1.NamespaceDefault, 57 Labels: map[string]string{ 58 clusterv1.MachineControlPlaneLabel: "", 59 }, 60 }, 61 Spec: clusterv1.MachineSpec{ 62 ClusterName: defaultCluster.Name, 63 Bootstrap: clusterv1.Bootstrap{ 64 ConfigRef: &corev1.ObjectReference{ 65 APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1", 66 Kind: "GenericBootstrapConfig", 67 Name: "bootstrap-config1", 68 }, 69 }, 70 InfrastructureRef: corev1.ObjectReference{ 71 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 72 Kind: "GenericInfrastructureMachine", 73 Name: "infra-config1", 74 }, 75 }, 76 } 77 78 defaultBootstrap := &unstructured.Unstructured{ 79 Object: map[string]interface{}{ 80 "kind": "GenericBootstrapConfig", 81 "apiVersion": "bootstrap.cluster.x-k8s.io/v1beta1", 82 "metadata": map[string]interface{}{ 83 "name": "bootstrap-config1", 84 "namespace": metav1.NamespaceDefault, 85 }, 86 "spec": map[string]interface{}{}, 87 "status": map[string]interface{}{}, 88 }, 89 } 90 91 defaultInfra := &unstructured.Unstructured{ 92 Object: map[string]interface{}{ 93 "kind": "GenericInfrastructureMachine", 94 "apiVersion": "infrastructure.cluster.x-k8s.io/v1beta1", 95 "metadata": map[string]interface{}{ 96 "name": "infra-config1", 97 "namespace": metav1.NamespaceDefault, 98 }, 99 "spec": map[string]interface{}{}, 100 "status": map[string]interface{}{}, 101 }, 102 } 103 104 t.Run("Should set OwnerReference and cluster name label on external objects", func(t *testing.T) { 105 g := NewWithT(t) 106 107 ns, err := env.CreateNamespace(ctx, "test-reconcile-machine-phases") 108 g.Expect(err).ToNot(HaveOccurred()) 109 defer func() { 110 g.Expect(env.Cleanup(ctx, ns)).To(Succeed()) 111 }() 112 113 cluster := defaultCluster.DeepCopy() 114 cluster.Namespace = ns.Name 115 116 bootstrapConfig := defaultBootstrap.DeepCopy() 117 bootstrapConfig.SetNamespace(ns.Name) 118 infraMachine := defaultInfra.DeepCopy() 119 infraMachine.SetNamespace(ns.Name) 120 machine := defaultMachine.DeepCopy() 121 machine.Namespace = ns.Name 122 123 g.Expect(env.Create(ctx, cluster)).To(Succeed()) 124 defaultKubeconfigSecret = kubeconfig.GenerateSecret(cluster, kubeconfig.FromEnvTestConfig(env.Config, cluster)) 125 g.Expect(env.Create(ctx, defaultKubeconfigSecret)).To(Succeed()) 126 127 g.Expect(env.Create(ctx, bootstrapConfig)).To(Succeed()) 128 g.Expect(env.Create(ctx, infraMachine)).To(Succeed()) 129 g.Expect(env.Create(ctx, machine)).To(Succeed()) 130 131 // Wait until BootstrapConfig has the ownerReference. 132 g.Eventually(func(g Gomega) bool { 133 if err := env.Get(ctx, client.ObjectKeyFromObject(bootstrapConfig), bootstrapConfig); err != nil { 134 return false 135 } 136 g.Expect(bootstrapConfig.GetOwnerReferences()).To(HaveLen(1)) 137 g.Expect(bootstrapConfig.GetLabels()[clusterv1.ClusterNameLabel]).To(BeEquivalentTo("test-cluster")) 138 return true 139 }, 10*time.Second).Should(BeTrue()) 140 141 // Wait until InfraMachine has the ownerReference. 142 g.Eventually(func(g Gomega) bool { 143 if err := env.Get(ctx, client.ObjectKeyFromObject(infraMachine), infraMachine); err != nil { 144 return false 145 } 146 g.Expect(infraMachine.GetOwnerReferences()).To(HaveLen(1)) 147 g.Expect(infraMachine.GetLabels()[clusterv1.ClusterNameLabel]).To(BeEquivalentTo("test-cluster")) 148 return true 149 }, 10*time.Second).Should(BeTrue()) 150 }) 151 152 t.Run("Should set `Pending` with a new Machine", func(t *testing.T) { 153 g := NewWithT(t) 154 155 ns, err := env.CreateNamespace(ctx, "test-reconcile-machine-phases") 156 g.Expect(err).ToNot(HaveOccurred()) 157 defer func() { 158 g.Expect(env.Cleanup(ctx, ns)).To(Succeed()) 159 }() 160 161 cluster := defaultCluster.DeepCopy() 162 cluster.Namespace = ns.Name 163 164 bootstrapConfig := defaultBootstrap.DeepCopy() 165 bootstrapConfig.SetNamespace(ns.Name) 166 infraMachine := defaultInfra.DeepCopy() 167 infraMachine.SetNamespace(ns.Name) 168 machine := defaultMachine.DeepCopy() 169 machine.Namespace = ns.Name 170 171 g.Expect(env.Create(ctx, cluster)).To(Succeed()) 172 defaultKubeconfigSecret = kubeconfig.GenerateSecret(cluster, kubeconfig.FromEnvTestConfig(env.Config, cluster)) 173 g.Expect(env.Create(ctx, defaultKubeconfigSecret)).To(Succeed()) 174 175 g.Expect(env.Create(ctx, bootstrapConfig)).To(Succeed()) 176 g.Expect(env.Create(ctx, infraMachine)).To(Succeed()) 177 g.Expect(env.Create(ctx, machine)).To(Succeed()) 178 179 // Wait until Machine was reconciled. 180 g.Eventually(func(g Gomega) bool { 181 if err := env.Get(ctx, client.ObjectKeyFromObject(machine), machine); err != nil { 182 return false 183 } 184 g.Expect(machine.Status.GetTypedPhase()).To(Equal(clusterv1.MachinePhasePending)) 185 // LastUpdated should be set as the phase changes 186 g.Expect(machine.Status.LastUpdated).NotTo(BeNil()) 187 return true 188 }, 10*time.Second).Should(BeTrue()) 189 }) 190 191 t.Run("Should set `Provisioning` when bootstrap is ready", func(t *testing.T) { 192 g := NewWithT(t) 193 194 ns, err := env.CreateNamespace(ctx, "test-reconcile-machine-phases") 195 g.Expect(err).ToNot(HaveOccurred()) 196 defer func() { 197 g.Expect(env.Cleanup(ctx, ns)).To(Succeed()) 198 }() 199 200 cluster := defaultCluster.DeepCopy() 201 cluster.Namespace = ns.Name 202 203 bootstrapConfig := defaultBootstrap.DeepCopy() 204 bootstrapConfig.SetNamespace(ns.Name) 205 infraMachine := defaultInfra.DeepCopy() 206 infraMachine.SetNamespace(ns.Name) 207 machine := defaultMachine.DeepCopy() 208 machine.Namespace = ns.Name 209 210 g.Expect(env.Create(ctx, cluster)).To(Succeed()) 211 defaultKubeconfigSecret = kubeconfig.GenerateSecret(cluster, kubeconfig.FromEnvTestConfig(env.Config, cluster)) 212 g.Expect(env.Create(ctx, defaultKubeconfigSecret)).To(Succeed()) 213 214 g.Expect(env.Create(ctx, bootstrapConfig)).To(Succeed()) 215 g.Expect(env.Create(ctx, infraMachine)).To(Succeed()) 216 // We have to subtract 2 seconds, because .status.lastUpdated does not contain miliseconds. 217 preUpdate := time.Now().Add(-2 * time.Second) 218 g.Expect(env.Create(ctx, machine)).To(Succeed()) 219 220 // Set the LastUpdated to be able to verify it is updated when the phase changes 221 modifiedMachine := machine.DeepCopy() 222 g.Expect(env.Status().Patch(ctx, modifiedMachine, client.MergeFrom(machine))).To(Succeed()) 223 224 // Set bootstrap ready. 225 modifiedBootstrapConfig := bootstrapConfig.DeepCopy() 226 g.Expect(unstructured.SetNestedField(modifiedBootstrapConfig.Object, true, "status", "ready")).To(Succeed()) 227 g.Expect(unstructured.SetNestedField(modifiedBootstrapConfig.Object, "secret-data", "status", "dataSecretName")).To(Succeed()) 228 g.Expect(env.Status().Patch(ctx, modifiedBootstrapConfig, client.MergeFrom(bootstrapConfig))).To(Succeed()) 229 230 // Wait until Machine was reconciled. 231 g.Eventually(func(g Gomega) bool { 232 if err := env.Get(ctx, client.ObjectKeyFromObject(machine), machine); err != nil { 233 return false 234 } 235 g.Expect(machine.Status.GetTypedPhase()).To(Equal(clusterv1.MachinePhaseProvisioning)) 236 // Verify that the LastUpdated timestamp was updated 237 g.Expect(machine.Status.LastUpdated).NotTo(BeNil()) 238 g.Expect(machine.Status.LastUpdated.After(preUpdate)).To(BeTrue()) 239 return true 240 }, 10*time.Second).Should(BeTrue()) 241 }) 242 243 t.Run("Should set `Running` when bootstrap and infra is ready", func(t *testing.T) { 244 g := NewWithT(t) 245 246 ns, err := env.CreateNamespace(ctx, "test-reconcile-machine-phases") 247 g.Expect(err).ToNot(HaveOccurred()) 248 defer func() { 249 g.Expect(env.Cleanup(ctx, ns)).To(Succeed()) 250 }() 251 252 nodeProviderID := fmt.Sprintf("test://%s", util.RandomString(6)) 253 254 cluster := defaultCluster.DeepCopy() 255 cluster.Namespace = ns.Name 256 257 bootstrapConfig := defaultBootstrap.DeepCopy() 258 bootstrapConfig.SetNamespace(ns.Name) 259 infraMachine := defaultInfra.DeepCopy() 260 infraMachine.SetNamespace(ns.Name) 261 g.Expect(unstructured.SetNestedField(infraMachine.Object, nodeProviderID, "spec", "providerID")).To(Succeed()) 262 g.Expect(unstructured.SetNestedField(infraMachine.Object, "us-east-2a", "spec", "failureDomain")).To(Succeed()) 263 machine := defaultMachine.DeepCopy() 264 machine.Namespace = ns.Name 265 266 // Create Node. 267 node := &corev1.Node{ 268 ObjectMeta: metav1.ObjectMeta{ 269 GenerateName: "machine-test-node-", 270 }, 271 Spec: corev1.NodeSpec{ProviderID: nodeProviderID}, 272 } 273 g.Expect(env.Create(ctx, node)).To(Succeed()) 274 defer func() { 275 g.Expect(env.Cleanup(ctx, node)).To(Succeed()) 276 }() 277 278 g.Expect(env.Create(ctx, cluster)).To(Succeed()) 279 defaultKubeconfigSecret = kubeconfig.GenerateSecret(cluster, kubeconfig.FromEnvTestConfig(env.Config, cluster)) 280 g.Expect(env.Create(ctx, defaultKubeconfigSecret)).To(Succeed()) 281 282 g.Expect(env.Create(ctx, bootstrapConfig)).To(Succeed()) 283 g.Expect(env.Create(ctx, infraMachine)).To(Succeed()) 284 // We have to subtract 2 seconds, because .status.lastUpdated does not contain miliseconds. 285 preUpdate := time.Now().Add(-2 * time.Second) 286 g.Expect(env.Create(ctx, machine)).To(Succeed()) 287 288 modifiedMachine := machine.DeepCopy() 289 // Set NodeRef. 290 machine.Status.NodeRef = &corev1.ObjectReference{Kind: "Node", Name: node.Name} 291 g.Expect(env.Status().Patch(ctx, modifiedMachine, client.MergeFrom(machine))).To(Succeed()) 292 293 // Set bootstrap ready. 294 modifiedBootstrapConfig := bootstrapConfig.DeepCopy() 295 g.Expect(unstructured.SetNestedField(modifiedBootstrapConfig.Object, true, "status", "ready")).To(Succeed()) 296 g.Expect(unstructured.SetNestedField(modifiedBootstrapConfig.Object, "secret-data", "status", "dataSecretName")).To(Succeed()) 297 g.Expect(env.Status().Patch(ctx, modifiedBootstrapConfig, client.MergeFrom(bootstrapConfig))).To(Succeed()) 298 299 // Set infra ready. 300 modifiedInfraMachine := infraMachine.DeepCopy() 301 g.Expect(unstructured.SetNestedField(modifiedInfraMachine.Object, true, "status", "ready")).To(Succeed()) 302 g.Expect(unstructured.SetNestedField(modifiedInfraMachine.Object, []interface{}{ 303 map[string]interface{}{ 304 "type": "InternalIP", 305 "address": "10.0.0.1", 306 }, 307 map[string]interface{}{ 308 "type": "InternalIP", 309 "address": "10.0.0.2", 310 }, 311 }, "status", "addresses")).To(Succeed()) 312 g.Expect(env.Status().Patch(ctx, modifiedInfraMachine, client.MergeFrom(infraMachine))).To(Succeed()) 313 314 // Wait until Machine was reconciled. 315 g.Eventually(func(g Gomega) bool { 316 if err := env.Get(ctx, client.ObjectKeyFromObject(machine), machine); err != nil { 317 return false 318 } 319 g.Expect(machine.Status.Addresses).To(HaveLen(2)) 320 g.Expect(*machine.Spec.FailureDomain).To(Equal("us-east-2a")) 321 g.Expect(machine.Status.GetTypedPhase()).To(Equal(clusterv1.MachinePhaseRunning)) 322 // Verify that the LastUpdated timestamp was updated 323 g.Expect(machine.Status.LastUpdated).NotTo(BeNil()) 324 g.Expect(machine.Status.LastUpdated.After(preUpdate)).To(BeTrue()) 325 return true 326 }, 10*time.Second).Should(BeTrue()) 327 }) 328 329 t.Run("Should set `Running` when bootstrap and infra is ready with no Status.Addresses", func(t *testing.T) { 330 g := NewWithT(t) 331 332 ns, err := env.CreateNamespace(ctx, "test-reconcile-machine-phases") 333 g.Expect(err).ToNot(HaveOccurred()) 334 defer func() { 335 g.Expect(env.Cleanup(ctx, ns)).To(Succeed()) 336 }() 337 338 nodeProviderID := fmt.Sprintf("test://%s", util.RandomString(6)) 339 340 cluster := defaultCluster.DeepCopy() 341 cluster.Namespace = ns.Name 342 343 bootstrapConfig := defaultBootstrap.DeepCopy() 344 bootstrapConfig.SetNamespace(ns.Name) 345 infraMachine := defaultInfra.DeepCopy() 346 infraMachine.SetNamespace(ns.Name) 347 g.Expect(unstructured.SetNestedField(infraMachine.Object, nodeProviderID, "spec", "providerID")).To(Succeed()) 348 machine := defaultMachine.DeepCopy() 349 machine.Namespace = ns.Name 350 351 // Create Node. 352 node := &corev1.Node{ 353 ObjectMeta: metav1.ObjectMeta{ 354 GenerateName: "machine-test-node-", 355 }, 356 Spec: corev1.NodeSpec{ProviderID: nodeProviderID}, 357 } 358 g.Expect(env.Create(ctx, node)).To(Succeed()) 359 defer func() { 360 g.Expect(env.Cleanup(ctx, node)).To(Succeed()) 361 }() 362 363 g.Expect(env.Create(ctx, cluster)).To(Succeed()) 364 defaultKubeconfigSecret = kubeconfig.GenerateSecret(cluster, kubeconfig.FromEnvTestConfig(env.Config, cluster)) 365 g.Expect(env.Create(ctx, defaultKubeconfigSecret)).To(Succeed()) 366 367 g.Expect(env.Create(ctx, bootstrapConfig)).To(Succeed()) 368 g.Expect(env.Create(ctx, infraMachine)).To(Succeed()) 369 // We have to subtract 2 seconds, because .status.lastUpdated does not contain miliseconds. 370 preUpdate := time.Now().Add(-2 * time.Second) 371 g.Expect(env.Create(ctx, machine)).To(Succeed()) 372 373 modifiedMachine := machine.DeepCopy() 374 // Set NodeRef. 375 machine.Status.NodeRef = &corev1.ObjectReference{Kind: "Node", Name: node.Name} 376 g.Expect(env.Status().Patch(ctx, modifiedMachine, client.MergeFrom(machine))).To(Succeed()) 377 378 // Set bootstrap ready. 379 modifiedBootstrapConfig := bootstrapConfig.DeepCopy() 380 g.Expect(unstructured.SetNestedField(modifiedBootstrapConfig.Object, true, "status", "ready")).To(Succeed()) 381 g.Expect(unstructured.SetNestedField(modifiedBootstrapConfig.Object, "secret-data", "status", "dataSecretName")).To(Succeed()) 382 g.Expect(env.Status().Patch(ctx, modifiedBootstrapConfig, client.MergeFrom(bootstrapConfig))).To(Succeed()) 383 384 // Set infra ready. 385 modifiedInfraMachine := infraMachine.DeepCopy() 386 g.Expect(unstructured.SetNestedField(modifiedInfraMachine.Object, true, "status", "ready")).To(Succeed()) 387 g.Expect(env.Status().Patch(ctx, modifiedInfraMachine, client.MergeFrom(infraMachine))).To(Succeed()) 388 389 // Wait until Machine was reconciled. 390 g.Eventually(func(g Gomega) bool { 391 if err := env.Get(ctx, client.ObjectKeyFromObject(machine), machine); err != nil { 392 return false 393 } 394 g.Expect(machine.Status.GetTypedPhase()).To(Equal(clusterv1.MachinePhaseRunning)) 395 g.Expect(machine.Status.Addresses).To(BeEmpty()) 396 // Verify that the LastUpdated timestamp was updated 397 g.Expect(machine.Status.LastUpdated).NotTo(BeNil()) 398 g.Expect(machine.Status.LastUpdated.After(preUpdate)).To(BeTrue()) 399 return true 400 }, 10*time.Second).Should(BeTrue()) 401 }) 402 403 t.Run("Should set `Running` when bootstrap, infra, and NodeRef is ready", func(t *testing.T) { 404 g := NewWithT(t) 405 406 ns, err := env.CreateNamespace(ctx, "test-reconcile-machine-phases") 407 g.Expect(err).ToNot(HaveOccurred()) 408 defer func() { 409 g.Expect(env.Cleanup(ctx, ns)).To(Succeed()) 410 }() 411 412 nodeProviderID := fmt.Sprintf("test://%s", util.RandomString(6)) 413 414 cluster := defaultCluster.DeepCopy() 415 cluster.Namespace = ns.Name 416 417 bootstrapConfig := defaultBootstrap.DeepCopy() 418 bootstrapConfig.SetNamespace(ns.Name) 419 infraMachine := defaultInfra.DeepCopy() 420 infraMachine.SetNamespace(ns.Name) 421 g.Expect(unstructured.SetNestedField(infraMachine.Object, nodeProviderID, "spec", "providerID")).To(Succeed()) 422 machine := defaultMachine.DeepCopy() 423 machine.Namespace = ns.Name 424 425 // Create Node. 426 node := &corev1.Node{ 427 ObjectMeta: metav1.ObjectMeta{ 428 GenerateName: "machine-test-node-", 429 }, 430 Spec: corev1.NodeSpec{ProviderID: nodeProviderID}, 431 } 432 g.Expect(env.Create(ctx, node)).To(Succeed()) 433 defer func() { 434 g.Expect(env.Cleanup(ctx, node)).To(Succeed()) 435 }() 436 437 g.Expect(env.Create(ctx, cluster)).To(Succeed()) 438 defaultKubeconfigSecret = kubeconfig.GenerateSecret(cluster, kubeconfig.FromEnvTestConfig(env.Config, cluster)) 439 g.Expect(env.Create(ctx, defaultKubeconfigSecret)).To(Succeed()) 440 441 g.Expect(env.Create(ctx, bootstrapConfig)).To(Succeed()) 442 g.Expect(env.Create(ctx, infraMachine)).To(Succeed()) 443 // We have to subtract 2 seconds, because .status.lastUpdated does not contain miliseconds. 444 preUpdate := time.Now().Add(-2 * time.Second) 445 g.Expect(env.Create(ctx, machine)).To(Succeed()) 446 447 modifiedMachine := machine.DeepCopy() 448 // Set NodeRef. 449 machine.Status.NodeRef = &corev1.ObjectReference{Kind: "Node", Name: node.Name} 450 g.Expect(env.Status().Patch(ctx, modifiedMachine, client.MergeFrom(machine))).To(Succeed()) 451 452 // Set bootstrap ready. 453 modifiedBootstrapConfig := bootstrapConfig.DeepCopy() 454 g.Expect(unstructured.SetNestedField(modifiedBootstrapConfig.Object, true, "status", "ready")).To(Succeed()) 455 g.Expect(unstructured.SetNestedField(modifiedBootstrapConfig.Object, "secret-data", "status", "dataSecretName")).To(Succeed()) 456 g.Expect(env.Status().Patch(ctx, modifiedBootstrapConfig, client.MergeFrom(bootstrapConfig))).To(Succeed()) 457 458 // Set infra ready. 459 modifiedInfraMachine := infraMachine.DeepCopy() 460 g.Expect(unstructured.SetNestedField(modifiedInfraMachine.Object, true, "status", "ready")).To(Succeed()) 461 g.Expect(env.Status().Patch(ctx, modifiedInfraMachine, client.MergeFrom(infraMachine))).To(Succeed()) 462 463 // Wait until Machine was reconciled. 464 g.Eventually(func(g Gomega) bool { 465 if err := env.Get(ctx, client.ObjectKeyFromObject(machine), machine); err != nil { 466 return false 467 } 468 g.Expect(machine.Status.GetTypedPhase()).To(Equal(clusterv1.MachinePhaseRunning)) 469 // Verify that the LastUpdated timestamp was updated 470 g.Expect(machine.Status.LastUpdated).NotTo(BeNil()) 471 g.Expect(machine.Status.LastUpdated.After(preUpdate)).To(BeTrue()) 472 return true 473 }, 10*time.Second).Should(BeTrue()) 474 }) 475 476 t.Run("Should set `Provisioned` when there is a ProviderID and there is no Node", func(t *testing.T) { 477 g := NewWithT(t) 478 479 ns, err := env.CreateNamespace(ctx, "test-reconcile-machine-phases") 480 g.Expect(err).ToNot(HaveOccurred()) 481 defer func() { 482 g.Expect(env.Cleanup(ctx, ns)).To(Succeed()) 483 }() 484 485 nodeProviderID := fmt.Sprintf("test://%s", util.RandomString(6)) 486 487 cluster := defaultCluster.DeepCopy() 488 cluster.Namespace = ns.Name 489 490 bootstrapConfig := defaultBootstrap.DeepCopy() 491 bootstrapConfig.SetNamespace(ns.Name) 492 infraMachine := defaultInfra.DeepCopy() 493 infraMachine.SetNamespace(ns.Name) 494 g.Expect(unstructured.SetNestedField(infraMachine.Object, nodeProviderID, "spec", "providerID")).To(Succeed()) 495 machine := defaultMachine.DeepCopy() 496 machine.Namespace = ns.Name 497 // Set Machine ProviderID. 498 machine.Spec.ProviderID = ptr.To(nodeProviderID) 499 500 g.Expect(env.Create(ctx, cluster)).To(Succeed()) 501 defaultKubeconfigSecret = kubeconfig.GenerateSecret(cluster, kubeconfig.FromEnvTestConfig(env.Config, cluster)) 502 g.Expect(env.Create(ctx, defaultKubeconfigSecret)).To(Succeed()) 503 504 g.Expect(env.Create(ctx, bootstrapConfig)).To(Succeed()) 505 g.Expect(env.Create(ctx, infraMachine)).To(Succeed()) 506 // We have to subtract 2 seconds, because .status.lastUpdated does not contain miliseconds. 507 preUpdate := time.Now().Add(-2 * time.Second) 508 g.Expect(env.Create(ctx, machine)).To(Succeed()) 509 510 // Set bootstrap ready. 511 modifiedBootstrapConfig := bootstrapConfig.DeepCopy() 512 g.Expect(unstructured.SetNestedField(modifiedBootstrapConfig.Object, true, "status", "ready")).To(Succeed()) 513 g.Expect(unstructured.SetNestedField(modifiedBootstrapConfig.Object, "secret-data", "status", "dataSecretName")).To(Succeed()) 514 g.Expect(env.Status().Patch(ctx, modifiedBootstrapConfig, client.MergeFrom(bootstrapConfig))).To(Succeed()) 515 516 // Set infra ready. 517 modifiedInfraMachine := infraMachine.DeepCopy() 518 g.Expect(unstructured.SetNestedField(modifiedInfraMachine.Object, true, "status", "ready")).To(Succeed()) 519 g.Expect(env.Status().Patch(ctx, modifiedInfraMachine, client.MergeFrom(infraMachine))).To(Succeed()) 520 521 // Wait until Machine was reconciled. 522 g.Eventually(func(g Gomega) bool { 523 if err := env.Get(ctx, client.ObjectKeyFromObject(machine), machine); err != nil { 524 return false 525 } 526 g.Expect(machine.Status.GetTypedPhase()).To(Equal(clusterv1.MachinePhaseProvisioned)) 527 // Verify that the LastUpdated timestamp was updated 528 g.Expect(machine.Status.LastUpdated).NotTo(BeNil()) 529 g.Expect(machine.Status.LastUpdated.After(preUpdate)).To(BeTrue()) 530 return true 531 }, 10*time.Second).Should(BeTrue()) 532 }) 533 534 t.Run("Should set `Deleting` when Machine is being deleted", func(t *testing.T) { 535 g := NewWithT(t) 536 537 ns, err := env.CreateNamespace(ctx, "test-reconcile-machine-phases") 538 g.Expect(err).ToNot(HaveOccurred()) 539 defer func() { 540 g.Expect(env.Cleanup(ctx, ns)).To(Succeed()) 541 }() 542 543 nodeProviderID := fmt.Sprintf("test://%s", util.RandomString(6)) 544 545 cluster := defaultCluster.DeepCopy() 546 cluster.Namespace = ns.Name 547 548 bootstrapConfig := defaultBootstrap.DeepCopy() 549 bootstrapConfig.SetNamespace(ns.Name) 550 infraMachine := defaultInfra.DeepCopy() 551 infraMachine.SetNamespace(ns.Name) 552 g.Expect(unstructured.SetNestedField(infraMachine.Object, nodeProviderID, "spec", "providerID")).To(Succeed()) 553 machine := defaultMachine.DeepCopy() 554 machine.Namespace = ns.Name 555 556 // Create Node. 557 node := &corev1.Node{ 558 ObjectMeta: metav1.ObjectMeta{ 559 GenerateName: "machine-test-node-", 560 }, 561 Spec: corev1.NodeSpec{ProviderID: nodeProviderID}, 562 } 563 g.Expect(env.Create(ctx, node)).To(Succeed()) 564 defer func() { 565 g.Expect(env.Cleanup(ctx, node)).To(Succeed()) 566 }() 567 568 g.Expect(env.Create(ctx, cluster)).To(Succeed()) 569 defaultKubeconfigSecret = kubeconfig.GenerateSecret(cluster, kubeconfig.FromEnvTestConfig(env.Config, cluster)) 570 g.Expect(env.Create(ctx, defaultKubeconfigSecret)).To(Succeed()) 571 572 g.Expect(env.Create(ctx, bootstrapConfig)).To(Succeed()) 573 g.Expect(env.Create(ctx, infraMachine)).To(Succeed()) 574 // We have to subtract 2 seconds, because .status.lastUpdated does not contain miliseconds. 575 preUpdate := time.Now().Add(-2 * time.Second) 576 g.Expect(env.Create(ctx, machine)).To(Succeed()) 577 578 // Set bootstrap ready. 579 modifiedBootstrapConfig := bootstrapConfig.DeepCopy() 580 g.Expect(unstructured.SetNestedField(modifiedBootstrapConfig.Object, true, "status", "ready")).To(Succeed()) 581 g.Expect(unstructured.SetNestedField(modifiedBootstrapConfig.Object, "secret-data", "status", "dataSecretName")).To(Succeed()) 582 g.Expect(env.Status().Patch(ctx, modifiedBootstrapConfig, client.MergeFrom(bootstrapConfig))).To(Succeed()) 583 584 // Set infra ready. 585 modifiedInfraMachine := infraMachine.DeepCopy() 586 g.Expect(unstructured.SetNestedField(modifiedInfraMachine.Object, true, "status", "ready")).To(Succeed()) 587 g.Expect(env.Status().Patch(ctx, modifiedInfraMachine, client.MergeFrom(infraMachine))).To(Succeed()) 588 589 // Wait until the Machine has the Machine finalizer 590 g.Eventually(func() []string { 591 if err := env.Get(ctx, client.ObjectKeyFromObject(machine), machine); err != nil { 592 return nil 593 } 594 return machine.Finalizers 595 }, 10*time.Second).Should(HaveLen(1)) 596 597 modifiedMachine := machine.DeepCopy() 598 // Set NodeRef. 599 machine.Status.NodeRef = &corev1.ObjectReference{Kind: "Node", Name: node.Name} 600 g.Expect(env.Status().Patch(ctx, modifiedMachine, client.MergeFrom(machine))).To(Succeed()) 601 602 modifiedMachine = machine.DeepCopy() 603 // Set finalizer so we can check the Machine later, otherwise it would be already gone. 604 modifiedMachine.Finalizers = append(modifiedMachine.Finalizers, "test") 605 g.Expect(env.Patch(ctx, modifiedMachine, client.MergeFrom(machine))).To(Succeed()) 606 607 // Delete Machine 608 g.Expect(env.Delete(ctx, machine)).To(Succeed()) 609 610 // Wait until Machine was reconciled. 611 g.Eventually(func(g Gomega) bool { 612 if err := env.Get(ctx, client.ObjectKeyFromObject(machine), machine); err != nil { 613 return false 614 } 615 g.Expect(machine.Status.GetTypedPhase()).To(Equal(clusterv1.MachinePhaseDeleting)) 616 nodeHealthyCondition := conditions.Get(machine, clusterv1.MachineNodeHealthyCondition) 617 g.Expect(nodeHealthyCondition.Status).To(Equal(corev1.ConditionFalse)) 618 g.Expect(nodeHealthyCondition.Reason).To(Equal(clusterv1.DeletingReason)) 619 // Verify that the LastUpdated timestamp was updated 620 g.Expect(machine.Status.LastUpdated).NotTo(BeNil()) 621 g.Expect(machine.Status.LastUpdated.After(preUpdate)).To(BeTrue()) 622 return true 623 }, 10*time.Second).Should(BeTrue()) 624 }) 625 } 626 627 func TestReconcileBootstrap(t *testing.T) { 628 defaultMachine := clusterv1.Machine{ 629 ObjectMeta: metav1.ObjectMeta{ 630 Name: "machine-test", 631 Namespace: metav1.NamespaceDefault, 632 Labels: map[string]string{ 633 clusterv1.ClusterNameLabel: "test-cluster", 634 }, 635 }, 636 Spec: clusterv1.MachineSpec{ 637 Bootstrap: clusterv1.Bootstrap{ 638 ConfigRef: &corev1.ObjectReference{ 639 APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1", 640 Kind: "GenericBootstrapConfig", 641 Name: "bootstrap-config1", 642 }, 643 }, 644 }, 645 } 646 647 defaultCluster := &clusterv1.Cluster{ 648 ObjectMeta: metav1.ObjectMeta{ 649 Name: "test-cluster", 650 Namespace: metav1.NamespaceDefault, 651 }, 652 } 653 654 testCases := []struct { 655 name string 656 bootstrapConfig map[string]interface{} 657 machine *clusterv1.Machine 658 expectResult ctrl.Result 659 expectError bool 660 expected func(g *WithT, m *clusterv1.Machine) 661 }{ 662 { 663 name: "new machine, bootstrap config ready with data", 664 bootstrapConfig: map[string]interface{}{ 665 "kind": "GenericBootstrapConfig", 666 "apiVersion": "bootstrap.cluster.x-k8s.io/v1beta1", 667 "metadata": map[string]interface{}{ 668 "name": "bootstrap-config1", 669 "namespace": metav1.NamespaceDefault, 670 }, 671 "spec": map[string]interface{}{}, 672 "status": map[string]interface{}{ 673 "ready": true, 674 "dataSecretName": "secret-data", 675 }, 676 }, 677 expectResult: ctrl.Result{}, 678 expectError: false, 679 expected: func(g *WithT, m *clusterv1.Machine) { 680 g.Expect(m.Status.BootstrapReady).To(BeTrue()) 681 g.Expect(m.Spec.Bootstrap.DataSecretName).NotTo(BeNil()) 682 g.Expect(*m.Spec.Bootstrap.DataSecretName).To(ContainSubstring("secret-data")) 683 }, 684 }, 685 { 686 name: "new machine, bootstrap config ready with no data", 687 bootstrapConfig: map[string]interface{}{ 688 "kind": "GenericBootstrapConfig", 689 "apiVersion": "bootstrap.cluster.x-k8s.io/v1beta1", 690 "metadata": map[string]interface{}{ 691 "name": "bootstrap-config1", 692 "namespace": metav1.NamespaceDefault, 693 }, 694 "spec": map[string]interface{}{}, 695 "status": map[string]interface{}{ 696 "ready": true, 697 }, 698 }, 699 expectResult: ctrl.Result{}, 700 expectError: true, 701 expected: func(g *WithT, m *clusterv1.Machine) { 702 g.Expect(m.Status.BootstrapReady).To(BeFalse()) 703 g.Expect(m.Spec.Bootstrap.DataSecretName).To(BeNil()) 704 }, 705 }, 706 { 707 name: "new machine, bootstrap config not ready", 708 bootstrapConfig: map[string]interface{}{ 709 "kind": "GenericBootstrapConfig", 710 "apiVersion": "bootstrap.cluster.x-k8s.io/v1beta1", 711 "metadata": map[string]interface{}{ 712 "name": "bootstrap-config1", 713 "namespace": metav1.NamespaceDefault, 714 }, 715 "spec": map[string]interface{}{}, 716 "status": map[string]interface{}{}, 717 }, 718 expectResult: ctrl.Result{}, 719 expectError: false, 720 expected: func(g *WithT, m *clusterv1.Machine) { 721 g.Expect(m.Status.BootstrapReady).To(BeFalse()) 722 }, 723 }, 724 { 725 name: "new machine, bootstrap config is not found", 726 bootstrapConfig: map[string]interface{}{ 727 "kind": "GenericBootstrapConfig", 728 "apiVersion": "bootstrap.cluster.x-k8s.io/v1beta1", 729 "metadata": map[string]interface{}{ 730 "name": "bootstrap-config1", 731 "namespace": "wrong-namespace", 732 }, 733 "spec": map[string]interface{}{}, 734 "status": map[string]interface{}{}, 735 }, 736 expectResult: ctrl.Result{RequeueAfter: externalReadyWait}, 737 expectError: false, 738 expected: func(g *WithT, m *clusterv1.Machine) { 739 g.Expect(m.Status.BootstrapReady).To(BeFalse()) 740 }, 741 }, 742 { 743 name: "new machine, no bootstrap config or data", 744 bootstrapConfig: map[string]interface{}{ 745 "kind": "GenericBootstrapConfig", 746 "apiVersion": "bootstrap.cluster.x-k8s.io/v1beta1", 747 "metadata": map[string]interface{}{ 748 "name": "bootstrap-config1", 749 "namespace": "wrong-namespace", 750 }, 751 "spec": map[string]interface{}{}, 752 "status": map[string]interface{}{}, 753 }, 754 expectResult: ctrl.Result{RequeueAfter: externalReadyWait}, 755 expectError: false, 756 }, 757 { 758 name: "existing machine, bootstrap data should not change", 759 bootstrapConfig: map[string]interface{}{ 760 "kind": "GenericBootstrapConfig", 761 "apiVersion": "bootstrap.cluster.x-k8s.io/v1beta1", 762 "metadata": map[string]interface{}{ 763 "name": "bootstrap-config1", 764 "namespace": metav1.NamespaceDefault, 765 }, 766 "spec": map[string]interface{}{}, 767 "status": map[string]interface{}{ 768 "ready": true, 769 "dataSecretName": "secret-data", 770 }, 771 }, 772 machine: &clusterv1.Machine{ 773 ObjectMeta: metav1.ObjectMeta{ 774 Name: "bootstrap-test-existing", 775 Namespace: metav1.NamespaceDefault, 776 }, 777 Spec: clusterv1.MachineSpec{ 778 Bootstrap: clusterv1.Bootstrap{ 779 ConfigRef: &corev1.ObjectReference{ 780 APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1", 781 Kind: "GenericBootstrapConfig", 782 Name: "bootstrap-config1", 783 }, 784 DataSecretName: ptr.To("secret-data"), 785 }, 786 }, 787 Status: clusterv1.MachineStatus{ 788 BootstrapReady: true, 789 }, 790 }, 791 expectResult: ctrl.Result{}, 792 expectError: false, 793 expected: func(g *WithT, m *clusterv1.Machine) { 794 g.Expect(m.Status.BootstrapReady).To(BeTrue()) 795 g.Expect(*m.Spec.Bootstrap.DataSecretName).To(BeEquivalentTo("secret-data")) 796 }, 797 }, 798 { 799 name: "existing machine, bootstrap provider is not ready, and ownerref updated", 800 bootstrapConfig: map[string]interface{}{ 801 "kind": "GenericBootstrapConfig", 802 "apiVersion": "bootstrap.cluster.x-k8s.io/v1beta1", 803 "metadata": map[string]interface{}{ 804 "name": "bootstrap-config1", 805 "namespace": metav1.NamespaceDefault, 806 "ownerReferences": []interface{}{ 807 map[string]interface{}{ 808 "apiVersion": clusterv1.GroupVersion.String(), 809 "kind": "MachineSet", 810 "name": "ms", 811 "uid": "1", 812 "controller": true, 813 }, 814 }, 815 }, 816 "spec": map[string]interface{}{}, 817 "status": map[string]interface{}{ 818 "ready": false, 819 }, 820 }, 821 machine: &clusterv1.Machine{ 822 ObjectMeta: metav1.ObjectMeta{ 823 Name: "bootstrap-test-existing", 824 Namespace: metav1.NamespaceDefault, 825 }, 826 Spec: clusterv1.MachineSpec{ 827 Bootstrap: clusterv1.Bootstrap{ 828 ConfigRef: &corev1.ObjectReference{ 829 APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1", 830 Kind: "GenericBootstrapConfig", 831 Name: "bootstrap-config1", 832 }, 833 }, 834 }, 835 Status: clusterv1.MachineStatus{ 836 BootstrapReady: true, 837 }, 838 }, 839 expectResult: ctrl.Result{}, 840 expectError: false, 841 expected: func(g *WithT, m *clusterv1.Machine) { 842 g.Expect(m.GetOwnerReferences()).NotTo(ContainRefOfGroupKind("cluster.x-k8s.io", "MachineSet")) 843 }, 844 }, 845 { 846 name: "existing machine, machineset owner and version v1alpha2, and ownerref updated", 847 bootstrapConfig: map[string]interface{}{ 848 "kind": "GenericBootstrapConfig", 849 "apiVersion": "bootstrap.cluster.x-k8s.io/v1beta1", 850 "metadata": map[string]interface{}{ 851 "name": "bootstrap-config1", 852 "namespace": metav1.NamespaceDefault, 853 "ownerReferences": []interface{}{ 854 map[string]interface{}{ 855 "apiVersion": "cluster.x-k8s.io/v1alpha2", 856 "kind": "MachineSet", 857 "name": "ms", 858 "uid": "1", 859 "controller": true, 860 }, 861 }, 862 }, 863 "spec": map[string]interface{}{}, 864 "status": map[string]interface{}{ 865 "ready": true, 866 }, 867 }, 868 machine: &clusterv1.Machine{ 869 ObjectMeta: metav1.ObjectMeta{ 870 Name: "bootstrap-test-existing", 871 Namespace: metav1.NamespaceDefault, 872 }, 873 Spec: clusterv1.MachineSpec{ 874 Bootstrap: clusterv1.Bootstrap{ 875 ConfigRef: &corev1.ObjectReference{ 876 APIVersion: "bootstrap.cluster.x-k8s.io/v1alpha2", 877 Kind: "GenericBootstrapConfig", 878 Name: "bootstrap-config1", 879 }, 880 }, 881 }, 882 Status: clusterv1.MachineStatus{ 883 BootstrapReady: true, 884 }, 885 }, 886 expectResult: ctrl.Result{}, 887 expectError: true, 888 expected: func(g *WithT, m *clusterv1.Machine) { 889 g.Expect(m.GetOwnerReferences()).NotTo(ContainRefOfGroupKind("cluster.x-k8s.io", "MachineSet")) 890 }, 891 }, 892 } 893 894 for _, tc := range testCases { 895 t.Run(tc.name, func(t *testing.T) { 896 g := NewWithT(t) 897 898 if tc.machine == nil { 899 tc.machine = defaultMachine.DeepCopy() 900 } 901 902 bootstrapConfig := &unstructured.Unstructured{Object: tc.bootstrapConfig} 903 c := fake.NewClientBuilder(). 904 WithObjects(tc.machine, 905 builder.GenericBootstrapConfigCRD.DeepCopy(), 906 builder.GenericInfrastructureMachineCRD.DeepCopy(), 907 bootstrapConfig, 908 ).Build() 909 r := &Reconciler{ 910 Client: c, 911 UnstructuredCachingClient: c, 912 } 913 914 s := &scope{cluster: defaultCluster, machine: tc.machine} 915 res, err := r.reconcileBootstrap(ctx, s) 916 g.Expect(res).To(BeComparableTo(tc.expectResult)) 917 if tc.expectError { 918 g.Expect(err).To(HaveOccurred()) 919 } else { 920 g.Expect(err).ToNot(HaveOccurred()) 921 } 922 923 if tc.expected != nil { 924 tc.expected(g, tc.machine) 925 } 926 }) 927 } 928 } 929 930 func TestReconcileInfrastructure(t *testing.T) { 931 defaultMachine := clusterv1.Machine{ 932 ObjectMeta: metav1.ObjectMeta{ 933 Name: "machine-test", 934 Namespace: metav1.NamespaceDefault, 935 Labels: map[string]string{ 936 clusterv1.ClusterNameLabel: "test-cluster", 937 }, 938 }, 939 Spec: clusterv1.MachineSpec{ 940 Bootstrap: clusterv1.Bootstrap{ 941 ConfigRef: &corev1.ObjectReference{ 942 APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1", 943 Kind: "GenericBootstrapConfig", 944 Name: "bootstrap-config1", 945 }, 946 }, 947 InfrastructureRef: corev1.ObjectReference{ 948 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 949 Kind: "GenericInfrastructureMachine", 950 Name: "infra-config1", 951 }, 952 }, 953 } 954 955 defaultCluster := &clusterv1.Cluster{ 956 ObjectMeta: metav1.ObjectMeta{ 957 Name: "test-cluster", 958 Namespace: metav1.NamespaceDefault, 959 }, 960 } 961 962 testCases := []struct { 963 name string 964 bootstrapConfig map[string]interface{} 965 infraConfig map[string]interface{} 966 machine *clusterv1.Machine 967 expectResult ctrl.Result 968 expectError bool 969 expectChanged bool 970 expected func(g *WithT, m *clusterv1.Machine) 971 }{ 972 { 973 name: "new machine, infrastructure config ready", 974 infraConfig: map[string]interface{}{ 975 "kind": "GenericInfrastructureMachine", 976 "apiVersion": "infrastructure.cluster.x-k8s.io/v1beta1", 977 "metadata": map[string]interface{}{ 978 "name": "infra-config1", 979 "namespace": metav1.NamespaceDefault, 980 "ownerReferences": []interface{}{ 981 map[string]interface{}{ 982 "apiVersion": clusterv1.GroupVersion.String(), 983 "kind": "MachineSet", 984 "name": "ms", 985 "uid": "1", 986 "controller": true, 987 }, 988 }, 989 }, 990 "spec": map[string]interface{}{ 991 "providerID": "test://id-1", 992 }, 993 "status": map[string]interface{}{ 994 "ready": true, 995 "addresses": []interface{}{ 996 map[string]interface{}{ 997 "type": "InternalIP", 998 "address": "10.0.0.1", 999 }, 1000 map[string]interface{}{ 1001 "type": "InternalIP", 1002 "address": "10.0.0.2", 1003 }, 1004 }, 1005 }, 1006 }, 1007 expectResult: ctrl.Result{}, 1008 expectError: false, 1009 expectChanged: true, 1010 expected: func(g *WithT, m *clusterv1.Machine) { 1011 g.Expect(m.Status.InfrastructureReady).To(BeTrue()) 1012 g.Expect(m.GetOwnerReferences()).NotTo(ContainRefOfGroupKind("cluster.x-k8s.io", "MachineSet")) 1013 }, 1014 }, 1015 { 1016 name: "ready bootstrap, infra, and nodeRef, machine is running, infra object is deleted, expect failed", 1017 machine: &clusterv1.Machine{ 1018 ObjectMeta: metav1.ObjectMeta{ 1019 Name: "machine-test", 1020 Namespace: metav1.NamespaceDefault, 1021 }, 1022 Spec: clusterv1.MachineSpec{ 1023 Bootstrap: clusterv1.Bootstrap{ 1024 ConfigRef: &corev1.ObjectReference{ 1025 APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1", 1026 Kind: "GenericBootstrapConfig", 1027 Name: "bootstrap-config1", 1028 }, 1029 }, 1030 InfrastructureRef: corev1.ObjectReference{ 1031 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 1032 Kind: "GenericInfrastructureMachine", 1033 Name: "infra-config1", 1034 }, 1035 }, 1036 Status: clusterv1.MachineStatus{ 1037 BootstrapReady: true, 1038 InfrastructureReady: true, 1039 NodeRef: &corev1.ObjectReference{Kind: "Node", Name: "machine-test-node"}, 1040 }, 1041 }, 1042 bootstrapConfig: map[string]interface{}{ 1043 "kind": "GenericBootstrapConfig", 1044 "apiVersion": "bootstrap.cluster.x-k8s.io/v1beta1", 1045 "metadata": map[string]interface{}{ 1046 "name": "bootstrap-config1", 1047 "namespace": metav1.NamespaceDefault, 1048 }, 1049 "spec": map[string]interface{}{}, 1050 "status": map[string]interface{}{ 1051 "ready": true, 1052 "dataSecretName": "secret-data", 1053 }, 1054 }, 1055 infraConfig: map[string]interface{}{ 1056 "kind": "GenericInfrastructureMachine", 1057 "apiVersion": "infrastructure.cluster.x-k8s.io/v1beta1", 1058 "metadata": map[string]interface{}{}, 1059 }, 1060 expectResult: ctrl.Result{}, 1061 expectError: true, 1062 expected: func(g *WithT, m *clusterv1.Machine) { 1063 g.Expect(m.Status.InfrastructureReady).To(BeTrue()) 1064 g.Expect(m.Status.FailureMessage).NotTo(BeNil()) 1065 g.Expect(m.Status.FailureReason).NotTo(BeNil()) 1066 g.Expect(m.Status.GetTypedPhase()).To(Equal(clusterv1.MachinePhaseFailed)) 1067 }, 1068 }, 1069 { 1070 name: "infrastructure ref is paused", 1071 infraConfig: map[string]interface{}{ 1072 "kind": "GenericInfrastructureMachine", 1073 "apiVersion": "infrastructure.cluster.x-k8s.io/v1beta1", 1074 "metadata": map[string]interface{}{ 1075 "name": "infra-config1", 1076 "namespace": metav1.NamespaceDefault, 1077 "annotations": map[string]interface{}{ 1078 "cluster.x-k8s.io/paused": "true", 1079 }, 1080 }, 1081 "spec": map[string]interface{}{ 1082 "providerID": "test://id-1", 1083 }, 1084 "status": map[string]interface{}{ 1085 "ready": true, 1086 "addresses": []interface{}{ 1087 map[string]interface{}{ 1088 "type": "InternalIP", 1089 "address": "10.0.0.1", 1090 }, 1091 map[string]interface{}{ 1092 "type": "InternalIP", 1093 "address": "10.0.0.2", 1094 }, 1095 }, 1096 }, 1097 }, 1098 expectResult: ctrl.Result{}, 1099 expectError: false, 1100 expectChanged: false, 1101 expected: func(g *WithT, m *clusterv1.Machine) { 1102 g.Expect(m.Status.InfrastructureReady).To(BeFalse()) 1103 }, 1104 }, 1105 } 1106 1107 for _, tc := range testCases { 1108 t.Run(tc.name, func(t *testing.T) { 1109 g := NewWithT(t) 1110 1111 if tc.machine == nil { 1112 tc.machine = defaultMachine.DeepCopy() 1113 } 1114 1115 infraConfig := &unstructured.Unstructured{Object: tc.infraConfig} 1116 c := fake.NewClientBuilder(). 1117 WithObjects(tc.machine, 1118 builder.GenericBootstrapConfigCRD.DeepCopy(), 1119 builder.GenericInfrastructureMachineCRD.DeepCopy(), 1120 infraConfig, 1121 ).Build() 1122 r := &Reconciler{ 1123 Client: c, 1124 UnstructuredCachingClient: c, 1125 } 1126 s := &scope{cluster: defaultCluster, machine: tc.machine} 1127 result, err := r.reconcileInfrastructure(ctx, s) 1128 r.reconcilePhase(ctx, tc.machine) 1129 g.Expect(result).To(BeComparableTo(tc.expectResult)) 1130 if tc.expectError { 1131 g.Expect(err).To(HaveOccurred()) 1132 } else { 1133 g.Expect(err).ToNot(HaveOccurred()) 1134 } 1135 1136 if tc.expected != nil { 1137 tc.expected(g, tc.machine) 1138 } 1139 }) 1140 } 1141 } 1142 1143 func TestReconcileCertificateExpiry(t *testing.T) { 1144 fakeTimeString := "2020-01-01T00:00:00Z" 1145 fakeTime, _ := time.Parse(time.RFC3339, fakeTimeString) 1146 fakeMetaTime := &metav1.Time{Time: fakeTime} 1147 1148 fakeTimeString2 := "2020-02-02T00:00:00Z" 1149 fakeTime2, _ := time.Parse(time.RFC3339, fakeTimeString2) 1150 fakeMetaTime2 := &metav1.Time{Time: fakeTime2} 1151 1152 bootstrapConfigWithExpiry := &unstructured.Unstructured{ 1153 Object: map[string]interface{}{ 1154 "kind": "GenericBootstrapConfig", 1155 "apiVersion": "bootstrap.cluster.x-k8s.io/v1beta1", 1156 "metadata": map[string]interface{}{ 1157 "name": "bootstrap-config-with-expiry", 1158 "namespace": metav1.NamespaceDefault, 1159 "annotations": map[string]interface{}{ 1160 clusterv1.MachineCertificatesExpiryDateAnnotation: fakeTimeString, 1161 }, 1162 }, 1163 "spec": map[string]interface{}{}, 1164 "status": map[string]interface{}{ 1165 "ready": true, 1166 "dataSecretName": "secret-data", 1167 }, 1168 }, 1169 } 1170 1171 bootstrapConfigWithoutExpiry := &unstructured.Unstructured{ 1172 Object: map[string]interface{}{ 1173 "kind": "GenericBootstrapConfig", 1174 "apiVersion": "bootstrap.cluster.x-k8s.io/v1beta1", 1175 "metadata": map[string]interface{}{ 1176 "name": "bootstrap-config-without-expiry", 1177 "namespace": metav1.NamespaceDefault, 1178 }, 1179 "spec": map[string]interface{}{}, 1180 "status": map[string]interface{}{ 1181 "ready": true, 1182 "dataSecretName": "secret-data", 1183 }, 1184 }, 1185 } 1186 1187 tests := []struct { 1188 name string 1189 machine *clusterv1.Machine 1190 bootstrapConfig *unstructured.Unstructured 1191 expected func(g *WithT, m *clusterv1.Machine) 1192 }{ 1193 { 1194 name: "worker machine with certificate expiry annotation should not update expiry date", 1195 machine: &clusterv1.Machine{ 1196 ObjectMeta: metav1.ObjectMeta{ 1197 Name: "bootstrap-test-existing", 1198 Namespace: metav1.NamespaceDefault, 1199 Annotations: map[string]string{ 1200 clusterv1.MachineCertificatesExpiryDateAnnotation: fakeTimeString, 1201 }, 1202 }, 1203 Spec: clusterv1.MachineSpec{ 1204 Bootstrap: clusterv1.Bootstrap{}, 1205 }, 1206 }, 1207 expected: func(g *WithT, m *clusterv1.Machine) { 1208 g.Expect(m.Status.CertificatesExpiryDate).To(BeNil()) 1209 }, 1210 }, 1211 { 1212 name: "control plane machine with no bootstrap config and no certificate expiry annotation should not set expiry date", 1213 machine: &clusterv1.Machine{ 1214 ObjectMeta: metav1.ObjectMeta{ 1215 Name: "bootstrap-test-existing", 1216 Namespace: metav1.NamespaceDefault, 1217 Labels: map[string]string{ 1218 clusterv1.MachineControlPlaneLabel: "", 1219 }, 1220 }, 1221 Spec: clusterv1.MachineSpec{ 1222 Bootstrap: clusterv1.Bootstrap{}, 1223 }, 1224 }, 1225 expected: func(g *WithT, m *clusterv1.Machine) { 1226 g.Expect(m.Status.CertificatesExpiryDate).To(BeNil()) 1227 }, 1228 }, 1229 { 1230 name: "control plane machine with bootstrap config and no certificate expiry annotation should not set expiry date", 1231 machine: &clusterv1.Machine{ 1232 ObjectMeta: metav1.ObjectMeta{ 1233 Name: "bootstrap-test-existing", 1234 Namespace: metav1.NamespaceDefault, 1235 Labels: map[string]string{ 1236 clusterv1.MachineControlPlaneLabel: "", 1237 }, 1238 }, 1239 Spec: clusterv1.MachineSpec{ 1240 Bootstrap: clusterv1.Bootstrap{ 1241 ConfigRef: &corev1.ObjectReference{ 1242 APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1", 1243 Kind: "GenericBootstrapConfig", 1244 Name: "bootstrap-config-without-expiry", 1245 }, 1246 }, 1247 }, 1248 }, 1249 bootstrapConfig: bootstrapConfigWithoutExpiry, 1250 expected: func(g *WithT, m *clusterv1.Machine) { 1251 g.Expect(m.Status.CertificatesExpiryDate).To(BeNil()) 1252 }, 1253 }, 1254 { 1255 name: "control plane machine with certificate expiry annotation in bootstrap config should set expiry date", 1256 machine: &clusterv1.Machine{ 1257 ObjectMeta: metav1.ObjectMeta{ 1258 Name: "bootstrap-test-existing", 1259 Namespace: metav1.NamespaceDefault, 1260 Labels: map[string]string{ 1261 clusterv1.MachineControlPlaneLabel: "", 1262 }, 1263 }, 1264 Spec: clusterv1.MachineSpec{ 1265 Bootstrap: clusterv1.Bootstrap{ 1266 ConfigRef: &corev1.ObjectReference{ 1267 APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1", 1268 Kind: "GenericBootstrapConfig", 1269 Name: "bootstrap-config-with-expiry", 1270 }, 1271 }, 1272 }, 1273 }, 1274 bootstrapConfig: bootstrapConfigWithExpiry, 1275 expected: func(g *WithT, m *clusterv1.Machine) { 1276 g.Expect(m.Status.CertificatesExpiryDate).To(Equal(fakeMetaTime)) 1277 }, 1278 }, 1279 { 1280 name: "control plane machine with certificate expiry annotation and no certificate expiry annotation on bootstrap config should set expiry date", 1281 machine: &clusterv1.Machine{ 1282 ObjectMeta: metav1.ObjectMeta{ 1283 Name: "bootstrap-test-existing", 1284 Namespace: metav1.NamespaceDefault, 1285 Labels: map[string]string{ 1286 clusterv1.MachineControlPlaneLabel: "", 1287 }, 1288 Annotations: map[string]string{ 1289 clusterv1.MachineCertificatesExpiryDateAnnotation: fakeTimeString, 1290 }, 1291 }, 1292 Spec: clusterv1.MachineSpec{ 1293 Bootstrap: clusterv1.Bootstrap{ 1294 ConfigRef: &corev1.ObjectReference{ 1295 APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1", 1296 Kind: "GenericBootstrapConfig", 1297 Name: "bootstrap-config-without-expiry", 1298 }, 1299 }, 1300 }, 1301 }, 1302 bootstrapConfig: bootstrapConfigWithoutExpiry, 1303 expected: func(g *WithT, m *clusterv1.Machine) { 1304 g.Expect(m.Status.CertificatesExpiryDate).To(Equal(fakeMetaTime)) 1305 }, 1306 }, 1307 { 1308 name: "control plane machine with certificate expiry annotation in machine should take precedence over bootstrap config and should set expiry date", 1309 machine: &clusterv1.Machine{ 1310 ObjectMeta: metav1.ObjectMeta{ 1311 Name: "bootstrap-test-existing", 1312 Namespace: metav1.NamespaceDefault, 1313 Labels: map[string]string{ 1314 clusterv1.MachineControlPlaneLabel: "", 1315 }, 1316 Annotations: map[string]string{ 1317 clusterv1.MachineCertificatesExpiryDateAnnotation: fakeTimeString2, 1318 }, 1319 }, 1320 Spec: clusterv1.MachineSpec{ 1321 Bootstrap: clusterv1.Bootstrap{ 1322 ConfigRef: &corev1.ObjectReference{ 1323 APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1", 1324 Kind: "GenericBootstrapConfig", 1325 Name: "bootstrap-config-with-expiry", 1326 }, 1327 }, 1328 }, 1329 }, 1330 bootstrapConfig: bootstrapConfigWithExpiry, 1331 expected: func(g *WithT, m *clusterv1.Machine) { 1332 g.Expect(m.Status.CertificatesExpiryDate).To(Equal(fakeMetaTime2)) 1333 }, 1334 }, 1335 { 1336 name: "reset certificates expiry information in machine status if the information is not available on the machine and the bootstrap config", 1337 machine: &clusterv1.Machine{ 1338 ObjectMeta: metav1.ObjectMeta{ 1339 Name: "bootstrap-test-existing", 1340 Namespace: metav1.NamespaceDefault, 1341 Labels: map[string]string{ 1342 clusterv1.MachineControlPlaneLabel: "", 1343 }, 1344 }, 1345 Spec: clusterv1.MachineSpec{ 1346 Bootstrap: clusterv1.Bootstrap{ 1347 ConfigRef: &corev1.ObjectReference{ 1348 APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1", 1349 Kind: "GenericBootstrapConfig", 1350 Name: "bootstrap-config-without-expiry", 1351 }, 1352 }, 1353 }, 1354 Status: clusterv1.MachineStatus{ 1355 CertificatesExpiryDate: fakeMetaTime, 1356 }, 1357 }, 1358 bootstrapConfig: bootstrapConfigWithoutExpiry, 1359 expected: func(g *WithT, m *clusterv1.Machine) { 1360 g.Expect(m.Status.CertificatesExpiryDate).To(BeNil()) 1361 }, 1362 }, 1363 } 1364 1365 for _, tc := range tests { 1366 t.Run(tc.name, func(t *testing.T) { 1367 g := NewWithT(t) 1368 1369 r := &Reconciler{} 1370 s := &scope{machine: tc.machine, bootstrapConfig: tc.bootstrapConfig} 1371 _, _ = r.reconcileCertificateExpiry(ctx, s) 1372 if tc.expected != nil { 1373 tc.expected(g, tc.machine) 1374 } 1375 }) 1376 } 1377 }