sigs.k8s.io/cluster-api@v1.7.1/exp/internal/controllers/machinepool_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 controllers 18 19 import ( 20 "fmt" 21 "testing" 22 "time" 23 24 "github.com/go-logr/logr" 25 . "github.com/onsi/gomega" 26 corev1 "k8s.io/api/core/v1" 27 apierrors "k8s.io/apimachinery/pkg/api/errors" 28 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 29 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 30 "k8s.io/apimachinery/pkg/types" 31 "k8s.io/client-go/tools/record" 32 "k8s.io/utils/ptr" 33 ctrl "sigs.k8s.io/controller-runtime" 34 "sigs.k8s.io/controller-runtime/pkg/client" 35 "sigs.k8s.io/controller-runtime/pkg/client/fake" 36 "sigs.k8s.io/controller-runtime/pkg/log" 37 38 clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" 39 "sigs.k8s.io/cluster-api/controllers/external" 40 "sigs.k8s.io/cluster-api/controllers/remote" 41 expv1 "sigs.k8s.io/cluster-api/exp/api/v1beta1" 42 "sigs.k8s.io/cluster-api/internal/test/builder" 43 "sigs.k8s.io/cluster-api/internal/util/ssa" 44 "sigs.k8s.io/cluster-api/util/kubeconfig" 45 "sigs.k8s.io/cluster-api/util/labels/format" 46 ) 47 48 const ( 49 clusterName = "test-cluster" 50 wrongNamespace = "wrong-namespace" 51 ) 52 53 func TestReconcileMachinePoolPhases(t *testing.T) { 54 deletionTimestamp := metav1.Now() 55 56 var defaultKubeconfigSecret *corev1.Secret 57 defaultCluster := &clusterv1.Cluster{ 58 ObjectMeta: metav1.ObjectMeta{ 59 Name: clusterName, 60 Namespace: metav1.NamespaceDefault, 61 }, 62 } 63 64 defaultMachinePool := expv1.MachinePool{ 65 ObjectMeta: metav1.ObjectMeta{ 66 Name: "machinepool-test", 67 Namespace: metav1.NamespaceDefault, 68 }, 69 Spec: expv1.MachinePoolSpec{ 70 ClusterName: defaultCluster.Name, 71 Replicas: ptr.To[int32](1), 72 Template: clusterv1.MachineTemplateSpec{ 73 Spec: clusterv1.MachineSpec{ 74 Bootstrap: clusterv1.Bootstrap{ 75 ConfigRef: &corev1.ObjectReference{ 76 APIVersion: builder.BootstrapGroupVersion.String(), 77 Kind: builder.TestBootstrapConfigKind, 78 Name: "bootstrap-config1", 79 }, 80 }, 81 InfrastructureRef: corev1.ObjectReference{ 82 APIVersion: builder.InfrastructureGroupVersion.String(), 83 Kind: builder.TestInfrastructureMachineTemplateKind, 84 Name: "infra-config1", 85 }, 86 }, 87 }, 88 }, 89 } 90 91 defaultBootstrap := &unstructured.Unstructured{ 92 Object: map[string]interface{}{ 93 "kind": builder.TestBootstrapConfigKind, 94 "apiVersion": builder.BootstrapGroupVersion.String(), 95 "metadata": map[string]interface{}{ 96 "name": "bootstrap-config1", 97 "namespace": metav1.NamespaceDefault, 98 }, 99 "spec": map[string]interface{}{}, 100 "status": map[string]interface{}{}, 101 }, 102 } 103 104 defaultInfra := &unstructured.Unstructured{ 105 Object: map[string]interface{}{ 106 "kind": builder.TestInfrastructureMachineTemplateKind, 107 "apiVersion": builder.InfrastructureGroupVersion.String(), 108 "metadata": map[string]interface{}{ 109 "name": "infra-config1", 110 "namespace": metav1.NamespaceDefault, 111 }, 112 "spec": map[string]interface{}{ 113 "providerIDList": []interface{}{ 114 "test://id-1", 115 }, 116 }, 117 "status": map[string]interface{}{}, 118 }, 119 } 120 121 t.Run("Should set OwnerReference and cluster name label on external objects", func(t *testing.T) { 122 g := NewWithT(t) 123 124 defaultKubeconfigSecret = kubeconfig.GenerateSecret(defaultCluster, kubeconfig.FromEnvTestConfig(env.Config, defaultCluster)) 125 machinepool := defaultMachinePool.DeepCopy() 126 bootstrapConfig := defaultBootstrap.DeepCopy() 127 infraConfig := defaultInfra.DeepCopy() 128 129 r := &MachinePoolReconciler{ 130 Client: fake.NewClientBuilder().WithObjects(defaultCluster, defaultKubeconfigSecret, machinepool, bootstrapConfig, infraConfig, builder.TestBootstrapConfigCRD, builder.TestInfrastructureMachineTemplateCRD).Build(), 131 } 132 133 res, err := r.reconcile(ctx, defaultCluster, machinepool) 134 g.Expect(err).ToNot(HaveOccurred()) 135 g.Expect(res.Requeue).To(BeFalse()) 136 137 r.reconcilePhase(machinepool) 138 139 g.Expect(r.Client.Get(ctx, types.NamespacedName{Name: bootstrapConfig.GetName(), Namespace: bootstrapConfig.GetNamespace()}, bootstrapConfig)).To(Succeed()) 140 141 g.Expect(bootstrapConfig.GetOwnerReferences()).To(HaveLen(1)) 142 g.Expect(bootstrapConfig.GetLabels()[clusterv1.ClusterNameLabel]).To(BeEquivalentTo(clusterName)) 143 144 g.Expect(r.Client.Get(ctx, types.NamespacedName{Name: infraConfig.GetName(), Namespace: infraConfig.GetNamespace()}, infraConfig)).To(Succeed()) 145 146 g.Expect(infraConfig.GetOwnerReferences()).To(HaveLen(1)) 147 g.Expect(infraConfig.GetLabels()[clusterv1.ClusterNameLabel]).To(BeEquivalentTo(clusterName)) 148 }) 149 150 t.Run("Should set `Pending` with a new MachinePool", func(t *testing.T) { 151 g := NewWithT(t) 152 153 defaultKubeconfigSecret = kubeconfig.GenerateSecret(defaultCluster, kubeconfig.FromEnvTestConfig(env.Config, defaultCluster)) 154 machinepool := defaultMachinePool.DeepCopy() 155 bootstrapConfig := defaultBootstrap.DeepCopy() 156 infraConfig := defaultInfra.DeepCopy() 157 158 r := &MachinePoolReconciler{ 159 Client: fake.NewClientBuilder().WithObjects(defaultCluster, defaultKubeconfigSecret, machinepool, bootstrapConfig, infraConfig, builder.TestBootstrapConfigCRD, builder.TestInfrastructureMachineTemplateCRD).Build(), 160 } 161 162 res, err := r.reconcile(ctx, defaultCluster, machinepool) 163 g.Expect(err).ToNot(HaveOccurred()) 164 g.Expect(res.Requeue).To(BeFalse()) 165 166 r.reconcilePhase(machinepool) 167 g.Expect(machinepool.Status.GetTypedPhase()).To(Equal(expv1.MachinePoolPhasePending)) 168 }) 169 170 t.Run("Should set `Provisioning` when bootstrap is ready", func(t *testing.T) { 171 g := NewWithT(t) 172 173 defaultKubeconfigSecret = kubeconfig.GenerateSecret(defaultCluster, kubeconfig.FromEnvTestConfig(env.Config, defaultCluster)) 174 machinepool := defaultMachinePool.DeepCopy() 175 bootstrapConfig := defaultBootstrap.DeepCopy() 176 infraConfig := defaultInfra.DeepCopy() 177 178 // Set bootstrap ready. 179 err := unstructured.SetNestedField(bootstrapConfig.Object, true, "status", "ready") 180 g.Expect(err).ToNot(HaveOccurred()) 181 182 err = unstructured.SetNestedField(bootstrapConfig.Object, "secret-data", "status", "dataSecretName") 183 g.Expect(err).ToNot(HaveOccurred()) 184 185 r := &MachinePoolReconciler{ 186 Client: fake.NewClientBuilder().WithObjects(defaultCluster, defaultKubeconfigSecret, machinepool, bootstrapConfig, infraConfig, builder.TestBootstrapConfigCRD, builder.TestInfrastructureMachineTemplateCRD).Build(), 187 } 188 189 res, err := r.reconcile(ctx, defaultCluster, machinepool) 190 g.Expect(err).ToNot(HaveOccurred()) 191 g.Expect(res.Requeue).To(BeFalse()) 192 193 r.reconcilePhase(machinepool) 194 g.Expect(machinepool.Status.GetTypedPhase()).To(Equal(expv1.MachinePoolPhaseProvisioning)) 195 }) 196 197 t.Run("Should set `Running` when bootstrap and infra is ready", func(t *testing.T) { 198 g := NewWithT(t) 199 200 defaultKubeconfigSecret = kubeconfig.GenerateSecret(defaultCluster, kubeconfig.FromEnvTestConfig(env.Config, defaultCluster)) 201 machinepool := defaultMachinePool.DeepCopy() 202 bootstrapConfig := defaultBootstrap.DeepCopy() 203 infraConfig := defaultInfra.DeepCopy() 204 205 // Set bootstrap ready. 206 err := unstructured.SetNestedField(bootstrapConfig.Object, true, "status", "ready") 207 g.Expect(err).ToNot(HaveOccurred()) 208 209 err = unstructured.SetNestedField(bootstrapConfig.Object, "secret-data", "status", "dataSecretName") 210 g.Expect(err).ToNot(HaveOccurred()) 211 212 // Set infra ready. 213 err = unstructured.SetNestedField(infraConfig.Object, true, "status", "ready") 214 g.Expect(err).ToNot(HaveOccurred()) 215 216 err = unstructured.SetNestedField(infraConfig.Object, int64(1), "status", "replicas") 217 g.Expect(err).ToNot(HaveOccurred()) 218 219 err = unstructured.SetNestedStringSlice(infraConfig.Object, []string{"test://machinepool-test-node"}, "spec", "providerIDList") 220 g.Expect(err).ToNot(HaveOccurred()) 221 222 err = unstructured.SetNestedField(infraConfig.Object, "us-east-2a", "spec", "failureDomain") 223 g.Expect(err).ToNot(HaveOccurred()) 224 225 // Set NodeRef. 226 machinepool.Status.NodeRefs = []corev1.ObjectReference{{Kind: "Node", Name: "machinepool-test-node"}} 227 228 fakeClient := fake.NewClientBuilder().WithObjects(defaultCluster, defaultKubeconfigSecret, machinepool, bootstrapConfig, infraConfig, builder.TestBootstrapConfigCRD, builder.TestInfrastructureMachineTemplateCRD).Build() 229 r := &MachinePoolReconciler{ 230 Client: fakeClient, 231 Tracker: remote.NewTestClusterCacheTracker(logr.New(log.NullLogSink{}), fakeClient, fakeClient, fakeClient.Scheme(), client.ObjectKey{Name: defaultCluster.Name, Namespace: defaultCluster.Namespace}), 232 } 233 234 res, err := r.reconcile(ctx, defaultCluster, machinepool) 235 g.Expect(err).ToNot(HaveOccurred()) 236 g.Expect(res.Requeue).To(BeFalse()) 237 238 // Set ReadyReplicas 239 machinepool.Status.ReadyReplicas = 1 240 241 r.reconcilePhase(machinepool) 242 g.Expect(machinepool.Status.GetTypedPhase()).To(Equal(expv1.MachinePoolPhaseRunning)) 243 }) 244 245 t.Run("Should set `Running` when bootstrap, infra, and ready replicas equals spec replicas", func(t *testing.T) { 246 g := NewWithT(t) 247 248 defaultKubeconfigSecret = kubeconfig.GenerateSecret(defaultCluster, kubeconfig.FromEnvTestConfig(env.Config, defaultCluster)) 249 machinepool := defaultMachinePool.DeepCopy() 250 bootstrapConfig := defaultBootstrap.DeepCopy() 251 infraConfig := defaultInfra.DeepCopy() 252 253 // Set bootstrap ready. 254 err := unstructured.SetNestedField(bootstrapConfig.Object, true, "status", "ready") 255 g.Expect(err).ToNot(HaveOccurred()) 256 257 err = unstructured.SetNestedField(bootstrapConfig.Object, "secret-data", "status", "dataSecretName") 258 g.Expect(err).ToNot(HaveOccurred()) 259 260 // Set infra ready. 261 err = unstructured.SetNestedStringSlice(infraConfig.Object, []string{"test://id-1"}, "spec", "providerIDList") 262 g.Expect(err).ToNot(HaveOccurred()) 263 264 err = unstructured.SetNestedField(infraConfig.Object, true, "status", "ready") 265 g.Expect(err).ToNot(HaveOccurred()) 266 267 err = unstructured.SetNestedField(infraConfig.Object, int64(1), "status", "replicas") 268 g.Expect(err).ToNot(HaveOccurred()) 269 270 err = unstructured.SetNestedField(infraConfig.Object, []interface{}{ 271 map[string]interface{}{ 272 "type": "InternalIP", 273 "address": "10.0.0.1", 274 }, 275 map[string]interface{}{ 276 "type": "InternalIP", 277 "address": "10.0.0.2", 278 }, 279 }, "addresses") 280 g.Expect(err).ToNot(HaveOccurred()) 281 282 // Set NodeRef. 283 machinepool.Status.NodeRefs = []corev1.ObjectReference{{Kind: "Node", Name: "machinepool-test-node"}} 284 285 fakeClient := fake.NewClientBuilder().WithObjects(defaultCluster, defaultKubeconfigSecret, machinepool, bootstrapConfig, infraConfig, builder.TestBootstrapConfigCRD, builder.TestInfrastructureMachineTemplateCRD).Build() 286 r := &MachinePoolReconciler{ 287 Client: fakeClient, 288 Tracker: remote.NewTestClusterCacheTracker(logr.New(log.NullLogSink{}), fakeClient, fakeClient, fakeClient.Scheme(), client.ObjectKey{Name: defaultCluster.Name, Namespace: defaultCluster.Namespace}), 289 } 290 291 res, err := r.reconcile(ctx, defaultCluster, machinepool) 292 g.Expect(err).ToNot(HaveOccurred()) 293 g.Expect(res.Requeue).To(BeFalse()) 294 295 // Set ReadyReplicas 296 machinepool.Status.ReadyReplicas = 1 297 298 r.reconcilePhase(machinepool) 299 g.Expect(machinepool.Status.GetTypedPhase()).To(Equal(expv1.MachinePoolPhaseRunning)) 300 }) 301 302 t.Run("Should set `Provisioned` when there is a NodeRef but infra is not ready ", func(t *testing.T) { 303 g := NewWithT(t) 304 305 defaultKubeconfigSecret = kubeconfig.GenerateSecret(defaultCluster, kubeconfig.FromEnvTestConfig(env.Config, defaultCluster)) 306 machinepool := defaultMachinePool.DeepCopy() 307 bootstrapConfig := defaultBootstrap.DeepCopy() 308 infraConfig := defaultInfra.DeepCopy() 309 310 // Set bootstrap ready. 311 err := unstructured.SetNestedField(bootstrapConfig.Object, true, "status", "ready") 312 g.Expect(err).ToNot(HaveOccurred()) 313 314 err = unstructured.SetNestedField(bootstrapConfig.Object, "secret-data", "status", "dataSecretName") 315 g.Expect(err).ToNot(HaveOccurred()) 316 317 // Set NodeRef. 318 machinepool.Status.NodeRefs = []corev1.ObjectReference{{Kind: "Node", Name: "machinepool-test-node"}} 319 320 r := &MachinePoolReconciler{ 321 Client: fake.NewClientBuilder().WithObjects(defaultCluster, defaultKubeconfigSecret, machinepool, bootstrapConfig, infraConfig, builder.TestBootstrapConfigCRD, builder.TestInfrastructureMachineTemplateCRD).Build(), 322 } 323 324 res, err := r.reconcile(ctx, defaultCluster, machinepool) 325 g.Expect(err).ToNot(HaveOccurred()) 326 g.Expect(res.Requeue).To(BeFalse()) 327 328 r.reconcilePhase(machinepool) 329 g.Expect(machinepool.Status.GetTypedPhase()).To(Equal(expv1.MachinePoolPhaseProvisioned)) 330 }) 331 332 t.Run("Should set `ScalingUp` when infra is scaling up", func(t *testing.T) { 333 g := NewWithT(t) 334 335 defaultKubeconfigSecret = kubeconfig.GenerateSecret(defaultCluster, kubeconfig.FromEnvTestConfig(env.Config, defaultCluster)) 336 machinepool := defaultMachinePool.DeepCopy() 337 bootstrapConfig := defaultBootstrap.DeepCopy() 338 infraConfig := defaultInfra.DeepCopy() 339 340 // Set bootstrap ready. 341 err := unstructured.SetNestedField(bootstrapConfig.Object, true, "status", "ready") 342 g.Expect(err).ToNot(HaveOccurred()) 343 344 err = unstructured.SetNestedField(bootstrapConfig.Object, "secret-data", "status", "dataSecretName") 345 g.Expect(err).ToNot(HaveOccurred()) 346 347 // Set infra ready. 348 err = unstructured.SetNestedStringSlice(infraConfig.Object, []string{"test://id-1"}, "spec", "providerIDList") 349 g.Expect(err).ToNot(HaveOccurred()) 350 351 err = unstructured.SetNestedField(infraConfig.Object, true, "status", "ready") 352 g.Expect(err).ToNot(HaveOccurred()) 353 354 err = unstructured.SetNestedField(infraConfig.Object, int64(1), "status", "replicas") 355 g.Expect(err).ToNot(HaveOccurred()) 356 357 // Set NodeRef. 358 machinepool.Status.NodeRefs = []corev1.ObjectReference{{Kind: "Node", Name: "machinepool-test-node"}} 359 360 fakeClient := fake.NewClientBuilder().WithObjects(defaultCluster, defaultKubeconfigSecret, machinepool, bootstrapConfig, infraConfig, builder.TestBootstrapConfigCRD, builder.TestInfrastructureMachineTemplateCRD).Build() 361 r := &MachinePoolReconciler{ 362 Client: fakeClient, 363 Tracker: remote.NewTestClusterCacheTracker(logr.New(log.NullLogSink{}), fakeClient, fakeClient, fakeClient.Scheme(), client.ObjectKey{Name: defaultCluster.Name, Namespace: defaultCluster.Namespace}), 364 } 365 366 res, err := r.reconcile(ctx, defaultCluster, machinepool) 367 g.Expect(err).ToNot(HaveOccurred()) 368 g.Expect(res.Requeue).To(BeFalse()) 369 370 // Set ReadyReplicas 371 machinepool.Status.ReadyReplicas = 1 372 373 // Scale up 374 machinepool.Spec.Replicas = ptr.To[int32](5) 375 376 r.reconcilePhase(machinepool) 377 g.Expect(machinepool.Status.GetTypedPhase()).To(Equal(expv1.MachinePoolPhaseScalingUp)) 378 }) 379 380 t.Run("Should set `ScalingDown` when infra is scaling down", func(t *testing.T) { 381 g := NewWithT(t) 382 383 defaultKubeconfigSecret = kubeconfig.GenerateSecret(defaultCluster, kubeconfig.FromEnvTestConfig(env.Config, defaultCluster)) 384 machinepool := defaultMachinePool.DeepCopy() 385 bootstrapConfig := defaultBootstrap.DeepCopy() 386 infraConfig := defaultInfra.DeepCopy() 387 388 // Set bootstrap ready. 389 err := unstructured.SetNestedField(bootstrapConfig.Object, true, "status", "ready") 390 g.Expect(err).ToNot(HaveOccurred()) 391 392 err = unstructured.SetNestedField(bootstrapConfig.Object, "secret-data", "status", "dataSecretName") 393 g.Expect(err).ToNot(HaveOccurred()) 394 395 // Set infra ready. 396 err = unstructured.SetNestedStringSlice(infraConfig.Object, []string{"test://id-1"}, "spec", "providerIDList") 397 g.Expect(err).ToNot(HaveOccurred()) 398 399 err = unstructured.SetNestedField(infraConfig.Object, true, "status", "ready") 400 g.Expect(err).ToNot(HaveOccurred()) 401 402 err = unstructured.SetNestedField(infraConfig.Object, int64(4), "status", "replicas") 403 g.Expect(err).ToNot(HaveOccurred()) 404 405 machinepool.Spec.Replicas = ptr.To[int32](4) 406 407 // Set NodeRef. 408 machinepool.Status.NodeRefs = []corev1.ObjectReference{ 409 {Kind: "Node", Name: "machinepool-test-node-0"}, 410 {Kind: "Node", Name: "machinepool-test-node-1"}, 411 {Kind: "Node", Name: "machinepool-test-node-2"}, 412 {Kind: "Node", Name: "machinepool-test-node-3"}, 413 } 414 415 fakeClient := fake.NewClientBuilder().WithObjects(defaultCluster, defaultKubeconfigSecret, machinepool, bootstrapConfig, infraConfig, builder.TestBootstrapConfigCRD, builder.TestInfrastructureMachineTemplateCRD).Build() 416 r := &MachinePoolReconciler{ 417 Client: fakeClient, 418 Tracker: remote.NewTestClusterCacheTracker(logr.New(log.NullLogSink{}), fakeClient, fakeClient, fakeClient.Scheme(), client.ObjectKey{Name: defaultCluster.Name, Namespace: defaultCluster.Namespace}), 419 } 420 421 res, err := r.reconcile(ctx, defaultCluster, machinepool) 422 g.Expect(err).ToNot(HaveOccurred()) 423 g.Expect(res.Requeue).To(BeFalse()) 424 425 // Set ReadyReplicas 426 machinepool.Status.ReadyReplicas = 4 427 428 // Scale down 429 machinepool.Spec.Replicas = ptr.To[int32](1) 430 431 r.reconcilePhase(machinepool) 432 g.Expect(machinepool.Status.GetTypedPhase()).To(Equal(expv1.MachinePoolPhaseScalingDown)) 433 }) 434 435 t.Run("Should set `Deleting` when MachinePool is being deleted", func(t *testing.T) { 436 g := NewWithT(t) 437 438 defaultKubeconfigSecret = kubeconfig.GenerateSecret(defaultCluster, kubeconfig.FromEnvTestConfig(env.Config, defaultCluster)) 439 machinepool := defaultMachinePool.DeepCopy() 440 bootstrapConfig := defaultBootstrap.DeepCopy() 441 infraConfig := defaultInfra.DeepCopy() 442 443 // Set bootstrap ready. 444 err := unstructured.SetNestedField(bootstrapConfig.Object, true, "status", "ready") 445 g.Expect(err).ToNot(HaveOccurred()) 446 447 err = unstructured.SetNestedField(bootstrapConfig.Object, "secret-data", "status", "dataSecretName") 448 g.Expect(err).ToNot(HaveOccurred()) 449 450 // Set infra ready. 451 err = unstructured.SetNestedStringSlice(infraConfig.Object, []string{"test://id-1"}, "spec", "providerIDList") 452 g.Expect(err).ToNot(HaveOccurred()) 453 454 err = unstructured.SetNestedField(infraConfig.Object, true, "status", "ready") 455 g.Expect(err).ToNot(HaveOccurred()) 456 457 err = unstructured.SetNestedField(infraConfig.Object, []interface{}{ 458 map[string]interface{}{ 459 "type": "InternalIP", 460 "address": "10.0.0.1", 461 }, 462 map[string]interface{}{ 463 "type": "InternalIP", 464 "address": "10.0.0.2", 465 }, 466 }, "addresses") 467 g.Expect(err).ToNot(HaveOccurred()) 468 469 // Set NodeRef. 470 machinepool.Status.NodeRefs = []corev1.ObjectReference{{Kind: "Node", Name: "machinepool-test-node"}} 471 472 // Set Deletion Timestamp. 473 machinepool.SetDeletionTimestamp(&deletionTimestamp) 474 machinepool.Finalizers = []string{expv1.MachinePoolFinalizer} 475 476 r := &MachinePoolReconciler{ 477 Client: fake.NewClientBuilder().WithObjects(defaultCluster, defaultKubeconfigSecret, machinepool, bootstrapConfig, infraConfig, builder.TestBootstrapConfigCRD, builder.TestInfrastructureMachineTemplateCRD).Build(), 478 } 479 480 res, err := r.reconcile(ctx, defaultCluster, machinepool) 481 g.Expect(err).ToNot(HaveOccurred()) 482 g.Expect(res.Requeue).To(BeFalse()) 483 484 r.reconcilePhase(machinepool) 485 g.Expect(machinepool.Status.GetTypedPhase()).To(Equal(expv1.MachinePoolPhaseDeleting)) 486 }) 487 488 t.Run("Should keep `Running` when MachinePool bootstrap config is changed to another ready one", func(t *testing.T) { 489 g := NewWithT(t) 490 491 defaultKubeconfigSecret = kubeconfig.GenerateSecret(defaultCluster, kubeconfig.FromEnvTestConfig(env.Config, defaultCluster)) 492 machinePool := defaultMachinePool.DeepCopy() 493 bootstrapConfig := defaultBootstrap.DeepCopy() 494 infraConfig := defaultInfra.DeepCopy() 495 496 // Set bootstrap ready. 497 err := unstructured.SetNestedField(bootstrapConfig.Object, true, "status", "ready") 498 g.Expect(err).ToNot(HaveOccurred()) 499 500 err = unstructured.SetNestedField(bootstrapConfig.Object, "secret-data", "status", "dataSecretName") 501 g.Expect(err).ToNot(HaveOccurred()) 502 503 // Set infra ready. 504 err = unstructured.SetNestedStringSlice(infraConfig.Object, []string{"test://id-1"}, "spec", "providerIDList") 505 g.Expect(err).ToNot(HaveOccurred()) 506 507 err = unstructured.SetNestedField(infraConfig.Object, true, "status", "ready") 508 g.Expect(err).ToNot(HaveOccurred()) 509 510 err = unstructured.SetNestedField(infraConfig.Object, []interface{}{ 511 map[string]interface{}{ 512 "type": "InternalIP", 513 "address": "10.0.0.1", 514 }, 515 map[string]interface{}{ 516 "type": "InternalIP", 517 "address": "10.0.0.2", 518 }, 519 }, "addresses") 520 g.Expect(err).ToNot(HaveOccurred()) 521 522 err = unstructured.SetNestedField(infraConfig.Object, int64(1), "status", "replicas") 523 g.Expect(err).ToNot(HaveOccurred()) 524 525 // Set NodeRef. 526 machinePool.Status.NodeRefs = []corev1.ObjectReference{{Kind: "Node", Name: "machinepool-test-node"}} 527 528 // Set replicas to fully reconciled 529 machinePool.Spec.ProviderIDList = []string{"test://id-1"} 530 machinePool.Status.ReadyReplicas = 1 531 machinePool.Status.Replicas = 1 532 533 r := &MachinePoolReconciler{ 534 Client: fake.NewClientBuilder().WithObjects(defaultCluster, defaultKubeconfigSecret, machinePool, bootstrapConfig, infraConfig, builder.TestBootstrapConfigCRD, builder.TestInfrastructureMachineTemplateCRD).Build(), 535 } 536 537 res, err := r.reconcile(ctx, defaultCluster, machinePool) 538 g.Expect(err).ToNot(HaveOccurred()) 539 g.Expect(res.Requeue).To(BeFalse()) 540 541 r.reconcilePhase(machinePool) 542 g.Expect(machinePool.Status.GetTypedPhase()).To(Equal(expv1.MachinePoolPhaseRunning)) 543 g.Expect(*machinePool.Spec.Template.Spec.Bootstrap.DataSecretName).To(Equal("secret-data")) 544 545 // Change bootstrap reference. 546 newBootstrapConfig := defaultBootstrap.DeepCopy() 547 newBootstrapConfig.SetName("bootstrap-config2") 548 err = unstructured.SetNestedField(newBootstrapConfig.Object, true, "status", "ready") 549 g.Expect(err).ToNot(HaveOccurred()) 550 err = unstructured.SetNestedField(newBootstrapConfig.Object, "secret-data-new", "status", "dataSecretName") 551 g.Expect(err).ToNot(HaveOccurred()) 552 err = r.Client.Create(ctx, newBootstrapConfig) 553 g.Expect(err).ToNot(HaveOccurred()) 554 machinePool.Spec.Template.Spec.Bootstrap.ConfigRef.Name = newBootstrapConfig.GetName() 555 556 // Reconcile again. The new bootstrap config should be used. 557 res, err = r.reconcile(ctx, defaultCluster, machinePool) 558 g.Expect(err).ToNot(HaveOccurred()) 559 g.Expect(res.Requeue).To(BeFalse()) 560 561 r.reconcilePhase(machinePool) 562 g.Expect(*machinePool.Spec.Template.Spec.Bootstrap.DataSecretName).To(Equal("secret-data-new")) 563 g.Expect(machinePool.Status.BootstrapReady).To(BeTrue()) 564 g.Expect(machinePool.Status.GetTypedPhase()).To(Equal(expv1.MachinePoolPhaseRunning)) 565 }) 566 567 t.Run("Should keep `Running` when MachinePool bootstrap config is changed to a non-ready one", func(t *testing.T) { 568 g := NewWithT(t) 569 570 defaultKubeconfigSecret = kubeconfig.GenerateSecret(defaultCluster, kubeconfig.FromEnvTestConfig(env.Config, defaultCluster)) 571 machinePool := defaultMachinePool.DeepCopy() 572 bootstrapConfig := defaultBootstrap.DeepCopy() 573 infraConfig := defaultInfra.DeepCopy() 574 575 // Set bootstrap ready 576 err := unstructured.SetNestedField(bootstrapConfig.Object, true, "status", "ready") 577 g.Expect(err).ToNot(HaveOccurred()) 578 579 err = unstructured.SetNestedField(bootstrapConfig.Object, "secret-data", "status", "dataSecretName") 580 g.Expect(err).ToNot(HaveOccurred()) 581 582 // Set infra ready 583 err = unstructured.SetNestedStringSlice(infraConfig.Object, []string{"test://id-1"}, "spec", "providerIDList") 584 g.Expect(err).ToNot(HaveOccurred()) 585 586 err = unstructured.SetNestedField(infraConfig.Object, true, "status", "ready") 587 g.Expect(err).ToNot(HaveOccurred()) 588 589 err = unstructured.SetNestedField(infraConfig.Object, []interface{}{ 590 map[string]interface{}{ 591 "type": "InternalIP", 592 "address": "10.0.0.1", 593 }, 594 map[string]interface{}{ 595 "type": "InternalIP", 596 "address": "10.0.0.2", 597 }, 598 }, "addresses") 599 g.Expect(err).ToNot(HaveOccurred()) 600 601 err = unstructured.SetNestedField(infraConfig.Object, int64(1), "status", "replicas") 602 g.Expect(err).ToNot(HaveOccurred()) 603 604 // Set NodeRef 605 machinePool.Status.NodeRefs = []corev1.ObjectReference{{Kind: "Node", Name: "machinepool-test-node"}} 606 607 // Set replicas to fully reconciled 608 machinePool.Spec.ProviderIDList = []string{"test://id-1"} 609 machinePool.Status.ReadyReplicas = 1 610 machinePool.Status.Replicas = 1 611 612 r := &MachinePoolReconciler{ 613 Client: fake.NewClientBuilder().WithObjects(defaultCluster, defaultKubeconfigSecret, machinePool, bootstrapConfig, infraConfig, builder.TestBootstrapConfigCRD, builder.TestInfrastructureMachineTemplateCRD).Build(), 614 } 615 616 res, err := r.reconcile(ctx, defaultCluster, machinePool) 617 g.Expect(err).ToNot(HaveOccurred()) 618 g.Expect(res.Requeue).To(BeFalse()) 619 620 r.reconcilePhase(machinePool) 621 g.Expect(machinePool.Status.GetTypedPhase()).To(Equal(expv1.MachinePoolPhaseRunning)) 622 g.Expect(*machinePool.Spec.Template.Spec.Bootstrap.DataSecretName).To(Equal("secret-data")) 623 624 // Change bootstrap reference 625 newBootstrapConfig := defaultBootstrap.DeepCopy() 626 newBootstrapConfig.SetName("bootstrap-config2") 627 err = unstructured.SetNestedField(newBootstrapConfig.Object, false, "status", "ready") 628 g.Expect(err).ToNot(HaveOccurred()) 629 // Fill the `dataSecretName` so we can check if the machine pool uses the non-ready secret immediately or, 630 // as it should, not yet 631 err = unstructured.SetNestedField(newBootstrapConfig.Object, "secret-data-new", "status", "dataSecretName") 632 g.Expect(err).ToNot(HaveOccurred()) 633 err = r.Client.Create(ctx, newBootstrapConfig) 634 g.Expect(err).ToNot(HaveOccurred()) 635 machinePool.Spec.Template.Spec.Bootstrap.ConfigRef.Name = newBootstrapConfig.GetName() 636 637 // Reconcile again. The new bootstrap config should be used 638 res, err = r.reconcile(ctx, defaultCluster, machinePool) 639 g.Expect(err).ToNot(HaveOccurred()) 640 641 // Controller should wait until bootstrap provider reports ready bootstrap config 642 g.Expect(res.Requeue).To(BeFalse()) 643 644 r.reconcilePhase(machinePool) 645 646 // The old secret should still be used, as the new bootstrap config is not marked ready 647 g.Expect(*machinePool.Spec.Template.Spec.Bootstrap.DataSecretName).To(Equal("secret-data")) 648 g.Expect(machinePool.Status.BootstrapReady).To(BeFalse()) 649 650 // There is no phase defined for "changing to new bootstrap config", so it should still be `Running` the 651 // old configuration 652 g.Expect(machinePool.Status.GetTypedPhase()).To(Equal(expv1.MachinePoolPhaseRunning)) 653 }) 654 } 655 656 func TestReconcileMachinePoolBootstrap(t *testing.T) { 657 defaultMachinePool := expv1.MachinePool{ 658 ObjectMeta: metav1.ObjectMeta{ 659 Name: "machinepool-test", 660 Namespace: metav1.NamespaceDefault, 661 Labels: map[string]string{ 662 clusterv1.ClusterNameLabel: clusterName, 663 }, 664 }, 665 Spec: expv1.MachinePoolSpec{ 666 Template: clusterv1.MachineTemplateSpec{ 667 Spec: clusterv1.MachineSpec{ 668 Bootstrap: clusterv1.Bootstrap{ 669 ConfigRef: &corev1.ObjectReference{ 670 APIVersion: builder.BootstrapGroupVersion.String(), 671 Kind: builder.TestBootstrapConfigKind, 672 Name: "bootstrap-config1", 673 }, 674 }, 675 }, 676 }, 677 }, 678 } 679 680 defaultCluster := &clusterv1.Cluster{ 681 ObjectMeta: metav1.ObjectMeta{ 682 Name: clusterName, 683 Namespace: metav1.NamespaceDefault, 684 }, 685 } 686 687 testCases := []struct { 688 name string 689 bootstrapConfig map[string]interface{} 690 machinepool *expv1.MachinePool 691 expectError bool 692 expectResult ctrl.Result 693 expected func(g *WithT, m *expv1.MachinePool) 694 }{ 695 { 696 name: "new machinepool, bootstrap config ready with data", 697 bootstrapConfig: map[string]interface{}{ 698 "kind": builder.TestBootstrapConfigKind, 699 "apiVersion": builder.BootstrapGroupVersion.String(), 700 "metadata": map[string]interface{}{ 701 "name": "bootstrap-config1", 702 "namespace": metav1.NamespaceDefault, 703 }, 704 "spec": map[string]interface{}{}, 705 "status": map[string]interface{}{ 706 "ready": true, 707 "dataSecretName": "secret-data", 708 }, 709 }, 710 expectError: false, 711 expected: func(g *WithT, m *expv1.MachinePool) { 712 g.Expect(m.Status.BootstrapReady).To(BeTrue()) 713 g.Expect(m.Spec.Template.Spec.Bootstrap.DataSecretName).ToNot(BeNil()) 714 g.Expect(*m.Spec.Template.Spec.Bootstrap.DataSecretName).To(ContainSubstring("secret-data")) 715 }, 716 }, 717 { 718 name: "new machinepool, bootstrap config ready with no data", 719 bootstrapConfig: map[string]interface{}{ 720 "kind": builder.TestBootstrapConfigKind, 721 "apiVersion": builder.BootstrapGroupVersion.String(), 722 "metadata": map[string]interface{}{ 723 "name": "bootstrap-config1", 724 "namespace": metav1.NamespaceDefault, 725 }, 726 "spec": map[string]interface{}{}, 727 "status": map[string]interface{}{ 728 "ready": true, 729 }, 730 }, 731 expectError: true, 732 expected: func(g *WithT, m *expv1.MachinePool) { 733 g.Expect(m.Status.BootstrapReady).To(BeFalse()) 734 g.Expect(m.Spec.Template.Spec.Bootstrap.DataSecretName).To(BeNil()) 735 }, 736 }, 737 { 738 name: "new machinepool, bootstrap config not ready", 739 bootstrapConfig: map[string]interface{}{ 740 "kind": builder.TestBootstrapConfigKind, 741 "apiVersion": builder.BootstrapGroupVersion.String(), 742 "metadata": map[string]interface{}{ 743 "name": "bootstrap-config1", 744 "namespace": metav1.NamespaceDefault, 745 }, 746 "spec": map[string]interface{}{}, 747 "status": map[string]interface{}{}, 748 }, 749 expectError: false, 750 expectResult: ctrl.Result{}, 751 expected: func(g *WithT, m *expv1.MachinePool) { 752 g.Expect(m.Status.BootstrapReady).To(BeFalse()) 753 }, 754 }, 755 { 756 name: "new machinepool, bootstrap config is not found", 757 bootstrapConfig: map[string]interface{}{ 758 "kind": builder.TestBootstrapConfigKind, 759 "apiVersion": builder.BootstrapGroupVersion.String(), 760 "metadata": map[string]interface{}{ 761 "name": "bootstrap-config1", 762 "namespace": wrongNamespace, 763 }, 764 "spec": map[string]interface{}{}, 765 "status": map[string]interface{}{}, 766 }, 767 expectError: true, 768 expected: func(g *WithT, m *expv1.MachinePool) { 769 g.Expect(m.Status.BootstrapReady).To(BeFalse()) 770 }, 771 }, 772 { 773 name: "new machinepool, no bootstrap config or data", 774 bootstrapConfig: map[string]interface{}{ 775 "kind": builder.TestBootstrapConfigKind, 776 "apiVersion": builder.BootstrapGroupVersion.String(), 777 "metadata": map[string]interface{}{ 778 "name": "bootstrap-config1", 779 "namespace": wrongNamespace, 780 }, 781 "spec": map[string]interface{}{}, 782 "status": map[string]interface{}{}, 783 }, 784 expectError: true, 785 }, 786 { 787 name: "existing machinepool with config ref, update data secret name", 788 bootstrapConfig: map[string]interface{}{ 789 "kind": builder.TestBootstrapConfigKind, 790 "apiVersion": builder.BootstrapGroupVersion.String(), 791 "metadata": map[string]interface{}{ 792 "name": "bootstrap-config1", 793 "namespace": metav1.NamespaceDefault, 794 }, 795 "spec": map[string]interface{}{}, 796 "status": map[string]interface{}{ 797 "ready": true, 798 "dataSecretName": "secret-data", 799 }, 800 }, 801 machinepool: &expv1.MachinePool{ 802 ObjectMeta: metav1.ObjectMeta{ 803 Name: "bootstrap-test-existing", 804 Namespace: metav1.NamespaceDefault, 805 }, 806 Spec: expv1.MachinePoolSpec{ 807 Template: clusterv1.MachineTemplateSpec{ 808 Spec: clusterv1.MachineSpec{ 809 Bootstrap: clusterv1.Bootstrap{ 810 ConfigRef: &corev1.ObjectReference{ 811 APIVersion: builder.BootstrapGroupVersion.String(), 812 Kind: builder.TestBootstrapConfigKind, 813 Name: "bootstrap-config1", 814 }, 815 DataSecretName: ptr.To("data"), 816 }, 817 }, 818 }, 819 }, 820 Status: expv1.MachinePoolStatus{ 821 BootstrapReady: true, 822 }, 823 }, 824 expectError: false, 825 expected: func(g *WithT, m *expv1.MachinePool) { 826 g.Expect(m.Status.BootstrapReady).To(BeTrue()) 827 g.Expect(*m.Spec.Template.Spec.Bootstrap.DataSecretName).To(Equal("secret-data")) 828 }, 829 }, 830 { 831 name: "existing machinepool without config ref, do not update data secret name", 832 bootstrapConfig: map[string]interface{}{ 833 "kind": builder.TestBootstrapConfigKind, 834 "apiVersion": builder.BootstrapGroupVersion.String(), 835 "metadata": map[string]interface{}{ 836 "name": "bootstrap-config1", 837 "namespace": metav1.NamespaceDefault, 838 }, 839 "spec": map[string]interface{}{}, 840 "status": map[string]interface{}{ 841 "ready": true, 842 "dataSecretName": "secret-data", 843 }, 844 }, 845 machinepool: &expv1.MachinePool{ 846 ObjectMeta: metav1.ObjectMeta{ 847 Name: "bootstrap-test-existing", 848 Namespace: metav1.NamespaceDefault, 849 }, 850 Spec: expv1.MachinePoolSpec{ 851 Template: clusterv1.MachineTemplateSpec{ 852 Spec: clusterv1.MachineSpec{ 853 Bootstrap: clusterv1.Bootstrap{ 854 DataSecretName: ptr.To("data"), 855 }, 856 }, 857 }, 858 }, 859 Status: expv1.MachinePoolStatus{ 860 BootstrapReady: true, 861 }, 862 }, 863 expectError: false, 864 expected: func(g *WithT, m *expv1.MachinePool) { 865 g.Expect(m.Status.BootstrapReady).To(BeTrue()) 866 g.Expect(*m.Spec.Template.Spec.Bootstrap.DataSecretName).To(Equal("data")) 867 }, 868 }, 869 { 870 name: "existing machinepool, bootstrap provider is not ready", 871 bootstrapConfig: map[string]interface{}{ 872 "kind": builder.TestBootstrapConfigKind, 873 "apiVersion": builder.BootstrapGroupVersion.String(), 874 "metadata": map[string]interface{}{ 875 "name": "bootstrap-config1", 876 "namespace": metav1.NamespaceDefault, 877 }, 878 "spec": map[string]interface{}{}, 879 "status": map[string]interface{}{ 880 "ready": false, 881 "data": "#!/bin/bash ... data", 882 }, 883 }, 884 machinepool: &expv1.MachinePool{ 885 ObjectMeta: metav1.ObjectMeta{ 886 Name: "bootstrap-test-existing", 887 Namespace: metav1.NamespaceDefault, 888 }, 889 Spec: expv1.MachinePoolSpec{ 890 Template: clusterv1.MachineTemplateSpec{ 891 Spec: clusterv1.MachineSpec{ 892 Bootstrap: clusterv1.Bootstrap{ 893 ConfigRef: &corev1.ObjectReference{ 894 APIVersion: builder.BootstrapGroupVersion.String(), 895 Kind: builder.TestBootstrapConfigKind, 896 Name: "bootstrap-config1", 897 }, 898 DataSecretName: ptr.To("data"), 899 }, 900 }, 901 }, 902 }, 903 Status: expv1.MachinePoolStatus{ 904 BootstrapReady: false, 905 }, 906 }, 907 expectError: false, 908 expectResult: ctrl.Result{}, 909 expected: func(g *WithT, m *expv1.MachinePool) { 910 g.Expect(m.Status.BootstrapReady).To(BeFalse()) 911 }, 912 }, 913 } 914 915 for _, tc := range testCases { 916 t.Run(tc.name, func(t *testing.T) { 917 g := NewWithT(t) 918 if tc.machinepool == nil { 919 tc.machinepool = defaultMachinePool.DeepCopy() 920 } 921 922 bootstrapConfig := &unstructured.Unstructured{Object: tc.bootstrapConfig} 923 r := &MachinePoolReconciler{ 924 Client: fake.NewClientBuilder().WithObjects(tc.machinepool, bootstrapConfig, builder.TestBootstrapConfigCRD, builder.TestInfrastructureMachineTemplateCRD).Build(), 925 } 926 927 res, err := r.reconcileBootstrap(ctx, defaultCluster, tc.machinepool) 928 g.Expect(res).To(BeComparableTo(tc.expectResult)) 929 if tc.expectError { 930 g.Expect(err).To(HaveOccurred()) 931 } else { 932 g.Expect(err).ToNot(HaveOccurred()) 933 } 934 935 if tc.expected != nil { 936 tc.expected(g, tc.machinepool) 937 } 938 }) 939 } 940 } 941 942 func TestReconcileMachinePoolInfrastructure(t *testing.T) { 943 defaultMachinePool := expv1.MachinePool{ 944 ObjectMeta: metav1.ObjectMeta{ 945 Name: "machinepool-test", 946 Namespace: metav1.NamespaceDefault, 947 Labels: map[string]string{ 948 clusterv1.ClusterNameLabel: clusterName, 949 }, 950 }, 951 Spec: expv1.MachinePoolSpec{ 952 Replicas: ptr.To[int32](1), 953 Template: clusterv1.MachineTemplateSpec{ 954 Spec: clusterv1.MachineSpec{ 955 Bootstrap: clusterv1.Bootstrap{ 956 ConfigRef: &corev1.ObjectReference{ 957 APIVersion: builder.BootstrapGroupVersion.String(), 958 Kind: builder.TestBootstrapConfigKind, 959 Name: "bootstrap-config1", 960 }, 961 }, 962 InfrastructureRef: corev1.ObjectReference{ 963 APIVersion: builder.InfrastructureGroupVersion.String(), 964 Kind: builder.TestInfrastructureMachineTemplateKind, 965 Name: "infra-config1", 966 }, 967 }, 968 }, 969 }, 970 } 971 972 defaultCluster := &clusterv1.Cluster{ 973 ObjectMeta: metav1.ObjectMeta{ 974 Name: clusterName, 975 Namespace: metav1.NamespaceDefault, 976 }, 977 } 978 979 testCases := []struct { 980 name string 981 bootstrapConfig map[string]interface{} 982 infraConfig map[string]interface{} 983 machinepool *expv1.MachinePool 984 expectError bool 985 expectChanged bool 986 expectRequeueAfter bool 987 expected func(g *WithT, m *expv1.MachinePool) 988 }{ 989 { 990 name: "new machinepool, infrastructure config ready", 991 infraConfig: map[string]interface{}{ 992 "kind": builder.TestInfrastructureMachineTemplateKind, 993 "apiVersion": builder.InfrastructureGroupVersion.String(), 994 "metadata": map[string]interface{}{ 995 "name": "infra-config1", 996 "namespace": metav1.NamespaceDefault, 997 }, 998 "spec": map[string]interface{}{ 999 "providerIDList": []interface{}{ 1000 "test://id-1", 1001 }, 1002 }, 1003 "status": map[string]interface{}{ 1004 "ready": true, 1005 "addresses": []interface{}{ 1006 map[string]interface{}{ 1007 "type": "InternalIP", 1008 "address": "10.0.0.1", 1009 }, 1010 map[string]interface{}{ 1011 "type": "InternalIP", 1012 "address": "10.0.0.2", 1013 }, 1014 }, 1015 }, 1016 }, 1017 expectError: false, 1018 expectChanged: true, 1019 expected: func(g *WithT, m *expv1.MachinePool) { 1020 g.Expect(m.Status.InfrastructureReady).To(BeTrue()) 1021 }, 1022 }, 1023 { 1024 name: "ready bootstrap, infra, and nodeRef, machinepool is running, infra object is deleted, expect failed", 1025 machinepool: &expv1.MachinePool{ 1026 ObjectMeta: metav1.ObjectMeta{ 1027 Name: "machinepool-test", 1028 Namespace: metav1.NamespaceDefault, 1029 }, 1030 Spec: expv1.MachinePoolSpec{ 1031 Replicas: ptr.To[int32](1), 1032 Template: clusterv1.MachineTemplateSpec{ 1033 Spec: clusterv1.MachineSpec{ 1034 Bootstrap: clusterv1.Bootstrap{ 1035 ConfigRef: &corev1.ObjectReference{ 1036 APIVersion: builder.BootstrapGroupVersion.String(), 1037 Kind: builder.TestBootstrapConfigKind, 1038 Name: "bootstrap-config1", 1039 }, 1040 }, 1041 InfrastructureRef: corev1.ObjectReference{ 1042 APIVersion: builder.InfrastructureGroupVersion.String(), 1043 Kind: builder.TestInfrastructureMachineTemplateKind, 1044 Name: "infra-config1", 1045 }, 1046 }, 1047 }, 1048 }, 1049 Status: expv1.MachinePoolStatus{ 1050 BootstrapReady: true, 1051 InfrastructureReady: true, 1052 NodeRefs: []corev1.ObjectReference{{Kind: "Node", Name: "machinepool-test-node"}}, 1053 }, 1054 }, 1055 bootstrapConfig: map[string]interface{}{ 1056 "kind": builder.TestBootstrapConfigKind, 1057 "apiVersion": builder.BootstrapGroupVersion.String(), 1058 "metadata": map[string]interface{}{ 1059 "name": "bootstrap-config1", 1060 "namespace": metav1.NamespaceDefault, 1061 }, 1062 "spec": map[string]interface{}{}, 1063 "status": map[string]interface{}{ 1064 "ready": true, 1065 "dataSecretName": "secret-data", 1066 }, 1067 }, 1068 infraConfig: map[string]interface{}{ 1069 "kind": builder.TestInfrastructureMachineTemplateKind, 1070 "apiVersion": builder.InfrastructureGroupVersion.String(), 1071 "metadata": map[string]interface{}{}, 1072 }, 1073 expectError: true, 1074 expectRequeueAfter: false, 1075 expected: func(g *WithT, m *expv1.MachinePool) { 1076 g.Expect(m.Status.InfrastructureReady).To(BeTrue()) 1077 g.Expect(m.Status.FailureMessage).ToNot(BeNil()) 1078 g.Expect(m.Status.FailureReason).ToNot(BeNil()) 1079 g.Expect(m.Status.GetTypedPhase()).To(Equal(expv1.MachinePoolPhaseFailed)) 1080 }, 1081 }, 1082 { 1083 name: "infrastructure ref is paused", 1084 infraConfig: map[string]interface{}{ 1085 "kind": builder.TestInfrastructureMachineTemplateKind, 1086 "apiVersion": builder.InfrastructureGroupVersion.String(), 1087 "metadata": map[string]interface{}{ 1088 "name": "infra-config1", 1089 "namespace": metav1.NamespaceDefault, 1090 "annotations": map[string]interface{}{ 1091 "cluster.x-k8s.io/paused": "true", 1092 }, 1093 }, 1094 "spec": map[string]interface{}{ 1095 "providerIDList": []interface{}{ 1096 "test://id-1", 1097 }, 1098 }, 1099 "status": map[string]interface{}{ 1100 "ready": true, 1101 "addresses": []interface{}{ 1102 map[string]interface{}{ 1103 "type": "InternalIP", 1104 "address": "10.0.0.1", 1105 }, 1106 map[string]interface{}{ 1107 "type": "InternalIP", 1108 "address": "10.0.0.2", 1109 }, 1110 }, 1111 }, 1112 }, 1113 expectError: false, 1114 expectChanged: false, 1115 expected: func(g *WithT, m *expv1.MachinePool) { 1116 g.Expect(m.Status.InfrastructureReady).To(BeFalse()) 1117 }, 1118 }, 1119 { 1120 name: "ready bootstrap, infra, and nodeRef, machinepool is running, replicas 0, providerIDList not set", 1121 machinepool: &expv1.MachinePool{ 1122 ObjectMeta: metav1.ObjectMeta{ 1123 Name: "machinepool-test", 1124 Namespace: metav1.NamespaceDefault, 1125 }, 1126 Spec: expv1.MachinePoolSpec{ 1127 Replicas: ptr.To[int32](0), 1128 Template: clusterv1.MachineTemplateSpec{ 1129 Spec: clusterv1.MachineSpec{ 1130 Bootstrap: clusterv1.Bootstrap{ 1131 ConfigRef: &corev1.ObjectReference{ 1132 APIVersion: builder.BootstrapGroupVersion.String(), 1133 Kind: builder.TestBootstrapConfigKind, 1134 Name: "bootstrap-config1", 1135 }, 1136 }, 1137 InfrastructureRef: corev1.ObjectReference{ 1138 APIVersion: builder.InfrastructureGroupVersion.String(), 1139 Kind: builder.TestInfrastructureMachineTemplateKind, 1140 Name: "infra-config1", 1141 }, 1142 }, 1143 }, 1144 }, 1145 Status: expv1.MachinePoolStatus{ 1146 BootstrapReady: true, 1147 InfrastructureReady: true, 1148 NodeRefs: []corev1.ObjectReference{{Kind: "Node", Name: "machinepool-test-node"}}, 1149 }, 1150 }, 1151 bootstrapConfig: map[string]interface{}{ 1152 "kind": builder.TestBootstrapConfigKind, 1153 "apiVersion": builder.BootstrapGroupVersion.String(), 1154 "metadata": map[string]interface{}{ 1155 "name": "bootstrap-config1", 1156 "namespace": metav1.NamespaceDefault, 1157 }, 1158 "spec": map[string]interface{}{}, 1159 "status": map[string]interface{}{ 1160 "ready": true, 1161 "dataSecretName": "secret-data", 1162 }, 1163 }, 1164 infraConfig: map[string]interface{}{ 1165 "kind": builder.TestInfrastructureMachineTemplateKind, 1166 "apiVersion": builder.InfrastructureGroupVersion.String(), 1167 "metadata": map[string]interface{}{ 1168 "name": "infra-config1", 1169 "namespace": metav1.NamespaceDefault, 1170 }, 1171 "spec": map[string]interface{}{ 1172 "providerIDList": []interface{}{}, 1173 }, 1174 "status": map[string]interface{}{ 1175 "ready": true, 1176 "addresses": []interface{}{ 1177 map[string]interface{}{ 1178 "type": "InternalIP", 1179 "address": "10.0.0.1", 1180 }, 1181 map[string]interface{}{ 1182 "type": "InternalIP", 1183 "address": "10.0.0.2", 1184 }, 1185 }, 1186 }, 1187 }, 1188 expectError: false, 1189 expectRequeueAfter: false, 1190 expected: func(g *WithT, m *expv1.MachinePool) { 1191 g.Expect(m.Status.InfrastructureReady).To(BeTrue()) 1192 g.Expect(m.Status.ReadyReplicas).To(Equal(int32(0))) 1193 g.Expect(m.Status.AvailableReplicas).To(Equal(int32(0))) 1194 g.Expect(m.Status.UnavailableReplicas).To(Equal(int32(0))) 1195 g.Expect(m.Status.FailureMessage).To(BeNil()) 1196 g.Expect(m.Status.FailureReason).To(BeNil()) 1197 g.Expect(m.Status.GetTypedPhase()).To(Equal(expv1.MachinePoolPhaseRunning)) 1198 }, 1199 }, 1200 } 1201 1202 for _, tc := range testCases { 1203 t.Run(tc.name, func(t *testing.T) { 1204 g := NewWithT(t) 1205 1206 if tc.machinepool == nil { 1207 tc.machinepool = defaultMachinePool.DeepCopy() 1208 } 1209 1210 infraConfig := &unstructured.Unstructured{Object: tc.infraConfig} 1211 r := &MachinePoolReconciler{ 1212 Client: fake.NewClientBuilder().WithObjects(tc.machinepool, infraConfig, builder.TestBootstrapConfigCRD, builder.TestInfrastructureMachineTemplateCRD).Build(), 1213 } 1214 1215 res, err := r.reconcileInfrastructure(ctx, defaultCluster, tc.machinepool) 1216 if tc.expectRequeueAfter { 1217 g.Expect(res.RequeueAfter).To(BeNumerically(">=", 0)) 1218 } else { 1219 g.Expect(res.RequeueAfter).To(Equal(time.Duration(0))) 1220 } 1221 r.reconcilePhase(tc.machinepool) 1222 if tc.expectError { 1223 g.Expect(err).To(HaveOccurred()) 1224 } else { 1225 g.Expect(err).ToNot(HaveOccurred()) 1226 } 1227 1228 if tc.expected != nil { 1229 tc.expected(g, tc.machinepool) 1230 } 1231 }) 1232 } 1233 } 1234 1235 func TestReconcileMachinePoolMachines(t *testing.T) { 1236 t.Run("Reconcile MachinePool Machines", func(t *testing.T) { 1237 g := NewWithT(t) 1238 1239 ns, err := env.CreateNamespace(ctx, "test-machinepool-machines") 1240 g.Expect(err).ToNot(HaveOccurred()) 1241 1242 cluster := builder.Cluster(ns.Name, clusterName).Build() 1243 g.Expect(env.Create(ctx, cluster)).To(Succeed()) 1244 1245 t.Run("Should do nothing if machines already exist", func(*testing.T) { 1246 machinePool := getMachinePool(2, "machinepool-test-1", clusterName, ns.Name) 1247 g.Expect(env.Create(ctx, &machinePool)).To(Succeed()) 1248 1249 infraMachines := getInfraMachines(2, machinePool.Name, clusterName, ns.Name) 1250 for i := range infraMachines { 1251 g.Expect(env.Create(ctx, &infraMachines[i])).To(Succeed()) 1252 } 1253 1254 machines := getMachines(2, machinePool.Name, clusterName, ns.Name) 1255 for i := range machines { 1256 g.Expect(env.Create(ctx, &machines[i])).To(Succeed()) 1257 } 1258 1259 infraConfig := map[string]interface{}{ 1260 "kind": builder.GenericInfrastructureMachinePoolKind, 1261 "apiVersion": "infrastructure.cluster.x-k8s.io/v1beta1", 1262 "metadata": map[string]interface{}{ 1263 "name": "infra-config1", 1264 "namespace": ns.Name, 1265 }, 1266 "spec": map[string]interface{}{ 1267 "providerIDList": []interface{}{ 1268 "test://id-1", 1269 }, 1270 }, 1271 "status": map[string]interface{}{ 1272 "ready": true, 1273 "addresses": []interface{}{ 1274 map[string]interface{}{ 1275 "type": "InternalIP", 1276 "address": "10.0.0.1", 1277 }, 1278 map[string]interface{}{ 1279 "type": "InternalIP", 1280 "address": "10.0.0.2", 1281 }, 1282 }, 1283 "infrastructureMachineKind": builder.GenericInfrastructureMachineKind, 1284 }, 1285 } 1286 g.Expect(env.Create(ctx, &unstructured.Unstructured{Object: infraConfig})).To(Succeed()) 1287 1288 r := &MachinePoolReconciler{ 1289 Client: env, 1290 ssaCache: ssa.NewCache(), 1291 } 1292 1293 err = r.reconcileMachines(ctx, &machinePool, &unstructured.Unstructured{Object: infraConfig}) 1294 r.reconcilePhase(&machinePool) 1295 g.Expect(err).ToNot(HaveOccurred()) 1296 1297 machineList := &clusterv1.MachineList{} 1298 labels := map[string]string{ 1299 clusterv1.ClusterNameLabel: clusterName, 1300 clusterv1.MachinePoolNameLabel: machinePool.Name, 1301 } 1302 g.Expect(env.GetAPIReader().List(ctx, machineList, client.InNamespace(cluster.Namespace), client.MatchingLabels(labels))).To(Succeed()) 1303 g.Expect(machineList.Items).To(HaveLen(2)) 1304 for i := range machineList.Items { 1305 machine := &machineList.Items[i] 1306 _, err := external.Get(ctx, r.Client, &machine.Spec.InfrastructureRef, machine.Namespace) 1307 g.Expect(err).ToNot(HaveOccurred()) 1308 } 1309 }) 1310 1311 t.Run("Should create two machines if two infra machines exist", func(*testing.T) { 1312 machinePool := getMachinePool(2, "machinepool-test-2", clusterName, ns.Name) 1313 g.Expect(env.Create(ctx, &machinePool)).To(Succeed()) 1314 1315 infraMachines := getInfraMachines(2, machinePool.Name, clusterName, ns.Name) 1316 for i := range infraMachines { 1317 g.Expect(env.Create(ctx, &infraMachines[i])).To(Succeed()) 1318 } 1319 1320 infraConfig := map[string]interface{}{ 1321 "kind": builder.GenericInfrastructureMachinePoolKind, 1322 "apiVersion": "infrastructure.cluster.x-k8s.io/v1beta1", 1323 "metadata": map[string]interface{}{ 1324 "name": "infra-config2", 1325 "namespace": ns.Name, 1326 }, 1327 "spec": map[string]interface{}{ 1328 "providerIDList": []interface{}{ 1329 "test://id-1", 1330 }, 1331 }, 1332 "status": map[string]interface{}{ 1333 "ready": true, 1334 "addresses": []interface{}{ 1335 map[string]interface{}{ 1336 "type": "InternalIP", 1337 "address": "10.0.0.1", 1338 }, 1339 map[string]interface{}{ 1340 "type": "InternalIP", 1341 "address": "10.0.0.2", 1342 }, 1343 }, 1344 "infrastructureMachineKind": builder.GenericInfrastructureMachineKind, 1345 }, 1346 } 1347 g.Expect(env.Create(ctx, &unstructured.Unstructured{Object: infraConfig})).To(Succeed()) 1348 1349 r := &MachinePoolReconciler{ 1350 Client: env, 1351 ssaCache: ssa.NewCache(), 1352 } 1353 1354 err = r.reconcileMachines(ctx, &machinePool, &unstructured.Unstructured{Object: infraConfig}) 1355 r.reconcilePhase(&machinePool) 1356 g.Expect(err).ToNot(HaveOccurred()) 1357 1358 machineList := &clusterv1.MachineList{} 1359 labels := map[string]string{ 1360 clusterv1.ClusterNameLabel: clusterName, 1361 clusterv1.MachinePoolNameLabel: machinePool.Name, 1362 } 1363 g.Expect(env.GetAPIReader().List(ctx, machineList, client.InNamespace(cluster.Namespace), client.MatchingLabels(labels))).To(Succeed()) 1364 g.Expect(machineList.Items).To(HaveLen(2)) 1365 for i := range machineList.Items { 1366 machine := &machineList.Items[i] 1367 _, err := external.Get(ctx, r.Client, &machine.Spec.InfrastructureRef, machine.Namespace) 1368 g.Expect(err).ToNot(HaveOccurred()) 1369 } 1370 }) 1371 1372 t.Run("Should do nothing if machinepool does not support machinepool machines", func(*testing.T) { 1373 machinePool := getMachinePool(2, "machinepool-test-3", clusterName, ns.Name) 1374 g.Expect(env.Create(ctx, &machinePool)).To(Succeed()) 1375 1376 infraConfig := map[string]interface{}{ 1377 "kind": builder.GenericInfrastructureMachinePoolKind, 1378 "apiVersion": "infrastructure.cluster.x-k8s.io/v1beta1", 1379 "metadata": map[string]interface{}{ 1380 "name": "infra-config3", 1381 "namespace": ns.Name, 1382 }, 1383 "spec": map[string]interface{}{ 1384 "providerIDList": []interface{}{ 1385 "test://id-1", 1386 }, 1387 }, 1388 "status": map[string]interface{}{ 1389 "ready": true, 1390 "addresses": []interface{}{ 1391 map[string]interface{}{ 1392 "type": "InternalIP", 1393 "address": "10.0.0.1", 1394 }, 1395 map[string]interface{}{ 1396 "type": "InternalIP", 1397 "address": "10.0.0.2", 1398 }, 1399 }, 1400 }, 1401 } 1402 g.Expect(env.Create(ctx, &unstructured.Unstructured{Object: infraConfig})).To(Succeed()) 1403 1404 r := &MachinePoolReconciler{ 1405 Client: env, 1406 ssaCache: ssa.NewCache(), 1407 } 1408 1409 err = r.reconcileMachines(ctx, &machinePool, &unstructured.Unstructured{Object: infraConfig}) 1410 r.reconcilePhase(&machinePool) 1411 g.Expect(err).ToNot(HaveOccurred()) 1412 1413 machineList := &clusterv1.MachineList{} 1414 labels := map[string]string{ 1415 clusterv1.ClusterNameLabel: clusterName, 1416 clusterv1.MachinePoolNameLabel: machinePool.Name, 1417 } 1418 g.Expect(env.GetAPIReader().List(ctx, machineList, client.InNamespace(cluster.Namespace), client.MatchingLabels(labels))).To(Succeed()) 1419 g.Expect(machineList.Items).To(BeEmpty()) 1420 }) 1421 }) 1422 } 1423 1424 func TestInfraMachineToMachinePoolMapper(t *testing.T) { 1425 machinePool1 := expv1.MachinePool{ 1426 ObjectMeta: metav1.ObjectMeta{ 1427 Name: "machinepool-1", 1428 Namespace: metav1.NamespaceDefault, 1429 Labels: map[string]string{ 1430 clusterv1.ClusterNameLabel: clusterName, 1431 }, 1432 }, 1433 } 1434 1435 machinePool2 := expv1.MachinePool{ 1436 ObjectMeta: metav1.ObjectMeta{ 1437 Name: "machinepool-2", 1438 Namespace: "other-namespace", 1439 Labels: map[string]string{ 1440 clusterv1.ClusterNameLabel: clusterName, 1441 }, 1442 }, 1443 } 1444 1445 machinePool3 := expv1.MachinePool{ 1446 ObjectMeta: metav1.ObjectMeta{ 1447 Name: "machinepool-3", 1448 Namespace: metav1.NamespaceDefault, 1449 Labels: map[string]string{ 1450 clusterv1.ClusterNameLabel: "other-cluster", 1451 }, 1452 }, 1453 } 1454 1455 machinePoolLongName := expv1.MachinePool{ 1456 ObjectMeta: metav1.ObjectMeta{ 1457 Name: "machinepool-very-very-very-very-very-very-very-very-very-very-very-very-very-very-very-very-very-very-very-very-long", // Use a name longer than 64 characters to trigger a hash 1458 Namespace: metav1.NamespaceDefault, 1459 Labels: map[string]string{ 1460 clusterv1.ClusterNameLabel: "other-cluster", 1461 }, 1462 }, 1463 } 1464 1465 infraMachine1 := unstructured.Unstructured{ 1466 Object: map[string]interface{}{ 1467 "kind": "InfrastructureMachine", 1468 "apiVersion": "infrastructure.cluster.x-k8s.io/v1beta1", 1469 "metadata": map[string]interface{}{ 1470 "name": "infra-machine1", 1471 "namespace": metav1.NamespaceDefault, 1472 "labels": map[string]interface{}{ 1473 clusterv1.ClusterNameLabel: clusterName, 1474 clusterv1.MachinePoolNameLabel: format.MustFormatValue(machinePool1.Name), 1475 }, 1476 }, 1477 }, 1478 } 1479 1480 infraMachine2 := unstructured.Unstructured{ 1481 Object: map[string]interface{}{ 1482 "kind": "InfrastructureMachine", 1483 "apiVersion": "infrastructure.cluster.x-k8s.io/v1beta1", 1484 "metadata": map[string]interface{}{ 1485 "name": "infra-machine2", 1486 "namespace": metav1.NamespaceDefault, 1487 "labels": map[string]interface{}{ 1488 clusterv1.ClusterNameLabel: "other-cluster", 1489 clusterv1.MachinePoolNameLabel: format.MustFormatValue(machinePoolLongName.Name), 1490 }, 1491 }, 1492 }, 1493 } 1494 1495 infraMachine3 := unstructured.Unstructured{ 1496 Object: map[string]interface{}{ 1497 "kind": "InfrastructureMachine", 1498 "apiVersion": "infrastructure.cluster.x-k8s.io/v1beta1", 1499 "metadata": map[string]interface{}{ 1500 "name": "infra-machine3", 1501 "namespace": metav1.NamespaceDefault, 1502 "labels": map[string]interface{}{ 1503 clusterv1.ClusterNameLabel: "other-cluster", 1504 clusterv1.MachinePoolNameLabel: format.MustFormatValue("missing-machinepool"), 1505 }, 1506 }, 1507 }, 1508 } 1509 1510 testCases := []struct { 1511 name string 1512 infraMachine *unstructured.Unstructured 1513 machinepools []expv1.MachinePool 1514 expectedMachinePool *expv1.MachinePool 1515 }{ 1516 { 1517 name: "match machinePool name with label value", 1518 infraMachine: &infraMachine1, 1519 machinepools: []expv1.MachinePool{ 1520 machinePool1, 1521 machinePool2, 1522 machinePool3, 1523 machinePoolLongName, 1524 }, 1525 expectedMachinePool: &machinePool1, 1526 }, 1527 { 1528 name: "match hash of machinePool name with label hash", 1529 infraMachine: &infraMachine2, 1530 machinepools: []expv1.MachinePool{ 1531 machinePool1, 1532 machinePool2, 1533 machinePool3, 1534 machinePoolLongName, 1535 }, 1536 expectedMachinePool: &machinePoolLongName, 1537 }, 1538 { 1539 name: "return nil if no machinePool matches", 1540 infraMachine: &infraMachine3, 1541 machinepools: []expv1.MachinePool{ 1542 machinePool1, 1543 machinePool2, 1544 machinePool3, 1545 machinePoolLongName, 1546 }, 1547 expectedMachinePool: nil, 1548 }, 1549 } 1550 1551 for _, tc := range testCases { 1552 t.Run(tc.name, func(t *testing.T) { 1553 g := NewWithT(t) 1554 1555 objs := []client.Object{tc.infraMachine.DeepCopy()} 1556 1557 for _, mp := range tc.machinepools { 1558 objs = append(objs, mp.DeepCopy()) 1559 } 1560 1561 r := &MachinePoolReconciler{ 1562 Client: fake.NewClientBuilder().WithObjects(objs...).Build(), 1563 } 1564 1565 result := r.infraMachineToMachinePoolMapper(ctx, tc.infraMachine) 1566 if tc.expectedMachinePool == nil { 1567 g.Expect(result).To(BeNil()) 1568 } else { 1569 g.Expect(result).To(HaveLen(1)) 1570 g.Expect(result[0].Name).To(Equal(tc.expectedMachinePool.Name)) 1571 g.Expect(result[0].Namespace).To(Equal(tc.expectedMachinePool.Namespace)) 1572 } 1573 }) 1574 } 1575 } 1576 1577 func TestReconcileMachinePoolScaleToFromZero(t *testing.T) { 1578 g := NewWithT(t) 1579 1580 ns, err := env.CreateNamespace(ctx, "machinepool-scale-zero") 1581 g.Expect(err).ToNot(HaveOccurred()) 1582 1583 // Set up cluster to test against. 1584 testCluster := &clusterv1.Cluster{ 1585 ObjectMeta: metav1.ObjectMeta{ 1586 GenerateName: "machinepool-scale-zero-", 1587 Namespace: ns.Name, 1588 }, 1589 } 1590 g.Expect(env.CreateAndWait(ctx, testCluster)).To(Succeed()) 1591 g.Expect(env.CreateKubeconfigSecret(ctx, testCluster)).To(Succeed()) 1592 defer func(do ...client.Object) { 1593 g.Expect(env.CleanupAndWait(ctx, do...)).To(Succeed()) 1594 }(testCluster) 1595 1596 defaultMachinePool := expv1.MachinePool{ 1597 ObjectMeta: metav1.ObjectMeta{ 1598 Name: "machinepool-test", 1599 Namespace: ns.Name, 1600 }, 1601 Spec: expv1.MachinePoolSpec{ 1602 ClusterName: testCluster.Name, 1603 Template: clusterv1.MachineTemplateSpec{ 1604 Spec: clusterv1.MachineSpec{ 1605 Bootstrap: clusterv1.Bootstrap{ 1606 ConfigRef: &corev1.ObjectReference{ 1607 APIVersion: builder.BootstrapGroupVersion.String(), 1608 Kind: builder.TestBootstrapConfigKind, 1609 Name: "bootstrap-config1", 1610 }, 1611 }, 1612 InfrastructureRef: corev1.ObjectReference{ 1613 APIVersion: builder.InfrastructureGroupVersion.String(), 1614 Kind: builder.TestInfrastructureMachineTemplateKind, 1615 Name: "infra-config1", 1616 }, 1617 }, 1618 }, 1619 }, 1620 Status: expv1.MachinePoolStatus{}, 1621 } 1622 1623 defaultBootstrap := &unstructured.Unstructured{ 1624 Object: map[string]interface{}{ 1625 "kind": builder.TestBootstrapConfigKind, 1626 "apiVersion": builder.BootstrapGroupVersion.String(), 1627 "metadata": map[string]interface{}{ 1628 "name": "bootstrap-config1", 1629 "namespace": ns.Name, 1630 }, 1631 "spec": map[string]interface{}{}, 1632 "status": map[string]interface{}{ 1633 "ready": true, 1634 "dataSecretName": "secret-data", 1635 }, 1636 }, 1637 } 1638 1639 defaultInfra := &unstructured.Unstructured{ 1640 Object: map[string]interface{}{ 1641 "kind": builder.TestInfrastructureMachineTemplateKind, 1642 "apiVersion": builder.InfrastructureGroupVersion.String(), 1643 "metadata": map[string]interface{}{ 1644 "name": "infra-config1", 1645 "namespace": ns.Name, 1646 }, 1647 "spec": map[string]interface{}{}, 1648 "status": map[string]interface{}{ 1649 "ready": true, 1650 }, 1651 }, 1652 } 1653 1654 t.Run("Should set `ScalingDown` when scaling to zero", func(t *testing.T) { 1655 g := NewWithT(t) 1656 1657 node := &corev1.Node{ 1658 ObjectMeta: metav1.ObjectMeta{ 1659 Name: "machinepool-test-node", 1660 }, 1661 Spec: corev1.NodeSpec{ 1662 ProviderID: "test://machinepool-test-node", 1663 }, 1664 Status: corev1.NodeStatus{ 1665 Conditions: []corev1.NodeCondition{ 1666 {Type: corev1.NodeReady, Status: corev1.ConditionTrue}, 1667 }, 1668 }, 1669 } 1670 g.Expect(env.CreateAndWait(ctx, node)).To(Succeed()) 1671 defer func(do ...client.Object) { 1672 g.Expect(env.CleanupAndWait(ctx, do...)).To(Succeed()) 1673 }(node) 1674 1675 kubeconfigSecret := kubeconfig.GenerateSecret(testCluster, kubeconfig.FromEnvTestConfig(env.Config, testCluster)) 1676 machinepool := defaultMachinePool.DeepCopy() 1677 bootstrapConfig := defaultBootstrap.DeepCopy() 1678 infraConfig := defaultInfra.DeepCopy() 1679 1680 // Setup prerequisites - a running MachinePool with one instance and user sets Replicas to 0 1681 1682 // set replicas to 0 1683 machinepool.Spec.Replicas = ptr.To[int32](0) 1684 1685 // set nodeRefs to one instance 1686 machinepool.Status.NodeRefs = []corev1.ObjectReference{{Kind: "Node", Name: "machinepool-test-node"}} 1687 1688 // set infra providerIDList 1689 err = unstructured.SetNestedStringSlice(infraConfig.Object, []string{"test://machinepool-test-node"}, "spec", "providerIDList") 1690 g.Expect(err).ToNot(HaveOccurred()) 1691 1692 // set infra replicas 1693 err = unstructured.SetNestedField(infraConfig.Object, int64(1), "status", "replicas") 1694 g.Expect(err).ToNot(HaveOccurred()) 1695 1696 fakeClient := fake.NewClientBuilder().WithObjects(testCluster, kubeconfigSecret, machinepool, bootstrapConfig, infraConfig, builder.TestBootstrapConfigCRD, builder.TestInfrastructureMachineTemplateCRD).Build() 1697 r := &MachinePoolReconciler{ 1698 Client: fakeClient, 1699 Tracker: remote.NewTestClusterCacheTracker(logr.New(log.NullLogSink{}), env.GetClient(), env.GetClient(), env.GetClient().Scheme(), client.ObjectKey{Name: testCluster.Name, Namespace: testCluster.Namespace}), 1700 recorder: record.NewFakeRecorder(32), 1701 } 1702 1703 res, err := r.reconcile(ctx, testCluster, machinepool) 1704 g.Expect(err).ToNot(HaveOccurred()) 1705 g.Expect(res.Requeue).To(BeFalse()) 1706 1707 r.reconcilePhase(machinepool) 1708 1709 g.Expect(machinepool.Status.GetTypedPhase()).To(Equal(expv1.MachinePoolPhaseScalingDown)) 1710 1711 delNode := &corev1.Node{} 1712 g.Expect(env.Get(ctx, client.ObjectKeyFromObject(node), delNode)).To(Succeed()) 1713 }) 1714 1715 t.Run("Should delete retired nodes when scaled to zero", func(t *testing.T) { 1716 g := NewWithT(t) 1717 1718 node := &corev1.Node{ 1719 ObjectMeta: metav1.ObjectMeta{ 1720 Name: "machinepool-test-node", 1721 }, 1722 Spec: corev1.NodeSpec{ 1723 ProviderID: "test://machinepool-test-node", 1724 }, 1725 Status: corev1.NodeStatus{ 1726 Conditions: []corev1.NodeCondition{ 1727 {Type: corev1.NodeReady, Status: corev1.ConditionTrue}, 1728 }, 1729 }, 1730 } 1731 g.Expect(env.CreateAndWait(ctx, node)).To(Succeed()) 1732 defer func(do ...client.Object) { 1733 g.Expect(env.CleanupAndWait(ctx, do...)).To(Succeed()) 1734 }(node) 1735 1736 kubeconfigSecret := kubeconfig.GenerateSecret(testCluster, kubeconfig.FromEnvTestConfig(env.Config, testCluster)) 1737 machinepool := defaultMachinePool.DeepCopy() 1738 bootstrapConfig := defaultBootstrap.DeepCopy() 1739 infraConfig := defaultInfra.DeepCopy() 1740 1741 // Setup prerequisites - a running MachinePool with one instance and user sets Replicas to 0 1742 1743 // set replicas to 0 1744 machinepool.Spec.Replicas = ptr.To[int32](0) 1745 1746 // set nodeRefs to one instance 1747 machinepool.Status.NodeRefs = []corev1.ObjectReference{{Kind: "Node", Name: "machinepool-test-node"}} 1748 1749 // set infra replicas 1750 err = unstructured.SetNestedField(infraConfig.Object, int64(0), "status", "replicas") 1751 g.Expect(err).ToNot(HaveOccurred()) 1752 1753 fakeClient := fake.NewClientBuilder().WithObjects(testCluster, kubeconfigSecret, machinepool, bootstrapConfig, infraConfig, builder.TestBootstrapConfigCRD, builder.TestInfrastructureMachineTemplateCRD).Build() 1754 r := &MachinePoolReconciler{ 1755 Client: fakeClient, 1756 Tracker: remote.NewTestClusterCacheTracker(logr.New(log.NullLogSink{}), env.GetClient(), env.GetClient(), env.GetClient().Scheme(), client.ObjectKey{Name: testCluster.Name, Namespace: testCluster.Namespace}), 1757 recorder: record.NewFakeRecorder(32), 1758 } 1759 1760 res, err := r.reconcile(ctx, testCluster, machinepool) 1761 g.Expect(err).ToNot(HaveOccurred()) 1762 g.Expect(res.Requeue).To(BeFalse()) 1763 1764 r.reconcilePhase(machinepool) 1765 g.Expect(machinepool.Status.GetTypedPhase()).To(Equal(expv1.MachinePoolPhaseRunning)) 1766 1767 delNode := &corev1.Node{} 1768 err = env.GetAPIReader().Get(ctx, client.ObjectKeyFromObject(node), delNode) 1769 g.Expect(err).To(HaveOccurred()) 1770 g.Expect(apierrors.IsNotFound(err)).To(BeTrue()) 1771 }) 1772 1773 t.Run("Should set `Running` when scaled to zero", func(t *testing.T) { 1774 g := NewWithT(t) 1775 1776 kubeconfigSecret := kubeconfig.GenerateSecret(testCluster, kubeconfig.FromEnvTestConfig(env.Config, testCluster)) 1777 machinepool := defaultMachinePool.DeepCopy() 1778 bootstrapConfig := defaultBootstrap.DeepCopy() 1779 infraConfig := defaultInfra.DeepCopy() 1780 1781 // Setup prerequisites - a running MachinePool with no instances and replicas set to 0 1782 1783 // set replicas to 0 1784 machinepool.Spec.Replicas = ptr.To[int32](0) 1785 1786 // set nodeRefs to no instance 1787 machinepool.Status.NodeRefs = []corev1.ObjectReference{} 1788 1789 // set infra replicas 1790 err := unstructured.SetNestedField(infraConfig.Object, int64(0), "status", "replicas") 1791 g.Expect(err).ToNot(HaveOccurred()) 1792 1793 r := &MachinePoolReconciler{ 1794 Client: fake.NewClientBuilder().WithObjects(testCluster, kubeconfigSecret, machinepool, bootstrapConfig, infraConfig, builder.TestBootstrapConfigCRD, builder.TestInfrastructureMachineTemplateCRD).Build(), 1795 recorder: record.NewFakeRecorder(32), 1796 } 1797 1798 res, err := r.reconcile(ctx, testCluster, machinepool) 1799 g.Expect(err).ToNot(HaveOccurred()) 1800 g.Expect(res.Requeue).To(BeFalse()) 1801 1802 r.reconcilePhase(machinepool) 1803 1804 g.Expect(machinepool.Status.GetTypedPhase()).To(Equal(expv1.MachinePoolPhaseRunning)) 1805 }) 1806 1807 t.Run("Should set `ScalingUp` when scaling from zero to one", func(t *testing.T) { 1808 g := NewWithT(t) 1809 1810 kubeconfigSecret := kubeconfig.GenerateSecret(testCluster, kubeconfig.FromEnvTestConfig(env.Config, testCluster)) 1811 machinepool := defaultMachinePool.DeepCopy() 1812 bootstrapConfig := defaultBootstrap.DeepCopy() 1813 infraConfig := defaultInfra.DeepCopy() 1814 1815 // Setup prerequisites - a running MachinePool with no instances and replicas set to 1 1816 1817 // set replicas to 1 1818 machinepool.Spec.Replicas = ptr.To[int32](1) 1819 1820 // set nodeRefs to no instance 1821 machinepool.Status.NodeRefs = []corev1.ObjectReference{} 1822 1823 // set infra replicas 1824 err := unstructured.SetNestedField(infraConfig.Object, int64(0), "status", "replicas") 1825 g.Expect(err).ToNot(HaveOccurred()) 1826 1827 r := &MachinePoolReconciler{ 1828 Client: fake.NewClientBuilder().WithObjects(testCluster, kubeconfigSecret, machinepool, bootstrapConfig, infraConfig, builder.TestBootstrapConfigCRD, builder.TestInfrastructureMachineTemplateCRD).Build(), 1829 recorder: record.NewFakeRecorder(32), 1830 } 1831 1832 res, err := r.reconcile(ctx, testCluster, machinepool) 1833 g.Expect(err).ToNot(HaveOccurred()) 1834 g.Expect(res.Requeue).To(BeFalse()) 1835 1836 r.reconcilePhase(machinepool) 1837 1838 g.Expect(machinepool.Status.GetTypedPhase()).To(Equal(expv1.MachinePoolPhaseScalingUp)) 1839 }) 1840 1841 t.Run("Should set `Running` when scaled from zero to one", func(t *testing.T) { 1842 g := NewWithT(t) 1843 1844 node := &corev1.Node{ 1845 ObjectMeta: metav1.ObjectMeta{ 1846 Name: "machinepool-test-node", 1847 }, 1848 Spec: corev1.NodeSpec{ 1849 ProviderID: "test://machinepool-test-node", 1850 }, 1851 Status: corev1.NodeStatus{ 1852 Conditions: []corev1.NodeCondition{ 1853 {Type: corev1.NodeReady, Status: corev1.ConditionTrue}, 1854 }, 1855 }, 1856 } 1857 g.Expect(env.CreateAndWait(ctx, node)).To(Succeed()) 1858 defer func(do ...client.Object) { 1859 g.Expect(env.CleanupAndWait(ctx, do...)).To(Succeed()) 1860 }(node) 1861 1862 kubeconfigSecret := kubeconfig.GenerateSecret(testCluster, kubeconfig.FromEnvTestConfig(env.Config, testCluster)) 1863 machinepool := defaultMachinePool.DeepCopy() 1864 bootstrapConfig := defaultBootstrap.DeepCopy() 1865 infraConfig := defaultInfra.DeepCopy() 1866 1867 // Setup prerequisites - a running MachinePool with no refs but providerIDList and replicas set to 1 1868 1869 // set replicas to 1 1870 machinepool.Spec.Replicas = ptr.To[int32](1) 1871 1872 // set nodeRefs to no instance 1873 machinepool.Status.NodeRefs = []corev1.ObjectReference{} 1874 1875 // set infra providerIDList 1876 err = unstructured.SetNestedStringSlice(infraConfig.Object, []string{"test://machinepool-test-node"}, "spec", "providerIDList") 1877 g.Expect(err).ToNot(HaveOccurred()) 1878 1879 // set infra replicas 1880 err = unstructured.SetNestedField(infraConfig.Object, int64(1), "status", "replicas") 1881 g.Expect(err).ToNot(HaveOccurred()) 1882 1883 fakeClient := fake.NewClientBuilder().WithObjects(testCluster, kubeconfigSecret, machinepool, bootstrapConfig, infraConfig, builder.TestBootstrapConfigCRD, builder.TestInfrastructureMachineTemplateCRD).Build() 1884 r := &MachinePoolReconciler{ 1885 Client: fakeClient, 1886 Tracker: remote.NewTestClusterCacheTracker(logr.New(log.NullLogSink{}), env.GetClient(), env.GetClient(), env.GetClient().Scheme(), client.ObjectKey{Name: testCluster.Name, Namespace: testCluster.Namespace}), 1887 recorder: record.NewFakeRecorder(32), 1888 } 1889 1890 res, err := r.reconcile(ctx, testCluster, machinepool) 1891 g.Expect(err).ToNot(HaveOccurred()) 1892 g.Expect(res.Requeue).To(BeFalse()) 1893 1894 r.reconcilePhase(machinepool) 1895 1896 g.Expect(machinepool.Status.GetTypedPhase()).To(Equal(expv1.MachinePoolPhaseRunning)) 1897 1898 delNode := &corev1.Node{} 1899 g.Expect(env.Get(ctx, client.ObjectKeyFromObject(node), delNode)).To(Succeed()) 1900 }) 1901 } 1902 1903 func getInfraMachines(replicas int, mpName, clusterName, nsName string) []unstructured.Unstructured { 1904 infraMachines := make([]unstructured.Unstructured, replicas) 1905 for i := 0; i < replicas; i++ { 1906 infraMachines[i] = unstructured.Unstructured{ 1907 Object: map[string]interface{}{ 1908 "kind": builder.GenericInfrastructureMachineKind, 1909 "apiVersion": "infrastructure.cluster.x-k8s.io/v1beta1", 1910 "metadata": map[string]interface{}{ 1911 "name": fmt.Sprintf("%s-infra-%d", mpName, i), 1912 "namespace": nsName, 1913 "labels": map[string]interface{}{ 1914 clusterv1.ClusterNameLabel: clusterName, 1915 clusterv1.MachinePoolNameLabel: mpName, 1916 }, 1917 }, 1918 }, 1919 } 1920 } 1921 return infraMachines 1922 } 1923 1924 func getMachines(replicas int, mpName, clusterName, nsName string) []clusterv1.Machine { 1925 machines := make([]clusterv1.Machine, replicas) 1926 for i := 0; i < replicas; i++ { 1927 machines[i] = clusterv1.Machine{ 1928 ObjectMeta: metav1.ObjectMeta{ 1929 Name: fmt.Sprintf("%s-machine-%d", mpName, i), 1930 Namespace: nsName, 1931 Labels: map[string]string{ 1932 clusterv1.ClusterNameLabel: clusterName, 1933 clusterv1.MachinePoolNameLabel: mpName, 1934 }, 1935 }, 1936 Spec: clusterv1.MachineSpec{ 1937 ClusterName: clusterName, 1938 Bootstrap: clusterv1.Bootstrap{ 1939 ConfigRef: &corev1.ObjectReference{ 1940 APIVersion: builder.BootstrapGroupVersion.String(), 1941 Kind: builder.GenericBootstrapConfigKind, 1942 Name: fmt.Sprintf("bootstrap-config-%d", i), 1943 }, 1944 }, 1945 InfrastructureRef: corev1.ObjectReference{ 1946 APIVersion: builder.InfrastructureGroupVersion.String(), 1947 Kind: builder.GenericInfrastructureMachineKind, 1948 Name: fmt.Sprintf("%s-infra-%d", mpName, i), 1949 }, 1950 }, 1951 } 1952 } 1953 return machines 1954 } 1955 1956 func getMachinePool(replicas int, mpName, clusterName, nsName string) expv1.MachinePool { 1957 return expv1.MachinePool{ 1958 ObjectMeta: metav1.ObjectMeta{ 1959 Name: mpName, 1960 Namespace: nsName, 1961 Labels: map[string]string{ 1962 clusterv1.ClusterNameLabel: clusterName, 1963 }, 1964 }, 1965 Spec: expv1.MachinePoolSpec{ 1966 ClusterName: clusterName, 1967 Replicas: ptr.To[int32](int32(replicas)), 1968 Template: clusterv1.MachineTemplateSpec{ 1969 Spec: clusterv1.MachineSpec{ 1970 ClusterName: clusterName, 1971 Bootstrap: clusterv1.Bootstrap{ 1972 ConfigRef: &corev1.ObjectReference{ 1973 APIVersion: builder.BootstrapGroupVersion.String(), 1974 Kind: builder.GenericBootstrapConfigKind, 1975 Name: "bootstrap-config1", 1976 }, 1977 }, 1978 InfrastructureRef: corev1.ObjectReference{ 1979 APIVersion: builder.InfrastructureGroupVersion.String(), 1980 Kind: builder.GenericInfrastructureMachineKind, 1981 Name: "infra-config1", 1982 }, 1983 }, 1984 }, 1985 }, 1986 } 1987 }