sigs.k8s.io/cluster-api-provider-azure@v1.17.0/pkg/mutators/azureasomanagedmachinepool_test.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 "errors" 22 "testing" 23 24 asocontainerservicev1 "github.com/Azure/azure-service-operator/v2/api/containerservice/v1api20231001" 25 "github.com/google/go-cmp/cmp" 26 . "github.com/onsi/gomega" 27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 29 "k8s.io/apimachinery/pkg/runtime" 30 "k8s.io/utils/ptr" 31 infrav1alpha "sigs.k8s.io/cluster-api-provider-azure/api/v1alpha1" 32 clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" 33 expv1 "sigs.k8s.io/cluster-api/exp/api/v1beta1" 34 "sigs.k8s.io/controller-runtime/pkg/client" 35 fakeclient "sigs.k8s.io/controller-runtime/pkg/client/fake" 36 ) 37 38 func TestSetAgentPoolDefaults(t *testing.T) { 39 ctx := context.Background() 40 g := NewGomegaWithT(t) 41 42 tests := []struct { 43 name string 44 asoManagedMachinePool *infrav1alpha.AzureASOManagedMachinePool 45 machinePool *expv1.MachinePool 46 expected []*unstructured.Unstructured 47 expectedErr error 48 }{ 49 { 50 name: "no ManagedClustersAgentPool", 51 asoManagedMachinePool: &infrav1alpha.AzureASOManagedMachinePool{ 52 Spec: infrav1alpha.AzureASOManagedMachinePoolSpec{ 53 AzureASOManagedMachinePoolTemplateResourceSpec: infrav1alpha.AzureASOManagedMachinePoolTemplateResourceSpec{ 54 Resources: []runtime.RawExtension{}, 55 }, 56 }, 57 }, 58 expectedErr: ErrNoManagedClustersAgentPoolDefined, 59 }, 60 { 61 name: "success", 62 asoManagedMachinePool: &infrav1alpha.AzureASOManagedMachinePool{ 63 Spec: infrav1alpha.AzureASOManagedMachinePoolSpec{ 64 AzureASOManagedMachinePoolTemplateResourceSpec: infrav1alpha.AzureASOManagedMachinePoolTemplateResourceSpec{ 65 Resources: []runtime.RawExtension{ 66 { 67 Raw: apJSON(g, &asocontainerservicev1.ManagedClustersAgentPool{}), 68 }, 69 }, 70 }, 71 }, 72 }, 73 machinePool: &expv1.MachinePool{ 74 Spec: expv1.MachinePoolSpec{ 75 Replicas: ptr.To[int32](1), 76 Template: clusterv1.MachineTemplateSpec{ 77 Spec: clusterv1.MachineSpec{ 78 Version: ptr.To("vcapi k8s version"), 79 }, 80 }, 81 }, 82 }, 83 expected: []*unstructured.Unstructured{ 84 apUnstructured(g, &asocontainerservicev1.ManagedClustersAgentPool{ 85 Spec: asocontainerservicev1.ManagedClusters_AgentPool_Spec{ 86 OrchestratorVersion: ptr.To("capi k8s version"), 87 Count: ptr.To(1), 88 }, 89 }), 90 }, 91 }, 92 } 93 94 for _, test := range tests { 95 t.Run(test.name, func(t *testing.T) { 96 g := NewGomegaWithT(t) 97 98 mutator := SetAgentPoolDefaults(nil, test.machinePool) 99 actual, err := ApplyMutators(ctx, test.asoManagedMachinePool.Spec.Resources, mutator) 100 if test.expectedErr != nil { 101 g.Expect(err).To(MatchError(test.expectedErr)) 102 } else { 103 g.Expect(err).NotTo(HaveOccurred()) 104 } 105 g.Expect(cmp.Diff(test.expected, actual)).To(BeEmpty()) 106 }) 107 } 108 } 109 110 func TestSetAgentPoolOrchestratorVersion(t *testing.T) { 111 ctx := context.Background() 112 113 tests := []struct { 114 name string 115 machinePool *expv1.MachinePool 116 agentPool *asocontainerservicev1.ManagedClustersAgentPool 117 expected *asocontainerservicev1.ManagedClustersAgentPool 118 expectedErr error 119 }{ 120 { 121 name: "no CAPI opinion", 122 machinePool: &expv1.MachinePool{ 123 Spec: expv1.MachinePoolSpec{ 124 Template: clusterv1.MachineTemplateSpec{ 125 Spec: clusterv1.MachineSpec{ 126 Version: nil, 127 }, 128 }, 129 }, 130 }, 131 agentPool: &asocontainerservicev1.ManagedClustersAgentPool{ 132 Spec: asocontainerservicev1.ManagedClusters_AgentPool_Spec{ 133 OrchestratorVersion: ptr.To("user k8s version"), 134 }, 135 }, 136 expected: &asocontainerservicev1.ManagedClustersAgentPool{ 137 Spec: asocontainerservicev1.ManagedClusters_AgentPool_Spec{ 138 OrchestratorVersion: ptr.To("user k8s version"), 139 }, 140 }, 141 }, 142 { 143 name: "set from CAPI opinion", 144 machinePool: &expv1.MachinePool{ 145 Spec: expv1.MachinePoolSpec{ 146 Template: clusterv1.MachineTemplateSpec{ 147 Spec: clusterv1.MachineSpec{ 148 Version: ptr.To("vcapi k8s version"), 149 }, 150 }, 151 }, 152 }, 153 agentPool: &asocontainerservicev1.ManagedClustersAgentPool{ 154 Spec: asocontainerservicev1.ManagedClusters_AgentPool_Spec{ 155 OrchestratorVersion: nil, 156 }, 157 }, 158 expected: &asocontainerservicev1.ManagedClustersAgentPool{ 159 Spec: asocontainerservicev1.ManagedClusters_AgentPool_Spec{ 160 OrchestratorVersion: ptr.To("capi k8s version"), 161 }, 162 }, 163 }, 164 { 165 name: "user value matching CAPI ok", 166 machinePool: &expv1.MachinePool{ 167 Spec: expv1.MachinePoolSpec{ 168 Template: clusterv1.MachineTemplateSpec{ 169 Spec: clusterv1.MachineSpec{ 170 Version: ptr.To("vcapi k8s version"), 171 }, 172 }, 173 }, 174 }, 175 agentPool: &asocontainerservicev1.ManagedClustersAgentPool{ 176 Spec: asocontainerservicev1.ManagedClusters_AgentPool_Spec{ 177 OrchestratorVersion: ptr.To("capi k8s version"), 178 }, 179 }, 180 expected: &asocontainerservicev1.ManagedClustersAgentPool{ 181 Spec: asocontainerservicev1.ManagedClusters_AgentPool_Spec{ 182 OrchestratorVersion: ptr.To("capi k8s version"), 183 }, 184 }, 185 }, 186 { 187 name: "incompatible", 188 machinePool: &expv1.MachinePool{ 189 ObjectMeta: metav1.ObjectMeta{ 190 Name: "mp", 191 }, 192 Spec: expv1.MachinePoolSpec{ 193 Template: clusterv1.MachineTemplateSpec{ 194 Spec: clusterv1.MachineSpec{ 195 Version: ptr.To("vcapi k8s version"), 196 }, 197 }, 198 }, 199 }, 200 agentPool: &asocontainerservicev1.ManagedClustersAgentPool{ 201 Spec: asocontainerservicev1.ManagedClusters_AgentPool_Spec{ 202 OrchestratorVersion: ptr.To("user k8s version"), 203 }, 204 }, 205 expectedErr: Incompatible{ 206 mutation: mutation{ 207 location: ".spec.orchestratorVersion", 208 val: "capi k8s version", 209 reason: "because MachinePool mp's spec.template.spec.version is vcapi k8s version", 210 }, 211 userVal: "user k8s version", 212 }, 213 }, 214 } 215 216 s := runtime.NewScheme() 217 NewGomegaWithT(t).Expect(asocontainerservicev1.AddToScheme(s)).To(Succeed()) 218 219 for _, test := range tests { 220 t.Run(test.name, func(t *testing.T) { 221 g := NewGomegaWithT(t) 222 223 before := test.agentPool.DeepCopy() 224 uap := apUnstructured(g, test.agentPool) 225 226 err := setAgentPoolOrchestratorVersion(ctx, test.machinePool, "", uap) 227 g.Expect(s.Convert(uap, test.agentPool, nil)).To(Succeed()) 228 if test.expectedErr != nil { 229 g.Expect(err).To(MatchError(test.expectedErr)) 230 g.Expect(cmp.Diff(before, test.agentPool)).To(BeEmpty()) // errors should never modify the resource. 231 } else { 232 g.Expect(err).NotTo(HaveOccurred()) 233 g.Expect(cmp.Diff(test.expected, test.agentPool)).To(BeEmpty()) 234 } 235 }) 236 } 237 } 238 239 func TestReconcileAutoscaling(t *testing.T) { 240 tests := []struct { 241 name string 242 autoscaling bool 243 machinePool *expv1.MachinePool 244 expected *expv1.MachinePool 245 expectedErr error 246 }{ 247 { 248 name: "autoscaling disabled removes aks annotation", 249 autoscaling: false, 250 machinePool: &expv1.MachinePool{ 251 ObjectMeta: metav1.ObjectMeta{ 252 Annotations: map[string]string{ 253 clusterv1.ReplicasManagedByAnnotation: infrav1alpha.ReplicasManagedByAKS, 254 }, 255 }, 256 }, 257 expected: &expv1.MachinePool{ 258 ObjectMeta: metav1.ObjectMeta{ 259 Annotations: map[string]string{}, 260 }, 261 }, 262 }, 263 { 264 name: "autoscaling disabled leaves other annotation", 265 autoscaling: false, 266 machinePool: &expv1.MachinePool{ 267 ObjectMeta: metav1.ObjectMeta{ 268 Annotations: map[string]string{ 269 clusterv1.ReplicasManagedByAnnotation: "not-" + infrav1alpha.ReplicasManagedByAKS, 270 }, 271 }, 272 }, 273 expected: &expv1.MachinePool{ 274 ObjectMeta: metav1.ObjectMeta{ 275 Annotations: map[string]string{ 276 clusterv1.ReplicasManagedByAnnotation: "not-" + infrav1alpha.ReplicasManagedByAKS, 277 }, 278 }, 279 }, 280 }, 281 { 282 name: "autoscaling enabled, manager undefined adds annotation", 283 autoscaling: true, 284 machinePool: &expv1.MachinePool{ 285 ObjectMeta: metav1.ObjectMeta{ 286 Annotations: map[string]string{}, 287 }, 288 }, 289 expected: &expv1.MachinePool{ 290 ObjectMeta: metav1.ObjectMeta{ 291 Annotations: map[string]string{ 292 clusterv1.ReplicasManagedByAnnotation: infrav1alpha.ReplicasManagedByAKS, 293 }, 294 }, 295 }, 296 }, 297 { 298 name: "autoscaling enabled, manager already set", 299 autoscaling: true, 300 machinePool: &expv1.MachinePool{ 301 ObjectMeta: metav1.ObjectMeta{ 302 Annotations: map[string]string{ 303 clusterv1.ReplicasManagedByAnnotation: infrav1alpha.ReplicasManagedByAKS, 304 }, 305 }, 306 }, 307 expected: &expv1.MachinePool{ 308 ObjectMeta: metav1.ObjectMeta{ 309 Annotations: map[string]string{ 310 clusterv1.ReplicasManagedByAnnotation: infrav1alpha.ReplicasManagedByAKS, 311 }, 312 }, 313 }, 314 }, 315 { 316 name: "autoscaling enabled, manager set to something else", 317 autoscaling: true, 318 machinePool: &expv1.MachinePool{ 319 ObjectMeta: metav1.ObjectMeta{ 320 Name: "mp", 321 Annotations: map[string]string{ 322 clusterv1.ReplicasManagedByAnnotation: "not-" + infrav1alpha.ReplicasManagedByAKS, 323 }, 324 }, 325 }, 326 expectedErr: errors.New("failed to enable autoscaling, replicas are already being managed by not-aks according to MachinePool mp's cluster.x-k8s.io/replicas-managed-by annotation"), 327 }, 328 } 329 330 for _, test := range tests { 331 t.Run(test.name, func(t *testing.T) { 332 g := NewGomegaWithT(t) 333 334 agentPool := &asocontainerservicev1.ManagedClustersAgentPool{ 335 Spec: asocontainerservicev1.ManagedClusters_AgentPool_Spec{ 336 EnableAutoScaling: ptr.To(test.autoscaling), 337 }, 338 } 339 340 err := reconcileAutoscaling(apUnstructured(g, agentPool), test.machinePool) 341 342 if test.expectedErr != nil { 343 g.Expect(err).To(MatchError(test.expectedErr)) 344 } else { 345 g.Expect(err).NotTo(HaveOccurred()) 346 g.Expect(cmp.Diff(test.expected, test.machinePool)).To(BeEmpty()) 347 } 348 }) 349 } 350 } 351 352 func TestSetAgentPoolCount(t *testing.T) { 353 ctx := context.Background() 354 355 tests := []struct { 356 name string 357 machinePool *expv1.MachinePool 358 agentPool *asocontainerservicev1.ManagedClustersAgentPool 359 existingAgentPool *asocontainerservicev1.ManagedClustersAgentPool 360 expected *asocontainerservicev1.ManagedClustersAgentPool 361 expectedErr error 362 }{ 363 { 364 name: "no CAPI opinion", 365 machinePool: &expv1.MachinePool{ 366 Spec: expv1.MachinePoolSpec{ 367 Replicas: nil, 368 }, 369 }, 370 agentPool: &asocontainerservicev1.ManagedClustersAgentPool{ 371 Spec: asocontainerservicev1.ManagedClusters_AgentPool_Spec{ 372 Count: ptr.To(2), 373 }, 374 }, 375 expected: &asocontainerservicev1.ManagedClustersAgentPool{ 376 Spec: asocontainerservicev1.ManagedClusters_AgentPool_Spec{ 377 Count: ptr.To(2), 378 }, 379 }, 380 }, 381 { 382 name: "autoscaling enabled", 383 machinePool: &expv1.MachinePool{ 384 ObjectMeta: metav1.ObjectMeta{ 385 Annotations: map[string]string{ 386 clusterv1.ReplicasManagedByAnnotation: infrav1alpha.ReplicasManagedByAKS, 387 }, 388 }, 389 Spec: expv1.MachinePoolSpec{ 390 Replicas: ptr.To[int32](3), 391 }, 392 }, 393 agentPool: &asocontainerservicev1.ManagedClustersAgentPool{ 394 Spec: asocontainerservicev1.ManagedClusters_AgentPool_Spec{ 395 Count: nil, 396 }, 397 }, 398 existingAgentPool: &asocontainerservicev1.ManagedClustersAgentPool{ 399 Status: asocontainerservicev1.ManagedClusters_AgentPool_STATUS{ 400 Count: ptr.To(2), 401 }, 402 }, 403 expected: &asocontainerservicev1.ManagedClustersAgentPool{ 404 Spec: asocontainerservicev1.ManagedClusters_AgentPool_Spec{ 405 Count: nil, 406 }, 407 }, 408 }, 409 { 410 name: "set from CAPI opinion", 411 machinePool: &expv1.MachinePool{ 412 Spec: expv1.MachinePoolSpec{ 413 Replicas: ptr.To[int32](1), 414 }, 415 }, 416 agentPool: &asocontainerservicev1.ManagedClustersAgentPool{ 417 Spec: asocontainerservicev1.ManagedClusters_AgentPool_Spec{ 418 Count: nil, 419 }, 420 }, 421 expected: &asocontainerservicev1.ManagedClustersAgentPool{ 422 Spec: asocontainerservicev1.ManagedClusters_AgentPool_Spec{ 423 Count: ptr.To(1), 424 }, 425 }, 426 }, 427 { 428 name: "user value matching CAPI ok", 429 machinePool: &expv1.MachinePool{ 430 Spec: expv1.MachinePoolSpec{ 431 Replicas: ptr.To[int32](1), 432 }, 433 }, 434 agentPool: &asocontainerservicev1.ManagedClustersAgentPool{ 435 Spec: asocontainerservicev1.ManagedClusters_AgentPool_Spec{ 436 Count: ptr.To(1), 437 }, 438 }, 439 expected: &asocontainerservicev1.ManagedClustersAgentPool{ 440 Spec: asocontainerservicev1.ManagedClusters_AgentPool_Spec{ 441 Count: ptr.To(1), 442 }, 443 }, 444 }, 445 { 446 name: "incompatible", 447 machinePool: &expv1.MachinePool{ 448 ObjectMeta: metav1.ObjectMeta{ 449 Name: "mp", 450 }, 451 Spec: expv1.MachinePoolSpec{ 452 Replicas: ptr.To[int32](1), 453 }, 454 }, 455 agentPool: &asocontainerservicev1.ManagedClustersAgentPool{ 456 Spec: asocontainerservicev1.ManagedClusters_AgentPool_Spec{ 457 Count: ptr.To(2), 458 }, 459 }, 460 expectedErr: Incompatible{ 461 mutation: mutation{ 462 location: ".spec.count", 463 val: int64(1), 464 reason: "because MachinePool mp's spec.replicas is 1", 465 }, 466 userVal: int64(2), 467 }, 468 }, 469 } 470 471 s := runtime.NewScheme() 472 NewGomegaWithT(t).Expect(asocontainerservicev1.AddToScheme(s)).To(Succeed()) 473 474 for _, test := range tests { 475 t.Run(test.name, func(t *testing.T) { 476 g := NewGomegaWithT(t) 477 478 var c client.Client 479 if test.existingAgentPool != nil { 480 c = fakeclient.NewClientBuilder(). 481 WithScheme(s). 482 WithObjects(test.existingAgentPool). 483 Build() 484 } 485 486 before := test.agentPool.DeepCopy() 487 uap := apUnstructured(g, test.agentPool) 488 489 err := setAgentPoolCount(ctx, c, test.machinePool, "", uap) 490 g.Expect(s.Convert(uap, test.agentPool, nil)).To(Succeed()) 491 if test.expectedErr != nil { 492 g.Expect(err).To(MatchError(test.expectedErr)) 493 g.Expect(cmp.Diff(before, test.agentPool)).To(BeEmpty()) // errors should never modify the resource. 494 } else { 495 g.Expect(err).NotTo(HaveOccurred()) 496 g.Expect(cmp.Diff(test.expected, test.agentPool)).To(BeEmpty()) 497 } 498 }) 499 } 500 }