sigs.k8s.io/cluster-api-provider-azure@v1.17.0/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  	asocontainerservicev1 "github.com/Azure/azure-service-operator/v2/api/containerservice/v1api20231001"
    26  	asocontainerservicev1preview "github.com/Azure/azure-service-operator/v2/api/containerservice/v1api20231102preview"
    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  		agentPoolName = agentPool.(*asocontainerservicev1preview.ManagedClustersAgentPool).AzureName()
   120  	} else {
   121  		agentPoolName = agentPool.(*asocontainerservicev1.ManagedClustersAgentPool).AzureName()
   122  	}
   123  
   124  	if err := s.agentPoolsSvc.Reconcile(ctx); err != nil {
   125  		return errors.Wrapf(err, "failed to reconcile machine pool %s", agentPoolName)
   126  	}
   127  
   128  	nodeResourceGroup := s.scope.NodeResourceGroup()
   129  	vmss, err := s.scaleSetsSvc.List(ctx, nodeResourceGroup)
   130  	if err != nil {
   131  		return errors.Wrapf(err, "failed to list vmss in resource group %s", nodeResourceGroup)
   132  	}
   133  
   134  	var match *armcompute.VirtualMachineScaleSet
   135  	for _, ss := range vmss {
   136  		ss := ss
   137  		if ss.Tags["poolName"] != nil && *ss.Tags["poolName"] == agentPoolName {
   138  			match = &ss
   139  			break
   140  		}
   141  
   142  		if ss.Tags["aks-managed-poolName"] != nil && *ss.Tags["aks-managed-poolName"] == agentPoolName {
   143  			match = &ss
   144  			break
   145  		}
   146  	}
   147  
   148  	if match == nil {
   149  		return azure.WithTransientError(NewAgentPoolVMSSNotFoundError(nodeResourceGroup, agentPoolName), 20*time.Second)
   150  	}
   151  
   152  	instances, err := s.scaleSetsSvc.ListInstances(ctx, nodeResourceGroup, *match.Name)
   153  	if err != nil {
   154  		return errors.Wrapf(err, "failed to reconcile machine pool %s", agentPoolName)
   155  	}
   156  
   157  	var providerIDs = make([]string, len(instances))
   158  	for i := 0; i < len(instances); i++ {
   159  		// Transform the VMSS instance resource representation to conform to the cloud-provider-azure representation
   160  		providerID, err := azprovider.ConvertResourceGroupNameToLower(azureutil.ProviderIDPrefix + *instances[i].ID)
   161  		if err != nil {
   162  			return errors.Wrapf(err, "failed to parse instance ID %s", *instances[i].ID)
   163  		}
   164  		providerIDs[i] = providerID
   165  	}
   166  
   167  	s.scope.SetAgentPoolProviderIDList(providerIDs)
   168  	s.scope.SetAgentPoolReplicas(int32(len(providerIDs)))
   169  	s.scope.SetAgentPoolReady(true)
   170  
   171  	log.Info("reconciled managed machine pool successfully")
   172  	return nil
   173  }
   174  
   175  // Pause pauses all components making up the machine pool.
   176  func (s *azureManagedMachinePoolService) Pause(ctx context.Context) error {
   177  	ctx, _, done := tele.StartSpanWithLogger(ctx, "controllers.azureManagedMachinePoolService.Pause")
   178  	defer done()
   179  
   180  	pauser, ok := s.agentPoolsSvc.(azure.Pauser)
   181  	if !ok {
   182  		return nil
   183  	}
   184  	if err := pauser.Pause(ctx); err != nil {
   185  		return errors.Wrapf(err, "failed to pause machine pool %s", s.scope.Name())
   186  	}
   187  
   188  	return nil
   189  }
   190  
   191  // Delete reconciles all the services in a predetermined order.
   192  func (s *azureManagedMachinePoolService) Delete(ctx context.Context) error {
   193  	ctx, _, done := tele.StartSpanWithLogger(ctx, "controllers.azureManagedMachinePoolService.Delete")
   194  	defer done()
   195  
   196  	if err := s.agentPoolsSvc.Delete(ctx); err != nil {
   197  		return errors.Wrapf(err, "failed to delete machine pool %s", s.scope.Name())
   198  	}
   199  
   200  	return nil
   201  }