github.com/verrazzano/verrazzano@v1.7.0/application-operator/controllers/cohworkload/coherenceworkload_controller.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 "errors" 9 "fmt" 10 "github.com/verrazzano/verrazzano/platform-operator/controllers/verrazzano/component/common" 11 "os" 12 "strconv" 13 "strings" 14 15 "github.com/crossplane/oam-kubernetes-runtime/apis/core/v1alpha2" 16 "github.com/crossplane/oam-kubernetes-runtime/pkg/oam" 17 vzapi "github.com/verrazzano/verrazzano/application-operator/apis/oam/v1alpha1" 18 "github.com/verrazzano/verrazzano/application-operator/controllers/clusters" 19 "github.com/verrazzano/verrazzano/application-operator/controllers/logging" 20 "github.com/verrazzano/verrazzano/application-operator/controllers/metricstrait" 21 vznav "github.com/verrazzano/verrazzano/application-operator/controllers/navigation" 22 "github.com/verrazzano/verrazzano/application-operator/metricsexporter" 23 vzconst "github.com/verrazzano/verrazzano/pkg/constants" 24 log2 "github.com/verrazzano/verrazzano/pkg/log" 25 vzlog2 "github.com/verrazzano/verrazzano/pkg/log/vzlog" 26 "go.uber.org/zap" 27 istionet "istio.io/api/networking/v1alpha3" 28 istioclient "istio.io/client-go/pkg/apis/networking/v1alpha3" 29 v1 "k8s.io/api/apps/v1" 30 corev1 "k8s.io/api/core/v1" 31 k8serrors "k8s.io/apimachinery/pkg/api/errors" 32 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 33 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 34 "k8s.io/apimachinery/pkg/runtime" 35 "k8s.io/apimachinery/pkg/types" 36 ctrl "sigs.k8s.io/controller-runtime" 37 "sigs.k8s.io/controller-runtime/pkg/client" 38 "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" 39 "sigs.k8s.io/controller-runtime/pkg/reconcile" 40 ) 41 42 const cohFluentdParsingRules = `<match fluent.**> 43 @type null 44 </match> 45 46 # Coherence Logs 47 <source> 48 @type tail 49 path /logs/coherence-*.log 50 pos_file /tmp/cohrence.log.pos 51 read_from_head true 52 tag coherence-cluster 53 multiline_flush_interval 20s 54 <parse> 55 @type multiline 56 format_firstline /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}.\d{3}/ 57 format1 /^(?<time>\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}.\d{3})\/(?<uptime>[0-9\.]+) (?<product>.+) <(?<level>[^\s]+)> \(thread=(?<thread>.+), member=(?<member>.+)\):[\S\s](?<log>.*)/ 58 </parse> 59 </source> 60 61 <filter coherence-cluster> 62 @type record_transformer 63 <record> 64 coherence.cluster.name "#{ENV['COH_CLUSTER_NAME']}" 65 role "#{ENV['COH_ROLE']}" 66 host "#{ENV['HOSTNAME']}" 67 pod-uid "#{ENV['COH_POD_UID']}" 68 oam.applicationconfiguration.namespace "#{ENV['NAMESPACE']}" 69 oam.applicationconfiguration.name "#{ENV['APP_CONF_NAME']}" 70 oam.component.namespace "#{ENV['NAMESPACE']}" 71 oam.component.name "#{ENV['COMPONENT_NAME']}" 72 verrazzano.cluster.name "#{ENV['CLUSTER_NAME']}" 73 </record> 74 </filter> 75 76 <match coherence-cluster> 77 @type stdout 78 </match> 79 ` 80 81 const ( 82 specField = "spec" 83 jvmField = "jvm" 84 argsField = "args" 85 workloadType = "coherence" 86 destinationRuleAPIVersion = "networking.istio.io/v1alpha3" 87 destinationRuleKind = "DestinationRule" 88 coherenceExtendPort = 9000 89 loggingNamePart = "logging-stdout" 90 loggingMountPath = "/fluentd/etc/custom.conf" 91 loggingKey = "custom.conf" 92 fluentdVolumeName = "fluentd-config-volume" 93 controllerName = "coherenceworkload" 94 ) 95 96 var specLabelsFields = []string{specField, "labels"} 97 var specAnnotationsFields = []string{specField, "annotations"} 98 var specPortsField = []string{specField, "ports"} 99 var specPortSvcFieldName = "service" 100 101 // additional JVM args that need to get added to the Coherence spec to enable logging 102 var additionalJvmArgs = []interface{}{ 103 "-Dcoherence.log=jdk", 104 "-Dcoherence.log.logger=com.oracle.coherence", 105 "-Djava.util.logging.config.file=/coherence-operator/utils/logging/logging.properties", 106 } 107 108 // this struct allows us to extract information from the unstructured Coherence spec 109 // so we can interface with the FLUENTD code 110 type containersMountsVolumes struct { 111 SideCars []corev1.Container 112 Volumes []corev1.Volume 113 VolumeMounts []corev1.VolumeMount 114 } 115 116 // Reconciler reconciles a VerrazzanoCoherenceWorkload object 117 type Reconciler struct { 118 client.Client 119 Log *zap.SugaredLogger 120 Scheme *runtime.Scheme 121 Metrics *metricstrait.Reconciler 122 } 123 124 // SetupWithManager registers our controller with the manager 125 func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error { 126 return ctrl.NewControllerManagedBy(mgr). 127 For(&vzapi.VerrazzanoCoherenceWorkload{}). 128 Complete(r) 129 } 130 131 // Reconcile reconciles a VerrazzanoCoherenceWorkload resource. It fetches the embedded Coherence CR, mutates it to add 132 // scopes and traits, and then writes out the CR (or deletes it if the workload is being deleted). 133 // +kubebuilder:rbac:groups=oam.verrazzano.io,resources=verrazzanocoherenceworkloads,verbs=get;list;watch;create;update;patch;delete 134 // +kubebuilder:rbac:groups=oam.verrazzano.io,resources=verrazzanocoherenceworkloads/status,verbs=get;update;patch 135 func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { 136 137 // We do not want any resource to get reconciled if it is in namespace kube-system 138 // This is due to a bug found in OKE, it should not affect functionality of any vz operators 139 // If this is the case then return success 140 counterMetricObject, errorCounterMetricObject, reconcileDurationMetricObject, zapLogForMetrics, err := metricsexporter.ExposeControllerMetrics(controllerName, metricsexporter.CohworkloadReconcileCounter, metricsexporter.CohworkloadReconcileError, metricsexporter.CohworkloadReconcileDuration) 141 if err != nil { 142 return ctrl.Result{}, err 143 } 144 reconcileDurationMetricObject.TimerStart() 145 defer reconcileDurationMetricObject.TimerStop() 146 147 if req.Namespace == vzconst.KubeSystem { 148 log := zap.S().With(log2.FieldResourceNamespace, req.Namespace, log2.FieldResourceName, req.Name, log2.FieldController, controllerName) 149 log.Infof("Coherence workload resource %v should not be reconciled in kube-system namespace, ignoring", req.NamespacedName) 150 return reconcile.Result{}, nil 151 } 152 153 if ctx == nil { 154 ctx = context.Background() 155 } 156 workload, err := r.fetchWorkload(ctx, req.NamespacedName, zap.S()) 157 if err != nil { 158 errorCounterMetricObject.Inc(zapLogForMetrics, err) 159 return clusters.IgnoreNotFoundWithLog(err, zap.S()) 160 } 161 log, err := clusters.GetResourceLogger("verrazzanocoherenceworkload", req.NamespacedName, workload) 162 if err != nil { 163 errorCounterMetricObject.Inc(zapLogForMetrics, err) 164 zap.S().Errorf("Failed to create controller logger for Coherence workload resource: %v", err) 165 return clusters.NewRequeueWithDelay(), nil 166 } 167 log.Oncef("Reconciling Coherence workload resource %v, generation %v", req.NamespacedName, workload.Generation) 168 res, err := r.doReconcile(ctx, workload, log) 169 if clusters.ShouldRequeue(res) { 170 return res, nil 171 } 172 // Never return an error since it has already been logged and we don't want the 173 // controller runtime to log again (with stack trace). Just re-queue if there is an error. 174 if err != nil { 175 errorCounterMetricObject.Inc(zapLogForMetrics, err) 176 return clusters.NewRequeueWithDelay(), nil 177 } 178 179 log.Oncef("Finished reconciling Coherence workload %v", req.NamespacedName) 180 counterMetricObject.Inc(zapLogForMetrics, err) 181 return ctrl.Result{}, nil 182 183 } 184 185 // doReconcile performs the reconciliation operations for the coherence workload 186 func (r *Reconciler) doReconcile(ctx context.Context, workload *vzapi.VerrazzanoCoherenceWorkload, log vzlog2.VerrazzanoLogger) (ctrl.Result, error) { 187 // fetch the workload and unwrap the Coherence resource 188 // Make sure the last generation exists in the status 189 result, err := r.ensureLastGeneration(workload) 190 if err != nil || result.Requeue { 191 return result, err 192 } 193 194 u, err := vznav.ConvertRawExtensionToUnstructured(&workload.Spec.Template) 195 if err != nil { 196 return reconcile.Result{}, err 197 } 198 199 // make sure the namespace is set to the namespace of the component 200 if err = unstructured.SetNestedField(u.Object, workload.Namespace, "metadata", "namespace"); err != nil { 201 return reconcile.Result{}, err 202 } 203 204 // the embedded resource doesn't have an API version or kind, so add them 205 gvk := vznav.APIVersionAndKindToContainedGVK(workload.APIVersion, workload.Kind) 206 if gvk == nil { 207 err = fmt.Errorf("failed to determine contained GroupVersionKind for workload") 208 log.Errorf("Failed to get the GroupVersionKind for workload %s: %v", workload, err) 209 return reconcile.Result{}, err 210 } 211 212 apiVersion, kind := gvk.ToAPIVersionAndKind() 213 u.SetAPIVersion(apiVersion) 214 u.SetKind(kind) 215 216 // mutate the Coherence resource, copy labels, add logging, etc. 217 if err = copySpecLabels(log, workload.ObjectMeta.GetLabels(), u); err != nil { 218 return reconcile.Result{}, err 219 } 220 if err = copyServiceLabels(log, workload.ObjectMeta.GetLabels(), u); err != nil { 221 return reconcile.Result{}, err 222 } 223 224 spec, found, _ := unstructured.NestedMap(u.Object, specField) 225 if !found { 226 return reconcile.Result{}, errors.New("embedded Coherence resource is missing the required 'spec' field") 227 } 228 229 // Attempt to get the existing Coherence StatefulSet. This is used in the case where we don't want to update any resources 230 // which are defined by Verrazzano such as the Fluentd image used by logging. In this case we obtain the previous 231 // Fluentd image and set that on the new Coherence StatefulSet. 232 var existingCoherence v1.StatefulSet 233 domainExists := true 234 coherenceKey := types.NamespacedName{Name: u.GetName(), Namespace: workload.Namespace} 235 if err := r.Get(ctx, coherenceKey, &existingCoherence); err != nil { 236 if k8serrors.IsNotFound(err) { 237 log.Debug("No existing Coherence StatefulSet found") 238 domainExists = false 239 } else { 240 log.Errorf("Failed to obtain an existing Coherence StatefulSet: %v", err) 241 return reconcile.Result{}, err 242 } 243 } 244 245 // If the Coherence cluster already exists, make sure that it can be restarted. 246 // If the cluster cannot be restarted, don't make any Coherence changes. 247 if domainExists && !r.isOkToRestartCoherence(workload) { 248 log.Debug("The Coherence resource will not be modified") 249 return ctrl.Result{}, nil 250 } 251 252 // Add the Fluentd sidecar container required for logging to the Coherence StatefulSet 253 if err = r.addLogging(ctx, log, workload, spec, &existingCoherence); err != nil { 254 return reconcile.Result{}, err 255 } 256 257 // Add logging traits to the Domain if they exist 258 if err = r.addLoggingTrait(ctx, log, workload, u, spec); err != nil { 259 return reconcile.Result{}, err 260 } 261 262 // spec has been updated with logging, set the new entries in the unstructured 263 if err = unstructured.SetNestedField(u.Object, spec, specField); err != nil { 264 return reconcile.Result{}, err 265 } 266 267 if err = r.addMetrics(ctx, log, workload.Namespace, workload, u); err != nil { 268 return reconcile.Result{}, err 269 } 270 271 // set istio injection annotation to false for Coherence pods 272 if err = r.disableIstioInjection(u); err != nil { 273 return reconcile.Result{}, err 274 } 275 276 // set controller reference so the Coherence CR gets deleted when the workload is deleted 277 if err = controllerutil.SetControllerReference(workload, u, r.Scheme); err != nil { 278 log.Errorf("Failed to set controller ref: %v", err) 279 return reconcile.Result{}, err 280 } 281 282 // write out restart-version in Coherence spec annotations 283 cohName, _, err := unstructured.NestedString(u.Object, "metadata", "name") 284 if err != nil { 285 return reconcile.Result{}, err 286 } 287 if err = r.addRestartVersionAnnotation(u, workload.Annotations[vzconst.RestartVersionAnnotation], cohName, workload.Namespace, log); err != nil { 288 return reconcile.Result{}, err 289 } 290 291 // make a copy of the Coherence spec since u.Object will get overwritten in CreateOrUpdate 292 // if the Coherence CR exists 293 specCopy, _, err := unstructured.NestedFieldCopy(u.Object, specField) 294 if err != nil { 295 log.Errorf("Failed to make a copy of the Coherence spec: %v", err) 296 return reconcile.Result{}, err 297 } 298 299 // write out the Coherence resource 300 _, err = controllerutil.CreateOrUpdate(ctx, r.Client, u, func() error { 301 return unstructured.SetNestedField(u.Object, specCopy, specField) 302 }) 303 if err != nil { 304 return reconcile.Result{}, log2.ConflictWithLog("Failed creating or updating Coherence CR", err, zap.S()) 305 } 306 307 // Get the namespace resource that the VerrazzanoCoherenceWorkload resource is deployed to 308 namespace := &corev1.Namespace{} 309 if err = r.Client.Get(ctx, client.ObjectKey{Namespace: "", Name: workload.Namespace}, namespace); err != nil { 310 return reconcile.Result{}, err 311 } 312 313 if err = r.createOrUpdateDestinationRule(ctx, log, namespace.Name, namespace.Labels, workload.ObjectMeta.Labels); err != nil { 314 return reconcile.Result{}, err 315 } 316 317 if err = r.updateStatusReconcileDone(ctx, workload); err != nil { 318 return reconcile.Result{}, err 319 } 320 321 return reconcile.Result{}, nil 322 } 323 324 // fetchWorkload fetches the VerrazzanoCoherenceWorkload data given a namespaced name 325 func (r *Reconciler) fetchWorkload(ctx context.Context, name types.NamespacedName, log *zap.SugaredLogger) (*vzapi.VerrazzanoCoherenceWorkload, error) { 326 var workload vzapi.VerrazzanoCoherenceWorkload 327 if err := r.Get(ctx, name, &workload); err != nil { 328 if k8serrors.IsNotFound(err) { 329 log.Debugf("VerrazzanoCoherenceWorkload %s has been deleted", name.Name) 330 } else { 331 log.Errorf("Failed to fetch VerrazzanoCoherenceWorkload %s", name) 332 } 333 return nil, err 334 } 335 336 return &workload, nil 337 } 338 339 // copySpecLabels copies specific labels from the Verrazzano workload to the contained Coherence resource's spec section 340 func copySpecLabels(log vzlog2.VerrazzanoLogger, workloadLabels map[string]string, coherence *unstructured.Unstructured) error { 341 labels, found, _ := unstructured.NestedStringMap(coherence.Object, specLabelsFields...) 342 if !found { 343 labels = map[string]string{} 344 } 345 346 // copy the oam component and app name labels 347 if componentName, ok := workloadLabels[oam.LabelAppComponent]; ok { 348 labels[oam.LabelAppComponent] = componentName 349 } 350 351 if appName, ok := workloadLabels[oam.LabelAppName]; ok { 352 labels[oam.LabelAppName] = appName 353 } 354 355 err := unstructured.SetNestedStringMap(coherence.Object, labels, specLabelsFields...) 356 if err != nil { 357 log.Errorf("Failed to set labels in spec: %v", err) 358 return err 359 } 360 361 return nil 362 } 363 364 // copySpecLabels copies specific labels from the Verrazzano workload to the contained Coherence resource's service section 365 func copyServiceLabels(log vzlog2.VerrazzanoLogger, workloadLabels map[string]string, coherence *unstructured.Unstructured) error { 366 portFields, found, _ := unstructured.NestedSlice(coherence.Object, specPortsField...) 367 if !found { 368 return nil 369 } 370 for _, portFld := range portFields { 371 port := portFld.(map[string]interface{}) 372 var svc map[string]interface{} 373 if port[specPortSvcFieldName] == nil { 374 svc = map[string]interface{}{"labels": map[string]interface{}{}} 375 port[specPortSvcFieldName] = svc 376 } else { 377 svc = port[specPortSvcFieldName].(map[string]interface{}) 378 if svc["labels"] == nil { 379 svc["labels"] = map[string]interface{}{} 380 } 381 } 382 svcLabels := svc["labels"].(map[string]interface{}) 383 // copy the oam component and app name labels 384 if componentName, ok := workloadLabels[oam.LabelAppComponent]; ok { 385 svcLabels[oam.LabelAppComponent] = componentName 386 } 387 if appName, ok := workloadLabels[oam.LabelAppName]; ok { 388 svcLabels[oam.LabelAppName] = appName 389 } 390 } 391 unstructured.SetNestedSlice(coherence.Object, portFields, specPortsField...) 392 return nil 393 } 394 395 // disableIstioInjection sets the sidecar.istio.io/inject annotation to false since Coherence does not work with Istio 396 func (r *Reconciler) disableIstioInjection(u *unstructured.Unstructured) error { 397 annotations, _, err := unstructured.NestedStringMap(u.Object, specAnnotationsFields...) 398 if err != nil { 399 return errors.New("unable to get annotations from Coherence spec") 400 } 401 402 // if no annotations exist initialize the annotations map otherwise update existing annotations. 403 if annotations == nil { 404 annotations = make(map[string]string) 405 } 406 annotations["sidecar.istio.io/inject"] = "false" 407 408 err = unstructured.SetNestedStringMap(u.Object, annotations, specAnnotationsFields...) 409 if err != nil { 410 return err 411 } 412 413 return nil 414 } 415 416 // addLogging adds a FLUENTD sidecar and updates the Coherence spec if there is an associated LogInfo 417 func (r *Reconciler) addLogging(ctx context.Context, log vzlog2.VerrazzanoLogger, workload *vzapi.VerrazzanoCoherenceWorkload, coherenceSpec map[string]interface{}, existingCoherence *v1.StatefulSet) error { 418 // extract just enough of the Coherence data into concrete types so we can merge with 419 // the FLUENTD data 420 var extracted containersMountsVolumes 421 if err := runtime.DefaultUnstructuredConverter.FromUnstructured(coherenceSpec, &extracted); err != nil { 422 return errors.New("unable to extract containers, volumes, and volume mounts from Coherence spec") 423 } 424 425 // fluentdPod starts with what's in the spec and we add in the FLUENTD things when Apply is 426 // called on the fluentdManager 427 fluentdPod := &logging.FluentdPod{ 428 Containers: extracted.SideCars, 429 Volumes: extracted.Volumes, 430 VolumeMounts: extracted.VolumeMounts, 431 LogPath: "/logs", 432 } 433 fluentdManager := &logging.Fluentd{ 434 Context: ctx, 435 Log: zap.S(), 436 Client: r.Client, 437 ParseRules: cohFluentdParsingRules, 438 StorageVolumeName: "logs", 439 StorageVolumeMountPath: "/logs", 440 WorkloadType: workloadType, 441 } 442 443 // fluentdManager.Apply wants a QRR but it only cares about the namespace (at least for 444 // this use case) 445 resource := vzapi.QualifiedResourceRelation{Namespace: workload.Namespace} 446 447 // note that this call has the side effect of creating a FLUENTD config map if one 448 // does not already exist in the namespace 449 if err := fluentdManager.Apply(logging.NewLogInfo(), resource, fluentdPod); err != nil { 450 return err 451 } 452 453 // fluentdPod now has the FLUENTD container, volumes, and volume mounts merged in 454 // with the existing spec data 455 456 // Coherence wants the volume mount for the FLUENTD config map stored in "configMapVolumes", so 457 // we have to move it from the FLUENTD container volume mounts 458 if err := moveConfigMapVolume(log, fluentdPod, coherenceSpec); err != nil { 459 return err 460 } 461 462 // convert the containers, volumes, and mounts in fluentdPod to unstructured and set 463 // the values in the spec 464 fluentdPodUnstructured, err := runtime.DefaultUnstructuredConverter.ToUnstructured(fluentdPod) 465 if err != nil { 466 return err 467 } 468 469 coherenceSpec["sideCars"] = fluentdPodUnstructured["containers"] 470 coherenceSpec["volumes"] = fluentdPodUnstructured["volumes"] 471 coherenceSpec["volumeMounts"] = fluentdPodUnstructured["volumeMounts"] 472 473 addJvmArgs(coherenceSpec) 474 475 return nil 476 } 477 478 // addMetrics adds the labels and annotations needed for metrics to the Coherence resource annotations which are propagated to the individual Coherence pods. 479 // Returns the success fo the operation and any error occurred. If metrics were successfully added, true is return with a nil error. 480 func (r *Reconciler) addMetrics(ctx context.Context, log vzlog2.VerrazzanoLogger, namespace string, workload *vzapi.VerrazzanoCoherenceWorkload, coherence *unstructured.Unstructured) error { 481 log.Debugf("Adding metric labels and annotations for: %s", workload.Name) 482 metricsTrait, err := vznav.MetricsTraitFromWorkloadLabels(ctx, r.Client, log.GetZapLogger(), namespace, workload.ObjectMeta) 483 if err != nil { 484 return err 485 } 486 487 if metricsTrait == nil { 488 log.Debug("Workload has no associated MetricTrait, nothing to do") 489 return nil 490 } 491 log.Debugf("Found associated metrics trait for workload: %s : %s", workload.Name, metricsTrait.Name) 492 493 traitDefaults, err := r.Metrics.NewTraitDefaultsForCOHWorkload(ctx, coherence) 494 if err != nil { 495 log.Errorf("Failed to get default metric trait values: %v", err) 496 return err 497 } 498 499 metricAnnotations, found, _ := unstructured.NestedStringMap(coherence.Object, specAnnotationsFields...) 500 if !found { 501 metricAnnotations = map[string]string{} 502 } 503 504 metricLabels, found, _ := unstructured.NestedStringMap(coherence.Object, specLabelsFields...) 505 if !found { 506 metricLabels = map[string]string{} 507 } 508 509 finalAnnotations := metricstrait.MutateAnnotations(metricsTrait, traitDefaults, metricAnnotations) 510 log.Debugf("Setting annotations on %s: %v", workload.Name, finalAnnotations) 511 err = unstructured.SetNestedStringMap(coherence.Object, finalAnnotations, specAnnotationsFields...) 512 if err != nil { 513 log.Errorf("Failed to set metric annotations on Coherence resource: %v", err) 514 return err 515 } 516 517 finalLabels := metricstrait.MutateLabels(metricsTrait, coherence, metricLabels) 518 log.Debugf("Setting labels on %s: %v", workload.Name, finalLabels) 519 520 err = unstructured.SetNestedStringMap(coherence.Object, finalLabels, specLabelsFields...) 521 if err != nil { 522 log.Errorf("Failed to set metric labels on Coherence resource: %v", err) 523 return err 524 } 525 526 return nil 527 } 528 529 // moveConfigMapVolume moves the FLUENTD config map volume definition. Coherence wants the volume mount 530 // for the FLUENTD config map stored in "configMapVolumes", so we will pull the mount out from the 531 // FLUENTD container and put it in its new home in the Coherence spec (this should all be handled 532 // by the FLUENTD code at some point but I tried to limit the surgery for now) 533 func moveConfigMapVolume(log vzlog2.VerrazzanoLogger, fluentdPod *logging.FluentdPod, coherenceSpec map[string]interface{}) error { 534 var fluentdVolMount corev1.VolumeMount 535 536 for _, container := range fluentdPod.Containers { 537 if container.Name == logging.FluentdStdoutSidecarName { 538 fluentdVolMount = container.VolumeMounts[0] 539 // Coherence needs the vol mount to match the config map name, so fix it, need 540 // to see if we can just change name set by the FLUENTD code 541 fluentdVolMount.Name = "fluentd-config" + "-" + workloadType 542 fluentdPod.Containers[0].VolumeMounts = nil 543 break 544 } 545 } 546 547 // add the config map volume mount to "configMapVolumes" in the spec 548 if fluentdVolMount.Name != "" { 549 fluentdVolMountUnstructured, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&fluentdVolMount) 550 if err != nil { 551 return err 552 } 553 554 if configMapVolumes, found := coherenceSpec["configMapVolumes"]; !found { 555 coherenceSpec["configMapVolumes"] = []interface{}{fluentdVolMountUnstructured} 556 } else { 557 vols := configMapVolumes.([]interface{}) 558 coherenceSpec["configMapVolumes"] = append(vols, fluentdVolMountUnstructured) 559 } 560 } else { 561 log.Debug("Expected to find config map volume mount in fluentd container but did not") 562 } 563 564 volumes := fluentdPod.Volumes 565 vIndex := -1 566 for v, volume := range volumes { 567 if volume.Name == fluentdVolumeName { 568 vIndex = v 569 } 570 } 571 if vIndex != -1 { 572 volumes[vIndex] = volumes[len(volumes)-1] 573 fluentdPod.Volumes = volumes[:len(volumes)-1] 574 } 575 576 return nil 577 } 578 579 // addJvmArgs adds the additional JVM args needed to enable and configure logging 580 // in the Coherence container 581 func addJvmArgs(coherenceSpec map[string]interface{}) { 582 var jvm map[string]interface{} 583 if val, found := coherenceSpec[jvmField]; !found { 584 jvm = make(map[string]interface{}) 585 coherenceSpec[jvmField] = jvm 586 } else { 587 jvm = val.(map[string]interface{}) 588 } 589 590 var args []interface{} 591 if val, found := jvm[argsField]; !found { 592 args = additionalJvmArgs 593 } else { 594 // just append our logging args, this needs to be improved to handle 595 // the case where one or more of the args are already present 596 args = val.([]interface{}) 597 args = append(args, additionalJvmArgs...) 598 } 599 jvm[argsField] = args 600 } 601 602 // createOrUpdateDestinationRule creates or updates an Istio destinationrule required by Coherence. 603 // The destinationrule is only created when the namespace has the label istio-injection=enabled. 604 func (r *Reconciler) createOrUpdateDestinationRule(ctx context.Context, log vzlog2.VerrazzanoLogger, namespace string, namespaceLabels map[string]string, workloadLabels map[string]string) error { 605 istioEnabled := false 606 value, ok := namespaceLabels["istio-injection"] 607 if ok && value == "enabled" { 608 istioEnabled = true 609 } 610 611 if !istioEnabled { 612 return nil 613 } 614 615 appName, ok := workloadLabels[oam.LabelAppName] 616 if !ok { 617 return errors.New("OAM app name label missing from metadata, unable to generate destination rule name") 618 } 619 620 // Create a destinationrule populating only name metadata. 621 // This is used as default if the destinationrule needs to be created. 622 destinationRule := &istioclient.DestinationRule{ 623 TypeMeta: metav1.TypeMeta{ 624 APIVersion: destinationRuleAPIVersion, 625 Kind: destinationRuleKind}, 626 ObjectMeta: metav1.ObjectMeta{ 627 Namespace: namespace, 628 Name: appName, 629 }, 630 } 631 632 log.Debugf("Creating/updating destination rule %s:%s", namespace, appName) 633 _, err := common.CreateOrUpdateProtobuf(ctx, r.Client, destinationRule, func() error { 634 return r.mutateDestinationRule(destinationRule, namespace, appName) 635 }) 636 637 return err 638 } 639 640 // mutateDestinationRule mutates the output destinationrule. 641 func (r *Reconciler) mutateDestinationRule(destinationRule *istioclient.DestinationRule, namespace string, appName string) error { 642 // Set the spec content. 643 destinationRule.Spec.Host = fmt.Sprintf("*.%s.svc.cluster.local", namespace) 644 destinationRule.Spec.TrafficPolicy = &istionet.TrafficPolicy{ 645 Tls: &istionet.ClientTLSSettings{ 646 Mode: istionet.ClientTLSSettings_ISTIO_MUTUAL, 647 }, 648 } 649 destinationRule.Spec.TrafficPolicy.PortLevelSettings = []*istionet.TrafficPolicy_PortTrafficPolicy{ 650 { 651 // Disable mutual TLS for the Coherence extend port 652 Port: &istionet.PortSelector{ 653 Number: coherenceExtendPort, 654 }, 655 Tls: &istionet.ClientTLSSettings{ 656 Mode: istionet.ClientTLSSettings_DISABLE, 657 }, 658 }, 659 } 660 661 // Set the owner reference. 662 appConfig := &v1alpha2.ApplicationConfiguration{} 663 err := r.Get(context.TODO(), types.NamespacedName{Namespace: namespace, Name: appName}, appConfig) 664 if err != nil { 665 return err 666 } 667 err = controllerutil.SetControllerReference(appConfig, destinationRule, r.Scheme) 668 if err != nil { 669 return err 670 } 671 672 return nil 673 } 674 675 func (r *Reconciler) updateStatusReconcileDone(ctx context.Context, workload *vzapi.VerrazzanoCoherenceWorkload) error { 676 if workload.Status.LastGeneration != strconv.Itoa(int(workload.Generation)) { 677 workload.Status.LastGeneration = strconv.Itoa(int(workload.Generation)) 678 return r.Status().Update(ctx, workload) 679 } 680 return nil 681 } 682 683 // addLoggingTrait adds the logging trait sidecar to the workload 684 func (r *Reconciler) addLoggingTrait(ctx context.Context, log vzlog2.VerrazzanoLogger, workload *vzapi.VerrazzanoCoherenceWorkload, coherence *unstructured.Unstructured, coherenceSpec map[string]interface{}) error { 685 loggingTrait, err := vznav.LoggingTraitFromWorkloadLabels(ctx, r.Client, log, workload.GetNamespace(), workload.ObjectMeta) 686 if err != nil { 687 return err 688 } 689 if loggingTrait == nil { 690 return nil 691 } 692 693 configMapName := loggingNamePart + "-" + coherence.GetName() + "-" + strings.ToLower(coherence.GetKind()) 694 configMap := &corev1.ConfigMap{} 695 err = r.Get(ctx, client.ObjectKey{Namespace: coherence.GetNamespace(), Name: configMapName}, configMap) 696 if err != nil && k8serrors.IsNotFound(err) { 697 data := make(map[string]string) 698 data["custom.conf"] = loggingTrait.Spec.LoggingConfig 699 configMap = &corev1.ConfigMap{ 700 ObjectMeta: metav1.ObjectMeta{ 701 Name: loggingNamePart + "-" + coherence.GetName() + "-" + strings.ToLower(coherence.GetKind()), 702 Namespace: coherence.GetNamespace(), 703 Labels: coherence.GetLabels(), 704 }, 705 Data: data, 706 } 707 err = controllerutil.SetControllerReference(workload, configMap, r.Scheme) 708 if err != nil { 709 return err 710 } 711 log.Debugf("Creating logging trait configmap %s:%s", coherence.GetNamespace(), configMapName) 712 err = r.Create(ctx, configMap) 713 if err != nil { 714 return err 715 } 716 } else if err != nil { 717 return err 718 } 719 log.Debugf("logging trait configmap %s:%s already exist", coherence.GetNamespace(), configMapName) 720 721 // extract just enough of the WebLogic data into concrete types so we can merge with 722 // the logging trait data 723 var extract containersMountsVolumes 724 if err := runtime.DefaultUnstructuredConverter.FromUnstructured(coherenceSpec, &extract); err != nil { 725 return fmt.Errorf("failed to extract containers, volumes, and volume mounts from Coherence spec") 726 } 727 extracted := &containersMountsVolumes{ 728 SideCars: extract.SideCars, 729 VolumeMounts: extract.VolumeMounts, 730 Volumes: extract.Volumes, 731 } 732 loggingVolumeMount := &corev1.VolumeMount{ 733 MountPath: loggingMountPath, 734 Name: configMapName, 735 SubPath: loggingKey, 736 ReadOnly: true, 737 } 738 739 loggingVolumeMountUnstructured, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&loggingVolumeMount) 740 if err != nil { 741 return err 742 } 743 if configMapVolumes, found := coherenceSpec["configMapVolumes"]; !found { 744 coherenceSpec["configMapVolumes"] = []interface{}{loggingVolumeMountUnstructured} 745 } else { 746 vols := configMapVolumes.([]interface{}) 747 volIndex := -1 748 for i, v := range vols { 749 if v.(map[string]interface{})["mountPath"] == loggingVolumeMountUnstructured["mountPath"] && v.(map[string]interface{})["name"] == loggingVolumeMountUnstructured["name"] { 750 volIndex = i 751 } 752 } 753 if volIndex == -1 { 754 vols = append(vols, loggingVolumeMountUnstructured) 755 } else { 756 vols[volIndex] = loggingVolumeMountUnstructured 757 } 758 coherenceSpec["configMapVolumes"] = vols 759 } 760 var image string 761 if len(loggingTrait.Spec.LoggingImage) != 0 { 762 image = loggingTrait.Spec.LoggingImage 763 } else { 764 image = os.Getenv("DEFAULT_FLUENTD_IMAGE") 765 } 766 envFluentd := &corev1.EnvVar{ 767 Name: "FLUENTD_CONF", 768 Value: "custom.conf", 769 } 770 loggingContainer := &corev1.Container{ 771 Name: loggingNamePart, 772 Image: image, 773 ImagePullPolicy: corev1.PullPolicy(loggingTrait.Spec.ImagePullPolicy), 774 Env: []corev1.EnvVar{*envFluentd}, 775 } 776 sIndex := -1 777 for i, s := range extracted.SideCars { 778 if s.Name == loggingNamePart { 779 sIndex = i 780 } 781 } 782 if sIndex != -1 { 783 extracted.SideCars[sIndex] = *loggingContainer 784 } else { 785 extracted.SideCars = append(extracted.SideCars, *loggingContainer) 786 } 787 788 // convert the containers, volumes, and mounts in extracted to unstructured and set 789 // the values in the spec 790 extractedUnstructured, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&extracted) 791 if err != nil { 792 return err 793 } 794 coherenceSpec["sideCars"] = extractedUnstructured["sideCars"] 795 796 return nil 797 } 798 799 func (r *Reconciler) addRestartVersionAnnotation(coherence *unstructured.Unstructured, restartVersion, name, namespace string, log vzlog2.VerrazzanoLogger) error { 800 if len(restartVersion) > 0 { 801 log.Debugf("The Coherence %s/%s restart version is set to %s", namespace, name, restartVersion) 802 annotations, _, err := unstructured.NestedStringMap(coherence.Object, specAnnotationsFields...) 803 if err != nil { 804 return errors.New("unable to get annotations from Coherence spec") 805 } 806 // if no annotations exist initialize the annotations map otherwise update existing annotations. 807 if annotations == nil { 808 annotations = make(map[string]string) 809 } 810 annotations[vzconst.RestartVersionAnnotation] = restartVersion 811 return unstructured.SetNestedStringMap(coherence.Object, annotations, specAnnotationsFields...) 812 } 813 return nil 814 } 815 816 // Make sure that the last generation exists in the status 817 func (r *Reconciler) ensureLastGeneration(wl *vzapi.VerrazzanoCoherenceWorkload) (ctrl.Result, error) { 818 if len(wl.Status.LastGeneration) > 0 { 819 return ctrl.Result{}, nil 820 } 821 822 // Update the status generation and always requeue 823 wl.Status.LastGeneration = strconv.Itoa(int(wl.Generation)) 824 err := r.Status().Update(context.TODO(), wl) 825 return ctrl.Result{Requeue: true, RequeueAfter: 1}, err 826 } 827 828 // Make sure that it is OK to restart Coherence 829 func (r *Reconciler) isOkToRestartCoherence(coh *vzapi.VerrazzanoCoherenceWorkload) bool { 830 // Check if user created or changed the restart annotation 831 if coh.Annotations != nil && coh.Annotations[vzconst.RestartVersionAnnotation] != coh.Status.LastRestartVersion { 832 return true 833 } 834 if coh.Status.LastGeneration == strconv.Itoa(int(coh.Generation)) { 835 // nothing in the spec has changed 836 return false 837 } 838 // The spec has changed because the generation is different from the saved one 839 return true 840 }