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