sigs.k8s.io/cluster-api@v1.6.3/internal/controllers/topology/cluster/current_state_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 cluster
    18  
    19  import (
    20  	"testing"
    21  	"time"
    22  
    23  	"github.com/google/go-cmp/cmp"
    24  	. "github.com/onsi/gomega"
    25  	corev1 "k8s.io/api/core/v1"
    26  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    27  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    28  	"sigs.k8s.io/controller-runtime/pkg/client"
    29  	"sigs.k8s.io/controller-runtime/pkg/client/fake"
    30  	. "sigs.k8s.io/controller-runtime/pkg/envtest/komega"
    31  
    32  	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
    33  	"sigs.k8s.io/cluster-api/internal/controllers/topology/cluster/scope"
    34  	"sigs.k8s.io/cluster-api/internal/test/builder"
    35  )
    36  
    37  func TestGetCurrentState(t *testing.T) {
    38  	crds := []client.Object{
    39  		builder.GenericControlPlaneCRD,
    40  		builder.GenericInfrastructureClusterCRD,
    41  		builder.GenericControlPlaneTemplateCRD,
    42  		builder.GenericInfrastructureClusterTemplateCRD,
    43  		builder.GenericBootstrapConfigTemplateCRD,
    44  		builder.GenericInfrastructureMachineTemplateCRD,
    45  		builder.GenericInfrastructureMachineCRD,
    46  		builder.GenericInfrastructureMachinePoolTemplateCRD,
    47  		builder.GenericInfrastructureMachinePoolCRD,
    48  	}
    49  
    50  	// The following is a block creating a number of objects for use in the test cases.
    51  
    52  	// InfrastructureCluster objects.
    53  	infraCluster := builder.InfrastructureCluster(metav1.NamespaceDefault, "infraOne").
    54  		Build()
    55  	infraCluster.SetLabels(map[string]string{clusterv1.ClusterTopologyOwnedLabel: ""})
    56  	infraClusterNotTopologyOwned := builder.InfrastructureCluster(metav1.NamespaceDefault, "infraOne").
    57  		Build()
    58  	infraClusterTemplate := builder.InfrastructureClusterTemplate(metav1.NamespaceDefault, "infraTemplateOne").
    59  		Build()
    60  
    61  	// ControlPlane and ControlPlaneInfrastructureMachineTemplate objects.
    62  	controlPlaneInfrastructureMachineTemplate := builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "cpInfraTemplate").
    63  		Build()
    64  	controlPlaneInfrastructureMachineTemplate.SetLabels(map[string]string{clusterv1.ClusterTopologyOwnedLabel: ""})
    65  	controlPlaneInfrastructureMachineTemplateNotTopologyOwned := builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "cpInfraTemplate").
    66  		Build()
    67  	controlPlaneTemplateWithInfrastructureMachine := builder.ControlPlaneTemplate(metav1.NamespaceDefault, "cpTemplateWithInfra1").
    68  		WithInfrastructureMachineTemplate(controlPlaneInfrastructureMachineTemplate).
    69  		Build()
    70  	controlPlaneTemplateWithInfrastructureMachineNotTopologyOwned := builder.ControlPlaneTemplate(metav1.NamespaceDefault, "cpTemplateWithInfra1").
    71  		WithInfrastructureMachineTemplate(controlPlaneInfrastructureMachineTemplateNotTopologyOwned).
    72  		Build()
    73  	controlPlane := builder.ControlPlane(metav1.NamespaceDefault, "cp1").
    74  		Build()
    75  	controlPlane.SetLabels(map[string]string{clusterv1.ClusterTopologyOwnedLabel: ""})
    76  	controlPlaneWithInfra := builder.ControlPlane(metav1.NamespaceDefault, "cp1").
    77  		WithInfrastructureMachineTemplate(controlPlaneInfrastructureMachineTemplate).
    78  		Build()
    79  	controlPlaneWithInfra.SetLabels(map[string]string{clusterv1.ClusterTopologyOwnedLabel: ""})
    80  	controlPlaneWithInfraNotTopologyOwned := builder.ControlPlane(metav1.NamespaceDefault, "cp1").
    81  		WithInfrastructureMachineTemplate(controlPlaneInfrastructureMachineTemplateNotTopologyOwned).
    82  		Build()
    83  	controlPlaneNotTopologyOwned := builder.ControlPlane(metav1.NamespaceDefault, "cp1").
    84  		Build()
    85  
    86  	// ClusterClass  objects.
    87  	clusterClassWithControlPlaneInfra := builder.ClusterClass(metav1.NamespaceDefault, "class1").
    88  		WithControlPlaneTemplate(controlPlaneTemplateWithInfrastructureMachine).
    89  		WithControlPlaneInfrastructureMachineTemplate(controlPlaneInfrastructureMachineTemplate).
    90  		Build()
    91  	clusterClassWithControlPlaneInfraNotTopologyOwned := builder.ClusterClass(metav1.NamespaceDefault, "class1").
    92  		WithControlPlaneTemplate(controlPlaneTemplateWithInfrastructureMachineNotTopologyOwned).
    93  		WithControlPlaneInfrastructureMachineTemplate(controlPlaneInfrastructureMachineTemplateNotTopologyOwned).
    94  		Build()
    95  	clusterClassWithNoControlPlaneInfra := builder.ClusterClass(metav1.NamespaceDefault, "class2").
    96  		Build()
    97  
    98  	// MachineDeployment and related objects.
    99  	emptyMachineDeployments := make(map[string]*scope.MachineDeploymentState)
   100  
   101  	machineDeploymentInfrastructure := builder.InfrastructureMachineTemplate(metav1.NamespaceDefault, "infra1").
   102  		Build()
   103  	machineDeploymentInfrastructure.SetLabels(map[string]string{clusterv1.ClusterTopologyOwnedLabel: ""})
   104  	machineDeploymentBootstrap := builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap1").
   105  		Build()
   106  	machineDeploymentBootstrap.SetLabels(map[string]string{clusterv1.ClusterTopologyOwnedLabel: ""})
   107  
   108  	machineDeployment := builder.MachineDeployment(metav1.NamespaceDefault, "md1").
   109  		WithLabels(map[string]string{
   110  			clusterv1.ClusterNameLabel:                          "cluster1",
   111  			clusterv1.ClusterTopologyOwnedLabel:                 "",
   112  			clusterv1.ClusterTopologyMachineDeploymentNameLabel: "md1",
   113  		}).
   114  		WithBootstrapTemplate(machineDeploymentBootstrap).
   115  		WithInfrastructureTemplate(machineDeploymentInfrastructure).
   116  		Build()
   117  	machineDeployment2 := builder.MachineDeployment(metav1.NamespaceDefault, "md2").
   118  		WithLabels(map[string]string{
   119  			clusterv1.ClusterNameLabel:                          "cluster1",
   120  			clusterv1.ClusterTopologyOwnedLabel:                 "",
   121  			clusterv1.ClusterTopologyMachineDeploymentNameLabel: "md2",
   122  		}).
   123  		WithBootstrapTemplate(machineDeploymentBootstrap).
   124  		WithInfrastructureTemplate(machineDeploymentInfrastructure).
   125  		Build()
   126  
   127  	// MachineHealthChecks for the MachineDeployment and the ControlPlane.
   128  	machineHealthCheckForMachineDeployment := builder.MachineHealthCheck(machineDeployment.Namespace, machineDeployment.Name).
   129  		WithSelector(*selectorForMachineDeploymentMHC(machineDeployment)).
   130  		WithUnhealthyConditions([]clusterv1.UnhealthyCondition{
   131  			{
   132  				Type:    corev1.NodeReady,
   133  				Status:  corev1.ConditionUnknown,
   134  				Timeout: metav1.Duration{Duration: 5 * time.Minute},
   135  			},
   136  			{
   137  				Type:    corev1.NodeReady,
   138  				Status:  corev1.ConditionFalse,
   139  				Timeout: metav1.Duration{Duration: 5 * time.Minute},
   140  			},
   141  		}).
   142  		WithClusterName("cluster1").
   143  		Build()
   144  
   145  	machineHealthCheckForControlPlane := builder.MachineHealthCheck(controlPlane.GetNamespace(), controlPlane.GetName()).
   146  		WithSelector(*selectorForControlPlaneMHC()).
   147  		WithUnhealthyConditions([]clusterv1.UnhealthyCondition{
   148  			{
   149  				Type:    corev1.NodeReady,
   150  				Status:  corev1.ConditionUnknown,
   151  				Timeout: metav1.Duration{Duration: 5 * time.Minute},
   152  			},
   153  			{
   154  				Type:    corev1.NodeReady,
   155  				Status:  corev1.ConditionFalse,
   156  				Timeout: metav1.Duration{Duration: 5 * time.Minute},
   157  			},
   158  		}).
   159  		WithClusterName("cluster1").
   160  		Build()
   161  
   162  	// MachinePool and related objects.
   163  	emptyMachinePools := make(map[string]*scope.MachinePoolState)
   164  
   165  	machinePoolInfrastructureTemplate := builder.InfrastructureMachinePoolTemplate(metav1.NamespaceDefault, "infra2").
   166  		Build()
   167  	machinePoolInfrastructure := builder.InfrastructureMachinePool(metav1.NamespaceDefault, "infra2").
   168  		Build()
   169  	machinePoolInfrastructure.SetLabels(map[string]string{clusterv1.ClusterTopologyOwnedLabel: ""})
   170  	machinePoolBootstrapTemplate := builder.BootstrapTemplate(metav1.NamespaceDefault, "bootstrap2").
   171  		Build()
   172  	machinePoolBootstrap := builder.BootstrapConfig(metav1.NamespaceDefault, "bootstrap2").
   173  		Build()
   174  	machinePoolBootstrap.SetLabels(map[string]string{clusterv1.ClusterTopologyOwnedLabel: ""})
   175  
   176  	machinePool := builder.MachinePool(metav1.NamespaceDefault, "mp1").
   177  		WithLabels(map[string]string{
   178  			clusterv1.ClusterNameLabel:                    "cluster1",
   179  			clusterv1.ClusterTopologyOwnedLabel:           "",
   180  			clusterv1.ClusterTopologyMachinePoolNameLabel: "mp1",
   181  		}).
   182  		WithBootstrap(machinePoolBootstrap).
   183  		WithInfrastructure(machinePoolInfrastructure).
   184  		Build()
   185  	machinePool2 := builder.MachinePool(metav1.NamespaceDefault, "mp2").
   186  		WithLabels(map[string]string{
   187  			clusterv1.ClusterNameLabel:                    "cluster1",
   188  			clusterv1.ClusterTopologyOwnedLabel:           "",
   189  			clusterv1.ClusterTopologyMachinePoolNameLabel: "mp2",
   190  		}).
   191  		WithBootstrap(machinePoolBootstrap).
   192  		WithInfrastructure(machinePoolInfrastructure).
   193  		Build()
   194  
   195  	tests := []struct {
   196  		name      string
   197  		cluster   *clusterv1.Cluster
   198  		blueprint *scope.ClusterBlueprint
   199  		objects   []client.Object
   200  		want      *scope.ClusterState
   201  		wantErr   bool
   202  	}{
   203  		{
   204  			name:      "Should read a Cluster when being processed by the topology controller for the first time (without references)",
   205  			cluster:   builder.Cluster(metav1.NamespaceDefault, "cluster1").Build(),
   206  			blueprint: &scope.ClusterBlueprint{},
   207  			// Expecting valid return with no ControlPlane or Infrastructure state defined and empty MachineDeployment state list
   208  			want: &scope.ClusterState{
   209  				Cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1").
   210  					// No InfrastructureCluster or ControlPlane references!
   211  					Build(),
   212  				ControlPlane:          &scope.ControlPlaneState{},
   213  				InfrastructureCluster: nil,
   214  				MachineDeployments:    emptyMachineDeployments,
   215  				MachinePools:          emptyMachinePools,
   216  			},
   217  		},
   218  		{
   219  			name: "Fails if the Cluster references an InfrastructureCluster that does not exist",
   220  			cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1").
   221  				WithInfrastructureCluster(infraCluster).
   222  				Build(),
   223  			blueprint: &scope.ClusterBlueprint{
   224  				InfrastructureClusterTemplate: infraClusterTemplate,
   225  			},
   226  			objects: []client.Object{
   227  				// InfrastructureCluster is missing!
   228  			},
   229  			wantErr: true, // this test fails as partial reconcile is undefined.
   230  		},
   231  		{
   232  			name: "Fails if the Cluster references an InfrastructureCluster that is not topology owned",
   233  			cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1").
   234  				WithInfrastructureCluster(infraClusterNotTopologyOwned).
   235  				Build(),
   236  			blueprint: &scope.ClusterBlueprint{
   237  				InfrastructureClusterTemplate: infraClusterTemplate,
   238  			},
   239  			objects: []client.Object{
   240  				infraClusterNotTopologyOwned,
   241  			},
   242  			wantErr: true, // this test fails as partial reconcile is undefined.
   243  		},
   244  		{
   245  			name: "Fails if the Cluster references an Control Plane that is not topology owned",
   246  			cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1").
   247  				WithControlPlane(controlPlaneNotTopologyOwned).
   248  				Build(),
   249  			blueprint: &scope.ClusterBlueprint{
   250  				ClusterClass: clusterClassWithControlPlaneInfra,
   251  				ControlPlane: &scope.ControlPlaneBlueprint{
   252  					Template: controlPlaneTemplateWithInfrastructureMachine,
   253  				},
   254  			},
   255  			objects: []client.Object{
   256  				controlPlaneNotTopologyOwned,
   257  			},
   258  			wantErr: true, // this test fails as partial reconcile is undefined.
   259  		},
   260  		{
   261  			name: "Should read  a partial Cluster (with InfrastructureCluster only)",
   262  			cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1").
   263  				WithInfrastructureCluster(infraCluster).
   264  				// No ControlPlane reference!
   265  				Build(),
   266  			blueprint: &scope.ClusterBlueprint{
   267  				InfrastructureClusterTemplate: infraClusterTemplate,
   268  			},
   269  			objects: []client.Object{
   270  				infraCluster,
   271  			},
   272  			// Expecting valid return with no ControlPlane, MachineDeployment, or MachinePool state defined but with a valid Infrastructure state.
   273  			want: &scope.ClusterState{
   274  				Cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1").
   275  					WithInfrastructureCluster(infraCluster).
   276  					Build(),
   277  				ControlPlane:          &scope.ControlPlaneState{},
   278  				InfrastructureCluster: infraCluster,
   279  				MachineDeployments:    emptyMachineDeployments,
   280  				MachinePools:          emptyMachinePools,
   281  			},
   282  		},
   283  		{
   284  			name: "Should read a partial Cluster (with InfrastructureCluster, ControlPlane, but without workers)",
   285  			cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1").
   286  				WithControlPlane(controlPlane).
   287  				WithInfrastructureCluster(infraCluster).
   288  				Build(),
   289  			blueprint: &scope.ClusterBlueprint{
   290  				ClusterClass:                  clusterClassWithNoControlPlaneInfra,
   291  				InfrastructureClusterTemplate: infraClusterTemplate,
   292  				ControlPlane: &scope.ControlPlaneBlueprint{
   293  					Template: controlPlaneTemplateWithInfrastructureMachine,
   294  				},
   295  			},
   296  			objects: []client.Object{
   297  				controlPlane,
   298  				infraCluster,
   299  				clusterClassWithNoControlPlaneInfra,
   300  				// Workers are missing!
   301  			},
   302  			// Expecting valid return with ControlPlane, no ControlPlane Infrastructure state, InfrastructureCluster state, and no defined MachineDeployment or MachinePool state.
   303  			want: &scope.ClusterState{
   304  				Cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1").
   305  					WithControlPlane(controlPlane).
   306  					WithInfrastructureCluster(infraCluster).
   307  					Build(),
   308  				ControlPlane:          &scope.ControlPlaneState{Object: controlPlane, InfrastructureMachineTemplate: nil},
   309  				InfrastructureCluster: infraCluster,
   310  				MachineDeployments:    emptyMachineDeployments,
   311  				MachinePools:          emptyMachinePools,
   312  			},
   313  		},
   314  		{
   315  			name: "Fails if the ClusterClass requires InfrastructureMachine for the ControlPlane, but the ControlPlane object does not have a reference for it",
   316  			cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1").
   317  				WithControlPlane(controlPlane).
   318  				WithInfrastructureCluster(infraCluster).
   319  				Build(),
   320  			blueprint: &scope.ClusterBlueprint{
   321  				ClusterClass:                  clusterClassWithControlPlaneInfra,
   322  				InfrastructureClusterTemplate: infraClusterTemplate,
   323  				ControlPlane: &scope.ControlPlaneBlueprint{
   324  					Template: controlPlaneTemplateWithInfrastructureMachine,
   325  				},
   326  			},
   327  			objects: []client.Object{
   328  				controlPlane,
   329  				infraCluster,
   330  				clusterClassWithControlPlaneInfra,
   331  			},
   332  			// Expecting error from ControlPlane having no valid ControlPlane Infrastructure with ClusterClass requiring ControlPlane Infrastructure.
   333  			wantErr: true,
   334  		},
   335  		{
   336  			name: "Should read a partial Cluster (with ControlPlane and ControlPlane InfrastructureMachineTemplate, but without InfrastructureCluster and workers)",
   337  			cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1").
   338  				// No InfrastructureCluster!
   339  				WithControlPlane(controlPlaneWithInfra).
   340  				Build(),
   341  			blueprint: &scope.ClusterBlueprint{
   342  				ClusterClass: clusterClassWithControlPlaneInfra,
   343  				ControlPlane: &scope.ControlPlaneBlueprint{
   344  					Template:                      controlPlaneTemplateWithInfrastructureMachine,
   345  					InfrastructureMachineTemplate: controlPlaneInfrastructureMachineTemplate,
   346  				},
   347  			},
   348  			objects: []client.Object{
   349  				controlPlaneWithInfra,
   350  				controlPlaneInfrastructureMachineTemplate,
   351  				// Workers are missing!
   352  			},
   353  			// Expecting valid return with valid ControlPlane state, but no ControlPlane Infrastructure, InfrastructureCluster, MachineDeployment, or MachinePool state defined.
   354  			want: &scope.ClusterState{
   355  				Cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1").
   356  					WithControlPlane(controlPlaneWithInfra).
   357  					Build(),
   358  				ControlPlane:          &scope.ControlPlaneState{Object: controlPlaneWithInfra, InfrastructureMachineTemplate: controlPlaneInfrastructureMachineTemplate},
   359  				InfrastructureCluster: nil,
   360  				MachineDeployments:    emptyMachineDeployments,
   361  				MachinePools:          emptyMachinePools,
   362  			},
   363  		},
   364  		{
   365  			name: "Should read a partial Cluster (with InfrastructureCluster ControlPlane and ControlPlane InfrastructureMachineTemplate, but without workers)",
   366  			cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1").
   367  				WithInfrastructureCluster(infraCluster).
   368  				WithControlPlane(controlPlaneWithInfra).
   369  				Build(),
   370  			blueprint: &scope.ClusterBlueprint{
   371  				ClusterClass:                  clusterClassWithControlPlaneInfra,
   372  				InfrastructureClusterTemplate: infraClusterTemplate,
   373  				ControlPlane: &scope.ControlPlaneBlueprint{
   374  					Template:                      controlPlaneTemplateWithInfrastructureMachine,
   375  					InfrastructureMachineTemplate: controlPlaneInfrastructureMachineTemplate,
   376  				},
   377  			},
   378  			objects: []client.Object{
   379  				infraCluster,
   380  				clusterClassWithControlPlaneInfra,
   381  				controlPlaneInfrastructureMachineTemplate,
   382  				controlPlaneWithInfra,
   383  				// Workers are missing!
   384  			},
   385  			// Expecting valid return with valid ControlPlane state, ControlPlane Infrastructure state and InfrastructureCluster state, but no defined MachineDeployment or MachinePool state.
   386  			want: &scope.ClusterState{
   387  				Cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1").
   388  					WithInfrastructureCluster(infraCluster).
   389  					WithControlPlane(controlPlaneWithInfra).
   390  					Build(),
   391  				ControlPlane:          &scope.ControlPlaneState{Object: controlPlaneWithInfra, InfrastructureMachineTemplate: controlPlaneInfrastructureMachineTemplate},
   392  				InfrastructureCluster: infraCluster,
   393  				MachineDeployments:    emptyMachineDeployments,
   394  				MachinePools:          emptyMachinePools,
   395  			},
   396  		},
   397  		{
   398  			name: "Should read a Cluster (with InfrastructureCluster, ControlPlane and ControlPlane InfrastructureMachineTemplate, workers)",
   399  			cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1").
   400  				WithTopology(builder.ClusterTopology().
   401  					WithMachineDeployment(clusterv1.MachineDeploymentTopology{
   402  						Class: "mdClass",
   403  						Name:  "md1",
   404  					}).
   405  					WithMachinePool(clusterv1.MachinePoolTopology{
   406  						Class: "mpClass",
   407  						Name:  "mp1",
   408  					}).
   409  					Build()).
   410  				Build(),
   411  			blueprint: &scope.ClusterBlueprint{
   412  				ClusterClass:                  clusterClassWithControlPlaneInfra,
   413  				InfrastructureClusterTemplate: infraClusterTemplate,
   414  				ControlPlane: &scope.ControlPlaneBlueprint{
   415  					Template:                      controlPlaneTemplateWithInfrastructureMachine,
   416  					InfrastructureMachineTemplate: controlPlaneInfrastructureMachineTemplate,
   417  				},
   418  				MachineDeployments: map[string]*scope.MachineDeploymentBlueprint{
   419  					"mdClass": {
   420  						BootstrapTemplate:             machineDeploymentBootstrap,
   421  						InfrastructureMachineTemplate: machineDeploymentInfrastructure,
   422  					},
   423  				},
   424  				MachinePools: map[string]*scope.MachinePoolBlueprint{
   425  					"mpClass": {
   426  						BootstrapTemplate:                 machinePoolBootstrap,
   427  						InfrastructureMachinePoolTemplate: machinePoolInfrastructure,
   428  					},
   429  				},
   430  			},
   431  			objects: []client.Object{
   432  				infraCluster,
   433  				clusterClassWithControlPlaneInfra,
   434  				controlPlaneInfrastructureMachineTemplate,
   435  				controlPlaneWithInfra,
   436  				machineDeploymentInfrastructure,
   437  				machineDeploymentBootstrap,
   438  				machineDeployment,
   439  				machinePoolInfrastructure,
   440  				machinePoolBootstrap,
   441  				machinePool,
   442  			},
   443  			// Expecting valid return with valid ControlPlane, ControlPlane Infrastructure, InfrastructureCluster, MachineDeployment and MachinePool state.
   444  			want: &scope.ClusterState{
   445  				Cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1").
   446  					WithTopology(builder.ClusterTopology().
   447  						WithMachineDeployment(clusterv1.MachineDeploymentTopology{
   448  							Class: "mdClass",
   449  							Name:  "md1",
   450  						}).
   451  						WithMachinePool(clusterv1.MachinePoolTopology{
   452  							Class: "mpClass",
   453  							Name:  "mp1",
   454  						}).
   455  						Build()).
   456  					Build(),
   457  				ControlPlane:          &scope.ControlPlaneState{},
   458  				InfrastructureCluster: nil,
   459  				MachineDeployments: map[string]*scope.MachineDeploymentState{
   460  					"md1": {Object: machineDeployment, BootstrapTemplate: machineDeploymentBootstrap, InfrastructureMachineTemplate: machineDeploymentInfrastructure}},
   461  				MachinePools: map[string]*scope.MachinePoolState{
   462  					"mp1": {Object: machinePool, BootstrapObject: machinePoolBootstrap, InfrastructureMachinePoolObject: machinePoolInfrastructure}},
   463  			},
   464  		},
   465  		{
   466  			name: "Fails if the ControlPlane references an InfrastructureMachineTemplate that is not topology owned",
   467  			cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1").
   468  				// No InfrastructureCluster!
   469  				WithControlPlane(controlPlaneWithInfraNotTopologyOwned).
   470  				Build(),
   471  			blueprint: &scope.ClusterBlueprint{
   472  				ClusterClass: clusterClassWithControlPlaneInfraNotTopologyOwned,
   473  				ControlPlane: &scope.ControlPlaneBlueprint{
   474  					Template:                      controlPlaneTemplateWithInfrastructureMachine,
   475  					InfrastructureMachineTemplate: controlPlaneInfrastructureMachineTemplate,
   476  				},
   477  			},
   478  			objects: []client.Object{
   479  				controlPlaneWithInfraNotTopologyOwned,
   480  				controlPlaneInfrastructureMachineTemplateNotTopologyOwned,
   481  			},
   482  			wantErr: true,
   483  		},
   484  		{
   485  			name: "Fails if the ControlPlane references an InfrastructureMachineTemplate that does not exist",
   486  			cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1").
   487  				WithControlPlane(controlPlane).
   488  				Build(),
   489  			blueprint: &scope.ClusterBlueprint{
   490  				ClusterClass: clusterClassWithControlPlaneInfra,
   491  				ControlPlane: &scope.ControlPlaneBlueprint{
   492  					Template:                      controlPlaneTemplateWithInfrastructureMachine,
   493  					InfrastructureMachineTemplate: controlPlaneInfrastructureMachineTemplate,
   494  				},
   495  			},
   496  			objects: []client.Object{
   497  				clusterClassWithControlPlaneInfra,
   498  				controlPlane,
   499  				// InfrastructureMachineTemplate is missing!
   500  			},
   501  			// Expecting error as ClusterClass references ControlPlane Infrastructure, but ControlPlane Infrastructure is missing in the cluster.
   502  			wantErr: true,
   503  		},
   504  		{
   505  			name: "Should ignore unmanaged MachineDeployments/MachinePools and MachineDeployments/MachinePools belonging to other clusters",
   506  			cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1").
   507  				Build(),
   508  			blueprint: &scope.ClusterBlueprint{ClusterClass: clusterClassWithControlPlaneInfra},
   509  			objects: []client.Object{
   510  				clusterClassWithControlPlaneInfra,
   511  				builder.MachineDeployment(metav1.NamespaceDefault, "no-managed-label").
   512  					WithLabels(map[string]string{
   513  						clusterv1.ClusterNameLabel: "cluster1",
   514  						// topology.cluster.x-k8s.io/owned label is missing (unmanaged)!
   515  					}).
   516  					WithBootstrapTemplate(machineDeploymentBootstrap).
   517  					WithInfrastructureTemplate(machineDeploymentInfrastructure).
   518  					Build(),
   519  				builder.MachineDeployment(metav1.NamespaceDefault, "wrong-cluster-label").
   520  					WithLabels(map[string]string{
   521  						clusterv1.ClusterNameLabel:                          "another-cluster",
   522  						clusterv1.ClusterTopologyOwnedLabel:                 "",
   523  						clusterv1.ClusterTopologyMachineDeploymentNameLabel: "md1",
   524  					}).
   525  					WithBootstrapTemplate(machineDeploymentBootstrap).
   526  					WithInfrastructureTemplate(machineDeploymentInfrastructure).
   527  					Build(),
   528  				builder.MachinePool(metav1.NamespaceDefault, "no-managed-label").
   529  					WithLabels(map[string]string{
   530  						clusterv1.ClusterNameLabel: "cluster1",
   531  						// topology.cluster.x-k8s.io/owned label is missing (unmanaged)!
   532  					}).
   533  					WithBootstrap(machinePoolBootstrap).
   534  					WithInfrastructure(machinePoolInfrastructure).
   535  					Build(),
   536  				builder.MachinePool(metav1.NamespaceDefault, "wrong-cluster-label").
   537  					WithLabels(map[string]string{
   538  						clusterv1.ClusterNameLabel:                    "another-cluster",
   539  						clusterv1.ClusterTopologyOwnedLabel:           "",
   540  						clusterv1.ClusterTopologyMachinePoolNameLabel: "mp1",
   541  					}).
   542  					WithBootstrap(machinePoolBootstrap).
   543  					WithInfrastructure(machinePoolInfrastructure).
   544  					Build(),
   545  			},
   546  			// Expect valid return with empty MachineDeployments and MachinePools properly filtered by label.
   547  			want: &scope.ClusterState{
   548  				Cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1").
   549  					Build(),
   550  				ControlPlane:          &scope.ControlPlaneState{},
   551  				InfrastructureCluster: nil,
   552  				MachineDeployments:    emptyMachineDeployments,
   553  				MachinePools:          emptyMachinePools,
   554  			},
   555  		},
   556  		{
   557  			name: "Fails if there are MachineDeployments without the topology.cluster.x-k8s.io/deployment-name",
   558  			cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1").
   559  				Build(),
   560  			blueprint: &scope.ClusterBlueprint{ClusterClass: clusterClassWithControlPlaneInfra},
   561  			objects: []client.Object{
   562  				clusterClassWithControlPlaneInfra,
   563  				builder.MachineDeployment(metav1.NamespaceDefault, "missing-topology-md-labelName").
   564  					WithLabels(map[string]string{
   565  						clusterv1.ClusterNameLabel:          "cluster1",
   566  						clusterv1.ClusterTopologyOwnedLabel: "",
   567  						// topology.cluster.x-k8s.io/deployment-name label is missing!
   568  					}).
   569  					WithBootstrapTemplate(machineDeploymentBootstrap).
   570  					WithInfrastructureTemplate(machineDeploymentInfrastructure).
   571  					Build(),
   572  			},
   573  			// Expect error to be thrown as no managed MachineDeployment is reconcilable unless it has a ClusterTopologyMachineDeploymentNameLabel.
   574  			wantErr: true,
   575  		},
   576  		{
   577  			name: "Fails if there are MachineDeployments with the same topology.cluster.x-k8s.io/deployment-name",
   578  			cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1").
   579  				WithTopology(builder.ClusterTopology().
   580  					WithMachineDeployment(clusterv1.MachineDeploymentTopology{
   581  						Class: "mdClass",
   582  						Name:  "md1",
   583  					}).
   584  					Build()).
   585  				Build(),
   586  			blueprint: &scope.ClusterBlueprint{
   587  				ClusterClass: clusterClassWithControlPlaneInfra,
   588  				ControlPlane: &scope.ControlPlaneBlueprint{
   589  					Template:                      controlPlaneTemplateWithInfrastructureMachine,
   590  					InfrastructureMachineTemplate: controlPlaneInfrastructureMachineTemplate,
   591  				},
   592  			},
   593  			objects: []client.Object{
   594  				clusterClassWithControlPlaneInfra,
   595  				machineDeploymentInfrastructure,
   596  				machineDeploymentBootstrap,
   597  				machineDeployment,
   598  				builder.MachineDeployment(metav1.NamespaceDefault, "duplicate-labels").
   599  					WithLabels(machineDeployment.Labels). // Another machine deployment with the same labels.
   600  					WithBootstrapTemplate(machineDeploymentBootstrap).
   601  					WithInfrastructureTemplate(machineDeploymentInfrastructure).
   602  					Build(),
   603  			},
   604  			// Expect error as two MachineDeployments with the same ClusterTopologyOwnedLabel should not exist for one cluster
   605  			wantErr: true,
   606  		},
   607  		{
   608  			name: "Fails if there are MachinePools without the topology.cluster.x-k8s.io/pool-name",
   609  			cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1").
   610  				Build(),
   611  			blueprint: &scope.ClusterBlueprint{ClusterClass: clusterClassWithControlPlaneInfra},
   612  			objects: []client.Object{
   613  				clusterClassWithControlPlaneInfra,
   614  				builder.MachinePool(metav1.NamespaceDefault, "missing-topology-mp-labelName").
   615  					WithLabels(map[string]string{
   616  						clusterv1.ClusterNameLabel:          "cluster1",
   617  						clusterv1.ClusterTopologyOwnedLabel: "",
   618  						// topology.cluster.x-k8s.io/pool-name label is missing!
   619  					}).
   620  					WithBootstrap(machinePoolBootstrap).
   621  					WithInfrastructure(machinePoolInfrastructure).
   622  					Build(),
   623  			},
   624  			// Expect error to be thrown as no managed MachinePool is reconcilable unless it has a ClusterTopologyMachinePoolNameLabel.
   625  			wantErr: true,
   626  		},
   627  		{
   628  			name: "Fails if there are MachinePools with the same topology.cluster.x-k8s.io/pool-name",
   629  			cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1").
   630  				WithTopology(builder.ClusterTopology().
   631  					WithMachinePool(clusterv1.MachinePoolTopology{
   632  						Class: "mpClass",
   633  						Name:  "mp1",
   634  					}).
   635  					Build()).
   636  				Build(),
   637  			blueprint: &scope.ClusterBlueprint{
   638  				ClusterClass: clusterClassWithControlPlaneInfra,
   639  				ControlPlane: &scope.ControlPlaneBlueprint{
   640  					Template:                      controlPlaneTemplateWithInfrastructureMachine,
   641  					InfrastructureMachineTemplate: controlPlaneInfrastructureMachineTemplate,
   642  				},
   643  			},
   644  			objects: []client.Object{
   645  				clusterClassWithControlPlaneInfra,
   646  				machinePoolInfrastructure,
   647  				machinePoolBootstrap,
   648  				machinePool,
   649  				builder.MachinePool(metav1.NamespaceDefault, "duplicate-labels").
   650  					WithLabels(machinePool.Labels). // Another machine pool with the same labels.
   651  					WithBootstrap(machinePoolBootstrap).
   652  					WithInfrastructure(machinePoolInfrastructure).
   653  					Build(),
   654  			},
   655  			// Expect error as two MachinePools with the same ClusterTopologyOwnedLabel should not exist for one cluster
   656  			wantErr: true,
   657  		},
   658  		{
   659  			name: "Should read a full Cluster (With InfrastructureCluster, ControlPlane and ControlPlane Infrastructure, MachineDeployments, MachinePools)",
   660  			cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1").
   661  				WithInfrastructureCluster(infraCluster).
   662  				WithControlPlane(controlPlaneWithInfra).
   663  				WithTopology(builder.ClusterTopology().
   664  					WithMachineDeployment(clusterv1.MachineDeploymentTopology{
   665  						Class: "mdClass",
   666  						Name:  "md1",
   667  					}).
   668  					WithMachinePool(clusterv1.MachinePoolTopology{
   669  						Class: "mpClass",
   670  						Name:  "mp1",
   671  					}).
   672  					Build()).
   673  				Build(),
   674  			blueprint: &scope.ClusterBlueprint{
   675  				ClusterClass:                  clusterClassWithControlPlaneInfra,
   676  				InfrastructureClusterTemplate: infraClusterTemplate,
   677  				ControlPlane: &scope.ControlPlaneBlueprint{
   678  					Template:                      controlPlaneTemplateWithInfrastructureMachine,
   679  					InfrastructureMachineTemplate: controlPlaneInfrastructureMachineTemplate,
   680  				},
   681  				MachineDeployments: map[string]*scope.MachineDeploymentBlueprint{
   682  					"mdClass": {
   683  						BootstrapTemplate:             machineDeploymentBootstrap,
   684  						InfrastructureMachineTemplate: machineDeploymentInfrastructure,
   685  					},
   686  				},
   687  				MachinePools: map[string]*scope.MachinePoolBlueprint{
   688  					"mpClass": {
   689  						BootstrapTemplate:                 machinePoolBootstrapTemplate,
   690  						InfrastructureMachinePoolTemplate: machinePoolInfrastructureTemplate,
   691  					},
   692  				},
   693  			},
   694  			objects: []client.Object{
   695  				infraCluster,
   696  				clusterClassWithControlPlaneInfra,
   697  				controlPlaneInfrastructureMachineTemplate,
   698  				controlPlaneWithInfra,
   699  				machineDeploymentInfrastructure,
   700  				machineDeploymentBootstrap,
   701  				machineDeployment,
   702  				machinePoolInfrastructure,
   703  				machinePoolBootstrap,
   704  				machinePool,
   705  			},
   706  			// Expect valid return of full ClusterState with MachineDeployments and MachinePools properly filtered by label.
   707  			want: &scope.ClusterState{
   708  				Cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1").
   709  					WithInfrastructureCluster(infraCluster).
   710  					WithControlPlane(controlPlaneWithInfra).
   711  					WithTopology(builder.ClusterTopology().
   712  						WithMachineDeployment(clusterv1.MachineDeploymentTopology{
   713  							Class: "mdClass",
   714  							Name:  "md1",
   715  						}).
   716  						WithMachinePool(clusterv1.MachinePoolTopology{
   717  							Class: "mpClass",
   718  							Name:  "mp1",
   719  						}).
   720  						Build()).
   721  					Build(),
   722  				ControlPlane:          &scope.ControlPlaneState{Object: controlPlaneWithInfra, InfrastructureMachineTemplate: controlPlaneInfrastructureMachineTemplate},
   723  				InfrastructureCluster: infraCluster,
   724  				MachineDeployments: map[string]*scope.MachineDeploymentState{
   725  					"md1": {
   726  						Object:                        machineDeployment,
   727  						BootstrapTemplate:             machineDeploymentBootstrap,
   728  						InfrastructureMachineTemplate: machineDeploymentInfrastructure,
   729  					},
   730  				},
   731  				MachinePools: map[string]*scope.MachinePoolState{
   732  					"mp1": {
   733  						Object:                          machinePool,
   734  						BootstrapObject:                 machinePoolBootstrap,
   735  						InfrastructureMachinePoolObject: machinePoolInfrastructure,
   736  					},
   737  				},
   738  			},
   739  		},
   740  		{
   741  			name: "Should read a full Cluster, even if a MachineDeployment & MachinePool topology has been deleted and the MachineDeployment & MachinePool still exists",
   742  			cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1").
   743  				WithInfrastructureCluster(infraCluster).
   744  				WithControlPlane(controlPlaneWithInfra).
   745  				WithTopology(builder.ClusterTopology().
   746  					WithMachineDeployment(clusterv1.MachineDeploymentTopology{
   747  						Class: "mdClass",
   748  						Name:  "md1",
   749  					}).
   750  					WithMachinePool(clusterv1.MachinePoolTopology{
   751  						Class: "mpClass",
   752  						Name:  "mp1",
   753  					}).
   754  					Build()).
   755  				Build(),
   756  			blueprint: &scope.ClusterBlueprint{
   757  				ClusterClass:                  clusterClassWithControlPlaneInfra,
   758  				InfrastructureClusterTemplate: infraClusterTemplate,
   759  				ControlPlane: &scope.ControlPlaneBlueprint{
   760  					Template:                      controlPlaneTemplateWithInfrastructureMachine,
   761  					InfrastructureMachineTemplate: controlPlaneInfrastructureMachineTemplate,
   762  				},
   763  				MachineDeployments: map[string]*scope.MachineDeploymentBlueprint{
   764  					"mdClass": {
   765  						BootstrapTemplate:             machineDeploymentBootstrap,
   766  						InfrastructureMachineTemplate: machineDeploymentInfrastructure,
   767  					},
   768  				},
   769  				MachinePools: map[string]*scope.MachinePoolBlueprint{
   770  					"mpClass": {
   771  						BootstrapTemplate:                 machinePoolBootstrapTemplate,
   772  						InfrastructureMachinePoolTemplate: machinePoolInfrastructureTemplate,
   773  					},
   774  				},
   775  			},
   776  			objects: []client.Object{
   777  				infraCluster,
   778  				clusterClassWithControlPlaneInfra,
   779  				controlPlaneInfrastructureMachineTemplate,
   780  				controlPlaneWithInfra,
   781  				machineDeploymentInfrastructure,
   782  				machineDeploymentBootstrap,
   783  				machineDeployment,
   784  				machineDeployment2,
   785  				machinePoolInfrastructure,
   786  				machinePoolBootstrap,
   787  				machinePool,
   788  				machinePool2,
   789  			},
   790  			// Expect valid return of full ClusterState with MachineDeployments and MachinePools properly filtered by label.
   791  			want: &scope.ClusterState{
   792  				Cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1").
   793  					WithInfrastructureCluster(infraCluster).
   794  					WithControlPlane(controlPlaneWithInfra).
   795  					WithTopology(builder.ClusterTopology().
   796  						WithMachineDeployment(clusterv1.MachineDeploymentTopology{
   797  							Class: "mdClass",
   798  							Name:  "md1",
   799  						}).
   800  						WithMachinePool(clusterv1.MachinePoolTopology{
   801  							Class: "mpClass",
   802  							Name:  "mp1",
   803  						}).
   804  						Build()).
   805  					Build(),
   806  				ControlPlane:          &scope.ControlPlaneState{Object: controlPlaneWithInfra, InfrastructureMachineTemplate: controlPlaneInfrastructureMachineTemplate},
   807  				InfrastructureCluster: infraCluster,
   808  				MachineDeployments: map[string]*scope.MachineDeploymentState{
   809  					"md1": {
   810  						Object:                        machineDeployment,
   811  						BootstrapTemplate:             machineDeploymentBootstrap,
   812  						InfrastructureMachineTemplate: machineDeploymentInfrastructure,
   813  					},
   814  					"md2": {
   815  						Object:                        machineDeployment2,
   816  						BootstrapTemplate:             machineDeploymentBootstrap,
   817  						InfrastructureMachineTemplate: machineDeploymentInfrastructure,
   818  					},
   819  				},
   820  				MachinePools: map[string]*scope.MachinePoolState{
   821  					"mp1": {
   822  						Object:                          machinePool,
   823  						BootstrapObject:                 machinePoolBootstrap,
   824  						InfrastructureMachinePoolObject: machinePoolInfrastructure,
   825  					},
   826  					"mp2": {
   827  						Object:                          machinePool2,
   828  						BootstrapObject:                 machinePoolBootstrap,
   829  						InfrastructureMachinePoolObject: machinePoolInfrastructure,
   830  					},
   831  				},
   832  			},
   833  		},
   834  		{
   835  			name: "Fails if a Cluster has a MachineDeployment without the Bootstrap Template ref",
   836  			cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1").
   837  				WithTopology(builder.ClusterTopology().
   838  					WithMachineDeployment(clusterv1.MachineDeploymentTopology{
   839  						Class: "mdClass",
   840  						Name:  "md1",
   841  					}).
   842  					Build()).
   843  				Build(),
   844  			blueprint: &scope.ClusterBlueprint{
   845  				ClusterClass:                  clusterClassWithControlPlaneInfra,
   846  				InfrastructureClusterTemplate: infraClusterTemplate,
   847  				ControlPlane: &scope.ControlPlaneBlueprint{
   848  					Template:                      controlPlaneTemplateWithInfrastructureMachine,
   849  					InfrastructureMachineTemplate: controlPlaneInfrastructureMachineTemplate,
   850  				},
   851  				MachineDeployments: map[string]*scope.MachineDeploymentBlueprint{
   852  					"mdClass": {
   853  						BootstrapTemplate:             machineDeploymentBootstrap,
   854  						InfrastructureMachineTemplate: machineDeploymentInfrastructure,
   855  					},
   856  				},
   857  			},
   858  			objects: []client.Object{
   859  				infraCluster,
   860  				clusterClassWithControlPlaneInfra,
   861  				controlPlaneInfrastructureMachineTemplate,
   862  				controlPlaneWithInfra,
   863  				machineDeploymentInfrastructure,
   864  				builder.MachineDeployment(metav1.NamespaceDefault, "no-bootstrap").
   865  					WithLabels(machineDeployment.Labels).
   866  					// No BootstrapConfigTemplate reference!
   867  					WithInfrastructureTemplate(machineDeploymentInfrastructure).
   868  					Build(),
   869  			},
   870  			// Expect error as Bootstrap Template not defined for MachineDeployments relevant to the Cluster.
   871  			wantErr: true,
   872  		},
   873  		{
   874  			name: "Fails if a Cluster has a MachinePool without the Bootstrap Template ref",
   875  			cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1").
   876  				WithTopology(builder.ClusterTopology().
   877  					WithMachinePool(clusterv1.MachinePoolTopology{
   878  						Class: "mpClass",
   879  						Name:  "mp1",
   880  					}).
   881  					Build()).
   882  				Build(),
   883  			blueprint: &scope.ClusterBlueprint{
   884  				ClusterClass:                  clusterClassWithControlPlaneInfra,
   885  				InfrastructureClusterTemplate: infraClusterTemplate,
   886  				ControlPlane: &scope.ControlPlaneBlueprint{
   887  					Template:                      controlPlaneTemplateWithInfrastructureMachine,
   888  					InfrastructureMachineTemplate: controlPlaneInfrastructureMachineTemplate,
   889  				},
   890  				MachinePools: map[string]*scope.MachinePoolBlueprint{
   891  					"mpClass": {
   892  						BootstrapTemplate:                 machinePoolBootstrapTemplate,
   893  						InfrastructureMachinePoolTemplate: machinePoolInfrastructureTemplate,
   894  					},
   895  				},
   896  			},
   897  			objects: []client.Object{
   898  				infraCluster,
   899  				clusterClassWithControlPlaneInfra,
   900  				controlPlaneInfrastructureMachineTemplate,
   901  				controlPlaneWithInfra,
   902  				machinePoolInfrastructure,
   903  				builder.MachinePool(metav1.NamespaceDefault, "no-bootstrap").
   904  					WithLabels(machinePool.Labels).
   905  					// No BootstrapConfig reference!
   906  					WithInfrastructure(machinePoolInfrastructure).
   907  					Build(),
   908  			},
   909  			// Expect error as BootstrapConfig not defined for MachinePools relevant to the Cluster.
   910  			wantErr: true,
   911  		},
   912  		{
   913  			name: "Fails if a Cluster has a MachineDeployments without the InfrastructureMachineTemplate ref",
   914  			cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1").
   915  				WithTopology(builder.ClusterTopology().
   916  					WithMachineDeployment(clusterv1.MachineDeploymentTopology{
   917  						Class: "mdClass",
   918  						Name:  "md1",
   919  					}).
   920  					Build()).
   921  				Build(),
   922  			blueprint: &scope.ClusterBlueprint{
   923  				ClusterClass:                  clusterClassWithControlPlaneInfra,
   924  				InfrastructureClusterTemplate: infraClusterTemplate,
   925  				ControlPlane: &scope.ControlPlaneBlueprint{
   926  					Template:                      controlPlaneTemplateWithInfrastructureMachine,
   927  					InfrastructureMachineTemplate: controlPlaneInfrastructureMachineTemplate,
   928  				},
   929  				MachineDeployments: map[string]*scope.MachineDeploymentBlueprint{
   930  					"mdClass": {
   931  						BootstrapTemplate:             machineDeploymentBootstrap,
   932  						InfrastructureMachineTemplate: machineDeploymentInfrastructure,
   933  					},
   934  				},
   935  			},
   936  			objects: []client.Object{
   937  				infraCluster,
   938  				clusterClassWithControlPlaneInfra,
   939  				controlPlaneInfrastructureMachineTemplate,
   940  				controlPlaneWithInfra,
   941  				machineDeploymentBootstrap,
   942  				builder.MachineDeployment(metav1.NamespaceDefault, "no-infra").
   943  					WithLabels(machineDeployment.Labels).
   944  					WithBootstrapTemplate(machineDeploymentBootstrap).
   945  					// No InfrastructureMachineTemplate reference!
   946  					Build(),
   947  			},
   948  			// Expect error as Infrastructure Template not defined for MachineDeployment relevant to the Cluster.
   949  			wantErr: true,
   950  		},
   951  		{
   952  			name: "Fails if a Cluster has a MachinePools without the InfrastructureMachinePool ref",
   953  			cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1").
   954  				WithTopology(builder.ClusterTopology().
   955  					WithMachinePool(clusterv1.MachinePoolTopology{
   956  						Class: "mpClass",
   957  						Name:  "mp1",
   958  					}).
   959  					Build()).
   960  				Build(),
   961  			blueprint: &scope.ClusterBlueprint{
   962  				ClusterClass:                  clusterClassWithControlPlaneInfra,
   963  				InfrastructureClusterTemplate: infraClusterTemplate,
   964  				ControlPlane: &scope.ControlPlaneBlueprint{
   965  					Template:                      controlPlaneTemplateWithInfrastructureMachine,
   966  					InfrastructureMachineTemplate: controlPlaneInfrastructureMachineTemplate,
   967  				},
   968  				MachinePools: map[string]*scope.MachinePoolBlueprint{
   969  					"mpClass": {
   970  						BootstrapTemplate:                 machinePoolBootstrapTemplate,
   971  						InfrastructureMachinePoolTemplate: machinePoolInfrastructureTemplate,
   972  					},
   973  				},
   974  			},
   975  			objects: []client.Object{
   976  				infraCluster,
   977  				clusterClassWithControlPlaneInfra,
   978  				controlPlaneInfrastructureMachineTemplate,
   979  				controlPlaneWithInfra,
   980  				machinePoolBootstrap,
   981  				builder.MachinePool(metav1.NamespaceDefault, "no-infra").
   982  					WithLabels(machinePool.Labels).
   983  					WithBootstrap(machinePoolBootstrap).
   984  					// No InfrastructureMachinePool reference!
   985  					Build(),
   986  			},
   987  			// Expect error as InfrastructureMachinePool not defined for MachinePool relevant to the Cluster.
   988  			wantErr: true,
   989  		},
   990  		{
   991  			name: "Pass reading a full Cluster with MachineHealthChecks for ControlPlane and MachineDeployment)",
   992  			cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1").
   993  				WithInfrastructureCluster(infraCluster).
   994  				WithControlPlane(controlPlaneWithInfra).
   995  				WithTopology(builder.ClusterTopology().
   996  					WithMachineDeployment(clusterv1.MachineDeploymentTopology{
   997  						Class: "mdClass",
   998  						Name:  "md1",
   999  					}).
  1000  					Build()).
  1001  				Build(),
  1002  			blueprint: &scope.ClusterBlueprint{
  1003  				ClusterClass:                  clusterClassWithControlPlaneInfra,
  1004  				InfrastructureClusterTemplate: infraClusterTemplate,
  1005  				ControlPlane: &scope.ControlPlaneBlueprint{
  1006  					Template:                      controlPlaneTemplateWithInfrastructureMachine,
  1007  					InfrastructureMachineTemplate: controlPlaneInfrastructureMachineTemplate,
  1008  				},
  1009  				MachineDeployments: map[string]*scope.MachineDeploymentBlueprint{
  1010  					"mdClass": {
  1011  						BootstrapTemplate:             machineDeploymentBootstrap,
  1012  						InfrastructureMachineTemplate: machineDeploymentInfrastructure,
  1013  					},
  1014  				},
  1015  			},
  1016  			objects: []client.Object{
  1017  				infraCluster,
  1018  				clusterClassWithControlPlaneInfra,
  1019  				controlPlaneInfrastructureMachineTemplate,
  1020  				controlPlaneWithInfra,
  1021  				machineDeploymentInfrastructure,
  1022  				machineDeploymentBootstrap,
  1023  				machineDeployment,
  1024  				machineHealthCheckForMachineDeployment,
  1025  				machineHealthCheckForControlPlane,
  1026  			},
  1027  			// Expect valid return of full ClusterState with MachineHealthChecks for both ControlPlane and MachineDeployment.
  1028  			want: &scope.ClusterState{
  1029  				Cluster: builder.Cluster(metav1.NamespaceDefault, "cluster1").
  1030  					WithInfrastructureCluster(infraCluster).
  1031  					WithControlPlane(controlPlaneWithInfra).
  1032  					WithTopology(builder.ClusterTopology().
  1033  						WithMachineDeployment(clusterv1.MachineDeploymentTopology{
  1034  							Class: "mdClass",
  1035  							Name:  "md1",
  1036  						}).
  1037  						Build()).
  1038  					Build(),
  1039  				ControlPlane: &scope.ControlPlaneState{
  1040  					Object:                        controlPlaneWithInfra,
  1041  					InfrastructureMachineTemplate: controlPlaneInfrastructureMachineTemplate,
  1042  					MachineHealthCheck:            machineHealthCheckForControlPlane,
  1043  				},
  1044  				InfrastructureCluster: infraCluster,
  1045  				MachineDeployments: map[string]*scope.MachineDeploymentState{
  1046  					"md1": {
  1047  						Object:                        machineDeployment,
  1048  						BootstrapTemplate:             machineDeploymentBootstrap,
  1049  						InfrastructureMachineTemplate: machineDeploymentInfrastructure,
  1050  						MachineHealthCheck:            machineHealthCheckForMachineDeployment,
  1051  					},
  1052  				},
  1053  				MachinePools: emptyMachinePools,
  1054  			},
  1055  		},
  1056  	}
  1057  	for _, tt := range tests {
  1058  		t.Run(tt.name, func(t *testing.T) {
  1059  			g := NewWithT(t)
  1060  
  1061  			// Sets up a scope with a Blueprint.
  1062  			s := scope.New(tt.cluster)
  1063  			s.Blueprint = tt.blueprint
  1064  
  1065  			// Sets up the fakeClient for the test case.
  1066  			objs := []client.Object{}
  1067  			objs = append(objs, crds...)
  1068  			objs = append(objs, tt.objects...)
  1069  			if tt.cluster != nil {
  1070  				objs = append(objs, tt.cluster)
  1071  			}
  1072  			fakeClient := fake.NewClientBuilder().
  1073  				WithScheme(fakeScheme).
  1074  				WithObjects(objs...).
  1075  				Build()
  1076  
  1077  			// Calls getCurrentState.
  1078  			r := &Reconciler{
  1079  				Client:                    fakeClient,
  1080  				APIReader:                 fakeClient,
  1081  				UnstructuredCachingClient: fakeClient,
  1082  				patchHelperFactory:        dryRunPatchHelperFactory(fakeClient),
  1083  			}
  1084  			got, err := r.getCurrentState(ctx, s)
  1085  
  1086  			// Checks the return error.
  1087  			if tt.wantErr {
  1088  				g.Expect(err).To(HaveOccurred())
  1089  			} else {
  1090  				g.Expect(err).ToNot(HaveOccurred())
  1091  			}
  1092  			if tt.want == nil {
  1093  				g.Expect(got).To(BeNil())
  1094  				return
  1095  			}
  1096  
  1097  			// Use EqualObject where the compared object is passed through the fakeClient. Elsewhere the Equal method is
  1098  			// good enough to establish equality.
  1099  			g.Expect(got.Cluster).To(EqualObject(tt.want.Cluster, IgnoreAutogeneratedMetadata))
  1100  			g.Expect(got.InfrastructureCluster).To(EqualObject(tt.want.InfrastructureCluster))
  1101  			g.Expect(got.ControlPlane).To(BeComparableTo(tt.want.ControlPlane), cmp.Diff(got.ControlPlane, tt.want.ControlPlane))
  1102  			g.Expect(got.MachineDeployments).To(BeComparableTo(tt.want.MachineDeployments), cmp.Diff(got.MachineDeployments, tt.want.MachineDeployments))
  1103  			g.Expect(got.MachinePools).To(BeComparableTo(tt.want.MachinePools), cmp.Diff(got.MachinePools, tt.want.MachinePools))
  1104  		})
  1105  	}
  1106  }
  1107  
  1108  func TestAlignRefAPIVersion(t *testing.T) {
  1109  	tests := []struct {
  1110  		name                     string
  1111  		templateFromClusterClass *unstructured.Unstructured
  1112  		currentRef               *corev1.ObjectReference
  1113  		want                     *corev1.ObjectReference
  1114  		wantErr                  bool
  1115  	}{
  1116  		{
  1117  			name: "Error for invalid apiVersion",
  1118  			templateFromClusterClass: &unstructured.Unstructured{Object: map[string]interface{}{
  1119  				"apiVersion": "infrastructure.cluster.x-k8s.io/v1beta1",
  1120  				"kind":       "DockerCluster",
  1121  			}},
  1122  			currentRef: &corev1.ObjectReference{
  1123  				APIVersion: "invalid/api/version",
  1124  				Kind:       "DockerCluster",
  1125  				Name:       "my-cluster-abc",
  1126  				Namespace:  metav1.NamespaceDefault,
  1127  			},
  1128  			wantErr: true,
  1129  		},
  1130  		{
  1131  			name: "Use apiVersion from ClusterClass: group and kind is the same",
  1132  			templateFromClusterClass: &unstructured.Unstructured{Object: map[string]interface{}{
  1133  				"apiVersion": "infrastructure.cluster.x-k8s.io/v1beta1",
  1134  				"kind":       "DockerCluster",
  1135  			}},
  1136  			currentRef: &corev1.ObjectReference{
  1137  				APIVersion: "infrastructure.cluster.x-k8s.io/v1beta2",
  1138  				Kind:       "DockerCluster",
  1139  				Name:       "my-cluster-abc",
  1140  				Namespace:  metav1.NamespaceDefault,
  1141  			},
  1142  			want: &corev1.ObjectReference{
  1143  				// Group & kind is the same => apiVersion is taken from ClusterClass.
  1144  				APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1",
  1145  				Kind:       "DockerCluster",
  1146  				Name:       "my-cluster-abc",
  1147  				Namespace:  metav1.NamespaceDefault,
  1148  			},
  1149  		},
  1150  		{
  1151  			name: "Use apiVersion from currentRef: kind is different",
  1152  			templateFromClusterClass: &unstructured.Unstructured{Object: map[string]interface{}{
  1153  				"apiVersion": "bootstrap.cluster.x-k8s.io/v1alpha4",
  1154  				"kind":       "DifferentConfigTemplate",
  1155  			}},
  1156  			currentRef: &corev1.ObjectReference{
  1157  				APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1",
  1158  				Kind:       "KubeadmConfigTemplate",
  1159  				Name:       "my-cluster-abc",
  1160  				Namespace:  metav1.NamespaceDefault,
  1161  			},
  1162  			want: &corev1.ObjectReference{
  1163  				// kind is different => apiVersion is taken from currentRef.
  1164  				APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1",
  1165  				Kind:       "KubeadmConfigTemplate",
  1166  				Name:       "my-cluster-abc",
  1167  				Namespace:  metav1.NamespaceDefault,
  1168  			},
  1169  		},
  1170  		{
  1171  			name: "Use apiVersion from currentRef: group is different",
  1172  			templateFromClusterClass: &unstructured.Unstructured{Object: map[string]interface{}{
  1173  				"apiVersion": "bootstrap2.cluster.x-k8s.io/v1beta1",
  1174  				"kind":       "KubeadmConfigTemplate",
  1175  			}},
  1176  			currentRef: &corev1.ObjectReference{
  1177  				APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1",
  1178  				Kind:       "KubeadmConfigTemplate",
  1179  				Name:       "my-cluster-abc",
  1180  				Namespace:  metav1.NamespaceDefault,
  1181  			},
  1182  			want: &corev1.ObjectReference{
  1183  				// group is different => apiVersion is taken from currentRef.
  1184  				APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1",
  1185  				Kind:       "KubeadmConfigTemplate",
  1186  				Name:       "my-cluster-abc",
  1187  				Namespace:  metav1.NamespaceDefault,
  1188  			},
  1189  		},
  1190  	}
  1191  	for _, tt := range tests {
  1192  		t.Run(tt.name, func(t *testing.T) {
  1193  			g := NewWithT(t)
  1194  
  1195  			got, err := alignRefAPIVersion(tt.templateFromClusterClass, tt.currentRef)
  1196  			if tt.wantErr {
  1197  				g.Expect(err).To(HaveOccurred())
  1198  				return
  1199  			}
  1200  			g.Expect(err).ToNot(HaveOccurred())
  1201  
  1202  			g.Expect(got).To(BeComparableTo(tt.want))
  1203  		})
  1204  	}
  1205  }