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  }