github.com/verrazzano/verrazzano@v1.7.0/application-operator/controllers/namespace/namespace_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  package namespace
     4  
     5  import (
     6  	"context"
     7  	"fmt"
     8  	"github.com/verrazzano/verrazzano/pkg/log/vzlog"
     9  	"net/http"
    10  	"sigs.k8s.io/controller-runtime/pkg/config/v1alpha1"
    11  	"testing"
    12  	"time"
    13  
    14  	"github.com/go-logr/logr"
    15  	"github.com/golang/mock/gomock"
    16  	"github.com/stretchr/testify/assert"
    17  	"github.com/verrazzano/verrazzano/application-operator/constants"
    18  	"github.com/verrazzano/verrazzano/application-operator/mocks"
    19  	vzconst "github.com/verrazzano/verrazzano/pkg/constants"
    20  	"go.uber.org/zap"
    21  
    22  	appsv1 "k8s.io/api/apps/v1"
    23  	corev1 "k8s.io/api/core/v1"
    24  	k8serrors "k8s.io/apimachinery/pkg/api/errors"
    25  	"k8s.io/apimachinery/pkg/api/meta"
    26  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    27  	"k8s.io/apimachinery/pkg/runtime"
    28  	"k8s.io/apimachinery/pkg/runtime/schema"
    29  	"k8s.io/apimachinery/pkg/types"
    30  	clientgoscheme "k8s.io/client-go/kubernetes/scheme"
    31  	"k8s.io/client-go/rest"
    32  	"k8s.io/client-go/tools/record"
    33  	ctrl "sigs.k8s.io/controller-runtime"
    34  	"sigs.k8s.io/controller-runtime/pkg/cache"
    35  	"sigs.k8s.io/controller-runtime/pkg/client"
    36  	"sigs.k8s.io/controller-runtime/pkg/client/fake"
    37  	"sigs.k8s.io/controller-runtime/pkg/healthz"
    38  	"sigs.k8s.io/controller-runtime/pkg/log"
    39  	"sigs.k8s.io/controller-runtime/pkg/manager"
    40  	"sigs.k8s.io/controller-runtime/pkg/reconcile"
    41  	"sigs.k8s.io/controller-runtime/pkg/webhook"
    42  )
    43  
    44  var testScheme = newScheme()
    45  var logger = vzlog.DefaultLogger()
    46  
    47  // newScheme creates a new scheme that includes this package's object to use for testing
    48  func newScheme() *runtime.Scheme {
    49  	scheme := runtime.NewScheme()
    50  	_ = clientgoscheme.AddToScheme(scheme)
    51  	return scheme
    52  }
    53  
    54  // newTestController - test helper to boostrap a NamespaceController for test purposes
    55  func newTestController(c client.Client) (*NamespaceController, error) {
    56  	mgr := fakeManager{
    57  		Client: c,
    58  		scheme: testScheme,
    59  	}
    60  	return NewNamespaceController(mgr, zap.S())
    61  }
    62  
    63  // TestReconcileNamespaceUpdate tests the Reconcile method for the following use case
    64  // GIVEN a request to Reconcile a Namespace resource
    65  // WHEN the namespace has the expected annotation
    66  // THEN ensure that no error is returned and the result does not indicate a requeue
    67  func TestReconcileNamespaceUpdate(t *testing.T) {
    68  	asserts := assert.New(t)
    69  	mocker := gomock.NewController(t)
    70  	mock := mocks.NewMockClient(mocker)
    71  
    72  	// Expect a call to get the namespace
    73  	mock.EXPECT().
    74  		Get(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
    75  		DoAndReturn(func(ctx context.Context, name types.NamespacedName, ns *corev1.Namespace, opts ...client.GetOption) error {
    76  			ns.Name = "myns"
    77  			ns.Annotations = map[string]string{
    78  				constants.OCILoggingIDAnnotation: "myocid",
    79  			}
    80  			ns.Finalizers = []string{"someFinalizer"}
    81  			return nil
    82  		})
    83  
    84  	// Expect a call to update the namespace that succeeds
    85  	mock.EXPECT().
    86  		Update(gomock.Any(), gomock.Any(), gomock.Any()).
    87  		DoAndReturn(func(ctx context.Context, ns *corev1.Namespace, opts ...client.UpdateOption) error {
    88  			return nil
    89  		})
    90  
    91  	// Expect calls to restart Fluentd
    92  	mockFluentdRestart(mock, asserts)
    93  
    94  	addNamespaceLoggingFunc = func(_ context.Context, _ client.Client, _ string, _ string) (bool, error) {
    95  		return true, nil
    96  	}
    97  	defer func() { addNamespaceLoggingFunc = addNamespaceLogging }()
    98  
    99  	nc, err := newTestController(mock)
   100  	asserts.NoError(err)
   101  
   102  	req := reconcile.Request{
   103  		NamespacedName: types.NamespacedName{Name: "myns"},
   104  	}
   105  	result, err := nc.Reconcile(context.TODO(), req)
   106  
   107  	mocker.Finish()
   108  	asserts.NoError(err)
   109  	asserts.Equal(ctrl.Result{}, result)
   110  }
   111  
   112  // TestReconcileNamespaceNotFound tests the Reconcile method for the following use case
   113  // GIVEN a request to Reconcile a Namespace resource
   114  // WHEN the namespace can not be found
   115  // THEN ensure that no error is returned and the result does not indicate a requeue
   116  func TestReconcileNamespaceNotFound(t *testing.T) {
   117  	runTestReconcileGetError(t, k8serrors.NewNotFound(schema.ParseGroupResource("Namespace"), "myns"), ctrl.Result{})
   118  }
   119  
   120  // TestReconcileNamespaceGetError tests the Reconcile method for the following use case
   121  // GIVEN a request to Reconcile a Namespace resource
   122  // WHEN the client Get() operation returns an error other than IsNotFound
   123  // THEN ensure that the unexpected error is returned and the result does not indicate a requeue (controllerruntime does this)
   124  func TestReconcileNamespaceGetError(t *testing.T) {
   125  	err := fmt.Errorf("some other error getting namespace")
   126  	runTestReconcileGetError(t, err, ctrl.Result{Requeue: true})
   127  }
   128  
   129  // runTestReconcileGetError - Common test code for the namespace Get() error cases
   130  func runTestReconcileGetError(t *testing.T, returnErr error, expectedResult ctrl.Result) {
   131  	asserts := assert.New(t)
   132  	mocker := gomock.NewController(t)
   133  	mock := mocks.NewMockClient(mocker)
   134  
   135  	// Expect a call to get the namespace
   136  	mock.EXPECT().
   137  		Get(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
   138  		DoAndReturn(func(ctx context.Context, name types.NamespacedName, ns *corev1.Namespace, opts ...client.GetOption) error {
   139  			return returnErr
   140  		})
   141  
   142  	// Expect no call to update the namespace
   143  	mock.EXPECT().Update(gomock.Any(), gomock.Any(), gomock.Any()).Times(0)
   144  
   145  	nc, err := newTestController(mock)
   146  	asserts.NoError(err)
   147  
   148  	req := reconcile.Request{
   149  		NamespacedName: types.NamespacedName{Name: "myns"},
   150  	}
   151  	result, err := nc.Reconcile(context.TODO(), req)
   152  
   153  	mocker.Finish()
   154  	asserts.Nil(err)
   155  	asserts.Equal(expectedResult.Requeue, result.Requeue)
   156  	if result.Requeue {
   157  		asserts.Greater(result.RequeueAfter.Seconds(), time.Duration(0).Seconds())
   158  	}
   159  }
   160  
   161  // TestReconcileNamespaceDeleted tests the Reconcile method for the following use case
   162  // GIVEN a request to Reconcile a Namespace resource
   163  // WHEN the namespace DeletionTimestamp has been set (namespace is in the process of being deleted)
   164  // THEN ensure that the namespace finalizer is deleted and no error or requeue result are returned
   165  func TestReconcileNamespaceDeleted(t *testing.T) {
   166  	asserts := assert.New(t)
   167  	mocker := gomock.NewController(t)
   168  	mock := mocks.NewMockClient(mocker)
   169  
   170  	// Expect a call to get the namespace
   171  	mock.EXPECT().
   172  		Get(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
   173  		DoAndReturn(func(ctx context.Context, name types.NamespacedName, ns *corev1.Namespace, opts ...client.GetOption) error {
   174  			ns.Name = "myns"
   175  			ns.DeletionTimestamp = &metav1.Time{Time: time.Now()}
   176  			ns.Annotations = map[string]string{
   177  				constants.OCILoggingIDAnnotation: "myocid",
   178  			}
   179  			ns.Finalizers = []string{"someFinalizer", namespaceControllerFinalizer}
   180  			return nil
   181  		})
   182  
   183  	// Expect calls to restart Fluentd
   184  	mockFluentdRestart(mock, asserts)
   185  
   186  	// Expect a call to update the namespace that succeeds
   187  	mock.EXPECT().
   188  		Update(gomock.Any(), gomock.Any(), gomock.Any()).
   189  		DoAndReturn(func(ctx context.Context, ns *corev1.Namespace, opts ...client.UpdateOption) error {
   190  			asserts.NotContainsf(ns.Finalizers, namespaceControllerFinalizer, "Finalizer not removed: ", ns.Finalizers)
   191  			return nil
   192  		})
   193  
   194  	removeNamespaceLoggingFunc = func(_ context.Context, _ client.Client, _ string) (bool, error) {
   195  		return true, nil
   196  	}
   197  	defer func() { removeNamespaceLoggingFunc = removeNamespaceLogging }()
   198  
   199  	nc, err := newTestController(mock)
   200  	asserts.NoError(err)
   201  
   202  	req := reconcile.Request{
   203  		NamespacedName: types.NamespacedName{Name: "myns"},
   204  	}
   205  	result, err := nc.Reconcile(context.TODO(), req)
   206  
   207  	mocker.Finish()
   208  	asserts.NoError(err)
   209  	asserts.Equal(ctrl.Result{}, result)
   210  }
   211  
   212  // TestReconcileNamespaceDeletedErrorOnUpdate tests the Reconcile method for the following use case
   213  // GIVEN a request to Reconcile a Namespace resource
   214  // WHEN the namespace DeletionTimestamp has been set (namespace deleted) and the remove OCI logging integration returns an error
   215  // THEN ensure than an error and a requeue are returned, and Update() is never called to remove the finalizer
   216  func TestReconcileNamespaceDeletedErrorOnUpdate(t *testing.T) {
   217  	asserts := assert.New(t)
   218  	mocker := gomock.NewController(t)
   219  	mock := mocks.NewMockClient(mocker)
   220  
   221  	// Expect a call to get the namespace
   222  	mock.EXPECT().
   223  		Get(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
   224  		DoAndReturn(func(ctx context.Context, name types.NamespacedName, ns *corev1.Namespace, opts ...client.GetOption) error {
   225  			ns.Name = "myns"
   226  			ns.DeletionTimestamp = &metav1.Time{Time: time.Now()}
   227  			ns.Annotations = map[string]string{
   228  				constants.OCILoggingIDAnnotation: "myocid",
   229  			}
   230  			ns.Finalizers = []string{"someFinalizer", namespaceControllerFinalizer}
   231  			return nil
   232  		})
   233  
   234  	// Expect no call to update the namespace
   235  	mock.EXPECT().Update(gomock.Any(), gomock.Any(), gomock.Any()).Times(0)
   236  
   237  	nc, err := newTestController(mock)
   238  	asserts.NoError(err)
   239  
   240  	// Force a failure
   241  	returnedErr := fmt.Errorf("error updating OCI Logging")
   242  	removeNamespaceLoggingFunc = func(_ context.Context, _ client.Client, _ string) (bool, error) {
   243  		return false, returnedErr
   244  	}
   245  	defer func() { removeNamespaceLoggingFunc = removeNamespaceLogging }()
   246  
   247  	req := reconcile.Request{
   248  		NamespacedName: types.NamespacedName{Name: "myns"},
   249  	}
   250  	result, _ := nc.Reconcile(context.TODO(), req)
   251  
   252  	mocker.Finish()
   253  	asserts.True(result.Requeue)
   254  	asserts.Greater(result.RequeueAfter.Seconds(), time.Duration(0).Seconds())
   255  }
   256  
   257  // TestReconcileNamespaceDeletedNoFinalizer tests the Reconcile method for the following use case
   258  // GIVEN a request to Reconcile a Namespace resource
   259  // WHEN the namespace DeletionTimestamp has been set (namespace is in the process of being deleted)
   260  // AND our finalizer doesn't exist (for example, we removed it on a previous reconcile)
   261  // THEN we do not update the namespace or attempt to remove any logging config
   262  func TestReconcileNamespaceDeletedNoFinalizer(t *testing.T) {
   263  	asserts := assert.New(t)
   264  	mocker := gomock.NewController(t)
   265  	mock := mocks.NewMockClient(mocker)
   266  
   267  	// Expect a call to get the namespace
   268  	mock.EXPECT().
   269  		Get(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
   270  		DoAndReturn(func(ctx context.Context, name types.NamespacedName, ns *corev1.Namespace, opts ...client.GetOption) error {
   271  			ns.Name = "myns"
   272  			ns.DeletionTimestamp = &metav1.Time{Time: time.Now()}
   273  			ns.Annotations = map[string]string{
   274  				constants.OCILoggingIDAnnotation: "myocid",
   275  			}
   276  			ns.Finalizers = []string{"someFinalizer"}
   277  			return nil
   278  		})
   279  
   280  	nc, err := newTestController(mock)
   281  	asserts.NoError(err)
   282  
   283  	req := reconcile.Request{
   284  		NamespacedName: types.NamespacedName{Name: "myns"},
   285  	}
   286  	result, err := nc.Reconcile(context.TODO(), req)
   287  
   288  	mocker.Finish()
   289  	asserts.NoError(err)
   290  	asserts.Equal(ctrl.Result{}, result)
   291  }
   292  
   293  // Test_removeFinalizer tests the removeFinalizer method for the following use case
   294  // GIVEN a request to removeFinalizer for a Namespace resource
   295  // WHEN the namespace has the NamespaceController finalizer present
   296  // THEN the NamespaceController finalizer is removed from the Namespace resource and no requeue is indicated
   297  func Test_removeFinalizer(t *testing.T) {
   298  	asserts := assert.New(t)
   299  	mocker := gomock.NewController(t)
   300  	mock := mocks.NewMockClient(mocker)
   301  
   302  	// Expect a call to update the namespace that succeeds
   303  	mock.EXPECT().
   304  		Update(gomock.Any(), gomock.Any(), gomock.Any()).
   305  		DoAndReturn(func(ctx context.Context, ns *corev1.Namespace, opts ...client.UpdateOption) error {
   306  			return nil
   307  		})
   308  
   309  	ns := &corev1.Namespace{
   310  		ObjectMeta: metav1.ObjectMeta{
   311  			Name:       "myns",
   312  			Finalizers: []string{namespaceControllerFinalizer, "anotherFinalizer"},
   313  		},
   314  	}
   315  
   316  	nc, err := newTestController(mock)
   317  	asserts.NoError(err)
   318  
   319  	result, err := nc.removeFinalizer(context.TODO(), ns, logger)
   320  
   321  	mocker.Finish()
   322  	asserts.NoError(err)
   323  	asserts.NotContainsf(ns.Finalizers, namespaceControllerFinalizer, "Finalizer not removed: ", ns.Finalizers)
   324  	asserts.Equal(ctrl.Result{}, result)
   325  }
   326  
   327  // Test_removeFinalizerNotPresent tests the removeFinalizer method for the following use case
   328  // GIVEN a request to removeFinalizer for a Namespace resource
   329  // WHEN the namespace does not have the NamespaceController finalizer present
   330  // THEN the NamespaceController finalizer field unchanged and no requeue is indicated
   331  func Test_removeFinalizerNotPresent(t *testing.T) {
   332  	asserts := assert.New(t)
   333  	mocker := gomock.NewController(t)
   334  	mock := mocks.NewMockClient(mocker)
   335  
   336  	// Expect a call to update the namespace that succeeds
   337  	mock.EXPECT().
   338  		Update(gomock.Any(), gomock.Any(), gomock.Any()).
   339  		DoAndReturn(func(ctx context.Context, ns *corev1.Namespace, opts ...client.UpdateOption) error {
   340  			return nil
   341  		})
   342  
   343  	ns := &corev1.Namespace{
   344  		ObjectMeta: metav1.ObjectMeta{
   345  			Name:       "myns",
   346  			Finalizers: []string{"anotherFinalizer"},
   347  		},
   348  	}
   349  
   350  	nc, err := newTestController(mock)
   351  	asserts.NoError(err)
   352  
   353  	result, err := nc.removeFinalizer(context.TODO(), ns, logger)
   354  
   355  	mocker.Finish()
   356  	asserts.NoError(err)
   357  	asserts.Equalf(ns.Finalizers, []string{"anotherFinalizer"}, "Finalizers modified unexpectedly: %v", ns.Finalizers)
   358  	asserts.Equal(ctrl.Result{}, result)
   359  }
   360  
   361  // Test_removeFinalizerErrorOnUpdate tests the removeFinalizer method for the following use case
   362  // GIVEN a request to removeFinalizer for a Namespace resource
   363  // WHEN the client returns an error on Update()
   364  // THEN an error is returned
   365  func Test_removeFinalizerErrorOnUpdate(t *testing.T) {
   366  	asserts := assert.New(t)
   367  	mocker := gomock.NewController(t)
   368  	mock := mocks.NewMockClient(mocker)
   369  
   370  	nc, err := newTestController(mock)
   371  	asserts.NoError(err)
   372  
   373  	ns := &corev1.Namespace{
   374  		ObjectMeta: metav1.ObjectMeta{
   375  			Name:       "myns",
   376  			Finalizers: []string{namespaceControllerFinalizer, "anotherFinalizer"},
   377  		},
   378  	}
   379  
   380  	// Expect a call to update the namespace that fails
   381  	expectedErr := fmt.Errorf("error updating namespace")
   382  	mock.EXPECT().
   383  		Update(gomock.Any(), gomock.Any(), gomock.Any()).
   384  		DoAndReturn(func(ctx context.Context, ns *corev1.Namespace, opts ...client.UpdateOption) error {
   385  			return expectedErr
   386  		})
   387  
   388  	result, err := nc.removeFinalizer(context.TODO(), ns, logger)
   389  
   390  	mocker.Finish()
   391  	asserts.Error(err)
   392  	asserts.Equalf(expectedErr, err, "Did not get expected error: %v", err)
   393  	asserts.Equal(ctrl.Result{}, result)
   394  }
   395  
   396  // Test_reconcileNamespaceErrorOnUpdate tests the reconcileNamespace method for the following use case
   397  // GIVEN a request to reconcileNamespace for a Namespace resource
   398  // WHEN the client returns an error on Update()
   399  // THEN an error and a requeue are returned
   400  func Test_reconcileNamespaceErrorOnUpdate(t *testing.T) {
   401  	asserts := assert.New(t)
   402  	mocker := gomock.NewController(t)
   403  	mock := mocks.NewMockClient(mocker)
   404  
   405  	nc, err := newTestController(mock)
   406  	asserts.NoError(err)
   407  
   408  	ns := &corev1.Namespace{
   409  		ObjectMeta: metav1.ObjectMeta{
   410  			Name: "myns",
   411  			Annotations: map[string]string{
   412  				constants.OCILoggingIDAnnotation: "myocid",
   413  			},
   414  			Finalizers: []string{"anotherFinalizer"},
   415  		},
   416  	}
   417  
   418  	expectedErr := fmt.Errorf("error updating namespace")
   419  
   420  	// Expect a call to update the namespace that fails
   421  	mock.EXPECT().
   422  		Update(gomock.Any(), gomock.Any(), gomock.Any()).
   423  		DoAndReturn(func(ctx context.Context, ns *corev1.Namespace, opts ...client.UpdateOption) error {
   424  			return expectedErr
   425  		})
   426  
   427  	err = nc.reconcileNamespace(context.TODO(), ns, logger)
   428  
   429  	mocker.Finish()
   430  	asserts.Error(err)
   431  	asserts.Equalf(expectedErr, err, "Did not get expected error: %v", err)
   432  }
   433  
   434  // Test_reconcileNamespace tests the reconcileNamespace method for the following use case
   435  // GIVEN a request to reconcileNamespace for a Namespace resource
   436  // WHEN the namespace is configured for OCI Logging
   437  // THEN no error or requeue are returned
   438  func Test_reconcileNamespace(t *testing.T) {
   439  	asserts := assert.New(t)
   440  	mocker := gomock.NewController(t)
   441  	mock := mocks.NewMockClient(mocker)
   442  
   443  	nc, err := newTestController(mock)
   444  	asserts.NoError(err)
   445  
   446  	ns := &corev1.Namespace{
   447  		ObjectMeta: metav1.ObjectMeta{
   448  			Name: "myns",
   449  			Annotations: map[string]string{
   450  				constants.OCILoggingIDAnnotation: "myocid",
   451  			},
   452  			Finalizers: []string{"anotherFinalizer", namespaceControllerFinalizer},
   453  		},
   454  	}
   455  
   456  	addNamespaceLoggingFunc = func(_ context.Context, _ client.Client, _ string, _ string) (bool, error) {
   457  		return false, nil
   458  	}
   459  	defer func() { addNamespaceLoggingFunc = addNamespaceLogging }()
   460  
   461  	err = nc.reconcileNamespace(context.TODO(), ns, logger)
   462  
   463  	mocker.Finish()
   464  	asserts.NoError(err)
   465  }
   466  
   467  // Test_reconcileNamespaceDelete tests the reconcileNamespaceDelete method for the following use case
   468  // GIVEN a request to reconcileNamespaceDelete for a Namespace resource
   469  // WHEN the namespace is configured for OCI Logging
   470  // THEN no error is returned
   471  //
   472  // Largely a placeholder for now
   473  func Test_reconcileNamespaceDelete(t *testing.T) {
   474  	asserts := assert.New(t)
   475  
   476  	nc, err := newTestController(fake.NewClientBuilder().WithScheme(testScheme).Build())
   477  	asserts.NoErrorf(err, "Error creating test controller")
   478  	ns := &corev1.Namespace{
   479  		ObjectMeta: metav1.ObjectMeta{
   480  			Name: "myns",
   481  			Annotations: map[string]string{
   482  				constants.OCILoggingIDAnnotation: "myocid",
   483  			},
   484  			Finalizers: []string{"anotherFinalizer", namespaceControllerFinalizer},
   485  		},
   486  	}
   487  
   488  	removeNamespaceLoggingFunc = func(_ context.Context, _ client.Client, _ string) (bool, error) {
   489  		return false, nil
   490  	}
   491  	defer func() { removeNamespaceLoggingFunc = removeNamespaceLogging }()
   492  
   493  	err = nc.reconcileNamespaceDelete(context.TODO(), ns, logger)
   494  	asserts.NoError(err)
   495  }
   496  
   497  // Test_reconcileOCILoggingRemoveOCILogging tests the reconcileOCILogging method for the following use case
   498  // GIVEN a request to reconcileOCILogging for a Namespace resource
   499  // WHEN the namespace is not configured for OCI Logging
   500  // THEN no error is returned
   501  func Test_reconcileOCILoggingRemoveOCILogging(t *testing.T) {
   502  	asserts := assert.New(t)
   503  	mocker := gomock.NewController(t)
   504  	mock := mocks.NewMockClient(mocker)
   505  
   506  	nc, err := newTestController(mock)
   507  	asserts.NoError(err)
   508  
   509  	ns := &corev1.Namespace{
   510  		ObjectMeta: metav1.ObjectMeta{
   511  			Name:       "myns",
   512  			Finalizers: []string{"anotherFinalizer"},
   513  		},
   514  	}
   515  
   516  	removeCalled := false
   517  	removeNamespaceLoggingFunc = func(_ context.Context, _ client.Client, _ string) (bool, error) {
   518  		removeCalled = true
   519  		return false, nil
   520  	}
   521  	defer func() { removeNamespaceLoggingFunc = removeNamespaceLogging }()
   522  
   523  	err = nc.reconcileOCILogging(context.TODO(), ns, logger)
   524  
   525  	mocker.Finish()
   526  	asserts.NoError(err)
   527  	asserts.True(removeCalled)
   528  }
   529  
   530  // Test_reconcileOCILoggingRemoveOCILoggingError tests the reconcileOCILogging method for the following use case
   531  // GIVEN a request to reconcileOCILogging for a Namespace resource
   532  // WHEN the namespace is not configured for OCI Logging and we fail removing the OCI Logging config from the configmap
   533  // THEN an error is returned
   534  func Test_reconcileOCILoggingRemoveOCILoggingError(t *testing.T) {
   535  	asserts := assert.New(t)
   536  	mocker := gomock.NewController(t)
   537  	mock := mocks.NewMockClient(mocker)
   538  
   539  	nc, err := newTestController(mock)
   540  	asserts.NoError(err)
   541  
   542  	ns := &corev1.Namespace{
   543  		ObjectMeta: metav1.ObjectMeta{
   544  			Name:       "myns",
   545  			Finalizers: []string{"anotherFinalizer"},
   546  		},
   547  	}
   548  
   549  	expectedErr := fmt.Errorf("error removing OCI logging")
   550  	removeNamespaceLoggingFunc = func(_ context.Context, _ client.Client, _ string) (bool, error) {
   551  		return false, expectedErr
   552  	}
   553  	defer func() { removeNamespaceLoggingFunc = removeNamespaceLogging }()
   554  
   555  	err = nc.reconcileOCILogging(context.TODO(), ns, logger)
   556  
   557  	mocker.Finish()
   558  	asserts.Error(err)
   559  	asserts.Equal(expectedErr, err)
   560  }
   561  
   562  // Test_reconcileOCILoggingAddOCILoggingUpdated tests the reconcileOCILogging method for the following use case
   563  // GIVEN a request to reconcileOCILogging for a Namespace resource
   564  // WHEN the namespace is configured for OCI Logging and processed for the first time
   565  // THEN the AddOCILogging function is called, the namespace finalizer is added, and no error is returned
   566  func Test_reconcileOCILoggingAddOCILoggingUpdated(t *testing.T) {
   567  	runAddOCILoggingTest(t, true)
   568  }
   569  
   570  // Test_reconcileOCILoggingAddOCILoggingUpdated tests the reconcileOCILogging method for the following use case
   571  // GIVEN a request to reconcileOCILogging for a Namespace resource
   572  // WHEN the AddOCILogging returns false (no changes)
   573  // THEN the AddOCILogging function is called, the namespace finalizer is added, and no error is returned
   574  func Test_reconcileOCILoggingAddOCILoggingNoOp(t *testing.T) {
   575  	runAddOCILoggingTest(t, false)
   576  }
   577  
   578  // runAddOCILoggingTest - shared helper for the AddOCILogging tests
   579  func runAddOCILoggingTest(t *testing.T, addLoggingResult bool) {
   580  	asserts := assert.New(t)
   581  	mocker := gomock.NewController(t)
   582  	mock := mocks.NewMockClient(mocker)
   583  
   584  	nc, err := newTestController(mock)
   585  	asserts.NoError(err)
   586  
   587  	ns := &corev1.Namespace{
   588  		ObjectMeta: metav1.ObjectMeta{
   589  			Name: "myns",
   590  			Annotations: map[string]string{
   591  				constants.OCILoggingIDAnnotation: "myocid",
   592  			},
   593  			Finalizers: []string{"anotherFinalizer"},
   594  		},
   595  	}
   596  
   597  	addCalled := false
   598  	addNamespaceLoggingFunc = func(_ context.Context, _ client.Client, _ string, _ string) (bool, error) {
   599  		addCalled = true
   600  		return addLoggingResult, nil
   601  	}
   602  	defer func() { addNamespaceLoggingFunc = addNamespaceLogging }()
   603  
   604  	// Expect a call to update the namespace annotations that succeeds
   605  	mock.EXPECT().
   606  		Update(gomock.Any(), gomock.Any(), gomock.Any()).
   607  		DoAndReturn(func(ctx context.Context, ns *corev1.Namespace, opts ...client.UpdateOption) error {
   608  			return nil
   609  		})
   610  
   611  	// if the result from adding logging returns true (meaning the Fluentd configmap was updated), then
   612  	// mock exceptions for restarting Fluentd
   613  	if addLoggingResult {
   614  		mockFluentdRestart(mock, asserts)
   615  	}
   616  
   617  	err = nc.reconcileOCILogging(context.TODO(), ns, logger)
   618  
   619  	mocker.Finish()
   620  	asserts.NoError(err)
   621  	asserts.Contains(ns.Finalizers, namespaceControllerFinalizer)
   622  	asserts.Len(ns.Finalizers, 2)
   623  	asserts.Truef(addCalled, "Add OCI Logging fn not called")
   624  }
   625  
   626  // Test_reconcileOCILoggingFinalizerAlreadyAdded tests the reconcileOCILogging method for the following use case
   627  // GIVEN a request to reconcileOCILogging for a Namespace resource
   628  // WHEN the NamespaceController finalizer is already present
   629  // THEN the AddOCILogging function is called, Update() is not called, the namespace finalizer set is unchanged, and no error is returned
   630  func Test_reconcileOCILoggingFinalizerAlreadyAdded(t *testing.T) {
   631  	asserts := assert.New(t)
   632  	mocker := gomock.NewController(t)
   633  	mock := mocks.NewMockClient(mocker)
   634  
   635  	nc, err := newTestController(mock)
   636  	asserts.NoError(err)
   637  
   638  	ns := &corev1.Namespace{
   639  		ObjectMeta: metav1.ObjectMeta{
   640  			Name: "myns",
   641  			Annotations: map[string]string{
   642  				constants.OCILoggingIDAnnotation: "myocid",
   643  			},
   644  			Finalizers: []string{"anotherFinalizer", namespaceControllerFinalizer},
   645  		},
   646  	}
   647  
   648  	addCalled := false
   649  	addNamespaceLoggingFunc = func(_ context.Context, _ client.Client, _ string, _ string) (bool, error) {
   650  		addCalled = true
   651  		return false, nil
   652  	}
   653  	defer func() { addNamespaceLoggingFunc = addNamespaceLogging }()
   654  
   655  	// Expect no calls to update the namespace
   656  	mock.EXPECT().Update(gomock.Any(), gomock.Any()).Times(0)
   657  
   658  	err = nc.reconcileOCILogging(context.TODO(), ns, logger)
   659  
   660  	mocker.Finish()
   661  	asserts.NoError(err)
   662  	asserts.Contains(ns.Finalizers, namespaceControllerFinalizer)
   663  	asserts.Len(ns.Finalizers, 2)
   664  	asserts.Truef(addCalled, "Add OCI Logging fn not called")
   665  }
   666  
   667  // Test_reconcileOCILoggingAddOCILoggingAddFailed tests the reconcileOCILogging method for the following use case
   668  // GIVEN a request to reconcileOCILogging for a Namespace resource
   669  // WHEN the OCI Logging annotation and NamespaceController finalizer are present and the AddOCILogging helper returns an error
   670  // THEN the AddOCILogging function is called, Update() is not called, the namespace finalizer set is unchanged, and an error is returned
   671  func Test_reconcileOCILoggingAddOCILoggingAddFailed(t *testing.T) {
   672  	asserts := assert.New(t)
   673  	mocker := gomock.NewController(t)
   674  	mock := mocks.NewMockClient(mocker)
   675  
   676  	nc, err := newTestController(mock)
   677  	asserts.NoError(err)
   678  
   679  	ns := &corev1.Namespace{
   680  		ObjectMeta: metav1.ObjectMeta{
   681  			Name: "myns",
   682  			Annotations: map[string]string{
   683  				constants.OCILoggingIDAnnotation: "myocid",
   684  			},
   685  			Finalizers: []string{"anotherFinalizer", namespaceControllerFinalizer},
   686  		},
   687  	}
   688  
   689  	expectedErr := fmt.Errorf("error adding OCI Logging configuration")
   690  	addNamespaceLoggingFunc = func(_ context.Context, _ client.Client, _ string, _ string) (bool, error) {
   691  		return false, expectedErr
   692  	}
   693  	defer func() { addNamespaceLoggingFunc = addNamespaceLogging }()
   694  
   695  	// Expect a call to update the namespace annotations that succeeds
   696  	mock.EXPECT().Update(gomock.Any(), gomock.Any()).Times(0)
   697  
   698  	err = nc.reconcileOCILogging(context.TODO(), ns, logger)
   699  
   700  	mocker.Finish()
   701  	asserts.Error(err)
   702  	asserts.Equal(expectedErr, err)
   703  	asserts.Contains(ns.Finalizers, namespaceControllerFinalizer)
   704  	asserts.Len(ns.Finalizers, 2)
   705  }
   706  
   707  // mockFluentdRestart - Mock exceptions for Fluentd daemonset restart
   708  func mockFluentdRestart(mock *mocks.MockClient, asserts *assert.Assertions) {
   709  	// Expect a call to get the Fleuntd Daemonset and another to update it with a restart time annotation
   710  	mock.EXPECT().
   711  		Get(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
   712  		DoAndReturn(func(ctx context.Context, name types.NamespacedName, ds *appsv1.DaemonSet, opts ...client.GetOption) error {
   713  			return nil
   714  		})
   715  	mock.EXPECT().
   716  		Update(gomock.Any(), gomock.Any(), gomock.Any()).
   717  		DoAndReturn(func(ctx context.Context, ds *appsv1.DaemonSet, opts ...client.UpdateOption) error {
   718  			asserts.Contains(ds.Spec.Template.ObjectMeta.Annotations, vzconst.VerrazzanoRestartAnnotation)
   719  			return nil
   720  		})
   721  }
   722  
   723  // TestReconcileKubeSystem tests to make sure we do not reconcile
   724  // Any resource that belong to the kube-system namespace
   725  func TestReconcileKubeSystem(t *testing.T) {
   726  	asserts := assert.New(t)
   727  
   728  	mocker := gomock.NewController(t)
   729  	mock := mocks.NewMockClient(mocker)
   730  
   731  	nc, err := newTestController(mock)
   732  	asserts.NoError(err)
   733  
   734  	// create a request and reconcile it
   735  	req := reconcile.Request{
   736  		NamespacedName: types.NamespacedName{Name: vzconst.KubeSystem},
   737  	}
   738  	result, err := nc.Reconcile(context.TODO(), req)
   739  
   740  	// Validate the results
   741  	mocker.Finish()
   742  	asserts.Nil(err)
   743  	asserts.True(result.IsZero())
   744  }
   745  
   746  // Fake manager for unit testing
   747  type fakeManager struct {
   748  	client.Client
   749  	scheme *runtime.Scheme
   750  }
   751  
   752  func (f fakeManager) Start(ctx context.Context) error {
   753  	return nil
   754  }
   755  
   756  func (f fakeManager) GetControllerOptions() v1alpha1.ControllerConfigurationSpec {
   757  	return v1alpha1.ControllerConfigurationSpec{}
   758  }
   759  
   760  func (f fakeManager) Add(_ manager.Runnable) error {
   761  	return nil
   762  }
   763  
   764  func (f fakeManager) Elected() <-chan struct{} {
   765  	return nil
   766  }
   767  
   768  func (f fakeManager) SetFields(_ interface{}) error {
   769  	return nil
   770  }
   771  
   772  func (f fakeManager) AddMetricsExtraHandler(_ string, _ http.Handler) error {
   773  	return nil
   774  }
   775  
   776  func (f fakeManager) AddHealthzCheck(_ string, _ healthz.Checker) error {
   777  	return nil
   778  }
   779  
   780  func (f fakeManager) AddReadyzCheck(_ string, _ healthz.Checker) error {
   781  	return nil
   782  }
   783  
   784  func (f fakeManager) GetConfig() *rest.Config {
   785  	return nil
   786  }
   787  
   788  func (f fakeManager) GetScheme() *runtime.Scheme {
   789  	return f.scheme
   790  }
   791  
   792  func (f fakeManager) GetClient() client.Client {
   793  	return f.Client
   794  }
   795  
   796  func (f fakeManager) GetFieldIndexer() client.FieldIndexer {
   797  	return nil
   798  }
   799  
   800  func (f fakeManager) GetCache() cache.Cache {
   801  	return nil
   802  }
   803  
   804  func (f fakeManager) GetEventRecorderFor(_ string) record.EventRecorder {
   805  	return nil
   806  }
   807  
   808  func (f fakeManager) GetRESTMapper() meta.RESTMapper {
   809  	return nil
   810  }
   811  
   812  func (f fakeManager) GetAPIReader() client.Reader {
   813  	return nil
   814  }
   815  
   816  func (f fakeManager) GetWebhookServer() *webhook.Server {
   817  	return nil
   818  }
   819  
   820  func (f fakeManager) GetLogger() logr.Logger {
   821  	return log.Log
   822  }
   823  
   824  var _ ctrl.Manager = fakeManager{}