github.com/verrazzano/verrazzano@v1.7.0/platform-operator/controllers/configmaps/overrides/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 overrides
     5  
     6  import (
     7  	"context"
     8  	vzstatus "github.com/verrazzano/verrazzano/platform-operator/controllers/verrazzano/healthcheck"
     9  	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
    10  	"testing"
    11  	"time"
    12  
    13  	"github.com/golang/mock/gomock"
    14  	"github.com/stretchr/testify/assert"
    15  
    16  	"github.com/verrazzano/verrazzano/pkg/log/vzlog"
    17  	vzapi "github.com/verrazzano/verrazzano/platform-operator/apis/verrazzano/v1alpha1"
    18  	"github.com/verrazzano/verrazzano/platform-operator/constants"
    19  	"github.com/verrazzano/verrazzano/platform-operator/internal/config"
    20  	"github.com/verrazzano/verrazzano/platform-operator/mocks"
    21  
    22  	corev1 "k8s.io/api/core/v1"
    23  	"k8s.io/apimachinery/pkg/api/errors"
    24  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    25  	"k8s.io/apimachinery/pkg/runtime"
    26  	"k8s.io/apimachinery/pkg/types"
    27  	ctrl "sigs.k8s.io/controller-runtime"
    28  	"sigs.k8s.io/controller-runtime/pkg/client"
    29  	"sigs.k8s.io/controller-runtime/pkg/client/fake"
    30  )
    31  
    32  // TestConfigMapReconciler tests ComponentConfigMapReconciler method for the following use case
    33  // GIVEN a request to reconcile a ConfigMap
    34  // WHEN the ConfigMap is referenced in the Verrazzano CR under a component and is also present the CR namespace
    35  // THEN the ReconcilingGeneration of the target component is set to 1
    36  func TestConfigMapReconciler(t *testing.T) {
    37  	asserts := assert.New(t)
    38  	cm := testConfigMap
    39  	cm.Finalizers = append(cm.Finalizers, constants.OverridesFinalizer)
    40  	cli := fake.NewClientBuilder().WithObjects(&testVZ, &cm).WithScheme(newScheme()).Build()
    41  
    42  	config.TestProfilesDir = "../../../manifests/profiles"
    43  	defer func() { config.TestProfilesDir = "" }()
    44  
    45  	request0 := newRequest(testNS, testCMName)
    46  	reconciler := newConfigMapReconciler(cli)
    47  	res0, err0 := reconciler.Reconcile(context.TODO(), request0)
    48  
    49  	asserts.NoError(err0)
    50  	asserts.Equal(false, res0.Requeue)
    51  
    52  	vz := vzapi.Verrazzano{}
    53  	err := cli.Get(context.TODO(), types.NamespacedName{Namespace: testNS, Name: testVZName}, &vz)
    54  	asserts.NoError(err)
    55  	asserts.Equal(int64(1), vz.Status.Components["prometheus-operator"].ReconcilingGeneration)
    56  }
    57  
    58  // TestAddFinalizer tests the Reconcile loop for the following use case
    59  // GIVEN a request to reconcile a ConfigMap that qualifies as an override
    60  // WHEN the ConfigMap is found without the overrides finalizer
    61  // THEN the overrides finalizer is added and we requeue without an error
    62  func TestAddFinalizer(t *testing.T) {
    63  	asserts := assert.New(t)
    64  	cli := fake.NewClientBuilder().WithObjects(&testVZ, &testConfigMap).WithScheme(newScheme()).Build()
    65  
    66  	config.TestProfilesDir = "../../../manifests/profiles"
    67  	defer func() { config.TestProfilesDir = "" }()
    68  
    69  	request0 := newRequest(testNS, testCMName)
    70  	reconciler := newConfigMapReconciler(cli)
    71  	res0, err0 := reconciler.Reconcile(context.TODO(), request0)
    72  
    73  	asserts.NoError(err0)
    74  	asserts.Equal(true, res0.Requeue)
    75  
    76  	cm := corev1.ConfigMap{}
    77  	err := cli.Get(context.TODO(), types.NamespacedName{Namespace: testNS, Name: testCMName}, &cm)
    78  	asserts.NoError(err)
    79  	asserts.True(controllerutil.ContainsFinalizer(&cm, constants.OverridesFinalizer))
    80  }
    81  
    82  // TestOtherFinalizers tests the Reconcile loop for the following use case
    83  // GIVEN a request to reconcile a ConfigMap that qualifies as an override resource and is scheduled for deletion
    84  // WHEN the ConfigMap is found with finalizers but the override finalizer is missing
    85  // THEN without updating the Verrazzano CR a requeue request is returned without an error
    86  func TestOtherFinalizers(t *testing.T) {
    87  	asserts := assert.New(t)
    88  	cm := testConfigMap
    89  	cm.Finalizers = append(cm.Finalizers, "test")
    90  	cm.DeletionTimestamp = &metav1.Time{Time: time.Now()}
    91  	cli := fake.NewClientBuilder().WithObjects(&testVZ, &cm).WithScheme(newScheme()).Build()
    92  
    93  	config.TestProfilesDir = "../../../manifests/profiles"
    94  	defer func() { config.TestProfilesDir = "" }()
    95  
    96  	request0 := newRequest(testNS, testCMName)
    97  	reconciler := newConfigMapReconciler(cli)
    98  	res0, err0 := reconciler.Reconcile(context.TODO(), request0)
    99  
   100  	asserts.NoError(err0)
   101  	asserts.Equal(true, res0.Requeue)
   102  
   103  	vz := &vzapi.Verrazzano{}
   104  	err1 := cli.Get(context.TODO(), types.NamespacedName{Namespace: testNS, Name: testVZName}, vz)
   105  	asserts.NoError(err1)
   106  	asserts.NotEqual(int64(1), vz.Status.Components["prometheus-operator"].ReconcilingGeneration)
   107  }
   108  
   109  // TestConfigMapNotFound tests the Reconcile method for the following use cases
   110  // GIVEN requests to reconcile a ConfigMap
   111  // WHEN the ConfigMap is not found in the cluster
   112  // THEN Verrazzano is updated if it's listed as an override, otherwise the request is ignored
   113  func TestConfigMapNotFound(t *testing.T) {
   114  	tests := []struct {
   115  		nsn types.NamespacedName
   116  	}{
   117  		{
   118  			nsn: types.NamespacedName{Namespace: testNS, Name: testCMName},
   119  		},
   120  		{
   121  			nsn: types.NamespacedName{Namespace: testNS, Name: "test"},
   122  		},
   123  	}
   124  
   125  	for i, tt := range tests {
   126  		asserts := assert.New(t)
   127  		cli := fake.NewClientBuilder().WithObjects(&testVZ).WithScheme(newScheme()).Build()
   128  
   129  		config.TestProfilesDir = "../../../manifests/profiles"
   130  		defer func() { config.TestProfilesDir = "" }()
   131  
   132  		request0 := newRequest(tt.nsn.Namespace, tt.nsn.Name)
   133  		reconciler := newConfigMapReconciler(cli)
   134  		res0, err0 := reconciler.Reconcile(context.TODO(), request0)
   135  
   136  		asserts.NoError(err0)
   137  		asserts.Equal(false, res0.Requeue)
   138  
   139  		vz := &vzapi.Verrazzano{}
   140  		err1 := cli.Get(context.TODO(), types.NamespacedName{Namespace: testNS, Name: testVZName}, vz)
   141  		asserts.NoError(err1)
   142  		if i == 0 {
   143  			asserts.Equal(int64(1), vz.Status.Components["prometheus-operator"].ReconcilingGeneration)
   144  		} else {
   145  			asserts.NotEqual(int64(1), vz.Status.Components["prometheus-operator"].ReconcilingGeneration)
   146  		}
   147  	}
   148  
   149  }
   150  
   151  // TestDeletion tests the Reconcile loop for the following use case
   152  // GIVEN a request to reconcile a ConfigMap that qualifies as an override
   153  // WHEN we find that it is scheduled for deletion and contains overrides finalizer
   154  // THEN the override finalizer is removed from the ConfigMap and Verrazzano CR is updated and request is returned without an error
   155  func TestDeletion(t *testing.T) {
   156  	asserts := assert.New(t)
   157  	cm := testConfigMap
   158  	cm.Finalizers = append(cm.Finalizers, constants.OverridesFinalizer)
   159  	cm.DeletionTimestamp = &metav1.Time{Time: time.Now()}
   160  	cli := fake.NewClientBuilder().WithObjects(&testVZ, &cm).WithScheme(newScheme()).Build()
   161  
   162  	config.TestProfilesDir = "../../../manifests/profiles"
   163  	defer func() { config.TestProfilesDir = "" }()
   164  
   165  	request0 := newRequest(testNS, testCMName)
   166  	reconciler := newConfigMapReconciler(cli)
   167  	res0, err0 := reconciler.Reconcile(context.TODO(), request0)
   168  
   169  	asserts.NoError(err0)
   170  	asserts.Equal(false, res0.Requeue)
   171  
   172  	cm1 := &corev1.ConfigMap{}
   173  	err1 := cli.Get(context.TODO(), types.NamespacedName{Namespace: testNS, Name: testCMName}, cm1)
   174  	asserts.True(errors.IsNotFound(err1))
   175  
   176  	vz := &vzapi.Verrazzano{}
   177  	err2 := cli.Get(context.TODO(), types.NamespacedName{Namespace: testNS, Name: testVZName}, vz)
   178  	asserts.NoError(err2)
   179  	asserts.Equal(int64(1), vz.Status.Components["prometheus-operator"].ReconcilingGeneration)
   180  }
   181  
   182  // TestConfigMapRequeue the ComponentConfigMapReconciler method for the following use case
   183  // GIVEN a request to reconcile a ConfigMap that qualifies as an override
   184  // WHEN the status of the Verrazzano CR is found without the Component Status details
   185  // THEN a requeue request is returned with an error
   186  func TestConfigMapRequeue(t *testing.T) {
   187  	asserts := assert.New(t)
   188  	vz := testVZ
   189  	vz.Status.Components = nil
   190  	asserts.Nil(vz.Status.Components)
   191  	cm := testConfigMap
   192  	cm.Finalizers = append(cm.Finalizers, constants.OverridesFinalizer)
   193  	cli := fake.NewClientBuilder().WithObjects(&vz, &cm).WithScheme(newScheme()).Build()
   194  
   195  	config.TestProfilesDir = "../../../manifests/profiles"
   196  	defer func() { config.TestProfilesDir = "" }()
   197  
   198  	request0 := newRequest(testNS, testCMName)
   199  	reconciler := newConfigMapReconciler(cli)
   200  	res0, err0 := reconciler.Reconcile(context.TODO(), request0)
   201  
   202  	asserts.Error(err0)
   203  	asserts.Contains(err0.Error(), "Components not initialized")
   204  	asserts.Equal(true, res0.Requeue)
   205  }
   206  
   207  // TestConfigMapCall tests the reconcileInstallOverrideConfigMap for the following use case
   208  // GIVEN a request to reconcile a ConfigMap
   209  // WHEN the request namespace matches the Verrazzano CR namespace
   210  // THEN expect a call to get the ConfigMap
   211  // Assumption there is existing effective-cr config map exists
   212  func TestConfigMapCall(t *testing.T) {
   213  	asserts := assert.New(t)
   214  	mocker := gomock.NewController(t)
   215  	mock := mocks.NewMockClient(mocker)
   216  	mockStatus := mocks.NewMockStatusWriter(mocker)
   217  	asserts.NotNil(mockStatus)
   218  
   219  	// Expect a call to have Effective CR config Map should be Fetched.
   220  	mock.EXPECT().
   221  		Get(gomock.Any(), types.NamespacedName{Namespace: testNS, Name: testVZConfig}, gomock.Not(gomock.Nil()), gomock.Any()).
   222  		DoAndReturn(func(ctx context.Context, name types.NamespacedName, cm *corev1.ConfigMap, opts ...client.GetOption) error {
   223  			return nil
   224  		})
   225  
   226  	// Expect a call to have Effective CR config Map should be Updated.
   227  	mock.EXPECT().Update(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil)
   228  	config.TestProfilesDir = "../../../manifests/profiles"
   229  	defer func() { config.TestProfilesDir = "" }()
   230  
   231  	expectGetConfigMapExists(mock, &testConfigMap, testNS, testCMName)
   232  
   233  	request := newRequest(testNS, testCMName)
   234  	reconciler := newConfigMapReconciler(mock)
   235  	result, err := reconciler.reconcileInstallOverrideConfigMap(context.TODO(), request, &testVZ)
   236  	asserts.NoError(err)
   237  	mocker.Finish()
   238  	asserts.Equal(false, result.Requeue)
   239  	asserts.Equal(time.Duration(0), result.RequeueAfter)
   240  }
   241  
   242  // TestOtherNS tests the reconcileInstallOverrideConfigMap for the following use case
   243  // GIVEN a request to reconcile a ConfigMap
   244  // WHEN the request namespace does not match with the CR namespace
   245  // THEN the request is ignored
   246  // Assumption there is existing effective-cr config map exists
   247  func TestOtherNS(t *testing.T) {
   248  	asserts := assert.New(t)
   249  	mocker := gomock.NewController(t)
   250  	mock := mocks.NewMockClient(mocker)
   251  	mockStatus := mocks.NewMockStatusWriter(mocker)
   252  	asserts.NotNil(mockStatus)
   253  
   254  	// Do not expect a call to get the ConfigMap if it's a different namespace
   255  	mock.EXPECT().
   256  		Get(gomock.Any(), gomock.Any(), gomock.Not(gomock.Nil()), gomock.Any()).MaxTimes(0)
   257  
   258  	request := newRequest("test0", "test1")
   259  	reconciler := newConfigMapReconciler(mock)
   260  	result, err := reconciler.reconcileInstallOverrideConfigMap(context.TODO(), request, &testVZ)
   261  	asserts.NoError(err)
   262  	mocker.Finish()
   263  	//asserts.Equal(false, result.Requeue)
   264  	asserts.Equal(true, result.Requeue)
   265  }
   266  
   267  // mock client request to get the configmap
   268  func expectGetConfigMapExists(mock *mocks.MockClient, cmToUse *corev1.ConfigMap, namespace string, name string) {
   269  	mock.EXPECT().
   270  		Get(gomock.Any(), types.NamespacedName{Namespace: namespace, Name: name}, gomock.Not(gomock.Nil()), gomock.Any()).
   271  		DoAndReturn(func(ctx context.Context, name types.NamespacedName, cm *corev1.ConfigMap, opts ...client.GetOption) error {
   272  			return nil
   273  		})
   274  }
   275  
   276  // newScheme creates a new scheme that includes this package's object to use for testing
   277  func newScheme() *runtime.Scheme {
   278  	scheme := runtime.NewScheme()
   279  	_ = corev1.AddToScheme(scheme)
   280  	_ = vzapi.AddToScheme(scheme)
   281  	return scheme
   282  }
   283  
   284  // newRequest creates a new reconciler request for testing
   285  // namespace - The namespace to use in the request
   286  // name - The name to use in the request
   287  func newRequest(namespace string, name string) ctrl.Request {
   288  	return ctrl.Request{
   289  		NamespacedName: types.NamespacedName{
   290  			Namespace: namespace,
   291  			Name:      name}}
   292  }
   293  
   294  // newConfigMapReconciler creates a new reconciler for testing
   295  func newConfigMapReconciler(c client.Client) OverridesConfigMapsReconciler {
   296  	vzLog := vzlog.DefaultLogger()
   297  	scheme := newScheme()
   298  	reconciler := OverridesConfigMapsReconciler{
   299  		Client:        c,
   300  		Scheme:        scheme,
   301  		log:           vzLog,
   302  		StatusUpdater: &vzstatus.FakeVerrazzanoStatusUpdater{Client: c},
   303  	}
   304  	return reconciler
   305  }