github.com/verrazzano/verrazzano@v1.7.0/cluster-operator/controllers/rancher/rancher_cluster_controller_test.go (about)

     1  // Copyright (c) 2022, 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 rancher
     5  
     6  import (
     7  	"context"
     8  	"testing"
     9  
    10  	"github.com/stretchr/testify/assert"
    11  	"go.uber.org/zap"
    12  	"k8s.io/apimachinery/pkg/api/errors"
    13  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    14  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    15  	"k8s.io/apimachinery/pkg/runtime"
    16  	"k8s.io/apimachinery/pkg/types"
    17  	clientgoscheme "k8s.io/client-go/kubernetes/scheme"
    18  	ctrl "sigs.k8s.io/controller-runtime"
    19  	"sigs.k8s.io/controller-runtime/pkg/client"
    20  	"sigs.k8s.io/controller-runtime/pkg/client/fake"
    21  
    22  	clustersv1alpha1 "github.com/verrazzano/verrazzano/cluster-operator/apis/clusters/v1alpha1"
    23  	vzconst "github.com/verrazzano/verrazzano/pkg/constants"
    24  )
    25  
    26  const (
    27  	clusterName = "c-m-hcknpvs7"
    28  	displayName = "unit-test-cluster"
    29  )
    30  
    31  // GIVEN a Rancher cluster resource is created
    32  // WHEN  the reconciler runs
    33  // THEN  a VMC is created and the cluster id is set in the status
    34  func TestReconcileCreateVMC(t *testing.T) {
    35  	asserts := assert.New(t)
    36  
    37  	fakeClient := fake.NewClientBuilder().WithScheme(newScheme()).WithObjects(newCattleCluster(clusterName, displayName)).Build()
    38  	reconciler := newRancherClusterReconciler(fakeClient)
    39  	request := newRequest(clusterName)
    40  
    41  	_, err := reconciler.Reconcile(context.TODO(), request)
    42  	asserts.NoError(err)
    43  
    44  	// expect that a VMC was created and that the cluster id is set in the status
    45  	vmc := &clustersv1alpha1.VerrazzanoManagedCluster{}
    46  	err = fakeClient.Get(context.TODO(), types.NamespacedName{Name: displayName, Namespace: vzconst.VerrazzanoMultiClusterNamespace}, vmc)
    47  	asserts.NoError(err)
    48  	asserts.Equal(clusterName, vmc.Status.RancherRegistration.ClusterID)
    49  }
    50  
    51  // GIVEN a Rancher cluster resource is created with labels
    52  // AND   the user has specified a cluster selector that matches the labels
    53  // WHEN  the reconciler runs
    54  // THEN  a VMC is created and the cluster id is set in the status
    55  func TestReconcileWithClusterSelector(t *testing.T) {
    56  	asserts := assert.New(t)
    57  
    58  	const (
    59  		labelName  = "test-label"
    60  		labelValue = "test-label-value"
    61  	)
    62  
    63  	cluster := newCattleCluster(clusterName, displayName)
    64  	cluster.SetLabels(map[string]string{labelName: labelValue})
    65  	fakeClient := fake.NewClientBuilder().WithScheme(newScheme()).WithObjects(cluster).Build()
    66  
    67  	reconciler := newRancherClusterReconciler(fakeClient)
    68  	reconciler.ClusterSelector = &metav1.LabelSelector{
    69  		MatchExpressions: []metav1.LabelSelectorRequirement{
    70  			{
    71  				Key:      labelName,
    72  				Operator: metav1.LabelSelectorOpIn,
    73  				Values:   []string{labelValue},
    74  			},
    75  		},
    76  	}
    77  	request := newRequest(clusterName)
    78  
    79  	_, err := reconciler.Reconcile(context.TODO(), request)
    80  	asserts.NoError(err)
    81  
    82  	// expect that a VMC was created and that the cluster id is set in the status
    83  	vmc := &clustersv1alpha1.VerrazzanoManagedCluster{}
    84  	err = fakeClient.Get(context.TODO(), types.NamespacedName{Name: displayName, Namespace: vzconst.VerrazzanoMultiClusterNamespace}, vmc)
    85  	asserts.NoError(err)
    86  	asserts.Equal(clusterName, vmc.Status.RancherRegistration.ClusterID)
    87  }
    88  
    89  // GIVEN a Rancher cluster resource is created with labels
    90  // AND   the user has specified a cluster selector that DOES NOT match the labels
    91  // WHEN  the reconciler runs
    92  // THEN  a VMC is not created
    93  func TestReconcileClusterSelectorMismatch(t *testing.T) {
    94  	asserts := assert.New(t)
    95  
    96  	const (
    97  		labelName  = "test-label"
    98  		labelValue = "test-label-value"
    99  	)
   100  
   101  	cluster := newCattleCluster(clusterName, displayName)
   102  	cluster.SetLabels(map[string]string{"label": "does-not-match"})
   103  	fakeClient := fake.NewClientBuilder().WithScheme(newScheme()).WithObjects(cluster).Build()
   104  
   105  	reconciler := newRancherClusterReconciler(fakeClient)
   106  	reconciler.ClusterSelector = &metav1.LabelSelector{
   107  		MatchExpressions: []metav1.LabelSelectorRequirement{
   108  			{
   109  				Key:      labelName,
   110  				Operator: metav1.LabelSelectorOpIn,
   111  				Values:   []string{labelValue},
   112  			},
   113  		},
   114  	}
   115  	request := newRequest(clusterName)
   116  
   117  	_, err := reconciler.Reconcile(context.TODO(), request)
   118  	asserts.NoError(err)
   119  
   120  	// expect that a VMC was not created
   121  	vmc := &clustersv1alpha1.VerrazzanoManagedCluster{}
   122  	err = fakeClient.Get(context.TODO(), types.NamespacedName{Name: displayName, Namespace: vzconst.VerrazzanoMultiClusterNamespace}, vmc)
   123  	asserts.Error(err)
   124  	asserts.True(errors.IsNotFound(err))
   125  }
   126  
   127  // GIVEN a Rancher cluster resource is created
   128  // AND   Rancher cluster sync is disabled
   129  // WHEN  the reconciler runs
   130  // THEN  a VMC is not created
   131  func TestReconcileClusterSyncDisabled(t *testing.T) {
   132  	asserts := assert.New(t)
   133  
   134  	fakeClient := fake.NewClientBuilder().WithScheme(newScheme()).WithObjects(newCattleCluster(clusterName, displayName)).Build()
   135  	reconciler := newRancherClusterReconciler(fakeClient)
   136  	reconciler.ClusterSyncEnabled = false
   137  	request := newRequest(clusterName)
   138  
   139  	_, err := reconciler.Reconcile(context.TODO(), request)
   140  	asserts.NoError(err)
   141  
   142  	// expect that a VMC was not created
   143  	vmc := &clustersv1alpha1.VerrazzanoManagedCluster{}
   144  	err = fakeClient.Get(context.TODO(), types.NamespacedName{Name: displayName, Namespace: vzconst.VerrazzanoMultiClusterNamespace}, vmc)
   145  	asserts.Error(err)
   146  	asserts.True(errors.IsNotFound(err))
   147  }
   148  
   149  // GIVEN a Rancher cluster resource is created and a VMC already exists for the cluster
   150  // WHEN  the reconciler runs
   151  // THEN  the VMC is updated and the cluster id is set in the status
   152  func TestReconcileCreateVMCAlreadyExists(t *testing.T) {
   153  	asserts := assert.New(t)
   154  
   155  	vmc := newVMC(displayName)
   156  	fakeClient := fake.NewClientBuilder().WithScheme(newScheme()).WithObjects(newCattleCluster(clusterName, displayName), vmc).Build()
   157  	reconciler := newRancherClusterReconciler(fakeClient)
   158  	request := newRequest(clusterName)
   159  
   160  	_, err := reconciler.Reconcile(context.TODO(), request)
   161  	asserts.NoError(err)
   162  
   163  	// expect the VMC still exists and that the cluster id is set in the status
   164  	err = fakeClient.Get(context.TODO(), types.NamespacedName{Name: displayName, Namespace: vzconst.VerrazzanoMultiClusterNamespace}, vmc)
   165  	asserts.NoError(err)
   166  	asserts.Equal(clusterName, vmc.Status.RancherRegistration.ClusterID)
   167  }
   168  
   169  // TestReconcileDeleteVMC tests reconciling and deleting VMCs.
   170  func TestReconcileDeleteVMC(t *testing.T) {
   171  	asserts := assert.New(t)
   172  
   173  	// GIVEN a Rancher cluster resource is being deleted
   174  	// AND   cluster sync is enabled
   175  	// WHEN  the reconciler runs
   176  	// THEN  the corresponding VMC is deleted
   177  	cluster := newCattleCluster(clusterName, displayName)
   178  	now := metav1.Now()
   179  	cluster.SetDeletionTimestamp(&now)
   180  	cluster.SetFinalizers([]string{finalizerName})
   181  	vmc := newVMC(displayName)
   182  	vmc.Status.RancherRegistration.ClusterID = clusterName
   183  	fakeClient := fake.NewClientBuilder().WithScheme(newScheme()).WithObjects(cluster, vmc).Build()
   184  
   185  	reconciler := newRancherClusterReconciler(fakeClient)
   186  	request := newRequest(clusterName)
   187  
   188  	_, err := reconciler.Reconcile(context.TODO(), request)
   189  	asserts.NoError(err)
   190  
   191  	// expect that the VMC was deleted
   192  	err = fakeClient.Get(context.TODO(), types.NamespacedName{Name: displayName, Namespace: vzconst.VerrazzanoMultiClusterNamespace}, vmc)
   193  	asserts.Error(err)
   194  	asserts.True(errors.IsNotFound(err))
   195  
   196  	// since the last finalizer was removed from the Rancher cluster, the cluster should be gone as well
   197  	cluster = &unstructured.Unstructured{}
   198  	cluster.SetGroupVersionKind(gvk)
   199  	err = fakeClient.Get(context.TODO(), types.NamespacedName{Name: clusterName}, cluster)
   200  	asserts.Error(err)
   201  	asserts.True(errors.IsNotFound(err))
   202  
   203  	// GIVEN a Rancher cluster resource is being deleted
   204  	// AND   cluster sync is disabled
   205  	// WHEN  the reconciler runs
   206  	// THEN  the corresponding VMC is deleted
   207  	cluster = newCattleCluster(clusterName, displayName)
   208  	cluster.SetDeletionTimestamp(&now)
   209  	cluster.SetFinalizers([]string{finalizerName})
   210  	vmc = newVMC(displayName)
   211  	vmc.Status.RancherRegistration.ClusterID = clusterName
   212  	fakeClient = fake.NewClientBuilder().WithScheme(newScheme()).WithObjects(cluster, vmc).Build()
   213  
   214  	reconciler = newRancherClusterReconciler(fakeClient)
   215  	// disable cluster sync, the VMC (if it exists) should be deleted even if this flag is false
   216  	reconciler.ClusterSyncEnabled = false
   217  	request = newRequest(clusterName)
   218  
   219  	_, err = reconciler.Reconcile(context.TODO(), request)
   220  	asserts.NoError(err)
   221  
   222  	// expect that the VMC was deleted
   223  	err = fakeClient.Get(context.TODO(), types.NamespacedName{Name: displayName, Namespace: vzconst.VerrazzanoMultiClusterNamespace}, vmc)
   224  	asserts.Error(err)
   225  	asserts.True(errors.IsNotFound(err))
   226  
   227  	// since the last finalizer was removed from the Rancher cluster, the cluster should be gone as well
   228  	cluster = &unstructured.Unstructured{}
   229  	cluster.SetGroupVersionKind(gvk)
   230  	err = fakeClient.Get(context.TODO(), types.NamespacedName{Name: clusterName}, cluster)
   231  	asserts.Error(err)
   232  	asserts.True(errors.IsNotFound(err))
   233  }
   234  
   235  // GIVEN a Rancher cluster resource is being deleted and the VMC does not exist
   236  // WHEN  the reconciler runs
   237  // THEN  no error is returned
   238  func TestReconcileDeleteVMCNotFound(t *testing.T) {
   239  	asserts := assert.New(t)
   240  
   241  	cluster := newCattleCluster(clusterName, displayName)
   242  	now := metav1.Now()
   243  	cluster.SetDeletionTimestamp(&now)
   244  	cluster.SetFinalizers([]string{finalizerName})
   245  	fakeClient := fake.NewClientBuilder().WithScheme(newScheme()).WithObjects(cluster).Build()
   246  	reconciler := newRancherClusterReconciler(fakeClient)
   247  	request := newRequest(clusterName)
   248  
   249  	_, err := reconciler.Reconcile(context.TODO(), request)
   250  	asserts.NoError(err)
   251  }
   252  
   253  // GIVEN a Rancher cluster resource has been deleted
   254  // WHEN  the reconciler runs
   255  // THEN  no error is returned
   256  func TestReconcileClusterGone(t *testing.T) {
   257  	asserts := assert.New(t)
   258  
   259  	fakeClient := fake.NewClientBuilder().WithScheme(newScheme()).Build()
   260  	reconciler := newRancherClusterReconciler(fakeClient)
   261  	request := newRequest(clusterName)
   262  
   263  	_, err := reconciler.Reconcile(context.TODO(), request)
   264  	asserts.NoError(err)
   265  }
   266  
   267  // GIVEN the local Rancher cluster resource is being reconciled
   268  // WHEN  the reconciler runs
   269  // THEN  no VMC is created
   270  func TestReconcileLocalCluster(t *testing.T) {
   271  	asserts := assert.New(t)
   272  
   273  	fakeClient := fake.NewClientBuilder().WithScheme(newScheme()).WithObjects(newCattleCluster(localClusterName, localClusterName)).Build()
   274  	reconciler := newRancherClusterReconciler(fakeClient)
   275  	request := newRequest(clusterName)
   276  
   277  	_, err := reconciler.Reconcile(context.TODO(), request)
   278  	asserts.NoError(err)
   279  
   280  	// expect no VMC created for the local cluster
   281  	vmc := &clustersv1alpha1.VerrazzanoManagedCluster{}
   282  	err = fakeClient.Get(context.TODO(), types.NamespacedName{Name: localClusterName, Namespace: vzconst.VerrazzanoMultiClusterNamespace}, vmc)
   283  	asserts.True(errors.IsNotFound(err))
   284  }
   285  
   286  // GIVEN a GVK exists for cattle clusters resources
   287  // WHEN  we fetch the client object for the cattle clusters GVK
   288  // THEN  the returned client object is not nil
   289  func TestCattleClusterClientObject(t *testing.T) {
   290  	asserts := assert.New(t)
   291  	obj := CattleClusterClientObject()
   292  	asserts.NotNil(obj)
   293  }
   294  
   295  func TestParseClusterErrorCases(t *testing.T) {
   296  	// GIVEN a cluster resource with no spec displayName field set
   297  	// WHEN  we attempt to get the displayName from the cluster object
   298  	// THEN  the expected error is returned
   299  	asserts := assert.New(t)
   300  	cluster := &unstructured.Unstructured{}
   301  	cluster.SetGroupVersionKind(gvk)
   302  	reconciler := newRancherClusterReconciler(nil)
   303  	_, err := reconciler.getClusterDisplayName(cluster)
   304  	asserts.ErrorContains(err, "Could not find spec displayName field")
   305  
   306  	// GIVEN a cluster resource with a bad type for the spec field
   307  	// WHEN  we attempt to get the displayName from the cluster object
   308  	// THEN  the expected error is returned
   309  	unstructured.SetNestedField(cluster.Object, true, "spec")
   310  	_, err = reconciler.getClusterDisplayName(cluster)
   311  	asserts.ErrorContains(err, ".spec.displayName accessor error")
   312  }
   313  
   314  // TestDeleteVMC tests the DeleteVMC function
   315  func TestDeleteVMC(t *testing.T) {
   316  	asserts := assert.New(t)
   317  	cluster := newCattleCluster(clusterName, displayName)
   318  	now := metav1.Now()
   319  	cluster.SetDeletionTimestamp(&now)
   320  	cluster.SetFinalizers([]string{finalizerName})
   321  	vmc := newVMC(displayName)
   322  	vmc.Status.RancherRegistration.ClusterID = clusterName
   323  	fakeClient := fake.NewClientBuilder().WithScheme(newScheme()).WithObjects(cluster, vmc).Build()
   324  	reconciler := newRancherClusterReconciler(fakeClient)
   325  
   326  	// GIVEN a VMC exists
   327  	// WHEN DeleteVMC is called
   328  	// THEN the VMC is deleted
   329  	err := reconciler.DeleteVMC(cluster)
   330  	asserts.NoError(err)
   331  
   332  	// expect that the VMC was deleted
   333  	err = fakeClient.Get(context.TODO(), types.NamespacedName{Name: displayName, Namespace: vzconst.VerrazzanoMultiClusterNamespace}, vmc)
   334  	asserts.True(errors.IsNotFound(err))
   335  	// GIVEN no VMC exists
   336  	// WHEN DeleteVMC is called
   337  	// THEN no error is returned
   338  	err = reconciler.DeleteVMC(cluster)
   339  	asserts.NoError(err)
   340  }
   341  
   342  func newRequest(name string) ctrl.Request {
   343  	return ctrl.Request{
   344  		NamespacedName: types.NamespacedName{
   345  			Name: name,
   346  		},
   347  	}
   348  }
   349  
   350  func newRancherClusterReconciler(c client.Client) RancherClusterReconciler {
   351  	return RancherClusterReconciler{
   352  		Client:             c,
   353  		Scheme:             newScheme(),
   354  		ClusterSyncEnabled: true,
   355  		Log:                zap.S(),
   356  	}
   357  }
   358  
   359  func newScheme() *runtime.Scheme {
   360  	scheme := runtime.NewScheme()
   361  	clientgoscheme.AddToScheme(scheme)
   362  	clustersv1alpha1.AddToScheme(scheme)
   363  	return scheme
   364  }
   365  
   366  func newCattleCluster(name, displayName string) *unstructured.Unstructured {
   367  	cluster := &unstructured.Unstructured{}
   368  	cluster.SetGroupVersionKind(gvk)
   369  	cluster.SetName(name)
   370  	unstructured.SetNestedField(cluster.Object, displayName, "spec", "displayName")
   371  	return cluster
   372  }