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{}