sigs.k8s.io/cluster-api-provider-azure@v1.14.3/controllers/azuremanagedmachinepool_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/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v5" 25 . "github.com/onsi/gomega" 26 "github.com/pkg/errors" 27 "go.uber.org/mock/gomock" 28 corev1 "k8s.io/api/core/v1" 29 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 30 "k8s.io/apimachinery/pkg/runtime" 31 "k8s.io/apimachinery/pkg/types" 32 "k8s.io/client-go/kubernetes/scheme" 33 "k8s.io/utils/ptr" 34 infrav1 "sigs.k8s.io/cluster-api-provider-azure/api/v1beta1" 35 "sigs.k8s.io/cluster-api-provider-azure/azure" 36 "sigs.k8s.io/cluster-api-provider-azure/azure/mock_azure" 37 "sigs.k8s.io/cluster-api-provider-azure/azure/scope" 38 "sigs.k8s.io/cluster-api-provider-azure/azure/services/agentpools" 39 "sigs.k8s.io/cluster-api-provider-azure/azure/services/agentpools/mock_agentpools" 40 gomock2 "sigs.k8s.io/cluster-api-provider-azure/internal/test/matchers/gomock" 41 reconcilerutils "sigs.k8s.io/cluster-api-provider-azure/util/reconciler" 42 clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" 43 expv1 "sigs.k8s.io/cluster-api/exp/api/v1beta1" 44 ctrl "sigs.k8s.io/controller-runtime" 45 "sigs.k8s.io/controller-runtime/pkg/client/fake" 46 ) 47 48 func TestAzureManagedMachinePoolReconcile(t *testing.T) { 49 type pausingReconciler struct { 50 *mock_azure.MockReconciler 51 *mock_azure.MockPauser 52 } 53 54 cases := []struct { 55 name string 56 Setup func(cb *fake.ClientBuilder, reconciler pausingReconciler, agentpools *mock_agentpools.MockAgentPoolScopeMockRecorder, nodelister *MockNodeListerMockRecorder) 57 Verify func(g *WithT, result ctrl.Result, err error) 58 }{ 59 { 60 name: "Reconcile succeed", 61 Setup: func(cb *fake.ClientBuilder, reconciler pausingReconciler, agentpools *mock_agentpools.MockAgentPoolScopeMockRecorder, nodelister *MockNodeListerMockRecorder) { 62 cluster, azManagedCluster, azManagedControlPlane, ammp, mp := newReadyAzureManagedMachinePoolCluster() 63 fakeAgentPoolSpec := fakeAgentPool() 64 providerIDs := []string{"azure:///subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/myresourcegroupname/providers/Microsoft.Compute/virtualMachineScaleSets/myScaleSetName/virtualMachines/156"} 65 fakeVirtualMachineScaleSet := fakeVirtualMachineScaleSet() 66 fakeVirtualMachineScaleSetVM := fakeVirtualMachineScaleSetVM() 67 68 reconciler.MockReconciler.EXPECT().Reconcile(gomock2.AContext()).Return(nil) 69 agentpools.SetSubnetName() 70 agentpools.AgentPoolSpec().Return(&fakeAgentPoolSpec) 71 agentpools.NodeResourceGroup().Return("fake-rg") 72 agentpools.SetAgentPoolProviderIDList(providerIDs) 73 agentpools.SetAgentPoolReplicas(int32(len(providerIDs))).Return() 74 agentpools.SetAgentPoolReady(true).Return() 75 agentpools.IsPreviewEnabled().Return(false) 76 77 nodelister.List(gomock2.AContext(), "fake-rg").Return(fakeVirtualMachineScaleSet, nil) 78 nodelister.ListInstances(gomock2.AContext(), "fake-rg", "vmssName").Return(fakeVirtualMachineScaleSetVM, nil) 79 80 cb.WithObjects(cluster, azManagedCluster, azManagedControlPlane, ammp, mp) 81 }, 82 Verify: func(g *WithT, result ctrl.Result, err error) { 83 g.Expect(err).NotTo(HaveOccurred()) 84 }, 85 }, 86 { 87 name: "Reconcile pause", 88 Setup: func(cb *fake.ClientBuilder, reconciler pausingReconciler, agentpools *mock_agentpools.MockAgentPoolScopeMockRecorder, nodelister *MockNodeListerMockRecorder) { 89 cluster, azManagedCluster, azManagedControlPlane, ammp, mp := newReadyAzureManagedMachinePoolCluster() 90 cluster.Spec.Paused = true 91 92 reconciler.MockPauser.EXPECT().Pause(gomock2.AContext()).Return(nil) 93 94 cb.WithObjects(cluster, azManagedCluster, azManagedControlPlane, ammp, mp) 95 }, 96 Verify: func(g *WithT, result ctrl.Result, err error) { 97 g.Expect(err).NotTo(HaveOccurred()) 98 }, 99 }, 100 { 101 name: "Reconcile delete", 102 Setup: func(cb *fake.ClientBuilder, reconciler pausingReconciler, _ *mock_agentpools.MockAgentPoolScopeMockRecorder, _ *MockNodeListerMockRecorder) { 103 cluster, azManagedCluster, azManagedControlPlane, ammp, mp := newReadyAzureManagedMachinePoolCluster() 104 reconciler.MockReconciler.EXPECT().Delete(gomock2.AContext()).Return(nil) 105 ammp.DeletionTimestamp = &metav1.Time{ 106 Time: time.Now(), 107 } 108 cb.WithObjects(cluster, azManagedCluster, azManagedControlPlane, ammp, mp) 109 }, 110 Verify: func(g *WithT, result ctrl.Result, err error) { 111 g.Expect(err).NotTo(HaveOccurred()) 112 }, 113 }, 114 { 115 name: "Reconcile delete transient error", 116 Setup: func(cb *fake.ClientBuilder, reconciler pausingReconciler, agentpools *mock_agentpools.MockAgentPoolScopeMockRecorder, _ *MockNodeListerMockRecorder) { 117 cluster, azManagedCluster, azManagedControlPlane, ammp, mp := newReadyAzureManagedMachinePoolCluster() 118 reconciler.MockReconciler.EXPECT().Delete(gomock2.AContext()).Return(azure.WithTransientError(errors.New("transient"), 76*time.Second)) 119 agentpools.Name() 120 ammp.DeletionTimestamp = &metav1.Time{ 121 Time: time.Now(), 122 } 123 cb.WithObjects(cluster, azManagedCluster, azManagedControlPlane, ammp, mp) 124 }, 125 Verify: func(g *WithT, result ctrl.Result, err error) { 126 g.Expect(err).NotTo(HaveOccurred()) 127 g.Expect(result.RequeueAfter).To(Equal(76 * time.Second)) 128 }, 129 }, 130 } 131 132 for _, c := range cases { 133 c := c 134 t.Run(c.name, func(t *testing.T) { 135 var ( 136 g = NewWithT(t) 137 mockCtrl = gomock.NewController(t) 138 reconciler = pausingReconciler{ 139 MockReconciler: mock_azure.NewMockReconciler(mockCtrl), 140 MockPauser: mock_azure.NewMockPauser(mockCtrl), 141 } 142 agentpools = mock_agentpools.NewMockAgentPoolScope(mockCtrl) 143 nodelister = NewMockNodeLister(mockCtrl) 144 fakeIdentity = &infrav1.AzureClusterIdentity{ 145 ObjectMeta: metav1.ObjectMeta{ 146 Name: "fake-identity", 147 Namespace: "default", 148 }, 149 Spec: infrav1.AzureClusterIdentitySpec{ 150 Type: infrav1.ServicePrincipal, 151 TenantID: "fake-tenantid", 152 }, 153 } 154 fakeSecret = &corev1.Secret{Data: map[string][]byte{"clientSecret": []byte("fooSecret")}} 155 initObjects = []runtime.Object{fakeIdentity, fakeSecret} 156 scheme = func() *runtime.Scheme { 157 s := runtime.NewScheme() 158 for _, addTo := range []func(s *runtime.Scheme) error{ 159 scheme.AddToScheme, 160 clusterv1.AddToScheme, 161 expv1.AddToScheme, 162 infrav1.AddToScheme, 163 corev1.AddToScheme, 164 } { 165 g.Expect(addTo(s)).To(Succeed()) 166 } 167 168 return s 169 }() 170 cb = fake.NewClientBuilder(). 171 WithStatusSubresource( 172 &infrav1.AzureManagedMachinePool{}, 173 ). 174 WithRuntimeObjects(initObjects...). 175 WithScheme(scheme) 176 ) 177 defer mockCtrl.Finish() 178 179 c.Setup(cb, reconciler, agentpools.EXPECT(), nodelister.EXPECT()) 180 controller := NewAzureManagedMachinePoolReconciler(cb.Build(), nil, reconcilerutils.Timeouts{}, "foo") 181 controller.createAzureManagedMachinePoolService = func(_ *scope.ManagedMachinePoolScope, _ time.Duration) (*azureManagedMachinePoolService, error) { 182 return &azureManagedMachinePoolService{ 183 scope: agentpools, 184 agentPoolsSvc: reconciler, 185 scaleSetsSvc: nodelister, 186 }, nil 187 } 188 res, err := controller.Reconcile(context.TODO(), ctrl.Request{ 189 NamespacedName: types.NamespacedName{ 190 Name: "foo-ammp", 191 Namespace: "foobar", 192 }, 193 }) 194 c.Verify(g, res, err) 195 }) 196 } 197 } 198 199 func newReadyAzureManagedMachinePoolCluster() (*clusterv1.Cluster, *infrav1.AzureManagedCluster, *infrav1.AzureManagedControlPlane, *infrav1.AzureManagedMachinePool, *expv1.MachinePool) { 200 // AzureManagedCluster 201 azManagedCluster := &infrav1.AzureManagedCluster{ 202 ObjectMeta: metav1.ObjectMeta{ 203 Name: "foo-azManagedCluster", 204 Namespace: "foobar", 205 OwnerReferences: []metav1.OwnerReference{ 206 { 207 Name: "foo-cluster", 208 Kind: "Cluster", 209 APIVersion: "cluster.x-k8s.io/v1beta1", 210 }, 211 }, 212 }, 213 Spec: infrav1.AzureManagedClusterSpec{ 214 ControlPlaneEndpoint: clusterv1.APIEndpoint{ 215 Host: "foo.bar", 216 Port: 123, 217 }, 218 }, 219 } 220 // AzureManagedControlPlane 221 azManagedControlPlane := &infrav1.AzureManagedControlPlane{ 222 ObjectMeta: metav1.ObjectMeta{ 223 Name: "foo-azManagedControlPlane", 224 Namespace: "foobar", 225 OwnerReferences: []metav1.OwnerReference{ 226 { 227 Name: "foo-cluster", 228 Kind: "Cluster", 229 APIVersion: "cluster.x-k8s.io/v1beta1", 230 }, 231 }, 232 }, 233 Spec: infrav1.AzureManagedControlPlaneSpec{ 234 ControlPlaneEndpoint: clusterv1.APIEndpoint{ 235 Host: "foo.bar", 236 Port: 123, 237 }, 238 AzureManagedControlPlaneClassSpec: infrav1.AzureManagedControlPlaneClassSpec{ 239 IdentityRef: &corev1.ObjectReference{ 240 Name: "fake-identity", 241 Namespace: "default", 242 Kind: "AzureClusterIdentity", 243 }, 244 }, 245 }, 246 Status: infrav1.AzureManagedControlPlaneStatus{ 247 Ready: true, 248 Initialized: true, 249 }, 250 } 251 // Cluster 252 cluster := &clusterv1.Cluster{ 253 ObjectMeta: metav1.ObjectMeta{ 254 Name: "foo-cluster", 255 Namespace: "foobar", 256 }, 257 Spec: clusterv1.ClusterSpec{ 258 ControlPlaneRef: &corev1.ObjectReference{ 259 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 260 Kind: infrav1.AzureManagedControlPlaneKind, 261 Name: azManagedControlPlane.Name, 262 Namespace: azManagedControlPlane.Namespace, 263 }, 264 InfrastructureRef: &corev1.ObjectReference{ 265 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 266 Kind: infrav1.AzureManagedClusterKind, 267 Name: azManagedCluster.Name, 268 Namespace: azManagedCluster.Namespace, 269 }, 270 }, 271 } 272 // AzureManagedMachinePool 273 ammp := &infrav1.AzureManagedMachinePool{ 274 ObjectMeta: metav1.ObjectMeta{ 275 Name: "foo-ammp", 276 Namespace: "foobar", 277 Finalizers: []string{"test"}, 278 OwnerReferences: []metav1.OwnerReference{ 279 { 280 Name: "foo-mp1", 281 Kind: "MachinePool", 282 APIVersion: "cluster.x-k8s.io/v1beta1", 283 }, 284 }, 285 }, 286 } 287 // MachinePool 288 mp := &expv1.MachinePool{ 289 ObjectMeta: metav1.ObjectMeta{ 290 Name: "foo-mp1", 291 Namespace: "foobar", 292 Labels: map[string]string{ 293 "cluster.x-k8s.io/cluster-name": cluster.Name, 294 }, 295 OwnerReferences: []metav1.OwnerReference{ 296 { 297 APIVersion: "cluster.x-k8s.io/v1beta1", 298 Kind: "Cluster", 299 Name: "foo-cluster", 300 }, 301 }, 302 }, 303 Spec: expv1.MachinePoolSpec{ 304 Template: clusterv1.MachineTemplateSpec{ 305 Spec: clusterv1.MachineSpec{ 306 ClusterName: cluster.Name, 307 InfrastructureRef: corev1.ObjectReference{ 308 APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", 309 Kind: "AzureManagedMachinePool", 310 Name: ammp.Name, 311 Namespace: ammp.Namespace, 312 }, 313 }, 314 }, 315 }, 316 } 317 318 return cluster, azManagedCluster, azManagedControlPlane, ammp, mp 319 } 320 321 func fakeAgentPool(changes ...func(*agentpools.AgentPoolSpec)) agentpools.AgentPoolSpec { 322 pool := agentpools.AgentPoolSpec{ 323 Name: "fake-agent-pool-name", 324 AzureName: "fake-agent-pool-name", 325 ResourceGroup: "fake-rg", 326 Cluster: "fake-cluster", 327 AvailabilityZones: []string{"fake-zone"}, 328 EnableAutoScaling: true, 329 EnableUltraSSD: ptr.To(true), 330 KubeletDiskType: (*infrav1.KubeletDiskType)(ptr.To("fake-kubelet-disk-type")), 331 MaxCount: ptr.To(5), 332 MaxPods: ptr.To(10), 333 MinCount: ptr.To(1), 334 Mode: "fake-mode", 335 NodeLabels: map[string]string{"fake-label": "fake-value"}, 336 NodeTaints: []string{"fake-taint"}, 337 OSDiskSizeGB: 2, 338 OsDiskType: ptr.To("fake-os-disk-type"), 339 OSType: ptr.To("fake-os-type"), 340 Replicas: 1, 341 SKU: "fake-sku", 342 Version: ptr.To("fake-version"), 343 VnetSubnetID: "fake-vnet-subnet-id", 344 AdditionalTags: infrav1.Tags{"fake": "tag"}, 345 } 346 347 for _, change := range changes { 348 change(&pool) 349 } 350 351 return pool 352 } 353 354 func fakeVirtualMachineScaleSetVM() []armcompute.VirtualMachineScaleSetVM { 355 virtualMachineScaleSetVM := []armcompute.VirtualMachineScaleSetVM{ 356 { 357 InstanceID: ptr.To("0"), 358 ID: ptr.To("/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/myResourceGroupName/providers/Microsoft.Compute/virtualMachineScaleSets/myScaleSetName/virtualMachines/156"), 359 Name: ptr.To("vm0"), 360 Zones: []*string{ptr.To("zone0")}, 361 Properties: &armcompute.VirtualMachineScaleSetVMProperties{ 362 ProvisioningState: ptr.To("Succeeded"), 363 OSProfile: &armcompute.OSProfile{ 364 ComputerName: ptr.To("instance-000000"), 365 }, 366 }, 367 }, 368 } 369 return virtualMachineScaleSetVM 370 } 371 372 func fakeVirtualMachineScaleSet() []armcompute.VirtualMachineScaleSet { 373 tags := map[string]*string{ 374 "foo": ptr.To("bazz"), 375 "poolName": ptr.To("fake-agent-pool-name"), 376 } 377 zones := []string{"zone0", "zone1"} 378 virtualMachineScaleSet := []armcompute.VirtualMachineScaleSet{ 379 { 380 SKU: &armcompute.SKU{ 381 Name: ptr.To("skuName"), 382 Tier: ptr.To("skuTier"), 383 Capacity: ptr.To[int64](2), 384 }, 385 Zones: azure.PtrSlice(&zones), 386 ID: ptr.To("vmssID"), 387 Name: ptr.To("vmssName"), 388 Location: ptr.To("westus2"), 389 Tags: tags, 390 Properties: &armcompute.VirtualMachineScaleSetProperties{ 391 SinglePlacementGroup: ptr.To(false), 392 ProvisioningState: ptr.To("Succeeded"), 393 }, 394 }, 395 } 396 return virtualMachineScaleSet 397 }