sigs.k8s.io/cluster-api@v1.7.1/internal/topology/check/compatibility_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 check
    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/apis/meta/v1/unstructured"
    26  	"k8s.io/apimachinery/pkg/util/validation/field"
    27  
    28  	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
    29  	"sigs.k8s.io/cluster-api/internal/test/builder"
    30  )
    31  
    32  type referencedObjectsCompatibilityTestCase struct {
    33  	name    string
    34  	current *unstructured.Unstructured
    35  	desired *unstructured.Unstructured
    36  	wantErr bool
    37  }
    38  
    39  var referencedObjectsCompatibilityTestCases = []referencedObjectsCompatibilityTestCase{
    40  	{
    41  		name: "Fails if group changes",
    42  		current: &unstructured.Unstructured{
    43  			Object: map[string]interface{}{
    44  				"apiVersion": "foo/v1beta1",
    45  			},
    46  		},
    47  		desired: &unstructured.Unstructured{
    48  			Object: map[string]interface{}{
    49  				"apiVersion": "bar/v1beta1",
    50  			},
    51  		},
    52  		wantErr: true,
    53  	},
    54  	{
    55  		name: "Fails if kind changes",
    56  		current: &unstructured.Unstructured{
    57  			Object: map[string]interface{}{
    58  				"kind": "foo",
    59  			},
    60  		},
    61  		desired: &unstructured.Unstructured{
    62  			Object: map[string]interface{}{
    63  				"kind": "bar",
    64  			},
    65  		},
    66  		wantErr: true,
    67  	},
    68  	{
    69  		name: "Pass if gvk remains the same",
    70  		current: &unstructured.Unstructured{
    71  			Object: map[string]interface{}{
    72  				"apiVersion": "infrastructure.cluster.x-k8s.io/foo",
    73  				"kind":       "foo",
    74  			},
    75  		},
    76  		desired: &unstructured.Unstructured{
    77  			Object: map[string]interface{}{
    78  				"apiVersion": "infrastructure.cluster.x-k8s.io/foo",
    79  				"kind":       "foo",
    80  			},
    81  		},
    82  		wantErr: false,
    83  	},
    84  	{
    85  		name: "Pass if version changes but group and kind remains the same",
    86  		current: &unstructured.Unstructured{
    87  			Object: map[string]interface{}{
    88  				"apiVersion": "infrastructure.cluster.x-k8s.io/foo",
    89  				"kind":       "foo",
    90  			},
    91  		},
    92  		desired: &unstructured.Unstructured{
    93  			Object: map[string]interface{}{
    94  				"apiVersion": "infrastructure.cluster.x-k8s.io/bar",
    95  				"kind":       "foo",
    96  			},
    97  		},
    98  		wantErr: false,
    99  	},
   100  	{
   101  		name: "Fails if namespace changes",
   102  		current: &unstructured.Unstructured{
   103  			Object: map[string]interface{}{
   104  				"metadata": map[string]interface{}{
   105  					"namespace": "foo",
   106  				},
   107  			},
   108  		},
   109  		desired: &unstructured.Unstructured{
   110  			Object: map[string]interface{}{
   111  				"metadata": map[string]interface{}{
   112  					"namespace": "bar",
   113  				},
   114  			},
   115  		},
   116  		wantErr: true,
   117  	},
   118  }
   119  
   120  func TestObjectsAreCompatible(t *testing.T) {
   121  	for _, tt := range referencedObjectsCompatibilityTestCases {
   122  		t.Run(tt.name, func(t *testing.T) {
   123  			g := NewWithT(t)
   124  			allErrs := ObjectsAreCompatible(tt.current, tt.desired)
   125  			if tt.wantErr {
   126  				g.Expect(allErrs).ToNot(BeEmpty())
   127  				return
   128  			}
   129  			g.Expect(allErrs).To(BeEmpty())
   130  		})
   131  	}
   132  }
   133  
   134  func TestObjectsAreStrictlyCompatible(t *testing.T) {
   135  	referencedObjectsStrictCompatibilityTestCases := append(referencedObjectsCompatibilityTestCases, []referencedObjectsCompatibilityTestCase{
   136  		{
   137  			name: "Fails if name changes",
   138  			current: &unstructured.Unstructured{
   139  				Object: map[string]interface{}{
   140  					"metadata": map[string]interface{}{
   141  						"name": "foo",
   142  					},
   143  				},
   144  			},
   145  			desired: &unstructured.Unstructured{
   146  				Object: map[string]interface{}{
   147  					"metadata": map[string]interface{}{
   148  						"name": "bar",
   149  					},
   150  				},
   151  			},
   152  			wantErr: true,
   153  		},
   154  		{
   155  			name: "Pass if name remains the same",
   156  			current: &unstructured.Unstructured{
   157  				Object: map[string]interface{}{
   158  					"metadata": map[string]interface{}{
   159  						"name": "foo",
   160  					},
   161  				},
   162  			},
   163  			desired: &unstructured.Unstructured{
   164  				Object: map[string]interface{}{
   165  					"metadata": map[string]interface{}{
   166  						"name": "foo",
   167  					},
   168  				},
   169  			},
   170  			wantErr: false,
   171  		},
   172  	}...)
   173  
   174  	for _, tt := range referencedObjectsStrictCompatibilityTestCases {
   175  		t.Run(tt.name, func(t *testing.T) {
   176  			g := NewWithT(t)
   177  
   178  			allErrs := ObjectsAreStrictlyCompatible(tt.current, tt.desired)
   179  			if tt.wantErr {
   180  				g.Expect(allErrs).ToNot(BeEmpty())
   181  				return
   182  			}
   183  			g.Expect(allErrs).To(BeEmpty())
   184  		})
   185  	}
   186  }
   187  
   188  func TestLocalObjectTemplatesAreCompatible(t *testing.T) {
   189  	template := clusterv1.LocalObjectTemplate{
   190  		Ref: &corev1.ObjectReference{
   191  			Namespace:  "default",
   192  			Name:       "foo",
   193  			Kind:       "bar",
   194  			APIVersion: "test.group.io/versionone",
   195  		},
   196  	}
   197  	compatibleNameChangeTemplate := clusterv1.LocalObjectTemplate{
   198  		Ref: &corev1.ObjectReference{
   199  			Namespace:  "default",
   200  			Name:       "newFoo",
   201  			Kind:       "bar",
   202  			APIVersion: "test.group.io/versionone",
   203  		},
   204  	}
   205  	compatibleAPIVersionChangeTemplate := clusterv1.LocalObjectTemplate{
   206  		Ref: &corev1.ObjectReference{
   207  			Namespace:  "default",
   208  			Name:       "foo",
   209  			Kind:       "bar",
   210  			APIVersion: "test.group.io/versiontwo",
   211  		},
   212  	}
   213  	incompatibleNamespaceChangeTemplate := clusterv1.LocalObjectTemplate{
   214  		Ref: &corev1.ObjectReference{
   215  			Namespace:  "different",
   216  			Name:       "foo",
   217  			Kind:       "bar",
   218  			APIVersion: "test.group.io/versionone",
   219  		},
   220  	}
   221  	incompatibleAPIGroupChangeTemplate := clusterv1.LocalObjectTemplate{
   222  		Ref: &corev1.ObjectReference{
   223  			Namespace:  "default",
   224  			Name:       "foo",
   225  			Kind:       "bar",
   226  			APIVersion: "production.group.io/versionone",
   227  		},
   228  	}
   229  	incompatibleAPIKindChangeTemplate := clusterv1.LocalObjectTemplate{
   230  		Ref: &corev1.ObjectReference{
   231  			Namespace:  "default",
   232  			Name:       "foo",
   233  			Kind:       "notabar",
   234  			APIVersion: "test.group.io/versionone",
   235  		},
   236  	}
   237  	tests := []struct {
   238  		name    string
   239  		current clusterv1.LocalObjectTemplate
   240  		desired clusterv1.LocalObjectTemplate
   241  		wantErr bool
   242  	}{
   243  		{
   244  			name:    "Allow change to template name",
   245  			current: template,
   246  			desired: compatibleNameChangeTemplate,
   247  			wantErr: false,
   248  		},
   249  		{
   250  			name:    "Allow change to template APIVersion",
   251  			current: template,
   252  			desired: compatibleAPIVersionChangeTemplate,
   253  			wantErr: false,
   254  		},
   255  		{
   256  			name:    "Block change to template API Group",
   257  			current: template,
   258  			desired: incompatibleAPIGroupChangeTemplate,
   259  			wantErr: true,
   260  		},
   261  		{
   262  			name:    "Block change to template namespace",
   263  			current: template,
   264  			desired: incompatibleNamespaceChangeTemplate,
   265  			wantErr: true,
   266  		},
   267  		{
   268  			name:    "Block change to template API Kind",
   269  			current: template,
   270  			desired: incompatibleAPIKindChangeTemplate,
   271  			wantErr: true,
   272  		},
   273  	}
   274  	for _, tt := range tests {
   275  		t.Run(tt.name, func(t *testing.T) {
   276  			g := NewWithT(t)
   277  			allErrs := LocalObjectTemplatesAreCompatible(tt.current, tt.desired, field.NewPath("spec"))
   278  			if tt.wantErr {
   279  				g.Expect(allErrs).ToNot(BeEmpty())
   280  				return
   281  			}
   282  			g.Expect(allErrs).To(BeEmpty())
   283  		})
   284  	}
   285  }
   286  
   287  func TestLocalObjectTemplateIsValid(t *testing.T) {
   288  	namespace := metav1.NamespaceDefault
   289  	pathPrefix := field.NewPath("this", "is", "a", "prefix")
   290  
   291  	validTemplate := &clusterv1.LocalObjectTemplate{
   292  		Ref: &corev1.ObjectReference{
   293  			Namespace:  "default",
   294  			Name:       "valid",
   295  			Kind:       "barTemplate",
   296  			APIVersion: "test.group.io/versionone",
   297  		},
   298  	}
   299  
   300  	nilTemplate := &clusterv1.LocalObjectTemplate{
   301  		Ref: nil,
   302  	}
   303  	emptyNameTemplate := &clusterv1.LocalObjectTemplate{
   304  		Ref: &corev1.ObjectReference{
   305  			Namespace:  "default",
   306  			Name:       "",
   307  			Kind:       "barTemplate",
   308  			APIVersion: "test.group.io/versionone",
   309  		},
   310  	}
   311  	wrongNamespaceTemplate := &clusterv1.LocalObjectTemplate{
   312  		Ref: &corev1.ObjectReference{
   313  			Namespace:  "wrongNamespace",
   314  			Name:       "foo",
   315  			Kind:       "barTemplate",
   316  			APIVersion: "test.group.io/versiontwo",
   317  		},
   318  	}
   319  	notTemplateKindTemplate := &clusterv1.LocalObjectTemplate{
   320  		Ref: &corev1.ObjectReference{
   321  			Namespace:  "default",
   322  			Name:       "foo",
   323  			Kind:       "bar",
   324  			APIVersion: "test.group.io/versionone",
   325  		},
   326  	}
   327  	invalidAPIVersionTemplate := &clusterv1.LocalObjectTemplate{
   328  		Ref: &corev1.ObjectReference{
   329  			Namespace:  "default",
   330  			Name:       "foo",
   331  			Kind:       "barTemplate",
   332  			APIVersion: "this/has/too/many/slashes",
   333  		},
   334  	}
   335  	emptyAPIVersionTemplate := &clusterv1.LocalObjectTemplate{
   336  		Ref: &corev1.ObjectReference{
   337  			Namespace:  "default",
   338  			Name:       "foo",
   339  			Kind:       "barTemplate",
   340  			APIVersion: "",
   341  		},
   342  	}
   343  
   344  	tests := []struct {
   345  		template *clusterv1.LocalObjectTemplate
   346  		name     string
   347  		wantErr  bool
   348  	}{
   349  		{
   350  			name:     "No error with valid Template",
   351  			template: validTemplate,
   352  			wantErr:  false,
   353  		},
   354  
   355  		{
   356  			name:     "Invalid if ref is nil",
   357  			template: nilTemplate,
   358  			wantErr:  true,
   359  		},
   360  		{
   361  			name:     "Invalid if name is empty",
   362  			template: emptyNameTemplate,
   363  			wantErr:  true,
   364  		},
   365  		{
   366  			name:     "Invalid if namespace doesn't match",
   367  			template: wrongNamespaceTemplate,
   368  			wantErr:  true,
   369  		},
   370  		{
   371  			name:     "Invalid if Kind doesn't have Template suffix",
   372  			template: notTemplateKindTemplate,
   373  			wantErr:  true,
   374  		},
   375  		{
   376  			name:     "Invalid if apiVersion is not valid",
   377  			template: invalidAPIVersionTemplate,
   378  			wantErr:  true,
   379  		},
   380  		{
   381  			name:     "Empty apiVersion is not valid",
   382  			template: emptyAPIVersionTemplate,
   383  			wantErr:  true,
   384  		},
   385  	}
   386  	for _, tt := range tests {
   387  		t.Run(tt.name, func(t *testing.T) {
   388  			g := NewWithT(t)
   389  			allErrs := LocalObjectTemplateIsValid(tt.template, namespace, pathPrefix)
   390  			if tt.wantErr {
   391  				g.Expect(allErrs).ToNot(BeEmpty())
   392  				return
   393  			}
   394  			g.Expect(allErrs).To(BeEmpty())
   395  		})
   396  	}
   397  }
   398  
   399  func TestClusterClassesAreCompatible(t *testing.T) {
   400  	ref := &corev1.ObjectReference{
   401  		APIVersion: "group.test.io/foo",
   402  		Kind:       "barTemplate",
   403  		Name:       "baz",
   404  		Namespace:  "default",
   405  	}
   406  	incompatibleRef := &corev1.ObjectReference{
   407  		APIVersion: "group.test.io/foo",
   408  		Kind:       "another-barTemplate",
   409  		Name:       "baz",
   410  		Namespace:  "default",
   411  	}
   412  	compatibleRef := &corev1.ObjectReference{
   413  		APIVersion: "group.test.io/another-foo",
   414  		Kind:       "barTemplate",
   415  		Name:       "another-baz",
   416  		Namespace:  "default",
   417  	}
   418  
   419  	tests := []struct {
   420  		name    string
   421  		current *clusterv1.ClusterClass
   422  		desired *clusterv1.ClusterClass
   423  		wantErr bool
   424  	}{
   425  		{
   426  			name:    "error if current is nil",
   427  			current: nil,
   428  			desired: builder.ClusterClass(metav1.NamespaceDefault, "class1").
   429  				WithInfrastructureClusterTemplate(
   430  					builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()).
   431  				WithControlPlaneTemplate(
   432  					refToUnstructured(ref)).
   433  				WithControlPlaneInfrastructureMachineTemplate(
   434  					refToUnstructured(ref)).
   435  				Build(),
   436  			wantErr: true,
   437  		},
   438  		{
   439  			name: "error if desired is nil",
   440  			current: builder.ClusterClass(metav1.NamespaceDefault, "class1").
   441  				WithInfrastructureClusterTemplate(
   442  					builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()).
   443  				WithControlPlaneTemplate(
   444  					refToUnstructured(ref)).
   445  				WithControlPlaneInfrastructureMachineTemplate(
   446  					refToUnstructured(ref)).
   447  				Build(),
   448  			desired: nil,
   449  			wantErr: true,
   450  		},
   451  
   452  		{
   453  			name: "pass for compatible clusterClasses",
   454  			current: builder.ClusterClass(metav1.NamespaceDefault, "class1").
   455  				WithInfrastructureClusterTemplate(
   456  					builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()).
   457  				WithControlPlaneTemplate(
   458  					refToUnstructured(ref)).
   459  				WithControlPlaneInfrastructureMachineTemplate(
   460  					refToUnstructured(ref)).
   461  				WithWorkerMachineDeploymentClasses(
   462  					*builder.MachineDeploymentClass("aa").
   463  						WithInfrastructureTemplate(
   464  							builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "infra1").Build()).
   465  						WithBootstrapTemplate(
   466  							builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()).
   467  						Build()).
   468  				WithWorkerMachinePoolClasses(
   469  					*builder.MachinePoolClass("bb").
   470  						WithInfrastructureTemplate(
   471  							builder.InfrastructureMachinePoolTemplate(metav1.NamespaceDefault, "infra1").Build()).
   472  						WithBootstrapTemplate(
   473  							builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()).
   474  						Build()).
   475  				Build(),
   476  			desired: builder.ClusterClass(metav1.NamespaceDefault, "class1").
   477  				WithInfrastructureClusterTemplate(
   478  					builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()).
   479  				WithControlPlaneTemplate(
   480  					refToUnstructured(compatibleRef)).
   481  				WithControlPlaneInfrastructureMachineTemplate(
   482  					refToUnstructured(compatibleRef)).
   483  				WithWorkerMachineDeploymentClasses(
   484  					*builder.MachineDeploymentClass("aa").
   485  						WithInfrastructureTemplate(
   486  							builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "infra1").Build()).
   487  						WithBootstrapTemplate(
   488  							builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()).
   489  						Build()).
   490  				WithWorkerMachinePoolClasses(
   491  					*builder.MachinePoolClass("bb").
   492  						WithInfrastructureTemplate(
   493  							builder.InfrastructureMachinePoolTemplate(metav1.NamespaceDefault, "infra1").Build()).
   494  						WithBootstrapTemplate(
   495  							builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()).
   496  						Build()).
   497  				Build(),
   498  			wantErr: false,
   499  		},
   500  		{
   501  			name: "error if clusterClass has incompatible ControlPlane ref",
   502  			current: builder.ClusterClass(metav1.NamespaceDefault, "class1").
   503  				WithInfrastructureClusterTemplate(
   504  					builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()).
   505  				WithControlPlaneTemplate(
   506  					refToUnstructured(ref)).
   507  				WithControlPlaneInfrastructureMachineTemplate(
   508  					refToUnstructured(ref)).
   509  				WithWorkerMachineDeploymentClasses(
   510  					*builder.MachineDeploymentClass("aa").
   511  						WithInfrastructureTemplate(
   512  							builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "infra1").Build()).
   513  						WithBootstrapTemplate(
   514  							builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()).
   515  						Build()).
   516  				WithWorkerMachinePoolClasses(
   517  					*builder.MachinePoolClass("bb").
   518  						WithInfrastructureTemplate(
   519  							builder.InfrastructureMachinePoolTemplate(metav1.NamespaceDefault, "infra1").Build()).
   520  						WithBootstrapTemplate(
   521  							builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()).
   522  						Build()).
   523  				Build(),
   524  			desired: builder.ClusterClass(metav1.NamespaceDefault, "class1").
   525  				WithInfrastructureClusterTemplate(
   526  					builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()).
   527  				WithControlPlaneTemplate(
   528  					refToUnstructured(incompatibleRef)).
   529  				WithControlPlaneInfrastructureMachineTemplate(
   530  					refToUnstructured(ref)).
   531  				WithWorkerMachineDeploymentClasses(
   532  					*builder.MachineDeploymentClass("aa").
   533  						WithInfrastructureTemplate(
   534  							builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "infra1").Build()).
   535  						WithBootstrapTemplate(
   536  							builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()).
   537  						Build()).
   538  				WithWorkerMachinePoolClasses(
   539  					*builder.MachinePoolClass("bb").
   540  						WithInfrastructureTemplate(
   541  							builder.InfrastructureMachinePoolTemplate(metav1.NamespaceDefault, "infra1").Build()).
   542  						WithBootstrapTemplate(
   543  							builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()).
   544  						Build()).
   545  				Build(),
   546  			wantErr: true,
   547  		},
   548  		{
   549  			name: "pass for incompatible ref in MachineDeploymentClass bootstrapTemplate clusterClasses",
   550  			current: builder.ClusterClass(metav1.NamespaceDefault, "class1").
   551  				WithInfrastructureClusterTemplate(
   552  					builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()).
   553  				WithControlPlaneTemplate(
   554  					refToUnstructured(ref)).
   555  				WithControlPlaneInfrastructureMachineTemplate(
   556  					refToUnstructured(ref)).
   557  				WithWorkerMachineDeploymentClasses(
   558  					*builder.MachineDeploymentClass("aa").
   559  						WithInfrastructureTemplate(
   560  							builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "infra1").Build()).
   561  						WithBootstrapTemplate(
   562  							refToUnstructured(ref)).Build()).
   563  				Build(),
   564  			desired: builder.ClusterClass(metav1.NamespaceDefault, "class1").
   565  				WithInfrastructureClusterTemplate(
   566  					builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()).
   567  				WithControlPlaneTemplate(
   568  					refToUnstructured(ref)).
   569  				WithControlPlaneInfrastructureMachineTemplate(
   570  					refToUnstructured(ref)).
   571  				WithWorkerMachineDeploymentClasses(
   572  					*builder.MachineDeploymentClass("aa").
   573  						WithInfrastructureTemplate(
   574  							builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "infra1").Build()).
   575  						WithBootstrapTemplate(
   576  							refToUnstructured(incompatibleRef)).Build()).
   577  				Build(),
   578  			wantErr: false,
   579  		},
   580  		{
   581  			name: "pass for incompatible ref in MachinePoolClass bootstrapTemplate clusterClasses",
   582  			current: builder.ClusterClass(metav1.NamespaceDefault, "class1").
   583  				WithInfrastructureClusterTemplate(
   584  					builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()).
   585  				WithControlPlaneTemplate(
   586  					refToUnstructured(ref)).
   587  				WithControlPlaneInfrastructureMachineTemplate(
   588  					refToUnstructured(ref)).
   589  				WithWorkerMachinePoolClasses(
   590  					*builder.MachinePoolClass("aa").
   591  						WithInfrastructureTemplate(
   592  							builder.InfrastructureMachinePoolTemplate(metav1.NamespaceDefault, "infra1").Build()).
   593  						WithBootstrapTemplate(
   594  							refToUnstructured(ref)).Build()).
   595  				Build(),
   596  			desired: builder.ClusterClass(metav1.NamespaceDefault, "class1").
   597  				WithInfrastructureClusterTemplate(
   598  					builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()).
   599  				WithControlPlaneTemplate(
   600  					refToUnstructured(ref)).
   601  				WithControlPlaneInfrastructureMachineTemplate(
   602  					refToUnstructured(ref)).
   603  				WithWorkerMachinePoolClasses(
   604  					*builder.MachinePoolClass("aa").
   605  						WithInfrastructureTemplate(
   606  							builder.InfrastructureMachinePoolTemplate(metav1.NamespaceDefault, "infra1").Build()).
   607  						WithBootstrapTemplate(
   608  							refToUnstructured(incompatibleRef)).Build()).
   609  				Build(),
   610  			wantErr: false,
   611  		},
   612  		{
   613  			name: "pass if machineDeploymentClass is removed from ClusterClass",
   614  			current: builder.ClusterClass(metav1.NamespaceDefault, "class1").
   615  				WithInfrastructureClusterTemplate(
   616  					builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()).
   617  				WithControlPlaneTemplate(
   618  					refToUnstructured(ref)).
   619  				WithControlPlaneInfrastructureMachineTemplate(
   620  					refToUnstructured(ref)).
   621  				WithWorkerMachineDeploymentClasses(
   622  					*builder.MachineDeploymentClass("aa").
   623  						WithInfrastructureTemplate(
   624  							builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "infra1").Build()).
   625  						WithBootstrapTemplate(
   626  							builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()).
   627  						Build(),
   628  					*builder.MachineDeploymentClass("bb").
   629  						WithInfrastructureTemplate(
   630  							builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "infra1").Build()).
   631  						WithBootstrapTemplate(
   632  							builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()).
   633  						Build()).
   634  				Build(),
   635  			desired: builder.ClusterClass(metav1.NamespaceDefault, "class1").
   636  				WithInfrastructureClusterTemplate(
   637  					builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()).
   638  				WithControlPlaneTemplate(
   639  					refToUnstructured(ref)).
   640  				WithControlPlaneInfrastructureMachineTemplate(
   641  					refToUnstructured(ref)).
   642  				WithWorkerMachineDeploymentClasses(
   643  					*builder.MachineDeploymentClass("aa").
   644  						WithInfrastructureTemplate(
   645  							builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "infra1").Build()).
   646  						WithBootstrapTemplate(
   647  							refToUnstructured(incompatibleRef)).Build()).
   648  				Build(),
   649  			wantErr: false,
   650  		},
   651  		{
   652  			name: "pass if machinePoolClass is removed from ClusterClass",
   653  			current: builder.ClusterClass(metav1.NamespaceDefault, "class1").
   654  				WithInfrastructureClusterTemplate(
   655  					builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()).
   656  				WithControlPlaneTemplate(
   657  					refToUnstructured(ref)).
   658  				WithControlPlaneInfrastructureMachineTemplate(
   659  					refToUnstructured(ref)).
   660  				WithWorkerMachinePoolClasses(
   661  					*builder.MachinePoolClass("aa").
   662  						WithInfrastructureTemplate(
   663  							builder.InfrastructureMachinePoolTemplate(metav1.NamespaceDefault, "infra1").Build()).
   664  						WithBootstrapTemplate(
   665  							builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()).
   666  						Build(),
   667  					*builder.MachinePoolClass("bb").
   668  						WithInfrastructureTemplate(
   669  							builder.InfrastructureMachinePoolTemplate(metav1.NamespaceDefault, "infra1").Build()).
   670  						WithBootstrapTemplate(
   671  							builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()).
   672  						Build()).
   673  				Build(),
   674  			desired: builder.ClusterClass(metav1.NamespaceDefault, "class1").
   675  				WithInfrastructureClusterTemplate(
   676  					builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()).
   677  				WithControlPlaneTemplate(
   678  					refToUnstructured(ref)).
   679  				WithControlPlaneInfrastructureMachineTemplate(
   680  					refToUnstructured(ref)).
   681  				WithWorkerMachinePoolClasses(
   682  					*builder.MachinePoolClass("aa").
   683  						WithInfrastructureTemplate(
   684  							builder.InfrastructureMachinePoolTemplate(metav1.NamespaceDefault, "infra1").Build()).
   685  						WithBootstrapTemplate(
   686  							refToUnstructured(incompatibleRef)).Build()).
   687  				Build(),
   688  			wantErr: false,
   689  		},
   690  	}
   691  	for _, tt := range tests {
   692  		g := NewWithT(t)
   693  		t.Run(tt.name, func(*testing.T) {
   694  			allErrs := ClusterClassesAreCompatible(tt.current, tt.desired)
   695  			if tt.wantErr {
   696  				g.Expect(allErrs).ToNot(BeEmpty())
   697  				return
   698  			}
   699  			g.Expect(allErrs).To(BeEmpty())
   700  		})
   701  	}
   702  }
   703  
   704  func TestMachineDeploymentClassesAreCompatible(t *testing.T) {
   705  	ref := &corev1.ObjectReference{
   706  		APIVersion: "group.test.io/foo",
   707  		Kind:       "barTemplate",
   708  		Name:       "baz",
   709  		Namespace:  "default",
   710  	}
   711  	compatibleRef := &corev1.ObjectReference{
   712  		APIVersion: "group.test.io/another-foo",
   713  		Kind:       "barTemplate",
   714  		Name:       "another-baz",
   715  		Namespace:  "default",
   716  	}
   717  	incompatibleRef := &corev1.ObjectReference{
   718  		APIVersion: "group.test.io/foo",
   719  		Kind:       "another-barTemplate",
   720  		Name:       "baz",
   721  		Namespace:  "default",
   722  	}
   723  
   724  	tests := []struct {
   725  		name    string
   726  		current *clusterv1.ClusterClass
   727  		desired *clusterv1.ClusterClass
   728  		wantErr bool
   729  	}{
   730  		{
   731  			name: "pass if machineDeploymentClasses are compatible",
   732  			current: builder.ClusterClass(metav1.NamespaceDefault, "class1").
   733  				WithInfrastructureClusterTemplate(
   734  					builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()).
   735  				WithControlPlaneTemplate(
   736  					refToUnstructured(ref)).
   737  				WithControlPlaneInfrastructureMachineTemplate(
   738  					refToUnstructured(ref)).
   739  				WithWorkerMachineDeploymentClasses(
   740  					*builder.MachineDeploymentClass("aa").
   741  						WithInfrastructureTemplate(
   742  							refToUnstructured(ref)).
   743  						WithBootstrapTemplate(
   744  							refToUnstructured(ref)).
   745  						Build()).
   746  				Build(),
   747  			desired: builder.ClusterClass(metav1.NamespaceDefault, "class1").
   748  				WithInfrastructureClusterTemplate(
   749  					builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()).
   750  				WithControlPlaneTemplate(
   751  					refToUnstructured(ref)).
   752  				WithControlPlaneInfrastructureMachineTemplate(
   753  					refToUnstructured(ref)).
   754  				WithWorkerMachineDeploymentClasses(
   755  					*builder.MachineDeploymentClass("aa").
   756  						WithInfrastructureTemplate(
   757  							refToUnstructured(compatibleRef)).
   758  						WithBootstrapTemplate(
   759  							refToUnstructured(incompatibleRef)).Build()).
   760  				Build(),
   761  			wantErr: false,
   762  		},
   763  		{
   764  			name: "pass if machineDeploymentClass is removed from ClusterClass",
   765  			current: builder.ClusterClass(metav1.NamespaceDefault, "class1").
   766  				WithInfrastructureClusterTemplate(
   767  					builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()).
   768  				WithControlPlaneTemplate(
   769  					refToUnstructured(ref)).
   770  				WithControlPlaneInfrastructureMachineTemplate(
   771  					refToUnstructured(ref)).
   772  				WithWorkerMachineDeploymentClasses(
   773  					*builder.MachineDeploymentClass("aa").
   774  						WithInfrastructureTemplate(
   775  							builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "infra1").Build()).
   776  						WithBootstrapTemplate(
   777  							builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()).
   778  						Build(),
   779  					*builder.MachineDeploymentClass("bb").
   780  						WithInfrastructureTemplate(
   781  							builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "infra1").Build()).
   782  						WithBootstrapTemplate(
   783  							builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()).
   784  						Build()).
   785  				Build(),
   786  			desired: builder.ClusterClass(metav1.NamespaceDefault, "class1").
   787  				WithInfrastructureClusterTemplate(
   788  					builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()).
   789  				WithControlPlaneTemplate(
   790  					refToUnstructured(ref)).
   791  				WithControlPlaneInfrastructureMachineTemplate(
   792  					refToUnstructured(ref)).
   793  				WithWorkerMachineDeploymentClasses(
   794  					*builder.MachineDeploymentClass("aa").
   795  						WithInfrastructureTemplate(
   796  							builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "infra1").Build()).
   797  						WithBootstrapTemplate(
   798  							refToUnstructured(incompatibleRef)).Build()).
   799  				Build(),
   800  			wantErr: false,
   801  		},
   802  		{
   803  			name: "error if machineDeploymentClass has multiple incompatible references",
   804  			current: builder.ClusterClass(metav1.NamespaceDefault, "class1").
   805  				WithInfrastructureClusterTemplate(
   806  					builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()).
   807  				WithControlPlaneTemplate(
   808  					refToUnstructured(ref)).
   809  				WithControlPlaneInfrastructureMachineTemplate(
   810  					refToUnstructured(ref)).
   811  				WithWorkerMachineDeploymentClasses(
   812  					*builder.MachineDeploymentClass("aa").
   813  						WithInfrastructureTemplate(
   814  							refToUnstructured(ref)).
   815  						WithBootstrapTemplate(
   816  							refToUnstructured(ref)).
   817  						Build(),
   818  				).
   819  				Build(),
   820  			desired: builder.ClusterClass(metav1.NamespaceDefault, "class1").
   821  				WithInfrastructureClusterTemplate(
   822  					builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()).
   823  				WithControlPlaneTemplate(
   824  					refToUnstructured(incompatibleRef)).
   825  				WithControlPlaneInfrastructureMachineTemplate(
   826  					refToUnstructured(incompatibleRef)).
   827  				WithWorkerMachineDeploymentClasses(
   828  					*builder.MachineDeploymentClass("aa").
   829  						WithInfrastructureTemplate(
   830  							refToUnstructured(incompatibleRef)).
   831  						WithBootstrapTemplate(
   832  							refToUnstructured(compatibleRef)).Build()).
   833  				Build(),
   834  			wantErr: true,
   835  		},
   836  	}
   837  	for _, tt := range tests {
   838  		t.Run(tt.name, func(t *testing.T) {
   839  			g := NewWithT(t)
   840  			allErrs := MachineDeploymentClassesAreCompatible(tt.current, tt.desired)
   841  			if tt.wantErr {
   842  				g.Expect(allErrs).ToNot(BeEmpty())
   843  				return
   844  			}
   845  			g.Expect(allErrs).To(BeEmpty())
   846  		})
   847  	}
   848  }
   849  
   850  func TestMachinePoolClassesAreCompatible(t *testing.T) {
   851  	ref := &corev1.ObjectReference{
   852  		APIVersion: "group.test.io/foo",
   853  		Kind:       "barTemplate",
   854  		Name:       "baz",
   855  		Namespace:  "default",
   856  	}
   857  	compatibleRef := &corev1.ObjectReference{
   858  		APIVersion: "group.test.io/another-foo",
   859  		Kind:       "barTemplate",
   860  		Name:       "another-baz",
   861  		Namespace:  "default",
   862  	}
   863  	incompatibleRef := &corev1.ObjectReference{
   864  		APIVersion: "group.test.io/foo",
   865  		Kind:       "another-barTemplate",
   866  		Name:       "baz",
   867  		Namespace:  "default",
   868  	}
   869  
   870  	tests := []struct {
   871  		name    string
   872  		current *clusterv1.ClusterClass
   873  		desired *clusterv1.ClusterClass
   874  		wantErr bool
   875  	}{
   876  		{
   877  			name: "pass if MachinePoolClasses are compatible",
   878  			current: builder.ClusterClass(metav1.NamespaceDefault, "class1").
   879  				WithInfrastructureClusterTemplate(
   880  					builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()).
   881  				WithControlPlaneTemplate(
   882  					refToUnstructured(ref)).
   883  				WithControlPlaneInfrastructureMachineTemplate(
   884  					refToUnstructured(ref)).
   885  				WithWorkerMachinePoolClasses(
   886  					*builder.MachinePoolClass("aa").
   887  						WithInfrastructureTemplate(
   888  							refToUnstructured(ref)).
   889  						WithBootstrapTemplate(
   890  							refToUnstructured(ref)).
   891  						Build()).
   892  				Build(),
   893  			desired: builder.ClusterClass(metav1.NamespaceDefault, "class1").
   894  				WithInfrastructureClusterTemplate(
   895  					builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()).
   896  				WithControlPlaneTemplate(
   897  					refToUnstructured(ref)).
   898  				WithControlPlaneInfrastructureMachineTemplate(
   899  					refToUnstructured(ref)).
   900  				WithWorkerMachinePoolClasses(
   901  					*builder.MachinePoolClass("aa").
   902  						WithInfrastructureTemplate(
   903  							refToUnstructured(compatibleRef)).
   904  						WithBootstrapTemplate(
   905  							refToUnstructured(incompatibleRef)).Build()).
   906  				Build(),
   907  			wantErr: false,
   908  		},
   909  		{
   910  			name: "pass if MachinePoolClass is removed from ClusterClass",
   911  			current: builder.ClusterClass(metav1.NamespaceDefault, "class1").
   912  				WithInfrastructureClusterTemplate(
   913  					builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()).
   914  				WithControlPlaneTemplate(
   915  					refToUnstructured(ref)).
   916  				WithControlPlaneInfrastructureMachineTemplate(
   917  					refToUnstructured(ref)).
   918  				WithWorkerMachinePoolClasses(
   919  					*builder.MachinePoolClass("aa").
   920  						WithInfrastructureTemplate(
   921  							builder.InfrastructureMachinePoolTemplate(metav1.NamespaceDefault, "infra1").Build()).
   922  						WithBootstrapTemplate(
   923  							builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()).
   924  						Build(),
   925  					*builder.MachinePoolClass("bb").
   926  						WithInfrastructureTemplate(
   927  							builder.InfrastructureMachinePoolTemplate(metav1.NamespaceDefault, "infra1").Build()).
   928  						WithBootstrapTemplate(
   929  							builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()).
   930  						Build()).
   931  				Build(),
   932  			desired: builder.ClusterClass(metav1.NamespaceDefault, "class1").
   933  				WithInfrastructureClusterTemplate(
   934  					builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()).
   935  				WithControlPlaneTemplate(
   936  					refToUnstructured(ref)).
   937  				WithControlPlaneInfrastructureMachineTemplate(
   938  					refToUnstructured(ref)).
   939  				WithWorkerMachinePoolClasses(
   940  					*builder.MachinePoolClass("aa").
   941  						WithInfrastructureTemplate(
   942  							builder.InfrastructureMachinePoolTemplate(metav1.NamespaceDefault, "infra1").Build()).
   943  						WithBootstrapTemplate(
   944  							refToUnstructured(incompatibleRef)).Build()).
   945  				Build(),
   946  			wantErr: false,
   947  		},
   948  		{
   949  			name: "error if MachinePoolClass has multiple incompatible references",
   950  			current: builder.ClusterClass(metav1.NamespaceDefault, "class1").
   951  				WithInfrastructureClusterTemplate(
   952  					builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()).
   953  				WithControlPlaneTemplate(
   954  					refToUnstructured(ref)).
   955  				WithControlPlaneInfrastructureMachineTemplate(
   956  					refToUnstructured(ref)).
   957  				WithWorkerMachinePoolClasses(
   958  					*builder.MachinePoolClass("aa").
   959  						WithInfrastructureTemplate(
   960  							refToUnstructured(ref)).
   961  						WithBootstrapTemplate(
   962  							refToUnstructured(ref)).
   963  						Build(),
   964  				).
   965  				Build(),
   966  			desired: builder.ClusterClass(metav1.NamespaceDefault, "class1").
   967  				WithInfrastructureClusterTemplate(
   968  					builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()).
   969  				WithControlPlaneTemplate(
   970  					refToUnstructured(incompatibleRef)).
   971  				WithControlPlaneInfrastructureMachineTemplate(
   972  					refToUnstructured(incompatibleRef)).
   973  				WithWorkerMachinePoolClasses(
   974  					*builder.MachinePoolClass("aa").
   975  						WithInfrastructureTemplate(
   976  							refToUnstructured(incompatibleRef)).
   977  						WithBootstrapTemplate(
   978  							refToUnstructured(compatibleRef)).Build()).
   979  				Build(),
   980  			wantErr: true,
   981  		},
   982  	}
   983  	for _, tt := range tests {
   984  		t.Run(tt.name, func(t *testing.T) {
   985  			g := NewWithT(t)
   986  			allErrs := MachinePoolClassesAreCompatible(tt.current, tt.desired)
   987  			if tt.wantErr {
   988  				g.Expect(allErrs).ToNot(BeEmpty())
   989  				return
   990  			}
   991  			g.Expect(allErrs).To(BeEmpty())
   992  		})
   993  	}
   994  }
   995  
   996  func TestMachineDeploymentClassesAreUnique(t *testing.T) {
   997  	tests := []struct {
   998  		name         string
   999  		clusterClass *clusterv1.ClusterClass
  1000  		wantErr      bool
  1001  	}{
  1002  		{
  1003  			name: "pass if MachineDeploymentClasses are unique",
  1004  			clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1").
  1005  				WithInfrastructureClusterTemplate(
  1006  					builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()).
  1007  				WithControlPlaneTemplate(
  1008  					builder.ControlPlane(metav1.NamespaceDefault, "cp1").Build()).
  1009  				WithControlPlaneInfrastructureMachineTemplate(
  1010  					builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "cpinfra1").Build()).
  1011  				WithWorkerMachineDeploymentClasses(
  1012  					*builder.MachineDeploymentClass("aa").
  1013  						WithInfrastructureTemplate(
  1014  							builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "infra1").Build()).
  1015  						WithBootstrapTemplate(
  1016  							builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()).
  1017  						Build(),
  1018  					*builder.MachineDeploymentClass("bb").
  1019  						WithInfrastructureTemplate(
  1020  							builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "infra1").Build()).
  1021  						WithBootstrapTemplate(
  1022  							builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()).
  1023  						Build()).
  1024  				Build(),
  1025  			wantErr: false,
  1026  		},
  1027  		{
  1028  			name: "fail if MachineDeploymentClasses are duplicated",
  1029  			clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1").
  1030  				WithInfrastructureClusterTemplate(
  1031  					builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()).
  1032  				WithControlPlaneTemplate(
  1033  					builder.ControlPlane(metav1.NamespaceDefault, "cp1").Build()).
  1034  				WithControlPlaneInfrastructureMachineTemplate(
  1035  					builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "cpinfra1").Build()).
  1036  				WithWorkerMachineDeploymentClasses(
  1037  					*builder.MachineDeploymentClass("aa").
  1038  						WithInfrastructureTemplate(
  1039  							builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "infra1").Build()).
  1040  						WithBootstrapTemplate(
  1041  							builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()).
  1042  						Build(),
  1043  					*builder.MachineDeploymentClass("aa").
  1044  						WithInfrastructureTemplate(
  1045  							builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "infra1").Build()).
  1046  						WithBootstrapTemplate(
  1047  							builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()).
  1048  						Build()).
  1049  				Build(),
  1050  			wantErr: true,
  1051  		},
  1052  		{
  1053  			name: "fail if multiple MachineDeploymentClasses are identical",
  1054  			clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1").
  1055  				WithInfrastructureClusterTemplate(
  1056  					builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()).
  1057  				WithControlPlaneTemplate(
  1058  					builder.ControlPlane(metav1.NamespaceDefault, "cp1").Build()).
  1059  				WithControlPlaneInfrastructureMachineTemplate(
  1060  					builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "cpinfra1").Build()).
  1061  				WithWorkerMachineDeploymentClasses(
  1062  					*builder.MachineDeploymentClass("aa").
  1063  						WithInfrastructureTemplate(
  1064  							builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "infra1").Build()).
  1065  						WithBootstrapTemplate(
  1066  							builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()).
  1067  						Build(),
  1068  					*builder.MachineDeploymentClass("aa").
  1069  						WithInfrastructureTemplate(
  1070  							builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "infra1").Build()).
  1071  						WithBootstrapTemplate(
  1072  							builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()).
  1073  						Build(),
  1074  					*builder.MachineDeploymentClass("aa").
  1075  						WithInfrastructureTemplate(
  1076  							builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "infra1").Build()).
  1077  						WithBootstrapTemplate(
  1078  							builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()).
  1079  						Build(),
  1080  					*builder.MachineDeploymentClass("aa").
  1081  						WithInfrastructureTemplate(
  1082  							builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "infra1").Build()).
  1083  						WithBootstrapTemplate(
  1084  							builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()).
  1085  						Build(),
  1086  				).
  1087  				Build(),
  1088  			wantErr: true,
  1089  		},
  1090  	}
  1091  	for _, tt := range tests {
  1092  		t.Run(tt.name, func(t *testing.T) {
  1093  			g := NewWithT(t)
  1094  			allErrs := MachineDeploymentClassesAreUnique(tt.clusterClass)
  1095  			if tt.wantErr {
  1096  				g.Expect(allErrs).ToNot(BeEmpty())
  1097  				return
  1098  			}
  1099  			g.Expect(allErrs).To(BeEmpty())
  1100  		})
  1101  	}
  1102  }
  1103  
  1104  func TestMachinePoolClassesAreUnique(t *testing.T) {
  1105  	tests := []struct {
  1106  		name         string
  1107  		clusterClass *clusterv1.ClusterClass
  1108  		wantErr      bool
  1109  	}{
  1110  		{
  1111  			name: "pass if MachinePoolClasses are unique",
  1112  			clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1").
  1113  				WithInfrastructureClusterTemplate(
  1114  					builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()).
  1115  				WithControlPlaneTemplate(
  1116  					builder.ControlPlane(metav1.NamespaceDefault, "cp1").Build()).
  1117  				WithControlPlaneInfrastructureMachineTemplate(
  1118  					builder.InfrastructureMachinePoolTemplate(metav1.NamespaceDefault, "cpinfra1").Build()).
  1119  				WithWorkerMachinePoolClasses(
  1120  					*builder.MachinePoolClass("aa").
  1121  						WithInfrastructureTemplate(
  1122  							builder.InfrastructureMachinePoolTemplate(metav1.NamespaceDefault, "infra1").Build()).
  1123  						WithBootstrapTemplate(
  1124  							builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()).
  1125  						Build(),
  1126  					*builder.MachinePoolClass("bb").
  1127  						WithInfrastructureTemplate(
  1128  							builder.InfrastructureMachinePoolTemplate(metav1.NamespaceDefault, "infra1").Build()).
  1129  						WithBootstrapTemplate(
  1130  							builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()).
  1131  						Build()).
  1132  				Build(),
  1133  			wantErr: false,
  1134  		},
  1135  		{
  1136  			name: "fail if MachinePoolClasses are duplicated",
  1137  			clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1").
  1138  				WithInfrastructureClusterTemplate(
  1139  					builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()).
  1140  				WithControlPlaneTemplate(
  1141  					builder.ControlPlane(metav1.NamespaceDefault, "cp1").Build()).
  1142  				WithControlPlaneInfrastructureMachineTemplate(
  1143  					builder.InfrastructureMachinePoolTemplate(metav1.NamespaceDefault, "cpinfra1").Build()).
  1144  				WithWorkerMachinePoolClasses(
  1145  					*builder.MachinePoolClass("aa").
  1146  						WithInfrastructureTemplate(
  1147  							builder.InfrastructureMachinePoolTemplate(metav1.NamespaceDefault, "infra1").Build()).
  1148  						WithBootstrapTemplate(
  1149  							builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()).
  1150  						Build(),
  1151  					*builder.MachinePoolClass("aa").
  1152  						WithInfrastructureTemplate(
  1153  							builder.InfrastructureMachinePoolTemplate(metav1.NamespaceDefault, "infra1").Build()).
  1154  						WithBootstrapTemplate(
  1155  							builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()).
  1156  						Build()).
  1157  				Build(),
  1158  			wantErr: true,
  1159  		},
  1160  		{
  1161  			name: "fail if multiple MachinePoolClasses are identical",
  1162  			clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1").
  1163  				WithInfrastructureClusterTemplate(
  1164  					builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()).
  1165  				WithControlPlaneTemplate(
  1166  					builder.ControlPlane(metav1.NamespaceDefault, "cp1").Build()).
  1167  				WithControlPlaneInfrastructureMachineTemplate(
  1168  					builder.InfrastructureMachinePoolTemplate(metav1.NamespaceDefault, "cpinfra1").Build()).
  1169  				WithWorkerMachinePoolClasses(
  1170  					*builder.MachinePoolClass("aa").
  1171  						WithInfrastructureTemplate(
  1172  							builder.InfrastructureMachinePoolTemplate(metav1.NamespaceDefault, "infra1").Build()).
  1173  						WithBootstrapTemplate(
  1174  							builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()).
  1175  						Build(),
  1176  					*builder.MachinePoolClass("aa").
  1177  						WithInfrastructureTemplate(
  1178  							builder.InfrastructureMachinePoolTemplate(metav1.NamespaceDefault, "infra1").Build()).
  1179  						WithBootstrapTemplate(
  1180  							builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()).
  1181  						Build(),
  1182  					*builder.MachinePoolClass("aa").
  1183  						WithInfrastructureTemplate(
  1184  							builder.InfrastructureMachinePoolTemplate(metav1.NamespaceDefault, "infra1").Build()).
  1185  						WithBootstrapTemplate(
  1186  							builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()).
  1187  						Build(),
  1188  					*builder.MachinePoolClass("aa").
  1189  						WithInfrastructureTemplate(
  1190  							builder.InfrastructureMachinePoolTemplate(metav1.NamespaceDefault, "infra1").Build()).
  1191  						WithBootstrapTemplate(
  1192  							builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()).
  1193  						Build(),
  1194  				).
  1195  				Build(),
  1196  			wantErr: true,
  1197  		},
  1198  	}
  1199  	for _, tt := range tests {
  1200  		t.Run(tt.name, func(t *testing.T) {
  1201  			g := NewWithT(t)
  1202  			allErrs := MachinePoolClassesAreUnique(tt.clusterClass)
  1203  			if tt.wantErr {
  1204  				g.Expect(allErrs).ToNot(BeEmpty())
  1205  				return
  1206  			}
  1207  			g.Expect(allErrs).To(BeEmpty())
  1208  		})
  1209  	}
  1210  }
  1211  
  1212  func TestMachineDeploymentTopologiesAreUniqueAndDefinedInClusterClass(t *testing.T) {
  1213  	tests := []struct {
  1214  		name         string
  1215  		clusterClass *clusterv1.ClusterClass
  1216  		cluster      *clusterv1.Cluster
  1217  		wantErr      bool
  1218  	}{
  1219  		{
  1220  			name: "fail if MachineDeploymentTopologies name is empty",
  1221  			clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1").
  1222  				WithInfrastructureClusterTemplate(
  1223  					builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()).
  1224  				WithControlPlaneTemplate(
  1225  					builder.ControlPlane(metav1.NamespaceDefault, "cp1").Build()).
  1226  				WithWorkerMachineDeploymentClasses(
  1227  					*builder.MachineDeploymentClass("aa").
  1228  						WithInfrastructureTemplate(
  1229  							builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "infra1").Build()).
  1230  						WithBootstrapTemplate(
  1231  							builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()).
  1232  						Build()).
  1233  				Build(),
  1234  			cluster: builder.Cluster("fooboo", "cluster1").
  1235  				WithTopology(builder.ClusterTopology().
  1236  					WithClass("foo").
  1237  					WithVersion("v1.19.1").
  1238  					WithMachineDeployment(
  1239  						// The name should not be empty.
  1240  						builder.MachineDeploymentTopology("").
  1241  							WithClass("aa").
  1242  							Build()).
  1243  					Build()).
  1244  				Build(),
  1245  			wantErr: true,
  1246  		},
  1247  		{
  1248  			name: "pass if MachineDeploymentTopologies are unique and defined in ClusterClass",
  1249  			clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1").
  1250  				WithInfrastructureClusterTemplate(
  1251  					builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()).
  1252  				WithControlPlaneTemplate(
  1253  					builder.ControlPlane(metav1.NamespaceDefault, "cp1").Build()).
  1254  				WithControlPlaneInfrastructureMachineTemplate(
  1255  					builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "cpinfra1").Build()).
  1256  				WithWorkerMachineDeploymentClasses(
  1257  					*builder.MachineDeploymentClass("aa").
  1258  						WithInfrastructureTemplate(
  1259  							builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "infra1").Build()).
  1260  						WithBootstrapTemplate(
  1261  							builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()).
  1262  						Build()).
  1263  				Build(),
  1264  			cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1").
  1265  				WithTopology(
  1266  					builder.ClusterTopology().
  1267  						WithClass("class1").
  1268  						WithVersion("v1.22.2").
  1269  						WithMachineDeployment(
  1270  							builder.MachineDeploymentTopology("workers1").
  1271  								WithClass("aa").
  1272  								Build()).
  1273  						Build()).
  1274  				Build(),
  1275  			wantErr: false,
  1276  		},
  1277  		{
  1278  			name: "fail if MachineDeploymentTopologies are unique but not defined in ClusterClass",
  1279  			clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1").
  1280  				WithInfrastructureClusterTemplate(
  1281  					builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()).
  1282  				WithControlPlaneTemplate(
  1283  					builder.ControlPlane(metav1.NamespaceDefault, "cp1").Build()).
  1284  				WithControlPlaneInfrastructureMachineTemplate(
  1285  					builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "cpinfra1").Build()).
  1286  				WithWorkerMachineDeploymentClasses(
  1287  					*builder.MachineDeploymentClass("aa").
  1288  						WithInfrastructureTemplate(
  1289  							builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "infra1").Build()).
  1290  						WithBootstrapTemplate(
  1291  							builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()).
  1292  						Build()).
  1293  				Build(),
  1294  			cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1").
  1295  				WithTopology(
  1296  					builder.ClusterTopology().
  1297  						WithClass("class1").
  1298  						WithVersion("v1.22.2").
  1299  						WithMachineDeployment(
  1300  							builder.MachineDeploymentTopology("workers1").
  1301  								WithClass("bb").
  1302  								Build()).
  1303  						Build()).
  1304  				Build(),
  1305  			wantErr: true,
  1306  		},
  1307  		{
  1308  			name: "fail if MachineDeploymentTopologies are not unique but is defined in ClusterClass",
  1309  			clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1").
  1310  				WithInfrastructureClusterTemplate(
  1311  					builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()).
  1312  				WithControlPlaneTemplate(
  1313  					builder.ControlPlane(metav1.NamespaceDefault, "cp1").Build()).
  1314  				WithControlPlaneInfrastructureMachineTemplate(
  1315  					builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "cpinfra1").Build()).
  1316  				WithWorkerMachineDeploymentClasses(
  1317  					*builder.MachineDeploymentClass("aa").
  1318  						WithInfrastructureTemplate(
  1319  							builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "infra1").Build()).
  1320  						WithBootstrapTemplate(
  1321  							builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()).
  1322  						Build()).
  1323  				Build(),
  1324  			cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1").
  1325  				WithTopology(
  1326  					builder.ClusterTopology().
  1327  						WithClass("class1").
  1328  						WithVersion("v1.22.2").
  1329  						WithMachineDeployment(
  1330  							builder.MachineDeploymentTopology("workers1").
  1331  								WithClass("aa").
  1332  								Build()).
  1333  						WithMachineDeployment(
  1334  							builder.MachineDeploymentTopology("workers1").
  1335  								WithClass("aa").
  1336  								Build()).
  1337  						Build()).
  1338  				Build(),
  1339  			wantErr: true,
  1340  		},
  1341  		{
  1342  			name: "pass if MachineDeploymentTopologies are unique and share a class that is defined in ClusterClass",
  1343  			clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1").
  1344  				WithInfrastructureClusterTemplate(
  1345  					builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()).
  1346  				WithControlPlaneTemplate(
  1347  					builder.ControlPlane(metav1.NamespaceDefault, "cp1").Build()).
  1348  				WithControlPlaneInfrastructureMachineTemplate(
  1349  					builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "cpinfra1").Build()).
  1350  				WithWorkerMachineDeploymentClasses(
  1351  					*builder.MachineDeploymentClass("aa").
  1352  						WithInfrastructureTemplate(
  1353  							builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "infra1").Build()).
  1354  						WithBootstrapTemplate(
  1355  							builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()).
  1356  						Build()).
  1357  				Build(),
  1358  			cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1").
  1359  				WithTopology(
  1360  					builder.ClusterTopology().
  1361  						WithClass("class1").
  1362  						WithVersion("v1.22.2").
  1363  						WithMachineDeployment(
  1364  							builder.MachineDeploymentTopology("workers1").
  1365  								WithClass("aa").
  1366  								Build()).
  1367  						WithMachineDeployment(
  1368  							builder.MachineDeploymentTopology("workers2").
  1369  								WithClass("aa").
  1370  								Build()).
  1371  						Build()).
  1372  				Build(),
  1373  			wantErr: false,
  1374  		},
  1375  		{
  1376  			name: "fail if MachineDeploymentTopology name is longer than 63 characters",
  1377  			clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1").
  1378  				WithInfrastructureClusterTemplate(
  1379  					builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()).
  1380  				WithControlPlaneTemplate(
  1381  					builder.ControlPlane(metav1.NamespaceDefault, "cp1").Build()).
  1382  				WithControlPlaneInfrastructureMachineTemplate(
  1383  					builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "cpinfra1").Build()).
  1384  				WithWorkerMachineDeploymentClasses(
  1385  					*builder.MachineDeploymentClass("aa").
  1386  						WithInfrastructureTemplate(
  1387  							builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "infra1").Build()).
  1388  						WithBootstrapTemplate(
  1389  							builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()).
  1390  						Build()).
  1391  				Build(),
  1392  			cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1").
  1393  				WithTopology(
  1394  					builder.ClusterTopology().
  1395  						WithClass("class1").
  1396  						WithVersion("v1.22.2").
  1397  						WithMachineDeployment(
  1398  							builder.MachineDeploymentTopology("machine-deployment-topology-name-that-has-longerthan63characterlooooooooooooooooooooooongname").
  1399  								WithClass("aa").
  1400  								Build()).
  1401  						Build()).
  1402  				Build(),
  1403  			wantErr: true,
  1404  		},
  1405  	}
  1406  	for _, tt := range tests {
  1407  		t.Run(tt.name, func(t *testing.T) {
  1408  			g := NewWithT(t)
  1409  			allErrs := MachineDeploymentTopologiesAreValidAndDefinedInClusterClass(tt.cluster, tt.clusterClass)
  1410  			if tt.wantErr {
  1411  				g.Expect(allErrs).ToNot(BeEmpty())
  1412  				return
  1413  			}
  1414  			g.Expect(allErrs).To(BeEmpty())
  1415  		})
  1416  	}
  1417  }
  1418  
  1419  func TestMachinePoolTopologiesAreUniqueAndDefinedInClusterClass(t *testing.T) {
  1420  	tests := []struct {
  1421  		name         string
  1422  		clusterClass *clusterv1.ClusterClass
  1423  		cluster      *clusterv1.Cluster
  1424  		wantErr      bool
  1425  	}{
  1426  		{
  1427  			name: "fail if MachinePoolTopologies name is empty",
  1428  			clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1").
  1429  				WithInfrastructureClusterTemplate(
  1430  					builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()).
  1431  				WithControlPlaneTemplate(
  1432  					builder.ControlPlane(metav1.NamespaceDefault, "cp1").Build()).
  1433  				WithWorkerMachinePoolClasses(
  1434  					*builder.MachinePoolClass("aa").
  1435  						WithInfrastructureTemplate(
  1436  							builder.InfrastructureMachinePoolTemplate(metav1.NamespaceDefault, "infra1").Build()).
  1437  						WithBootstrapTemplate(
  1438  							builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()).
  1439  						Build()).
  1440  				Build(),
  1441  			cluster: builder.Cluster("fooboo", "cluster1").
  1442  				WithTopology(builder.ClusterTopology().
  1443  					WithClass("foo").
  1444  					WithVersion("v1.19.1").
  1445  					WithMachinePool(
  1446  						// The name should not be empty.
  1447  						builder.MachinePoolTopology("").
  1448  							WithClass("aa").
  1449  							Build()).
  1450  					Build()).
  1451  				Build(),
  1452  			wantErr: true,
  1453  		},
  1454  		{
  1455  			name: "pass if MachinePoolTopologies are unique and defined in ClusterClass",
  1456  			clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1").
  1457  				WithInfrastructureClusterTemplate(
  1458  					builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()).
  1459  				WithControlPlaneTemplate(
  1460  					builder.ControlPlane(metav1.NamespaceDefault, "cp1").Build()).
  1461  				WithControlPlaneInfrastructureMachineTemplate(
  1462  					builder.InfrastructureMachinePoolTemplate(metav1.NamespaceDefault, "cpinfra1").Build()).
  1463  				WithWorkerMachinePoolClasses(
  1464  					*builder.MachinePoolClass("aa").
  1465  						WithInfrastructureTemplate(
  1466  							builder.InfrastructureMachinePoolTemplate(metav1.NamespaceDefault, "infra1").Build()).
  1467  						WithBootstrapTemplate(
  1468  							builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()).
  1469  						Build()).
  1470  				Build(),
  1471  			cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1").
  1472  				WithTopology(
  1473  					builder.ClusterTopology().
  1474  						WithClass("class1").
  1475  						WithVersion("v1.22.2").
  1476  						WithMachinePool(
  1477  							builder.MachinePoolTopology("workers1").
  1478  								WithClass("aa").
  1479  								Build()).
  1480  						Build()).
  1481  				Build(),
  1482  			wantErr: false,
  1483  		},
  1484  		{
  1485  			name: "fail if MachinePoolTopologies are unique but not defined in ClusterClass",
  1486  			clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1").
  1487  				WithInfrastructureClusterTemplate(
  1488  					builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()).
  1489  				WithControlPlaneTemplate(
  1490  					builder.ControlPlane(metav1.NamespaceDefault, "cp1").Build()).
  1491  				WithControlPlaneInfrastructureMachineTemplate(
  1492  					builder.InfrastructureMachinePoolTemplate(metav1.NamespaceDefault, "cpinfra1").Build()).
  1493  				WithWorkerMachinePoolClasses(
  1494  					*builder.MachinePoolClass("aa").
  1495  						WithInfrastructureTemplate(
  1496  							builder.InfrastructureMachinePoolTemplate(metav1.NamespaceDefault, "infra1").Build()).
  1497  						WithBootstrapTemplate(
  1498  							builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()).
  1499  						Build()).
  1500  				Build(),
  1501  			cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1").
  1502  				WithTopology(
  1503  					builder.ClusterTopology().
  1504  						WithClass("class1").
  1505  						WithVersion("v1.22.2").
  1506  						WithMachinePool(
  1507  							builder.MachinePoolTopology("workers1").
  1508  								WithClass("bb").
  1509  								Build()).
  1510  						Build()).
  1511  				Build(),
  1512  			wantErr: true,
  1513  		},
  1514  		{
  1515  			name: "fail if MachinePoolTopologies are not unique but is defined in ClusterClass",
  1516  			clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1").
  1517  				WithInfrastructureClusterTemplate(
  1518  					builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()).
  1519  				WithControlPlaneTemplate(
  1520  					builder.ControlPlane(metav1.NamespaceDefault, "cp1").Build()).
  1521  				WithControlPlaneInfrastructureMachineTemplate(
  1522  					builder.InfrastructureMachinePoolTemplate(metav1.NamespaceDefault, "cpinfra1").Build()).
  1523  				WithWorkerMachinePoolClasses(
  1524  					*builder.MachinePoolClass("aa").
  1525  						WithInfrastructureTemplate(
  1526  							builder.InfrastructureMachinePoolTemplate(metav1.NamespaceDefault, "infra1").Build()).
  1527  						WithBootstrapTemplate(
  1528  							builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()).
  1529  						Build()).
  1530  				Build(),
  1531  			cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1").
  1532  				WithTopology(
  1533  					builder.ClusterTopology().
  1534  						WithClass("class1").
  1535  						WithVersion("v1.22.2").
  1536  						WithMachinePool(
  1537  							builder.MachinePoolTopology("workers1").
  1538  								WithClass("aa").
  1539  								Build()).
  1540  						WithMachinePool(
  1541  							builder.MachinePoolTopology("workers1").
  1542  								WithClass("aa").
  1543  								Build()).
  1544  						Build()).
  1545  				Build(),
  1546  			wantErr: true,
  1547  		},
  1548  		{
  1549  			name: "pass if MachinePoolTopologies are unique and share a class that is defined in ClusterClass",
  1550  			clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1").
  1551  				WithInfrastructureClusterTemplate(
  1552  					builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()).
  1553  				WithControlPlaneTemplate(
  1554  					builder.ControlPlane(metav1.NamespaceDefault, "cp1").Build()).
  1555  				WithControlPlaneInfrastructureMachineTemplate(
  1556  					builder.InfrastructureMachinePoolTemplate(metav1.NamespaceDefault, "cpinfra1").Build()).
  1557  				WithWorkerMachinePoolClasses(
  1558  					*builder.MachinePoolClass("aa").
  1559  						WithInfrastructureTemplate(
  1560  							builder.InfrastructureMachinePoolTemplate(metav1.NamespaceDefault, "infra1").Build()).
  1561  						WithBootstrapTemplate(
  1562  							builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()).
  1563  						Build()).
  1564  				Build(),
  1565  			cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1").
  1566  				WithTopology(
  1567  					builder.ClusterTopology().
  1568  						WithClass("class1").
  1569  						WithVersion("v1.22.2").
  1570  						WithMachinePool(
  1571  							builder.MachinePoolTopology("workers1").
  1572  								WithClass("aa").
  1573  								Build()).
  1574  						WithMachinePool(
  1575  							builder.MachinePoolTopology("workers2").
  1576  								WithClass("aa").
  1577  								Build()).
  1578  						Build()).
  1579  				Build(),
  1580  			wantErr: false,
  1581  		},
  1582  		{
  1583  			name: "fail if MachinePoolTopology name is longer than 63 characters",
  1584  			clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1").
  1585  				WithInfrastructureClusterTemplate(
  1586  					builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infra1").Build()).
  1587  				WithControlPlaneTemplate(
  1588  					builder.ControlPlane(metav1.NamespaceDefault, "cp1").Build()).
  1589  				WithControlPlaneInfrastructureMachineTemplate(
  1590  					builder.InfrastructureMachinePoolTemplate(metav1.NamespaceDefault, "cpinfra1").Build()).
  1591  				WithWorkerMachinePoolClasses(
  1592  					*builder.MachinePoolClass("aa").
  1593  						WithInfrastructureTemplate(
  1594  							builder.InfrastructureMachinePoolTemplate(metav1.NamespaceDefault, "infra1").Build()).
  1595  						WithBootstrapTemplate(
  1596  							builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").Build()).
  1597  						Build()).
  1598  				Build(),
  1599  			cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1").
  1600  				WithTopology(
  1601  					builder.ClusterTopology().
  1602  						WithClass("class1").
  1603  						WithVersion("v1.22.2").
  1604  						WithMachinePool(
  1605  							builder.MachinePoolTopology("machine-pool-topology-name-that-has-longerthan63characterlooooooooooooooooooooooongname").
  1606  								WithClass("aa").
  1607  								Build()).
  1608  						Build()).
  1609  				Build(),
  1610  			wantErr: true,
  1611  		},
  1612  	}
  1613  	for _, tt := range tests {
  1614  		t.Run(tt.name, func(t *testing.T) {
  1615  			g := NewWithT(t)
  1616  			allErrs := MachinePoolTopologiesAreValidAndDefinedInClusterClass(tt.cluster, tt.clusterClass)
  1617  			if tt.wantErr {
  1618  				g.Expect(allErrs).ToNot(BeEmpty())
  1619  				return
  1620  			}
  1621  			g.Expect(allErrs).To(BeEmpty())
  1622  		})
  1623  	}
  1624  }
  1625  
  1626  func TestClusterClassReferencesAreValid(t *testing.T) {
  1627  	ref := &corev1.ObjectReference{
  1628  		APIVersion: "group.test.io/foo",
  1629  		Kind:       "barTemplate",
  1630  		Name:       "baz",
  1631  		Namespace:  "default",
  1632  	}
  1633  	invalidRef := &corev1.ObjectReference{
  1634  		APIVersion: "group.test.io/foo",
  1635  		Kind:       "another-barTemplate",
  1636  		Name:       "baz",
  1637  		Namespace:  "",
  1638  	}
  1639  
  1640  	tests := []struct {
  1641  		name         string
  1642  		clusterClass *clusterv1.ClusterClass
  1643  		wantErr      bool
  1644  	}{
  1645  		{
  1646  			name: "pass for clusterClass with valid references",
  1647  			clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1").
  1648  				WithInfrastructureClusterTemplate(
  1649  					refToUnstructured(ref)).
  1650  				WithControlPlaneTemplate(
  1651  					refToUnstructured(ref)).
  1652  				WithControlPlaneInfrastructureMachineTemplate(
  1653  					refToUnstructured(ref)).
  1654  				WithWorkerMachineDeploymentClasses(
  1655  					*builder.MachineDeploymentClass("aa").
  1656  						WithInfrastructureTemplate(
  1657  							refToUnstructured(ref)).
  1658  						WithBootstrapTemplate(
  1659  							refToUnstructured(ref)).
  1660  						Build()).
  1661  				WithWorkerMachinePoolClasses(
  1662  					*builder.MachinePoolClass("aa").
  1663  						WithInfrastructureTemplate(
  1664  							refToUnstructured(ref)).
  1665  						WithBootstrapTemplate(
  1666  							refToUnstructured(ref)).
  1667  						Build()).
  1668  				Build(),
  1669  			wantErr: false,
  1670  		},
  1671  		{
  1672  			name: "error if clusterClass has multiple invalid md refs",
  1673  			clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1").
  1674  				WithInfrastructureClusterTemplate(
  1675  					refToUnstructured(invalidRef)).
  1676  				WithControlPlaneTemplate(
  1677  					refToUnstructured(invalidRef)).
  1678  				WithControlPlaneInfrastructureMachineTemplate(
  1679  					refToUnstructured(invalidRef)).
  1680  				WithWorkerMachineDeploymentClasses(
  1681  					*builder.MachineDeploymentClass("a").
  1682  						WithInfrastructureTemplate(
  1683  							refToUnstructured(invalidRef)).
  1684  						WithBootstrapTemplate(
  1685  							refToUnstructured(invalidRef)).
  1686  						Build(),
  1687  					*builder.MachineDeploymentClass("b").
  1688  						WithInfrastructureTemplate(
  1689  							refToUnstructured(invalidRef)).
  1690  						WithBootstrapTemplate(
  1691  							refToUnstructured(invalidRef)).
  1692  						Build(),
  1693  					*builder.MachineDeploymentClass("c").
  1694  						WithInfrastructureTemplate(
  1695  							refToUnstructured(invalidRef)).
  1696  						WithBootstrapTemplate(
  1697  							refToUnstructured(invalidRef)).
  1698  						Build()).
  1699  				Build(),
  1700  			wantErr: true,
  1701  		},
  1702  		{
  1703  			name: "error if clusterClass has multiple invalid mp refs",
  1704  			clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1").
  1705  				WithInfrastructureClusterTemplate(
  1706  					refToUnstructured(invalidRef)).
  1707  				WithControlPlaneTemplate(
  1708  					refToUnstructured(invalidRef)).
  1709  				WithControlPlaneInfrastructureMachineTemplate(
  1710  					refToUnstructured(invalidRef)).
  1711  				WithWorkerMachinePoolClasses(
  1712  					*builder.MachinePoolClass("a").
  1713  						WithInfrastructureTemplate(
  1714  							refToUnstructured(invalidRef)).
  1715  						WithBootstrapTemplate(
  1716  							refToUnstructured(invalidRef)).
  1717  						Build(),
  1718  					*builder.MachinePoolClass("b").
  1719  						WithInfrastructureTemplate(
  1720  							refToUnstructured(invalidRef)).
  1721  						WithBootstrapTemplate(
  1722  							refToUnstructured(invalidRef)).
  1723  						Build(),
  1724  					*builder.MachinePoolClass("c").
  1725  						WithInfrastructureTemplate(
  1726  							refToUnstructured(invalidRef)).
  1727  						WithBootstrapTemplate(
  1728  							refToUnstructured(invalidRef)).
  1729  						Build()).
  1730  				Build(),
  1731  			wantErr: true,
  1732  		},
  1733  		{
  1734  			name: "error if clusterClass has invalid ControlPlane ref",
  1735  			clusterClass: builder.ClusterClass(metav1.NamespaceDefault, "class1").
  1736  				WithInfrastructureClusterTemplate(
  1737  					refToUnstructured(ref)).
  1738  				WithControlPlaneTemplate(
  1739  					refToUnstructured(invalidRef)).
  1740  				WithControlPlaneInfrastructureMachineTemplate(
  1741  					refToUnstructured(ref)).
  1742  				WithWorkerMachineDeploymentClasses(
  1743  					*builder.MachineDeploymentClass("aa").
  1744  						WithInfrastructureTemplate(
  1745  							refToUnstructured(ref)).
  1746  						WithBootstrapTemplate(
  1747  							refToUnstructured(ref)).
  1748  						Build()).
  1749  				WithWorkerMachinePoolClasses(
  1750  					*builder.MachinePoolClass("aa").
  1751  						WithInfrastructureTemplate(
  1752  							refToUnstructured(ref)).
  1753  						WithBootstrapTemplate(
  1754  							refToUnstructured(ref)).
  1755  						Build()).
  1756  				Build(),
  1757  			wantErr: true,
  1758  		},
  1759  	}
  1760  	for _, tt := range tests {
  1761  		t.Run(tt.name, func(t *testing.T) {
  1762  			g := NewWithT(t)
  1763  			allErrs := ClusterClassReferencesAreValid(tt.clusterClass)
  1764  			if tt.wantErr {
  1765  				g.Expect(allErrs).ToNot(BeEmpty())
  1766  				return
  1767  			}
  1768  			g.Expect(allErrs).To(BeEmpty())
  1769  		})
  1770  	}
  1771  }
  1772  
  1773  func refToUnstructured(ref *corev1.ObjectReference) *unstructured.Unstructured {
  1774  	gvk := ref.GetObjectKind().GroupVersionKind()
  1775  	output := &unstructured.Unstructured{}
  1776  	output.SetKind(gvk.Kind)
  1777  	output.SetAPIVersion(gvk.GroupVersion().String())
  1778  	output.SetName(ref.Name)
  1779  	output.SetNamespace(ref.Namespace)
  1780  	return output
  1781  }