sigs.k8s.io/cluster-api@v1.7.1/exp/internal/controllers/machinepool_controller_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 "testing" 21 22 . "github.com/onsi/gomega" 23 corev1 "k8s.io/api/core/v1" 24 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 25 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 26 "k8s.io/client-go/kubernetes/scheme" 27 "k8s.io/utils/ptr" 28 ctrl "sigs.k8s.io/controller-runtime" 29 "sigs.k8s.io/controller-runtime/pkg/client" 30 "sigs.k8s.io/controller-runtime/pkg/client/fake" 31 "sigs.k8s.io/controller-runtime/pkg/reconcile" 32 33 clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" 34 expv1 "sigs.k8s.io/cluster-api/exp/api/v1beta1" 35 "sigs.k8s.io/cluster-api/internal/test/builder" 36 "sigs.k8s.io/cluster-api/util" 37 "sigs.k8s.io/cluster-api/util/conditions" 38 ) 39 40 func TestMachinePoolFinalizer(t *testing.T) { 41 bootstrapData := "some valid machinepool bootstrap data" 42 clusterCorrectMeta := &clusterv1.Cluster{ 43 ObjectMeta: metav1.ObjectMeta{ 44 Namespace: metav1.NamespaceDefault, 45 Name: "valid-cluster", 46 }, 47 } 48 49 machinePoolValidCluster := &expv1.MachinePool{ 50 ObjectMeta: metav1.ObjectMeta{ 51 Name: "machinePool1", 52 Namespace: metav1.NamespaceDefault, 53 }, 54 Spec: expv1.MachinePoolSpec{ 55 Replicas: ptr.To[int32](1), 56 Template: clusterv1.MachineTemplateSpec{ 57 Spec: clusterv1.MachineSpec{ 58 Bootstrap: clusterv1.Bootstrap{ 59 DataSecretName: &bootstrapData, 60 }, 61 }, 62 }, 63 ClusterName: "valid-cluster", 64 }, 65 } 66 67 machinePoolWithFinalizer := &expv1.MachinePool{ 68 ObjectMeta: metav1.ObjectMeta{ 69 Name: "machinePool2", 70 Namespace: metav1.NamespaceDefault, 71 Finalizers: []string{"some-other-finalizer"}, 72 }, 73 Spec: expv1.MachinePoolSpec{ 74 Replicas: ptr.To[int32](1), 75 Template: clusterv1.MachineTemplateSpec{ 76 Spec: clusterv1.MachineSpec{ 77 Bootstrap: clusterv1.Bootstrap{ 78 DataSecretName: &bootstrapData, 79 }, 80 }, 81 }, 82 ClusterName: "valid-cluster", 83 }, 84 } 85 86 testCases := []struct { 87 name string 88 request reconcile.Request 89 m *expv1.MachinePool 90 expectedFinalizers []string 91 }{ 92 { 93 name: "should add a machinePool finalizer to the machinePool if it doesn't have one", 94 request: reconcile.Request{ 95 NamespacedName: util.ObjectKey(machinePoolValidCluster), 96 }, 97 m: machinePoolValidCluster, 98 expectedFinalizers: []string{expv1.MachinePoolFinalizer}, 99 }, 100 { 101 name: "should append the machinePool finalizer to the machinePool if it already has a finalizer", 102 request: reconcile.Request{ 103 NamespacedName: util.ObjectKey(machinePoolWithFinalizer), 104 }, 105 m: machinePoolWithFinalizer, 106 expectedFinalizers: []string{"some-other-finalizer", expv1.MachinePoolFinalizer}, 107 }, 108 } 109 110 for _, tc := range testCases { 111 t.Run(tc.name, func(t *testing.T) { 112 g := NewWithT(t) 113 114 mr := &MachinePoolReconciler{ 115 Client: fake.NewClientBuilder().WithObjects( 116 clusterCorrectMeta, 117 machinePoolValidCluster, 118 machinePoolWithFinalizer, 119 ).Build(), 120 } 121 122 _, _ = mr.Reconcile(ctx, tc.request) 123 124 key := client.ObjectKey{Namespace: tc.m.Namespace, Name: tc.m.Name} 125 var actual expv1.MachinePool 126 if len(tc.expectedFinalizers) > 0 { 127 g.Expect(mr.Client.Get(ctx, key, &actual)).To(Succeed()) 128 g.Expect(actual.Finalizers).ToNot(BeEmpty()) 129 g.Expect(actual.Finalizers).To(Equal(tc.expectedFinalizers)) 130 } else { 131 g.Expect(actual.Finalizers).To(BeEmpty()) 132 } 133 }) 134 } 135 } 136 137 func TestMachinePoolOwnerReference(t *testing.T) { 138 bootstrapData := "some valid machinepool bootstrap data" 139 testCluster := &clusterv1.Cluster{ 140 TypeMeta: metav1.TypeMeta{Kind: "Cluster", APIVersion: clusterv1.GroupVersion.String()}, 141 ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceDefault, Name: "test-cluster"}, 142 } 143 144 machinePoolInvalidCluster := &expv1.MachinePool{ 145 ObjectMeta: metav1.ObjectMeta{ 146 Name: "machinePool1", 147 Namespace: metav1.NamespaceDefault, 148 }, 149 Spec: expv1.MachinePoolSpec{ 150 Replicas: ptr.To[int32](1), 151 ClusterName: "invalid", 152 }, 153 } 154 155 machinePoolValidCluster := &expv1.MachinePool{ 156 ObjectMeta: metav1.ObjectMeta{ 157 Name: "machinePool2", 158 Namespace: metav1.NamespaceDefault, 159 }, 160 Spec: expv1.MachinePoolSpec{ 161 Replicas: ptr.To[int32](1), 162 Template: clusterv1.MachineTemplateSpec{ 163 Spec: clusterv1.MachineSpec{ 164 Bootstrap: clusterv1.Bootstrap{ 165 DataSecretName: &bootstrapData, 166 }, 167 }, 168 }, 169 ClusterName: "test-cluster", 170 }, 171 } 172 173 machinePoolValidMachinePool := &expv1.MachinePool{ 174 ObjectMeta: metav1.ObjectMeta{ 175 Name: "machinePool3", 176 Namespace: metav1.NamespaceDefault, 177 Labels: map[string]string{ 178 clusterv1.ClusterNameLabel: "valid-cluster", 179 }, 180 }, 181 Spec: expv1.MachinePoolSpec{ 182 Replicas: ptr.To[int32](1), 183 Template: clusterv1.MachineTemplateSpec{ 184 Spec: clusterv1.MachineSpec{ 185 Bootstrap: clusterv1.Bootstrap{ 186 DataSecretName: &bootstrapData, 187 }, 188 }, 189 }, 190 ClusterName: "test-cluster", 191 }, 192 } 193 194 testCases := []struct { 195 name string 196 request reconcile.Request 197 m *expv1.MachinePool 198 expectedOR []metav1.OwnerReference 199 }{ 200 { 201 name: "should add owner reference to machinePool referencing a cluster with correct type meta", 202 request: reconcile.Request{ 203 NamespacedName: util.ObjectKey(machinePoolValidCluster), 204 }, 205 m: machinePoolValidCluster, 206 expectedOR: []metav1.OwnerReference{ 207 { 208 APIVersion: testCluster.APIVersion, 209 Kind: testCluster.Kind, 210 Name: testCluster.Name, 211 UID: testCluster.UID, 212 }, 213 }, 214 }, 215 } 216 217 for _, tc := range testCases { 218 t.Run(tc.name, func(t *testing.T) { 219 g := NewWithT(t) 220 221 fakeClient := fake.NewClientBuilder().WithObjects( 222 testCluster, 223 machinePoolInvalidCluster, 224 machinePoolValidCluster, 225 machinePoolValidMachinePool, 226 ).WithStatusSubresource(&expv1.MachinePool{}).Build() 227 mr := &MachinePoolReconciler{ 228 Client: fakeClient, 229 APIReader: fakeClient, 230 } 231 232 key := client.ObjectKey{Namespace: tc.m.Namespace, Name: tc.m.Name} 233 var actual expv1.MachinePool 234 235 // this first requeue is to add finalizer 236 result, err := mr.Reconcile(ctx, tc.request) 237 g.Expect(err).ToNot(HaveOccurred()) 238 g.Expect(result).To(BeComparableTo(ctrl.Result{})) 239 g.Expect(mr.Client.Get(ctx, key, &actual)).To(Succeed()) 240 g.Expect(actual.Finalizers).To(ContainElement(expv1.MachinePoolFinalizer)) 241 242 _, _ = mr.Reconcile(ctx, tc.request) 243 244 if len(tc.expectedOR) > 0 { 245 g.Expect(mr.Client.Get(ctx, key, &actual)).To(Succeed()) 246 g.Expect(actual.OwnerReferences).To(BeComparableTo(tc.expectedOR)) 247 } else { 248 g.Expect(actual.OwnerReferences).To(BeEmpty()) 249 } 250 }) 251 } 252 } 253 254 func TestReconcileMachinePoolRequest(t *testing.T) { 255 infraConfig := unstructured.Unstructured{ 256 Object: map[string]interface{}{ 257 "kind": builder.TestInfrastructureMachineTemplateKind, 258 "apiVersion": builder.InfrastructureGroupVersion.String(), 259 "metadata": map[string]interface{}{ 260 "name": "infra-config1", 261 "namespace": metav1.NamespaceDefault, 262 }, 263 "spec": map[string]interface{}{ 264 "providerIDList": []interface{}{ 265 "test://id-1", 266 }, 267 }, 268 "status": map[string]interface{}{ 269 "ready": true, 270 "addresses": []interface{}{ 271 map[string]interface{}{ 272 "type": "InternalIP", 273 "address": "10.0.0.1", 274 }, 275 }, 276 }, 277 }, 278 } 279 280 time := metav1.Now() 281 282 testCluster := clusterv1.Cluster{ 283 TypeMeta: metav1.TypeMeta{Kind: "Cluster", APIVersion: clusterv1.GroupVersion.String()}, 284 ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceDefault, Name: "test-cluster"}, 285 } 286 287 bootstrapConfig := &unstructured.Unstructured{ 288 Object: map[string]interface{}{ 289 "kind": builder.TestBootstrapConfigKind, 290 "apiVersion": builder.BootstrapGroupVersion.String(), 291 "metadata": map[string]interface{}{ 292 "name": "test-bootstrap", 293 "namespace": metav1.NamespaceDefault, 294 }, 295 }, 296 } 297 298 type expected struct { 299 result reconcile.Result 300 err bool 301 } 302 testCases := []struct { 303 machinePool expv1.MachinePool 304 expected expected 305 }{ 306 { 307 machinePool: expv1.MachinePool{ 308 ObjectMeta: metav1.ObjectMeta{ 309 Name: "created", 310 Namespace: metav1.NamespaceDefault, 311 Finalizers: []string{expv1.MachinePoolFinalizer}, 312 }, 313 Spec: expv1.MachinePoolSpec{ 314 ClusterName: "test-cluster", 315 ProviderIDList: []string{"test://id-1"}, 316 Replicas: ptr.To[int32](1), 317 Template: clusterv1.MachineTemplateSpec{ 318 Spec: clusterv1.MachineSpec{ 319 320 InfrastructureRef: corev1.ObjectReference{ 321 APIVersion: builder.InfrastructureGroupVersion.String(), 322 Kind: builder.TestInfrastructureMachineTemplateKind, 323 Name: "infra-config1", 324 }, 325 Bootstrap: clusterv1.Bootstrap{DataSecretName: ptr.To("data")}, 326 }, 327 }, 328 }, 329 Status: expv1.MachinePoolStatus{ 330 Replicas: 1, 331 ReadyReplicas: 1, 332 NodeRefs: []corev1.ObjectReference{ 333 {Name: "test"}, 334 }, 335 ObservedGeneration: 1, 336 }, 337 }, 338 expected: expected{ 339 result: reconcile.Result{}, 340 err: false, 341 }, 342 }, 343 { 344 machinePool: expv1.MachinePool{ 345 ObjectMeta: metav1.ObjectMeta{ 346 Name: "updated", 347 Namespace: metav1.NamespaceDefault, 348 Finalizers: []string{expv1.MachinePoolFinalizer}, 349 }, 350 Spec: expv1.MachinePoolSpec{ 351 ClusterName: "test-cluster", 352 ProviderIDList: []string{"test://id-1"}, 353 Replicas: ptr.To[int32](1), 354 Template: clusterv1.MachineTemplateSpec{ 355 Spec: clusterv1.MachineSpec{ 356 InfrastructureRef: corev1.ObjectReference{ 357 APIVersion: builder.InfrastructureGroupVersion.String(), 358 Kind: builder.TestInfrastructureMachineTemplateKind, 359 Name: "infra-config1", 360 }, 361 Bootstrap: clusterv1.Bootstrap{DataSecretName: ptr.To("data")}, 362 }, 363 }, 364 }, 365 Status: expv1.MachinePoolStatus{ 366 Replicas: 1, 367 ReadyReplicas: 1, 368 NodeRefs: []corev1.ObjectReference{ 369 {Name: "test"}, 370 }, 371 ObservedGeneration: 1, 372 }, 373 }, 374 expected: expected{ 375 result: reconcile.Result{}, 376 err: false, 377 }, 378 }, 379 { 380 machinePool: expv1.MachinePool{ 381 ObjectMeta: metav1.ObjectMeta{ 382 Name: "deleted", 383 Namespace: metav1.NamespaceDefault, 384 Labels: map[string]string{ 385 clusterv1.MachineControlPlaneLabel: "", 386 }, 387 Finalizers: []string{expv1.MachinePoolFinalizer}, 388 DeletionTimestamp: &time, 389 }, 390 Spec: expv1.MachinePoolSpec{ 391 ClusterName: "test-cluster", 392 Replicas: ptr.To[int32](1), 393 Template: clusterv1.MachineTemplateSpec{ 394 Spec: clusterv1.MachineSpec{ 395 InfrastructureRef: corev1.ObjectReference{ 396 APIVersion: builder.InfrastructureGroupVersion.String(), 397 Kind: builder.TestInfrastructureMachineTemplateKind, 398 Name: "infra-config1", 399 }, 400 Bootstrap: clusterv1.Bootstrap{DataSecretName: ptr.To("data")}, 401 }, 402 }, 403 }, 404 }, 405 expected: expected{ 406 result: reconcile.Result{}, 407 err: false, 408 }, 409 }, 410 } 411 412 for i := range testCases { 413 tc := testCases[i] 414 t.Run("machinePool should be "+tc.machinePool.Name, func(t *testing.T) { 415 g := NewWithT(t) 416 417 clientFake := fake.NewClientBuilder().WithObjects( 418 &testCluster, 419 &tc.machinePool, 420 &infraConfig, 421 bootstrapConfig, 422 builder.TestBootstrapConfigCRD, 423 builder.TestInfrastructureMachineTemplateCRD, 424 ).WithStatusSubresource(&expv1.MachinePool{}).Build() 425 426 r := &MachinePoolReconciler{ 427 Client: clientFake, 428 APIReader: clientFake, 429 } 430 431 result, err := r.Reconcile(ctx, reconcile.Request{NamespacedName: util.ObjectKey(&tc.machinePool)}) 432 if tc.expected.err { 433 g.Expect(err).To(HaveOccurred()) 434 } else { 435 g.Expect(err).ToNot(HaveOccurred()) 436 } 437 438 g.Expect(result).To(BeComparableTo(tc.expected.result)) 439 }) 440 } 441 } 442 443 func TestReconcileMachinePoolDeleteExternal(t *testing.T) { 444 testCluster := &clusterv1.Cluster{ 445 ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceDefault, Name: "test-cluster"}, 446 } 447 448 bootstrapConfig := &unstructured.Unstructured{ 449 Object: map[string]interface{}{ 450 "kind": builder.TestBootstrapConfigKind, 451 "apiVersion": builder.BootstrapGroupVersion.String(), 452 "metadata": map[string]interface{}{ 453 "name": "delete-bootstrap", 454 "namespace": metav1.NamespaceDefault, 455 }, 456 }, 457 } 458 459 infraConfig := &unstructured.Unstructured{ 460 Object: map[string]interface{}{ 461 "kind": builder.TestInfrastructureMachineTemplateKind, 462 "apiVersion": builder.InfrastructureGroupVersion.String(), 463 "metadata": map[string]interface{}{ 464 "name": "delete-infra", 465 "namespace": metav1.NamespaceDefault, 466 }, 467 }, 468 } 469 470 machinePool := &expv1.MachinePool{ 471 ObjectMeta: metav1.ObjectMeta{ 472 Name: "delete", 473 Namespace: metav1.NamespaceDefault, 474 }, 475 Spec: expv1.MachinePoolSpec{ 476 ClusterName: "test-cluster", 477 Replicas: ptr.To[int32](1), 478 Template: clusterv1.MachineTemplateSpec{ 479 Spec: clusterv1.MachineSpec{ 480 InfrastructureRef: corev1.ObjectReference{ 481 APIVersion: builder.InfrastructureGroupVersion.String(), 482 Kind: builder.TestInfrastructureMachineTemplateKind, 483 Name: "delete-infra", 484 }, 485 Bootstrap: clusterv1.Bootstrap{ 486 ConfigRef: &corev1.ObjectReference{ 487 APIVersion: builder.BootstrapGroupVersion.String(), 488 Kind: builder.TestBootstrapConfigKind, 489 Name: "delete-bootstrap", 490 }, 491 }, 492 }, 493 }, 494 }, 495 } 496 497 testCases := []struct { 498 name string 499 bootstrapExists bool 500 infraExists bool 501 expected bool 502 expectError bool 503 }{ 504 { 505 name: "should continue to reconcile delete of external refs since both refs exists", 506 bootstrapExists: true, 507 infraExists: true, 508 expected: false, 509 expectError: false, 510 }, 511 { 512 name: "should continue to reconcile delete of external refs since infra ref exist", 513 bootstrapExists: false, 514 infraExists: true, 515 expected: false, 516 expectError: false, 517 }, 518 { 519 name: "should continue to reconcile delete of external refs since bootstrap ref exist", 520 bootstrapExists: true, 521 infraExists: false, 522 expected: false, 523 expectError: false, 524 }, 525 { 526 name: "should no longer reconcile deletion of external refs since both don't exist", 527 bootstrapExists: false, 528 infraExists: false, 529 expected: true, 530 expectError: false, 531 }, 532 } 533 534 for _, tc := range testCases { 535 t.Run(tc.name, func(t *testing.T) { 536 g := NewWithT(t) 537 objs := []client.Object{testCluster, machinePool} 538 539 if tc.bootstrapExists { 540 objs = append(objs, bootstrapConfig) 541 } 542 543 if tc.infraExists { 544 objs = append(objs, infraConfig) 545 } 546 547 r := &MachinePoolReconciler{ 548 Client: fake.NewClientBuilder().WithObjects(objs...).Build(), 549 } 550 551 ok, err := r.reconcileDeleteExternal(ctx, machinePool) 552 g.Expect(ok).To(Equal(tc.expected)) 553 if tc.expectError { 554 g.Expect(err).To(HaveOccurred()) 555 } else { 556 g.Expect(err).ToNot(HaveOccurred()) 557 } 558 }) 559 } 560 } 561 562 func TestRemoveMachinePoolFinalizerAfterDeleteReconcile(t *testing.T) { 563 g := NewWithT(t) 564 565 dt := metav1.Now() 566 567 testCluster := &clusterv1.Cluster{ 568 ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceDefault, Name: "test-cluster"}, 569 } 570 571 m := &expv1.MachinePool{ 572 ObjectMeta: metav1.ObjectMeta{ 573 Name: "delete123", 574 Namespace: metav1.NamespaceDefault, 575 Finalizers: []string{expv1.MachinePoolFinalizer, "test"}, 576 DeletionTimestamp: &dt, 577 }, 578 Spec: expv1.MachinePoolSpec{ 579 ClusterName: "test-cluster", 580 Replicas: ptr.To[int32](1), 581 Template: clusterv1.MachineTemplateSpec{ 582 Spec: clusterv1.MachineSpec{ 583 InfrastructureRef: corev1.ObjectReference{ 584 APIVersion: builder.InfrastructureGroupVersion.String(), 585 Kind: builder.TestInfrastructureMachineTemplateKind, 586 Name: "infra-config1", 587 }, 588 Bootstrap: clusterv1.Bootstrap{DataSecretName: ptr.To("data")}, 589 }, 590 }, 591 }, 592 } 593 key := client.ObjectKey{Namespace: m.Namespace, Name: m.Name} 594 mr := &MachinePoolReconciler{ 595 Client: fake.NewClientBuilder().WithObjects(testCluster, m).WithStatusSubresource(&expv1.MachinePool{}).Build(), 596 } 597 _, err := mr.Reconcile(ctx, reconcile.Request{NamespacedName: key}) 598 g.Expect(err).ToNot(HaveOccurred()) 599 600 var actual expv1.MachinePool 601 g.Expect(mr.Client.Get(ctx, key, &actual)).To(Succeed()) 602 g.Expect(actual.ObjectMeta.Finalizers).To(Equal([]string{"test"})) 603 } 604 605 func TestMachinePoolConditions(t *testing.T) { 606 testCluster := &clusterv1.Cluster{ 607 ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceDefault, Name: "test-cluster"}, 608 } 609 610 bootstrapConfig := func(ready bool) *unstructured.Unstructured { 611 return &unstructured.Unstructured{ 612 Object: map[string]interface{}{ 613 "kind": builder.TestBootstrapConfigKind, 614 "apiVersion": builder.BootstrapGroupVersion.String(), 615 "metadata": map[string]interface{}{ 616 "name": "bootstrap1", 617 "namespace": metav1.NamespaceDefault, 618 }, 619 "status": map[string]interface{}{ 620 "ready": ready, 621 "dataSecretName": "data", 622 }, 623 }, 624 } 625 } 626 627 infraConfig := func(ready bool) *unstructured.Unstructured { 628 return &unstructured.Unstructured{ 629 Object: map[string]interface{}{ 630 "kind": builder.TestInfrastructureMachineTemplateKind, 631 "apiVersion": builder.InfrastructureGroupVersion.String(), 632 "metadata": map[string]interface{}{ 633 "name": "infra1", 634 "namespace": metav1.NamespaceDefault, 635 }, 636 "status": map[string]interface{}{ 637 "ready": ready, 638 }, 639 "spec": map[string]interface{}{ 640 "providerIDList": []interface{}{ 641 "azure://westus2/id-node-4", 642 "aws://us-east-1/id-node-1", 643 }, 644 }, 645 }, 646 } 647 } 648 649 machinePool := &expv1.MachinePool{ 650 ObjectMeta: metav1.ObjectMeta{ 651 Name: "blah", 652 Namespace: metav1.NamespaceDefault, 653 Finalizers: []string{expv1.MachinePoolFinalizer}, 654 }, 655 Spec: expv1.MachinePoolSpec{ 656 ClusterName: "test-cluster", 657 Replicas: ptr.To[int32](2), 658 Template: clusterv1.MachineTemplateSpec{ 659 Spec: clusterv1.MachineSpec{ 660 InfrastructureRef: corev1.ObjectReference{ 661 APIVersion: builder.InfrastructureGroupVersion.String(), 662 Kind: builder.TestInfrastructureMachineTemplateKind, 663 Name: "infra1", 664 }, 665 Bootstrap: clusterv1.Bootstrap{ 666 ConfigRef: &corev1.ObjectReference{ 667 APIVersion: builder.BootstrapGroupVersion.String(), 668 Kind: builder.TestBootstrapConfigKind, 669 Name: "bootstrap1", 670 }, 671 }, 672 }, 673 }, 674 }, 675 } 676 677 nodeList := corev1.NodeList{ 678 Items: []corev1.Node{ 679 { 680 ObjectMeta: metav1.ObjectMeta{ 681 Name: "node-1", 682 }, 683 Spec: corev1.NodeSpec{ 684 ProviderID: "aws://us-east-1/id-node-1", 685 }, 686 Status: corev1.NodeStatus{Conditions: []corev1.NodeCondition{{Type: corev1.NodeReady}}}, 687 }, 688 { 689 ObjectMeta: metav1.ObjectMeta{ 690 Name: "azure-node-4", 691 }, 692 Spec: corev1.NodeSpec{ 693 ProviderID: "azure://westus2/id-node-4", 694 }, 695 Status: corev1.NodeStatus{Conditions: []corev1.NodeCondition{{Type: corev1.NodeReady}}}, 696 }, 697 }, 698 } 699 700 testcases := []struct { 701 name string 702 bootstrapReady bool 703 infrastructureReady bool 704 expectError bool 705 beforeFunc func(bootstrap, infra *unstructured.Unstructured, mp *expv1.MachinePool, nodeList *corev1.NodeList) 706 conditionAssertFunc func(t *testing.T, getter conditions.Getter) 707 }{ 708 { 709 name: "all conditions true", 710 bootstrapReady: true, 711 infrastructureReady: true, 712 beforeFunc: func(_, _ *unstructured.Unstructured, mp *expv1.MachinePool, _ *corev1.NodeList) { 713 mp.Spec.ProviderIDList = []string{"azure://westus2/id-node-4", "aws://us-east-1/id-node-1"} 714 mp.Status = expv1.MachinePoolStatus{ 715 NodeRefs: []corev1.ObjectReference{ 716 {Name: "node-1"}, 717 {Name: "azure-node-4"}, 718 }, 719 Replicas: 2, 720 ReadyReplicas: 2, 721 } 722 }, 723 conditionAssertFunc: func(t *testing.T, getter conditions.Getter) { 724 t.Helper() 725 g := NewWithT(t) 726 727 g.Expect(getter.GetConditions()).NotTo(BeEmpty()) 728 for _, c := range getter.GetConditions() { 729 g.Expect(c.Status).To(Equal(corev1.ConditionTrue)) 730 } 731 }, 732 }, 733 { 734 name: "boostrap not ready", 735 bootstrapReady: false, 736 infrastructureReady: true, 737 beforeFunc: func(bootstrap, _ *unstructured.Unstructured, _ *expv1.MachinePool, _ *corev1.NodeList) { 738 addConditionsToExternal(bootstrap, clusterv1.Conditions{ 739 { 740 Type: clusterv1.ReadyCondition, 741 Status: corev1.ConditionFalse, 742 Severity: clusterv1.ConditionSeverityInfo, 743 Reason: "Custom reason", 744 }, 745 }) 746 }, 747 conditionAssertFunc: func(t *testing.T, getter conditions.Getter) { 748 t.Helper() 749 g := NewWithT(t) 750 751 g.Expect(conditions.Has(getter, clusterv1.BootstrapReadyCondition)).To(BeTrue()) 752 infraReadyCondition := conditions.Get(getter, clusterv1.BootstrapReadyCondition) 753 g.Expect(infraReadyCondition.Status).To(Equal(corev1.ConditionFalse)) 754 g.Expect(infraReadyCondition.Reason).To(Equal("Custom reason")) 755 }, 756 }, 757 { 758 name: "bootstrap not ready with fallback condition", 759 bootstrapReady: false, 760 infrastructureReady: true, 761 conditionAssertFunc: func(t *testing.T, getter conditions.Getter) { 762 t.Helper() 763 g := NewWithT(t) 764 765 g.Expect(conditions.Has(getter, clusterv1.BootstrapReadyCondition)).To(BeTrue()) 766 bootstrapReadyCondition := conditions.Get(getter, clusterv1.BootstrapReadyCondition) 767 g.Expect(bootstrapReadyCondition.Status).To(Equal(corev1.ConditionFalse)) 768 769 g.Expect(conditions.Has(getter, clusterv1.ReadyCondition)).To(BeTrue()) 770 readyCondition := conditions.Get(getter, clusterv1.ReadyCondition) 771 g.Expect(readyCondition.Status).To(Equal(corev1.ConditionFalse)) 772 }, 773 }, 774 { 775 name: "infrastructure not ready", 776 bootstrapReady: true, 777 infrastructureReady: false, 778 beforeFunc: func(_, infra *unstructured.Unstructured, _ *expv1.MachinePool, _ *corev1.NodeList) { 779 addConditionsToExternal(infra, clusterv1.Conditions{ 780 { 781 Type: clusterv1.ReadyCondition, 782 Status: corev1.ConditionFalse, 783 Severity: clusterv1.ConditionSeverityInfo, 784 Reason: "Custom reason", 785 }, 786 }) 787 }, 788 conditionAssertFunc: func(t *testing.T, getter conditions.Getter) { 789 t.Helper() 790 791 g := NewWithT(t) 792 793 g.Expect(conditions.Has(getter, clusterv1.InfrastructureReadyCondition)).To(BeTrue()) 794 infraReadyCondition := conditions.Get(getter, clusterv1.InfrastructureReadyCondition) 795 g.Expect(infraReadyCondition.Status).To(Equal(corev1.ConditionFalse)) 796 g.Expect(infraReadyCondition.Reason).To(Equal("Custom reason")) 797 }, 798 }, 799 { 800 name: "infrastructure not ready with fallback condition", 801 bootstrapReady: true, 802 infrastructureReady: false, 803 conditionAssertFunc: func(t *testing.T, getter conditions.Getter) { 804 t.Helper() 805 g := NewWithT(t) 806 807 g.Expect(conditions.Has(getter, clusterv1.InfrastructureReadyCondition)).To(BeTrue()) 808 infraReadyCondition := conditions.Get(getter, clusterv1.InfrastructureReadyCondition) 809 g.Expect(infraReadyCondition.Status).To(Equal(corev1.ConditionFalse)) 810 811 g.Expect(conditions.Has(getter, clusterv1.ReadyCondition)).To(BeTrue()) 812 readyCondition := conditions.Get(getter, clusterv1.ReadyCondition) 813 g.Expect(readyCondition.Status).To(Equal(corev1.ConditionFalse)) 814 }, 815 }, 816 { 817 name: "incorrect infrastructure reference", 818 bootstrapReady: true, 819 expectError: true, 820 beforeFunc: func(_, _ *unstructured.Unstructured, mp *expv1.MachinePool, _ *corev1.NodeList) { 821 mp.Spec.Template.Spec.InfrastructureRef = corev1.ObjectReference{ 822 APIVersion: builder.InfrastructureGroupVersion.String(), 823 Kind: builder.TestInfrastructureMachineTemplateKind, 824 Name: "does-not-exist", 825 } 826 }, 827 conditionAssertFunc: func(t *testing.T, getter conditions.Getter) { 828 t.Helper() 829 g := NewWithT(t) 830 831 g.Expect(conditions.Has(getter, clusterv1.InfrastructureReadyCondition)).To(BeTrue()) 832 infraReadyCondition := conditions.Get(getter, clusterv1.InfrastructureReadyCondition) 833 g.Expect(infraReadyCondition.Status).To(Equal(corev1.ConditionFalse)) 834 }, 835 }, 836 } 837 838 for _, tt := range testcases { 839 t.Run(tt.name, func(t *testing.T) { 840 g := NewWithT(t) 841 842 // setup objects 843 bootstrap := bootstrapConfig(tt.bootstrapReady) 844 infra := infraConfig(tt.infrastructureReady) 845 mp := machinePool.DeepCopy() 846 nodes := nodeList.DeepCopy() 847 if tt.beforeFunc != nil { 848 tt.beforeFunc(bootstrap, infra, mp, nodes) 849 } 850 851 g.Expect(clusterv1.AddToScheme(scheme.Scheme)).To(Succeed()) 852 853 clientFake := fake.NewClientBuilder().WithObjects( 854 testCluster, 855 mp, 856 infra, 857 bootstrap, 858 &nodes.Items[0], 859 &nodes.Items[1], 860 builder.TestBootstrapConfigCRD, 861 builder.TestInfrastructureMachineTemplateCRD, 862 ).WithStatusSubresource(&expv1.MachinePool{}).Build() 863 864 r := &MachinePoolReconciler{ 865 Client: clientFake, 866 APIReader: clientFake, 867 } 868 869 _, err := r.Reconcile(ctx, reconcile.Request{NamespacedName: util.ObjectKey(machinePool)}) 870 if !tt.expectError { 871 g.Expect(err).ToNot(HaveOccurred()) 872 } 873 874 m := &expv1.MachinePool{} 875 machinePoolKey := client.ObjectKeyFromObject(machinePool) 876 g.Expect(r.Client.Get(ctx, machinePoolKey, m)).ToNot(HaveOccurred()) 877 878 tt.conditionAssertFunc(t, m) 879 }) 880 } 881 } 882 883 // adds a condition list to an external object. 884 func addConditionsToExternal(u *unstructured.Unstructured, newConditions clusterv1.Conditions) { 885 existingConditions := clusterv1.Conditions{} 886 if cs := conditions.UnstructuredGetter(u).GetConditions(); len(cs) != 0 { 887 existingConditions = cs 888 } 889 existingConditions = append(existingConditions, newConditions...) 890 conditions.UnstructuredSetter(u).SetConditions(existingConditions) 891 }