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 }