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 }