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  }