github.com/verrazzano/verrazzano@v1.7.1/cluster-operator/controllers/vmc/update_status_test.go (about)

     1  // Copyright (c) 2023, Oracle and/or its affiliates.
     2  // Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
     3  
     4  package vmc
     5  
     6  import (
     7  	"context"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/golang/mock/gomock"
    12  	"github.com/stretchr/testify/assert"
    13  	"github.com/verrazzano/verrazzano/cluster-operator/apis/clusters/v1alpha1"
    14  	"github.com/verrazzano/verrazzano/cluster-operator/internal/capi"
    15  	"github.com/verrazzano/verrazzano/pkg/log/vzlog"
    16  	"github.com/verrazzano/verrazzano/pkg/rancherutil"
    17  	vzv1beta1 "github.com/verrazzano/verrazzano/platform-operator/apis/verrazzano/v1beta1"
    18  	"github.com/verrazzano/verrazzano/platform-operator/constants"
    19  	"github.com/verrazzano/verrazzano/platform-operator/mocks"
    20  	corev1 "k8s.io/api/core/v1"
    21  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    22  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    23  	"k8s.io/apimachinery/pkg/runtime"
    24  	"k8s.io/apimachinery/pkg/types"
    25  	"sigs.k8s.io/cluster-api/api/v1beta1"
    26  	"sigs.k8s.io/controller-runtime/pkg/client"
    27  	"sigs.k8s.io/controller-runtime/pkg/client/fake"
    28  )
    29  
    30  const (
    31  	testCAPIClusterName                = "test-capi-cluster"
    32  	testClusterClassName               = "test-cluster-class"
    33  	testCAPINamespace                  = "c-12345"
    34  	testVMCName                        = "test-vmc"
    35  	testVMCNamespace                   = "verrazzano-mc"
    36  	fakeControlPlaneProviderAPIVersion = "controlPlaneAPIversion"
    37  	fakeControlPlaneProviderKind       = "controlPlaneKind"
    38  )
    39  
    40  // TestUpdateStatus tests the updateStatus function
    41  func TestUpdateStatus(t *testing.T) {
    42  	// clear any cached user auth tokens when the test completes
    43  	defer rancherutil.DeleteStoredTokens()
    44  
    45  	asserts := assert.New(t)
    46  	mocker := gomock.NewController(t)
    47  	mock := mocks.NewMockClient(mocker)
    48  	mockStatus := mocks.NewMockStatusWriter(mocker)
    49  	asserts.NotNil(mockStatus)
    50  
    51  	// Expect the requests for the existing VMC resource
    52  	mock.EXPECT().Get(gomock.Any(), types.NamespacedName{Namespace: constants.VerrazzanoMultiClusterNamespace, Name: testManagedCluster}, gomock.AssignableToTypeOf(&v1alpha1.VerrazzanoManagedCluster{}), gomock.Any()).
    53  		DoAndReturn(func(ctx context.Context, nsn types.NamespacedName, vmc *v1alpha1.VerrazzanoManagedCluster, opts ...client.GetOption) error {
    54  			return nil
    55  		}).AnyTimes()
    56  
    57  	// GIVEN a VMC with a status state unset and the last agent connect time set
    58  	// WHEN the updateStatus function is called
    59  	// THEN the status state is updated to pending
    60  	mock.EXPECT().Status().Return(mockStatus)
    61  	mockStatus.EXPECT().
    62  		Update(gomock.Any(), gomock.AssignableToTypeOf(&v1alpha1.VerrazzanoManagedCluster{}), gomock.Any()).
    63  		DoAndReturn(func(ctx context.Context, vmc *v1alpha1.VerrazzanoManagedCluster, opts ...client.UpdateOption) error {
    64  			asserts.Equal(v1alpha1.StatePending, vmc.Status.State)
    65  			return nil
    66  		})
    67  
    68  	vmc := v1alpha1.VerrazzanoManagedCluster{
    69  		ObjectMeta: metav1.ObjectMeta{
    70  			Name:      testManagedCluster,
    71  			Namespace: constants.VerrazzanoMultiClusterNamespace,
    72  		},
    73  		Status: v1alpha1.VerrazzanoManagedClusterStatus{
    74  			LastAgentConnectTime: &metav1.Time{
    75  				Time: time.Now(),
    76  			},
    77  		},
    78  	}
    79  	reconciler := newVMCReconciler(mock)
    80  	reconciler.log = vzlog.DefaultLogger()
    81  
    82  	err := reconciler.updateStatus(context.TODO(), &vmc)
    83  
    84  	// Validate the results
    85  	mocker.Finish()
    86  	asserts.NoError(err)
    87  
    88  	// GIVEN a VMC with a status state of pending and the last agent connect time set
    89  	// WHEN the updateStatus function is called
    90  	// THEN the status state is updated to active
    91  	mock.EXPECT().Status().Return(mockStatus)
    92  	mockStatus.EXPECT().
    93  		Update(gomock.Any(), gomock.AssignableToTypeOf(&v1alpha1.VerrazzanoManagedCluster{}), gomock.Any()).
    94  		DoAndReturn(func(ctx context.Context, vmc *v1alpha1.VerrazzanoManagedCluster, opts ...client.UpdateOption) error {
    95  			asserts.Equal(v1alpha1.StateActive, vmc.Status.State)
    96  			return nil
    97  		})
    98  
    99  	err = reconciler.updateStatus(context.TODO(), &vmc)
   100  
   101  	// Validate the results
   102  	mocker.Finish()
   103  	asserts.NoError(err)
   104  
   105  	// GIVEN a VMC with a last agent connect time set in the past
   106  	// WHEN the updateStatus function is called
   107  	// THEN the status state is updated to inactive
   108  	past := metav1.Unix(0, 0)
   109  	vmc.Status.LastAgentConnectTime = &past
   110  
   111  	// Expect the Rancher registration status to be set appropriately
   112  	mock.EXPECT().Status().Return(mockStatus)
   113  	mockStatus.EXPECT().
   114  		Update(gomock.Any(), gomock.AssignableToTypeOf(&v1alpha1.VerrazzanoManagedCluster{}), gomock.Any()).
   115  		DoAndReturn(func(ctx context.Context, vmc *v1alpha1.VerrazzanoManagedCluster, opts ...client.UpdateOption) error {
   116  			asserts.Equal(v1alpha1.StateInactive, vmc.Status.State)
   117  			return nil
   118  		})
   119  
   120  	err = reconciler.updateStatus(context.TODO(), &vmc)
   121  
   122  	// Validate the results
   123  	mocker.Finish()
   124  	asserts.NoError(err)
   125  }
   126  
   127  // TestUpdateStatusImported tests that updateStatus correctly sets the VMC's status.imported field
   128  func TestUpdateStatusImported(t *testing.T) {
   129  	a := assert.New(t)
   130  	scheme := runtime.NewScheme()
   131  	_ = v1alpha1.AddToScheme(scheme)
   132  	_ = v1beta1.AddToScheme(scheme)
   133  
   134  	defaultCAPIClientFunc := getCAPIClientFunc
   135  	defer func() { getCAPIClientFunc = defaultCAPIClientFunc }()
   136  
   137  	tests := []struct {
   138  		testName         string
   139  		vmc              *v1alpha1.VerrazzanoManagedCluster
   140  		expectedImported bool
   141  	}{
   142  		{
   143  			"imported cluster",
   144  			newVMC(testVMCName, testVMCNamespace),
   145  			true,
   146  		},
   147  		{
   148  			"ClusterAPI cluster",
   149  			newVMCWithClusterRef(testVMCName, testVMCNamespace, testCAPIClusterName, testCAPINamespace),
   150  			false,
   151  		},
   152  	}
   153  
   154  	for _, tt := range tests {
   155  		t.Run(tt.testName, func(t *testing.T) {
   156  			// GIVEN a VMC with either a nil or non-nil ClusterRef
   157  			// WHEN updateStatus is called
   158  			// THEN expect the VMC's status imported field to be set
   159  			getCAPIClientFunc = fakeCAPIClient
   160  			ctx := context.TODO()
   161  			fakeClient := fake.NewClientBuilder().WithScheme(scheme).WithObjects(tt.vmc).Build()
   162  			r := &VerrazzanoManagedClusterReconciler{
   163  				Client: fakeClient,
   164  				log:    vzlog.DefaultLogger(),
   165  			}
   166  
   167  			err := r.updateStatus(ctx, tt.vmc)
   168  			a.NoError(err)
   169  
   170  			retrievedVMC := &v1alpha1.VerrazzanoManagedCluster{}
   171  			err = r.Get(ctx, types.NamespacedName{Name: tt.vmc.Name, Namespace: tt.vmc.Namespace}, retrievedVMC)
   172  			a.NoError(err)
   173  			a.Equal(tt.expectedImported, *retrievedVMC.Status.Imported)
   174  		})
   175  	}
   176  }
   177  
   178  // TestUpdateProvider tests that updateProvider correctly sets the VMC's status.provider field
   179  func TestUpdateProvider(t *testing.T) {
   180  	a := assert.New(t)
   181  	scheme := runtime.NewScheme()
   182  	_ = v1alpha1.AddToScheme(scheme)
   183  	_ = v1beta1.AddToScheme(scheme)
   184  
   185  	tests := []struct {
   186  		testName             string
   187  		vmc                  *v1alpha1.VerrazzanoManagedCluster
   188  		capiCluster          *v1beta1.Cluster
   189  		clusterClass         *v1beta1.ClusterClass
   190  		controlPlaneProvider string
   191  		infraProvider        string
   192  		expectedVMCProvider  string
   193  		err                  error
   194  	}{
   195  		{
   196  			"imported cluster",
   197  			newVMC(testVMCName, testVMCNamespace),
   198  			nil,
   199  			nil,
   200  			"",
   201  			"",
   202  			importedProviderDisplayName,
   203  			nil,
   204  		},
   205  		{
   206  			"CAPI Cluster without ClusterClass and a generic provider",
   207  			newVMCWithClusterRef(testVMCName, testVMCNamespace, testCAPIClusterName, testCAPINamespace),
   208  			newCAPICluster(testCAPIClusterName, testCAPINamespace),
   209  			nil,
   210  			"SomeControlPlaneProvider",
   211  			"SomeInfraProvider",
   212  			"SomeControlPlaneProvider on SomeInfraProvider Infrastructure",
   213  			nil,
   214  		},
   215  		{
   216  			"CAPI Cluster without ClusterClass and Oracle OKE Provider",
   217  			newVMCWithClusterRef(testVMCName, testVMCNamespace, testCAPIClusterName, testCAPINamespace),
   218  			newCAPICluster(testCAPIClusterName, testCAPINamespace),
   219  			nil,
   220  			capi.OKEControlPlaneProvider,
   221  			capi.OKEInfrastructureProvider,
   222  			okeProviderDisplayName,
   223  			nil,
   224  		},
   225  		{
   226  			"CAPI Cluster with ClusterClass and Oracle OCNE Provider",
   227  			newVMCWithClusterRef(testVMCName, testVMCNamespace, testCAPIClusterName, testCAPINamespace),
   228  			newCAPIClusterWithClassReference(testCAPIClusterName, testClusterClassName, testCAPINamespace),
   229  			newCAPIClusterClass(testClusterClassName, testCAPINamespace),
   230  			capi.OCNEControlPlaneProvider,
   231  			capi.OCNEInfrastructureProvider,
   232  			ocneProviderDisplayName,
   233  			nil,
   234  		},
   235  	}
   236  
   237  	for _, tt := range tests {
   238  		t.Run(tt.testName, func(t *testing.T) {
   239  			objects := []client.Object{tt.vmc}
   240  			if tt.capiCluster != nil {
   241  				if tt.clusterClass == nil {
   242  					tt.capiCluster.Spec.InfrastructureRef.Kind = tt.infraProvider
   243  					tt.capiCluster.Spec.ControlPlaneRef.Kind = tt.controlPlaneProvider
   244  				}
   245  				objects = append(objects, tt.capiCluster)
   246  			}
   247  			if tt.clusterClass != nil {
   248  				tt.clusterClass.Spec.Infrastructure.Ref.Kind = tt.infraProvider
   249  				tt.clusterClass.Spec.ControlPlane.Ref.Kind = tt.controlPlaneProvider
   250  				objects = append(objects, tt.clusterClass)
   251  			}
   252  			fakeClient := fake.NewClientBuilder().WithScheme(scheme).WithObjects(objects...).Build()
   253  			r := &VerrazzanoManagedClusterReconciler{
   254  				Client: fakeClient,
   255  				log:    vzlog.DefaultLogger(),
   256  			}
   257  
   258  			// WHEN updateProvider is called
   259  			// THEN expect the VMC's status provider to be tt.expectedVMCProvider
   260  			err := r.updateProvider(tt.vmc)
   261  
   262  			a.Equal(tt.err, err)
   263  			a.Equal(tt.expectedVMCProvider, tt.vmc.Status.Provider)
   264  		})
   265  	}
   266  }
   267  
   268  // TestUpdateStateCAPI tests that updateState correctly updates the status.state of the VMC when
   269  // the VMC has a reference to a CAPI cluster
   270  func TestUpdateStateCAPI(t *testing.T) {
   271  	a := assert.New(t)
   272  	scheme := runtime.NewScheme()
   273  	_ = v1alpha1.AddToScheme(scheme)
   274  	_ = v1beta1.AddToScheme(scheme)
   275  	vmc := newVMCWithClusterRef(testVMCName, testVMCNamespace, testCAPIClusterName, testCAPINamespace)
   276  
   277  	tests := []struct {
   278  		testName         string
   279  		capiCluster      *v1beta1.Cluster
   280  		capiPhase        string
   281  		expectedVMCState string
   282  		err              error
   283  	}{
   284  		{
   285  			"valid CAPI phase",
   286  			newCAPICluster(testCAPIClusterName, testCAPINamespace),
   287  			string(v1alpha1.StateProvisioned),
   288  			string(v1alpha1.StateProvisioned),
   289  			nil,
   290  		},
   291  		{
   292  			"empty CAPI phase",
   293  			newCAPICluster(testCAPIClusterName, testCAPINamespace),
   294  			"",
   295  			string(v1alpha1.StateUnknown),
   296  			nil,
   297  		},
   298  		{
   299  			"nonexistent CAPI cluster",
   300  			nil,
   301  			"",
   302  			"",
   303  			nil,
   304  		},
   305  	}
   306  
   307  	for _, tt := range tests {
   308  		t.Run(tt.testName, func(t *testing.T) {
   309  			// GIVEN a CAPI cluster with a phase of tt.capiPhase
   310  			//   and a VMC with a clusterRef to that CAPI cluster and an unset status state
   311  			// WHEN updateState is called
   312  			// THEN expect the VMC's status state to be tt.expectedVMCState
   313  			vmc.Status.State = ""
   314  			objects := []client.Object{vmc}
   315  			if tt.capiCluster != nil {
   316  				tt.capiCluster.Status.Phase = tt.capiPhase
   317  				objects = append(objects, tt.capiCluster)
   318  			}
   319  			fakeClient := fake.NewClientBuilder().WithScheme(scheme).WithObjects(objects...).Build()
   320  			r := &VerrazzanoManagedClusterReconciler{
   321  				Client: fakeClient,
   322  				log:    vzlog.DefaultLogger(),
   323  			}
   324  
   325  			err := r.updateState(vmc)
   326  
   327  			a.Equal(tt.err, err)
   328  			a.Equal(tt.expectedVMCState, string(vmc.Status.State))
   329  		})
   330  	}
   331  }
   332  
   333  // TestShouldUpdateK8sVersion tests that shouldUpdateK8sVersion correctly determines when the VMC controller should
   334  // update the VMC's Kubernetes version.
   335  func TestShouldUpdateK8sVersion(t *testing.T) {
   336  	a := assert.New(t)
   337  	scheme := runtime.NewScheme()
   338  	_ = v1alpha1.AddToScheme(scheme)
   339  	_ = v1beta1.AddToScheme(scheme)
   340  	_ = vzv1beta1.AddToScheme(scheme)
   341  
   342  	defaultCAPIClientFunc := getCAPIClientFunc
   343  	defer func() { getCAPIClientFunc = defaultCAPIClientFunc }()
   344  
   345  	tests := []struct {
   346  		testName      string
   347  		setClusterRef bool
   348  		vzOnCAPI      bool
   349  		shouldUpdate  bool
   350  		err           error
   351  	}{
   352  		{
   353  			"non-ClusterAPI cluster",
   354  			false,
   355  			false,
   356  			false,
   357  			nil,
   358  		},
   359  		{
   360  			"ClusterAPI cluster without Verrazzano",
   361  			true,
   362  			false,
   363  			true,
   364  			nil,
   365  		},
   366  		{
   367  			"ClusterAPI cluster with Verrazzano",
   368  			true,
   369  			true,
   370  			false,
   371  			nil,
   372  		},
   373  	}
   374  
   375  	for _, tt := range tests {
   376  		t.Run(tt.testName, func(t *testing.T) {
   377  			var vmc *v1alpha1.VerrazzanoManagedCluster
   378  			if tt.setClusterRef {
   379  				if tt.vzOnCAPI {
   380  					getCAPIClientFunc = fakeCAPIClientWithVZ
   381  				} else {
   382  					getCAPIClientFunc = fakeCAPIClient
   383  				}
   384  				vmc = newVMCWithClusterRef(testVMCName, testVMCNamespace, testCAPIClusterName, testCAPINamespace)
   385  			} else {
   386  				vmc = newVMC(testVMCName, testVMCNamespace)
   387  			}
   388  			fakeClient := fake.NewClientBuilder().WithScheme(scheme).WithObjects(vmc).Build()
   389  			r := &VerrazzanoManagedClusterReconciler{
   390  				Client: fakeClient,
   391  				Scheme: scheme,
   392  				log:    vzlog.DefaultLogger(),
   393  			}
   394  
   395  			shouldUpdate, err := r.shouldUpdateK8sVersion(vmc)
   396  			a.Equal(tt.err, err)
   397  			a.Equal(tt.shouldUpdate, shouldUpdate)
   398  		})
   399  	}
   400  }
   401  
   402  // TestUpdateK8sVersionUsingCAPI tests that updateK8sVersionUsingCAPI correctly updates the Kubernetes version
   403  // on the VMC
   404  func TestUpdateK8sVersionUsingCAPI(t *testing.T) {
   405  	const cpProviderName = "SomeControlPlaneProvider"
   406  	const expectedK8sVersion = "v9.99.9"
   407  
   408  	a := assert.New(t)
   409  	scheme := runtime.NewScheme()
   410  	_ = v1alpha1.AddToScheme(scheme)
   411  	_ = v1beta1.AddToScheme(scheme)
   412  
   413  	// Create a VMC that references a CAPI cluster
   414  	vmc := newVMCWithClusterRef(testVMCName, testVMCNamespace, testCAPIClusterName, testCAPINamespace)
   415  	// The CAPI cluster references some control plane provider
   416  	cluster := newCAPICluster(testCAPIClusterName, testCAPINamespace)
   417  	cluster.Spec.ControlPlaneRef = &corev1.ObjectReference{
   418  		Name:       cpProviderName,
   419  		Namespace:  testCAPINamespace,
   420  		APIVersion: fakeControlPlaneProviderAPIVersion,
   421  		Kind:       fakeControlPlaneProviderKind,
   422  	}
   423  	// Create the control plane provider CR on the fake client
   424  	cpProvider := newFakeControlPlaneProvider(testCAPINamespace, cpProviderName, expectedK8sVersion)
   425  	fakeClient := fake.NewClientBuilder().WithScheme(scheme).WithObjects(vmc, cluster, cpProvider).Build()
   426  	r := &VerrazzanoManagedClusterReconciler{
   427  		Client: fakeClient,
   428  		Scheme: scheme,
   429  		log:    vzlog.DefaultLogger(),
   430  	}
   431  
   432  	err := r.updateK8sVersionUsingCAPI(vmc)
   433  	a.Nil(err)
   434  	a.Equal(expectedK8sVersion, vmc.Status.Kubernetes.Version)
   435  }
   436  
   437  // fakeCAPIClient returns a fake client for a CAPI workload cluster
   438  func fakeCAPIClient(ctx context.Context, cli client.Client, cluster types.NamespacedName, scheme *runtime.Scheme) (client.Client, error) {
   439  	return fake.NewClientBuilder().WithScheme(scheme).WithObjects().Build(), nil
   440  }
   441  
   442  // fakeCAPIClient returns a fake client for a CAPI workload cluster
   443  func fakeCAPIClientWithVZ(ctx context.Context, cli client.Client, cluster types.NamespacedName, scheme *runtime.Scheme) (client.Client, error) {
   444  	vz := vzv1beta1.Verrazzano{}
   445  	return fake.NewClientBuilder().WithScheme(scheme).WithObjects(&vz).Build(), nil
   446  }
   447  
   448  // newVMCWithClusterRef returns a VMC struct pointer, with the status.clusterRef field set to point to a CAPI Cluster
   449  func newVMCWithClusterRef(name, namespace, clusterName, clusterNamespace string) *v1alpha1.VerrazzanoManagedCluster {
   450  	vmc := &v1alpha1.VerrazzanoManagedCluster{
   451  		ObjectMeta: metav1.ObjectMeta{
   452  			Name:      name,
   453  			Namespace: namespace,
   454  		},
   455  		Status: v1alpha1.VerrazzanoManagedClusterStatus{
   456  			ClusterRef: &v1alpha1.ClusterReference{
   457  				Name:       clusterName,
   458  				Namespace:  clusterNamespace,
   459  				APIVersion: "cluster.x-k8s.io/v1beta1",
   460  				Kind:       "Cluster",
   461  			},
   462  		},
   463  	}
   464  	return vmc
   465  }
   466  
   467  // newVMC returns a VMC struct pointer
   468  func newVMC(name, namespace string) *v1alpha1.VerrazzanoManagedCluster {
   469  	vmc := &v1alpha1.VerrazzanoManagedCluster{
   470  		ObjectMeta: metav1.ObjectMeta{
   471  			Name:      name,
   472  			Namespace: namespace,
   473  		},
   474  	}
   475  	return vmc
   476  }
   477  
   478  // newCAPICluster returns a CAPI Cluster
   479  func newCAPICluster(name, namespace string) *v1beta1.Cluster {
   480  	cluster := v1beta1.Cluster{
   481  		TypeMeta: metav1.TypeMeta{
   482  			Kind:       "Cluster",
   483  			APIVersion: "cluster.x-k8s.io/v1beta1",
   484  		},
   485  		ObjectMeta: metav1.ObjectMeta{
   486  			Name:      name,
   487  			Namespace: namespace,
   488  		},
   489  		Spec: v1beta1.ClusterSpec{
   490  			InfrastructureRef: &corev1.ObjectReference{},
   491  			ControlPlaneRef:   &corev1.ObjectReference{},
   492  		},
   493  	}
   494  	return &cluster
   495  }
   496  
   497  // newCAPIClusterWithClassReference returns a CAPI Cluster which references a ClusterClass
   498  func newCAPIClusterWithClassReference(name, className, namespace string) *v1beta1.Cluster {
   499  	cluster := v1beta1.Cluster{
   500  		TypeMeta: metav1.TypeMeta{
   501  			Kind:       "Cluster",
   502  			APIVersion: "cluster.x-k8s.io/v1beta1",
   503  		},
   504  		ObjectMeta: metav1.ObjectMeta{
   505  			Name:      name,
   506  			Namespace: namespace,
   507  		},
   508  		Spec: v1beta1.ClusterSpec{
   509  			Topology: &v1beta1.Topology{
   510  				Class: className,
   511  			},
   512  		},
   513  	}
   514  	return &cluster
   515  }
   516  
   517  // newCAPIClusterClass returns a CAPI ClusterClass
   518  func newCAPIClusterClass(name, namespace string) *v1beta1.ClusterClass {
   519  	clusterClass := v1beta1.ClusterClass{
   520  		TypeMeta: metav1.TypeMeta{
   521  			Kind:       "ClusterClass",
   522  			APIVersion: "cluster.x-k8s.io/v1beta1",
   523  		},
   524  		ObjectMeta: metav1.ObjectMeta{
   525  			Name:      name,
   526  			Namespace: namespace,
   527  		},
   528  		Spec: v1beta1.ClusterClassSpec{
   529  			Infrastructure: v1beta1.LocalObjectTemplate{
   530  				Ref: &corev1.ObjectReference{},
   531  			},
   532  			ControlPlane: v1beta1.ControlPlaneClass{
   533  				LocalObjectTemplate: v1beta1.LocalObjectTemplate{
   534  					Ref: &corev1.ObjectReference{},
   535  				},
   536  			},
   537  		},
   538  	}
   539  	return &clusterClass
   540  }
   541  
   542  // newFakeControlPlaneProvider returns a pointer to an unstructured object, representing a control
   543  // plane provider CR
   544  func newFakeControlPlaneProvider(namespace, name, k8sVersion string) *unstructured.Unstructured {
   545  	return &unstructured.Unstructured{
   546  		Object: map[string]interface{}{
   547  			"apiVersion": fakeControlPlaneProviderAPIVersion,
   548  			"kind":       fakeControlPlaneProviderKind,
   549  			"metadata": map[string]interface{}{
   550  				"namespace": namespace,
   551  				"name":      name,
   552  			},
   553  			"status": map[string]interface{}{
   554  				"version": k8sVersion,
   555  			},
   556  		},
   557  	}
   558  }