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  }