github.com/verrazzano/verrazzano@v1.7.0/application-operator/controllers/cohworkload/coherenceworkload_controller_test.go (about) 1 // Copyright (c) 2020, 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 cohworkload 5 6 import ( 7 "context" 8 "fmt" 9 "strings" 10 "testing" 11 12 "github.com/go-logr/logr" 13 "github.com/prometheus/client_golang/prometheus/testutil" 14 vzconst "github.com/verrazzano/verrazzano/pkg/constants" 15 "github.com/verrazzano/verrazzano/pkg/log/vzlog" 16 17 oamrt "github.com/crossplane/crossplane-runtime/apis/common/v1" 18 "github.com/crossplane/oam-kubernetes-runtime/apis/core" 19 oamcore "github.com/crossplane/oam-kubernetes-runtime/apis/core/v1alpha2" 20 "github.com/crossplane/oam-kubernetes-runtime/pkg/oam" 21 "github.com/golang/mock/gomock" 22 asserts "github.com/stretchr/testify/assert" 23 vzapi "github.com/verrazzano/verrazzano/application-operator/apis/oam/v1alpha1" 24 "github.com/verrazzano/verrazzano/application-operator/controllers/logging" 25 "github.com/verrazzano/verrazzano/application-operator/controllers/metricstrait" 26 "github.com/verrazzano/verrazzano/application-operator/metricsexporter" 27 "github.com/verrazzano/verrazzano/application-operator/mocks" 28 vzstring "github.com/verrazzano/verrazzano/pkg/string" 29 "go.uber.org/zap" 30 istionet "istio.io/api/networking/v1alpha3" 31 istioclient "istio.io/client-go/pkg/apis/networking/v1alpha3" 32 v1 "k8s.io/api/apps/v1" 33 corev1 "k8s.io/api/core/v1" 34 k8serrors "k8s.io/apimachinery/pkg/api/errors" 35 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 36 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 37 "k8s.io/apimachinery/pkg/runtime" 38 k8sschema "k8s.io/apimachinery/pkg/runtime/schema" 39 "k8s.io/apimachinery/pkg/types" 40 k8scheme "k8s.io/client-go/kubernetes/scheme" 41 ctrl "sigs.k8s.io/controller-runtime" 42 "sigs.k8s.io/controller-runtime/pkg/client" 43 "sigs.k8s.io/controller-runtime/pkg/client/fake" 44 ) 45 46 const namespace = "unit-test-namespace" 47 const coherenceAPIVersion = "coherence.oracle.com/v1" 48 const coherenceKind = "Coherence" 49 const testRestartVersion = "new-restart" 50 const loggingTrait = ` 51 { 52 "apiVersion": "oam.verrazzano.io/v1alpha1", 53 "kind": "LoggingTrait", 54 "name": "my-logging-trait" 55 } 56 ` 57 58 var specJvmArgsFields = []string{specField, jvmField, argsField} 59 60 // TestReconcilerSetupWithManager test the creation of the logging scope reconciler. 61 // GIVEN a controller implementation 62 // WHEN the controller is created 63 // THEN verify no error is returned 64 func TestReconcilerSetupWithManager(t *testing.T) { 65 assert := asserts.New(t) 66 67 var mocker *gomock.Controller 68 var mgr *mocks.MockManager 69 var cli *mocks.MockClient 70 var scheme *runtime.Scheme 71 var reconciler Reconciler 72 var err error 73 74 mocker = gomock.NewController(t) 75 mgr = mocks.NewMockManager(mocker) 76 cli = mocks.NewMockClient(mocker) 77 scheme = runtime.NewScheme() 78 _ = vzapi.AddToScheme(scheme) 79 metricsReconciler := &metricstrait.Reconciler{Client: cli, Scheme: scheme, Scraper: "verrazzano-system/vmi-system-prometheus-0"} 80 reconciler = Reconciler{Client: cli, Scheme: scheme, Metrics: metricsReconciler} 81 mgr.EXPECT().GetControllerOptions().AnyTimes() 82 mgr.EXPECT().GetScheme().Return(scheme) 83 mgr.EXPECT().GetLogger().Return(logr.Discard()) 84 mgr.EXPECT().SetFields(gomock.Any()).Return(nil).AnyTimes() 85 mgr.EXPECT().Add(gomock.Any()).Return(nil).AnyTimes() 86 err = reconciler.SetupWithManager(mgr) 87 mocker.Finish() 88 assert.NoError(err) 89 } 90 91 // TestReconcileCreateCoherence tests the basic happy path of reconciling a VerrazzanoCoherenceWorkload. We 92 // expect to write out a Coherence CR but we aren't adding logging or any other scopes or traits. 93 // GIVEN a VerrazzanoCoherenceWorkload resource is created 94 // WHEN the controller Reconcile function is called 95 // THEN expect a Coherence CR to be written 96 func TestReconcileCreateCoherence(t *testing.T) { 97 appConfigName := "unit-test-app-config" 98 componentName := "unit-test-component" 99 labels := map[string]string{oam.LabelAppComponent: componentName, oam.LabelAppName: appConfigName} 100 101 tests := []struct { 102 name string 103 coherenceJSON string 104 expectedSvcLabelCount int 105 }{ 106 {name: "Coherence with no ports", coherenceJSON: `{"metadata":{"name":"unit-test-cluster"},"spec":{"replicas":3}}`, expectedSvcLabelCount: 0}, 107 108 {name: "Coherence with ports, no services", 109 coherenceJSON: `{"metadata":{"name":"unit-test-cluster"},"spec":{"replicas":3,"ports":[{"name": "http","port":"8080"}]}}`, 110 expectedSvcLabelCount: len(labels)}, 111 112 {name: "Coherence with port services, no existing service labels", 113 coherenceJSON: `{"metadata":{"name":"unit-test-cluster"},"spec":{"replicas":3,"ports":[{"name": "http","port":"8080", "service":{"enabled": true}}]}}`, 114 expectedSvcLabelCount: len(labels)}, 115 {name: "Coherence with ports services, with existing labels", 116 coherenceJSON: `{"metadata":{"name":"unit-test-cluster"},"spec":{"replicas":3,"ports":[{"name": "http","port":"8080", "service":{"enabled": true, "labels": {"l1": "val1", "l2": "val2"}}}]}}`, 117 expectedSvcLabelCount: len(labels) + 2}, 118 } 119 for _, tt := range tests { 120 t.Run(tt.name, func(t *testing.T) { 121 assert := asserts.New(t) 122 123 var mocker = gomock.NewController(t) 124 var cli = mocks.NewMockClient(mocker) 125 mockStatus := mocks.NewMockStatusWriter(mocker) 126 127 // expect call to fetch existing coherence StatefulSet 128 cli.EXPECT(). 129 Get(gomock.Any(), types.NamespacedName{Namespace: namespace, Name: "unit-test-cluster"}, gomock.Not(gomock.Nil()), gomock.Any()). 130 DoAndReturn(func(ctx context.Context, name types.NamespacedName, coherence *v1.StatefulSet, opt ...client.GetOption) error { 131 return k8serrors.NewNotFound(k8sschema.GroupResource{}, "test") 132 }) 133 // expect a call to fetch the VerrazzanoCoherenceWorkload 134 cli.EXPECT(). 135 Get(gomock.Any(), types.NamespacedName{Namespace: namespace, Name: "unit-test-verrazzano-coherence-workload"}, gomock.Not(gomock.Nil()), gomock.Any()). 136 DoAndReturn(func(ctx context.Context, name types.NamespacedName, workload *vzapi.VerrazzanoCoherenceWorkload, opt ...client.GetOption) error { 137 // coherenceJSON := `{"metadata":{"name":"unit-test-cluster"},"spec":{"replicas":3}}` 138 // coherenceJSON := `{"metadata":{"name":"unit-test-cluster"},"spec":{"replicas":3,"ports":[{"name": "http","port":"8080"}]}}` 139 workload.Spec.Template = runtime.RawExtension{Raw: []byte(tt.coherenceJSON)} 140 workload.ObjectMeta.Labels = labels 141 workload.APIVersion = vzapi.SchemeGroupVersion.String() 142 workload.Kind = "VerrazzanoCoherenceWorkload" 143 workload.Namespace = namespace 144 workload.ObjectMeta.Generation = 2 145 workload.Status.LastGeneration = "1" 146 return nil 147 }) 148 // expect a call to list the FLUENTD config maps 149 cli.EXPECT(). 150 List(gomock.Any(), getUnstructuredConfigMapList(), gomock.Any()). 151 DoAndReturn(func(ctx context.Context, list *unstructured.UnstructuredList, opts ...client.ListOption) error { 152 // return no resources 153 return nil 154 }) 155 // no config maps found, so expect a call to create a config map with our parsing rules 156 cli.EXPECT(). 157 Create(gomock.Any(), gomock.Any(), gomock.Any()). 158 DoAndReturn(func(ctx context.Context, configMap *corev1.ConfigMap, opts ...client.CreateOption) error { 159 assert.Equal(strings.Join(strings.Split(cohFluentdParsingRules, "{{ .CAFile}}"), ""), configMap.Data["fluentd.conf"]) 160 return nil 161 }) 162 cli.EXPECT(). 163 Get(gomock.Any(), gomock.Eq(client.ObjectKey{Namespace: namespace, Name: appConfigName}), gomock.Not(gomock.Nil()), gomock.Any()). 164 DoAndReturn(func(ctx context.Context, key client.ObjectKey, appConfig *oamcore.ApplicationConfiguration, opt ...client.GetOption) error { 165 component := oamcore.ApplicationConfigurationComponent{ComponentName: componentName} 166 appConfig.Spec.Components = []oamcore.ApplicationConfigurationComponent{component} 167 return nil 168 }) 169 // expect a call to get the application configuration for the workload 170 cli.EXPECT(). 171 Get(gomock.Any(), gomock.Eq(types.NamespacedName{Namespace: namespace, Name: appConfigName}), gomock.Not(gomock.Nil()), gomock.Any()). 172 DoAndReturn(func(ctx context.Context, name types.NamespacedName, appConfig *oamcore.ApplicationConfiguration, opt ...client.GetOption) error { 173 appConfig.Spec.Components = []oamcore.ApplicationConfigurationComponent{{ComponentName: componentName}} 174 return nil 175 }) 176 // expect a call to attempt to get the Coherence CR - return not found 177 cli.EXPECT(). 178 Get(gomock.Any(), gomock.Eq(client.ObjectKey{Namespace: namespace, Name: "unit-test-cluster"}), gomock.Not(gomock.Nil()), gomock.Any()). 179 DoAndReturn(func(ctx context.Context, key client.ObjectKey, u *unstructured.Unstructured, opt ...client.GetOption) error { 180 return k8serrors.NewNotFound(k8sschema.GroupResource{}, "") 181 }) 182 // expect a call to create the Coherence CR 183 cli.EXPECT(). 184 Create(gomock.Any(), gomock.Any(), gomock.Any()). 185 DoAndReturn(func(ctx context.Context, u *unstructured.Unstructured, opts ...client.CreateOption) error { 186 assert.Equal(coherenceAPIVersion, u.GetAPIVersion()) 187 assert.Equal(coherenceKind, u.GetKind()) 188 189 // make sure the OAM component and app name labels were copied to the spec 190 specLabels, _, _ := unstructured.NestedStringMap(u.Object, specLabelsFields...) 191 assert.Equal(labels, specLabels) 192 193 // make sure the OAM component and app name labels were copied to the service section 194 // of every port in the spec 195 ports, _, _ := unstructured.NestedSlice(u.Object, specPortsField...) 196 for _, port := range ports { 197 p := port.(map[string]interface{}) 198 svc := p[specPortSvcFieldName].(map[string]interface{}) 199 assert.NotNil(svc, "ports in Coherence should contain service field") 200 svcLbls := svc["labels"].(map[string]interface{}) 201 assert.NotNilf(svc, "service labels in Coherence object port %s should not be nil", p["name"]) 202 assert.Equalf(tt.expectedSvcLabelCount, len(svcLbls), "expected %d svc labels in port but got %d", tt.expectedSvcLabelCount, len(svcLbls)) 203 for expectedKey, expectedVal := range labels { 204 assert.Equalf(expectedVal, svcLbls[expectedKey], "expected service for port %s to contain label %s=%s", p["name"], expectedKey, expectedVal) 205 } 206 } 207 208 // make sure sidecar.istio.io/inject annotation was added 209 annotations, _, _ := unstructured.NestedStringMap(u.Object, specAnnotationsFields...) 210 assert.Equal(annotations, map[string]string{"sidecar.istio.io/inject": "false"}) 211 return nil 212 }) 213 // expect a call to get the namespace for the Coherence resource 214 cli.EXPECT(). 215 Get(gomock.Any(), gomock.Eq(client.ObjectKey{Namespace: "", Name: namespace}), gomock.Not(gomock.Nil()), gomock.Any()). 216 DoAndReturn(func(ctx context.Context, key client.ObjectKey, namespace *corev1.Namespace, opt ...client.GetOption) error { 217 namespace.Name = "test-namespace" 218 return nil 219 }) 220 221 // expect a call to status update 222 cli.EXPECT().Status().Return(mockStatus).AnyTimes() 223 // expect a call to update the status upgrade version 224 mockStatus.EXPECT(). 225 Update(gomock.Any(), gomock.Any(), gomock.Any()). 226 DoAndReturn(func(ctx context.Context, workload *vzapi.VerrazzanoCoherenceWorkload, opts ...client.UpdateOption) error { 227 return nil 228 }) 229 230 // create a request and reconcile it 231 request := newRequest(namespace, "unit-test-verrazzano-coherence-workload") 232 reconciler := newReconciler(cli) 233 result, err := reconciler.Reconcile(context.TODO(), request) 234 235 mocker.Finish() 236 assert.NoError(err) 237 assert.Equal(false, result.Requeue) 238 }) 239 } 240 } 241 242 // TestReconcileCreateCoherenceWithLogging tests the happy path of reconciling a VerrazzanoCoherenceWorkload with 243 // an attached logging scope. We expect to write out a Coherence CR with the FLUENTD sidecar and 244 // additional JVM args set. 245 // GIVEN a VerrazzanoCoherenceWorkload resource is created with a logging scope 246 // WHEN the controller Reconcile function is called 247 // THEN expect a Coherence CR to be written 248 func TestReconcileCreateCoherenceWithLogging(t *testing.T) { 249 250 assert := asserts.New(t) 251 252 var mocker = gomock.NewController(t) 253 var cli = mocks.NewMockClient(mocker) 254 mockStatus := mocks.NewMockStatusWriter(mocker) 255 256 appConfigName := "unit-test-app-config" 257 componentName := "unit-test-component" 258 fluentdImage := "unit-test-image:latest" 259 labels := map[string]string{oam.LabelAppComponent: componentName, oam.LabelAppName: appConfigName} 260 // set the Fluentd image which is obtained via env then reset at end of test 261 initialDefaultFluentdImage := logging.DefaultFluentdImage 262 logging.DefaultFluentdImage = fluentdImage 263 defer func() { logging.DefaultFluentdImage = initialDefaultFluentdImage }() 264 265 // expect call to fetch existing coherence StatefulSet 266 cli.EXPECT(). 267 Get(gomock.Any(), types.NamespacedName{Namespace: namespace, Name: "unit-test-cluster"}, gomock.Not(gomock.Nil()), gomock.Any()). 268 DoAndReturn(func(ctx context.Context, name types.NamespacedName, coherence *v1.StatefulSet, opt ...client.GetOption) error { 269 return k8serrors.NewNotFound(k8sschema.GroupResource{}, "test") 270 }) 271 272 // expect a call to fetch the VerrazzanoCoherenceWorkload 273 cli.EXPECT(). 274 Get(gomock.Any(), types.NamespacedName{Namespace: namespace, Name: "unit-test-verrazzano-coherence-workload"}, gomock.Not(gomock.Nil()), gomock.Any()). 275 DoAndReturn(func(ctx context.Context, name types.NamespacedName, workload *vzapi.VerrazzanoCoherenceWorkload, opt ...client.GetOption) error { 276 json := `{"metadata":{"name":"unit-test-cluster"},"spec":{"replicas":3}}` 277 workload.Spec.Template = runtime.RawExtension{Raw: []byte(json)} 278 workload.ObjectMeta.Labels = labels 279 workload.APIVersion = vzapi.SchemeGroupVersion.String() 280 workload.Kind = "VerrazzanoCoherenceWorkload" 281 workload.Namespace = namespace 282 workload.ObjectMeta.Generation = 2 283 workload.Status.LastGeneration = "1" 284 return nil 285 }) 286 // expect a call to fetch the OAM application configuration (and the component has an attached logging scope) 287 cli.EXPECT(). 288 Get(gomock.Any(), gomock.Eq(client.ObjectKey{Namespace: namespace, Name: appConfigName}), gomock.Not(gomock.Nil()), gomock.Any()). 289 DoAndReturn(func(ctx context.Context, key client.ObjectKey, appConfig *oamcore.ApplicationConfiguration, opt ...client.GetOption) error { 290 component := oamcore.ApplicationConfigurationComponent{ComponentName: componentName} 291 appConfig.Spec.Components = []oamcore.ApplicationConfigurationComponent{component} 292 return nil 293 }) 294 // expect a call to list the FLUENTD config maps 295 cli.EXPECT(). 296 List(gomock.Any(), getUnstructuredConfigMapList(), gomock.Any()). 297 DoAndReturn(func(ctx context.Context, list *unstructured.UnstructuredList, opts ...client.ListOption) error { 298 // return no resources 299 return nil 300 }) 301 // no config maps found, so expect a call to create a config map with our parsing rules 302 cli.EXPECT(). 303 Create(gomock.Any(), gomock.Any(), gomock.Any()). 304 DoAndReturn(func(ctx context.Context, configMap *corev1.ConfigMap, opts ...client.CreateOption) error { 305 assert.Equal(strings.Join(strings.Split(cohFluentdParsingRules, "{{ .CAFile}}"), ""), configMap.Data["fluentd.conf"]) 306 return nil 307 }) 308 // expect a call to get the application configuration for the workload 309 cli.EXPECT(). 310 Get(gomock.Any(), gomock.Eq(types.NamespacedName{Namespace: namespace, Name: appConfigName}), gomock.Not(gomock.Nil()), gomock.Any()). 311 DoAndReturn(func(ctx context.Context, name types.NamespacedName, appConfig *oamcore.ApplicationConfiguration, opt ...client.GetOption) error { 312 appConfig.Spec.Components = []oamcore.ApplicationConfigurationComponent{{ComponentName: componentName}} 313 return nil 314 }) 315 // expect a call to attempt to get the Coherence CR - return not found 316 cli.EXPECT(). 317 Get(gomock.Any(), gomock.Eq(client.ObjectKey{Namespace: namespace, Name: "unit-test-cluster"}), gomock.Not(gomock.Nil()), gomock.Any()). 318 DoAndReturn(func(ctx context.Context, key client.ObjectKey, u *unstructured.Unstructured, opt ...client.GetOption) error { 319 return k8serrors.NewNotFound(k8sschema.GroupResource{}, "") 320 }) 321 // expect a call to create the Coherence CR 322 cli.EXPECT(). 323 Create(gomock.Any(), gomock.Any(), gomock.Any()). 324 DoAndReturn(func(ctx context.Context, u *unstructured.Unstructured, opts ...client.CreateOption) error { 325 assert.Equal(coherenceAPIVersion, u.GetAPIVersion()) 326 assert.Equal(coherenceKind, u.GetKind()) 327 328 // make sure JVM args were added 329 jvmArgs, _, _ := unstructured.NestedSlice(u.Object, specJvmArgsFields...) 330 assert.Equal(additionalJvmArgs, jvmArgs) 331 332 // make sure side car was added 333 sideCars, _, _ := unstructured.NestedSlice(u.Object, specField, "sideCars") 334 assert.Equal(1, len(sideCars)) 335 // assert correct Fluentd image 336 assert.Equal(fluentdImage, sideCars[0].(map[string]interface{})["image"]) 337 338 // make sure sidecar.istio.io/inject annotation was added 339 annotations, _, _ := unstructured.NestedStringMap(u.Object, specAnnotationsFields...) 340 assert.Equal(annotations, map[string]string{"sidecar.istio.io/inject": "false"}) 341 return nil 342 }) 343 // expect a call to get the namespace for the Coherence resource 344 cli.EXPECT(). 345 Get(gomock.Any(), gomock.Eq(client.ObjectKey{Namespace: "", Name: namespace}), gomock.Not(gomock.Nil()), gomock.Any()). 346 DoAndReturn(func(ctx context.Context, key client.ObjectKey, namespace *corev1.Namespace, opt ...client.GetOption) error { 347 return nil 348 }) 349 350 // expect a call to status update 351 cli.EXPECT().Status().Return(mockStatus).AnyTimes() 352 // expect a call to update the status upgrade version 353 mockStatus.EXPECT(). 354 Update(gomock.Any(), gomock.Any(), gomock.Any()). 355 DoAndReturn(func(ctx context.Context, workload *vzapi.VerrazzanoCoherenceWorkload, opts ...client.UpdateOption) error { 356 return nil 357 }) 358 359 // create a request and reconcile it 360 request := newRequest(namespace, "unit-test-verrazzano-coherence-workload") 361 reconciler := newReconciler(cli) 362 result, err := reconciler.Reconcile(context.TODO(), request) 363 364 mocker.Finish() 365 assert.NoError(err) 366 assert.Equal(false, result.Requeue) 367 } 368 369 // TestReconcileCreateCoherenceWithLogging tests the happy path of reconciling a VerrazzanoCoherenceWorkload with 370 // an attached logging scope. We expect to write out a Coherence CR with the FLUENTD sidecar and 371 // additional JVM args set. 372 // GIVEN a VerrazzanoCoherenceWorkload resource is created with a logging scope 373 // WHEN the controller Reconcile function is called 374 // THEN expect a Coherence CR to be written 375 func TestReconcileCreateCoherenceWithCustomLogging(t *testing.T) { 376 377 assert := asserts.New(t) 378 379 var mocker = gomock.NewController(t) 380 var cli = mocks.NewMockClient(mocker) 381 mockStatus := mocks.NewMockStatusWriter(mocker) 382 383 appConfigName := "unit-test-app-config" 384 componentName := "unit-test-component" 385 fluentdImage := "unit-test-image:latest" 386 workloadName := "unit-test-verrazzano-coherence-workload" 387 labels := map[string]string{oam.LabelAppComponent: componentName, oam.LabelAppName: appConfigName} 388 // set the Fluentd image which is obtained via env then reset at end of test 389 initialDefaultFluentdImage := logging.DefaultFluentdImage 390 logging.DefaultFluentdImage = fluentdImage 391 defer func() { logging.DefaultFluentdImage = initialDefaultFluentdImage }() 392 393 // expect call to fetch existing coherence StatefulSet 394 cli.EXPECT(). 395 Get(gomock.Any(), types.NamespacedName{Namespace: namespace, Name: "unit-test-cluster"}, gomock.Not(gomock.Nil()), gomock.Any()). 396 DoAndReturn(func(ctx context.Context, name types.NamespacedName, coherence *v1.StatefulSet, opt ...client.GetOption) error { 397 return k8serrors.NewNotFound(k8sschema.GroupResource{}, "test") 398 }) 399 400 // expect a call to fetch the VerrazzanoCoherenceWorkload 401 cli.EXPECT(). 402 Get(gomock.Any(), types.NamespacedName{Namespace: namespace, Name: workloadName}, gomock.Not(gomock.Nil()), gomock.Any()). 403 DoAndReturn(func(ctx context.Context, name types.NamespacedName, workload *vzapi.VerrazzanoCoherenceWorkload, opt ...client.GetOption) error { 404 json := `{"metadata":{"name":"unit-test-cluster"},"spec":{"replicas":3}}` 405 workload.Spec.Template = runtime.RawExtension{Raw: []byte(json)} 406 workload.ObjectMeta.Labels = labels 407 workload.APIVersion = vzapi.SchemeGroupVersion.String() 408 workload.Kind = "VerrazzanoCoherenceWorkload" 409 workload.Namespace = namespace 410 workload.Name = workloadName 411 workload.ObjectMeta.Generation = 2 412 workload.Status.LastGeneration = "1" 413 workload.OwnerReferences = []metav1.OwnerReference{ 414 { 415 UID: types.UID(namespace), 416 }, 417 } 418 return nil 419 }) 420 // expect a call to list the logging traits 421 cli.EXPECT(). 422 List(gomock.Any(), &vzapi.LoggingTraitList{TypeMeta: metav1.TypeMeta{Kind: "LoggingTrait", APIVersion: "oam.verrazzano.io/v1alpha1"}}, gomock.Not(gomock.Nil())). 423 DoAndReturn(func(ctx context.Context, loggingTraitList *vzapi.LoggingTraitList, inNamespace client.InNamespace) error { 424 loggingTraitList.Items = []vzapi.LoggingTrait{ 425 { 426 ObjectMeta: metav1.ObjectMeta{ 427 OwnerReferences: []metav1.OwnerReference{ 428 { 429 UID: types.UID(namespace), 430 }, 431 }, 432 }, 433 Spec: vzapi.LoggingTraitSpec{ 434 WorkloadReference: oamrt.TypedReference{ 435 Name: workloadName, 436 }, 437 }, 438 }, 439 } 440 return nil 441 }) 442 // expect a call to get the application configuration for the workload 443 cli.EXPECT(). 444 Get(gomock.Any(), gomock.Eq(types.NamespacedName{Namespace: namespace, Name: appConfigName}), gomock.Not(gomock.Nil()), gomock.Any()). 445 DoAndReturn(func(ctx context.Context, name types.NamespacedName, appConfig *oamcore.ApplicationConfiguration, opt ...client.GetOption) error { 446 appConfig.Spec.Components = []oamcore.ApplicationConfigurationComponent{ 447 { 448 ComponentName: componentName, 449 Traits: []oamcore.ComponentTrait{ 450 { 451 Trait: runtime.RawExtension{ 452 Raw: []byte(strings.ReplaceAll(strings.ReplaceAll(loggingTrait, " ", ""), "\n", "")), 453 }, 454 }, 455 }, 456 }, 457 } 458 return nil 459 }) 460 // expect a call to get the ConfigMap for logging - return not found 461 cli.EXPECT(). 462 Get(gomock.Any(), gomock.Eq(client.ObjectKey{Namespace: namespace, Name: loggingNamePart + "-unit-test-cluster-coherence"}), gomock.Not(gomock.Nil()), gomock.Any()). 463 DoAndReturn(func(ctx context.Context, key client.ObjectKey, configMap *corev1.ConfigMap, opt ...client.GetOption) error { 464 return k8serrors.NewNotFound(k8sschema.GroupResource{ 465 Group: "", 466 Resource: "ConfigMap", 467 }, 468 "logging-stdout-unit-test-cluster-coherence") 469 }) 470 // expect a call to fetch the OAM application configuration (and the component has an attached logging scope) 471 cli.EXPECT(). 472 Get(gomock.Any(), gomock.Eq(client.ObjectKey{Namespace: namespace, Name: appConfigName}), gomock.Not(gomock.Nil()), gomock.Any()). 473 DoAndReturn(func(ctx context.Context, key client.ObjectKey, appConfig *oamcore.ApplicationConfiguration, opt ...client.GetOption) error { 474 component := oamcore.ApplicationConfigurationComponent{ComponentName: componentName} 475 appConfig.Spec.Components = []oamcore.ApplicationConfigurationComponent{component} 476 return nil 477 }) 478 // expect a call to list the FLUENTD config maps 479 cli.EXPECT(). 480 List(gomock.Any(), getUnstructuredConfigMapList(), gomock.Any()). 481 DoAndReturn(func(ctx context.Context, list *unstructured.UnstructuredList, opts ...client.ListOption) error { 482 // return no resources 483 return nil 484 }) 485 // Define expected ConfigMap 486 data := make(map[string]string) 487 data["custom.conf"] = "" 488 customLoggingConfigMap := &corev1.ConfigMap{ 489 TypeMeta: metav1.TypeMeta{ 490 Kind: "", 491 APIVersion: "", 492 }, 493 ObjectMeta: metav1.ObjectMeta{ 494 Name: loggingNamePart + "-unit-test-cluster-coherence", 495 Namespace: namespace, 496 CreationTimestamp: metav1.Time{}, 497 OwnerReferences: []metav1.OwnerReference{ 498 { 499 APIVersion: "oam.verrazzano.io/v1alpha1", 500 Kind: "VerrazzanoCoherenceWorkload", 501 Name: "unit-test-verrazzano-coherence-workload", 502 UID: "", 503 Controller: newTrue(), 504 BlockOwnerDeletion: newTrue(), 505 }, 506 }, 507 }, 508 Data: data, 509 } 510 // expect a call to create the custom logging config map 511 cli.EXPECT(). 512 Create(gomock.Any(), customLoggingConfigMap, gomock.Any()). 513 DoAndReturn(func(ctx context.Context, configMap *corev1.ConfigMap, opts ...client.CreateOption) error { 514 return nil 515 }) 516 // no config maps found, so expect a call to create a config map with our parsing rules 517 cli.EXPECT(). 518 Create(gomock.Any(), gomock.Any(), gomock.Any()). 519 DoAndReturn(func(ctx context.Context, configMap *corev1.ConfigMap, opts ...client.CreateOption) error { 520 assert.Equal(strings.Join(strings.Split(cohFluentdParsingRules, "{{ .CAFile}}"), ""), configMap.Data["fluentd.conf"]) 521 return nil 522 }) 523 // expect a call to attempt to get the Coherence CR - return not found 524 cli.EXPECT(). 525 Get(gomock.Any(), gomock.Eq(client.ObjectKey{Namespace: namespace, Name: "unit-test-cluster"}), gomock.Not(gomock.Nil()), gomock.Any()). 526 DoAndReturn(func(ctx context.Context, key client.ObjectKey, u *unstructured.Unstructured, opt ...client.GetOption) error { 527 return k8serrors.NewNotFound(k8sschema.GroupResource{}, "") 528 }) 529 // expect a call to create the Coherence CR 530 cli.EXPECT(). 531 Create(gomock.Any(), gomock.Any(), gomock.Any()). 532 DoAndReturn(func(ctx context.Context, u *unstructured.Unstructured, opts ...client.CreateOption) error { 533 assert.Equal(coherenceAPIVersion, u.GetAPIVersion()) 534 assert.Equal(coherenceKind, u.GetKind()) 535 536 // make sure the OAM component and app name labels were copied 537 specLabels, _, _ := unstructured.NestedStringMap(u.Object, specLabelsFields...) 538 assert.Equal(labels, specLabels) 539 540 // make sure sidecar.istio.io/inject annotation was added 541 annotations, _, _ := unstructured.NestedStringMap(u.Object, specAnnotationsFields...) 542 assert.Equal(annotations, map[string]string{"sidecar.istio.io/inject": "false"}) 543 return nil 544 }) 545 // expect a call to get the namespace for the Coherence resource 546 cli.EXPECT(). 547 Get(gomock.Any(), gomock.Eq(client.ObjectKey{Namespace: "", Name: namespace}), gomock.Not(gomock.Nil()), gomock.Any()). 548 DoAndReturn(func(ctx context.Context, key client.ObjectKey, namespace *corev1.Namespace, opt ...client.GetOption) error { 549 return nil 550 }) 551 552 // expect a call to status update 553 cli.EXPECT().Status().Return(mockStatus).AnyTimes() 554 // expect a call to update the status upgrade version 555 mockStatus.EXPECT(). 556 Update(gomock.Any(), gomock.Any(), gomock.Any()). 557 DoAndReturn(func(ctx context.Context, workload *vzapi.VerrazzanoCoherenceWorkload, opts ...client.UpdateOption) error { 558 return nil 559 }) 560 561 // create a request and reconcile it 562 request := newRequest(namespace, "unit-test-verrazzano-coherence-workload") 563 reconciler := newReconciler(cli) 564 result, err := reconciler.Reconcile(context.TODO(), request) 565 566 mocker.Finish() 567 assert.NoError(err) 568 assert.Equal(false, result.Requeue) 569 } 570 571 // TestReconcileCreateCoherenceWithLogging tests the happy path of reconciling a VerrazzanoCoherenceWorkload with 572 // an attached logging scope. We expect to write out a Coherence CR with the FLUENTD sidecar and 573 // additional JVM args set. 574 // GIVEN a VerrazzanoCoherenceWorkload resource is created with a logging scope 575 // WHEN the controller Reconcile function is called 576 // THEN expect a Coherence CR to be written 577 func TestReconcileCreateCoherenceWithCustomLoggingConfigMapExists(t *testing.T) { 578 579 assert := asserts.New(t) 580 581 var mocker = gomock.NewController(t) 582 var cli = mocks.NewMockClient(mocker) 583 mockStatus := mocks.NewMockStatusWriter(mocker) 584 585 appConfigName := "unit-test-app-config" 586 componentName := "unit-test-component" 587 fluentdImage := "unit-test-image:latest" 588 workloadName := "unit-test-verrazzano-coherence-workload" 589 labels := map[string]string{oam.LabelAppComponent: componentName, oam.LabelAppName: appConfigName} 590 // set the Fluentd image which is obtained via env then reset at end of test 591 initialDefaultFluentdImage := logging.DefaultFluentdImage 592 logging.DefaultFluentdImage = fluentdImage 593 defer func() { logging.DefaultFluentdImage = initialDefaultFluentdImage }() 594 595 // expect call to fetch existing coherence StatefulSet 596 cli.EXPECT(). 597 Get(gomock.Any(), types.NamespacedName{Namespace: namespace, Name: "unit-test-cluster"}, gomock.Not(gomock.Nil()), gomock.Any()). 598 DoAndReturn(func(ctx context.Context, name types.NamespacedName, coherence *v1.StatefulSet, opt ...client.GetOption) error { 599 return k8serrors.NewNotFound(k8sschema.GroupResource{}, "test") 600 }) 601 602 // expect a call to fetch the VerrazzanoCoherenceWorkload 603 cli.EXPECT(). 604 Get(gomock.Any(), types.NamespacedName{Namespace: namespace, Name: workloadName}, gomock.Not(gomock.Nil()), gomock.Any()). 605 DoAndReturn(func(ctx context.Context, name types.NamespacedName, workload *vzapi.VerrazzanoCoherenceWorkload, opt ...client.GetOption) error { 606 json := `{"metadata":{"name":"unit-test-cluster"},"spec":{"replicas":3}}` 607 workload.Spec.Template = runtime.RawExtension{Raw: []byte(json)} 608 workload.ObjectMeta.Labels = labels 609 workload.APIVersion = vzapi.SchemeGroupVersion.String() 610 workload.Kind = "VerrazzanoCoherenceWorkload" 611 workload.Namespace = namespace 612 workload.Name = workloadName 613 workload.ObjectMeta.Generation = 2 614 workload.Status.LastGeneration = "1" 615 workload.OwnerReferences = []metav1.OwnerReference{ 616 { 617 UID: types.UID(namespace), 618 }, 619 } 620 return nil 621 }) 622 // expect a call to list the logging traits 623 cli.EXPECT(). 624 List(gomock.Any(), &vzapi.LoggingTraitList{TypeMeta: metav1.TypeMeta{Kind: "LoggingTrait", APIVersion: "oam.verrazzano.io/v1alpha1"}}, gomock.Not(gomock.Nil())). 625 DoAndReturn(func(ctx context.Context, loggingTraitList *vzapi.LoggingTraitList, inNamespace client.InNamespace) error { 626 loggingTraitList.Items = []vzapi.LoggingTrait{ 627 { 628 ObjectMeta: metav1.ObjectMeta{ 629 OwnerReferences: []metav1.OwnerReference{ 630 { 631 UID: types.UID(namespace), 632 }, 633 }, 634 }, 635 Spec: vzapi.LoggingTraitSpec{ 636 WorkloadReference: oamrt.TypedReference{ 637 Name: workloadName, 638 }, 639 }, 640 }, 641 } 642 return nil 643 }) 644 // expect a call to get the application configuration for the workload 645 cli.EXPECT(). 646 Get(gomock.Any(), gomock.Eq(types.NamespacedName{Namespace: namespace, Name: appConfigName}), gomock.Not(gomock.Nil()), gomock.Any()). 647 DoAndReturn(func(ctx context.Context, name types.NamespacedName, appConfig *oamcore.ApplicationConfiguration, opt ...client.GetOption) error { 648 649 appConfig.Spec.Components = []oamcore.ApplicationConfigurationComponent{ 650 { 651 ComponentName: componentName, 652 Traits: []oamcore.ComponentTrait{ 653 { 654 Trait: runtime.RawExtension{ 655 Raw: []byte(strings.ReplaceAll(strings.ReplaceAll(loggingTrait, " ", ""), "\n", "")), 656 }, 657 }, 658 }, 659 }, 660 } 661 return nil 662 }) 663 // expect a call to get the ConfigMap for logging - return not found 664 cli.EXPECT(). 665 Get(gomock.Any(), gomock.Eq(client.ObjectKey{Namespace: namespace, Name: loggingNamePart + "-unit-test-cluster-coherence"}), gomock.Not(gomock.Nil()), gomock.Any()). 666 DoAndReturn(func(ctx context.Context, key client.ObjectKey, configMap *corev1.ConfigMap, opt ...client.GetOption) error { 667 return nil 668 }) 669 // expect a call to fetch the OAM application configuration (and the component has an attached logging scope) 670 cli.EXPECT(). 671 Get(gomock.Any(), gomock.Eq(client.ObjectKey{Namespace: namespace, Name: appConfigName}), gomock.Not(gomock.Nil()), gomock.Any()). 672 DoAndReturn(func(ctx context.Context, key client.ObjectKey, appConfig *oamcore.ApplicationConfiguration, opt ...client.GetOption) error { 673 component := oamcore.ApplicationConfigurationComponent{ComponentName: componentName} 674 appConfig.Spec.Components = []oamcore.ApplicationConfigurationComponent{component} 675 return nil 676 }) 677 // expect a call to list the FLUENTD config maps 678 cli.EXPECT(). 679 List(gomock.Any(), getUnstructuredConfigMapList(), gomock.Any()). 680 DoAndReturn(func(ctx context.Context, list *unstructured.UnstructuredList, opts ...client.ListOption) error { 681 // return no resources 682 return nil 683 }) 684 // no config maps found, so expect a call to create a config map with our parsing rules 685 cli.EXPECT(). 686 Create(gomock.Any(), gomock.Any(), gomock.Any()). 687 DoAndReturn(func(ctx context.Context, configMap *corev1.ConfigMap, opts ...client.CreateOption) error { 688 assert.Equal(strings.Join(strings.Split(cohFluentdParsingRules, "{{ .CAFile}}"), ""), configMap.Data["fluentd.conf"]) 689 return nil 690 }) 691 // expect a call to attempt to get the Coherence CR - return not found 692 cli.EXPECT(). 693 Get(gomock.Any(), gomock.Eq(client.ObjectKey{Namespace: namespace, Name: "unit-test-cluster"}), gomock.Not(gomock.Nil()), gomock.Any()). 694 DoAndReturn(func(ctx context.Context, key client.ObjectKey, u *unstructured.Unstructured, opt ...client.GetOption) error { 695 return k8serrors.NewNotFound(k8sschema.GroupResource{}, "") 696 }) 697 // expect a call to create the Coherence CR 698 cli.EXPECT(). 699 Create(gomock.Any(), gomock.Any(), gomock.Any()). 700 DoAndReturn(func(ctx context.Context, u *unstructured.Unstructured, opts ...client.CreateOption) error { 701 assert.Equal(coherenceAPIVersion, u.GetAPIVersion()) 702 assert.Equal(coherenceKind, u.GetKind()) 703 704 // make sure the OAM component and app name labels were copied 705 specLabels, _, _ := unstructured.NestedStringMap(u.Object, specLabelsFields...) 706 assert.Equal(labels, specLabels) 707 708 // make sure sidecar.istio.io/inject annotation was added 709 annotations, _, _ := unstructured.NestedStringMap(u.Object, specAnnotationsFields...) 710 assert.Equal(annotations, map[string]string{"sidecar.istio.io/inject": "false"}) 711 return nil 712 }) 713 // expect a call to get the namespace for the Coherence resource 714 cli.EXPECT(). 715 Get(gomock.Any(), gomock.Eq(client.ObjectKey{Namespace: "", Name: namespace}), gomock.Not(gomock.Nil()), gomock.Any()). 716 DoAndReturn(func(ctx context.Context, key client.ObjectKey, namespace *corev1.Namespace, opt ...client.GetOption) error { 717 return nil 718 }) 719 720 // expect a call to status update 721 cli.EXPECT().Status().Return(mockStatus).AnyTimes() 722 // expect a call to update the status upgrade version 723 mockStatus.EXPECT(). 724 Update(gomock.Any(), gomock.Any(), gomock.Any()). 725 DoAndReturn(func(ctx context.Context, workload *vzapi.VerrazzanoCoherenceWorkload, opts ...client.UpdateOption) error { 726 return nil 727 }) 728 729 // create a request and reconcile it 730 request := newRequest(namespace, "unit-test-verrazzano-coherence-workload") 731 reconciler := newReconciler(cli) 732 result, err := reconciler.Reconcile(context.TODO(), request) 733 734 mocker.Finish() 735 assert.NoError(err) 736 assert.Equal(false, result.Requeue) 737 } 738 739 // TestReconcileUpdateFluentdImage tests reconciling a VerrazzanoCoherenceWorkload when the Fluentd image 740 // in the Coherence sidecar is old and a new image is available. This should result in the latest Fluentd 741 // image being pulled from the env and replaced in the sidecar 742 // GIVEN a VerrazzanoCoherenceWorkload resource that is using an old Fluentd image 743 // WHEN the controller Reconcile function is called 744 // THEN the Fluentd image should be replaced in the Fluentd sidecar 745 func TestReconcileUpdateFluentdImage(t *testing.T) { 746 747 assert := asserts.New(t) 748 749 var mocker = gomock.NewController(t) 750 var cli = mocks.NewMockClient(mocker) 751 mockStatus := mocks.NewMockStatusWriter(mocker) 752 753 appConfigName := "unit-test-app-config" 754 componentName := "unit-test-component" 755 fluentdImage := "unit-test-image:latest" 756 labels := map[string]string{oam.LabelAppComponent: componentName, oam.LabelAppName: appConfigName} 757 758 // set the Fluentd image which is obtained via env then reset at end of test 759 initialDefaultFluentdImage := logging.DefaultFluentdImage 760 logging.DefaultFluentdImage = fluentdImage 761 defer func() { logging.DefaultFluentdImage = initialDefaultFluentdImage }() 762 763 // expect call to fetch existing coherence StatefulSet 764 cli.EXPECT(). 765 Get(gomock.Any(), types.NamespacedName{Namespace: namespace, Name: "unit-test-cluster"}, gomock.Not(gomock.Nil()), gomock.Any()). 766 DoAndReturn(func(ctx context.Context, name types.NamespacedName, coherence *v1.StatefulSet, opt ...client.GetOption) error { 767 // return nil error because Coherence StatefulSet exists 768 return nil 769 }) 770 // expect a call to fetch the VerrazzanoCoherenceWorkload 771 cli.EXPECT(). 772 Get(gomock.Any(), types.NamespacedName{Namespace: namespace, Name: "unit-test-verrazzano-coherence-workload"}, gomock.Not(gomock.Nil()), gomock.Any()). 773 DoAndReturn(func(ctx context.Context, name types.NamespacedName, workload *vzapi.VerrazzanoCoherenceWorkload, opt ...client.GetOption) error { 774 json := `{"metadata":{"name":"unit-test-cluster"},"spec":{"replicas":3}}` 775 workload.Spec.Template = runtime.RawExtension{Raw: []byte(json)} 776 workload.ObjectMeta.Labels = labels 777 workload.APIVersion = vzapi.SchemeGroupVersion.String() 778 workload.Kind = "VerrazzanoCoherenceWorkload" 779 workload.Namespace = namespace 780 workload.ObjectMeta.Generation = 2 781 workload.Status.LastGeneration = "1" 782 return nil 783 }) 784 // expect a call to fetch the OAM application configuration (and the component has an attached logging scope) 785 cli.EXPECT(). 786 Get(gomock.Any(), gomock.Eq(client.ObjectKey{Namespace: namespace, Name: appConfigName}), gomock.Not(gomock.Nil()), gomock.Any()). 787 DoAndReturn(func(ctx context.Context, key client.ObjectKey, appConfig *oamcore.ApplicationConfiguration, opt ...client.GetOption) error { 788 component := oamcore.ApplicationConfigurationComponent{ComponentName: componentName} 789 appConfig.Spec.Components = []oamcore.ApplicationConfigurationComponent{component} 790 return nil 791 }) 792 // expect a call to list the FLUENTD config maps 793 cli.EXPECT(). 794 List(gomock.Any(), getUnstructuredConfigMapList(), gomock.Any()). 795 DoAndReturn(func(ctx context.Context, list *unstructured.UnstructuredList, opts ...client.ListOption) error { 796 // return no resources 797 return nil 798 }) 799 // no config maps found, so expect a call to create a config map with our parsing rules 800 cli.EXPECT(). 801 Create(gomock.Any(), gomock.Any(), gomock.Any()). 802 DoAndReturn(func(ctx context.Context, configMap *corev1.ConfigMap, opts ...client.CreateOption) error { 803 assert.Equal(strings.Join(strings.Split(cohFluentdParsingRules, "{{ .CAFile}}"), ""), configMap.Data["fluentd.conf"]) 804 return nil 805 }) 806 // expect a call to attempt to get the Coherence CR 807 cli.EXPECT(). 808 Get(gomock.Any(), types.NamespacedName{Namespace: namespace, Name: "unit-test-cluster"}, gomock.Not(gomock.Nil()), gomock.Any()). 809 DoAndReturn(func(ctx context.Context, name types.NamespacedName, u *unstructured.Unstructured, opt ...client.GetOption) error { 810 // set the old Fluentd image on the returned obj 811 containers, _, _ := unstructured.NestedSlice(u.Object, "spec", "sideCars") 812 unstructured.SetNestedField(containers[0].(map[string]interface{}), "unit-test-image:existing", "image") 813 unstructured.SetNestedSlice(u.Object, containers, "spec", "sideCars") 814 // return nil error because Coherence StatefulSet exists 815 return nil 816 }) 817 // expect a call to update the Coherence CR 818 cli.EXPECT(). 819 Update(gomock.Any(), gomock.Any(), gomock.Any()). 820 DoAndReturn(func(ctx context.Context, u *unstructured.Unstructured, opts ...client.UpdateOption) error { 821 assert.Equal(coherenceAPIVersion, u.GetAPIVersion()) 822 assert.Equal(coherenceKind, u.GetKind()) 823 824 // make sure JVM args were added 825 jvmArgs, _, _ := unstructured.NestedSlice(u.Object, specJvmArgsFields...) 826 assert.Equal(additionalJvmArgs, jvmArgs) 827 828 // make sure side car was added 829 sideCars, _, _ := unstructured.NestedSlice(u.Object, specField, "sideCars") 830 assert.Equal(1, len(sideCars)) 831 // assert correct Fluentd image 832 assert.Equal(fluentdImage, sideCars[0].(map[string]interface{})["image"]) 833 834 // make sure sidecar.istio.io/inject annotation was added 835 annotations, _, _ := unstructured.NestedStringMap(u.Object, specAnnotationsFields...) 836 assert.Equal(annotations, map[string]string{"sidecar.istio.io/inject": "false"}) 837 return nil 838 }) 839 // expect a call to get the application configuration for the workload 840 cli.EXPECT(). 841 Get(gomock.Any(), gomock.Eq(types.NamespacedName{Namespace: namespace, Name: appConfigName}), gomock.Not(gomock.Nil()), gomock.Any()). 842 DoAndReturn(func(ctx context.Context, name types.NamespacedName, appConfig *oamcore.ApplicationConfiguration, opt ...client.GetOption) error { 843 appConfig.Spec.Components = []oamcore.ApplicationConfigurationComponent{{ComponentName: componentName}} 844 return nil 845 }) 846 // expect a call to get the namespace for the Coherence resource 847 cli.EXPECT(). 848 Get(gomock.Any(), gomock.Eq(client.ObjectKey{Namespace: "", Name: namespace}), gomock.Not(gomock.Nil()), gomock.Any()). 849 DoAndReturn(func(ctx context.Context, key client.ObjectKey, namespace *corev1.Namespace, opt ...client.GetOption) error { 850 return nil 851 }) 852 853 // expect a call to status update 854 cli.EXPECT().Status().Return(mockStatus).AnyTimes() 855 mockStatus.EXPECT(). 856 Update(gomock.Any(), gomock.Any(), gomock.Any()). 857 DoAndReturn(func(ctx context.Context, workload *vzapi.VerrazzanoCoherenceWorkload, opts ...client.UpdateOption) error { 858 return nil 859 }) 860 861 // create a request and reconcile it 862 request := newRequest(namespace, "unit-test-verrazzano-coherence-workload") 863 reconciler := newReconciler(cli) 864 result, err := reconciler.Reconcile(context.TODO(), request) 865 866 mocker.Finish() 867 assert.NoError(err) 868 assert.Equal(false, result.Requeue) 869 } 870 871 // TestReconcileUpdateCR tests reconciling a VerrazzanoCoherenceWorkload when the Coherence 872 // CR already exists. We expect the CR to be updated. 873 // GIVEN a VerrazzanoCoherenceWorkload resource is updated 874 // WHEN the controller Reconcile function is called and the Coherence CR already exists 875 // THEN the Coherence CR is updated 876 func TestReconcileUpdateCR(t *testing.T) { 877 878 assert := asserts.New(t) 879 880 var mocker = gomock.NewController(t) 881 var cli = mocks.NewMockClient(mocker) 882 mockStatus := mocks.NewMockStatusWriter(mocker) 883 884 appConfigName := "unit-test-app-config" 885 componentName := "unit-test-component" 886 existingFluentdImage := "unit-test-image:existing" 887 labels := map[string]string{oam.LabelAppComponent: componentName, oam.LabelAppName: appConfigName} 888 containers := []corev1.Container{{Name: logging.FluentdStdoutSidecarName, Image: existingFluentdImage}} 889 890 // simulate the "replicas" field changing to 3 891 replicasFromWorkload := int64(3) 892 893 // expect call to fetch existing coherence StatefulSet 894 cli.EXPECT(). 895 Get(gomock.Any(), types.NamespacedName{Namespace: namespace, Name: "unit-test-cluster"}, gomock.Not(gomock.Nil()), gomock.Any()). 896 DoAndReturn(func(ctx context.Context, name types.NamespacedName, coherence *v1.StatefulSet, opt ...client.GetOption) error { 897 coherence.Spec.Template.Spec.Containers = containers 898 // return nil error because Coherence StatefulSet exists 899 return nil 900 }) 901 // expect a call to fetch the OAM application configuration 902 cli.EXPECT(). 903 Get(gomock.Any(), gomock.Eq(client.ObjectKey{Namespace: namespace, Name: appConfigName}), gomock.Not(gomock.Nil()), gomock.Any()). 904 DoAndReturn(func(ctx context.Context, key client.ObjectKey, appConfig *oamcore.ApplicationConfiguration, opt ...client.GetOption) error { 905 component := oamcore.ApplicationConfigurationComponent{ComponentName: componentName} 906 appConfig.Spec.Components = []oamcore.ApplicationConfigurationComponent{component} 907 return nil 908 }) 909 // expect a call to fetch the VerrazzanoCoherenceWorkload 910 cli.EXPECT(). 911 Get(gomock.Any(), types.NamespacedName{Namespace: namespace, Name: "unit-test-verrazzano-coherence-workload"}, gomock.Not(gomock.Nil()), gomock.Any()). 912 DoAndReturn(func(ctx context.Context, name types.NamespacedName, workload *vzapi.VerrazzanoCoherenceWorkload, opt ...client.GetOption) error { 913 json := `{"metadata":{"name":"unit-test-cluster"},"spec":{"replicas":` + fmt.Sprint(replicasFromWorkload) + `}}` 914 workload.Spec.Template = runtime.RawExtension{Raw: []byte(json)} 915 workload.ObjectMeta.Labels = labels 916 workload.APIVersion = vzapi.SchemeGroupVersion.String() 917 workload.Kind = "VerrazzanoCoherenceWorkload" 918 workload.Namespace = namespace 919 workload.ObjectMeta.Generation = 2 920 workload.Status.LastGeneration = "1" 921 return nil 922 }) 923 // expect a call to list the FLUENTD config maps 924 cli.EXPECT(). 925 List(gomock.Any(), getUnstructuredConfigMapList(), gomock.Any()). 926 DoAndReturn(func(ctx context.Context, list *unstructured.UnstructuredList, opts ...client.ListOption) error { 927 // return no resources 928 return nil 929 }) 930 // no config maps found, so expect a call to create a config map with our parsing rules 931 cli.EXPECT(). 932 Create(gomock.Any(), gomock.Any(), gomock.Any()). 933 DoAndReturn(func(ctx context.Context, configMap *corev1.ConfigMap, opts ...client.CreateOption) error { 934 assert.Equal(strings.Join(strings.Split(cohFluentdParsingRules, "{{ .CAFile}}"), ""), configMap.Data["fluentd.conf"]) 935 return nil 936 }) 937 // expect a call to get the application configuration for the workload 938 cli.EXPECT(). 939 Get(gomock.Any(), gomock.Eq(types.NamespacedName{Namespace: namespace, Name: appConfigName}), gomock.Not(gomock.Nil()), gomock.Any()). 940 DoAndReturn(func(ctx context.Context, name types.NamespacedName, appConfig *oamcore.ApplicationConfiguration, opt ...client.GetOption) error { 941 appConfig.Spec.Components = []oamcore.ApplicationConfigurationComponent{{ComponentName: componentName}} 942 return nil 943 }) 944 // expect a call to attempt to get the Coherence CR and return an existing resource 945 cli.EXPECT(). 946 Get(gomock.Any(), gomock.Eq(client.ObjectKey{Namespace: namespace, Name: "unit-test-cluster"}), gomock.Not(gomock.Nil()), gomock.Any()). 947 DoAndReturn(func(ctx context.Context, key client.ObjectKey, u *unstructured.Unstructured, opt ...client.GetOption) error { 948 // note this resource has a "replicas" field currently set to 1 949 spec := map[string]interface{}{"replicas": int64(1)} 950 return unstructured.SetNestedField(u.Object, spec, specField) 951 }) 952 // expect a call to update the Coherence CR 953 cli.EXPECT(). 954 Update(gomock.Any(), gomock.Any(), gomock.Any()). 955 DoAndReturn(func(ctx context.Context, u *unstructured.Unstructured, opts ...client.UpdateOption) error { 956 assert.Equal(coherenceAPIVersion, u.GetAPIVersion()) 957 assert.Equal(coherenceKind, u.GetKind()) 958 959 // make sure the replicas field is set to the correct value (from our workload spec) 960 spec, _, _ := unstructured.NestedMap(u.Object, specField) 961 assert.Equal(replicasFromWorkload, spec["replicas"]) 962 return nil 963 }) 964 // expect a call to get the namespace for the Coherence resource 965 cli.EXPECT(). 966 Get(gomock.Any(), gomock.Eq(client.ObjectKey{Namespace: "", Name: namespace}), gomock.Not(gomock.Nil()), gomock.Any()). 967 DoAndReturn(func(ctx context.Context, key client.ObjectKey, namespace *corev1.Namespace, opt ...client.GetOption) error { 968 return nil 969 }) 970 971 // expect a call to status update 972 cli.EXPECT().Status().Return(mockStatus).AnyTimes() 973 // expect a call to update the status upgrade version 974 mockStatus.EXPECT(). 975 Update(gomock.Any(), gomock.Any(), gomock.Any()). 976 DoAndReturn(func(ctx context.Context, workload *vzapi.VerrazzanoCoherenceWorkload, opts ...client.UpdateOption) error { 977 return nil 978 }) 979 980 // create a request and reconcile it 981 request := newRequest(namespace, "unit-test-verrazzano-coherence-workload") 982 reconciler := newReconciler(cli) 983 result, err := reconciler.Reconcile(context.TODO(), request) 984 985 mocker.Finish() 986 assert.NoError(err) 987 assert.Equal(false, result.Requeue) 988 } 989 990 // TestReconcileWithLoggingWithJvmArgs tests the happy path of reconciling a VerrazzanoCoherenceWorkload with 991 // an attached logging scope and the Coherence spec as existing JVM args. We expect to write out a Coherence CR 992 // with the FLUENTD sidecar and a JVM args list that has the user-specified args and the args we add 993 // for logging. 994 // GIVEN a VerrazzanoCoherenceWorkload resource is created with a logging scope and JVM args 995 // WHEN the controller Reconcile function is called 996 // THEN expect a Coherence CR to be written with the combined JVM args 997 func TestReconcileWithLoggingWithJvmArgs(t *testing.T) { 998 assert := asserts.New(t) 999 var mocker = gomock.NewController(t) 1000 var cli = mocks.NewMockClient(mocker) 1001 mockStatus := mocks.NewMockStatusWriter(mocker) 1002 1003 appConfigName := "unit-test-app-config" 1004 componentName := "unit-test-component" 1005 fluentdImage := "unit-test-image:latest" 1006 existingJvmArg := "-Dcoherence.test=unit-test" 1007 labels := map[string]string{oam.LabelAppComponent: componentName, oam.LabelAppName: appConfigName} 1008 1009 // set the Fluentd image which is obtained via env then reset at end of test 1010 initialDefaultFluentdImage := logging.DefaultFluentdImage 1011 logging.DefaultFluentdImage = fluentdImage 1012 defer func() { logging.DefaultFluentdImage = initialDefaultFluentdImage }() 1013 1014 // expect call to fetch existing coherence StatefulSet 1015 cli.EXPECT(). 1016 Get(gomock.Any(), types.NamespacedName{Namespace: namespace, Name: "unit-test-cluster"}, gomock.Not(gomock.Nil()), gomock.Any()). 1017 DoAndReturn(func(ctx context.Context, name types.NamespacedName, coherence *v1.StatefulSet, opt ...client.GetOption) error { 1018 // return nil error because Coherence StatefulSet exists 1019 return nil 1020 }) 1021 // expect a call to fetch the OAM application configuration (and the component has an attached logging scope) 1022 cli.EXPECT(). 1023 Get(gomock.Any(), gomock.Eq(client.ObjectKey{Namespace: namespace, Name: appConfigName}), gomock.Not(gomock.Nil()), gomock.Any()). 1024 DoAndReturn(func(ctx context.Context, key client.ObjectKey, appConfig *oamcore.ApplicationConfiguration, opt ...client.GetOption) error { 1025 component := oamcore.ApplicationConfigurationComponent{ComponentName: componentName} 1026 appConfig.Spec.Components = []oamcore.ApplicationConfigurationComponent{component} 1027 return nil 1028 }) 1029 // expect a call to fetch the VerrazzanoCoherenceWorkload 1030 cli.EXPECT(). 1031 Get(gomock.Any(), types.NamespacedName{Namespace: namespace, Name: "unit-test-verrazzano-coherence-workload"}, gomock.Not(gomock.Nil()), gomock.Any()). 1032 DoAndReturn(func(ctx context.Context, name types.NamespacedName, workload *vzapi.VerrazzanoCoherenceWorkload, opt ...client.GetOption) error { 1033 json := `{"metadata":{"name":"unit-test-cluster"},"spec":{"jvm":{"args": ["` + existingJvmArg + `"]}}}` 1034 workload.Spec.Template = runtime.RawExtension{Raw: []byte(json)} 1035 workload.ObjectMeta.Labels = labels 1036 workload.APIVersion = vzapi.SchemeGroupVersion.String() 1037 workload.Kind = "VerrazzanoCoherenceWorkload" 1038 workload.Namespace = namespace 1039 workload.ObjectMeta.Generation = 2 1040 workload.Status.LastGeneration = "1" 1041 return nil 1042 }) 1043 // expect a call to list the FLUENTD config maps 1044 cli.EXPECT(). 1045 List(gomock.Any(), getUnstructuredConfigMapList(), gomock.Any()). 1046 DoAndReturn(func(ctx context.Context, list *unstructured.UnstructuredList, opts ...client.ListOption) error { 1047 // return no resources 1048 return nil 1049 }) 1050 // no config maps found, so expect a call to create a config map with our parsing rules 1051 cli.EXPECT(). 1052 Create(gomock.Any(), gomock.Any(), gomock.Any()). 1053 DoAndReturn(func(ctx context.Context, configMap *corev1.ConfigMap, opts ...client.CreateOption) error { 1054 assert.Equal(strings.Join(strings.Split(cohFluentdParsingRules, "{{ .CAFile}}"), ""), configMap.Data["fluentd.conf"]) 1055 return nil 1056 }) 1057 // expect a call to get the application configuration for the workload 1058 cli.EXPECT(). 1059 Get(gomock.Any(), gomock.Eq(types.NamespacedName{Namespace: namespace, Name: appConfigName}), gomock.Not(gomock.Nil()), gomock.Any()). 1060 DoAndReturn(func(ctx context.Context, name types.NamespacedName, appConfig *oamcore.ApplicationConfiguration, opt ...client.GetOption) error { 1061 appConfig.Spec.Components = []oamcore.ApplicationConfigurationComponent{{ComponentName: componentName}} 1062 return nil 1063 }) 1064 // expect a call to attempt to get the Coherence CR - return not found 1065 cli.EXPECT(). 1066 Get(gomock.Any(), gomock.Eq(client.ObjectKey{Namespace: namespace, Name: "unit-test-cluster"}), gomock.Not(gomock.Nil()), gomock.Any()). 1067 DoAndReturn(func(ctx context.Context, key client.ObjectKey, u *unstructured.Unstructured, opt ...client.GetOption) error { 1068 return k8serrors.NewNotFound(k8sschema.GroupResource{}, "") 1069 }) 1070 // expect a call to create the Coherence CR 1071 cli.EXPECT(). 1072 Create(gomock.Any(), gomock.Any(), gomock.Any()). 1073 DoAndReturn(func(ctx context.Context, u *unstructured.Unstructured, opts ...client.CreateOption) error { 1074 assert.Equal(coherenceAPIVersion, u.GetAPIVersion()) 1075 assert.Equal(coherenceKind, u.GetKind()) 1076 1077 // make sure JVM args were added and that the existing arg is still present 1078 jvmArgs, _, _ := unstructured.NestedStringSlice(u.Object, specJvmArgsFields...) 1079 assert.Equal(len(additionalJvmArgs)+1, len(jvmArgs)) 1080 assert.True(vzstring.SliceContainsString(jvmArgs, existingJvmArg)) 1081 1082 // make sure side car was added 1083 sideCars, _, _ := unstructured.NestedSlice(u.Object, specField, "sideCars") 1084 assert.Equal(1, len(sideCars)) 1085 1086 // make sure sidecar.istio.io/inject annotation was added 1087 annotations, _, _ := unstructured.NestedStringMap(u.Object, specAnnotationsFields...) 1088 assert.Equal(annotations, map[string]string{"sidecar.istio.io/inject": "false"}) 1089 return nil 1090 }) 1091 // expect a call to get the namespace for the Coherence resource 1092 cli.EXPECT(). 1093 Get(gomock.Any(), gomock.Eq(client.ObjectKey{Namespace: "", Name: namespace}), gomock.Not(gomock.Nil()), gomock.Any()). 1094 DoAndReturn(func(ctx context.Context, key client.ObjectKey, namespace *corev1.Namespace, opt ...client.GetOption) error { 1095 return nil 1096 }) 1097 1098 // expect a call to status update 1099 cli.EXPECT().Status().Return(mockStatus).AnyTimes() 1100 // expect a call to update the status upgrade version 1101 mockStatus.EXPECT(). 1102 Update(gomock.Any(), gomock.Any(), gomock.Any()). 1103 DoAndReturn(func(ctx context.Context, workload *vzapi.VerrazzanoCoherenceWorkload, opts ...client.UpdateOption) error { 1104 return nil 1105 }) 1106 1107 // create a request and reconcile it 1108 request := newRequest(namespace, "unit-test-verrazzano-coherence-workload") 1109 reconciler := newReconciler(cli) 1110 result, err := reconciler.Reconcile(context.TODO(), request) 1111 1112 mocker.Finish() 1113 assert.NoError(err) 1114 assert.Equal(false, result.Requeue) 1115 } 1116 1117 // TestReconcileErrorOnCreate tests reconciling a VerrazzanoCoherenceWorkload and an 1118 // error occurs attempting to create the Coherence CR. 1119 // GIVEN a VerrazzanoCoherenceWorkload resource is created 1120 // WHEN the controller Reconcile function is called and there is an error creating the Coherence CR 1121 // THEN expect an error to be returned 1122 func TestReconcileErrorOnCreate(t *testing.T) { 1123 assert := asserts.New(t) 1124 var mocker = gomock.NewController(t) 1125 var cli = mocks.NewMockClient(mocker) 1126 1127 appConfigName := "unit-test-app-config" 1128 componentName := "unit-test-component" 1129 labels := map[string]string{oam.LabelAppComponent: componentName, oam.LabelAppName: appConfigName} 1130 1131 // expect call to fetch existing coherence StatefulSet 1132 cli.EXPECT(). 1133 Get(gomock.Any(), types.NamespacedName{Namespace: namespace, Name: "unit-test-cluster"}, gomock.Not(gomock.Nil()), gomock.Any()). 1134 DoAndReturn(func(ctx context.Context, name types.NamespacedName, coherence *v1.StatefulSet, opt ...client.GetOption) error { 1135 return k8serrors.NewNotFound(k8sschema.GroupResource{}, "test") 1136 }) 1137 // expect a call to fetch the OAM application configuration 1138 cli.EXPECT(). 1139 Get(gomock.Any(), gomock.Eq(client.ObjectKey{Namespace: namespace, Name: appConfigName}), gomock.Not(gomock.Nil()), gomock.Any()). 1140 DoAndReturn(func(ctx context.Context, key client.ObjectKey, appConfig *oamcore.ApplicationConfiguration, opt ...client.GetOption) error { 1141 component := oamcore.ApplicationConfigurationComponent{ComponentName: componentName} 1142 appConfig.Spec.Components = []oamcore.ApplicationConfigurationComponent{component} 1143 return nil 1144 }) 1145 // expect a call to fetch the VerrazzanoCoherenceWorkload 1146 cli.EXPECT(). 1147 Get(gomock.Any(), types.NamespacedName{Namespace: namespace, Name: "unit-test-verrazzano-coherence-workload"}, gomock.Not(gomock.Nil()), gomock.Any()). 1148 DoAndReturn(func(ctx context.Context, name types.NamespacedName, workload *vzapi.VerrazzanoCoherenceWorkload, opt ...client.GetOption) error { 1149 json := `{"metadata":{"name":"unit-test-cluster"},"spec":{"replicas":3}}` 1150 workload.Spec.Template = runtime.RawExtension{Raw: []byte(json)} 1151 workload.ObjectMeta.Labels = labels 1152 workload.APIVersion = vzapi.SchemeGroupVersion.String() 1153 workload.Kind = "VerrazzanoCoherenceWorkload" 1154 workload.Namespace = namespace 1155 workload.ObjectMeta.Generation = 2 1156 workload.Status.LastGeneration = "1" 1157 return nil 1158 }) 1159 // expect a call to list the FLUENTD config maps 1160 cli.EXPECT(). 1161 List(gomock.Any(), getUnstructuredConfigMapList(), gomock.Any()). 1162 DoAndReturn(func(ctx context.Context, list *unstructured.UnstructuredList, opts ...client.ListOption) error { 1163 // return no resources 1164 return nil 1165 }) 1166 // no config maps found, so expect a call to create a config map with our parsing rules 1167 cli.EXPECT(). 1168 Create(gomock.Any(), gomock.Any(), gomock.Any()). 1169 DoAndReturn(func(ctx context.Context, configMap *corev1.ConfigMap, opts ...client.CreateOption) error { 1170 assert.Equal(strings.Join(strings.Split(cohFluentdParsingRules, "{{ .CAFile}}"), ""), configMap.Data["fluentd.conf"]) 1171 return nil 1172 }) 1173 // expect a call to get the application configuration for the workload 1174 cli.EXPECT(). 1175 Get(gomock.Any(), gomock.Eq(types.NamespacedName{Namespace: namespace, Name: appConfigName}), gomock.Not(gomock.Nil()), gomock.Any()). 1176 DoAndReturn(func(ctx context.Context, name types.NamespacedName, appConfig *oamcore.ApplicationConfiguration, opt ...client.GetOption) error { 1177 appConfig.Spec.Components = []oamcore.ApplicationConfigurationComponent{{ComponentName: componentName}} 1178 return nil 1179 }) 1180 // expect a call to attempt to get the Coherence CR - return not found 1181 cli.EXPECT(). 1182 Get(gomock.Any(), gomock.Eq(client.ObjectKey{Namespace: namespace, Name: "unit-test-cluster"}), gomock.Not(gomock.Nil()), gomock.Any()). 1183 DoAndReturn(func(ctx context.Context, key client.ObjectKey, u *unstructured.Unstructured, opt ...client.GetOption) error { 1184 return k8serrors.NewNotFound(k8sschema.GroupResource{}, "") 1185 }) 1186 // expect a call to create the Coherence CR and return an error 1187 cli.EXPECT(). 1188 Create(gomock.Any(), gomock.Any(), gomock.Any()). 1189 DoAndReturn(func(ctx context.Context, u *unstructured.Unstructured, opts ...client.CreateOption) error { 1190 assert.Equal(coherenceAPIVersion, u.GetAPIVersion()) 1191 assert.Equal(coherenceKind, u.GetKind()) 1192 return k8serrors.NewBadRequest("An error has occurred") 1193 }) 1194 1195 // create a request and reconcile it 1196 request := newRequest(namespace, "unit-test-verrazzano-coherence-workload") 1197 reconciler := newReconciler(cli) 1198 result, err := reconciler.Reconcile(context.TODO(), request) 1199 1200 mocker.Finish() 1201 assert.Nil(err) 1202 assert.True(result.Requeue) 1203 } 1204 1205 // TestReconcileWorkloadNotFound tests reconciling a VerrazzanoCoherenceWorkload when the workload 1206 // cannot be fetched. This happens when the workload has been deleted by the OAM runtime. 1207 // GIVEN a VerrazzanoCoherenceWorkload resource has been deleted 1208 // WHEN the controller Reconcile function is called and we attempt to fetch the workload 1209 // THEN return success from the controller as there is nothing more to do 1210 func TestReconcileWorkloadNotFound(t *testing.T) { 1211 assert := asserts.New(t) 1212 1213 var mocker = gomock.NewController(t) 1214 var cli = mocks.NewMockClient(mocker) 1215 1216 // expect a call to fetch the VerrazzanoCoherenceWorkload 1217 cli.EXPECT(). 1218 Get(gomock.Any(), types.NamespacedName{Namespace: namespace, Name: "unit-test-verrazzano-coherence-workload"}, gomock.Not(gomock.Nil()), gomock.Any()). 1219 DoAndReturn(func(ctx context.Context, name types.NamespacedName, workload *vzapi.VerrazzanoCoherenceWorkload, opt ...client.GetOption) error { 1220 return k8serrors.NewNotFound(k8sschema.GroupResource{}, "") 1221 }) 1222 1223 // create a request and reconcile it 1224 request := newRequest(namespace, "unit-test-verrazzano-coherence-workload") 1225 reconciler := newReconciler(cli) 1226 result, err := reconciler.Reconcile(context.TODO(), request) 1227 1228 mocker.Finish() 1229 assert.NoError(err) 1230 assert.Equal(false, result.Requeue) 1231 } 1232 1233 // TestReconcileFetchWorkloadError tests reconciling a VerrazzanoCoherenceWorkload when the workload 1234 // cannot be fetched due to an unexpected error. 1235 // GIVEN a VerrazzanoCoherenceWorkload resource has been created 1236 // WHEN the controller Reconcile function is called and we attempt to fetch the workload and get an error 1237 // THEN return the error 1238 func TestReconcileFetchWorkloadError(t *testing.T) { 1239 assert := asserts.New(t) 1240 1241 var mocker = gomock.NewController(t) 1242 var cli = mocks.NewMockClient(mocker) 1243 1244 // expect a call to fetch the VerrazzanoCoherenceWorkload 1245 cli.EXPECT(). 1246 Get(gomock.Any(), types.NamespacedName{Namespace: namespace, Name: "unit-test-verrazzano-coherence-workload"}, gomock.Not(gomock.Nil()), gomock.Any()). 1247 DoAndReturn(func(ctx context.Context, name types.NamespacedName, workload *vzapi.VerrazzanoCoherenceWorkload, opt ...client.GetOption) error { 1248 return k8serrors.NewBadRequest("An error has occurred") 1249 }) 1250 1251 // create a request and reconcile it 1252 request := newRequest(namespace, "unit-test-verrazzano-coherence-workload") 1253 reconciler := newReconciler(cli) 1254 result, err := reconciler.Reconcile(context.TODO(), request) 1255 1256 mocker.Finish() 1257 assert.Nil(err) 1258 assert.True(result.Requeue) 1259 } 1260 1261 // TestCreateUpdateDestinationRuleCreate tests creation of a destination rule 1262 // GIVEN the destination rule does not exist 1263 // WHEN the controller createOrUpdateDestinationRule function is called 1264 // THEN expect no error to be returned and destination rule is created 1265 func TestCreateUpdateDestinationRuleCreate(t *testing.T) { 1266 assert := asserts.New(t) 1267 1268 var mocker = gomock.NewController(t) 1269 var cli = mocks.NewMockClient(mocker) 1270 1271 // Expect a call to get a destination rule and return that it is not found. 1272 cli.EXPECT(). 1273 Get(gomock.Any(), types.NamespacedName{Namespace: "test-namespace", Name: "test-app"}, gomock.Not(gomock.Nil()), gomock.Any()). 1274 Return(k8serrors.NewNotFound(k8sschema.GroupResource{Group: "test-space", Resource: "DestinationRule"}, "test-space-myapp-dr")) 1275 1276 // Expect a call to get the appconfig resource to set the owner reference 1277 cli.EXPECT(). 1278 Get(gomock.Any(), types.NamespacedName{Namespace: "test-namespace", Name: "test-app"}, gomock.Not(gomock.Nil()), gomock.Any()). 1279 DoAndReturn(func(ctx context.Context, name types.NamespacedName, app *oamcore.ApplicationConfiguration, opt ...client.GetOption) error { 1280 app.TypeMeta = metav1.TypeMeta{ 1281 APIVersion: "core.oam.dev/v1alpha2", 1282 Kind: "ApplicationConfiguration", 1283 } 1284 return nil 1285 }) 1286 1287 // Expect a call to create the destinationrule and return success 1288 cli.EXPECT(). 1289 Create(gomock.Any(), gomock.Any(), gomock.Any()). 1290 DoAndReturn(func(ctx context.Context, dr *istioclient.DestinationRule, opts ...client.CreateOption) error { 1291 assert.Equal(destinationRuleKind, dr.Kind) 1292 assert.Equal(destinationRuleAPIVersion, dr.APIVersion) 1293 assert.Equal("*.test-namespace.svc.cluster.local", dr.Spec.Host) 1294 assert.Equal(istionet.ClientTLSSettings_ISTIO_MUTUAL, dr.Spec.TrafficPolicy.Tls.Mode) 1295 assert.Equal(uint32(coherenceExtendPort), dr.Spec.TrafficPolicy.PortLevelSettings[0].Port.Number) 1296 assert.Equal(istionet.ClientTLSSettings_DISABLE, dr.Spec.TrafficPolicy.PortLevelSettings[0].Tls.Mode) 1297 assert.Equal(1, len(dr.OwnerReferences)) 1298 assert.Equal("ApplicationConfiguration", dr.OwnerReferences[0].Kind) 1299 assert.Equal("core.oam.dev/v1alpha2", dr.OwnerReferences[0].APIVersion) 1300 return nil 1301 }) 1302 1303 scheme := runtime.NewScheme() 1304 istioclient.AddToScheme(scheme) 1305 core.AddToScheme(scheme) 1306 vzapi.AddToScheme(scheme) 1307 reconciler := Reconciler{Client: cli, Scheme: scheme} 1308 1309 namespaceLabels := make(map[string]string) 1310 namespaceLabels["istio-injection"] = "enabled" 1311 workloadLabels := make(map[string]string) 1312 workloadLabels["app.oam.dev/name"] = "test-app" 1313 err := reconciler.createOrUpdateDestinationRule(context.Background(), vzlog.DefaultLogger(), "test-namespace", namespaceLabels, workloadLabels) 1314 mocker.Finish() 1315 assert.NoError(err) 1316 } 1317 1318 // TestCreateUpdateDestinationRuleUpdate tests update of a destination rule 1319 // GIVEN the destination rule exist 1320 // WHEN the controller createOrUpdateDestinationRule function is called 1321 // THEN expect no error to be returned and destination rule is updated 1322 func TestCreateUpdateDestinationRuleUpdate(t *testing.T) { 1323 assert := asserts.New(t) 1324 1325 var mocker = gomock.NewController(t) 1326 var cli = mocks.NewMockClient(mocker) 1327 1328 // Expect a call to get a destination rule and return that it was found. 1329 cli.EXPECT(). 1330 Get(gomock.Any(), types.NamespacedName{Namespace: "test-namespace", Name: "test-app"}, gomock.Not(gomock.Nil()), gomock.Any()). 1331 DoAndReturn(func(ctx context.Context, name types.NamespacedName, dr *istioclient.DestinationRule, opt ...client.GetOption) error { 1332 dr.TypeMeta = metav1.TypeMeta{ 1333 APIVersion: destinationRuleAPIVersion, 1334 Kind: destinationRuleKind} 1335 return nil 1336 }) 1337 1338 // Expect a call to get the appconfig resource to set the owner reference 1339 cli.EXPECT(). 1340 Get(gomock.Any(), types.NamespacedName{Namespace: "test-namespace", Name: "test-app"}, gomock.Not(gomock.Nil()), gomock.Any()). 1341 DoAndReturn(func(ctx context.Context, name types.NamespacedName, app *oamcore.ApplicationConfiguration, opt ...client.GetOption) error { 1342 app.TypeMeta = metav1.TypeMeta{ 1343 APIVersion: "core.oam.dev/v1alpha2", 1344 Kind: "ApplicationConfiguration", 1345 } 1346 return nil 1347 }) 1348 1349 // Expect a call to update the destinationrule and return success 1350 cli.EXPECT(). 1351 Update(gomock.Any(), gomock.Any(), gomock.Any()). 1352 DoAndReturn(func(ctx context.Context, dr *istioclient.DestinationRule, opts ...client.UpdateOption) error { 1353 assert.Equal(destinationRuleKind, dr.Kind) 1354 assert.Equal(destinationRuleAPIVersion, dr.APIVersion) 1355 assert.Equal("*.test-namespace.svc.cluster.local", dr.Spec.Host) 1356 assert.Equal(istionet.ClientTLSSettings_ISTIO_MUTUAL, dr.Spec.TrafficPolicy.Tls.Mode) 1357 assert.Equal(uint32(coherenceExtendPort), dr.Spec.TrafficPolicy.PortLevelSettings[0].Port.Number) 1358 assert.Equal(istionet.ClientTLSSettings_DISABLE, dr.Spec.TrafficPolicy.PortLevelSettings[0].Tls.Mode) 1359 assert.Equal(1, len(dr.OwnerReferences)) 1360 assert.Equal("ApplicationConfiguration", dr.OwnerReferences[0].Kind) 1361 assert.Equal("core.oam.dev/v1alpha2", dr.OwnerReferences[0].APIVersion) 1362 return nil 1363 }) 1364 1365 scheme := runtime.NewScheme() 1366 istioclient.AddToScheme(scheme) 1367 core.AddToScheme(scheme) 1368 vzapi.AddToScheme(scheme) 1369 reconciler := Reconciler{Client: cli, Scheme: scheme} 1370 1371 namespaceLabels := make(map[string]string) 1372 namespaceLabels["istio-injection"] = "enabled" 1373 workloadLabels := make(map[string]string) 1374 workloadLabels["app.oam.dev/name"] = "test-app" 1375 err := reconciler.createOrUpdateDestinationRule(context.Background(), vzlog.DefaultLogger(), "test-namespace", namespaceLabels, workloadLabels) 1376 mocker.Finish() 1377 assert.NoError(err) 1378 } 1379 1380 // TestCreateUpdateDestinationRuleNoOamLabel tests failure when no OAM label found 1381 // GIVEN no app.oam.dev/name label specified 1382 // WHEN the controller createOrUpdateDestinationRule function is called 1383 // THEN expect an error to be returned 1384 func TestCreateUpdateDestinationRuleNoOamLabel(t *testing.T) { 1385 assert := asserts.New(t) 1386 1387 reconciler := Reconciler{} 1388 namespaceLabels := make(map[string]string) 1389 namespaceLabels["istio-injection"] = "enabled" 1390 workloadLabels := make(map[string]string) 1391 err := reconciler.createOrUpdateDestinationRule(context.Background(), vzlog.DefaultLogger(), "test-namespace", namespaceLabels, workloadLabels) 1392 assert.Equal("OAM app name label missing from metadata, unable to generate destination rule name", err.Error()) 1393 } 1394 1395 // TestCreateUpdateDestinationRuleNoIstioLabel tests failure when no istio label found 1396 // GIVEN no istio-injection label specified 1397 // WHEN the controller createOrUpdateDestinationRule function is called 1398 // THEN expect an error to be returned 1399 func TestCreateUpdateDestinationRuleNoLabel(t *testing.T) { 1400 assert := asserts.New(t) 1401 1402 reconciler := Reconciler{} 1403 namespaceLabels := make(map[string]string) 1404 workloadLabels := make(map[string]string) 1405 err := reconciler.createOrUpdateDestinationRule(context.Background(), vzlog.DefaultLogger(), "test-namespace", namespaceLabels, workloadLabels) 1406 assert.NoError(err) 1407 } 1408 1409 // newScheme creates a new scheme that includes this package's object to use for testing 1410 func newScheme() *runtime.Scheme { 1411 scheme := runtime.NewScheme() 1412 vzapi.AddToScheme(scheme) 1413 return scheme 1414 } 1415 1416 // newReconciler creates a new reconciler for testing 1417 // c - The K8s client to inject into the reconciler 1418 func newReconciler(c client.Client) Reconciler { 1419 scheme := newScheme() 1420 metricsReconciler := &metricstrait.Reconciler{Client: c, Scheme: scheme, Scraper: "verrazzano-system/vmi-system-prometheus-0"} 1421 return Reconciler{ 1422 Client: c, 1423 Log: zap.S().With("test"), 1424 Scheme: scheme, 1425 Metrics: metricsReconciler, 1426 } 1427 } 1428 1429 // newRequest creates a new reconciler request for testing 1430 // namespace - The namespace to use in the request 1431 // name - The name to use in the request 1432 func newRequest(namespace string, name string) ctrl.Request { 1433 return ctrl.Request{ 1434 NamespacedName: types.NamespacedName{ 1435 Namespace: namespace, 1436 Name: name, 1437 }, 1438 } 1439 } 1440 1441 // Used for bool in struct literal 1442 func newTrue() *bool { 1443 b := true 1444 return &b 1445 } 1446 1447 func getUnstructuredConfigMapList() *unstructured.UnstructuredList { 1448 unstructuredConfigMapList := &unstructured.UnstructuredList{} 1449 unstructuredConfigMapList.SetAPIVersion("v1") 1450 unstructuredConfigMapList.SetKind("ConfigMap") 1451 return unstructuredConfigMapList 1452 } 1453 1454 // TestReconcileRestart tests reconciling a VerrazzanoCoherenceWorkload with the restart-version specified in its annotations. 1455 // This should result in restart-version annotation written to the Coherence CR. 1456 // GIVEN a VerrazzanoCoherenceWorkload resource 1457 // WHEN the controller Reconcile function is called and the restart-version is specified in annotations 1458 // THEN the restart-version annotation written to the Coherence CR 1459 func TestReconcileRestart(t *testing.T) { 1460 assert := asserts.New(t) 1461 1462 var mocker = gomock.NewController(t) 1463 var cli = mocks.NewMockClient(mocker) 1464 mockStatus := mocks.NewMockStatusWriter(mocker) 1465 1466 appConfigName := "unit-test-app-config" 1467 componentName := "unit-test-component" 1468 fluentdImage := "unit-test-image:latest" 1469 labels := map[string]string{oam.LabelAppComponent: componentName, oam.LabelAppName: appConfigName} 1470 annotations := map[string]string{vzconst.RestartVersionAnnotation: testRestartVersion} 1471 1472 // set the Fluentd image which is obtained via env then reset at end of test 1473 initialDefaultFluentdImage := logging.DefaultFluentdImage 1474 logging.DefaultFluentdImage = fluentdImage 1475 defer func() { logging.DefaultFluentdImage = initialDefaultFluentdImage }() 1476 1477 // expect call to fetch existing coherence StatefulSet 1478 cli.EXPECT(). 1479 Get(gomock.Any(), types.NamespacedName{Namespace: namespace, Name: "unit-test-cluster"}, gomock.Not(gomock.Nil()), gomock.Any()). 1480 DoAndReturn(func(ctx context.Context, name types.NamespacedName, coherence *v1.StatefulSet, opt ...client.GetOption) error { 1481 // return nil error because Coherence StatefulSet exists 1482 return nil 1483 }) 1484 // expect a call to fetch the VerrazzanoCoherenceWorkload 1485 cli.EXPECT(). 1486 Get(gomock.Any(), types.NamespacedName{Namespace: namespace, Name: "unit-test-verrazzano-coherence-workload"}, gomock.Not(gomock.Nil()), gomock.Any()). 1487 DoAndReturn(func(ctx context.Context, name types.NamespacedName, workload *vzapi.VerrazzanoCoherenceWorkload, opt ...client.GetOption) error { 1488 json := `{"metadata":{"name":"unit-test-cluster"},"spec":{"replicas":3}}` 1489 workload.Spec.Template = runtime.RawExtension{Raw: []byte(json)} 1490 workload.ObjectMeta.Labels = labels 1491 workload.ObjectMeta.Annotations = annotations 1492 workload.APIVersion = vzapi.SchemeGroupVersion.String() 1493 workload.Kind = "VerrazzanoCoherenceWorkload" 1494 workload.Namespace = namespace 1495 workload.ObjectMeta.Generation = 2 1496 workload.Status.LastGeneration = "1" 1497 return nil 1498 }) 1499 // expect a call to fetch the OAM application configuration (and the component has an attached logging scope) 1500 cli.EXPECT(). 1501 Get(gomock.Any(), gomock.Eq(client.ObjectKey{Namespace: namespace, Name: appConfigName}), gomock.Not(gomock.Nil()), gomock.Any()). 1502 DoAndReturn(func(ctx context.Context, key client.ObjectKey, appConfig *oamcore.ApplicationConfiguration, opt ...client.GetOption) error { 1503 component := oamcore.ApplicationConfigurationComponent{ComponentName: componentName} 1504 appConfig.Spec.Components = []oamcore.ApplicationConfigurationComponent{component} 1505 return nil 1506 }) 1507 // expect a call to list the FLUENTD config maps 1508 cli.EXPECT(). 1509 List(gomock.Any(), getUnstructuredConfigMapList(), gomock.Any()). 1510 DoAndReturn(func(ctx context.Context, list *unstructured.UnstructuredList, opts ...client.ListOption) error { 1511 // return no resources 1512 return nil 1513 }) 1514 // no config maps found, so expect a call to create a config map with our parsing rules 1515 cli.EXPECT(). 1516 Create(gomock.Any(), gomock.Any(), gomock.Any()). 1517 DoAndReturn(func(ctx context.Context, configMap *corev1.ConfigMap, opts ...client.CreateOption) error { 1518 assert.Equal(strings.Join(strings.Split(cohFluentdParsingRules, "{{ .CAFile}}"), ""), configMap.Data["fluentd.conf"]) 1519 return nil 1520 }) 1521 // expect a call to attempt to get the Coherence CR 1522 cli.EXPECT(). 1523 Get(gomock.Any(), types.NamespacedName{Namespace: namespace, Name: "unit-test-cluster"}, gomock.Not(gomock.Nil()), gomock.Any()). 1524 DoAndReturn(func(ctx context.Context, name types.NamespacedName, u *unstructured.Unstructured, opt ...client.GetOption) error { 1525 // set the old Fluentd image on the returned obj 1526 containers, _, _ := unstructured.NestedSlice(u.Object, "spec", "sideCars") 1527 unstructured.SetNestedField(containers[0].(map[string]interface{}), "unit-test-image:existing", "image") 1528 unstructured.SetNestedSlice(u.Object, containers, "spec", "sideCars") 1529 // return nil error because Coherence StatefulSet exists 1530 return nil 1531 }) 1532 // expect a call to update the Coherence CR 1533 cli.EXPECT(). 1534 Update(gomock.Any(), gomock.AssignableToTypeOf(&unstructured.Unstructured{}), gomock.Any()). 1535 DoAndReturn(func(ctx context.Context, u *unstructured.Unstructured, opts ...client.UpdateOption) error { 1536 assert.Equal(coherenceAPIVersion, u.GetAPIVersion()) 1537 assert.Equal(coherenceKind, u.GetKind()) 1538 1539 // make sure JVM args were added 1540 jvmArgs, _, _ := unstructured.NestedSlice(u.Object, specJvmArgsFields...) 1541 assert.Equal(additionalJvmArgs, jvmArgs) 1542 1543 // make sure side car was added 1544 sideCars, _, _ := unstructured.NestedSlice(u.Object, specField, "sideCars") 1545 assert.Equal(1, len(sideCars)) 1546 // assert correct Fluentd image 1547 assert.Equal(fluentdImage, sideCars[0].(map[string]interface{})["image"]) 1548 1549 // make sure sidecar.istio.io/inject annotation was added 1550 annotations, _, _ := unstructured.NestedStringMap(u.Object, specAnnotationsFields...) 1551 assert.Equal(annotations, map[string]string{"sidecar.istio.io/inject": "false", vzconst.RestartVersionAnnotation: testRestartVersion}) 1552 return nil 1553 }) 1554 // expect a call to get the application configuration for the workload 1555 cli.EXPECT(). 1556 Get(gomock.Any(), gomock.Eq(types.NamespacedName{Namespace: namespace, Name: appConfigName}), gomock.Not(gomock.Nil()), gomock.Any()). 1557 DoAndReturn(func(ctx context.Context, name types.NamespacedName, appConfig *oamcore.ApplicationConfiguration, opt ...client.GetOption) error { 1558 appConfig.Spec.Components = []oamcore.ApplicationConfigurationComponent{{ComponentName: componentName}} 1559 return nil 1560 }) 1561 // expect a call to get the namespace for the Coherence resource 1562 cli.EXPECT(). 1563 Get(gomock.Any(), gomock.Eq(client.ObjectKey{Namespace: "", Name: namespace}), gomock.Not(gomock.Nil()), gomock.Any()). 1564 DoAndReturn(func(ctx context.Context, key client.ObjectKey, namespace *corev1.Namespace, opt ...client.GetOption) error { 1565 return nil 1566 }) 1567 1568 // expect a call to status update 1569 cli.EXPECT().Status().Return(mockStatus).AnyTimes() 1570 // expect a call to update the status upgrade version 1571 mockStatus.EXPECT(). 1572 Update(gomock.Any(), gomock.Any(), gomock.Any()). 1573 DoAndReturn(func(ctx context.Context, workload *vzapi.VerrazzanoCoherenceWorkload, opts ...client.UpdateOption) error { 1574 return nil 1575 }) 1576 1577 // create a request and reconcile it 1578 request := newRequest(namespace, "unit-test-verrazzano-coherence-workload") 1579 reconciler := newReconciler(cli) 1580 result, err := reconciler.Reconcile(context.TODO(), request) 1581 1582 mocker.Finish() 1583 assert.NoError(err) 1584 assert.Equal(false, result.Requeue) 1585 } 1586 1587 // TestReconcileKubeSystem tests to make sure we do not reconcile 1588 // Any resource that belong to the kube-system namespace 1589 func TestReconcileKubeSystem(t *testing.T) { 1590 1591 assert := asserts.New(t) 1592 1593 var mocker = gomock.NewController(t) 1594 var cli = mocks.NewMockClient(mocker) 1595 1596 // create a request and reconcile it 1597 request := newRequest(vzconst.KubeSystem, "unit-test-verrazzano-helidon-workload") 1598 reconciler := newReconciler(cli) 1599 result, err := reconciler.Reconcile(context.TODO(), request) 1600 1601 mocker.Finish() 1602 assert.Nil(err) 1603 assert.True(result.IsZero()) 1604 } 1605 1606 // TestReconcileFailed tests to make sure the failure metric is being exposed 1607 func TestReconcileFailed(t *testing.T) { 1608 testAppConfigName := "unit-test-app-config" 1609 testNamespace := "test-ns" 1610 1611 scheme := k8scheme.Scheme 1612 assert := asserts.New(t) 1613 clientBuilder := fake.NewClientBuilder().WithScheme(scheme).Build() 1614 // Create a request and reconcile it 1615 reconciler := newReconciler(clientBuilder) 1616 request := newRequest(testNamespace, testAppConfigName) 1617 reconcileerrorCounterObject, err := metricsexporter.GetSimpleCounterMetric(metricsexporter.CohworkloadReconcileError) 1618 assert.NoError(err) 1619 // Expect a call to fetch the error 1620 reconcileFailedCounterBefore := testutil.ToFloat64(reconcileerrorCounterObject.Get()) 1621 reconciler.Reconcile(context.TODO(), request) 1622 reconcileFailedCounterAfter := testutil.ToFloat64(reconcileerrorCounterObject.Get()) 1623 assert.Equal(reconcileFailedCounterBefore, reconcileFailedCounterAfter-1) 1624 }