sigs.k8s.io/cluster-api-provider-azure@v1.17.0/exp/controllers/azuremachinepoolmachine_controller_test.go (about)

     1  /*
     2  Copyright 2020 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  	"go.uber.org/mock/gomock"
    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/utils/ptr"
    31  	infrav1 "sigs.k8s.io/cluster-api-provider-azure/api/v1beta1"
    32  	"sigs.k8s.io/cluster-api-provider-azure/azure"
    33  	"sigs.k8s.io/cluster-api-provider-azure/azure/mock_azure"
    34  	"sigs.k8s.io/cluster-api-provider-azure/azure/scope"
    35  	infrav1exp "sigs.k8s.io/cluster-api-provider-azure/exp/api/v1beta1"
    36  	gomock2 "sigs.k8s.io/cluster-api-provider-azure/internal/test/matchers/gomock"
    37  	reconcilerutils "sigs.k8s.io/cluster-api-provider-azure/util/reconciler"
    38  	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
    39  	expv1 "sigs.k8s.io/cluster-api/exp/api/v1beta1"
    40  	ctrl "sigs.k8s.io/controller-runtime"
    41  	"sigs.k8s.io/controller-runtime/pkg/client"
    42  	"sigs.k8s.io/controller-runtime/pkg/client/fake"
    43  )
    44  
    45  func TestAzureMachinePoolMachineReconciler_Reconcile(t *testing.T) {
    46  	cases := []struct {
    47  		Name   string
    48  		Setup  func(cb *fake.ClientBuilder, reconciler *mock_azure.MockReconcilerMockRecorder)
    49  		Verify func(g *WithT, c client.Client, result ctrl.Result, err error)
    50  	}{
    51  		{
    52  			Name: "should successfully reconcile",
    53  			Setup: func(cb *fake.ClientBuilder, reconciler *mock_azure.MockReconcilerMockRecorder) {
    54  				objects := getReadyMachinePoolMachineClusterObjects(false, nil)
    55  				reconciler.Reconcile(gomock2.AContext()).Return(nil)
    56  				cb.WithObjects(objects...)
    57  			},
    58  			Verify: func(g *WithT, c client.Client, result ctrl.Result, err error) {
    59  				g.Expect(err).NotTo(HaveOccurred())
    60  			},
    61  		},
    62  		{
    63  			Name: "should successfully delete",
    64  			Setup: func(cb *fake.ClientBuilder, reconciler *mock_azure.MockReconcilerMockRecorder) {
    65  				objects := getReadyMachinePoolMachineClusterObjects(true, nil)
    66  				reconciler.Delete(gomock2.AContext()).Return(nil)
    67  				cb.WithObjects(objects...)
    68  			},
    69  			Verify: func(g *WithT, c client.Client, result ctrl.Result, err error) {
    70  				g.Expect(err).NotTo(HaveOccurred())
    71  			},
    72  		},
    73  		{
    74  			Name: "should delete Machine if VMSS VM has state Deleting",
    75  			Setup: func(cb *fake.ClientBuilder, reconciler *mock_azure.MockReconcilerMockRecorder) {
    76  				objects := getReadyMachinePoolMachineClusterObjects(false, ptr.To(infrav1.Deleting))
    77  				reconciler.Reconcile(gomock2.AContext()).Return(nil)
    78  				cb.WithObjects(objects...)
    79  			},
    80  			Verify: func(g *WithT, c client.Client, result ctrl.Result, err error) {
    81  				g.Expect(err).NotTo(HaveOccurred())
    82  
    83  				machine := &clusterv1.Machine{}
    84  				err = c.Get(context.Background(), types.NamespacedName{
    85  					Name:      "ma1",
    86  					Namespace: "default",
    87  				}, machine)
    88  				g.Expect(err).To(HaveOccurred())
    89  				g.Expect(err.Error()).Should(ContainSubstring("not found"))
    90  			},
    91  		},
    92  		{
    93  			Name: "should remove finalizer if Machine is not found and AzureMachinePool has deletionTimestamp set",
    94  			Setup: func(cb *fake.ClientBuilder, reconciler *mock_azure.MockReconcilerMockRecorder) {
    95  				objects := getDeletingMachinePoolObjects()
    96  				cb.WithObjects(objects...)
    97  			},
    98  			Verify: func(g *WithT, c client.Client, result ctrl.Result, err error) {
    99  				g.Expect(err).NotTo(HaveOccurred())
   100  
   101  				ampm := &infrav1exp.AzureMachinePoolMachine{}
   102  				err = c.Get(context.Background(), types.NamespacedName{
   103  					Name:      "ampm1",
   104  					Namespace: "default",
   105  				}, ampm)
   106  				g.Expect(err).To(HaveOccurred())
   107  				g.Expect(err.Error()).Should(ContainSubstring("not found"))
   108  			},
   109  		},
   110  	}
   111  
   112  	for _, c := range cases {
   113  		c := c
   114  		t.Run(c.Name, func(t *testing.T) {
   115  			var (
   116  				g          = NewWithT(t)
   117  				mockCtrl   = gomock.NewController(t)
   118  				reconciler = mock_azure.NewMockReconciler(mockCtrl)
   119  				scheme     = func() *runtime.Scheme {
   120  					s := runtime.NewScheme()
   121  					for _, addTo := range []func(s *runtime.Scheme) error{
   122  						clusterv1.AddToScheme,
   123  						expv1.AddToScheme,
   124  						infrav1.AddToScheme,
   125  						infrav1exp.AddToScheme,
   126  						corev1.AddToScheme,
   127  					} {
   128  						g.Expect(addTo(s)).To(Succeed())
   129  					}
   130  
   131  					return s
   132  				}()
   133  				cb = fake.NewClientBuilder().WithScheme(scheme)
   134  			)
   135  			defer mockCtrl.Finish()
   136  
   137  			c.Setup(cb, reconciler.EXPECT())
   138  			cl := cb.Build()
   139  			controller := NewAzureMachinePoolMachineController(cl, nil, reconcilerutils.Timeouts{}, "foo")
   140  			controller.reconcilerFactory = func(_ *scope.MachinePoolMachineScope) (azure.Reconciler, error) {
   141  				return reconciler, nil
   142  			}
   143  			res, err := controller.Reconcile(context.TODO(), ctrl.Request{
   144  				NamespacedName: types.NamespacedName{
   145  					Name:      "ampm1",
   146  					Namespace: "default",
   147  				},
   148  			})
   149  			c.Verify(g, cl, res, err)
   150  		})
   151  	}
   152  }
   153  
   154  func getReadyMachinePoolMachineClusterObjects(ampmIsDeleting bool, ampmProvisioningState *infrav1.ProvisioningState) []client.Object {
   155  	azCluster := &infrav1.AzureCluster{
   156  		TypeMeta: metav1.TypeMeta{
   157  			Kind: "AzureCluster",
   158  		},
   159  		ObjectMeta: metav1.ObjectMeta{
   160  			Name:      "azCluster1",
   161  			Namespace: "default",
   162  		},
   163  		Spec: infrav1.AzureClusterSpec{
   164  			AzureClusterClassSpec: infrav1.AzureClusterClassSpec{
   165  				SubscriptionID: "subID",
   166  				IdentityRef: &corev1.ObjectReference{
   167  					Name:      "fake-identity",
   168  					Namespace: "default",
   169  					Kind:      "AzureClusterIdentity",
   170  				},
   171  			},
   172  		},
   173  	}
   174  
   175  	cluster := &clusterv1.Cluster{
   176  		TypeMeta: metav1.TypeMeta{
   177  			Kind: "Cluster",
   178  		},
   179  		ObjectMeta: metav1.ObjectMeta{
   180  			Name:      "cluster1",
   181  			Namespace: "default",
   182  		},
   183  		Spec: clusterv1.ClusterSpec{
   184  			InfrastructureRef: &corev1.ObjectReference{
   185  				Name:      azCluster.Name,
   186  				Namespace: "default",
   187  				Kind:      "AzureCluster",
   188  			},
   189  		},
   190  		Status: clusterv1.ClusterStatus{
   191  			InfrastructureReady: true,
   192  		},
   193  	}
   194  
   195  	mp := &expv1.MachinePool{
   196  		TypeMeta: metav1.TypeMeta{
   197  			Kind: "MachinePool",
   198  		},
   199  		ObjectMeta: metav1.ObjectMeta{
   200  			Name:      "mp1",
   201  			Namespace: "default",
   202  			Labels: map[string]string{
   203  				"cluster.x-k8s.io/cluster-name": cluster.Name,
   204  			},
   205  		},
   206  	}
   207  
   208  	amp := &infrav1exp.AzureMachinePool{
   209  		TypeMeta: metav1.TypeMeta{
   210  			Kind: "AzureMachinePool",
   211  		},
   212  		ObjectMeta: metav1.ObjectMeta{
   213  			Name:      "amp1",
   214  			Namespace: "default",
   215  			OwnerReferences: []metav1.OwnerReference{
   216  				{
   217  					Name:       mp.Name,
   218  					Kind:       "MachinePool",
   219  					APIVersion: expv1.GroupVersion.String(),
   220  				},
   221  			},
   222  		},
   223  	}
   224  
   225  	ma := &clusterv1.Machine{
   226  		ObjectMeta: metav1.ObjectMeta{
   227  			Name:      "ma1",
   228  			Namespace: "default",
   229  			OwnerReferences: []metav1.OwnerReference{
   230  				{
   231  					Name:       mp.Name,
   232  					Kind:       "MachinePool",
   233  					APIVersion: expv1.GroupVersion.String(),
   234  				},
   235  			},
   236  			Labels: map[string]string{
   237  				"cluster.x-k8s.io/cluster-name": cluster.Name,
   238  			},
   239  		},
   240  	}
   241  
   242  	ampm := &infrav1exp.AzureMachinePoolMachine{
   243  		ObjectMeta: metav1.ObjectMeta{
   244  			Name:       "ampm1",
   245  			Namespace:  "default",
   246  			Finalizers: []string{"test"},
   247  			Labels: map[string]string{
   248  				clusterv1.ClusterNameLabel: cluster.Name,
   249  			},
   250  			OwnerReferences: []metav1.OwnerReference{
   251  				{
   252  					Name:       amp.Name,
   253  					Kind:       infrav1.AzureMachinePoolKind,
   254  					APIVersion: infrav1exp.GroupVersion.String(),
   255  				},
   256  				{
   257  					Name:       ma.Name,
   258  					Kind:       "Machine",
   259  					APIVersion: clusterv1.GroupVersion.String(),
   260  				},
   261  			},
   262  		},
   263  	}
   264  
   265  	fakeIdentity := &infrav1.AzureClusterIdentity{
   266  		ObjectMeta: metav1.ObjectMeta{
   267  			Name:      "fake-identity",
   268  			Namespace: "default",
   269  		},
   270  		Spec: infrav1.AzureClusterIdentitySpec{
   271  			Type: infrav1.ServicePrincipal,
   272  			ClientSecret: corev1.SecretReference{
   273  				Name:      "fooSecret",
   274  				Namespace: "default",
   275  			},
   276  			TenantID: "fake-tenantid",
   277  		},
   278  	}
   279  
   280  	fakeSecret := &corev1.Secret{
   281  		ObjectMeta: metav1.ObjectMeta{
   282  			Name:      "fooSecret",
   283  			Namespace: "default",
   284  		},
   285  		Data: map[string][]byte{
   286  			"clientSecret": []byte("fooSecret"),
   287  		},
   288  	}
   289  
   290  	if ampmIsDeleting {
   291  		ampm.DeletionTimestamp = &metav1.Time{
   292  			Time: time.Now(),
   293  		}
   294  	}
   295  	if ampmProvisioningState != nil {
   296  		ampm.Status = infrav1exp.AzureMachinePoolMachineStatus{
   297  			ProvisioningState: ampmProvisioningState,
   298  		}
   299  	}
   300  
   301  	return []client.Object{cluster, azCluster, mp, amp, ma, ampm, fakeIdentity, fakeSecret}
   302  }
   303  
   304  func getDeletingMachinePoolObjects() []client.Object {
   305  	azCluster := &infrav1.AzureCluster{
   306  		TypeMeta: metav1.TypeMeta{
   307  			Kind: "AzureCluster",
   308  		},
   309  		ObjectMeta: metav1.ObjectMeta{
   310  			Name:      "azCluster1",
   311  			Namespace: "default",
   312  		},
   313  		Spec: infrav1.AzureClusterSpec{
   314  			AzureClusterClassSpec: infrav1.AzureClusterClassSpec{
   315  				SubscriptionID: "subID",
   316  				IdentityRef: &corev1.ObjectReference{
   317  					Name:      "fake-identity",
   318  					Namespace: "default",
   319  					Kind:      "AzureClusterIdentity",
   320  				},
   321  			},
   322  		},
   323  	}
   324  
   325  	cluster := &clusterv1.Cluster{
   326  		TypeMeta: metav1.TypeMeta{
   327  			Kind: "Cluster",
   328  		},
   329  		ObjectMeta: metav1.ObjectMeta{
   330  			Name:      "cluster1",
   331  			Namespace: "default",
   332  		},
   333  		Spec: clusterv1.ClusterSpec{
   334  			InfrastructureRef: &corev1.ObjectReference{
   335  				Name:      azCluster.Name,
   336  				Namespace: "default",
   337  				Kind:      "AzureCluster",
   338  			},
   339  		},
   340  		Status: clusterv1.ClusterStatus{
   341  			InfrastructureReady: true,
   342  		},
   343  	}
   344  
   345  	mp := &expv1.MachinePool{
   346  		TypeMeta: metav1.TypeMeta{
   347  			Kind: "MachinePool",
   348  		},
   349  		ObjectMeta: metav1.ObjectMeta{
   350  			Name:       "mp1",
   351  			Namespace:  "default",
   352  			Finalizers: []string{"test"},
   353  			DeletionTimestamp: &metav1.Time{
   354  				Time: time.Now(),
   355  			},
   356  			Labels: map[string]string{
   357  				"cluster.x-k8s.io/cluster-name": cluster.Name,
   358  			},
   359  		},
   360  	}
   361  
   362  	amp := &infrav1exp.AzureMachinePool{
   363  		TypeMeta: metav1.TypeMeta{
   364  			Kind: "AzureMachinePool",
   365  		},
   366  		ObjectMeta: metav1.ObjectMeta{
   367  			Name:       "amp1",
   368  			Namespace:  "default",
   369  			Finalizers: []string{"test"},
   370  			DeletionTimestamp: &metav1.Time{
   371  				Time: time.Now(),
   372  			},
   373  			OwnerReferences: []metav1.OwnerReference{
   374  				{
   375  					Name:       mp.Name,
   376  					Kind:       "MachinePool",
   377  					APIVersion: expv1.GroupVersion.String(),
   378  				},
   379  			},
   380  		},
   381  	}
   382  
   383  	ampm := &infrav1exp.AzureMachinePoolMachine{
   384  		ObjectMeta: metav1.ObjectMeta{
   385  			Name:      "ampm1",
   386  			Namespace: "default",
   387  			DeletionTimestamp: &metav1.Time{
   388  				Time: time.Now(),
   389  			},
   390  			Finalizers: []string{infrav1exp.AzureMachinePoolMachineFinalizer},
   391  			Labels: map[string]string{
   392  				clusterv1.ClusterNameLabel: cluster.Name,
   393  			},
   394  			OwnerReferences: []metav1.OwnerReference{
   395  				{
   396  					Name:       amp.Name,
   397  					Kind:       infrav1.AzureMachinePoolKind,
   398  					APIVersion: infrav1exp.GroupVersion.String(),
   399  				},
   400  			},
   401  		},
   402  	}
   403  
   404  	fakeIdentity := &infrav1.AzureClusterIdentity{
   405  		ObjectMeta: metav1.ObjectMeta{
   406  			Name:      "fake-identity",
   407  			Namespace: "default",
   408  		},
   409  		Spec: infrav1.AzureClusterIdentitySpec{
   410  			Type: infrav1.ServicePrincipal,
   411  			ClientSecret: corev1.SecretReference{
   412  				Name:      "fooSecret",
   413  				Namespace: "default",
   414  			},
   415  			TenantID: "fake-tenantid",
   416  		},
   417  	}
   418  
   419  	fakeSecret := &corev1.Secret{
   420  		ObjectMeta: metav1.ObjectMeta{
   421  			Name:      "fooSecret",
   422  			Namespace: "default",
   423  		},
   424  		Data: map[string][]byte{
   425  			"clientSecret": []byte("fooSecret"),
   426  		},
   427  	}
   428  
   429  	return []client.Object{cluster, azCluster, mp, amp, ampm, fakeIdentity, fakeSecret}
   430  }