sigs.k8s.io/cluster-api-provider-azure@v1.14.3/azure/scope/managedmachinepool.go (about) 1 /* 2 Copyright 2022 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 scope 18 19 import ( 20 "context" 21 "fmt" 22 "strings" 23 24 "github.com/Azure/azure-service-operator/v2/pkg/genruntime" 25 "github.com/pkg/errors" 26 "k8s.io/utils/ptr" 27 infrav1 "sigs.k8s.io/cluster-api-provider-azure/api/v1beta1" 28 "sigs.k8s.io/cluster-api-provider-azure/azure" 29 "sigs.k8s.io/cluster-api-provider-azure/azure/services/agentpools" 30 "sigs.k8s.io/cluster-api-provider-azure/util/futures" 31 "sigs.k8s.io/cluster-api-provider-azure/util/tele" 32 "sigs.k8s.io/cluster-api-provider-azure/util/versions" 33 clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" 34 expv1 "sigs.k8s.io/cluster-api/exp/api/v1beta1" 35 "sigs.k8s.io/cluster-api/util/conditions" 36 "sigs.k8s.io/cluster-api/util/patch" 37 "sigs.k8s.io/controller-runtime/pkg/client" 38 ) 39 40 // ManagedMachinePoolScopeParams defines the input parameters used to create a new managed 41 // control plane. 42 type ManagedMachinePoolScopeParams struct { 43 ManagedMachinePool 44 Client client.Client 45 Cluster *clusterv1.Cluster 46 ControlPlane *infrav1.AzureManagedControlPlane 47 ManagedControlPlaneScope azure.ManagedClusterScoper 48 } 49 50 // ManagedMachinePool defines the scope interface for a managed machine pool. 51 type ManagedMachinePool struct { 52 InfraMachinePool *infrav1.AzureManagedMachinePool 53 MachinePool *expv1.MachinePool 54 } 55 56 // NewManagedMachinePoolScope creates a new Scope from the supplied parameters. 57 // This is meant to be called for each reconcile iteration. 58 func NewManagedMachinePoolScope(ctx context.Context, params ManagedMachinePoolScopeParams) (*ManagedMachinePoolScope, error) { 59 _, _, done := tele.StartSpanWithLogger(ctx, "scope.NewManagedMachinePoolScope") 60 defer done() 61 62 if params.Cluster == nil { 63 return nil, errors.New("failed to generate new scope from nil Cluster") 64 } 65 66 if params.ControlPlane == nil { 67 return nil, errors.New("failed to generate new scope from nil ControlPlane") 68 } 69 70 helper, err := patch.NewHelper(params.InfraMachinePool, params.Client) 71 if err != nil { 72 return nil, errors.Wrap(err, "failed to init patch helper") 73 } 74 75 capiMachinePoolPatchHelper, err := patch.NewHelper(params.MachinePool, params.Client) 76 if err != nil { 77 return nil, errors.Wrap(err, "failed to init patch helper") 78 } 79 80 return &ManagedMachinePoolScope{ 81 Client: params.Client, 82 Cluster: params.Cluster, 83 ControlPlane: params.ControlPlane, 84 MachinePool: params.MachinePool, 85 InfraMachinePool: params.InfraMachinePool, 86 patchHelper: helper, 87 capiMachinePoolPatchHelper: capiMachinePoolPatchHelper, 88 ManagedClusterScoper: params.ManagedControlPlaneScope, 89 }, nil 90 } 91 92 // ManagedMachinePoolScope defines the basic context for an actuator to operate upon. 93 type ManagedMachinePoolScope struct { 94 Client client.Client 95 patchHelper *patch.Helper 96 capiMachinePoolPatchHelper *patch.Helper 97 98 azure.ManagedClusterScoper 99 Cluster *clusterv1.Cluster 100 MachinePool *expv1.MachinePool 101 ControlPlane *infrav1.AzureManagedControlPlane 102 InfraMachinePool *infrav1.AzureManagedMachinePool 103 } 104 105 // PatchObject persists the cluster configuration and status. 106 func (s *ManagedMachinePoolScope) PatchObject(ctx context.Context) error { 107 ctx, _, done := tele.StartSpanWithLogger(ctx, "scope.ManagedMachinePoolScope.PatchObject") 108 defer done() 109 110 conditions.SetSummary(s.InfraMachinePool) 111 112 return s.patchHelper.Patch( 113 ctx, 114 s.InfraMachinePool, 115 patch.WithOwnedConditions{Conditions: []clusterv1.ConditionType{ 116 clusterv1.ReadyCondition, 117 }}) 118 } 119 120 // Close closes the current scope persisting the cluster configuration and status. 121 func (s *ManagedMachinePoolScope) Close(ctx context.Context) error { 122 ctx, _, done := tele.StartSpanWithLogger(ctx, "scope.ManagedMachinePoolScope.Close") 123 defer done() 124 125 return s.PatchObject(ctx) 126 } 127 128 // GetClient implements aso.Scope. 129 func (s *ManagedMachinePoolScope) GetClient() client.Client { 130 return s.Client 131 } 132 133 // ASOOwner implements aso.Scope. 134 func (s *ManagedMachinePoolScope) ASOOwner() client.Object { 135 return s.InfraMachinePool 136 } 137 138 // Name returns the name of the infra machine pool. 139 func (s *ManagedMachinePoolScope) Name() string { 140 return s.InfraMachinePool.Name 141 } 142 143 // SetSubnetName updates AzureManagedMachinePool.SubnetName if AzureManagedMachinePool.SubnetName is empty with s.ControlPlane.Spec.VirtualNetwork.Subnet.Name. 144 func (s *ManagedMachinePoolScope) SetSubnetName() { 145 s.InfraMachinePool.Spec.SubnetName = getAgentPoolSubnet(s.ControlPlane, s.InfraMachinePool) 146 } 147 148 // AgentPoolSpec returns an azure.ResourceSpecGetter for currently reconciled AzureManagedMachinePool. 149 func (s *ManagedMachinePoolScope) AgentPoolSpec() azure.ASOResourceSpecGetter[genruntime.MetaObject] { 150 return buildAgentPoolSpec(s.ControlPlane, s.MachinePool, s.InfraMachinePool) 151 } 152 153 func getAgentPoolSubnet(controlPlane *infrav1.AzureManagedControlPlane, infraMachinePool *infrav1.AzureManagedMachinePool) *string { 154 if infraMachinePool.Spec.SubnetName == nil { 155 return ptr.To(controlPlane.Spec.VirtualNetwork.Subnet.Name) 156 } 157 return infraMachinePool.Spec.SubnetName 158 } 159 160 func buildAgentPoolSpec(managedControlPlane *infrav1.AzureManagedControlPlane, 161 machinePool *expv1.MachinePool, 162 managedMachinePool *infrav1.AzureManagedMachinePool) azure.ASOResourceSpecGetter[genruntime.MetaObject] { 163 normalizedVersion := getManagedMachinePoolVersion(managedControlPlane, machinePool) 164 165 replicas := int32(1) 166 if machinePool.Spec.Replicas != nil { 167 replicas = *machinePool.Spec.Replicas 168 } 169 170 agentPoolSpec := &agentpools.AgentPoolSpec{ 171 Name: managedMachinePool.Name, 172 AzureName: ptr.Deref(managedMachinePool.Spec.Name, ""), 173 ResourceGroup: managedControlPlane.Spec.ResourceGroupName, 174 Cluster: managedControlPlane.Name, 175 SKU: managedMachinePool.Spec.SKU, 176 Replicas: int(replicas), 177 Version: normalizedVersion, 178 OSType: managedMachinePool.Spec.OSType, 179 VnetSubnetID: azure.SubnetID( 180 managedControlPlane.Spec.SubscriptionID, 181 managedControlPlane.Spec.VirtualNetwork.ResourceGroup, 182 managedControlPlane.Spec.VirtualNetwork.Name, 183 ptr.Deref(getAgentPoolSubnet(managedControlPlane, managedMachinePool), ""), 184 ), 185 Mode: managedMachinePool.Spec.Mode, 186 MaxPods: managedMachinePool.Spec.MaxPods, 187 AvailabilityZones: managedMachinePool.Spec.AvailabilityZones, 188 OsDiskType: managedMachinePool.Spec.OsDiskType, 189 EnableUltraSSD: managedMachinePool.Spec.EnableUltraSSD, 190 EnableNodePublicIP: managedMachinePool.Spec.EnableNodePublicIP, 191 NodePublicIPPrefixID: ptr.Deref(managedMachinePool.Spec.NodePublicIPPrefixID, ""), 192 ScaleSetPriority: managedMachinePool.Spec.ScaleSetPriority, 193 ScaleDownMode: managedMachinePool.Spec.ScaleDownMode, 194 SpotMaxPrice: managedMachinePool.Spec.SpotMaxPrice, 195 AdditionalTags: managedMachinePool.Spec.AdditionalTags, 196 KubeletDiskType: managedMachinePool.Spec.KubeletDiskType, 197 LinuxOSConfig: managedMachinePool.Spec.LinuxOSConfig, 198 EnableFIPS: managedMachinePool.Spec.EnableFIPS, 199 EnableEncryptionAtHost: managedMachinePool.Spec.EnableEncryptionAtHost, 200 Patches: managedMachinePool.Spec.ASOManagedClustersAgentPoolPatches, 201 Preview: ptr.Deref(managedControlPlane.Spec.EnablePreviewFeatures, false), 202 } 203 204 if managedMachinePool.Spec.OSDiskSizeGB != nil { 205 agentPoolSpec.OSDiskSizeGB = *managedMachinePool.Spec.OSDiskSizeGB 206 } 207 208 if len(managedMachinePool.Spec.Taints) > 0 { 209 nodeTaints := make([]string, 0, len(managedMachinePool.Spec.Taints)) 210 for _, t := range managedMachinePool.Spec.Taints { 211 nodeTaints = append(nodeTaints, fmt.Sprintf("%s=%s:%s", t.Key, t.Value, t.Effect)) 212 } 213 agentPoolSpec.NodeTaints = nodeTaints 214 } 215 216 if managedMachinePool.Spec.Scaling != nil { 217 agentPoolSpec.EnableAutoScaling = true 218 agentPoolSpec.MaxCount = managedMachinePool.Spec.Scaling.MaxSize 219 agentPoolSpec.MinCount = managedMachinePool.Spec.Scaling.MinSize 220 } 221 222 if len(managedMachinePool.Spec.NodeLabels) > 0 { 223 agentPoolSpec.NodeLabels = managedMachinePool.Spec.NodeLabels 224 } 225 226 if managedMachinePool.Spec.KubeletConfig != nil { 227 agentPoolSpec.KubeletConfig = &agentpools.KubeletConfig{ 228 CPUManagerPolicy: (*string)(managedMachinePool.Spec.KubeletConfig.CPUManagerPolicy), 229 CPUCfsQuota: managedMachinePool.Spec.KubeletConfig.CPUCfsQuota, 230 CPUCfsQuotaPeriod: managedMachinePool.Spec.KubeletConfig.CPUCfsQuotaPeriod, 231 ImageGcHighThreshold: managedMachinePool.Spec.KubeletConfig.ImageGcHighThreshold, 232 ImageGcLowThreshold: managedMachinePool.Spec.KubeletConfig.ImageGcLowThreshold, 233 TopologyManagerPolicy: (*string)(managedMachinePool.Spec.KubeletConfig.TopologyManagerPolicy), 234 FailSwapOn: managedMachinePool.Spec.KubeletConfig.FailSwapOn, 235 ContainerLogMaxSizeMB: managedMachinePool.Spec.KubeletConfig.ContainerLogMaxSizeMB, 236 ContainerLogMaxFiles: managedMachinePool.Spec.KubeletConfig.ContainerLogMaxFiles, 237 PodMaxPids: managedMachinePool.Spec.KubeletConfig.PodMaxPids, 238 } 239 if len(managedMachinePool.Spec.KubeletConfig.AllowedUnsafeSysctls) > 0 { 240 agentPoolSpec.KubeletConfig.AllowedUnsafeSysctls = managedMachinePool.Spec.KubeletConfig.AllowedUnsafeSysctls 241 } 242 } 243 244 return agentPoolSpec 245 } 246 247 // IsPreviewEnabled returns the value of the EnablePreviewFeatures field from the AzureManagedControlPlane. 248 func (s *ManagedMachinePoolScope) IsPreviewEnabled() bool { 249 return ptr.Deref(s.ControlPlane.Spec.EnablePreviewFeatures, false) 250 } 251 252 // SetAgentPoolProviderIDList sets a list of agent pool's Azure VM IDs. 253 func (s *ManagedMachinePoolScope) SetAgentPoolProviderIDList(providerIDs []string) { 254 s.InfraMachinePool.Spec.ProviderIDList = providerIDs 255 } 256 257 // SetAgentPoolReplicas sets the number of agent pool replicas. 258 func (s *ManagedMachinePoolScope) SetAgentPoolReplicas(replicas int32) { 259 s.InfraMachinePool.Status.Replicas = replicas 260 } 261 262 // SetAgentPoolReady sets the flag that indicates if the agent pool is ready or not. 263 func (s *ManagedMachinePoolScope) SetAgentPoolReady(ready bool) { 264 s.InfraMachinePool.Status.Ready = ready 265 } 266 267 // SetLongRunningOperationState will set the future on the AzureManagedMachinePool status to allow the resource to continue 268 // in the next reconciliation. 269 func (s *ManagedMachinePoolScope) SetLongRunningOperationState(future *infrav1.Future) { 270 futures.Set(s.InfraMachinePool, future) 271 } 272 273 // GetLongRunningOperationState will get the future on the AzureManagedMachinePool status. 274 func (s *ManagedMachinePoolScope) GetLongRunningOperationState(name, service, futureType string) *infrav1.Future { 275 return futures.Get(s.InfraMachinePool, name, service, futureType) 276 } 277 278 // DeleteLongRunningOperationState will delete the future from the AzureManagedMachinePool status. 279 func (s *ManagedMachinePoolScope) DeleteLongRunningOperationState(name, service, futureType string) { 280 futures.Delete(s.InfraMachinePool, name, service, futureType) 281 } 282 283 // UpdateDeleteStatus updates a condition on the AzureManagedControlPlane status after a DELETE operation. 284 func (s *ManagedMachinePoolScope) UpdateDeleteStatus(condition clusterv1.ConditionType, service string, err error) { 285 switch { 286 case err == nil: 287 conditions.MarkFalse(s.InfraMachinePool, condition, infrav1.DeletedReason, clusterv1.ConditionSeverityInfo, "%s successfully deleted", service) 288 case azure.IsOperationNotDoneError(err): 289 conditions.MarkFalse(s.InfraMachinePool, condition, infrav1.DeletingReason, clusterv1.ConditionSeverityInfo, "%s deleting", service) 290 default: 291 conditions.MarkFalse(s.InfraMachinePool, condition, infrav1.DeletionFailedReason, clusterv1.ConditionSeverityError, "%s failed to delete. err: %s", service, err.Error()) 292 } 293 } 294 295 // UpdatePutStatus updates a condition on the AzureManagedMachinePool status after a PUT operation. 296 func (s *ManagedMachinePoolScope) UpdatePutStatus(condition clusterv1.ConditionType, service string, err error) { 297 switch { 298 case err == nil: 299 conditions.MarkTrue(s.InfraMachinePool, condition) 300 case azure.IsOperationNotDoneError(err): 301 conditions.MarkFalse(s.InfraMachinePool, condition, infrav1.CreatingReason, clusterv1.ConditionSeverityInfo, "%s creating or updating", service) 302 default: 303 conditions.MarkFalse(s.InfraMachinePool, condition, infrav1.FailedReason, clusterv1.ConditionSeverityError, "%s failed to create or update. err: %s", service, err.Error()) 304 } 305 } 306 307 // UpdatePatchStatus updates a condition on the AzureManagedMachinePool status after a PATCH operation. 308 func (s *ManagedMachinePoolScope) UpdatePatchStatus(condition clusterv1.ConditionType, service string, err error) { 309 switch { 310 case err == nil: 311 conditions.MarkTrue(s.InfraMachinePool, condition) 312 case azure.IsOperationNotDoneError(err): 313 conditions.MarkFalse(s.InfraMachinePool, condition, infrav1.UpdatingReason, clusterv1.ConditionSeverityInfo, "%s updating", service) 314 default: 315 conditions.MarkFalse(s.InfraMachinePool, condition, infrav1.FailedReason, clusterv1.ConditionSeverityError, "%s failed to update. err: %s", service, err.Error()) 316 } 317 } 318 319 // PatchCAPIMachinePoolObject persists the capi machinepool configuration and status. 320 func (s *ManagedMachinePoolScope) PatchCAPIMachinePoolObject(ctx context.Context) error { 321 return s.capiMachinePoolPatchHelper.Patch( 322 ctx, 323 s.MachinePool, 324 ) 325 } 326 327 // SetCAPIMachinePoolReplicas sets the associated MachinePool replica count. 328 func (s *ManagedMachinePoolScope) SetCAPIMachinePoolReplicas(replicas *int) { 329 var setReplicas *int32 330 if replicas != nil { 331 setReplicas = ptr.To(int32(*replicas)) 332 } 333 s.MachinePool.Spec.Replicas = setReplicas 334 } 335 336 // SetCAPIMachinePoolAnnotation sets the specified annotation on the associated MachinePool. 337 func (s *ManagedMachinePoolScope) SetCAPIMachinePoolAnnotation(key, value string) { 338 if s.MachinePool.Annotations == nil { 339 s.MachinePool.Annotations = make(map[string]string) 340 } 341 s.MachinePool.Annotations[key] = value 342 } 343 344 // RemoveCAPIMachinePoolAnnotation removes the specified annotation on the associated MachinePool. 345 func (s *ManagedMachinePoolScope) RemoveCAPIMachinePoolAnnotation(key string) { 346 delete(s.MachinePool.Annotations, key) 347 } 348 349 // GetCAPIMachinePoolAnnotation gets the specified annotation on the associated MachinePool. 350 func (s *ManagedMachinePoolScope) GetCAPIMachinePoolAnnotation(key string) (success bool, value string) { 351 val, ok := s.MachinePool.Annotations[key] 352 return ok, val 353 } 354 355 func getManagedMachinePoolVersion(managedControlPlane *infrav1.AzureManagedControlPlane, machinePool *expv1.MachinePool) *string { 356 var v, av string 357 if machinePool != nil { 358 v = ptr.Deref(machinePool.Spec.Template.Spec.Version, "") 359 } 360 if managedControlPlane != nil { 361 av = managedControlPlane.Status.AutoUpgradeVersion 362 } 363 higherVersion := versions.GetHigherK8sVersion(v, av) 364 if higherVersion == "" { 365 // When both mp.Version and mcp.Status.AutoUpgradeVersion are not set we return nil 366 return nil 367 } 368 return ptr.To(strings.TrimPrefix(higherVersion, "v")) 369 }