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 }