sigs.k8s.io/cluster-api-provider-azure@v1.14.3/api/v1beta1/azurecluster_validation_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 "testing" 21 22 . "github.com/onsi/gomega" 23 corev1 "k8s.io/api/core/v1" 24 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 25 "k8s.io/apimachinery/pkg/util/validation/field" 26 "k8s.io/utils/ptr" 27 ) 28 29 func TestClusterNameValidation(t *testing.T) { 30 tests := []struct { 31 name string 32 clusterName string 33 wantErr bool 34 }{ 35 { 36 name: "cluster name more than 44 characters", 37 clusterName: "vegkebfadbczdtevzjiyookobkdgfofjxmlquonomzoes", 38 wantErr: true, 39 }, 40 { 41 name: "cluster name with letters", 42 clusterName: "cluster", 43 wantErr: false, 44 }, 45 { 46 name: "cluster name with upper case letters", 47 clusterName: "clusterName", 48 wantErr: true, 49 }, 50 { 51 name: "cluster name with hyphen", 52 clusterName: "test-cluster", 53 wantErr: false, 54 }, 55 { 56 name: "cluster name with letters and numbers", 57 clusterName: "clustername1", 58 wantErr: false, 59 }, 60 { 61 name: "cluster name with special characters", 62 clusterName: "cluster$?name", 63 wantErr: true, 64 }, 65 { 66 name: "cluster name starting with underscore", 67 clusterName: "_clustername", 68 wantErr: true, 69 }, 70 { 71 name: "cluster name starting with number", 72 clusterName: "1clustername", 73 wantErr: false, 74 }, 75 { 76 name: "cluster name with underscore", 77 clusterName: "cluster_name", 78 wantErr: true, 79 }, 80 { 81 name: "cluster name with period", 82 clusterName: "cluster.name", 83 wantErr: true, 84 }, 85 } 86 for _, tc := range tests { 87 t.Run(tc.name, func(t *testing.T) { 88 g := NewWithT(t) 89 azureCluster := AzureCluster{ 90 ObjectMeta: metav1.ObjectMeta{ 91 Name: tc.clusterName, 92 }, 93 } 94 95 allErrs := azureCluster.validateClusterName() 96 if tc.wantErr { 97 g.Expect(allErrs).NotTo(BeNil()) 98 } else { 99 g.Expect(allErrs).To(BeNil()) 100 } 101 }) 102 } 103 } 104 105 func TestClusterWithPreexistingVnetValid(t *testing.T) { 106 type tests struct { 107 name string 108 cluster *AzureCluster 109 } 110 testCase := []tests{ 111 { 112 name: "azurecluster with pre-existing vnet - valid", 113 cluster: createValidCluster(), 114 }, 115 { 116 name: "azurecluster with pre-existing vnet and cluster subnet - valid", 117 cluster: createValidClusterWithClusterSubnet(), 118 }, 119 } 120 for _, tc := range testCase { 121 t.Run(tc.name, func(t *testing.T) { 122 g := NewWithT(t) 123 _, err := tc.cluster.validateCluster(nil) 124 g.Expect(err).NotTo(HaveOccurred()) 125 }) 126 } 127 } 128 129 func TestClusterWithPreexistingVnetInvalid(t *testing.T) { 130 type test struct { 131 name string 132 cluster *AzureCluster 133 } 134 135 testCase := test{ 136 name: "azurecluster with pre-existing vnet - invalid", 137 cluster: createValidCluster(), 138 } 139 140 // invalid because it doesn't specify a controlplane subnet 141 testCase.cluster.Spec.NetworkSpec.Subnets[0] = SubnetSpec{ 142 SubnetClassSpec: SubnetClassSpec{ 143 Role: "random", 144 Name: "random-subnet", 145 }, 146 } 147 148 t.Run(testCase.name, func(t *testing.T) { 149 g := NewWithT(t) 150 _, err := testCase.cluster.validateCluster(nil) 151 g.Expect(err).To(HaveOccurred()) 152 }) 153 } 154 155 func TestClusterWithoutPreexistingVnetValid(t *testing.T) { 156 type tests struct { 157 name string 158 cluster *AzureCluster 159 } 160 161 testCase := []tests{ 162 { 163 name: "azurecluster without pre-existing vnet - valid", 164 cluster: createValidCluster(), 165 }, 166 { 167 name: "azurecluster without pre-existing vnet with cluster subnet - valid", 168 cluster: createValidClusterWithClusterSubnet(), 169 }, 170 } 171 for _, tc := range testCase { 172 // When ResourceGroup is an empty string, the cluster doesn't 173 // have a pre-existing vnet. 174 tc.cluster.Spec.NetworkSpec.Vnet.ResourceGroup = "" 175 176 t.Run(tc.name, func(t *testing.T) { 177 g := NewWithT(t) 178 _, err := tc.cluster.validateCluster(nil) 179 g.Expect(err).NotTo(HaveOccurred()) 180 }) 181 } 182 } 183 184 func TestClusterSpecWithPreexistingVnetValid(t *testing.T) { 185 type tests struct { 186 name string 187 cluster *AzureCluster 188 } 189 190 testCase := []tests{ 191 { 192 name: "azurecluster spec with pre-existing vnet - valid", 193 cluster: createValidCluster(), 194 }, 195 { 196 name: "azurecluster spec with pre-existing vnet with cluster subnet - valid", 197 cluster: createValidClusterWithClusterSubnet(), 198 }, 199 } 200 for _, tc := range testCase { 201 t.Run(tc.name, func(t *testing.T) { 202 g := NewWithT(t) 203 errs := tc.cluster.validateClusterSpec(nil) 204 g.Expect(errs).To(BeNil()) 205 }) 206 } 207 } 208 209 func TestClusterSpecWithPreexistingVnetInvalid(t *testing.T) { 210 type test struct { 211 name string 212 cluster *AzureCluster 213 } 214 215 testCase := test{ 216 name: "azurecluster spec with pre-existing vnet - invalid", 217 cluster: createValidCluster(), 218 } 219 220 // invalid because it doesn't specify a controlplane subnet 221 testCase.cluster.Spec.NetworkSpec.Subnets[0] = SubnetSpec{ 222 SubnetClassSpec: SubnetClassSpec{ 223 Role: "random", 224 Name: "random-subnet", 225 }, 226 } 227 228 t.Run(testCase.name, func(t *testing.T) { 229 g := NewWithT(t) 230 errs := testCase.cluster.validateClusterSpec(nil) 231 g.Expect(errs).NotTo(BeEmpty()) 232 }) 233 } 234 235 func TestClusterSpecWithoutPreexistingVnetValid(t *testing.T) { 236 type test struct { 237 name string 238 cluster *AzureCluster 239 } 240 241 testCase := test{ 242 name: "azurecluster spec without pre-existing vnet - valid", 243 cluster: createValidCluster(), 244 } 245 246 // When ResourceGroup is an empty string, the cluster doesn't 247 // have a pre-existing vnet. 248 testCase.cluster.Spec.NetworkSpec.Vnet.ResourceGroup = "" 249 250 t.Run(testCase.name, func(t *testing.T) { 251 g := NewWithT(t) 252 errs := testCase.cluster.validateClusterSpec(nil) 253 g.Expect(errs).To(BeNil()) 254 }) 255 } 256 257 func TestClusterSpecWithoutIdentityRefInvalid(t *testing.T) { 258 type test struct { 259 name string 260 cluster *AzureCluster 261 } 262 263 testCase := test{ 264 name: "azurecluster spec without identityRef - invalid", 265 cluster: createValidCluster(), 266 } 267 268 // invalid because it doesn't specify an identityRef 269 testCase.cluster.Spec.IdentityRef = nil 270 271 t.Run(testCase.name, func(t *testing.T) { 272 g := NewWithT(t) 273 errs := testCase.cluster.validateClusterSpec(nil) 274 g.Expect(errs).NotTo(BeEmpty()) 275 }) 276 } 277 278 func TestClusterSpecWithWrongKindInvalid(t *testing.T) { 279 type test struct { 280 name string 281 cluster *AzureCluster 282 } 283 284 testCase := test{ 285 name: "azurecluster spec with wrong kind - invalid", 286 cluster: createValidCluster(), 287 } 288 289 // invalid because it doesn't specify AzureClusterIdentity as the kind 290 testCase.cluster.Spec.IdentityRef.Kind = "bad" 291 292 t.Run(testCase.name, func(t *testing.T) { 293 g := NewWithT(t) 294 errs := testCase.cluster.validateClusterSpec(nil) 295 g.Expect(errs).NotTo(BeEmpty()) 296 }) 297 } 298 299 func TestNetworkSpecWithPreexistingVnetValid(t *testing.T) { 300 type tests struct { 301 name string 302 networkSpec NetworkSpec 303 } 304 305 testCase := []tests{ 306 { 307 name: "azurecluster networkspec with pre-existing vnet - valid", 308 networkSpec: createValidNetworkSpec(), 309 }, 310 { 311 name: "azurecluster networkspec with pre-existing vnet only cluster subnet - valid", 312 networkSpec: createClusterNetworkSpec(), 313 }, 314 } 315 316 for _, test := range testCase { 317 t.Run(test.name, func(t *testing.T) { 318 g := NewWithT(t) 319 errs := validateNetworkSpec(test.networkSpec, NetworkSpec{}, field.NewPath("spec").Child("networkSpec")) 320 g.Expect(errs).To(BeNil()) 321 }) 322 } 323 } 324 325 func TestNetworkSpecWithPreexistingVnetLackRequiredSubnets(t *testing.T) { 326 type test struct { 327 name string 328 networkSpec NetworkSpec 329 } 330 331 testCase := test{ 332 name: "azurecluster networkspec with pre-existing vnet - lack required subnets", 333 networkSpec: createValidNetworkSpec(), 334 } 335 336 // invalid because it doesn't specify a node subnet 337 testCase.networkSpec.Subnets = testCase.networkSpec.Subnets[:1] 338 339 t.Run(testCase.name, func(t *testing.T) { 340 g := NewWithT(t) 341 errs := validateNetworkSpec(testCase.networkSpec, NetworkSpec{}, field.NewPath("spec").Child("networkSpec")) 342 g.Expect(errs).To(HaveLen(1)) 343 g.Expect(errs[0].Type).To(Equal(field.ErrorTypeRequired)) 344 g.Expect(errs[0].Field).To(Equal("spec.networkSpec.subnets")) 345 g.Expect(errs[0].Error()).To(ContainSubstring("required role node not included")) 346 }) 347 } 348 349 func TestNetworkSpecWithPreexistingVnetInvalidResourceGroup(t *testing.T) { 350 type test struct { 351 name string 352 networkSpec NetworkSpec 353 } 354 355 testCase := test{ 356 name: "azurecluster networkspec with pre-existing vnet - invalid resource group", 357 networkSpec: createValidNetworkSpec(), 358 } 359 360 testCase.networkSpec.Vnet.ResourceGroup = "invalid-name###" 361 362 t.Run(testCase.name, func(t *testing.T) { 363 g := NewWithT(t) 364 errs := validateNetworkSpec(testCase.networkSpec, NetworkSpec{}, field.NewPath("spec").Child("networkSpec")) 365 g.Expect(errs).To(HaveLen(1)) 366 g.Expect(errs[0].Type).To(Equal(field.ErrorTypeInvalid)) 367 g.Expect(errs[0].Field).To(Equal("spec.networkSpec.vnet.resourceGroup")) 368 g.Expect(errs[0].BadValue).To(BeEquivalentTo(testCase.networkSpec.Vnet.ResourceGroup)) 369 }) 370 } 371 372 func TestNetworkSpecWithoutPreexistingVnetValid(t *testing.T) { 373 type test struct { 374 name string 375 networkSpec NetworkSpec 376 } 377 378 testCase := test{ 379 name: "azurecluster networkspec without pre-existing vnet - valid", 380 networkSpec: createValidNetworkSpec(), 381 } 382 383 testCase.networkSpec.Vnet.ResourceGroup = "" 384 385 t.Run(testCase.name, func(t *testing.T) { 386 g := NewWithT(t) 387 errs := validateNetworkSpec(testCase.networkSpec, NetworkSpec{}, field.NewPath("spec").Child("networkSpec")) 388 g.Expect(errs).To(BeNil()) 389 }) 390 } 391 392 func TestResourceGroupValid(t *testing.T) { 393 type test struct { 394 name string 395 resourceGroup string 396 } 397 398 testCase := test{ 399 name: "resourcegroup name - valid", 400 resourceGroup: "custom-vnet", 401 } 402 403 t.Run(testCase.name, func(t *testing.T) { 404 g := NewWithT(t) 405 err := validateResourceGroup(testCase.resourceGroup, 406 field.NewPath("spec").Child("networkSpec").Child("vnet").Child("resourceGroup")) 407 g.Expect(err).NotTo(HaveOccurred()) 408 }) 409 } 410 411 func TestResourceGroupInvalid(t *testing.T) { 412 type test struct { 413 name string 414 resourceGroup string 415 } 416 417 testCase := test{ 418 name: "resourcegroup name - invalid", 419 resourceGroup: "inv@lid-rg", 420 } 421 422 t.Run(testCase.name, func(t *testing.T) { 423 g := NewWithT(t) 424 err := validateResourceGroup(testCase.resourceGroup, 425 field.NewPath("spec").Child("networkSpec").Child("vnet").Child("resourceGroup")) 426 g.Expect(err).NotTo(BeNil()) 427 g.Expect(err.Type).To(Equal(field.ErrorTypeInvalid)) 428 g.Expect(err.Field).To(Equal("spec.networkSpec.vnet.resourceGroup")) 429 g.Expect(err.BadValue).To(BeEquivalentTo(testCase.resourceGroup)) 430 }) 431 } 432 433 func TestValidateVnetCIDR(t *testing.T) { 434 tests := []struct { 435 name string 436 vnetCidrBlocks []string 437 wantErr bool 438 expectedErr field.Error 439 }{ 440 { 441 name: "valid subnet cidr", 442 vnetCidrBlocks: []string{"10.0.0.0/8"}, 443 wantErr: false, 444 }, 445 { 446 name: "invalid subnet cidr not in the right format", 447 vnetCidrBlocks: []string{"10.0.0.0/8", "foo/bar"}, 448 wantErr: true, 449 expectedErr: field.Error{ 450 Type: "FieldValueInvalid", 451 Field: "vnet.cidrBlocks", 452 BadValue: "foo/bar", 453 Detail: "invalid CIDR format", 454 }, 455 }, 456 } 457 for _, testCase := range tests { 458 t.Run(testCase.name, func(t *testing.T) { 459 g := NewWithT(t) 460 err := validateVnetCIDR(testCase.vnetCidrBlocks, field.NewPath("vnet.cidrBlocks")) 461 if testCase.wantErr { 462 g.Expect(err).To(ContainElement(MatchError(testCase.expectedErr.Error()))) 463 } else { 464 g.Expect(err).To(BeEmpty()) 465 } 466 }) 467 } 468 } 469 470 func TestClusterSubnetsValid(t *testing.T) { 471 type test struct { 472 name string 473 subnets Subnets 474 err field.ErrorList 475 } 476 var nilList field.ErrorList 477 testCases := []test{ 478 { 479 name: "subnets - valid", 480 subnets: Subnets{ 481 { 482 SubnetClassSpec: SubnetClassSpec{ 483 Role: SubnetCluster, 484 Name: "cluster-subnet-1", 485 }, 486 }, 487 { 488 SubnetClassSpec: SubnetClassSpec{ 489 Role: SubnetCluster, 490 Name: "cluster-subnet-2", 491 }, 492 }, 493 }, 494 err: nilList, 495 }, 496 { 497 name: "duplicate subnets - invalid", 498 subnets: Subnets{ 499 { 500 SubnetClassSpec: SubnetClassSpec{ 501 Role: SubnetCluster, 502 Name: "cluster-subnet-1", 503 }, 504 }, 505 { 506 SubnetClassSpec: SubnetClassSpec{ 507 Role: SubnetCluster, 508 Name: "cluster-subnet-1", 509 }, 510 }, 511 { 512 SubnetClassSpec: SubnetClassSpec{ 513 Role: SubnetCluster, 514 Name: "#$cluster-subnet-1", 515 }, 516 }, 517 }, 518 err: field.ErrorList{ 519 { 520 Type: "FieldValueDuplicate", 521 Field: "spec.networkSpec.subnets", 522 BadValue: "cluster-subnet-1", 523 }, 524 { 525 Type: "FieldValueInvalid", 526 Field: "spec.networkSpec.subnets[2].name", 527 BadValue: "#$cluster-subnet-1", 528 Detail: "name of subnet doesn't match regex ^[-\\w\\._]+$", 529 }, 530 }, 531 }, 532 { 533 name: "no subnet", 534 subnets: Subnets{}, 535 err: field.ErrorList{ 536 { 537 Type: "FieldValueRequired", 538 Field: "spec.networkSpec.subnets", 539 BadValue: "", 540 Detail: "required role control-plane not included in provided subnets", 541 }, 542 { 543 Type: "FieldValueRequired", 544 Field: "spec.networkSpec.subnets", 545 BadValue: "", 546 Detail: "required role node not included in provided subnets", 547 }, 548 }, 549 }, 550 } 551 for _, tc := range testCases { 552 t.Run(tc.name, func(t *testing.T) { 553 g := NewWithT(t) 554 errs := validateSubnets(tc.subnets, createValidVnet(), 555 field.NewPath("spec").Child("networkSpec").Child("subnets")) 556 g.Expect(errs).To(ConsistOf(tc.err)) 557 }) 558 } 559 } 560 561 func TestSubnetsValid(t *testing.T) { 562 type test struct { 563 name string 564 subnets Subnets 565 } 566 567 testCase := test{ 568 name: "subnets - valid", 569 subnets: createValidSubnets(), 570 } 571 572 t.Run(testCase.name, func(t *testing.T) { 573 g := NewWithT(t) 574 errs := validateSubnets(testCase.subnets, createValidVnet(), 575 field.NewPath("spec").Child("networkSpec").Child("subnets")) 576 g.Expect(errs).To(BeNil()) 577 }) 578 } 579 580 func TestSubnetsInvalidSubnetName(t *testing.T) { 581 type test struct { 582 name string 583 subnets Subnets 584 } 585 586 testCase := test{ 587 name: "subnets - invalid subnet name", 588 subnets: createValidSubnets(), 589 } 590 591 testCase.subnets[0].Name = "invalid-subnet-name-due-to-bracket)" 592 593 t.Run(testCase.name, func(t *testing.T) { 594 g := NewWithT(t) 595 errs := validateSubnets(testCase.subnets, createValidVnet(), 596 field.NewPath("spec").Child("networkSpec").Child("subnets")) 597 g.Expect(errs).To(HaveLen(1)) 598 g.Expect(errs[0].Type).To(Equal(field.ErrorTypeInvalid)) 599 g.Expect(errs[0].Field).To(Equal("spec.networkSpec.subnets[0].name")) 600 g.Expect(errs[0].BadValue).To(BeEquivalentTo("invalid-subnet-name-due-to-bracket)")) 601 }) 602 } 603 604 func TestSubnetsInvalidLackRequiredSubnet(t *testing.T) { 605 type test struct { 606 name string 607 subnets Subnets 608 } 609 610 testCase := test{ 611 name: "subnets - lack required subnet", 612 subnets: createValidSubnets(), 613 } 614 615 testCase.subnets[0].Role = "random-role" 616 617 t.Run(testCase.name, func(t *testing.T) { 618 g := NewWithT(t) 619 errs := validateSubnets(testCase.subnets, createValidVnet(), 620 field.NewPath("spec").Child("networkSpec").Child("subnets")) 621 g.Expect(errs).To(HaveLen(1)) 622 g.Expect(errs[0].Type).To(Equal(field.ErrorTypeRequired)) 623 g.Expect(errs[0].Field).To(Equal("spec.networkSpec.subnets")) 624 g.Expect(errs[0].Detail).To(ContainSubstring("required role control-plane not included")) 625 }) 626 } 627 628 func TestSubnetNamesNotUnique(t *testing.T) { 629 type test struct { 630 name string 631 subnets Subnets 632 } 633 634 testCase := test{ 635 name: "subnets - names not unique", 636 subnets: createValidSubnets(), 637 } 638 639 testCase.subnets[0].Name = "subnet-name" 640 testCase.subnets[1].Name = "subnet-name" 641 642 t.Run(testCase.name, func(t *testing.T) { 643 g := NewWithT(t) 644 errs := validateSubnets(testCase.subnets, createValidVnet(), 645 field.NewPath("spec").Child("networkSpec").Child("subnets")) 646 g.Expect(errs).To(HaveLen(1)) 647 g.Expect(errs[0].Type).To(Equal(field.ErrorTypeDuplicate)) 648 g.Expect(errs[0].Field).To(Equal("spec.networkSpec.subnets")) 649 }) 650 } 651 652 func TestSubnetNameValid(t *testing.T) { 653 type test struct { 654 name string 655 subnetName string 656 } 657 658 testCase := test{ 659 name: "subnet name - valid", 660 subnetName: "control-plane-subnet", 661 } 662 663 t.Run(testCase.name, func(t *testing.T) { 664 g := NewWithT(t) 665 err := validateSubnetName(testCase.subnetName, 666 field.NewPath("spec").Child("networkSpec").Child("subnets").Index(0).Child("name")) 667 g.Expect(err).NotTo(HaveOccurred()) 668 }) 669 } 670 671 func TestSubnetNameInvalid(t *testing.T) { 672 type test struct { 673 name string 674 subnetName string 675 } 676 677 testCase := test{ 678 name: "subnet name - invalid", 679 subnetName: "inv@lid-subnet-name", 680 } 681 682 t.Run(testCase.name, func(t *testing.T) { 683 g := NewWithT(t) 684 err := validateSubnetName(testCase.subnetName, 685 field.NewPath("spec").Child("networkSpec").Child("subnets").Index(0).Child("name")) 686 g.Expect(err).NotTo(BeNil()) 687 g.Expect(err.Type).To(Equal(field.ErrorTypeInvalid)) 688 g.Expect(err.Field).To(Equal("spec.networkSpec.subnets[0].name")) 689 g.Expect(err.BadValue).To(BeEquivalentTo(testCase.subnetName)) 690 }) 691 } 692 693 func TestValidateSubnetCIDR(t *testing.T) { 694 tests := []struct { 695 name string 696 vnetCidrBlocks []string 697 subnetCidrBlocks []string 698 wantErr bool 699 expectedErr field.Error 700 }{ 701 { 702 name: "valid subnet cidr", 703 vnetCidrBlocks: []string{"10.0.0.0/8"}, 704 subnetCidrBlocks: []string{"10.1.0.0/16", "10.0.0.0/16"}, 705 wantErr: false, 706 }, 707 { 708 name: "invalid subnet cidr not in the right format", 709 vnetCidrBlocks: []string{"10.0.0.0/8"}, 710 subnetCidrBlocks: []string{"10.1.0.0/16", "10.0.0.0/16", "foo/bar"}, 711 wantErr: true, 712 expectedErr: field.Error{ 713 Type: "FieldValueInvalid", 714 Field: "subnets.cidrBlocks", 715 BadValue: "foo/bar", 716 Detail: "invalid CIDR format", 717 }, 718 }, 719 { 720 name: "subnet cidr not in vnet range", 721 vnetCidrBlocks: []string{"10.0.0.0/8"}, 722 subnetCidrBlocks: []string{"10.1.0.0/16", "10.0.0.0/16", "11.1.0.0/16"}, 723 wantErr: true, 724 expectedErr: field.Error{ 725 Type: "FieldValueInvalid", 726 Field: "subnets.cidrBlocks", 727 BadValue: "11.1.0.0/16", 728 Detail: "subnet CIDR not in vnet address space: [10.0.0.0/8]", 729 }, 730 }, 731 { 732 name: "subnet cidr in at least one vnet's range in case of multiple vnet cidr blocks", 733 vnetCidrBlocks: []string{"10.0.0.0/8", "11.0.0.0/8"}, 734 subnetCidrBlocks: []string{"10.1.0.0/16", "10.0.0.0/16", "11.1.0.0/16"}, 735 wantErr: false, 736 }, 737 } 738 for _, testCase := range tests { 739 t.Run(testCase.name, func(t *testing.T) { 740 g := NewWithT(t) 741 err := validateSubnetCIDR(testCase.subnetCidrBlocks, testCase.vnetCidrBlocks, field.NewPath("subnets.cidrBlocks")) 742 if testCase.wantErr { 743 // Searches for expected error in list of thrown errors 744 g.Expect(err).To(ContainElement(MatchError(testCase.expectedErr.Error()))) 745 } else { 746 g.Expect(err).To(BeEmpty()) 747 } 748 }) 749 } 750 } 751 752 func TestValidateSecurityRule(t *testing.T) { 753 tests := []struct { 754 name string 755 validRule SecurityRule 756 wantErr bool 757 }{ 758 { 759 name: "security rule - valid priority", 760 validRule: SecurityRule{ 761 Name: "allow_apiserver", 762 Description: "Allow K8s API Server", 763 Priority: 101, 764 }, 765 wantErr: false, 766 }, 767 { 768 name: "security rule - invalid low priority", 769 validRule: SecurityRule{ 770 Name: "allow_apiserver", 771 Description: "Allow K8s API Server", 772 Priority: 99, 773 }, 774 wantErr: true, 775 }, 776 { 777 name: "security rule - invalid high priority", 778 validRule: SecurityRule{ 779 Name: "allow_apiserver", 780 Description: "Allow K8s API Server", 781 Priority: 5000, 782 }, 783 wantErr: true, 784 }, 785 { 786 name: "security rule - invalid sources priority", 787 validRule: SecurityRule{ 788 Name: "allow_apiserver", 789 Description: "Allow K8s API Server", 790 Priority: 4000, 791 Source: ptr.To("*"), 792 Sources: []*string{ 793 ptr.To("*"), 794 ptr.To("unknown"), 795 }, 796 }, 797 wantErr: true, 798 }, 799 { 800 name: "security rule - valid sources", 801 validRule: SecurityRule{ 802 Name: "allow_apiserver", 803 Description: "Allow K8s API Server", 804 Priority: 4000, 805 Sources: []*string{ 806 ptr.To("*"), 807 ptr.To("unknown"), 808 }, 809 }, 810 wantErr: false, 811 }, 812 } 813 for _, testCase := range tests { 814 testCase := testCase 815 t.Run(testCase.name, func(t *testing.T) { 816 t.Parallel() 817 g := NewWithT(t) 818 errs := validateSecurityRule( 819 testCase.validRule, 820 field.NewPath("spec").Child("networkSpec").Child("subnets").Index(0).Child("securityGroup").Child("securityRules").Index(0), 821 ) 822 if testCase.wantErr { 823 g.Expect(errs).NotTo(BeNil()) 824 g.Expect(errs).To(HaveLen(1)) 825 } else { 826 g.Expect(errs).To(BeNil()) 827 g.Expect(errs).To(BeEmpty()) 828 } 829 }) 830 } 831 } 832 833 func TestValidateAPIServerLB(t *testing.T) { 834 testcases := []struct { 835 name string 836 lb LoadBalancerSpec 837 old LoadBalancerSpec 838 cpCIDRS []string 839 wantErr bool 840 expectedErr field.Error 841 }{ 842 { 843 name: "invalid SKU", 844 lb: LoadBalancerSpec{ 845 Name: "my-awesome-lb", 846 FrontendIPs: []FrontendIP{ 847 { 848 Name: "ip-config", 849 }, 850 }, 851 LoadBalancerClassSpec: LoadBalancerClassSpec{ 852 SKU: "Awesome", 853 Type: Public, 854 }, 855 }, 856 wantErr: true, 857 expectedErr: field.Error{ 858 Type: "FieldValueNotSupported", 859 Field: "apiServerLB.sku", 860 BadValue: "Awesome", 861 Detail: "supported values: \"Standard\"", 862 }, 863 }, 864 { 865 name: "invalid Type", 866 lb: LoadBalancerSpec{ 867 LoadBalancerClassSpec: LoadBalancerClassSpec{ 868 Type: "Foo", 869 }, 870 }, 871 wantErr: true, 872 expectedErr: field.Error{ 873 Type: "FieldValueNotSupported", 874 Field: "apiServerLB.type", 875 BadValue: "Foo", 876 Detail: "supported values: \"Public\", \"Internal\"", 877 }, 878 }, 879 { 880 name: "invalid Name", 881 lb: LoadBalancerSpec{ 882 Name: "***", 883 }, 884 wantErr: true, 885 expectedErr: field.Error{ 886 Type: "FieldValueInvalid", 887 Field: "apiServerLB.name", 888 BadValue: "***", 889 Detail: "name of load balancer doesn't match regex ^[-\\w\\._]+$", 890 }, 891 }, 892 { 893 name: "too many IP configs", 894 lb: LoadBalancerSpec{ 895 FrontendIPs: []FrontendIP{ 896 { 897 Name: "ip-1", 898 }, 899 { 900 Name: "ip-2", 901 }, 902 }, 903 }, 904 wantErr: true, 905 expectedErr: field.Error{ 906 Type: "FieldValueInvalid", 907 Field: "apiServerLB.frontendIPConfigs", 908 BadValue: []FrontendIP{ 909 { 910 Name: "ip-1", 911 }, 912 { 913 Name: "ip-2", 914 }, 915 }, 916 Detail: "API Server Load balancer should have 1 Frontend IP", 917 }, 918 }, 919 { 920 name: "public LB with private IP", 921 lb: LoadBalancerSpec{ 922 FrontendIPs: []FrontendIP{ 923 { 924 Name: "ip-1", 925 FrontendIPClass: FrontendIPClass{ 926 PrivateIPAddress: "10.0.0.4", 927 }, 928 }, 929 }, 930 LoadBalancerClassSpec: LoadBalancerClassSpec{ 931 Type: Public, 932 }, 933 }, 934 wantErr: true, 935 expectedErr: field.Error{ 936 Type: "FieldValueForbidden", 937 Field: "apiServerLB.frontendIPConfigs[0].privateIP", 938 Detail: "Public Load Balancers cannot have a Private IP", 939 }, 940 }, 941 { 942 name: "internal LB with public IP", 943 lb: LoadBalancerSpec{ 944 FrontendIPs: []FrontendIP{ 945 { 946 Name: "ip-1", 947 PublicIP: &PublicIPSpec{ 948 Name: "my-invalid-ip", 949 }, 950 }, 951 }, 952 LoadBalancerClassSpec: LoadBalancerClassSpec{ 953 Type: Internal, 954 }, 955 }, 956 wantErr: true, 957 expectedErr: field.Error{ 958 Type: "FieldValueForbidden", 959 Field: "apiServerLB.frontendIPConfigs[0].publicIP", 960 Detail: "Internal Load Balancers cannot have a Public IP", 961 }, 962 }, 963 { 964 name: "internal LB with invalid private IP", 965 lb: LoadBalancerSpec{ 966 FrontendIPs: []FrontendIP{ 967 { 968 Name: "ip-1", 969 FrontendIPClass: FrontendIPClass{ 970 PrivateIPAddress: "NAIP", 971 }, 972 }, 973 }, 974 LoadBalancerClassSpec: LoadBalancerClassSpec{ 975 Type: Internal, 976 }, 977 }, 978 wantErr: true, 979 expectedErr: field.Error{ 980 Type: "FieldValueInvalid", 981 Field: "apiServerLB.frontendIPConfigs[0].privateIP", 982 BadValue: "NAIP", 983 Detail: "Internal LB IP address isn't a valid IPv4 or IPv6 address", 984 }, 985 }, 986 { 987 name: "internal LB with out of range private IP", 988 lb: LoadBalancerSpec{ 989 FrontendIPs: []FrontendIP{ 990 { 991 Name: "ip-1", 992 FrontendIPClass: FrontendIPClass{ 993 PrivateIPAddress: "20.1.2.3", 994 }, 995 }, 996 }, 997 LoadBalancerClassSpec: LoadBalancerClassSpec{ 998 Type: Internal, 999 }, 1000 }, 1001 cpCIDRS: []string{"10.0.0.0/24", "10.1.0.0/24"}, 1002 wantErr: true, 1003 expectedErr: field.Error{ 1004 Type: "FieldValueInvalid", 1005 Field: "apiServerLB.frontendIPConfigs[0].privateIP", 1006 BadValue: "20.1.2.3", 1007 Detail: "Internal LB IP address needs to be in control plane subnet range ([10.0.0.0/24 10.1.0.0/24])", 1008 }, 1009 }, 1010 { 1011 name: "internal LB with in range private IP", 1012 lb: LoadBalancerSpec{ 1013 FrontendIPs: []FrontendIP{ 1014 { 1015 Name: "ip-1", 1016 FrontendIPClass: FrontendIPClass{ 1017 PrivateIPAddress: "10.1.0.3", 1018 }, 1019 }, 1020 }, 1021 LoadBalancerClassSpec: LoadBalancerClassSpec{ 1022 Type: Internal, 1023 SKU: SKUStandard, 1024 }, 1025 Name: "my-private-lb", 1026 }, 1027 cpCIDRS: []string{"10.0.0.0/24", "10.1.0.0/24"}, 1028 wantErr: false, 1029 }, 1030 } 1031 1032 for _, test := range testcases { 1033 test := test 1034 t.Run(test.name, func(t *testing.T) { 1035 t.Parallel() 1036 g := NewWithT(t) 1037 err := validateAPIServerLB(test.lb, test.old, test.cpCIDRS, field.NewPath("apiServerLB")) 1038 if test.wantErr { 1039 g.Expect(err).To(ContainElement(MatchError(test.expectedErr.Error()))) 1040 } else { 1041 g.Expect(err).To(BeEmpty()) 1042 } 1043 }) 1044 } 1045 } 1046 func TestPrivateDNSZoneName(t *testing.T) { 1047 testcases := []struct { 1048 name string 1049 network NetworkSpec 1050 wantErr bool 1051 expectedErr field.Error 1052 }{ 1053 { 1054 name: "testInvalidPrivateDNSZoneName", 1055 network: NetworkSpec{ 1056 NetworkClassSpec: NetworkClassSpec{ 1057 PrivateDNSZoneName: "wrong@d_ns.io", 1058 }, 1059 APIServerLB: createValidAPIServerInternalLB(), 1060 }, 1061 expectedErr: field.Error{ 1062 Type: "FieldValueInvalid", 1063 Field: "spec.networkSpec.privateDNSZoneName", 1064 BadValue: "wrong@d_ns.io", 1065 Detail: "PrivateDNSZoneName can only contain alphanumeric characters, underscores and dashes, must end with an alphanumeric character", 1066 }, 1067 wantErr: true, 1068 }, 1069 { 1070 name: "testValidPrivateDNSZoneName", 1071 network: NetworkSpec{ 1072 NetworkClassSpec: NetworkClassSpec{ 1073 PrivateDNSZoneName: "good.dns.io", 1074 }, 1075 APIServerLB: createValidAPIServerInternalLB(), 1076 }, 1077 wantErr: false, 1078 }, 1079 { 1080 name: "testValidPrivateDNSZoneNameWithUnderscore", 1081 network: NetworkSpec{ 1082 NetworkClassSpec: NetworkClassSpec{ 1083 PrivateDNSZoneName: "_good.__dns.io", 1084 }, 1085 APIServerLB: createValidAPIServerInternalLB(), 1086 }, 1087 wantErr: false, 1088 }, 1089 { 1090 name: "testBadAPIServerLBType", 1091 network: NetworkSpec{ 1092 NetworkClassSpec: NetworkClassSpec{ 1093 PrivateDNSZoneName: "good.dns.io", 1094 }, 1095 APIServerLB: LoadBalancerSpec{ 1096 Name: "my-lb", 1097 LoadBalancerClassSpec: LoadBalancerClassSpec{ 1098 Type: Public, 1099 }, 1100 }, 1101 }, 1102 expectedErr: field.Error{ 1103 Type: "FieldValueInvalid", 1104 Field: "spec.networkSpec.privateDNSZoneName", 1105 BadValue: "Public", 1106 Detail: "PrivateDNSZoneName is available only if APIServerLB.Type is Internal", 1107 }, 1108 wantErr: true, 1109 }, 1110 } 1111 1112 for _, test := range testcases { 1113 test := test 1114 t.Run(test.name, func(t *testing.T) { 1115 t.Parallel() 1116 g := NewWithT(t) 1117 err := validatePrivateDNSZoneName(test.network.PrivateDNSZoneName, test.network.APIServerLB.Type, field.NewPath("spec", "networkSpec", "privateDNSZoneName")) 1118 if test.wantErr { 1119 g.Expect(err).To(ContainElement(MatchError(test.expectedErr.Error()))) 1120 } else { 1121 g.Expect(err).To(BeEmpty()) 1122 } 1123 }) 1124 } 1125 } 1126 1127 func TestValidateNodeOutboundLB(t *testing.T) { 1128 testcases := []struct { 1129 name string 1130 lb *LoadBalancerSpec 1131 old *LoadBalancerSpec 1132 apiServerLB LoadBalancerSpec 1133 wantErr bool 1134 expectedErr field.Error 1135 }{ 1136 { 1137 name: "no lb for public clusters", 1138 lb: nil, 1139 apiServerLB: LoadBalancerSpec{ 1140 LoadBalancerClassSpec: LoadBalancerClassSpec{ 1141 Type: Public, 1142 }, 1143 }, 1144 wantErr: true, 1145 expectedErr: field.Error{ 1146 Type: "FieldValueRequired", 1147 Field: "nodeOutboundLB", 1148 BadValue: nil, 1149 Detail: "Node outbound load balancer cannot be nil for public clusters.", 1150 }, 1151 }, 1152 { 1153 name: "no lb allowed for internal clusters", 1154 lb: nil, 1155 apiServerLB: LoadBalancerSpec{ 1156 LoadBalancerClassSpec: LoadBalancerClassSpec{ 1157 Type: Internal, 1158 }, 1159 }, 1160 wantErr: false, 1161 }, 1162 { 1163 name: "invalid ID update", 1164 lb: &LoadBalancerSpec{ 1165 ID: "some-id", 1166 }, 1167 old: &LoadBalancerSpec{ 1168 ID: "old-id", 1169 }, 1170 wantErr: true, 1171 expectedErr: field.Error{ 1172 Type: "FieldValueForbidden", 1173 Field: "nodeOutboundLB.id", 1174 BadValue: "some-id", 1175 Detail: "Node outbound load balancer ID should not be modified after AzureCluster creation.", 1176 }, 1177 }, 1178 { 1179 name: "invalid Name update", 1180 lb: &LoadBalancerSpec{ 1181 Name: "some-name", 1182 }, 1183 old: &LoadBalancerSpec{ 1184 Name: "old-name", 1185 }, 1186 wantErr: true, 1187 expectedErr: field.Error{ 1188 Type: "FieldValueForbidden", 1189 Field: "nodeOutboundLB.name", 1190 BadValue: "some-name", 1191 Detail: "Node outbound load balancer Name should not be modified after AzureCluster creation.", 1192 }, 1193 }, 1194 { 1195 name: "invalid SKU update", 1196 lb: &LoadBalancerSpec{ 1197 LoadBalancerClassSpec: LoadBalancerClassSpec{ 1198 SKU: "some-sku", 1199 }, 1200 }, 1201 old: &LoadBalancerSpec{ 1202 LoadBalancerClassSpec: LoadBalancerClassSpec{ 1203 SKU: "old-sku", 1204 }, 1205 }, 1206 wantErr: true, 1207 expectedErr: field.Error{ 1208 Type: "FieldValueForbidden", 1209 Field: "nodeOutboundLB.sku", 1210 BadValue: "some-sku", 1211 Detail: "Node outbound load balancer SKU should not be modified after AzureCluster creation.", 1212 }, 1213 }, 1214 { 1215 name: "invalid FrontendIps update", 1216 lb: &LoadBalancerSpec{ 1217 FrontendIPs: []FrontendIP{{ 1218 Name: "some-frontend-ip", 1219 }}, 1220 }, 1221 old: &LoadBalancerSpec{ 1222 FrontendIPs: []FrontendIP{{ 1223 Name: "old-frontend-ip", 1224 }}, 1225 }, 1226 wantErr: true, 1227 expectedErr: field.Error{ 1228 Type: "FieldValueForbidden", 1229 Field: "nodeOutboundLB.frontendIPs[0]", 1230 BadValue: FrontendIP{ 1231 Name: "some-frontend-ip", 1232 }, 1233 Detail: "Node outbound load balancer FrontendIPs cannot be modified after AzureCluster creation.", 1234 }, 1235 }, 1236 { 1237 name: "FrontendIps can update when frontendIpsCount changes", 1238 lb: &LoadBalancerSpec{ 1239 FrontendIPs: []FrontendIP{{ 1240 Name: "some-frontend-ip-1", 1241 }, { 1242 Name: "some-frontend-ip-2", 1243 }}, 1244 FrontendIPsCount: ptr.To[int32](2), 1245 }, 1246 old: &LoadBalancerSpec{ 1247 FrontendIPs: []FrontendIP{{ 1248 Name: "old-frontend-ip", 1249 }}, 1250 LoadBalancerClassSpec: LoadBalancerClassSpec{}, 1251 }, 1252 wantErr: false, 1253 }, 1254 { 1255 name: "frontend ips count exceeds max value", 1256 lb: &LoadBalancerSpec{ 1257 FrontendIPsCount: ptr.To[int32](100), 1258 }, 1259 wantErr: true, 1260 expectedErr: field.Error{ 1261 Type: "FieldValueInvalid", 1262 Field: "nodeOutboundLB.frontendIPsCount", 1263 BadValue: 100, 1264 Detail: "Max front end ips allowed is 16", 1265 }, 1266 }, 1267 } 1268 1269 for _, test := range testcases { 1270 test := test 1271 t.Run(test.name, func(t *testing.T) { 1272 t.Parallel() 1273 g := NewWithT(t) 1274 err := validateNodeOutboundLB(test.lb, test.old, test.apiServerLB, field.NewPath("nodeOutboundLB")) 1275 if test.wantErr { 1276 g.Expect(err).To(ContainElement(MatchError(test.expectedErr.Error()))) 1277 } else { 1278 g.Expect(err).To(BeEmpty()) 1279 } 1280 }) 1281 } 1282 } 1283 1284 func TestValidateControlPlaneNodeOutboundLB(t *testing.T) { 1285 testcases := []struct { 1286 name string 1287 lb *LoadBalancerSpec 1288 old *LoadBalancerSpec 1289 apiServerLB LoadBalancerSpec 1290 wantErr bool 1291 expectedErr field.Error 1292 }{ 1293 { 1294 name: "cp outbound lb cannot be set for public clusters", 1295 lb: &LoadBalancerSpec{Name: "foo"}, 1296 apiServerLB: LoadBalancerSpec{ 1297 LoadBalancerClassSpec: LoadBalancerClassSpec{ 1298 Type: Public, 1299 }, 1300 }, 1301 wantErr: true, 1302 expectedErr: field.Error{ 1303 Type: "FieldValueForbidden", 1304 Field: "controlPlaneOutboundLB", 1305 BadValue: LoadBalancerSpec{Name: "foo"}, 1306 Detail: "Control plane outbound load balancer cannot be set for public clusters.", 1307 }, 1308 }, 1309 { 1310 name: "cp outbound lb can be set for private clusters", 1311 lb: &LoadBalancerSpec{Name: "foo"}, 1312 apiServerLB: LoadBalancerSpec{ 1313 LoadBalancerClassSpec: LoadBalancerClassSpec{ 1314 Type: Internal, 1315 }, 1316 }, 1317 wantErr: false, 1318 }, 1319 { 1320 name: "cp outbound lb can be nil for private clusters", 1321 lb: nil, 1322 apiServerLB: LoadBalancerSpec{ 1323 LoadBalancerClassSpec: LoadBalancerClassSpec{ 1324 Type: Internal, 1325 }, 1326 }, 1327 wantErr: false, 1328 }, 1329 { 1330 name: "frontend ips count exceeds max value", 1331 lb: &LoadBalancerSpec{ 1332 FrontendIPsCount: ptr.To[int32](100), 1333 }, 1334 apiServerLB: LoadBalancerSpec{ 1335 LoadBalancerClassSpec: LoadBalancerClassSpec{ 1336 Type: Internal, 1337 }, 1338 }, 1339 wantErr: true, 1340 expectedErr: field.Error{ 1341 Type: "FieldValueInvalid", 1342 Field: "controlPlaneOutboundLB.frontendIPsCount", 1343 BadValue: 100, 1344 Detail: "Max front end ips allowed is 16", 1345 }, 1346 }, 1347 } 1348 1349 for _, test := range testcases { 1350 test := test 1351 t.Run(test.name, func(t *testing.T) { 1352 t.Parallel() 1353 g := NewWithT(t) 1354 err := validateControlPlaneOutboundLB(test.lb, test.apiServerLB, field.NewPath("controlPlaneOutboundLB")) 1355 if test.wantErr { 1356 g.Expect(err).To(ContainElement(MatchError(test.expectedErr.Error()))) 1357 } else { 1358 g.Expect(err).To(BeEmpty()) 1359 } 1360 }) 1361 } 1362 } 1363 1364 func TestValidateCloudProviderConfigOverrides(t *testing.T) { 1365 tests := []struct { 1366 name string 1367 oldConfig *CloudProviderConfigOverrides 1368 newConfig *CloudProviderConfigOverrides 1369 wantErr bool 1370 expectedErr field.Error 1371 }{ 1372 { 1373 name: "both old and new config nil", 1374 wantErr: false, 1375 }, 1376 { 1377 name: "both old and new config are same", 1378 oldConfig: &CloudProviderConfigOverrides{RateLimits: []RateLimitSpec{{ 1379 Name: "foo", 1380 Config: RateLimitConfig{CloudProviderRateLimitBucket: 10, CloudProviderRateLimit: true}, 1381 }}}, 1382 newConfig: &CloudProviderConfigOverrides{RateLimits: []RateLimitSpec{{ 1383 Name: "foo", 1384 Config: RateLimitConfig{CloudProviderRateLimitBucket: 10, CloudProviderRateLimit: true}, 1385 }}}, 1386 wantErr: false, 1387 }, 1388 { 1389 name: "old and new config are not same", 1390 oldConfig: &CloudProviderConfigOverrides{RateLimits: []RateLimitSpec{{ 1391 Name: "foo", 1392 Config: RateLimitConfig{CloudProviderRateLimitBucket: 10, CloudProviderRateLimit: true}, 1393 }}}, 1394 newConfig: &CloudProviderConfigOverrides{RateLimits: []RateLimitSpec{{ 1395 Name: "foo", 1396 Config: RateLimitConfig{CloudProviderRateLimitBucket: 11, CloudProviderRateLimit: true}, 1397 }}}, 1398 wantErr: true, 1399 expectedErr: field.Error{ 1400 Type: "FieldValueInvalid", 1401 Field: "spec.cloudProviderConfigOverrides", 1402 BadValue: CloudProviderConfigOverrides{RateLimits: []RateLimitSpec{{ 1403 Name: "foo", 1404 Config: RateLimitConfig{CloudProviderRateLimitBucket: 11, CloudProviderRateLimit: true}, 1405 }}}, 1406 Detail: "cannot change cloudProviderConfigOverrides cluster creation", 1407 }, 1408 }, 1409 { 1410 name: "new config is nil", 1411 oldConfig: &CloudProviderConfigOverrides{RateLimits: []RateLimitSpec{{ 1412 Name: "foo", 1413 Config: RateLimitConfig{CloudProviderRateLimitBucket: 10, CloudProviderRateLimit: true}, 1414 }}}, 1415 wantErr: true, 1416 expectedErr: field.Error{ 1417 Type: "FieldValueInvalid", 1418 Field: "spec.cloudProviderConfigOverrides", 1419 BadValue: nil, 1420 Detail: "cannot change cloudProviderConfigOverrides cluster creation", 1421 }, 1422 }, 1423 { 1424 name: "old config is nil", 1425 newConfig: &CloudProviderConfigOverrides{RateLimits: []RateLimitSpec{{ 1426 Name: "foo", 1427 Config: RateLimitConfig{CloudProviderRateLimitBucket: 10, CloudProviderRateLimit: true}, 1428 }}}, 1429 wantErr: true, 1430 expectedErr: field.Error{ 1431 Type: "FieldValueInvalid", 1432 Field: "spec.cloudProviderConfigOverrides", 1433 BadValue: &CloudProviderConfigOverrides{RateLimits: []RateLimitSpec{{ 1434 Name: "foo", 1435 Config: RateLimitConfig{CloudProviderRateLimitBucket: 10, CloudProviderRateLimit: true}, 1436 }}}, 1437 Detail: "cannot change cloudProviderConfigOverrides cluster creation", 1438 }, 1439 }, 1440 } 1441 for _, testCase := range tests { 1442 t.Run(testCase.name, func(t *testing.T) { 1443 g := NewWithT(t) 1444 err := validateCloudProviderConfigOverrides(testCase.oldConfig, testCase.newConfig, field.NewPath("spec.cloudProviderConfigOverrides")) 1445 if testCase.wantErr { 1446 g.Expect(err).To(ContainElement(MatchError(testCase.expectedErr.Error()))) 1447 } else { 1448 g.Expect(err).To(BeEmpty()) 1449 } 1450 }) 1451 } 1452 } 1453 1454 func createValidClusterWithClusterSubnet() *AzureCluster { 1455 return &AzureCluster{ 1456 ObjectMeta: metav1.ObjectMeta{ 1457 Name: "test-cluster", 1458 }, 1459 Spec: AzureClusterSpec{ 1460 NetworkSpec: createValidNetworkSpecWithClusterSubnet(), 1461 AzureClusterClassSpec: AzureClusterClassSpec{ 1462 IdentityRef: &corev1.ObjectReference{ 1463 Kind: "AzureClusterIdentity", 1464 }, 1465 }, 1466 }, 1467 } 1468 } 1469 1470 func createValidCluster() *AzureCluster { 1471 return &AzureCluster{ 1472 ObjectMeta: metav1.ObjectMeta{ 1473 Name: "test-cluster", 1474 }, 1475 Spec: AzureClusterSpec{ 1476 NetworkSpec: createValidNetworkSpec(), 1477 AzureClusterClassSpec: AzureClusterClassSpec{ 1478 IdentityRef: &corev1.ObjectReference{ 1479 Kind: AzureClusterIdentityKind, 1480 }, 1481 }, 1482 }, 1483 } 1484 } 1485 1486 func createClusterNetworkSpec() NetworkSpec { 1487 return NetworkSpec{ 1488 Vnet: VnetSpec{ 1489 ResourceGroup: "custom-vnet", 1490 Name: "my-vnet", 1491 }, 1492 Subnets: Subnets{ 1493 { 1494 SubnetClassSpec: SubnetClassSpec{ 1495 Role: "cluster", 1496 Name: "cluster-subnet", 1497 }, 1498 }, 1499 }, 1500 APIServerLB: createValidAPIServerLB(), 1501 NodeOutboundLB: createValidNodeOutboundLB(), 1502 } 1503 } 1504 1505 func createValidNetworkSpecWithClusterSubnet() NetworkSpec { 1506 return NetworkSpec{ 1507 Vnet: VnetSpec{ 1508 ResourceGroup: "custom-vnet", 1509 Name: "my-vnet", 1510 }, 1511 Subnets: Subnets{ 1512 { 1513 SubnetClassSpec: SubnetClassSpec{ 1514 Role: "cluster", 1515 Name: "cluster-subnet", 1516 }, 1517 }, 1518 }, 1519 APIServerLB: createValidAPIServerLB(), 1520 NodeOutboundLB: createValidNodeOutboundLB(), 1521 } 1522 } 1523 1524 func createValidNetworkSpec() NetworkSpec { 1525 return NetworkSpec{ 1526 Vnet: VnetSpec{ 1527 ResourceGroup: "custom-vnet", 1528 Name: "my-vnet", 1529 }, 1530 Subnets: createValidSubnets(), 1531 APIServerLB: createValidAPIServerLB(), 1532 NodeOutboundLB: createValidNodeOutboundLB(), 1533 } 1534 } 1535 1536 func createValidSubnets() Subnets { 1537 return Subnets{ 1538 { 1539 SubnetClassSpec: SubnetClassSpec{ 1540 Role: "control-plane", 1541 Name: "control-plane-subnet", 1542 }, 1543 }, 1544 { 1545 SubnetClassSpec: SubnetClassSpec{ 1546 Role: "node", 1547 Name: "node-subnet", 1548 }, 1549 }, 1550 } 1551 } 1552 1553 func createValidVnet() VnetSpec { 1554 return VnetSpec{ 1555 ResourceGroup: "custom-vnet", 1556 Name: "my-vnet", 1557 VnetClassSpec: VnetClassSpec{ 1558 CIDRBlocks: []string{DefaultVnetCIDR}, 1559 }, 1560 } 1561 } 1562 1563 func createValidAPIServerLB() LoadBalancerSpec { 1564 return LoadBalancerSpec{ 1565 Name: "my-lb", 1566 FrontendIPs: []FrontendIP{ 1567 { 1568 Name: "ip-config", 1569 PublicIP: &PublicIPSpec{ 1570 Name: "public-ip", 1571 DNSName: "myfqdn.azure.com", 1572 }, 1573 }, 1574 }, 1575 LoadBalancerClassSpec: LoadBalancerClassSpec{ 1576 SKU: SKUStandard, 1577 Type: Public, 1578 }, 1579 } 1580 } 1581 1582 func createValidNodeOutboundLB() *LoadBalancerSpec { 1583 return &LoadBalancerSpec{ 1584 FrontendIPsCount: ptr.To[int32](1), 1585 } 1586 } 1587 1588 func createValidAPIServerInternalLB() LoadBalancerSpec { 1589 return LoadBalancerSpec{ 1590 Name: "my-lb", 1591 FrontendIPs: []FrontendIP{ 1592 { 1593 Name: "ip-config-private", 1594 FrontendIPClass: FrontendIPClass{ 1595 PrivateIPAddress: "10.10.1.1", 1596 }, 1597 }, 1598 }, 1599 LoadBalancerClassSpec: LoadBalancerClassSpec{ 1600 SKU: SKUStandard, 1601 Type: Internal, 1602 }, 1603 } 1604 } 1605 1606 func TestValidateServiceEndpoints(t *testing.T) { 1607 tests := []struct { 1608 name string 1609 serviceEndpoints ServiceEndpoints 1610 wantErr bool 1611 expectedErr field.Error 1612 }{ 1613 { 1614 name: "valid service endpoint", 1615 serviceEndpoints: []ServiceEndpointSpec{{ 1616 Service: "Microsoft.Foo", 1617 Locations: []string{"*", "eastus2"}, 1618 }}, 1619 wantErr: false, 1620 }, 1621 { 1622 name: "invalid service endpoint name doesn't start with Microsoft", 1623 serviceEndpoints: []ServiceEndpointSpec{{ 1624 Service: "Foo", 1625 Locations: []string{"*"}, 1626 }}, 1627 wantErr: true, 1628 expectedErr: field.Error{ 1629 Type: "FieldValueInvalid", 1630 Field: "subnets[0].serviceEndpoints[0].service", 1631 BadValue: "Foo", 1632 Detail: "service name of endpoint service doesn't match regex ^Microsoft\\.[a-zA-Z]{1,42}[a-zA-Z0-9]{0,42}$", 1633 }, 1634 }, 1635 { 1636 name: "invalid service endpoint name contains invalid characters", 1637 serviceEndpoints: []ServiceEndpointSpec{{ 1638 Service: "Microsoft.Foo", 1639 Locations: []string{"*"}, 1640 }, { 1641 Service: "Microsoft.Foo-Bar", 1642 Locations: []string{"*"}, 1643 }}, 1644 wantErr: true, 1645 expectedErr: field.Error{ 1646 Type: "FieldValueInvalid", 1647 Field: "subnets[0].serviceEndpoints[1].service", 1648 BadValue: "Microsoft.Foo-Bar", 1649 Detail: "service name of endpoint service doesn't match regex ^Microsoft\\.[a-zA-Z]{1,42}[a-zA-Z0-9]{0,42}$", 1650 }, 1651 }, 1652 { 1653 name: "invalid service endpoint location contains invalid characters", 1654 serviceEndpoints: []ServiceEndpointSpec{{ 1655 Service: "Microsoft.Foo", 1656 Locations: []string{"*"}, 1657 }, { 1658 Service: "Microsoft.Bar", 1659 Locations: []string{"foo", "foo-bar"}, 1660 }}, 1661 wantErr: true, 1662 expectedErr: field.Error{ 1663 Type: "FieldValueInvalid", 1664 Field: "subnets[0].serviceEndpoints[1].locations[1]", 1665 BadValue: "foo-bar", 1666 Detail: "location doesn't match regex ^([a-z]{1,42}\\d{0,5}|[*])$", 1667 }, 1668 }, 1669 } 1670 for _, testCase := range tests { 1671 t.Run(testCase.name, func(t *testing.T) { 1672 g := NewWithT(t) 1673 err := validateServiceEndpoints(testCase.serviceEndpoints, field.NewPath("subnets[0].serviceEndpoints")) 1674 if testCase.wantErr { 1675 // Searches for expected error in list of thrown errors 1676 g.Expect(err).To(ContainElement(MatchError(testCase.expectedErr.Error()))) 1677 } else { 1678 g.Expect(err).To(BeEmpty()) 1679 } 1680 }) 1681 } 1682 } 1683 1684 func TestServiceEndpointsLackRequiredFieldService(t *testing.T) { 1685 type test struct { 1686 name string 1687 serviceEndpoints ServiceEndpoints 1688 } 1689 1690 testCase := test{ 1691 name: "service endpoint missing service name", 1692 serviceEndpoints: []ServiceEndpointSpec{{ 1693 Locations: []string{"*"}, 1694 }}, 1695 } 1696 1697 t.Run(testCase.name, func(t *testing.T) { 1698 g := NewWithT(t) 1699 errs := validateServiceEndpoints(testCase.serviceEndpoints, field.NewPath("subnets[0].serviceEndpoints")) 1700 g.Expect(errs).To(HaveLen(1)) 1701 g.Expect(errs[0].Type).To(Equal(field.ErrorTypeRequired)) 1702 g.Expect(errs[0].Field).To(Equal("subnets[0].serviceEndpoints[0].service")) 1703 g.Expect(errs[0].Error()).To(ContainSubstring("service is required for all service endpoints")) 1704 }) 1705 } 1706 1707 func TestServiceEndpointsLackRequiredFieldLocations(t *testing.T) { 1708 type test struct { 1709 name string 1710 serviceEndpoints ServiceEndpoints 1711 } 1712 1713 testCase := test{ 1714 name: "service endpoint missing locations", 1715 serviceEndpoints: []ServiceEndpointSpec{{ 1716 Service: "Microsoft.Foo", 1717 }}, 1718 } 1719 1720 t.Run(testCase.name, func(t *testing.T) { 1721 g := NewWithT(t) 1722 errs := validateServiceEndpoints(testCase.serviceEndpoints, field.NewPath("subnets[0].serviceEndpoints")) 1723 g.Expect(errs).To(HaveLen(1)) 1724 g.Expect(errs[0].Type).To(Equal(field.ErrorTypeRequired)) 1725 g.Expect(errs[0].Field).To(Equal("subnets[0].serviceEndpoints[0].locations")) 1726 g.Expect(errs[0].Error()).To(ContainSubstring("locations are required for all service endpoints")) 1727 }) 1728 } 1729 1730 func TestClusterWithExtendedLocationInvalid(t *testing.T) { 1731 type test struct { 1732 name string 1733 cluster *AzureCluster 1734 } 1735 1736 testCase := test{ 1737 name: "azurecluster spec with extended location but not enable EdgeZone feature gate flag", 1738 cluster: createValidCluster(), 1739 } 1740 1741 testCase.cluster.Spec.ExtendedLocation = &ExtendedLocationSpec{ 1742 Name: "rr4", 1743 Type: "EdgeZone", 1744 } 1745 1746 t.Run(testCase.name, func(t *testing.T) { 1747 g := NewWithT(t) 1748 err := testCase.cluster.validateClusterSpec(nil) 1749 g.Expect(err).NotTo(BeNil()) 1750 }) 1751 }