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  }