sigs.k8s.io/cluster-api-provider-azure@v1.14.3/controllers/azuremachine_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 "context" 21 "testing" 22 "time" 23 24 . "github.com/onsi/gomega" 25 "github.com/pkg/errors" 26 corev1 "k8s.io/api/core/v1" 27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 "k8s.io/apimachinery/pkg/runtime" 29 "k8s.io/apimachinery/pkg/types" 30 "k8s.io/client-go/tools/record" 31 "k8s.io/utils/ptr" 32 infrav1 "sigs.k8s.io/cluster-api-provider-azure/api/v1beta1" 33 "sigs.k8s.io/cluster-api-provider-azure/azure" 34 "sigs.k8s.io/cluster-api-provider-azure/azure/scope" 35 "sigs.k8s.io/cluster-api-provider-azure/azure/services/resourceskus" 36 "sigs.k8s.io/cluster-api-provider-azure/util/reconciler" 37 clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" 38 capierrors "sigs.k8s.io/cluster-api/errors" 39 ctrl "sigs.k8s.io/controller-runtime" 40 "sigs.k8s.io/controller-runtime/pkg/client" 41 "sigs.k8s.io/controller-runtime/pkg/client/fake" 42 "sigs.k8s.io/controller-runtime/pkg/reconcile" 43 ) 44 45 type TestMachineReconcileInput struct { 46 createAzureMachineService func(*scope.MachineScope) (*azureMachineService, error) 47 azureMachineOptions func(am *infrav1.AzureMachine) 48 expectedErr string 49 machineScopeFailureReason capierrors.MachineStatusError 50 ready bool 51 cache *scope.MachineCache 52 skuCache scope.SKUCacher 53 expectedResult reconcile.Result 54 } 55 56 func TestAzureMachineReconcile(t *testing.T) { 57 g := NewWithT(t) 58 scheme, err := newScheme() 59 g.Expect(err).NotTo(HaveOccurred()) 60 61 defaultCluster := getFakeCluster() 62 defaultAzureCluster := getFakeAzureCluster() 63 defaultAzureMachine := getFakeAzureMachine() 64 defaultMachine := getFakeMachine(defaultAzureMachine) 65 defaultAzureClusterIdentity := getFakeAzureClusterIdentity() 66 defaultSecret := &corev1.Secret{Data: map[string][]byte{"clientSecret": []byte("fooSecret")}} 67 68 cases := map[string]struct { 69 objects []runtime.Object 70 fail bool 71 err string 72 event string 73 }{ 74 "should reconcile normally": { 75 objects: []runtime.Object{ 76 defaultCluster, 77 defaultAzureCluster, 78 defaultAzureMachine, 79 defaultMachine, 80 defaultAzureClusterIdentity, 81 defaultSecret, 82 }, 83 }, 84 "should not fail if the azure machine is not found": { 85 objects: []runtime.Object{ 86 defaultCluster, 87 defaultAzureCluster, 88 defaultMachine, 89 defaultAzureClusterIdentity, 90 }, 91 }, 92 "should fail if machine is not found": { 93 objects: []runtime.Object{ 94 defaultCluster, 95 defaultAzureCluster, 96 defaultAzureMachine, 97 defaultAzureClusterIdentity, 98 }, 99 fail: true, 100 err: "machines.cluster.x-k8s.io \"my-machine\" not found", 101 }, 102 "should return if azureMachine has not yet set ownerref": { 103 objects: []runtime.Object{ 104 defaultCluster, 105 defaultAzureCluster, 106 getFakeAzureMachine(func(am *infrav1.AzureMachine) { 107 am.OwnerReferences = nil 108 }), 109 defaultMachine, 110 defaultAzureClusterIdentity, 111 }, 112 event: "Machine controller dependency not yet met", 113 }, 114 "should return if cluster does not exist": { 115 objects: []runtime.Object{ 116 defaultAzureCluster, 117 defaultAzureMachine, 118 defaultMachine, 119 defaultAzureClusterIdentity, 120 }, 121 event: "Unable to get cluster from metadata", 122 }, 123 "should return if azureCluster does not yet available": { 124 objects: []runtime.Object{ 125 defaultCluster, 126 defaultAzureMachine, 127 defaultMachine, 128 defaultAzureClusterIdentity, 129 }, 130 event: "AzureCluster unavailable", 131 }, 132 } 133 134 for name, tc := range cases { 135 t.Run(name, func(t *testing.T) { 136 fakeClient := fake.NewClientBuilder(). 137 WithScheme(scheme). 138 WithRuntimeObjects(tc.objects...). 139 WithStatusSubresource( 140 &infrav1.AzureMachine{}, 141 ). 142 Build() 143 144 resultIdentity := &infrav1.AzureClusterIdentity{} 145 key := client.ObjectKey{Name: defaultAzureClusterIdentity.Name, Namespace: defaultAzureClusterIdentity.Namespace} 146 g.Expect(fakeClient.Get(context.TODO(), key, resultIdentity)).To(Succeed()) 147 148 reconciler := &AzureMachineReconciler{ 149 Client: fakeClient, 150 Recorder: record.NewFakeRecorder(128), 151 } 152 153 _, err := reconciler.Reconcile(context.Background(), ctrl.Request{ 154 NamespacedName: types.NamespacedName{ 155 Namespace: "default", 156 Name: "my-machine", 157 }, 158 }) 159 if tc.event != "" { 160 g.Expect(reconciler.Recorder.(*record.FakeRecorder).Events).To(Receive(ContainSubstring(tc.event))) 161 } 162 if tc.fail { 163 g.Expect(err).To(MatchError(tc.err)) 164 } else { 165 g.Expect(err).NotTo(HaveOccurred()) 166 } 167 }) 168 } 169 } 170 171 type fakeSKUCacher struct{} 172 173 func (f fakeSKUCacher) Get(context.Context, string, resourceskus.ResourceType) (resourceskus.SKU, error) { 174 return resourceskus.SKU{}, errors.New("not implemented") 175 } 176 177 func TestAzureMachineReconcileNormal(t *testing.T) { 178 cases := map[string]TestMachineReconcileInput{ 179 "should reconcile normally": { 180 createAzureMachineService: getFakeAzureMachineService, 181 cache: &scope.MachineCache{}, 182 ready: true, 183 }, 184 "should skip reconciliation if error state is detected on azure machine": { 185 azureMachineOptions: func(am *infrav1.AzureMachine) { 186 updateError := capierrors.UpdateMachineError 187 am.Status.FailureReason = &updateError 188 }, 189 createAzureMachineService: getFakeAzureMachineService, 190 }, 191 "should fail if failed to initialize machine cache": { 192 createAzureMachineService: getFakeAzureMachineService, 193 cache: nil, 194 skuCache: fakeSKUCacher{}, 195 expectedErr: "failed to init machine scope cache", 196 }, 197 "should fail if identities are not ready": { 198 azureMachineOptions: func(am *infrav1.AzureMachine) { 199 am.Status.Conditions = clusterv1.Conditions{ 200 { 201 Type: infrav1.VMIdentitiesReadyCondition, 202 Reason: infrav1.UserAssignedIdentityMissingReason, 203 Status: corev1.ConditionFalse, 204 }, 205 } 206 }, 207 createAzureMachineService: getFakeAzureMachineService, 208 cache: &scope.MachineCache{}, 209 expectedErr: "VM identities are not ready", 210 }, 211 "should fail if azure machine service creator fails": { 212 createAzureMachineService: func(*scope.MachineScope) (*azureMachineService, error) { 213 return nil, errors.New("failed to create azure machine service") 214 }, 215 cache: &scope.MachineCache{}, 216 expectedErr: "failed to create azure machine service", 217 }, 218 "should fail if VM is deleted": { 219 createAzureMachineService: getFakeAzureMachineServiceWithVMDeleted, 220 machineScopeFailureReason: capierrors.UpdateMachineError, 221 cache: &scope.MachineCache{}, 222 expectedErr: "failed to reconcile AzureMachine", 223 }, 224 "should reconcile if terminal error is received": { 225 createAzureMachineService: getFakeAzureMachineServiceWithTerminalError, 226 machineScopeFailureReason: capierrors.CreateMachineError, 227 cache: &scope.MachineCache{}, 228 }, 229 "should requeue if transient error is received": { 230 createAzureMachineService: getFakeAzureMachineServiceWithTransientError, 231 cache: &scope.MachineCache{}, 232 expectedResult: reconcile.Result{RequeueAfter: 10 * time.Second}, 233 }, 234 "should return error for general failures": { 235 createAzureMachineService: getFakeAzureMachineServiceWithGeneralError, 236 cache: &scope.MachineCache{}, 237 expectedErr: "failed to reconcile AzureMachine", 238 }, 239 } 240 241 for name, c := range cases { 242 tc := c 243 t.Run(name, func(t *testing.T) { 244 g := NewWithT(t) 245 246 reconciler, machineScope, clusterScope, err := getMachineReconcileInputs(tc) 247 g.Expect(err).NotTo(HaveOccurred()) 248 249 result, err := reconciler.reconcileNormal(context.Background(), machineScope, clusterScope) 250 g.Expect(result).To(Equal(tc.expectedResult)) 251 252 if tc.ready { 253 g.Expect(machineScope.AzureMachine.Status.Ready).To(BeTrue()) 254 } 255 if tc.machineScopeFailureReason != "" { 256 g.Expect(machineScope.AzureMachine.Status.FailureReason).NotTo(BeNil()) 257 g.Expect(*machineScope.AzureMachine.Status.FailureReason).To(Equal(tc.machineScopeFailureReason)) 258 } 259 if tc.expectedErr != "" { 260 g.Expect(err).To(HaveOccurred()) 261 g.Expect(err.Error()).To(ContainSubstring(tc.expectedErr)) 262 } else { 263 g.Expect(err).NotTo(HaveOccurred()) 264 } 265 }) 266 } 267 } 268 269 func TestAzureMachineReconcilePause(t *testing.T) { 270 cases := map[string]TestMachineReconcileInput{ 271 "should pause successfully": { 272 createAzureMachineService: getFakeAzureMachineService, 273 cache: &scope.MachineCache{}, 274 }, 275 "should fail if failed to create azure machine service": { 276 createAzureMachineService: getFakeAzureMachineServiceWithFailure, 277 cache: &scope.MachineCache{}, 278 expectedErr: "failed to create AzureMachineService", 279 }, 280 "should fail to pause for errors": { 281 createAzureMachineService: getFakeAzureMachineServiceWithGeneralError, 282 cache: &scope.MachineCache{}, 283 expectedErr: "failed to pause azure machine service", 284 }, 285 } 286 287 for name, c := range cases { 288 tc := c 289 t.Run(name, func(t *testing.T) { 290 g := NewWithT(t) 291 292 reconciler, machineScope, _, err := getMachineReconcileInputs(tc) 293 g.Expect(err).NotTo(HaveOccurred()) 294 295 result, err := reconciler.reconcilePause(context.Background(), machineScope) 296 g.Expect(result).To(Equal(tc.expectedResult)) 297 298 if tc.expectedErr != "" { 299 g.Expect(err).To(HaveOccurred()) 300 g.Expect(err.Error()).To(ContainSubstring(tc.expectedErr)) 301 } else { 302 g.Expect(err).NotTo(HaveOccurred()) 303 } 304 }) 305 } 306 } 307 308 func TestAzureMachineReconcileDelete(t *testing.T) { 309 cases := map[string]TestMachineReconcileInput{ 310 "should delete successfully": { 311 createAzureMachineService: getFakeAzureMachineService, 312 cache: &scope.MachineCache{}, 313 }, 314 "should fail if failed to create azure machine service": { 315 createAzureMachineService: getFakeAzureMachineServiceWithFailure, 316 cache: &scope.MachineCache{}, 317 expectedErr: "failed to create AzureMachineService", 318 }, 319 "should requeue if transient error is received": { 320 createAzureMachineService: getFakeAzureMachineServiceWithTransientError, 321 cache: &scope.MachineCache{}, 322 expectedResult: reconcile.Result{RequeueAfter: 10 * time.Second}, 323 }, 324 "should fail to delete for non-transient errors": { 325 createAzureMachineService: getFakeAzureMachineServiceWithGeneralError, 326 cache: &scope.MachineCache{}, 327 expectedErr: "error deleting AzureMachine", 328 }, 329 } 330 331 for name, c := range cases { 332 tc := c 333 t.Run(name, func(t *testing.T) { 334 g := NewWithT(t) 335 336 reconciler, machineScope, clusterScope, err := getMachineReconcileInputs(tc) 337 g.Expect(err).NotTo(HaveOccurred()) 338 339 result, err := reconciler.reconcileDelete(context.Background(), machineScope, clusterScope) 340 g.Expect(result).To(Equal(tc.expectedResult)) 341 342 if tc.expectedErr != "" { 343 g.Expect(err).To(HaveOccurred()) 344 g.Expect(err.Error()).To(ContainSubstring(tc.expectedErr)) 345 } else { 346 g.Expect(err).NotTo(HaveOccurred()) 347 } 348 }) 349 } 350 } 351 352 func getMachineReconcileInputs(tc TestMachineReconcileInput) (*AzureMachineReconciler, *scope.MachineScope, *scope.ClusterScope, error) { 353 scheme, err := newScheme() 354 if err != nil { 355 return nil, nil, nil, err 356 } 357 358 var azureMachine *infrav1.AzureMachine 359 if tc.azureMachineOptions != nil { 360 azureMachine = getFakeAzureMachine(tc.azureMachineOptions) 361 } else { 362 azureMachine = getFakeAzureMachine() 363 } 364 365 cluster := getFakeCluster() 366 azureCluster := getFakeAzureCluster(func(ac *infrav1.AzureCluster) { 367 ac.Spec.Location = "westus2" 368 }) 369 machine := getFakeMachine(azureMachine, func(m *clusterv1.Machine) { 370 m.Spec.Bootstrap = clusterv1.Bootstrap{ 371 DataSecretName: ptr.To("fooSecret"), 372 } 373 }) 374 azureClusterIdentity := getFakeAzureClusterIdentity(func(identity *infrav1.AzureClusterIdentity) { 375 identity.Spec.ClientSecret.Name = "fooSecret" 376 identity.Spec.ClientSecret.Namespace = "default" 377 }) 378 379 objects := []runtime.Object{ 380 cluster, 381 azureCluster, 382 machine, 383 azureMachine, 384 azureClusterIdentity, 385 &corev1.Secret{ 386 ObjectMeta: metav1.ObjectMeta{ 387 Name: "fooSecret", 388 Namespace: "default", 389 }, 390 Data: map[string][]byte{ 391 "clientSecret": []byte("fooSecret"), 392 }, 393 }, 394 } 395 396 client := fake.NewClientBuilder(). 397 WithScheme(scheme). 398 WithRuntimeObjects(objects...). 399 WithStatusSubresource( 400 &infrav1.AzureMachine{}, 401 ). 402 Build() 403 404 reconciler := &AzureMachineReconciler{ 405 Client: client, 406 Recorder: record.NewFakeRecorder(128), 407 createAzureMachineService: tc.createAzureMachineService, 408 } 409 410 clusterScope, err := scope.NewClusterScope(context.Background(), scope.ClusterScopeParams{ 411 Client: client, 412 Cluster: cluster, 413 AzureCluster: azureCluster, 414 }) 415 if err != nil { 416 return nil, nil, nil, err 417 } 418 419 machineScope, err := scope.NewMachineScope(scope.MachineScopeParams{ 420 Client: client, 421 Machine: machine, 422 AzureMachine: azureMachine, 423 ClusterScope: clusterScope, 424 Cache: tc.cache, 425 SKUCache: tc.skuCache, 426 }) 427 if err != nil { 428 return nil, nil, nil, err 429 } 430 431 return reconciler, machineScope, clusterScope, nil 432 } 433 434 func getFakeAzureMachineService(machineScope *scope.MachineScope) (*azureMachineService, error) { 435 cache, err := resourceskus.GetCache(machineScope, machineScope.Location()) 436 if err != nil { 437 return nil, errors.Wrap(err, "failed creating a NewCache") 438 } 439 440 return getDefaultAzureMachineService(machineScope, cache), nil 441 } 442 443 func getFakeAzureMachineServiceWithFailure(_ *scope.MachineScope) (*azureMachineService, error) { 444 return nil, errors.New("failed to create AzureMachineService") 445 } 446 447 func getFakeAzureMachineServiceWithVMDeleted(machineScope *scope.MachineScope) (*azureMachineService, error) { 448 cache, err := resourceskus.GetCache(machineScope, machineScope.Location()) 449 if err != nil { 450 return nil, errors.Wrap(err, "failed creating a NewCache") 451 } 452 453 ams := getDefaultAzureMachineService(machineScope, cache) 454 ams.Reconcile = func(context.Context) error { 455 return azure.VMDeletedError{} 456 } 457 458 return ams, nil 459 } 460 461 func getFakeAzureMachineServiceWithTerminalError(machineScope *scope.MachineScope) (*azureMachineService, error) { 462 cache, err := resourceskus.GetCache(machineScope, machineScope.Location()) 463 if err != nil { 464 return nil, errors.Wrap(err, "failed creating a NewCache") 465 } 466 467 ams := getDefaultAzureMachineService(machineScope, cache) 468 ams.Reconcile = func(context.Context) error { 469 return azure.WithTerminalError(errors.New("failed to reconcile AzureMachine")) 470 } 471 472 return ams, nil 473 } 474 475 func getFakeAzureMachineServiceWithTransientError(machineScope *scope.MachineScope) (*azureMachineService, error) { 476 cache, err := resourceskus.GetCache(machineScope, machineScope.Location()) 477 if err != nil { 478 return nil, errors.Wrap(err, "failed creating a NewCache") 479 } 480 481 ams := getDefaultAzureMachineService(machineScope, cache) 482 ams.Reconcile = func(context.Context) error { 483 return azure.WithTransientError(errors.New("failed to reconcile AzureMachine"), 10*time.Second) 484 } 485 ams.Delete = func(context.Context) error { 486 return azure.WithTransientError(errors.New("failed to reconcile AzureMachine"), 10*time.Second) 487 } 488 489 return ams, nil 490 } 491 492 func getFakeAzureMachineServiceWithGeneralError(machineScope *scope.MachineScope) (*azureMachineService, error) { 493 cache, err := resourceskus.GetCache(machineScope, machineScope.Location()) 494 if err != nil { 495 return nil, errors.Wrap(err, "failed creating a NewCache") 496 } 497 498 ams := getDefaultAzureMachineService(machineScope, cache) 499 ams.Reconcile = func(context.Context) error { 500 return errors.New("foo error") 501 } 502 ams.Pause = func(context.Context) error { 503 return errors.New("foo error") 504 } 505 ams.Delete = func(context.Context) error { 506 return errors.New("foo error") 507 } 508 509 return ams, nil 510 } 511 512 func getDefaultAzureMachineService(machineScope *scope.MachineScope, cache *resourceskus.Cache) *azureMachineService { 513 return &azureMachineService{ 514 scope: machineScope, 515 services: []azure.ServiceReconciler{}, 516 skuCache: cache, 517 Reconcile: func(context.Context) error { 518 return nil 519 }, 520 Pause: func(context.Context) error { 521 return nil 522 }, 523 Delete: func(context.Context) error { 524 return nil 525 }, 526 } 527 } 528 529 func getFakeCluster() *clusterv1.Cluster { 530 return &clusterv1.Cluster{ 531 ObjectMeta: metav1.ObjectMeta{ 532 Name: "my-cluster", 533 Namespace: "default", 534 }, 535 Spec: clusterv1.ClusterSpec{ 536 InfrastructureRef: &corev1.ObjectReference{ 537 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 538 Kind: infrav1.AzureClusterKind, 539 Name: "my-azure-cluster", 540 }, 541 }, 542 Status: clusterv1.ClusterStatus{ 543 InfrastructureReady: true, 544 }, 545 } 546 } 547 548 func getFakeAzureCluster(changes ...func(*infrav1.AzureCluster)) *infrav1.AzureCluster { 549 input := &infrav1.AzureCluster{ 550 ObjectMeta: metav1.ObjectMeta{ 551 Name: "my-azure-cluster", 552 Namespace: "default", 553 }, 554 Spec: infrav1.AzureClusterSpec{ 555 AzureClusterClassSpec: infrav1.AzureClusterClassSpec{ 556 SubscriptionID: "123", 557 IdentityRef: &corev1.ObjectReference{ 558 Name: "fake-identity", 559 Namespace: "default", 560 Kind: "AzureClusterIdentity", 561 }, 562 }, 563 NetworkSpec: infrav1.NetworkSpec{ 564 Subnets: infrav1.Subnets{ 565 { 566 SubnetClassSpec: infrav1.SubnetClassSpec{ 567 Name: "node", 568 Role: infrav1.SubnetNode, 569 }, 570 }, 571 }, 572 APIServerLB: infrav1.LoadBalancerSpec{ 573 Name: "my-cluster-public-lb", 574 FrontendIPs: []infrav1.FrontendIP{ 575 { 576 PublicIP: &infrav1.PublicIPSpec{ 577 Name: "my-cluster-public-lb-frontEnd", 578 DNSName: "my-cluster-fb560e20.westus2.cloudapp.azure.com", 579 }, 580 }, 581 }, 582 }, 583 }, 584 ControlPlaneEndpoint: clusterv1.APIEndpoint{ 585 Port: 6443, 586 }, 587 }, 588 } 589 for _, change := range changes { 590 change(input) 591 } 592 593 return input 594 } 595 596 func getFakeAzureMachine(changes ...func(*infrav1.AzureMachine)) *infrav1.AzureMachine { 597 input := &infrav1.AzureMachine{ 598 ObjectMeta: metav1.ObjectMeta{ 599 Name: "my-machine", 600 Namespace: "default", 601 Labels: map[string]string{ 602 clusterv1.ClusterNameLabel: "my-cluster", 603 }, 604 OwnerReferences: []metav1.OwnerReference{ 605 { 606 APIVersion: "cluster.x-k8s.io/v1beta1", 607 Kind: "Machine", 608 Name: "my-machine", 609 }, 610 }, 611 }, 612 Spec: infrav1.AzureMachineSpec{ 613 VMSize: "Standard_D2s_v3", 614 }, 615 } 616 for _, change := range changes { 617 change(input) 618 } 619 620 return input 621 } 622 623 func getFakeAzureClusterIdentity(changes ...func(*infrav1.AzureClusterIdentity)) *infrav1.AzureClusterIdentity { 624 input := &infrav1.AzureClusterIdentity{ 625 ObjectMeta: metav1.ObjectMeta{ 626 Name: "fake-identity", 627 Namespace: "default", 628 }, 629 Spec: infrav1.AzureClusterIdentitySpec{ 630 Type: infrav1.ServicePrincipal, 631 ClientID: "fake-client-id", 632 TenantID: "fake-tenant-id", 633 }, 634 } 635 636 for _, change := range changes { 637 change(input) 638 } 639 640 return input 641 } 642 643 func getFakeMachine(azureMachine *infrav1.AzureMachine, changes ...func(*clusterv1.Machine)) *clusterv1.Machine { 644 input := &clusterv1.Machine{ 645 ObjectMeta: metav1.ObjectMeta{ 646 Name: "my-machine", 647 Namespace: "default", 648 Labels: map[string]string{ 649 clusterv1.ClusterNameLabel: "my-cluster", 650 }, 651 }, 652 TypeMeta: metav1.TypeMeta{ 653 APIVersion: "cluster.x-k8s.io/v1beta1", 654 Kind: "Machine", 655 }, 656 Spec: clusterv1.MachineSpec{ 657 InfrastructureRef: corev1.ObjectReference{ 658 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 659 Kind: "AzureMachine", 660 Name: azureMachine.Name, 661 Namespace: azureMachine.Namespace, 662 }, 663 Version: ptr.To("v1.22.0"), 664 }, 665 } 666 for _, change := range changes { 667 change(input) 668 } 669 670 return input 671 } 672 673 func TestConditions(t *testing.T) { 674 g := NewWithT(t) 675 scheme, err := newScheme() 676 g.Expect(err).NotTo(HaveOccurred()) 677 678 testcases := []struct { 679 name string 680 clusterStatus clusterv1.ClusterStatus 681 machine *clusterv1.Machine 682 azureMachine *infrav1.AzureMachine 683 expectedConditions []clusterv1.Condition 684 }{ 685 { 686 name: "cluster infrastructure is not ready yet", 687 clusterStatus: clusterv1.ClusterStatus{ 688 InfrastructureReady: false, 689 }, 690 machine: &clusterv1.Machine{ 691 ObjectMeta: metav1.ObjectMeta{ 692 Labels: map[string]string{ 693 clusterv1.ClusterNameLabel: "my-cluster", 694 }, 695 Name: "my-machine", 696 }, 697 }, 698 azureMachine: &infrav1.AzureMachine{ 699 ObjectMeta: metav1.ObjectMeta{ 700 Name: "azure-test1", 701 OwnerReferences: []metav1.OwnerReference{ 702 { 703 APIVersion: clusterv1.GroupVersion.String(), 704 Kind: "Machine", 705 Name: "test1", 706 }, 707 }, 708 }, 709 }, 710 expectedConditions: []clusterv1.Condition{{ 711 Type: "VMRunning", 712 Status: corev1.ConditionFalse, 713 Severity: clusterv1.ConditionSeverityInfo, 714 Reason: "WaitingForClusterInfrastructure", 715 }}, 716 }, 717 { 718 name: "bootstrap data secret reference is not yet available", 719 clusterStatus: clusterv1.ClusterStatus{ 720 InfrastructureReady: true, 721 }, 722 machine: &clusterv1.Machine{ 723 ObjectMeta: metav1.ObjectMeta{ 724 Labels: map[string]string{ 725 clusterv1.ClusterNameLabel: "my-cluster", 726 }, 727 Name: "my-machine", 728 }, 729 }, 730 azureMachine: &infrav1.AzureMachine{ 731 ObjectMeta: metav1.ObjectMeta{ 732 Name: "azure-test1", 733 OwnerReferences: []metav1.OwnerReference{ 734 { 735 APIVersion: clusterv1.GroupVersion.String(), 736 Kind: "Machine", 737 Name: "test1", 738 }, 739 }, 740 }, 741 }, 742 expectedConditions: []clusterv1.Condition{{ 743 Type: "VMRunning", 744 Status: corev1.ConditionFalse, 745 Severity: clusterv1.ConditionSeverityInfo, 746 Reason: "WaitingForBootstrapData", 747 }}, 748 }, 749 } 750 for _, tc := range testcases { 751 t.Run(tc.name, func(t *testing.T) { 752 cluster := &clusterv1.Cluster{ 753 ObjectMeta: metav1.ObjectMeta{ 754 Name: "my-cluster", 755 }, 756 Status: tc.clusterStatus, 757 } 758 azureCluster := &infrav1.AzureCluster{ 759 Spec: infrav1.AzureClusterSpec{ 760 AzureClusterClassSpec: infrav1.AzureClusterClassSpec{ 761 SubscriptionID: "123", 762 IdentityRef: &corev1.ObjectReference{ 763 Name: "fake-identity", 764 Namespace: "default", 765 Kind: "AzureClusterIdentity", 766 }, 767 }, 768 }, 769 } 770 azureClusterIdentity := getFakeAzureClusterIdentity() 771 defaultSecret := &corev1.Secret{Data: map[string][]byte{"clientSecret": []byte("fooSecret")}} 772 773 initObjects := []runtime.Object{ 774 cluster, 775 tc.machine, 776 azureCluster, 777 tc.azureMachine, 778 azureClusterIdentity, 779 defaultSecret, 780 } 781 fakeClient := fake.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects(initObjects...).Build() 782 resultIdentity := &infrav1.AzureClusterIdentity{} 783 key := client.ObjectKey{Name: azureClusterIdentity.Name, Namespace: azureClusterIdentity.Namespace} 784 g.Expect(fakeClient.Get(context.TODO(), key, resultIdentity)).To(Succeed()) 785 recorder := record.NewFakeRecorder(10) 786 787 reconciler := NewAzureMachineReconciler(fakeClient, recorder, reconciler.Timeouts{}, "") 788 789 clusterScope, err := scope.NewClusterScope(context.TODO(), scope.ClusterScopeParams{ 790 Client: fakeClient, 791 Cluster: cluster, 792 AzureCluster: azureCluster, 793 }) 794 g.Expect(err).NotTo(HaveOccurred()) 795 796 machineScope, err := scope.NewMachineScope(scope.MachineScopeParams{ 797 Client: fakeClient, 798 ClusterScope: clusterScope, 799 Machine: tc.machine, 800 AzureMachine: tc.azureMachine, 801 Cache: &scope.MachineCache{}, 802 }) 803 g.Expect(err).NotTo(HaveOccurred()) 804 805 _, err = reconciler.reconcileNormal(context.TODO(), machineScope, clusterScope) 806 g.Expect(err).NotTo(HaveOccurred()) 807 808 g.Expect(machineScope.AzureMachine.GetConditions()).To(HaveLen(len(tc.expectedConditions))) 809 for i, c := range machineScope.AzureMachine.GetConditions() { 810 g.Expect(conditionsMatch(c, tc.expectedConditions[i])).To(BeTrue()) 811 } 812 }) 813 } 814 } 815 816 func conditionsMatch(i, j clusterv1.Condition) bool { 817 return i.Type == j.Type && 818 i.Status == j.Status && 819 i.Reason == j.Reason && 820 i.Severity == j.Severity 821 }