sigs.k8s.io/cluster-api-provider-azure@v1.14.3/controllers/azuremanagedmachinepool_reconciler.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  	"fmt"
    22  	"time"
    23  
    24  	"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v5"
    25  	asocontainerservicev1preview "github.com/Azure/azure-service-operator/v2/api/containerservice/v1api20230202preview"
    26  	asocontainerservicev1 "github.com/Azure/azure-service-operator/v2/api/containerservice/v1api20231001"
    27  	"github.com/pkg/errors"
    28  	azprovider "sigs.k8s.io/cloud-provider-azure/pkg/provider"
    29  	"sigs.k8s.io/cluster-api-provider-azure/azure"
    30  	"sigs.k8s.io/cluster-api-provider-azure/azure/scope"
    31  	"sigs.k8s.io/cluster-api-provider-azure/azure/services/agentpools"
    32  	"sigs.k8s.io/cluster-api-provider-azure/azure/services/scalesets"
    33  	azureutil "sigs.k8s.io/cluster-api-provider-azure/util/azure"
    34  	"sigs.k8s.io/cluster-api-provider-azure/util/tele"
    35  )
    36  
    37  type (
    38  	// azureManagedMachinePoolService contains the services required by the cluster controller.
    39  	azureManagedMachinePoolService struct {
    40  		scope         agentpools.AgentPoolScope
    41  		agentPoolsSvc azure.Reconciler
    42  		scaleSetsSvc  NodeLister
    43  	}
    44  
    45  	// AgentPoolVMSSNotFoundError represents a reconcile error when the VMSS for an agent pool can't be found.
    46  	AgentPoolVMSSNotFoundError struct {
    47  		NodeResourceGroup string
    48  		PoolName          string
    49  	}
    50  
    51  	// NodeLister is a service interface for returning generic lists.
    52  	NodeLister interface {
    53  		ListInstances(context.Context, string, string) ([]armcompute.VirtualMachineScaleSetVM, error)
    54  		List(context.Context, string) ([]armcompute.VirtualMachineScaleSet, error)
    55  	}
    56  )
    57  
    58  // NewAgentPoolVMSSNotFoundError creates a new AgentPoolVMSSNotFoundError.
    59  func NewAgentPoolVMSSNotFoundError(nodeResourceGroup, poolName string) *AgentPoolVMSSNotFoundError {
    60  	return &AgentPoolVMSSNotFoundError{
    61  		NodeResourceGroup: nodeResourceGroup,
    62  		PoolName:          poolName,
    63  	}
    64  }
    65  
    66  func (a *AgentPoolVMSSNotFoundError) Error() string {
    67  	msgFmt := "failed to find vm scale set in resource group %s matching pool named %s"
    68  	return fmt.Sprintf(msgFmt, a.NodeResourceGroup, a.PoolName)
    69  }
    70  
    71  // Is returns true if the target error is an `AgentPoolVMSSNotFoundError`.
    72  func (a *AgentPoolVMSSNotFoundError) Is(target error) bool {
    73  	var err *AgentPoolVMSSNotFoundError
    74  	ok := errors.As(target, &err)
    75  	return ok
    76  }
    77  
    78  // newAzureManagedMachinePoolService populates all the services based on input scope.
    79  func newAzureManagedMachinePoolService(scope *scope.ManagedMachinePoolScope, apiCallTimeout time.Duration) (*azureManagedMachinePoolService, error) {
    80  	scaleSetAuthorizer, err := scaleSetAuthorizer(scope)
    81  	if err != nil {
    82  		return nil, err
    83  	}
    84  	scaleSetsClient, err := scalesets.NewClient(scaleSetAuthorizer, apiCallTimeout)
    85  	if err != nil {
    86  		return nil, err
    87  	}
    88  	return &azureManagedMachinePoolService{
    89  		scope:         scope,
    90  		agentPoolsSvc: agentpools.New(scope),
    91  		scaleSetsSvc:  scaleSetsClient,
    92  	}, nil
    93  }
    94  
    95  // scaleSetAuthorizer takes a scope and determines if a regional authorizer is needed for scale sets
    96  // see https://github.com/kubernetes-sigs/cluster-api-provider-azure/pull/1850 for context on region based authorizer.
    97  func scaleSetAuthorizer(scope *scope.ManagedMachinePoolScope) (azure.Authorizer, error) {
    98  	if scope.ControlPlane.Spec.AzureEnvironment == azure.PublicCloudName {
    99  		return azure.WithRegionalBaseURI(scope, scope.Location()) // public cloud supports regional end points
   100  	}
   101  
   102  	return scope, nil
   103  }
   104  
   105  // Reconcile reconciles all the services in a predetermined order.
   106  func (s *azureManagedMachinePoolService) Reconcile(ctx context.Context) error {
   107  	ctx, log, done := tele.StartSpanWithLogger(ctx, "controllers.azureManagedMachinePoolService.Reconcile")
   108  	defer done()
   109  
   110  	s.scope.SetSubnetName()
   111  
   112  	log.Info("reconciling managed machine pool")
   113  	agentPool, err := s.scope.AgentPoolSpec().Parameters(ctx, nil)
   114  	if err != nil {
   115  		return errors.Wrap(err, "failed to get agent pool parameters")
   116  	}
   117  	var agentPoolName string
   118  	if s.scope.IsPreviewEnabled() {
   119  		agentPoolTyped := agentPool.(*asocontainerservicev1preview.ManagedClustersAgentPool)
   120  		agentPoolName = agentPoolTyped.AzureName()
   121  	} else {
   122  		agentPoolTyped := agentPool.(*asocontainerservicev1.ManagedClustersAgentPool)
   123  		agentPoolName = agentPoolTyped.AzureName()
   124  	}
   125  
   126  	if err := s.agentPoolsSvc.Reconcile(ctx); err != nil {
   127  		return errors.Wrapf(err, "failed to reconcile machine pool %s", agentPoolName)
   128  	}
   129  
   130  	nodeResourceGroup := s.scope.NodeResourceGroup()
   131  	vmss, err := s.scaleSetsSvc.List(ctx, nodeResourceGroup)
   132  	if err != nil {
   133  		return errors.Wrapf(err, "failed to list vmss in resource group %s", nodeResourceGroup)
   134  	}
   135  
   136  	var match *armcompute.VirtualMachineScaleSet
   137  	for _, ss := range vmss {
   138  		ss := ss
   139  		if ss.Tags["poolName"] != nil && *ss.Tags["poolName"] == agentPoolName {
   140  			match = &ss
   141  			break
   142  		}
   143  
   144  		if ss.Tags["aks-managed-poolName"] != nil && *ss.Tags["aks-managed-poolName"] == agentPoolName {
   145  			match = &ss
   146  			break
   147  		}
   148  	}
   149  
   150  	if match == nil {
   151  		return azure.WithTransientError(NewAgentPoolVMSSNotFoundError(nodeResourceGroup, agentPoolName), 20*time.Second)
   152  	}
   153  
   154  	instances, err := s.scaleSetsSvc.ListInstances(ctx, nodeResourceGroup, *match.Name)
   155  	if err != nil {
   156  		return errors.Wrapf(err, "failed to reconcile machine pool %s", agentPoolName)
   157  	}
   158  
   159  	var providerIDs = make([]string, len(instances))
   160  	for i := 0; i < len(instances); i++ {
   161  		// Transform the VMSS instance resource representation to conform to the cloud-provider-azure representation
   162  		providerID, err := azprovider.ConvertResourceGroupNameToLower(azureutil.ProviderIDPrefix + *instances[i].ID)
   163  		if err != nil {
   164  			return errors.Wrapf(err, "failed to parse instance ID %s", *instances[i].ID)
   165  		}
   166  		providerIDs[i] = providerID
   167  	}
   168  
   169  	s.scope.SetAgentPoolProviderIDList(providerIDs)
   170  	s.scope.SetAgentPoolReplicas(int32(len(providerIDs)))
   171  	s.scope.SetAgentPoolReady(true)
   172  
   173  	log.Info("reconciled managed machine pool successfully")
   174  	return nil
   175  }
   176  
   177  // Pause pauses all components making up the machine pool.
   178  func (s *azureManagedMachinePoolService) Pause(ctx context.Context) error {
   179  	ctx, _, done := tele.StartSpanWithLogger(ctx, "controllers.azureManagedMachinePoolService.Pause")
   180  	defer done()
   181  
   182  	pauser, ok := s.agentPoolsSvc.(azure.Pauser)
   183  	if !ok {
   184  		return nil
   185  	}
   186  	if err := pauser.Pause(ctx); err != nil {
   187  		return errors.Wrapf(err, "failed to pause machine pool %s", s.scope.Name())
   188  	}
   189  
   190  	return nil
   191  }
   192  
   193  // Delete reconciles all the services in a predetermined order.
   194  func (s *azureManagedMachinePoolService) Delete(ctx context.Context) error {
   195  	ctx, _, done := tele.StartSpanWithLogger(ctx, "controllers.azureManagedMachinePoolService.Delete")
   196  	defer done()
   197  
   198  	if err := s.agentPoolsSvc.Delete(ctx); err != nil {
   199  		return errors.Wrapf(err, "failed to delete machine pool %s", s.scope.Name())
   200  	}
   201  
   202  	return nil
   203  }