sigs.k8s.io/cluster-api-provider-aws@v1.5.5/controlplane/eks/api/v1beta1/awsmanagedcontrolplane_webhook_test.go (about) 1 /* 2 Copyright 2021 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 v1beta1 18 19 import ( 20 "context" 21 "strings" 22 "testing" 23 24 "github.com/aws/aws-sdk-go/aws" 25 . "github.com/onsi/gomega" 26 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 27 "k8s.io/utils/pointer" 28 29 infrav1 "sigs.k8s.io/cluster-api-provider-aws/api/v1beta1" 30 utildefaulting "sigs.k8s.io/cluster-api/util/defaulting" 31 ) 32 33 var ( 34 vV1_17_1 = "v1.17.1" 35 vV1_17 = "v1.17" 36 vV1_16 = "v1.16" 37 ) 38 39 func TestDefaultingWebhook(t *testing.T) { 40 defaultTestBastion := infrav1.Bastion{ 41 AllowedCIDRBlocks: []string{"0.0.0.0/0"}, 42 } 43 AZUsageLimit := 3 44 defaultVPCSpec := infrav1.VPCSpec{ 45 AvailabilityZoneUsageLimit: &AZUsageLimit, 46 AvailabilityZoneSelection: &infrav1.AZSelectionSchemeOrdered, 47 } 48 defaultIdentityRef := &infrav1.AWSIdentityReference{ 49 Kind: infrav1.ControllerIdentityKind, 50 Name: infrav1.AWSClusterControllerIdentityName, 51 } 52 defaultNetworkSpec := infrav1.NetworkSpec{ 53 VPC: defaultVPCSpec, 54 CNI: &infrav1.CNISpec{ 55 CNIIngressRules: []infrav1.CNIIngressRule{ 56 { 57 Description: "bgp (calico)", 58 Protocol: "tcp", 59 FromPort: 179, 60 ToPort: 179, 61 }, 62 { 63 Description: "IP-in-IP (calico)", 64 Protocol: "4", 65 FromPort: -1, 66 ToPort: 65535, 67 }, 68 }, 69 }, 70 } 71 72 tests := []struct { 73 name string 74 resourceName string 75 resourceNS string 76 expectHash bool 77 expect string 78 spec AWSManagedControlPlaneSpec 79 expectSpec AWSManagedControlPlaneSpec 80 }{ 81 { 82 name: "less than 100 chars", 83 resourceName: "cluster1", 84 resourceNS: "default", 85 expectHash: false, 86 expectSpec: AWSManagedControlPlaneSpec{EKSClusterName: "default_cluster1", IdentityRef: defaultIdentityRef, Bastion: defaultTestBastion, NetworkSpec: defaultNetworkSpec, TokenMethod: &EKSTokenMethodIAMAuthenticator}, 87 }, 88 { 89 name: "less than 100 chars, dot in name", 90 resourceName: "team1.cluster1", 91 resourceNS: "default", 92 expectHash: false, 93 expectSpec: AWSManagedControlPlaneSpec{EKSClusterName: "default_team1_cluster1", IdentityRef: defaultIdentityRef, Bastion: defaultTestBastion, NetworkSpec: defaultNetworkSpec, TokenMethod: &EKSTokenMethodIAMAuthenticator}, 94 }, 95 { 96 name: "more than 100 chars", 97 resourceName: "abcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcde", 98 resourceNS: "default", 99 expectHash: true, 100 expectSpec: AWSManagedControlPlaneSpec{EKSClusterName: "capi_", IdentityRef: defaultIdentityRef, Bastion: defaultTestBastion, NetworkSpec: defaultNetworkSpec, TokenMethod: &EKSTokenMethodIAMAuthenticator}, 101 }, 102 { 103 name: "with patch", 104 resourceName: "cluster1", 105 resourceNS: "default", 106 expectHash: false, 107 spec: AWSManagedControlPlaneSpec{Version: &vV1_17_1}, 108 expectSpec: AWSManagedControlPlaneSpec{EKSClusterName: "default_cluster1", Version: &vV1_17, IdentityRef: defaultIdentityRef, Bastion: defaultTestBastion, NetworkSpec: defaultNetworkSpec, TokenMethod: &EKSTokenMethodIAMAuthenticator}, 109 }, 110 { 111 name: "with allowed ip on bastion", 112 resourceName: "cluster1", 113 resourceNS: "default", 114 expectHash: false, 115 spec: AWSManagedControlPlaneSpec{Bastion: infrav1.Bastion{AllowedCIDRBlocks: []string{"100.100.100.100/0"}}}, 116 expectSpec: AWSManagedControlPlaneSpec{EKSClusterName: "default_cluster1", IdentityRef: defaultIdentityRef, Bastion: infrav1.Bastion{AllowedCIDRBlocks: []string{"100.100.100.100/0"}}, NetworkSpec: defaultNetworkSpec, TokenMethod: &EKSTokenMethodIAMAuthenticator}, 117 }, 118 { 119 name: "with CNI on network", 120 resourceName: "cluster1", 121 resourceNS: "default", 122 expectHash: false, 123 spec: AWSManagedControlPlaneSpec{NetworkSpec: infrav1.NetworkSpec{CNI: &infrav1.CNISpec{}}}, 124 expectSpec: AWSManagedControlPlaneSpec{EKSClusterName: "default_cluster1", IdentityRef: defaultIdentityRef, Bastion: defaultTestBastion, NetworkSpec: infrav1.NetworkSpec{CNI: &infrav1.CNISpec{}, VPC: defaultVPCSpec}, TokenMethod: &EKSTokenMethodIAMAuthenticator}, 125 }, 126 { 127 name: "secondary CIDR", 128 resourceName: "cluster1", 129 resourceNS: "default", 130 expectHash: false, 131 expectSpec: AWSManagedControlPlaneSpec{EKSClusterName: "default_cluster1", IdentityRef: defaultIdentityRef, Bastion: defaultTestBastion, NetworkSpec: defaultNetworkSpec, SecondaryCidrBlock: nil, TokenMethod: &EKSTokenMethodIAMAuthenticator}, 132 }, 133 } 134 135 for _, tc := range tests { 136 t.Run(tc.name, func(t *testing.T) { 137 ctx := context.TODO() 138 g := NewWithT(t) 139 140 mcp := &AWSManagedControlPlane{ 141 ObjectMeta: metav1.ObjectMeta{ 142 Name: tc.resourceName, 143 Namespace: tc.resourceNS, 144 }, 145 } 146 t.Run("for AWSManagedMachinePool", utildefaulting.DefaultValidateTest(mcp)) 147 mcp.Spec = tc.spec 148 149 g.Expect(testEnv.Create(ctx, mcp)).To(Succeed()) 150 151 defer func() { 152 testEnv.Delete(ctx, mcp) 153 }() 154 155 g.Expect(mcp.Spec.EKSClusterName).ToNot(BeEmpty()) 156 157 if tc.expectHash { 158 g.Expect(strings.HasPrefix(mcp.Spec.EKSClusterName, "capa_")).To(BeTrue()) 159 // We don't care about the exact name 160 tc.expectSpec.EKSClusterName = mcp.Spec.EKSClusterName 161 } 162 g.Expect(mcp.Spec).To(Equal(tc.expectSpec)) 163 }) 164 } 165 } 166 167 func TestWebhookCreate(t *testing.T) { 168 tests := []struct { //nolint:maligned 169 name string 170 eksClusterName string 171 expectError bool 172 eksVersion string 173 hasAddons bool 174 disableVPCCNI bool 175 additionalTags infrav1.Tags 176 secondaryCidr *string 177 kubeProxy KubeProxy 178 }{ 179 { 180 name: "ekscluster specified", 181 eksClusterName: "default_cluster1", 182 expectError: false, 183 hasAddons: false, 184 disableVPCCNI: false, 185 additionalTags: infrav1.Tags{ 186 "a": "b", 187 "key-2": "value-2", 188 }, 189 }, 190 { 191 name: "ekscluster NOT specified", 192 eksClusterName: "", 193 expectError: false, 194 hasAddons: false, 195 disableVPCCNI: false, 196 }, 197 { 198 name: "invalid version", 199 eksClusterName: "default_cluster1", 200 eksVersion: "v1.x17", 201 expectError: true, 202 hasAddons: false, 203 disableVPCCNI: false, 204 }, 205 { 206 name: "addons with allowed k8s version", 207 eksClusterName: "default_cluster1", 208 eksVersion: "v1.18", 209 expectError: false, 210 hasAddons: true, 211 disableVPCCNI: false, 212 }, 213 { 214 name: "addons with not allowed k8s version", 215 eksClusterName: "default_cluster1", 216 eksVersion: "v1.17", 217 expectError: true, 218 hasAddons: true, 219 disableVPCCNI: false, 220 }, 221 { 222 name: "disable vpc cni allowed with no addons or secondary cidr", 223 eksClusterName: "default_cluster1", 224 eksVersion: "v1.19", 225 expectError: false, 226 hasAddons: false, 227 disableVPCCNI: true, 228 }, 229 { 230 name: "disable vpc cni not allowed with vpc cni addon", 231 eksClusterName: "default_cluster1", 232 eksVersion: "v1.19", 233 expectError: true, 234 hasAddons: true, 235 disableVPCCNI: true, 236 }, 237 { 238 name: "disable vpc cni allowed with valid secondary", 239 eksClusterName: "default_cluster1", 240 eksVersion: "v1.19", 241 expectError: false, 242 hasAddons: false, 243 disableVPCCNI: true, 244 secondaryCidr: aws.String("100.64.0.0/16"), 245 }, 246 { 247 name: "disable vpc cni allowed with invalid secondary", 248 eksClusterName: "default_cluster1", 249 eksVersion: "v1.19", 250 expectError: true, 251 hasAddons: false, 252 disableVPCCNI: true, 253 secondaryCidr: aws.String("100.64.0.0/10"), 254 }, 255 { 256 name: "invalid tags not allowed", 257 eksClusterName: "default_cluster1", 258 expectError: true, 259 hasAddons: false, 260 disableVPCCNI: false, 261 additionalTags: infrav1.Tags{ 262 "key-1": "value-1", 263 "": "value-2", 264 strings.Repeat("CAPI", 33): "value-3", 265 "key-4": strings.Repeat("CAPI", 65), 266 }, 267 }, 268 { 269 name: "disable kube-proxy allowed with no addons", 270 eksClusterName: "default_cluster1", 271 eksVersion: "v1.19", 272 expectError: false, 273 hasAddons: false, 274 disableVPCCNI: false, 275 kubeProxy: KubeProxy{ 276 Disable: true, 277 }, 278 }, 279 { 280 name: "disable kube-proxy not allowed with kube-proxy addon", 281 eksClusterName: "default_cluster1", 282 eksVersion: "v1.19", 283 expectError: true, 284 hasAddons: true, 285 disableVPCCNI: false, 286 kubeProxy: KubeProxy{ 287 Disable: true, 288 }, 289 }, 290 { 291 name: "disable vpc cni and disable kube-proxy allowed with no addons", 292 eksClusterName: "default_cluster1", 293 eksVersion: "v1.19", 294 expectError: false, 295 hasAddons: false, 296 disableVPCCNI: true, 297 kubeProxy: KubeProxy{ 298 Disable: true, 299 }, 300 }, 301 { 302 name: "disable vpc cni and disable kube-proxy not allowed with vpc cni and kube-proxy addons", 303 eksClusterName: "default_cluster1", 304 eksVersion: "v1.19", 305 expectError: true, 306 hasAddons: true, 307 disableVPCCNI: true, 308 kubeProxy: KubeProxy{ 309 Disable: true, 310 }, 311 }, 312 } 313 314 for _, tc := range tests { 315 t.Run(tc.name, func(t *testing.T) { 316 ctx := context.TODO() 317 g := NewWithT(t) 318 319 mcp := &AWSManagedControlPlane{ 320 ObjectMeta: metav1.ObjectMeta{ 321 GenerateName: "mcp-", 322 Namespace: "default", 323 }, 324 Spec: AWSManagedControlPlaneSpec{ 325 EKSClusterName: tc.eksClusterName, 326 DisableVPCCNI: tc.disableVPCCNI, 327 KubeProxy: tc.kubeProxy, 328 AdditionalTags: tc.additionalTags, 329 }, 330 } 331 if tc.eksVersion != "" { 332 mcp.Spec.Version = &tc.eksVersion 333 } 334 if tc.hasAddons { 335 testAddons := []Addon{ 336 { 337 Name: vpcCniAddon, 338 Version: "v1.0.0", 339 }, 340 { 341 Name: kubeProxyAddon, 342 Version: "v1.0.0", 343 }, 344 } 345 mcp.Spec.Addons = &testAddons 346 } 347 if tc.secondaryCidr != nil { 348 mcp.Spec.SecondaryCidrBlock = tc.secondaryCidr 349 } 350 351 err := testEnv.Create(ctx, mcp) 352 353 if tc.expectError { 354 g.Expect(err).ToNot(BeNil()) 355 } else { 356 g.Expect(err).To(BeNil()) 357 } 358 }) 359 } 360 } 361 362 func TestWebhookUpdate(t *testing.T) { 363 tests := []struct { 364 name string 365 oldClusterSpec AWSManagedControlPlaneSpec 366 newClusterSpec AWSManagedControlPlaneSpec 367 oldClusterName string 368 newClusterName string 369 oldEksVersion string 370 newEksVersion string 371 expectError bool 372 }{ 373 { 374 name: "ekscluster specified, same cluster names", 375 oldClusterSpec: AWSManagedControlPlaneSpec{ 376 EKSClusterName: "default_cluster1", 377 }, 378 newClusterSpec: AWSManagedControlPlaneSpec{ 379 EKSClusterName: "default_cluster1", 380 }, 381 expectError: false, 382 }, 383 { 384 name: "ekscluster specified, different cluster names", 385 oldClusterSpec: AWSManagedControlPlaneSpec{ 386 EKSClusterName: "default_cluster1", 387 }, 388 newClusterSpec: AWSManagedControlPlaneSpec{ 389 EKSClusterName: "default_cluster2", 390 }, 391 expectError: true, 392 }, 393 { 394 name: "old ekscluster specified, no new cluster name", 395 oldClusterSpec: AWSManagedControlPlaneSpec{ 396 EKSClusterName: "default_cluster1", 397 }, 398 newClusterSpec: AWSManagedControlPlaneSpec{ 399 EKSClusterName: "", 400 }, 401 expectError: true, 402 }, 403 { 404 name: "older version", 405 oldClusterSpec: AWSManagedControlPlaneSpec{ 406 EKSClusterName: "default_cluster1", 407 Version: &vV1_17, 408 }, 409 newClusterSpec: AWSManagedControlPlaneSpec{ 410 EKSClusterName: "default_cluster1", 411 Version: &vV1_16, 412 }, 413 expectError: true, 414 }, 415 { 416 name: "same version", 417 oldClusterSpec: AWSManagedControlPlaneSpec{ 418 EKSClusterName: "default_cluster1", 419 Version: &vV1_17, 420 }, 421 newClusterSpec: AWSManagedControlPlaneSpec{ 422 EKSClusterName: "default_cluster1", 423 Version: &vV1_17, 424 }, 425 expectError: false, 426 }, 427 { 428 name: "newer version", 429 oldClusterSpec: AWSManagedControlPlaneSpec{ 430 EKSClusterName: "default_cluster1", 431 Version: &vV1_16, 432 }, 433 newClusterSpec: AWSManagedControlPlaneSpec{ 434 EKSClusterName: "default_cluster1", 435 Version: &vV1_17, 436 }, 437 expectError: false, 438 }, 439 { 440 name: "change in encryption config to nil", 441 oldClusterSpec: AWSManagedControlPlaneSpec{ 442 EKSClusterName: "default_cluster1", 443 EncryptionConfig: &EncryptionConfig{ 444 Provider: pointer.String("provider"), 445 Resources: []*string{pointer.String("foo"), pointer.String("bar")}, 446 }, 447 }, 448 newClusterSpec: AWSManagedControlPlaneSpec{ 449 EKSClusterName: "default_cluster1", 450 }, 451 expectError: true, 452 }, 453 { 454 name: "change in encryption config from nil to valid encryption-config", 455 oldClusterSpec: AWSManagedControlPlaneSpec{ 456 EKSClusterName: "default_cluster1", 457 }, 458 newClusterSpec: AWSManagedControlPlaneSpec{ 459 EKSClusterName: "default_cluster1", 460 EncryptionConfig: &EncryptionConfig{ 461 Provider: pointer.String("provider"), 462 Resources: []*string{pointer.String("foo"), pointer.String("bar")}, 463 }, 464 }, 465 expectError: false, 466 }, 467 { 468 name: "change in provider of encryption config", 469 oldClusterSpec: AWSManagedControlPlaneSpec{ 470 EKSClusterName: "default_cluster1", 471 EncryptionConfig: &EncryptionConfig{ 472 Provider: pointer.String("provider"), 473 Resources: []*string{pointer.String("foo"), pointer.String("bar")}, 474 }, 475 }, 476 newClusterSpec: AWSManagedControlPlaneSpec{ 477 EKSClusterName: "default_cluster1", 478 EncryptionConfig: &EncryptionConfig{ 479 Provider: pointer.String("new-provider"), 480 Resources: []*string{pointer.String("foo"), pointer.String("bar")}, 481 }, 482 }, 483 expectError: true, 484 }, 485 { 486 name: "no change in provider of encryption config", 487 oldClusterSpec: AWSManagedControlPlaneSpec{ 488 EKSClusterName: "default_cluster1", 489 EncryptionConfig: &EncryptionConfig{ 490 Provider: pointer.String("provider"), 491 }, 492 }, 493 newClusterSpec: AWSManagedControlPlaneSpec{ 494 EKSClusterName: "default_cluster1", 495 EncryptionConfig: &EncryptionConfig{ 496 Provider: pointer.String("provider"), 497 }, 498 }, 499 expectError: false, 500 }, 501 { 502 name: "ekscluster specified, same name, invalid tags", 503 oldClusterSpec: AWSManagedControlPlaneSpec{ 504 EKSClusterName: "default_cluster1", 505 }, 506 newClusterSpec: AWSManagedControlPlaneSpec{ 507 EKSClusterName: "default_cluster1", 508 AdditionalTags: infrav1.Tags{ 509 "key-1": "value-1", 510 "": "value-2", 511 strings.Repeat("CAPI", 33): "value-3", 512 "key-4": strings.Repeat("CAPI", 65), 513 }, 514 }, 515 expectError: true, 516 }, 517 } 518 519 for _, tc := range tests { 520 t.Run(tc.name, func(t *testing.T) { 521 g := NewWithT(t) 522 ctx := context.TODO() 523 mcp := &AWSManagedControlPlane{ 524 ObjectMeta: metav1.ObjectMeta{ 525 GenerateName: "mcp-", 526 Namespace: "default", 527 }, 528 Spec: tc.oldClusterSpec, 529 } 530 g.Expect(testEnv.Create(ctx, mcp)).To(Succeed()) 531 mcp.Spec = tc.newClusterSpec 532 err := testEnv.Update(ctx, mcp) 533 534 if tc.expectError { 535 g.Expect(err).ToNot(BeNil()) 536 } else { 537 g.Expect(err).To(BeNil()) 538 } 539 }) 540 } 541 } 542 543 func TestValidatingWebhookCreate_SecondaryCidr(t *testing.T) { 544 tests := []struct { 545 name string 546 expectError bool 547 cidrRange string 548 }{ 549 { 550 name: "complete range 1", 551 cidrRange: "100.64.0.0/10", 552 expectError: true, 553 }, 554 { 555 name: "complete range 2", 556 cidrRange: "198.19.0.0/16", 557 expectError: false, 558 }, 559 { 560 name: "subrange", 561 cidrRange: "100.67.0.0/16", 562 expectError: false, 563 }, 564 { 565 name: "invalid value", 566 cidrRange: "not a cidr range", 567 expectError: true, 568 }, 569 { 570 name: "unsupported range", 571 cidrRange: "10.0.0.1/20", 572 expectError: true, 573 }, 574 { 575 name: "too large", 576 cidrRange: "100.64.0.0/15", 577 expectError: true, 578 }, 579 { 580 name: "too small", 581 cidrRange: "100.64.0.0/29", 582 expectError: true, 583 }, 584 } 585 586 for _, tc := range tests { 587 t.Run(tc.name, func(t *testing.T) { 588 g := NewWithT(t) 589 590 mcp := &AWSManagedControlPlane{ 591 Spec: AWSManagedControlPlaneSpec{ 592 EKSClusterName: "default_cluster1", 593 }, 594 } 595 if tc.cidrRange != "" { 596 mcp.Spec.SecondaryCidrBlock = &tc.cidrRange 597 } 598 err := mcp.ValidateCreate() 599 600 if tc.expectError { 601 g.Expect(err).ToNot(BeNil()) 602 } else { 603 g.Expect(err).To(BeNil()) 604 } 605 }) 606 } 607 } 608 609 func TestValidatingWebhookUpdate_SecondaryCidr(t *testing.T) { 610 tests := []struct { 611 name string 612 cidrRange string 613 expectError bool 614 }{ 615 { 616 name: "complete range 1", 617 cidrRange: "100.64.0.0/10", 618 expectError: true, 619 }, 620 { 621 name: "complete range 2", 622 cidrRange: "198.19.0.0/16", 623 expectError: false, 624 }, 625 { 626 name: "subrange", 627 cidrRange: "100.67.0.0/16", 628 expectError: false, 629 }, 630 { 631 name: "invalid value", 632 cidrRange: "not a cidr range", 633 expectError: true, 634 }, 635 { 636 name: "unsupported range", 637 cidrRange: "10.0.0.1/20", 638 expectError: true, 639 }, 640 { 641 name: "too large", 642 cidrRange: "100.64.0.0/15", 643 expectError: true, 644 }, 645 { 646 name: "too small", 647 cidrRange: "100.64.0.0/29", 648 expectError: true, 649 }, 650 } 651 652 for _, tc := range tests { 653 t.Run(tc.name, func(t *testing.T) { 654 g := NewWithT(t) 655 656 newMCP := &AWSManagedControlPlane{ 657 Spec: AWSManagedControlPlaneSpec{ 658 EKSClusterName: "default_cluster1", 659 SecondaryCidrBlock: &tc.cidrRange, 660 }, 661 } 662 oldMCP := &AWSManagedControlPlane{ 663 Spec: AWSManagedControlPlaneSpec{ 664 EKSClusterName: "default_cluster1", 665 SecondaryCidrBlock: nil, 666 }, 667 } 668 669 err := newMCP.ValidateUpdate(oldMCP) 670 671 if tc.expectError { 672 g.Expect(err).ToNot(BeNil()) 673 } else { 674 g.Expect(err).To(BeNil()) 675 } 676 }) 677 } 678 }