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 }