sigs.k8s.io/cluster-api-provider-azure@v1.17.0/pkg/mutators/azureasomanagedmachinepool.go (about)

     1  /*
     2  Copyright 2024 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 mutators
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"strings"
    23  
    24  	asocontainerservicev1 "github.com/Azure/azure-service-operator/v2/api/containerservice/v1api20231001"
    25  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    26  	infrav1alpha "sigs.k8s.io/cluster-api-provider-azure/api/v1alpha1"
    27  	"sigs.k8s.io/cluster-api-provider-azure/util/tele"
    28  	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
    29  	expv1 "sigs.k8s.io/cluster-api/exp/api/v1beta1"
    30  	"sigs.k8s.io/controller-runtime/pkg/client"
    31  	"sigs.k8s.io/controller-runtime/pkg/reconcile"
    32  )
    33  
    34  // ErrNoManagedClustersAgentPoolDefined describes an AzureASOManagedMachinePool without a ManagedClustersAgentPool.
    35  var ErrNoManagedClustersAgentPoolDefined = fmt.Errorf("no %s ManagedClustersAgentPools defined in AzureASOManagedMachinePool spec.resources", asocontainerservicev1.GroupVersion.Group)
    36  
    37  // SetAgentPoolDefaults propagates config from a MachinePool to an AzureASOManagedMachinePool's defined ManagedClustersAgentPool.
    38  func SetAgentPoolDefaults(ctrlClient client.Client, machinePool *expv1.MachinePool) ResourcesMutator {
    39  	return func(ctx context.Context, us []*unstructured.Unstructured) error {
    40  		ctx, _, done := tele.StartSpanWithLogger(ctx, "mutators.SetAgentPoolDefaults")
    41  		defer done()
    42  
    43  		var agentPool *unstructured.Unstructured
    44  		var agentPoolPath string
    45  		for i, u := range us {
    46  			if u.GroupVersionKind().Group == asocontainerservicev1.GroupVersion.Group &&
    47  				u.GroupVersionKind().Kind == "ManagedClustersAgentPool" {
    48  				agentPool = u
    49  				agentPoolPath = fmt.Sprintf("spec.resources[%d]", i)
    50  				break
    51  			}
    52  		}
    53  		if agentPool == nil {
    54  			return reconcile.TerminalError(ErrNoManagedClustersAgentPoolDefined)
    55  		}
    56  
    57  		if err := setAgentPoolOrchestratorVersion(ctx, machinePool, agentPoolPath, agentPool); err != nil {
    58  			return err
    59  		}
    60  
    61  		if err := reconcileAutoscaling(agentPool, machinePool); err != nil {
    62  			return err
    63  		}
    64  
    65  		if err := setAgentPoolCount(ctx, ctrlClient, machinePool, agentPoolPath, agentPool); err != nil {
    66  			return err
    67  		}
    68  
    69  		return nil
    70  	}
    71  }
    72  
    73  func setAgentPoolOrchestratorVersion(ctx context.Context, machinePool *expv1.MachinePool, agentPoolPath string, agentPool *unstructured.Unstructured) error {
    74  	_, log, done := tele.StartSpanWithLogger(ctx, "mutators.setAgentPoolOrchestratorVersion")
    75  	defer done()
    76  
    77  	if machinePool.Spec.Template.Spec.Version == nil {
    78  		return nil
    79  	}
    80  
    81  	k8sVersionPath := []string{"spec", "orchestratorVersion"}
    82  	capiK8sVersion := strings.TrimPrefix(*machinePool.Spec.Template.Spec.Version, "v")
    83  	userK8sVersion, k8sVersionFound, err := unstructured.NestedString(agentPool.UnstructuredContent(), k8sVersionPath...)
    84  	if err != nil {
    85  		return err
    86  	}
    87  	setK8sVersion := mutation{
    88  		location: agentPoolPath + "." + strings.Join(k8sVersionPath, "."),
    89  		val:      capiK8sVersion,
    90  		reason:   fmt.Sprintf("because MachinePool %s's spec.template.spec.version is %s", machinePool.Name, *machinePool.Spec.Template.Spec.Version),
    91  	}
    92  	if k8sVersionFound && userK8sVersion != capiK8sVersion {
    93  		return Incompatible{
    94  			mutation: setK8sVersion,
    95  			userVal:  userK8sVersion,
    96  		}
    97  	}
    98  	logMutation(log, setK8sVersion)
    99  	return unstructured.SetNestedField(agentPool.UnstructuredContent(), capiK8sVersion, k8sVersionPath...)
   100  }
   101  
   102  func reconcileAutoscaling(agentPool *unstructured.Unstructured, machinePool *expv1.MachinePool) error {
   103  	autoscaling, _, err := unstructured.NestedBool(agentPool.UnstructuredContent(), "spec", "enableAutoScaling")
   104  	if err != nil {
   105  		return err
   106  	}
   107  
   108  	// Update the MachinePool replica manager annotation. This isn't wrapped in a mutation object because
   109  	// it's not modifying an ASO resource and users are not expected to set this manually. This behavior
   110  	// is documented by CAPI as expected of a provider.
   111  	replicaManager, ok := machinePool.Annotations[clusterv1.ReplicasManagedByAnnotation]
   112  	if autoscaling {
   113  		if !ok {
   114  			if machinePool.Annotations == nil {
   115  				machinePool.Annotations = make(map[string]string)
   116  			}
   117  			machinePool.Annotations[clusterv1.ReplicasManagedByAnnotation] = infrav1alpha.ReplicasManagedByAKS
   118  		} else if replicaManager != infrav1alpha.ReplicasManagedByAKS {
   119  			return fmt.Errorf("failed to enable autoscaling, replicas are already being managed by %s according to MachinePool %s's %s annotation", replicaManager, machinePool.Name, clusterv1.ReplicasManagedByAnnotation)
   120  		}
   121  	} else if !autoscaling && replicaManager == infrav1alpha.ReplicasManagedByAKS {
   122  		// Removing this annotation informs the MachinePool controller that this MachinePool is no longer
   123  		// being autoscaled.
   124  		delete(machinePool.Annotations, clusterv1.ReplicasManagedByAnnotation)
   125  	}
   126  
   127  	return nil
   128  }
   129  
   130  func setAgentPoolCount(ctx context.Context, ctrlClient client.Client, machinePool *expv1.MachinePool, agentPoolPath string, agentPool *unstructured.Unstructured) error {
   131  	_, log, done := tele.StartSpanWithLogger(ctx, "mutators.setAgentPoolCount")
   132  	defer done()
   133  
   134  	if machinePool.Spec.Replicas == nil {
   135  		return nil
   136  	}
   137  
   138  	// When managed by any autoscaler, CAPZ should not provide any spec.count to the ManagedClustersAgentPool
   139  	// to prevent ASO from overwriting the autoscaler's opinion of the replica count.
   140  	// The MachinePool's spec.replicas is used to seed an initial value as required by AKS.
   141  	if _, autoscaling := machinePool.Annotations[clusterv1.ReplicasManagedByAnnotation]; autoscaling {
   142  		existingAgentPool := &asocontainerservicev1.ManagedClustersAgentPool{}
   143  		err := ctrlClient.Get(ctx, client.ObjectKey{Namespace: machinePool.GetNamespace(), Name: agentPool.GetName()}, existingAgentPool)
   144  		if client.IgnoreNotFound(err) != nil {
   145  			return err
   146  		}
   147  		if err == nil && existingAgentPool.Status.Count != nil {
   148  			return nil
   149  		}
   150  	}
   151  
   152  	countPath := []string{"spec", "count"}
   153  	capiCount := int64(*machinePool.Spec.Replicas)
   154  	userCount, countFound, err := unstructured.NestedInt64(agentPool.UnstructuredContent(), countPath...)
   155  	if err != nil {
   156  		return err
   157  	}
   158  	setCount := mutation{
   159  		location: agentPoolPath + "." + strings.Join(countPath, "."),
   160  		val:      capiCount,
   161  		reason:   fmt.Sprintf("because MachinePool %s's spec.replicas is %d", machinePool.Name, capiCount),
   162  	}
   163  	if countFound && userCount != capiCount {
   164  		return Incompatible{
   165  			mutation: setCount,
   166  			userVal:  userCount,
   167  		}
   168  	}
   169  	logMutation(log, setCount)
   170  	return unstructured.SetNestedField(agentPool.UnstructuredContent(), capiCount, countPath...)
   171  }