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 }