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 }