sigs.k8s.io/cluster-api-provider-azure@v1.14.3/test/e2e/azure_machinepools.go (about)

     1  //go:build e2e
     2  // +build e2e
     3  
     4  /*
     5  Copyright 2023 The Kubernetes Authors.
     6  
     7  Licensed under the Apache License, Version 2.0 (the "License");
     8  you may not use this file except in compliance with the License.
     9  You may obtain a copy of the License at
    10  
    11      http://www.apache.org/licenses/LICENSE-2.0
    12  
    13  Unless required by applicable law or agreed to in writing, software
    14  distributed under the License is distributed on an "AS IS" BASIS,
    15  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    16  See the License for the specific language governing permissions and
    17  limitations under the License.
    18  */
    19  
    20  package e2e
    21  
    22  import (
    23  	"context"
    24  	"sync"
    25  
    26  	"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v5"
    27  	. "github.com/onsi/ginkgo/v2"
    28  	. "github.com/onsi/gomega"
    29  	corev1 "k8s.io/api/core/v1"
    30  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    31  	"k8s.io/apimachinery/pkg/labels"
    32  	"k8s.io/apimachinery/pkg/selection"
    33  	"k8s.io/utils/ptr"
    34  	infrav1 "sigs.k8s.io/cluster-api-provider-azure/api/v1beta1"
    35  	infrav1exp "sigs.k8s.io/cluster-api-provider-azure/exp/api/v1beta1"
    36  	azureutil "sigs.k8s.io/cluster-api-provider-azure/util/azure"
    37  	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
    38  	expv1 "sigs.k8s.io/cluster-api/exp/api/v1beta1"
    39  	"sigs.k8s.io/cluster-api/test/framework"
    40  	"sigs.k8s.io/controller-runtime/pkg/client"
    41  )
    42  
    43  const (
    44  	AzureMachinePoolsSpecName = "azure-machinepools"
    45  	regexpFlexibleVM          = `^azure:\/\/\/subscriptions\/[0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}\/resourceGroups\/.+\/providers\/Microsoft.Compute\/virtualMachines\/.+$`
    46  	regexpUniformInstance     = `^azure:\/\/\/subscriptions\/[0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}\/resourceGroups\/.+\/providers\/Microsoft.Compute\/virtualMachineScaleSets\/.+\/virtualMachines\/\d+$`
    47  )
    48  
    49  // AzureMachinePoolsSpecInput is the input for AzureMachinePoolsSpec.
    50  type (
    51  	AzureMachinePoolsSpecInput struct {
    52  		Cluster               *clusterv1.Cluster
    53  		BootstrapClusterProxy framework.ClusterProxy
    54  		Namespace             *corev1.Namespace
    55  		ClusterName           string
    56  		WaitIntervals         []interface{}
    57  	}
    58  )
    59  
    60  // AzureMachinePoolsSpec tests that the expected machinepool resources exist.
    61  func AzureMachinePoolsSpec(ctx context.Context, inputGetter func() AzureMachinePoolsSpecInput) {
    62  	input := inputGetter()
    63  	Expect(input.Cluster).NotTo(BeNil(), "Invalid argument. input.Cluster can't be nil when calling %s spec", AzureMachinePoolsSpecName)
    64  	Expect(input.BootstrapClusterProxy).NotTo(BeNil(), "Invalid argument. input.BootstrapClusterProxy can't be nil when calling %s spec", AzureMachinePoolsSpecName)
    65  	Expect(input.Namespace).NotTo(BeNil(), "Invalid argument. input.Namespace can't be nil when calling %s spec", AzureMachinePoolsSpecName)
    66  	Expect(input.ClusterName).NotTo(BeEmpty(), "Invalid argument. input.ClusterName can't be empty when calling %s spec", AzureMachinePoolsSpecName)
    67  	Expect(input.WaitIntervals).NotTo(BeEmpty(), "Invalid argument. input.WaitIntervals can't be empty when calling %s spec", AzureMachinePoolsSpecName)
    68  
    69  	var (
    70  		bootstrapClusterProxy = input.BootstrapClusterProxy
    71  		workloadClusterProxy  = bootstrapClusterProxy.GetWorkloadCluster(ctx, input.Namespace.Name, input.ClusterName)
    72  		clusterLabels         = map[string]string{clusterv1.ClusterNameLabel: workloadClusterProxy.GetName()}
    73  	)
    74  
    75  	Expect(workloadClusterProxy).NotTo(BeNil())
    76  	mgmtClient := bootstrapClusterProxy.GetClient()
    77  	Expect(mgmtClient).NotTo(BeNil())
    78  
    79  	Byf("listing AzureMachinePools for cluster %s in namespace %s", input.ClusterName, input.Namespace.Name)
    80  	ampList := &infrav1exp.AzureMachinePoolList{}
    81  	Expect(mgmtClient.List(ctx, ampList, client.InNamespace(input.Namespace.Name), client.MatchingLabels(clusterLabels))).To(Succeed())
    82  	Expect(ampList.Items).NotTo(BeEmpty())
    83  	machinepools := []*expv1.MachinePool{}
    84  	for _, amp := range ampList.Items {
    85  		Byf("checking AzureMachinePool %s in %s orchestration mode", amp.Name, amp.Spec.OrchestrationMode)
    86  		Expect(amp.Status.Replicas).To(BeNumerically("==", len(amp.Spec.ProviderIDList)))
    87  		for _, providerID := range amp.Spec.ProviderIDList {
    88  			switch amp.Spec.OrchestrationMode {
    89  			case infrav1.OrchestrationModeType(armcompute.OrchestrationModeFlexible):
    90  				Expect(providerID).To(MatchRegexp(regexpFlexibleVM))
    91  			default:
    92  				Expect(providerID).To(MatchRegexp(regexpUniformInstance))
    93  			}
    94  		}
    95  		mp, err := azureutil.FindParentMachinePool(amp.Name, bootstrapClusterProxy.GetClient())
    96  		Expect(err).NotTo(HaveOccurred())
    97  		Expect(mp).NotTo(BeNil())
    98  		machinepools = append(machinepools, mp)
    99  	}
   100  
   101  	var wg sync.WaitGroup
   102  	for _, mp := range machinepools {
   103  		goalReplicas := ptr.Deref[int32](mp.Spec.Replicas, 0) + 1
   104  		Byf("Scaling machine pool %s out from %d to %d", mp.Name, *mp.Spec.Replicas, goalReplicas)
   105  		wg.Add(1)
   106  		go func(mp *expv1.MachinePool) {
   107  			defer GinkgoRecover()
   108  			defer wg.Done()
   109  			framework.ScaleMachinePoolAndWait(ctx, framework.ScaleMachinePoolAndWaitInput{
   110  				ClusterProxy:              bootstrapClusterProxy,
   111  				Cluster:                   input.Cluster,
   112  				Replicas:                  goalReplicas,
   113  				MachinePools:              []*expv1.MachinePool{mp},
   114  				WaitForMachinePoolToScale: input.WaitIntervals,
   115  			})
   116  		}(mp)
   117  	}
   118  	wg.Wait()
   119  
   120  	for _, mp := range machinepools {
   121  		goalReplicas := ptr.Deref[int32](mp.Spec.Replicas, 0) - 1
   122  		Byf("Scaling machine pool %s in from %d to %d", mp.Name, *mp.Spec.Replicas, goalReplicas)
   123  		wg.Add(1)
   124  		go func(mp *expv1.MachinePool) {
   125  			defer GinkgoRecover()
   126  			defer wg.Done()
   127  			framework.ScaleMachinePoolAndWait(ctx, framework.ScaleMachinePoolAndWaitInput{
   128  				ClusterProxy:              bootstrapClusterProxy,
   129  				Cluster:                   input.Cluster,
   130  				Replicas:                  goalReplicas,
   131  				MachinePools:              []*expv1.MachinePool{mp},
   132  				WaitForMachinePoolToScale: input.WaitIntervals,
   133  			})
   134  		}(mp)
   135  	}
   136  	wg.Wait()
   137  
   138  	By("verifying that workload nodes are schedulable")
   139  	clientset := workloadClusterProxy.GetClientSet()
   140  	Expect(clientset).NotTo(BeNil())
   141  	workloadNodeRequirement, err := labels.NewRequirement("node-role.kubernetes.io/control-plane", selection.DoesNotExist, nil)
   142  	Expect(err).NotTo(HaveOccurred())
   143  	selector := labels.NewSelector().Add(*workloadNodeRequirement)
   144  	nodeList, err := clientset.CoreV1().Nodes().List(ctx, metav1.ListOptions{LabelSelector: selector.String()})
   145  	Expect(err).NotTo(HaveOccurred())
   146  	Expect(nodeList.Items).NotTo(BeEmpty())
   147  	for _, node := range nodeList.Items {
   148  		for _, taint := range node.Spec.Taints {
   149  			Expect(taint.Effect).NotTo(BeElementOf(
   150  				corev1.TaintEffectNoSchedule, corev1.TaintEffectPreferNoSchedule, corev1.TaintEffectNoExecute),
   151  				"node %s has %s taint", node.Name, taint.Effect)
   152  		}
   153  	}
   154  }