sigs.k8s.io/cluster-api@v1.7.1/internal/webhooks/test/clusterclass_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 test
    18  
    19  import (
    20  	"testing"
    21  
    22  	. "github.com/onsi/gomega"
    23  	corev1 "k8s.io/api/core/v1"
    24  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    25  	utilfeature "k8s.io/component-base/featuregate/testing"
    26  	"sigs.k8s.io/controller-runtime/pkg/client"
    27  
    28  	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
    29  	"sigs.k8s.io/cluster-api/feature"
    30  	"sigs.k8s.io/cluster-api/internal/test/builder"
    31  )
    32  
    33  const (
    34  	workerClassName1 = "linux-worker"
    35  	workerClassName2 = "windows-worker"
    36  
    37  	infrastructureClusterTemplateName1 = "infrastructureclustertemplate1"
    38  	infrastructureClusterTemplateName2 = "infracluster2"
    39  
    40  	controlPlaneTemplateName1 = "controlplanetemplate1"
    41  
    42  	infrastructureMachineTemplateName1 = "infrastructuremachinetemplate1"
    43  	infrastructureMachineTemplateName2 = "infrastructuremachinetemplate2"
    44  
    45  	clusterClassName1 = "clusterclass1"
    46  	clusterClassName2 = "clusterclass2"
    47  	clusterClassName3 = "clusterclass3"
    48  	clusterClassName4 = "clusterclass4"
    49  	clusterClassName5 = "clusterclass5"
    50  
    51  	clusterName1 = "cluster1"
    52  	clusterName2 = "cluster2"
    53  	clusterName3 = "cluster3"
    54  	clusterName4 = "cluster4"
    55  
    56  	bootstrapTemplateName1 = "bootstraptemplate1"
    57  )
    58  
    59  // TestClusterClassWebhook_Succeed_Create tests the correct creation behaviour for a valid ClusterClass.
    60  func TestClusterClassWebhook_Succeed_Create(t *testing.T) {
    61  	defer utilfeature.SetFeatureGateDuringTest(t, feature.Gates, feature.ClusterTopology, true)()
    62  	g := NewWithT(t)
    63  
    64  	ns, err := env.CreateNamespace(ctx, "test-topology-clusterclass-webhook")
    65  	g.Expect(err).ToNot(HaveOccurred())
    66  	// Create the objects needed for the integration test:
    67  	// - a ClusterClass with all the related templates
    68  	clusterClass1 := builder.ClusterClass(ns.Name, clusterClassName1).
    69  		WithInfrastructureClusterTemplate(
    70  			builder.InfrastructureClusterTemplate(ns.Name, infrastructureClusterTemplateName1).Build()).
    71  		WithControlPlaneTemplate(
    72  			builder.ControlPlaneTemplate(ns.Name, controlPlaneTemplateName1).
    73  				Build()).
    74  		WithWorkerMachineDeploymentClasses(
    75  			*builder.MachineDeploymentClass("md1").
    76  				WithInfrastructureTemplate(
    77  					builder.InfrastructureMachineTemplate(ns.Name, "OLD_INFRA").Build()).
    78  				WithBootstrapTemplate(
    79  					builder.BootstrapTemplate(ns.Name, bootstrapTemplateName1).Build()).
    80  				Build(),
    81  			*builder.MachineDeploymentClass("md2").
    82  				WithInfrastructureTemplate(
    83  					builder.InfrastructureMachineTemplate(ns.Name, infrastructureMachineTemplateName1).Build()).
    84  				WithBootstrapTemplate(
    85  					builder.BootstrapTemplate(ns.Name, bootstrapTemplateName1).Build()).
    86  				Build()).
    87  		Build()
    88  
    89  	// Clean up the resources once the test is finished.
    90  	t.Cleanup(func() {
    91  		g.Expect(env.Cleanup(ctx, clusterClass1)).To(Succeed())
    92  		g.Expect(env.Cleanup(ctx, ns)).To(Succeed())
    93  	})
    94  
    95  	// Create the ClusterClass in the API server. Expect no error.
    96  	g.Expect(env.Create(ctx, clusterClass1)).To(Succeed())
    97  }
    98  
    99  // TestClusterClassWebhook_Fail_Create tests the correct creation behaviour for an invalid ClusterClass with missing references.
   100  // In this case creation of the ClusterClass should be blocked by the webhook.
   101  func TestClusterClassWebhook_Fail_Create(t *testing.T) {
   102  	defer utilfeature.SetFeatureGateDuringTest(t, feature.Gates, feature.ClusterTopology, true)()
   103  	g := NewWithT(t)
   104  
   105  	ns, err := env.CreateNamespace(ctx, "test-topology-clusterclass-webhook")
   106  	g.Expect(err).ToNot(HaveOccurred())
   107  
   108  	cleanup, err := createTemplates(ns)
   109  	g.Expect(err).ToNot(HaveOccurred())
   110  
   111  	// Create the objects needed for the integration test:
   112  	// - a ClusterClass with all the related templates
   113  	clusterClass1 := builder.ClusterClass(ns.Name, clusterClassName1).
   114  		WithInfrastructureClusterTemplate(
   115  			builder.TestInfrastructureClusterTemplate(ns.Name, infrastructureClusterTemplateName1).Build()).
   116  		// ControlPlaneTemplate not defined in this ClusterClass making it invalid.
   117  		WithWorkerMachineDeploymentClasses(
   118  			*builder.MachineDeploymentClass("md1").
   119  				WithInfrastructureTemplate(
   120  					builder.TestInfrastructureMachineTemplate(ns.Name, infrastructureMachineTemplateName1).Build()).
   121  				WithBootstrapTemplate(
   122  					builder.TestBootstrapTemplate(ns.Name, bootstrapTemplateName1).Build()).
   123  				Build(),
   124  			*builder.MachineDeploymentClass("md2").
   125  				WithInfrastructureTemplate(
   126  					builder.TestInfrastructureMachineTemplate(ns.Name, infrastructureMachineTemplateName1).Build()).
   127  				WithBootstrapTemplate(
   128  					builder.TestBootstrapTemplate(ns.Name, bootstrapTemplateName1).Build()).
   129  				Build()).
   130  		Build()
   131  
   132  	// Clean up the resources once the test is finished.
   133  	t.Cleanup(func() {
   134  		g.Expect(cleanup()).To(Succeed())
   135  		g.Expect(env.Cleanup(ctx, clusterClass1)).To(Succeed())
   136  		g.Expect(env.Cleanup(ctx, ns)).To(Succeed())
   137  	})
   138  
   139  	// Create the ClusterClass in the API server. Expect this to fail because of missing templates.
   140  	g.Expect(env.Create(ctx, clusterClass1)).NotTo(Succeed())
   141  }
   142  
   143  // TestClusterWebhook_Succeed_Update tests the correct update behaviour for a ClusterClass with references in existing Clusters.
   144  // In this case deletion of the ClusterClass should succeed by the webhook as the update is compatible with the existing Clusters using the ClusterClass.
   145  func TestClusterWebhook_Succeed_Update(t *testing.T) {
   146  	defer utilfeature.SetFeatureGateDuringTest(t, feature.Gates, feature.ClusterTopology, true)()
   147  	g := NewWithT(t)
   148  
   149  	ns, err := env.CreateNamespace(ctx, "test-topology-clusterclass-webhook")
   150  	g.Expect(err).ToNot(HaveOccurred())
   151  
   152  	cleanup, err := createTemplates(ns)
   153  	g.Expect(err).ToNot(HaveOccurred())
   154  
   155  	// Create the objects needed for the integration test:
   156  	// - a ClusterClass with all the related templates
   157  	// - a Cluster using the above ClusterClass
   158  	clusterClass := builder.ClusterClass(ns.Name, clusterClassName1).
   159  		WithInfrastructureClusterTemplate(
   160  			builder.TestInfrastructureClusterTemplate(ns.Name, infrastructureClusterTemplateName1).Build()).
   161  		WithControlPlaneTemplate(
   162  			builder.TestControlPlaneTemplate(ns.Name, controlPlaneTemplateName1).
   163  				Build()).
   164  		WithWorkerMachineDeploymentClasses(
   165  			*builder.MachineDeploymentClass("NOT_USED").
   166  				WithInfrastructureTemplate(
   167  					builder.TestInfrastructureMachineTemplate(ns.Name, infrastructureMachineTemplateName1).Build()).
   168  				WithBootstrapTemplate(
   169  					builder.TestBootstrapTemplate(ns.Name, bootstrapTemplateName1).Build()).
   170  				Build(),
   171  			*builder.MachineDeploymentClass(workerClassName1).
   172  				WithInfrastructureTemplate(
   173  					builder.TestInfrastructureMachineTemplate(ns.Name, infrastructureMachineTemplateName1).Build()).
   174  				WithBootstrapTemplate(
   175  					builder.TestBootstrapTemplate(ns.Name, bootstrapTemplateName1).Build()).
   176  				Build()).
   177  		Build()
   178  
   179  	cluster := builder.Cluster(ns.Name, clusterName1).
   180  		WithLabels(map[string]string{clusterv1.ClusterTopologyOwnedLabel: ""}).
   181  		WithTopology(
   182  			builder.ClusterTopology().
   183  				WithVersion("v1.20.1").
   184  				WithClass(clusterClassName1).
   185  				WithMachineDeployment(
   186  					builder.MachineDeploymentTopology("md1").
   187  						WithClass(workerClassName1).
   188  						Build(),
   189  				).
   190  				Build()).
   191  		Build()
   192  
   193  	// Create the ClusterClass in the API server.
   194  	g.Expect(env.CreateAndWait(ctx, clusterClass)).To(Succeed())
   195  
   196  	// Create a Cluster using the ClusterClass.
   197  	g.Expect(env.CreateAndWait(ctx, cluster)).To(Succeed())
   198  	// Clean up the resources once the test is finished.
   199  	t.Cleanup(func() {
   200  		g.Expect(cleanup()).To(Succeed())
   201  		g.Expect(env.CleanupAndWait(ctx, cluster)).To(Succeed())
   202  		g.Expect(env.Cleanup(ctx, clusterClass)).To(Succeed())
   203  		g.Expect(env.Cleanup(ctx, ns)).To(Succeed())
   204  	})
   205  
   206  	// Retrieve the ClusterClass from the API server.
   207  	actualClusterClass := &clusterv1.ClusterClass{}
   208  	g.Expect(env.Get(ctx, client.ObjectKey{Namespace: ns.Name, Name: clusterClassName1}, actualClusterClass)).To(Succeed())
   209  
   210  	// Remove MachineDeploymentClass , which is not used by cluster1 from the clusterClass
   211  	actualClusterClass.Spec.Workers.MachineDeployments = []clusterv1.MachineDeploymentClass{
   212  		actualClusterClass.Spec.Workers.MachineDeployments[1],
   213  	}
   214  	// Change the template used in the ClusterClass to a compatible alternative (Only name is changed).
   215  	actualClusterClass.Spec.Infrastructure.Ref.Name = infrastructureClusterTemplateName2
   216  
   217  	// Attempt to update the ClusterClass with the above changes.
   218  	// Expect no error here as the updates are compatible with the current Clusters using the ClusterClass.
   219  	g.Expect(env.Update(ctx, actualClusterClass)).To(Succeed())
   220  }
   221  
   222  // TestClusterWebhook_Fail_Update tests the correct update behaviour for a ClusterClass with references in existing Clusters.
   223  // In this case deletion of the ClusterClass should be blocked by the webhook as the update is incompatible with the existing Clusters using the ClusterClass.
   224  func TestClusterWebhook_Fail_Update(t *testing.T) {
   225  	defer utilfeature.SetFeatureGateDuringTest(t, feature.Gates, feature.ClusterTopology, true)()
   226  	g := NewWithT(t)
   227  
   228  	ns, err := env.CreateNamespace(ctx, "test-topology-clusterclass-webhook")
   229  	g.Expect(err).ToNot(HaveOccurred())
   230  
   231  	cleanup, err := createTemplates(ns)
   232  	g.Expect(err).ToNot(HaveOccurred())
   233  
   234  	// Create the objects needed for the integration test:
   235  	// - a ClusterClass with all the related templates
   236  	// - a Cluster using the above ClusterClass
   237  	clusterClass := builder.ClusterClass(ns.Name, clusterClassName1).
   238  		WithInfrastructureClusterTemplate(
   239  			builder.TestInfrastructureClusterTemplate(ns.Name, infrastructureClusterTemplateName1).Build()).
   240  		WithControlPlaneTemplate(
   241  			builder.TestControlPlaneTemplate(ns.Name, controlPlaneTemplateName1).
   242  				Build()).
   243  		WithWorkerMachineDeploymentClasses(
   244  			*builder.MachineDeploymentClass("NOT_USED").
   245  				WithInfrastructureTemplate(
   246  					builder.TestInfrastructureMachineTemplate(ns.Name, infrastructureMachineTemplateName1).Build()).
   247  				WithBootstrapTemplate(
   248  					builder.TestBootstrapTemplate(ns.Name, bootstrapTemplateName1).Build()).
   249  				Build(),
   250  			*builder.MachineDeploymentClass("IN_USE").
   251  				WithInfrastructureTemplate(
   252  					builder.TestInfrastructureMachineTemplate(ns.Name, infrastructureMachineTemplateName1).Build()).
   253  				WithBootstrapTemplate(
   254  					builder.TestBootstrapTemplate(ns.Name, bootstrapTemplateName1).Build()).
   255  				Build()).
   256  		Build()
   257  
   258  	cluster := builder.Cluster(ns.Name, clusterName1).
   259  		WithLabels(map[string]string{clusterv1.ClusterTopologyOwnedLabel: ""}).
   260  		WithTopology(
   261  			builder.ClusterTopology().
   262  				WithVersion("v1.20.1").
   263  				WithClass(clusterClassName1).
   264  				WithMachineDeployment(
   265  					builder.MachineDeploymentTopology("md1").
   266  						WithClass("IN_USE").
   267  						Build(),
   268  				).
   269  				Build()).
   270  		Build()
   271  
   272  	// Create the ClusterClass in the API server.
   273  	g.Expect(env.CreateAndWait(ctx, clusterClass)).To(Succeed())
   274  
   275  	// Create a cluster using the ClusterClass.
   276  	g.Expect(env.CreateAndWait(ctx, cluster)).To(Succeed())
   277  
   278  	// Clean up the resources once the test is finished.
   279  	t.Cleanup(func() {
   280  		g.Expect(cleanup()).To(Succeed())
   281  		g.Expect(env.CleanupAndWait(ctx, cluster)).To(Succeed())
   282  		g.Expect(env.Cleanup(ctx, clusterClass)).To(Succeed())
   283  		g.Expect(env.Cleanup(ctx, ns)).To(Succeed())
   284  	})
   285  
   286  	// Retrieve the clusterClass from the API server.
   287  	actualClusterClass := &clusterv1.ClusterClass{}
   288  	g.Expect(env.Get(ctx, client.ObjectKey{Namespace: ns.Name, Name: clusterClassName1}, actualClusterClass)).To(Succeed())
   289  
   290  	// Remove MachineDeploymentClass "IN_USE", which is used by cluster1 from the clusterClass
   291  	actualClusterClass.Spec.Workers.MachineDeployments = []clusterv1.MachineDeploymentClass{
   292  		actualClusterClass.Spec.Workers.MachineDeployments[0],
   293  	}
   294  
   295  	// Attempt to update the ClusterClass with the above changes.
   296  	// Expect an error here as the updates are incompatible with the Current Clusters using the ClusterClass.
   297  	g.Expect(env.Update(ctx, actualClusterClass)).NotTo(Succeed())
   298  }
   299  
   300  // TestClusterClassWebhook_Fail_Delete tests the correct deletion behaviour for a ClusterClass with references in existing Clusters.
   301  // In this case deletion of the ClusterClass should be blocked by the webhook.
   302  func TestClusterClassWebhook_Delete(t *testing.T) {
   303  	defer utilfeature.SetFeatureGateDuringTest(t, feature.Gates, feature.ClusterTopology, true)()
   304  	g := NewWithT(t)
   305  
   306  	ns, err := env.CreateNamespace(ctx, "test-topology-clusterclass-webhook")
   307  	g.Expect(err).ToNot(HaveOccurred())
   308  
   309  	cleanup, err := createTemplates(ns)
   310  	g.Expect(err).ToNot(HaveOccurred())
   311  
   312  	// Create the objects needed for the integration test:
   313  	// - a two ClusterClasses with all the related templates
   314  	// - a Cluster using one of the ClusterClasses
   315  	clusterClass1 := builder.ClusterClass(ns.Name, clusterClassName1).
   316  		WithInfrastructureClusterTemplate(
   317  			builder.TestInfrastructureClusterTemplate(ns.Name, infrastructureClusterTemplateName1).Build()).
   318  		WithControlPlaneTemplate(
   319  			builder.TestControlPlaneTemplate(ns.Name, controlPlaneTemplateName1).
   320  				Build()).
   321  		WithWorkerMachineDeploymentClasses(
   322  			*builder.MachineDeploymentClass(workerClassName1).
   323  				WithInfrastructureTemplate(
   324  					builder.TestInfrastructureMachineTemplate(ns.Name, infrastructureMachineTemplateName1).Build()).
   325  				WithBootstrapTemplate(
   326  					builder.TestBootstrapTemplate(ns.Name, bootstrapTemplateName1).Build()).
   327  				Build(),
   328  			*builder.MachineDeploymentClass(workerClassName2).
   329  				WithInfrastructureTemplate(
   330  					builder.TestInfrastructureMachineTemplate(ns.Name, infrastructureMachineTemplateName1).Build()).
   331  				WithBootstrapTemplate(
   332  					builder.TestBootstrapTemplate(ns.Name, bootstrapTemplateName1).Build()).
   333  				Build()).
   334  		Build()
   335  
   336  	clusterClass2 := builder.ClusterClass(ns.Name, clusterClassName2).
   337  		WithInfrastructureClusterTemplate(
   338  			builder.TestInfrastructureClusterTemplate(ns.Name, infrastructureClusterTemplateName1).Build()).
   339  		WithControlPlaneTemplate(
   340  			builder.TestControlPlaneTemplate(ns.Name, controlPlaneTemplateName1).
   341  				Build()).
   342  		WithWorkerMachineDeploymentClasses(
   343  			*builder.MachineDeploymentClass(workerClassName1).
   344  				WithInfrastructureTemplate(
   345  					builder.TestInfrastructureMachineTemplate(ns.Name, infrastructureMachineTemplateName1).Build()).
   346  				WithBootstrapTemplate(
   347  					builder.TestBootstrapTemplate(ns.Name, bootstrapTemplateName1).Build()).
   348  				Build(),
   349  			*builder.MachineDeploymentClass(workerClassName2).
   350  				WithInfrastructureTemplate(
   351  					builder.TestInfrastructureMachineTemplate(ns.Name, infrastructureMachineTemplateName1).Build()).
   352  				WithBootstrapTemplate(
   353  					builder.TestBootstrapTemplate(ns.Name, bootstrapTemplateName1).Build()).
   354  				Build()).
   355  		Build()
   356  
   357  	cluster := builder.Cluster(ns.Name, clusterName2).
   358  		WithLabels(map[string]string{clusterv1.ClusterTopologyOwnedLabel: ""}).
   359  		WithTopology(
   360  			builder.ClusterTopology().
   361  				WithVersion("v1.20.1").
   362  				WithClass(clusterClassName1).
   363  				WithMachineDeployment(
   364  					builder.MachineDeploymentTopology("md1").
   365  						WithClass(workerClassName1).
   366  						Build(),
   367  				).
   368  				Build()).
   369  		Build()
   370  
   371  	// Create the ClusterClasses in the API server.
   372  	g.Expect(env.CreateAndWait(ctx, clusterClass1)).To(Succeed())
   373  	g.Expect(env.CreateAndWait(ctx, clusterClass2)).To(Succeed())
   374  
   375  	// Create a clusters.
   376  	g.Expect(env.CreateAndWait(ctx, cluster)).To(Succeed())
   377  
   378  	// Clean up the resources once the test is finished.
   379  	t.Cleanup(func() {
   380  		g.Expect(cleanup()).To(Succeed())
   381  		g.Expect(env.Cleanup(ctx, cluster)).To(Succeed())
   382  		g.Expect(env.Cleanup(ctx, ns)).To(Succeed())
   383  	})
   384  
   385  	// Attempt to delete ClusterClass "class1" which is in use.
   386  	// Expect no error here as the webhook should allow the deletion of an existing ClusterClass.
   387  	g.Expect(env.Delete(ctx, clusterClass1)).To(Not(Succeed()))
   388  
   389  	// Attempt to delete ClusterClass "class2" which is not in use.
   390  	// Expect no error here as the webhook should allow the deletion of an existing ClusterClass.
   391  	g.Expect(env.Delete(ctx, clusterClass2)).To(Succeed())
   392  }
   393  
   394  // TestClusterClassWebhook_Succeed_Delete tests the correct deletion behaviour for a ClusterClass with no references in existing Clusters.
   395  // In this case deletion of the ClusterClass should succeed.
   396  func TestClusterClassWebhook_Delete_MultipleExistingClusters(t *testing.T) {
   397  	g := NewWithT(t)
   398  	ns, err := env.CreateNamespace(ctx, "test-clusterclass-webhook")
   399  	g.Expect(err).ToNot(HaveOccurred())
   400  
   401  	cleanup, err := createTemplates(ns)
   402  	g.Expect(err).ToNot(HaveOccurred())
   403  	// Create the objects needed for the integration test:
   404  	// - Five ClusterClasses with all the related templates
   405  	// - Five Clusters using all ClusterClass except "class1"
   406  	clusterClasses := []client.Object{
   407  		builder.ClusterClass(ns.Name, clusterClassName1).
   408  			WithInfrastructureClusterTemplate(
   409  				builder.TestInfrastructureClusterTemplate(ns.Name, infrastructureClusterTemplateName1).Build()).
   410  			WithControlPlaneTemplate(
   411  				builder.ControlPlaneTemplate(ns.Name, controlPlaneTemplateName1).
   412  					Build()).
   413  			Build(),
   414  		builder.ClusterClass(ns.Name, clusterClassName2).
   415  			WithInfrastructureClusterTemplate(
   416  				builder.TestInfrastructureClusterTemplate(ns.Name, infrastructureClusterTemplateName1).Build()).
   417  			WithControlPlaneTemplate(
   418  				builder.TestControlPlaneTemplate(ns.Name, controlPlaneTemplateName1).
   419  					Build()).
   420  			Build(),
   421  		builder.ClusterClass(ns.Name, clusterClassName3).
   422  			WithInfrastructureClusterTemplate(
   423  				builder.TestInfrastructureClusterTemplate(ns.Name, infrastructureClusterTemplateName1).Build()).
   424  			WithControlPlaneTemplate(
   425  				builder.TestControlPlaneTemplate(ns.Name, controlPlaneTemplateName1).
   426  					Build()).
   427  			Build(),
   428  		builder.ClusterClass(ns.Name, clusterClassName4).
   429  			WithInfrastructureClusterTemplate(
   430  				builder.TestInfrastructureClusterTemplate(ns.Name, infrastructureClusterTemplateName1).Build()).
   431  			WithControlPlaneTemplate(
   432  				builder.TestControlPlaneTemplate(ns.Name, controlPlaneTemplateName1).
   433  					Build()).
   434  			Build(),
   435  		builder.ClusterClass(ns.Name, clusterClassName5).
   436  			WithInfrastructureClusterTemplate(
   437  				builder.TestInfrastructureClusterTemplate(ns.Name, infrastructureClusterTemplateName1).Build()).
   438  			WithControlPlaneTemplate(
   439  				builder.TestControlPlaneTemplate(ns.Name, controlPlaneTemplateName1).
   440  					Build()).
   441  			Build(),
   442  	}
   443  	clusters := []client.Object{
   444  		// class1 is not referenced in any of the below Clusters
   445  		builder.Cluster(ns.Name, clusterName1).
   446  			WithLabels(map[string]string{clusterv1.ClusterTopologyOwnedLabel: ""}).
   447  			WithTopology(
   448  				builder.ClusterTopology().
   449  					WithVersion("v1.20.1").
   450  					WithClass(clusterClassName5).
   451  					Build()).
   452  			Build(),
   453  		builder.Cluster(ns.Name, clusterName2).
   454  			WithLabels(map[string]string{clusterv1.ClusterTopologyOwnedLabel: ""}).
   455  			WithTopology(
   456  				builder.ClusterTopology().
   457  					WithVersion("v1.20.1").
   458  					WithClass(clusterClassName2).
   459  					Build()).
   460  			Build(),
   461  		builder.Cluster(ns.Name, clusterName3).
   462  			WithLabels(map[string]string{clusterv1.ClusterTopologyOwnedLabel: ""}).
   463  			WithTopology(
   464  				builder.ClusterTopology().
   465  					WithVersion("v1.20.1").
   466  					WithClass(clusterClassName3).
   467  					Build()).
   468  			Build(),
   469  		builder.Cluster(ns.Name, clusterName4).
   470  			WithLabels(map[string]string{clusterv1.ClusterTopologyOwnedLabel: ""}).
   471  			WithTopology(
   472  				builder.ClusterTopology().
   473  					WithVersion("v1.20.1").
   474  					WithClass(clusterClassName4).
   475  					Build()).
   476  			Build(),
   477  	}
   478  	var classForDeletion string
   479  	// Create the ClusterClasses in the API server.
   480  	for _, class := range clusterClasses {
   481  		g.Expect(env.CreateAndWait(ctx, class)).To(Succeed())
   482  	}
   483  
   484  	// Create each of the clusters.
   485  	for _, c := range clusters {
   486  		g.Expect(env.CreateAndWait(ctx, c)).To(Succeed())
   487  	}
   488  
   489  	// defer a function to clean up created resources.
   490  	defer func() {
   491  		g.Expect(cleanup()).To(Succeed())
   492  		// Delete each of the clusters.
   493  		for _, c := range clusters {
   494  			g.Expect(env.CleanupAndWait(ctx, c)).To(Succeed())
   495  		}
   496  
   497  		// Delete the ClusterClasses in the API server.
   498  		for _, class := range clusterClasses {
   499  			// The classForDeletion should not exist at this point.
   500  			if class.GetName() != classForDeletion {
   501  				if err := env.Delete(ctx, class); err != nil {
   502  					if apierrors.IsNotFound(err) {
   503  						continue
   504  					}
   505  					g.Expect(err).ToNot(HaveOccurred())
   506  				}
   507  			}
   508  		}
   509  	}()
   510  
   511  	// This is the unused clusterClass to be deleted.
   512  	classForDeletion = clusterClassName1
   513  
   514  	class := &clusterv1.ClusterClass{}
   515  	g.Expect(env.Get(ctx, client.ObjectKey{Namespace: ns.Name, Name: classForDeletion}, class)).To(Succeed())
   516  
   517  	// Attempt to delete ClusterClass "class1" which is not in use.
   518  	// Expect no error here as the webhook should allow the deletion of an unused ClusterClass.
   519  	g.Expect(env.Delete(ctx, class)).To(Succeed())
   520  
   521  	// This is a clusterClass in use to be deleted.
   522  	classForDeletion = clusterClassName3
   523  
   524  	class = &clusterv1.ClusterClass{}
   525  	g.Expect(env.Get(ctx, client.ObjectKey{Namespace: ns.Name, Name: classForDeletion}, class)).To(Succeed())
   526  
   527  	// Attempt to delete ClusterClass "class3" which is in use.
   528  	// Expect an error here as the webhook should not allow the deletion of an existing ClusterClass.
   529  	g.Expect(env.Delete(ctx, class)).To(Not(Succeed()))
   530  }
   531  
   532  // createTemplates builds and then creates all required ClusterClass templates in the envtest API server.
   533  func createTemplates(ns *corev1.Namespace) (func() error, error) {
   534  	// Templates for MachineInfrastructure, ClusterInfrastructure, ControlPlane and Bootstrap.
   535  	infrastructureMachineTemplate1 := builder.TestInfrastructureMachineTemplate(ns.Name, infrastructureMachineTemplateName1).Build()
   536  	infrastructureMachineTemplate2 := builder.TestInfrastructureMachineTemplate(ns.Name, infrastructureMachineTemplateName2).Build()
   537  	infrastructureClusterTemplate1 := builder.TestInfrastructureClusterTemplate(ns.Name, infrastructureClusterTemplateName1).Build()
   538  	infrastructureClusterTemplate2 := builder.TestInfrastructureClusterTemplate(ns.Name, infrastructureClusterTemplateName2).Build()
   539  	controlPlaneTemplate := builder.TestControlPlaneTemplate(ns.Name, controlPlaneTemplateName1).Build()
   540  	bootstrapTemplate := builder.TestBootstrapTemplate(ns.Name, bootstrapTemplateName1).Build()
   541  
   542  	// Create a set of templates from the objects above to add to the API server when the test environment starts.
   543  	initObjs := []client.Object{
   544  		infrastructureClusterTemplate1,
   545  		infrastructureClusterTemplate2,
   546  		infrastructureMachineTemplate1,
   547  		infrastructureMachineTemplate2,
   548  		bootstrapTemplate,
   549  		controlPlaneTemplate,
   550  	}
   551  	cleanup := func() error {
   552  		for _, o := range initObjs {
   553  			if err := env.CleanupAndWait(ctx, o); err != nil {
   554  				return err
   555  			}
   556  		}
   557  		return nil
   558  	}
   559  
   560  	for _, obj := range initObjs {
   561  		if err := env.CreateAndWait(ctx, obj); err != nil {
   562  			return cleanup, err
   563  		}
   564  	}
   565  	return cleanup, nil
   566  }