sigs.k8s.io/cluster-api-provider-azure@v1.17.0/api/v1beta1/azureclustertemplate_validation_test.go (about)

     1  /*
     2  Copyright 2022 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  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    24  	"k8s.io/apimachinery/pkg/util/validation/field"
    25  	"k8s.io/utils/ptr"
    26  )
    27  
    28  func TestValdateVnetCIDRs(t *testing.T) {
    29  	cases := []struct {
    30  		name            string
    31  		clusterTemplate *AzureClusterTemplate
    32  		expectValid     bool
    33  	}{
    34  		{
    35  			name: "valid vnet",
    36  			clusterTemplate: &AzureClusterTemplate{
    37  				ObjectMeta: metav1.ObjectMeta{
    38  					Name: "test-cluster-template",
    39  				},
    40  				Spec: AzureClusterTemplateSpec{
    41  					Template: AzureClusterTemplateResource{
    42  						Spec: AzureClusterTemplateResourceSpec{
    43  							NetworkSpec: NetworkTemplateSpec{
    44  								Vnet: VnetTemplateSpec{
    45  									VnetClassSpec: VnetClassSpec{
    46  										CIDRBlocks: []string{DefaultVnetCIDR},
    47  									},
    48  								},
    49  							},
    50  						},
    51  					},
    52  				},
    53  			},
    54  			expectValid: true,
    55  		},
    56  		{
    57  			name: "invalid vnet CIDR",
    58  			clusterTemplate: &AzureClusterTemplate{
    59  				ObjectMeta: metav1.ObjectMeta{
    60  					Name: "test-cluster-template",
    61  				},
    62  				Spec: AzureClusterTemplateSpec{
    63  					Template: AzureClusterTemplateResource{
    64  						Spec: AzureClusterTemplateResourceSpec{
    65  							NetworkSpec: NetworkTemplateSpec{
    66  								Vnet: VnetTemplateSpec{
    67  									VnetClassSpec: VnetClassSpec{
    68  										CIDRBlocks: []string{"1.2.3/12"},
    69  									},
    70  								},
    71  							},
    72  						},
    73  					},
    74  				},
    75  			},
    76  			expectValid: false,
    77  		},
    78  	}
    79  
    80  	for _, c := range cases {
    81  		tc := c
    82  		t.Run(tc.name, func(t *testing.T) {
    83  			t.Parallel()
    84  			g := NewWithT(t)
    85  			res := validateVnetCIDR(
    86  				tc.clusterTemplate.Spec.Template.Spec.NetworkSpec.Vnet.CIDRBlocks,
    87  				field.NewPath("spec").Child("template").Child("spec").
    88  					Child("networkSpec").Child("vnet").Child("cidrBlocks"))
    89  
    90  			if tc.expectValid {
    91  				g.Expect(res).To(BeNil())
    92  			} else {
    93  				g.Expect(res).NotTo(BeNil())
    94  			}
    95  		})
    96  	}
    97  }
    98  
    99  func TestValidateSubnetTemplates(t *testing.T) {
   100  	cases := []struct {
   101  		name            string
   102  		clusterTemplate *AzureClusterTemplate
   103  		expectValid     bool
   104  	}{
   105  		{
   106  			name: "valid subnets",
   107  			clusterTemplate: &AzureClusterTemplate{
   108  				ObjectMeta: metav1.ObjectMeta{
   109  					Name: "test-cluster-template",
   110  				},
   111  				Spec: AzureClusterTemplateSpec{
   112  					Template: AzureClusterTemplateResource{
   113  						Spec: AzureClusterTemplateResourceSpec{
   114  							NetworkSpec: NetworkTemplateSpec{
   115  								Vnet: VnetTemplateSpec{
   116  									VnetClassSpec: VnetClassSpec{
   117  										CIDRBlocks: []string{DefaultVnetCIDR},
   118  									},
   119  								},
   120  								Subnets: SubnetTemplatesSpec{
   121  									{
   122  										SubnetClassSpec: SubnetClassSpec{
   123  											Role:       SubnetControlPlane,
   124  											CIDRBlocks: []string{DefaultControlPlaneSubnetCIDR},
   125  											Name:       "foo-controlPlane-subnet",
   126  										},
   127  									},
   128  									{
   129  										SubnetClassSpec: SubnetClassSpec{
   130  											Role:       SubnetNode,
   131  											CIDRBlocks: []string{DefaultNodeSubnetCIDR},
   132  											Name:       "foo-workerSubnet-subnet",
   133  										},
   134  									},
   135  								},
   136  							},
   137  						},
   138  					},
   139  				},
   140  			},
   141  			expectValid: true,
   142  		},
   143  		{
   144  			name: "invalid subnets - missing/empty subnet name",
   145  			clusterTemplate: &AzureClusterTemplate{
   146  				ObjectMeta: metav1.ObjectMeta{
   147  					Name: "test-cluster-template",
   148  				},
   149  				Spec: AzureClusterTemplateSpec{
   150  					Template: AzureClusterTemplateResource{
   151  						Spec: AzureClusterTemplateResourceSpec{
   152  							NetworkSpec: NetworkTemplateSpec{
   153  								Vnet: VnetTemplateSpec{
   154  									VnetClassSpec: VnetClassSpec{
   155  										CIDRBlocks: []string{DefaultVnetCIDR},
   156  									},
   157  								},
   158  								Subnets: SubnetTemplatesSpec{
   159  									{
   160  										SubnetClassSpec: SubnetClassSpec{
   161  											Role:       SubnetControlPlane,
   162  											CIDRBlocks: []string{DefaultControlPlaneSubnetCIDR},
   163  											Name:       "",
   164  										},
   165  									},
   166  									{
   167  										SubnetClassSpec: SubnetClassSpec{
   168  											Role:       SubnetNode,
   169  											CIDRBlocks: []string{DefaultNodeSubnetCIDR},
   170  											Name:       "",
   171  										},
   172  									},
   173  								},
   174  							},
   175  						},
   176  					},
   177  				},
   178  			},
   179  			expectValid: false,
   180  		},
   181  		{
   182  			name: "invalid subnets - duplicate subnet names",
   183  			clusterTemplate: &AzureClusterTemplate{
   184  				ObjectMeta: metav1.ObjectMeta{
   185  					Name: "test-cluster-template",
   186  				},
   187  				Spec: AzureClusterTemplateSpec{
   188  					Template: AzureClusterTemplateResource{
   189  						Spec: AzureClusterTemplateResourceSpec{
   190  							NetworkSpec: NetworkTemplateSpec{
   191  								Vnet: VnetTemplateSpec{
   192  									VnetClassSpec: VnetClassSpec{
   193  										CIDRBlocks: []string{DefaultVnetCIDR},
   194  									},
   195  								},
   196  								Subnets: SubnetTemplatesSpec{
   197  									{
   198  										SubnetClassSpec: SubnetClassSpec{
   199  											Role:       SubnetControlPlane,
   200  											CIDRBlocks: []string{DefaultControlPlaneSubnetCIDR},
   201  											Name:       "foo-controlPlane-subnet",
   202  										},
   203  									},
   204  									{
   205  										SubnetClassSpec: SubnetClassSpec{
   206  											Role:       SubnetNode,
   207  											CIDRBlocks: []string{DefaultNodeSubnetCIDR},
   208  											Name:       "foo-workerSubnet-subnet",
   209  										},
   210  									},
   211  									{
   212  										SubnetClassSpec: SubnetClassSpec{
   213  											Role:       SubnetNode,
   214  											CIDRBlocks: []string{"10.2.0.0/16"},
   215  											Name:       "foo-workerSubnet-subnet",
   216  										},
   217  									},
   218  								},
   219  							},
   220  						},
   221  					},
   222  				},
   223  			},
   224  			expectValid: false,
   225  		},
   226  		{
   227  			name: "invalid subnets - wrong security rule priorities - lower than minimum",
   228  			clusterTemplate: &AzureClusterTemplate{
   229  				ObjectMeta: metav1.ObjectMeta{
   230  					Name: "test-cluster-template",
   231  				},
   232  				Spec: AzureClusterTemplateSpec{
   233  					Template: AzureClusterTemplateResource{
   234  						Spec: AzureClusterTemplateResourceSpec{
   235  							NetworkSpec: NetworkTemplateSpec{
   236  								Vnet: VnetTemplateSpec{
   237  									VnetClassSpec: VnetClassSpec{
   238  										CIDRBlocks: []string{DefaultVnetCIDR},
   239  									},
   240  								},
   241  								Subnets: SubnetTemplatesSpec{
   242  									{
   243  										SubnetClassSpec: SubnetClassSpec{
   244  											Role:       SubnetControlPlane,
   245  											CIDRBlocks: []string{DefaultControlPlaneSubnetCIDR},
   246  										},
   247  										SecurityGroup: SecurityGroupClass{
   248  											SecurityRules: SecurityRules{
   249  												SecurityRule{
   250  													Priority: 50,
   251  												},
   252  											},
   253  										},
   254  									},
   255  									{
   256  										SubnetClassSpec: SubnetClassSpec{
   257  											Role:       SubnetNode,
   258  											CIDRBlocks: []string{DefaultNodeSubnetCIDR},
   259  										},
   260  									},
   261  								},
   262  							},
   263  						},
   264  					},
   265  				},
   266  			},
   267  			expectValid: false,
   268  		},
   269  		{
   270  			name: "invalid subnets - wrong security rule priorities - higher than maximum",
   271  			clusterTemplate: &AzureClusterTemplate{
   272  				ObjectMeta: metav1.ObjectMeta{
   273  					Name: "test-cluster-template",
   274  				},
   275  				Spec: AzureClusterTemplateSpec{
   276  					Template: AzureClusterTemplateResource{
   277  						Spec: AzureClusterTemplateResourceSpec{
   278  							NetworkSpec: NetworkTemplateSpec{
   279  								Vnet: VnetTemplateSpec{
   280  									VnetClassSpec: VnetClassSpec{
   281  										CIDRBlocks: []string{DefaultVnetCIDR},
   282  									},
   283  								},
   284  								Subnets: SubnetTemplatesSpec{
   285  									{
   286  										SubnetClassSpec: SubnetClassSpec{
   287  											Role:       SubnetControlPlane,
   288  											CIDRBlocks: []string{DefaultControlPlaneSubnetCIDR},
   289  										},
   290  										SecurityGroup: SecurityGroupClass{
   291  											SecurityRules: SecurityRules{
   292  												SecurityRule{
   293  													Priority: 4097,
   294  												},
   295  											},
   296  										},
   297  									},
   298  									{
   299  										SubnetClassSpec: SubnetClassSpec{
   300  											Role:       SubnetNode,
   301  											CIDRBlocks: []string{DefaultNodeSubnetCIDR},
   302  										},
   303  									},
   304  								},
   305  							},
   306  						},
   307  					},
   308  				},
   309  			},
   310  			expectValid: false,
   311  		},
   312  		{
   313  			name: "invalid subnet CIDR",
   314  			clusterTemplate: &AzureClusterTemplate{
   315  				ObjectMeta: metav1.ObjectMeta{
   316  					Name: "test-cluster-template",
   317  				},
   318  				Spec: AzureClusterTemplateSpec{
   319  					Template: AzureClusterTemplateResource{
   320  						Spec: AzureClusterTemplateResourceSpec{
   321  							NetworkSpec: NetworkTemplateSpec{
   322  								Vnet: VnetTemplateSpec{
   323  									VnetClassSpec: VnetClassSpec{
   324  										CIDRBlocks: []string{DefaultVnetCIDR},
   325  									},
   326  								},
   327  								Subnets: SubnetTemplatesSpec{
   328  									{
   329  										SubnetClassSpec: SubnetClassSpec{
   330  											Role:       SubnetControlPlane,
   331  											CIDRBlocks: []string{"11.0.0.0/16"},
   332  										},
   333  									},
   334  									{
   335  										SubnetClassSpec: SubnetClassSpec{
   336  											Role:       SubnetNode,
   337  											CIDRBlocks: []string{DefaultNodeSubnetCIDR},
   338  										},
   339  									},
   340  								},
   341  							},
   342  						},
   343  					},
   344  				},
   345  			},
   346  			expectValid: false,
   347  		},
   348  		{
   349  			name: "missing required control plane role",
   350  			clusterTemplate: &AzureClusterTemplate{
   351  				ObjectMeta: metav1.ObjectMeta{
   352  					Name: "test-cluster-template",
   353  				},
   354  				Spec: AzureClusterTemplateSpec{
   355  					Template: AzureClusterTemplateResource{
   356  						Spec: AzureClusterTemplateResourceSpec{
   357  							NetworkSpec: NetworkTemplateSpec{
   358  								Vnet: VnetTemplateSpec{
   359  									VnetClassSpec: VnetClassSpec{
   360  										CIDRBlocks: []string{DefaultVnetCIDR},
   361  									},
   362  								},
   363  								Subnets: SubnetTemplatesSpec{
   364  									{
   365  										SubnetClassSpec: SubnetClassSpec{
   366  											Role:       SubnetNode,
   367  											CIDRBlocks: []string{DefaultNodeSubnetCIDR},
   368  										},
   369  									},
   370  								},
   371  							},
   372  						},
   373  					},
   374  				},
   375  			},
   376  			expectValid: false,
   377  		},
   378  		{
   379  			name: "missing required node role",
   380  			clusterTemplate: &AzureClusterTemplate{
   381  				ObjectMeta: metav1.ObjectMeta{
   382  					Name: "test-cluster-template",
   383  				},
   384  				Spec: AzureClusterTemplateSpec{
   385  					Template: AzureClusterTemplateResource{
   386  						Spec: AzureClusterTemplateResourceSpec{
   387  							NetworkSpec: NetworkTemplateSpec{
   388  								Vnet: VnetTemplateSpec{
   389  									VnetClassSpec: VnetClassSpec{
   390  										CIDRBlocks: []string{DefaultVnetCIDR},
   391  									},
   392  								},
   393  								Subnets: SubnetTemplatesSpec{
   394  									{
   395  										SubnetClassSpec: SubnetClassSpec{
   396  											Role:       SubnetControlPlane,
   397  											CIDRBlocks: []string{DefaultControlPlaneSubnetCIDR},
   398  										},
   399  									},
   400  								},
   401  							},
   402  						},
   403  					},
   404  				},
   405  			},
   406  			expectValid: false,
   407  		},
   408  	}
   409  
   410  	for _, c := range cases {
   411  		tc := c
   412  		t.Run(tc.name, func(t *testing.T) {
   413  			t.Parallel()
   414  			g := NewWithT(t)
   415  			res := validateSubnetTemplates(
   416  				tc.clusterTemplate.Spec.Template.Spec.NetworkSpec.Subnets,
   417  				tc.clusterTemplate.Spec.Template.Spec.NetworkSpec.Vnet,
   418  				field.NewPath("spec").Child("template").Child("spec").Child("networkSpec").Child("subnets"),
   419  			)
   420  
   421  			if tc.expectValid {
   422  				g.Expect(res).To(BeNil())
   423  			} else {
   424  				g.Expect(res).NotTo(BeNil())
   425  			}
   426  		})
   427  	}
   428  }
   429  
   430  func TestValidateAPIServerLBTemplate(t *testing.T) {
   431  	cases := []struct {
   432  		name            string
   433  		clusterTemplate *AzureClusterTemplate
   434  		expectValid     bool
   435  	}{
   436  		{
   437  			name: "valid lb",
   438  			clusterTemplate: &AzureClusterTemplate{
   439  				ObjectMeta: metav1.ObjectMeta{
   440  					Name: "test-cluster-template",
   441  				},
   442  				Spec: AzureClusterTemplateSpec{
   443  					Template: AzureClusterTemplateResource{
   444  						Spec: AzureClusterTemplateResourceSpec{
   445  							NetworkSpec: NetworkTemplateSpec{
   446  								APIServerLB: LoadBalancerClassSpec{
   447  									SKU:                  SKUStandard,
   448  									Type:                 Public,
   449  									IdleTimeoutInMinutes: ptr.To[int32](DefaultOutboundRuleIdleTimeoutInMinutes),
   450  								},
   451  							},
   452  						},
   453  					},
   454  				},
   455  			},
   456  			expectValid: true,
   457  		},
   458  		{
   459  			name: "invalid lb SKU",
   460  			clusterTemplate: &AzureClusterTemplate{
   461  				ObjectMeta: metav1.ObjectMeta{
   462  					Name: "test-cluster-template",
   463  				},
   464  				Spec: AzureClusterTemplateSpec{
   465  					Template: AzureClusterTemplateResource{
   466  						Spec: AzureClusterTemplateResourceSpec{
   467  							NetworkSpec: NetworkTemplateSpec{
   468  								APIServerLB: LoadBalancerClassSpec{
   469  									SKU:                  SKU("wrong"),
   470  									Type:                 Public,
   471  									IdleTimeoutInMinutes: ptr.To[int32](DefaultOutboundRuleIdleTimeoutInMinutes),
   472  								},
   473  							},
   474  						},
   475  					},
   476  				},
   477  			},
   478  			expectValid: false,
   479  		},
   480  		{
   481  			name: "invalid lb Type",
   482  			clusterTemplate: &AzureClusterTemplate{
   483  				ObjectMeta: metav1.ObjectMeta{
   484  					Name: "test-cluster-template",
   485  				},
   486  				Spec: AzureClusterTemplateSpec{
   487  					Template: AzureClusterTemplateResource{
   488  						Spec: AzureClusterTemplateResourceSpec{
   489  							NetworkSpec: NetworkTemplateSpec{
   490  								APIServerLB: LoadBalancerClassSpec{
   491  									SKU:                  SKUStandard,
   492  									Type:                 LBType("wrong"),
   493  									IdleTimeoutInMinutes: ptr.To[int32](DefaultOutboundRuleIdleTimeoutInMinutes),
   494  								},
   495  							},
   496  						},
   497  					},
   498  				},
   499  			},
   500  			expectValid: false,
   501  		},
   502  	}
   503  
   504  	for _, c := range cases {
   505  		tc := c
   506  		t.Run(tc.name, func(t *testing.T) {
   507  			t.Parallel()
   508  			g := NewWithT(t)
   509  			res := tc.clusterTemplate.validateAPIServerLB(
   510  				field.NewPath("spec").Child("template").Child("spec").Child("networkSpec").Child("apiServerLB"),
   511  			)
   512  
   513  			if tc.expectValid {
   514  				g.Expect(res).To(BeNil())
   515  			} else {
   516  				g.Expect(res).NotTo(BeNil())
   517  			}
   518  		})
   519  	}
   520  }
   521  
   522  func TestControlPlaneOutboundLBTemplate(t *testing.T) {
   523  	cases := []struct {
   524  		name            string
   525  		clusterTemplate *AzureClusterTemplate
   526  		expectValid     bool
   527  	}{
   528  		{
   529  			name: "valid controlplaneoutbound LB",
   530  			clusterTemplate: &AzureClusterTemplate{
   531  				ObjectMeta: metav1.ObjectMeta{
   532  					Name: "test-cluster-template",
   533  				},
   534  				Spec: AzureClusterTemplateSpec{
   535  					Template: AzureClusterTemplateResource{
   536  						Spec: AzureClusterTemplateResourceSpec{
   537  							NetworkSpec: NetworkTemplateSpec{
   538  								APIServerLB: LoadBalancerClassSpec{
   539  									Type: Public,
   540  								},
   541  							},
   542  						},
   543  					},
   544  				},
   545  			},
   546  			expectValid: true,
   547  		},
   548  		{
   549  			name: "invalid controlplaneoutbound LB - should not have a controlplane outbound lb for public clusters",
   550  			clusterTemplate: &AzureClusterTemplate{
   551  				ObjectMeta: metav1.ObjectMeta{
   552  					Name: "test-cluster-template",
   553  				},
   554  				Spec: AzureClusterTemplateSpec{
   555  					Template: AzureClusterTemplateResource{
   556  						Spec: AzureClusterTemplateResourceSpec{
   557  							NetworkSpec: NetworkTemplateSpec{
   558  								APIServerLB: LoadBalancerClassSpec{
   559  									Type: Public,
   560  								},
   561  								ControlPlaneOutboundLB: &LoadBalancerClassSpec{},
   562  							},
   563  						},
   564  					},
   565  				},
   566  			},
   567  			expectValid: false,
   568  		},
   569  		{
   570  			name: "invalid controlplaneoutbound LB - IdleTimeoutInMinutes less than minimum for private clusters",
   571  			clusterTemplate: &AzureClusterTemplate{
   572  				ObjectMeta: metav1.ObjectMeta{
   573  					Name: "test-cluster-template",
   574  				},
   575  				Spec: AzureClusterTemplateSpec{
   576  					Template: AzureClusterTemplateResource{
   577  						Spec: AzureClusterTemplateResourceSpec{
   578  							NetworkSpec: NetworkTemplateSpec{
   579  								APIServerLB: LoadBalancerClassSpec{
   580  									Type: Internal,
   581  								},
   582  								ControlPlaneOutboundLB: &LoadBalancerClassSpec{
   583  									IdleTimeoutInMinutes: ptr.To[int32](2),
   584  								},
   585  							},
   586  						},
   587  					},
   588  				},
   589  			},
   590  			expectValid: false,
   591  		},
   592  		{
   593  			name: "invalid controlplaneoutbound LB - IdleTimeoutInMinutes more than maximum for private clusters",
   594  			clusterTemplate: &AzureClusterTemplate{
   595  				ObjectMeta: metav1.ObjectMeta{
   596  					Name: "test-cluster-template",
   597  				},
   598  				Spec: AzureClusterTemplateSpec{
   599  					Template: AzureClusterTemplateResource{
   600  						Spec: AzureClusterTemplateResourceSpec{
   601  							NetworkSpec: NetworkTemplateSpec{
   602  								APIServerLB: LoadBalancerClassSpec{
   603  									Type: Internal,
   604  								},
   605  								ControlPlaneOutboundLB: &LoadBalancerClassSpec{
   606  									IdleTimeoutInMinutes: ptr.To[int32](60),
   607  								},
   608  							},
   609  						},
   610  					},
   611  				},
   612  			},
   613  			expectValid: false,
   614  		},
   615  		{
   616  			name: "valid controlplaneoutbound LB - can be empty for private clusters",
   617  			clusterTemplate: &AzureClusterTemplate{
   618  				ObjectMeta: metav1.ObjectMeta{
   619  					Name: "test-cluster-template",
   620  				},
   621  				Spec: AzureClusterTemplateSpec{
   622  					Template: AzureClusterTemplateResource{
   623  						Spec: AzureClusterTemplateResourceSpec{
   624  							NetworkSpec: NetworkTemplateSpec{
   625  								APIServerLB: LoadBalancerClassSpec{
   626  									Type: Internal,
   627  								},
   628  							},
   629  						},
   630  					},
   631  				},
   632  			},
   633  			expectValid: true,
   634  		},
   635  	}
   636  
   637  	for _, c := range cases {
   638  		tc := c
   639  		t.Run(tc.name, func(t *testing.T) {
   640  			t.Parallel()
   641  			g := NewWithT(t)
   642  			res := tc.clusterTemplate.validateControlPlaneOutboundLB()
   643  
   644  			if tc.expectValid {
   645  				g.Expect(res).To(BeNil())
   646  			} else {
   647  				g.Expect(res).NotTo(BeNil())
   648  			}
   649  		})
   650  	}
   651  }
   652  
   653  func TestNodeOutboundLBTemplate(t *testing.T) {
   654  	cases := []struct {
   655  		name            string
   656  		clusterTemplate *AzureClusterTemplate
   657  		expectValid     bool
   658  	}{
   659  		{
   660  			name: "cannot be nil for public clusters",
   661  			clusterTemplate: &AzureClusterTemplate{
   662  				ObjectMeta: metav1.ObjectMeta{
   663  					Name: "test-cluster-template",
   664  				},
   665  				Spec: AzureClusterTemplateSpec{
   666  					Template: AzureClusterTemplateResource{
   667  						Spec: AzureClusterTemplateResourceSpec{
   668  							NetworkSpec: NetworkTemplateSpec{
   669  								APIServerLB: LoadBalancerClassSpec{
   670  									Type: Public,
   671  								},
   672  							},
   673  						},
   674  					},
   675  				},
   676  			},
   677  			expectValid: false,
   678  		},
   679  		{
   680  			name: "can be nil for private clusters",
   681  			clusterTemplate: &AzureClusterTemplate{
   682  				ObjectMeta: metav1.ObjectMeta{
   683  					Name: "test-cluster-template",
   684  				},
   685  				Spec: AzureClusterTemplateSpec{
   686  					Template: AzureClusterTemplateResource{
   687  						Spec: AzureClusterTemplateResourceSpec{
   688  							NetworkSpec: NetworkTemplateSpec{
   689  								APIServerLB: LoadBalancerClassSpec{
   690  									Type: Internal,
   691  								},
   692  							},
   693  						},
   694  					},
   695  				},
   696  			},
   697  			expectValid: true,
   698  		},
   699  		{
   700  			name: "timeout should not be less than minimum",
   701  			clusterTemplate: &AzureClusterTemplate{
   702  				ObjectMeta: metav1.ObjectMeta{
   703  					Name: "test-cluster-template",
   704  				},
   705  				Spec: AzureClusterTemplateSpec{
   706  					Template: AzureClusterTemplateResource{
   707  						Spec: AzureClusterTemplateResourceSpec{
   708  							NetworkSpec: NetworkTemplateSpec{
   709  								APIServerLB: LoadBalancerClassSpec{
   710  									Type: Public,
   711  								},
   712  								NodeOutboundLB: &LoadBalancerClassSpec{
   713  									IdleTimeoutInMinutes: ptr.To[int32](2),
   714  								},
   715  							},
   716  						},
   717  					},
   718  				},
   719  			},
   720  			expectValid: false,
   721  		},
   722  		{
   723  			name: "timeout should not be more than maximum",
   724  			clusterTemplate: &AzureClusterTemplate{
   725  				ObjectMeta: metav1.ObjectMeta{
   726  					Name: "test-cluster-template",
   727  				},
   728  				Spec: AzureClusterTemplateSpec{
   729  					Template: AzureClusterTemplateResource{
   730  						Spec: AzureClusterTemplateResourceSpec{
   731  							NetworkSpec: NetworkTemplateSpec{
   732  								APIServerLB: LoadBalancerClassSpec{
   733  									Type: Public,
   734  								},
   735  								NodeOutboundLB: &LoadBalancerClassSpec{
   736  									IdleTimeoutInMinutes: ptr.To[int32](60),
   737  								},
   738  							},
   739  						},
   740  					},
   741  				},
   742  			},
   743  			expectValid: false,
   744  		},
   745  	}
   746  
   747  	for _, c := range cases {
   748  		tc := c
   749  		t.Run(tc.name, func(t *testing.T) {
   750  			t.Parallel()
   751  			g := NewWithT(t)
   752  			res := tc.clusterTemplate.validateNodeOutboundLB()
   753  
   754  			if tc.expectValid {
   755  				g.Expect(res).To(BeNil())
   756  			} else {
   757  				g.Expect(res).NotTo(BeNil())
   758  			}
   759  		})
   760  	}
   761  }
   762  
   763  func TestValidatePrivateDNSZoneName(t *testing.T) {
   764  	cases := []struct {
   765  		name            string
   766  		clusterTemplate *AzureClusterTemplate
   767  		expectValid     bool
   768  	}{
   769  		{
   770  			name: "not set",
   771  			clusterTemplate: &AzureClusterTemplate{
   772  				ObjectMeta: metav1.ObjectMeta{
   773  					Name: "test-cluster-template",
   774  				},
   775  				Spec: AzureClusterTemplateSpec{
   776  					Template: AzureClusterTemplateResource{
   777  						Spec: AzureClusterTemplateResourceSpec{
   778  							NetworkSpec: NetworkTemplateSpec{},
   779  						},
   780  					},
   781  				},
   782  			},
   783  			expectValid: true,
   784  		},
   785  		{
   786  			name: "show not be set if APIServerLB.Type is not internal",
   787  			clusterTemplate: &AzureClusterTemplate{
   788  				ObjectMeta: metav1.ObjectMeta{
   789  					Name: "test-cluster-template",
   790  				},
   791  				Spec: AzureClusterTemplateSpec{
   792  					Template: AzureClusterTemplateResource{
   793  						Spec: AzureClusterTemplateResourceSpec{
   794  							NetworkSpec: NetworkTemplateSpec{
   795  								APIServerLB: LoadBalancerClassSpec{
   796  									Type: Public,
   797  								},
   798  								NetworkClassSpec: NetworkClassSpec{
   799  									PrivateDNSZoneName: "a.b.c",
   800  								},
   801  							},
   802  						},
   803  					},
   804  				},
   805  			},
   806  			expectValid: false,
   807  		},
   808  	}
   809  
   810  	for _, c := range cases {
   811  		tc := c
   812  		t.Run(tc.name, func(t *testing.T) {
   813  			t.Parallel()
   814  			g := NewWithT(t)
   815  			res := tc.clusterTemplate.validatePrivateDNSZoneName()
   816  
   817  			if tc.expectValid {
   818  				g.Expect(res).To(BeNil())
   819  			} else {
   820  				g.Expect(res).NotTo(BeNil())
   821  			}
   822  		})
   823  	}
   824  }
   825  func TestValidateNetworkSpec(t *testing.T) {
   826  	cases := []struct {
   827  		name            string
   828  		clusterTemplate *AzureClusterTemplate
   829  		expectValid     bool
   830  	}{
   831  		{
   832  			name: "subnet with SubnetNode role and enabled IPv6 triggers needOutboundLB and calls validateNodeOutboundLB",
   833  			clusterTemplate: &AzureClusterTemplate{
   834  				ObjectMeta: metav1.ObjectMeta{
   835  					Name: "test-cluster-template",
   836  				},
   837  				Spec: AzureClusterTemplateSpec{
   838  					Template: AzureClusterTemplateResource{
   839  						Spec: AzureClusterTemplateResourceSpec{
   840  							NetworkSpec: NetworkTemplateSpec{
   841  								Subnets: SubnetTemplatesSpec{
   842  									{
   843  										SubnetClassSpec: SubnetClassSpec{
   844  											Role:       SubnetNode,
   845  											CIDRBlocks: []string{"2001:beea::1/64"},
   846  										},
   847  									},
   848  								},
   849  							},
   850  						},
   851  					},
   852  				},
   853  			},
   854  			expectValid: false,
   855  		},
   856  		{
   857  			name: "subnet with non-SubnetNode role",
   858  			clusterTemplate: &AzureClusterTemplate{
   859  				ObjectMeta: metav1.ObjectMeta{
   860  					Name: "test-cluster-template",
   861  				},
   862  				Spec: AzureClusterTemplateSpec{
   863  					Template: AzureClusterTemplateResource{
   864  						Spec: AzureClusterTemplateResourceSpec{
   865  							NetworkSpec: NetworkTemplateSpec{
   866  								Subnets: SubnetTemplatesSpec{
   867  									{
   868  										SubnetClassSpec: SubnetClassSpec{
   869  											Role:       "SomeOtherRole",
   870  											CIDRBlocks: []string{"10.0.0.0/24"},
   871  										},
   872  									},
   873  								},
   874  							},
   875  						},
   876  					},
   877  				},
   878  			},
   879  			expectValid: true, // No need for outbound LB when not SubnetNode
   880  		},
   881  	}
   882  
   883  	for _, c := range cases {
   884  		tc := c
   885  		t.Run(tc.name, func(t *testing.T) {
   886  			t.Parallel()
   887  			g := NewWithT(t)
   888  			res := tc.clusterTemplate.validateNetworkSpec()
   889  			if tc.expectValid {
   890  				g.Expect(res).To(BeNil())
   891  			} else {
   892  				g.Expect(res).NotTo(BeNil())
   893  			}
   894  		})
   895  	}
   896  }