sigs.k8s.io/cluster-api-provider-azure@v1.14.3/controllers/azurecluster_reconciler_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  	"errors"
    22  	"testing"
    23  	"time"
    24  
    25  	"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v5"
    26  	asoresourcesv1 "github.com/Azure/azure-service-operator/v2/api/resources/v1api20200601"
    27  	asoannotations "github.com/Azure/azure-service-operator/v2/pkg/common/annotations"
    28  	. "github.com/onsi/gomega"
    29  	"go.uber.org/mock/gomock"
    30  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    31  	"k8s.io/apimachinery/pkg/runtime"
    32  	"k8s.io/utils/ptr"
    33  	infrav1 "sigs.k8s.io/cluster-api-provider-azure/api/v1beta1"
    34  	"sigs.k8s.io/cluster-api-provider-azure/azure"
    35  	"sigs.k8s.io/cluster-api-provider-azure/azure/mock_azure"
    36  	"sigs.k8s.io/cluster-api-provider-azure/azure/scope"
    37  	"sigs.k8s.io/cluster-api-provider-azure/azure/services/groups"
    38  	"sigs.k8s.io/cluster-api-provider-azure/azure/services/resourceskus"
    39  	"sigs.k8s.io/cluster-api-provider-azure/azure/services/vnetpeerings"
    40  	gomockinternal "sigs.k8s.io/cluster-api-provider-azure/internal/test/matchers/gomock"
    41  	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
    42  	"sigs.k8s.io/controller-runtime/pkg/client"
    43  	fakeclient "sigs.k8s.io/controller-runtime/pkg/client/fake"
    44  )
    45  
    46  func TestAzureClusterServiceReconcile(t *testing.T) {
    47  	cases := map[string]struct {
    48  		expectedError string
    49  		expect        func(one *mock_azure.MockServiceReconcilerMockRecorder, two *mock_azure.MockServiceReconcilerMockRecorder, three *mock_azure.MockServiceReconcilerMockRecorder)
    50  	}{
    51  		"all services are reconciled in order": {
    52  			expectedError: "",
    53  			expect: func(one *mock_azure.MockServiceReconcilerMockRecorder, two *mock_azure.MockServiceReconcilerMockRecorder, three *mock_azure.MockServiceReconcilerMockRecorder) {
    54  				gomock.InOrder(
    55  					one.Reconcile(gomockinternal.AContext()).Return(nil),
    56  					two.Reconcile(gomockinternal.AContext()).Return(nil),
    57  					three.Reconcile(gomockinternal.AContext()).Return(nil))
    58  			},
    59  		},
    60  		"service reconcile fails": {
    61  			expectedError: "failed to reconcile AzureCluster service two: some error happened",
    62  			expect: func(one *mock_azure.MockServiceReconcilerMockRecorder, two *mock_azure.MockServiceReconcilerMockRecorder, three *mock_azure.MockServiceReconcilerMockRecorder) {
    63  				gomock.InOrder(
    64  					one.Reconcile(gomockinternal.AContext()).Return(nil),
    65  					two.Reconcile(gomockinternal.AContext()).Return(errors.New("some error happened")),
    66  					two.Name().Return("two"))
    67  			},
    68  		},
    69  	}
    70  
    71  	for name, tc := range cases {
    72  		tc := tc
    73  		t.Run(name, func(t *testing.T) {
    74  			g := NewWithT(t)
    75  
    76  			t.Parallel()
    77  			mockCtrl := gomock.NewController(t)
    78  			defer mockCtrl.Finish()
    79  			svcOneMock := mock_azure.NewMockServiceReconciler(mockCtrl)
    80  			svcTwoMock := mock_azure.NewMockServiceReconciler(mockCtrl)
    81  			svcThreeMock := mock_azure.NewMockServiceReconciler(mockCtrl)
    82  
    83  			tc.expect(svcOneMock.EXPECT(), svcTwoMock.EXPECT(), svcThreeMock.EXPECT())
    84  
    85  			s := &azureClusterService{
    86  				scope: &scope.ClusterScope{
    87  					Cluster:      &clusterv1.Cluster{},
    88  					AzureCluster: &infrav1.AzureCluster{},
    89  				},
    90  				services: []azure.ServiceReconciler{
    91  					svcOneMock,
    92  					svcTwoMock,
    93  					svcThreeMock,
    94  				},
    95  				skuCache: resourceskus.NewStaticCache([]armcompute.ResourceSKU{}, ""),
    96  			}
    97  
    98  			err := s.reconcile(context.TODO())
    99  			if tc.expectedError != "" {
   100  				g.Expect(err).To(HaveOccurred())
   101  				g.Expect(err).To(MatchError(tc.expectedError))
   102  			} else {
   103  				g.Expect(err).NotTo(HaveOccurred())
   104  			}
   105  		})
   106  	}
   107  }
   108  
   109  func TestAzureClusterServicePause(t *testing.T) {
   110  	type pausingServiceReconciler struct {
   111  		*mock_azure.MockServiceReconciler
   112  		*mock_azure.MockPauser
   113  	}
   114  
   115  	cases := map[string]struct {
   116  		expectedError string
   117  		expect        func(one pausingServiceReconciler, two pausingServiceReconciler, three pausingServiceReconciler)
   118  	}{
   119  		"all services are paused in order": {
   120  			expectedError: "",
   121  			expect: func(one pausingServiceReconciler, two pausingServiceReconciler, three pausingServiceReconciler) {
   122  				gomock.InOrder(
   123  					one.MockPauser.EXPECT().Pause(gomockinternal.AContext()).Return(nil),
   124  					two.MockPauser.EXPECT().Pause(gomockinternal.AContext()).Return(nil),
   125  					three.MockPauser.EXPECT().Pause(gomockinternal.AContext()).Return(nil))
   126  			},
   127  		},
   128  		"service pause fails": {
   129  			expectedError: "failed to pause AzureCluster service two: some error happened",
   130  			expect: func(one pausingServiceReconciler, two pausingServiceReconciler, _ pausingServiceReconciler) {
   131  				gomock.InOrder(
   132  					one.MockPauser.EXPECT().Pause(gomockinternal.AContext()).Return(nil),
   133  					two.MockPauser.EXPECT().Pause(gomockinternal.AContext()).Return(errors.New("some error happened")),
   134  					two.MockServiceReconciler.EXPECT().Name().Return("two"))
   135  			},
   136  		},
   137  	}
   138  
   139  	for name, tc := range cases {
   140  		tc := tc
   141  		t.Run(name, func(t *testing.T) {
   142  			g := NewWithT(t)
   143  
   144  			t.Parallel()
   145  			mockCtrl := gomock.NewController(t)
   146  			defer mockCtrl.Finish()
   147  
   148  			newPausingServiceReconciler := func() pausingServiceReconciler {
   149  				return pausingServiceReconciler{
   150  					mock_azure.NewMockServiceReconciler(mockCtrl),
   151  					mock_azure.NewMockPauser(mockCtrl),
   152  				}
   153  			}
   154  			svcOneMock := newPausingServiceReconciler()
   155  			svcTwoMock := newPausingServiceReconciler()
   156  			svcThreeMock := newPausingServiceReconciler()
   157  
   158  			tc.expect(svcOneMock, svcTwoMock, svcThreeMock)
   159  
   160  			s := &azureClusterService{
   161  				services: []azure.ServiceReconciler{
   162  					svcOneMock,
   163  					svcTwoMock,
   164  					svcThreeMock,
   165  				},
   166  			}
   167  
   168  			err := s.pause(context.TODO())
   169  			if tc.expectedError != "" {
   170  				g.Expect(err).To(HaveOccurred())
   171  				g.Expect(err).To(MatchError(tc.expectedError))
   172  			} else {
   173  				g.Expect(err).NotTo(HaveOccurred())
   174  			}
   175  		})
   176  	}
   177  }
   178  
   179  func TestAzureClusterServiceDelete(t *testing.T) {
   180  	clusterName := "cluster"
   181  	azClusterName := "azCluster"
   182  	namespace := "ns"
   183  	resourceGroup := "rg"
   184  
   185  	ownerRefs := []metav1.OwnerReference{
   186  		{
   187  			APIVersion:         infrav1.GroupVersion.String(),
   188  			Kind:               infrav1.AzureClusterKind,
   189  			Name:               azClusterName,
   190  			Controller:         ptr.To(true),
   191  			BlockOwnerDeletion: ptr.To(true),
   192  		},
   193  	}
   194  
   195  	cases := map[string]struct {
   196  		expectedError string
   197  		clientBuilder func(g Gomega) client.Client
   198  		expect        func(grp *mock_azure.MockServiceReconcilerMockRecorder, vpr *mock_azure.MockServiceReconcilerMockRecorder, one *mock_azure.MockServiceReconcilerMockRecorder, two *mock_azure.MockServiceReconcilerMockRecorder, three *mock_azure.MockServiceReconcilerMockRecorder)
   199  	}{
   200  		"Resource Group is deleted successfully": {
   201  			expectedError: "",
   202  			clientBuilder: func(g Gomega) client.Client {
   203  				scheme := runtime.NewScheme()
   204  				g.Expect(infrav1.AddToScheme(scheme)).To(Succeed())
   205  				g.Expect(asoresourcesv1.AddToScheme(scheme)).To(Succeed())
   206  
   207  				rg := &asoresourcesv1.ResourceGroup{
   208  					ObjectMeta: metav1.ObjectMeta{
   209  						Name:            resourceGroup,
   210  						Namespace:       namespace,
   211  						OwnerReferences: ownerRefs,
   212  						Annotations: map[string]string{
   213  							asoannotations.ReconcilePolicy: string(asoannotations.ReconcilePolicyManage),
   214  						},
   215  					},
   216  				}
   217  
   218  				c := fakeclient.NewClientBuilder().
   219  					WithScheme(scheme).
   220  					WithObjects(rg).
   221  					Build()
   222  
   223  				return c
   224  			},
   225  			expect: func(grp *mock_azure.MockServiceReconcilerMockRecorder, vpr *mock_azure.MockServiceReconcilerMockRecorder, _ *mock_azure.MockServiceReconcilerMockRecorder, _ *mock_azure.MockServiceReconcilerMockRecorder, _ *mock_azure.MockServiceReconcilerMockRecorder) {
   226  				gomock.InOrder(
   227  					grp.Name().Return(groups.ServiceName),
   228  					vpr.Name().Return(vnetpeerings.ServiceName),
   229  					vpr.Delete(gomockinternal.AContext()).Return(nil),
   230  					grp.Name().Return(groups.ServiceName),
   231  					grp.Delete(gomockinternal.AContext()).Return(nil))
   232  			},
   233  		},
   234  		"Resource Group delete fails": {
   235  			expectedError: "failed to delete resource group: internal error",
   236  			clientBuilder: func(g Gomega) client.Client {
   237  				scheme := runtime.NewScheme()
   238  				g.Expect(infrav1.AddToScheme(scheme)).To(Succeed())
   239  				g.Expect(asoresourcesv1.AddToScheme(scheme)).To(Succeed())
   240  
   241  				rg := &asoresourcesv1.ResourceGroup{
   242  					ObjectMeta: metav1.ObjectMeta{
   243  						Name:            resourceGroup,
   244  						Namespace:       namespace,
   245  						OwnerReferences: ownerRefs,
   246  						Annotations: map[string]string{
   247  							asoannotations.ReconcilePolicy: string(asoannotations.ReconcilePolicyManage),
   248  						},
   249  					},
   250  				}
   251  
   252  				c := fakeclient.NewClientBuilder().
   253  					WithScheme(scheme).
   254  					WithObjects(rg).
   255  					Build()
   256  
   257  				return c
   258  			},
   259  			expect: func(grp *mock_azure.MockServiceReconcilerMockRecorder, vpr *mock_azure.MockServiceReconcilerMockRecorder, _ *mock_azure.MockServiceReconcilerMockRecorder, _ *mock_azure.MockServiceReconcilerMockRecorder, _ *mock_azure.MockServiceReconcilerMockRecorder) {
   260  				gomock.InOrder(
   261  					grp.Name().Return(groups.ServiceName),
   262  					vpr.Name().Return(vnetpeerings.ServiceName),
   263  					vpr.Delete(gomockinternal.AContext()).Return(nil),
   264  					grp.Name().Return(groups.ServiceName),
   265  					grp.Delete(gomockinternal.AContext()).Return(errors.New("internal error")))
   266  			},
   267  		},
   268  		"Resource Group not owned by cluster": {
   269  			expectedError: "",
   270  			clientBuilder: func(g Gomega) client.Client {
   271  				scheme := runtime.NewScheme()
   272  				g.Expect(infrav1.AddToScheme(scheme)).To(Succeed())
   273  				g.Expect(asoresourcesv1.AddToScheme(scheme)).To(Succeed())
   274  
   275  				rg := &asoresourcesv1.ResourceGroup{
   276  					ObjectMeta: metav1.ObjectMeta{
   277  						Name:            resourceGroup,
   278  						Namespace:       namespace,
   279  						OwnerReferences: []metav1.OwnerReference{},
   280  					},
   281  				}
   282  
   283  				c := fakeclient.NewClientBuilder().
   284  					WithScheme(scheme).
   285  					WithObjects(rg).
   286  					Build()
   287  
   288  				return c
   289  			},
   290  			expect: func(grp *mock_azure.MockServiceReconcilerMockRecorder, vpr *mock_azure.MockServiceReconcilerMockRecorder, one *mock_azure.MockServiceReconcilerMockRecorder, two *mock_azure.MockServiceReconcilerMockRecorder, three *mock_azure.MockServiceReconcilerMockRecorder) {
   291  				gomock.InOrder(
   292  					three.Delete(gomockinternal.AContext()).Return(nil),
   293  					two.Delete(gomockinternal.AContext()).Return(nil),
   294  					one.Delete(gomockinternal.AContext()).Return(nil),
   295  					vpr.Delete(gomockinternal.AContext()).Return(nil),
   296  					grp.Delete(gomockinternal.AContext()).Return(nil))
   297  			},
   298  		},
   299  		"service delete fails": {
   300  			expectedError: "failed to delete AzureCluster service two: some error happened",
   301  			clientBuilder: func(g Gomega) client.Client {
   302  				scheme := runtime.NewScheme()
   303  				g.Expect(infrav1.AddToScheme(scheme)).To(Succeed())
   304  				g.Expect(asoresourcesv1.AddToScheme(scheme)).To(Succeed())
   305  
   306  				rg := &asoresourcesv1.ResourceGroup{
   307  					ObjectMeta: metav1.ObjectMeta{
   308  						Name:            resourceGroup,
   309  						Namespace:       namespace,
   310  						OwnerReferences: []metav1.OwnerReference{},
   311  					},
   312  				}
   313  
   314  				c := fakeclient.NewClientBuilder().
   315  					WithScheme(scheme).
   316  					WithObjects(rg).
   317  					Build()
   318  
   319  				return c
   320  			},
   321  			expect: func(_ *mock_azure.MockServiceReconcilerMockRecorder, _ *mock_azure.MockServiceReconcilerMockRecorder, _ *mock_azure.MockServiceReconcilerMockRecorder, two *mock_azure.MockServiceReconcilerMockRecorder, three *mock_azure.MockServiceReconcilerMockRecorder) {
   322  				gomock.InOrder(
   323  					three.Delete(gomockinternal.AContext()).Return(nil),
   324  					two.Delete(gomockinternal.AContext()).Return(errors.New("some error happened")),
   325  					two.Name().Return("two"))
   326  			},
   327  		},
   328  	}
   329  
   330  	for name, tc := range cases {
   331  		tc := tc
   332  		t.Run(name, func(t *testing.T) {
   333  			g := NewWithT(t)
   334  
   335  			t.Parallel()
   336  			mockCtrl := gomock.NewController(t)
   337  			defer mockCtrl.Finish()
   338  			groupsMock := mock_azure.NewMockServiceReconciler(mockCtrl)
   339  			vnetpeeringsMock := mock_azure.NewMockServiceReconciler(mockCtrl)
   340  			svcOneMock := mock_azure.NewMockServiceReconciler(mockCtrl)
   341  			svcTwoMock := mock_azure.NewMockServiceReconciler(mockCtrl)
   342  			svcThreeMock := mock_azure.NewMockServiceReconciler(mockCtrl)
   343  
   344  			tc.expect(groupsMock.EXPECT(), vnetpeeringsMock.EXPECT(), svcOneMock.EXPECT(), svcTwoMock.EXPECT(), svcThreeMock.EXPECT())
   345  			c := tc.clientBuilder(g)
   346  
   347  			s := &azureClusterService{
   348  				scope: &scope.ClusterScope{
   349  					Client: c,
   350  					AzureCluster: &infrav1.AzureCluster{
   351  						ObjectMeta: metav1.ObjectMeta{
   352  							Name:      azClusterName,
   353  							Namespace: namespace,
   354  						},
   355  						Spec: infrav1.AzureClusterSpec{
   356  							ResourceGroup: resourceGroup,
   357  							NetworkSpec: infrav1.NetworkSpec{
   358  								Vnet: infrav1.VnetSpec{
   359  									ResourceGroup: resourceGroup,
   360  								},
   361  							},
   362  						},
   363  					},
   364  					Cluster: &clusterv1.Cluster{
   365  						ObjectMeta: metav1.ObjectMeta{
   366  							Name:              clusterName,
   367  							Namespace:         namespace,
   368  							DeletionTimestamp: &metav1.Time{Time: time.Now()},
   369  						},
   370  					},
   371  				},
   372  				services: []azure.ServiceReconciler{
   373  					groupsMock,
   374  					vnetpeeringsMock,
   375  					svcOneMock,
   376  					svcTwoMock,
   377  					svcThreeMock,
   378  				},
   379  				skuCache: resourceskus.NewStaticCache([]armcompute.ResourceSKU{}, ""),
   380  			}
   381  
   382  			err := s.delete(context.TODO())
   383  			if tc.expectedError != "" {
   384  				g.Expect(err).To(HaveOccurred())
   385  				g.Expect(err).To(MatchError(tc.expectedError))
   386  			} else {
   387  				g.Expect(err).NotTo(HaveOccurred())
   388  			}
   389  		})
   390  	}
   391  }