github.com/verrazzano/verrazzano@v1.7.0/application-operator/controllers/metricstrait/workloads.go (about)

     1  // Copyright (c) 2022, 2023, Oracle and/or its affiliates.
     2  // Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
     3  
     4  package metricstrait
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  
    10  	vzapi "github.com/verrazzano/verrazzano/application-operator/apis/oam/v1alpha1"
    11  	vznav "github.com/verrazzano/verrazzano/application-operator/controllers/navigation"
    12  	"github.com/verrazzano/verrazzano/application-operator/controllers/reconcileresults"
    13  	"github.com/verrazzano/verrazzano/pkg/log/vzlog"
    14  	k8sapps "k8s.io/api/apps/v1"
    15  	k8score "k8s.io/api/core/v1"
    16  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    17  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    18  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    19  	"k8s.io/apimachinery/pkg/runtime/schema"
    20  	"sigs.k8s.io/controller-runtime/pkg/client"
    21  	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
    22  )
    23  
    24  // createOrUpdateWorkloads creates or updates resources related to this trait
    25  // The related resources are the workload children and the Prometheus config
    26  func (r *Reconciler) createOrUpdateRelatedWorkloads(ctx context.Context, trait *vzapi.MetricsTrait, workload *unstructured.Unstructured, traitDefaults *vzapi.MetricsTraitSpec, children []*unstructured.Unstructured, log vzlog.VerrazzanoLogger) *reconcileresults.ReconcileResults {
    27  	log.Debugf("Creating or updating workload children of the Prometheus workload: %s", workload.GetName())
    28  	status := reconcileresults.ReconcileResults{}
    29  	for _, child := range children {
    30  		switch child.GroupVersionKind() {
    31  		case k8sapps.SchemeGroupVersion.WithKind(deploymentKind):
    32  			// In the case of VerrazzanoHelidonWorkload, it isn't unwrapped so we need to check to see
    33  			// if the workload is a wrapper kind in addition to checking to see if the owner is a wrapper kind.
    34  			// In the case of a wrapper kind or owner, the status is not being updated here as this is handled by the
    35  			// wrapper owner which is the corresponding Verrazzano wrapper resource/controller.
    36  			if !vznav.IsOwnedByVerrazzanoWorkloadKind(workload) && !vznav.IsVerrazzanoWorkloadKind(workload) {
    37  				status.RecordOutcome(r.updateRelatedDeployment(ctx, trait, workload, traitDefaults, child, log))
    38  			}
    39  		case k8sapps.SchemeGroupVersion.WithKind(statefulSetKind):
    40  			// In the case of a workload having an owner that is a wrapper kind, the status is not being updated here
    41  			// as this is handled by the wrapper owner which is the corresponding Verrazzano wrapper resource/controller.
    42  			if !vznav.IsOwnedByVerrazzanoWorkloadKind(workload) {
    43  				status.RecordOutcome(r.updateRelatedStatefulSet(ctx, trait, workload, traitDefaults, child, log))
    44  			}
    45  		case k8score.SchemeGroupVersion.WithKind(podKind):
    46  			// In the case of a workload having an owner that is a wrapper kind, the status is not being updated here
    47  			// as this is handled by the wrapper owner which is the corresponding Verrazzano wrapper resource/controller.
    48  			if !vznav.IsOwnedByVerrazzanoWorkloadKind(workload) {
    49  				status.RecordOutcome(r.updateRelatedPod(ctx, trait, workload, traitDefaults, child, log))
    50  			}
    51  		}
    52  	}
    53  	return &status
    54  }
    55  
    56  // updateRelatedDeployment updates the labels and annotations of a related workload deployment.
    57  // For example containerized workloads produce related deployments.
    58  func (r *Reconciler) updateRelatedDeployment(ctx context.Context, trait *vzapi.MetricsTrait, workload *unstructured.Unstructured, traitDefaults *vzapi.MetricsTraitSpec, child *unstructured.Unstructured, log vzlog.VerrazzanoLogger) (vzapi.QualifiedResourceRelation, controllerutil.OperationResult, error) {
    59  	deployment := &k8sapps.Deployment{
    60  		TypeMeta:   metav1.TypeMeta{APIVersion: child.GetAPIVersion(), Kind: child.GetKind()},
    61  		ObjectMeta: metav1.ObjectMeta{Namespace: child.GetNamespace(), Name: child.GetName()},
    62  	}
    63  	ref := vzapi.QualifiedResourceRelation{APIVersion: child.GetAPIVersion(), Kind: child.GetKind(), Namespace: child.GetNamespace(), Name: child.GetName(), Role: sourceRole}
    64  
    65  	err := r.Get(ctx, client.ObjectKeyFromObject(deployment), deployment)
    66  
    67  	if err != nil && !apierrors.IsNotFound(err) {
    68  		return ref, controllerutil.OperationResultNone, fmt.Errorf("failed to getworkload child deployment %s: %v", vznav.GetNamespacedNameFromObjectMeta(deployment.ObjectMeta).Name, err)
    69  	}
    70  
    71  	if apierrors.IsNotFound(err) || deployment.CreationTimestamp.IsZero() {
    72  		log.Debug("Workload child deployment not found")
    73  		return ref, controllerutil.OperationResultNone, apierrors.NewNotFound(schema.GroupResource{Group: deployment.APIVersion, Resource: deployment.Kind}, deployment.Name)
    74  	}
    75  
    76  	replicaSets := &k8sapps.ReplicaSetList{}
    77  	err = r.List(ctx, replicaSets, client.InNamespace(deployment.Namespace))
    78  	if err != nil && !apierrors.IsNotFound(err) {
    79  		return ref, controllerutil.OperationResultNone, fmt.Errorf("failed to get replicasets of workload child deployment %s: %v", vznav.GetNamespacedNameFromObjectMeta(deployment.ObjectMeta).Name, err)
    80  	}
    81  
    82  	if apierrors.IsNotFound(err) || len(replicaSets.Items) == 0 {
    83  		log.Debug("Replicasets of Workload child deployment not found")
    84  		return ref, controllerutil.OperationResultNone, apierrors.NewNotFound(schema.GroupResource{Group: replicaSets.APIVersion, Resource: replicaSets.Kind}, deployment.Name)
    85  	}
    86  
    87  	for _, replicaSet := range replicaSets.Items {
    88  		for _, replicaSetOwnerRef := range replicaSet.GetOwnerReferences() {
    89  			if replicaSetOwnerRef.APIVersion == deployment.APIVersion && replicaSetOwnerRef.Kind == deployment.Kind && replicaSetOwnerRef.Name == deployment.Name {
    90  				res, err := r.updateRelatedPods(ctx, trait, workload, traitDefaults, log, ref.Namespace, replicaSet.Kind, replicaSet.Name, replicaSet.APIVersion)
    91  				if err != nil {
    92  					return ref, res, err
    93  				}
    94  			}
    95  
    96  		}
    97  
    98  	}
    99  
   100  	return ref, controllerutil.OperationResultUpdated, nil
   101  
   102  }
   103  
   104  // updateRelatedStatefulSet updates the labels and annotations of a related workload stateful set.
   105  // For example coherence workloads produce related stateful sets.
   106  func (r *Reconciler) updateRelatedStatefulSet(ctx context.Context, trait *vzapi.MetricsTrait, workload *unstructured.Unstructured, traitDefaults *vzapi.MetricsTraitSpec, child *unstructured.Unstructured, log vzlog.VerrazzanoLogger) (vzapi.QualifiedResourceRelation, controllerutil.OperationResult, error) {
   107  	log.Debugf("Update workload stateful set %s", vznav.GetNamespacedNameFromUnstructured(child))
   108  	ref := vzapi.QualifiedResourceRelation{APIVersion: child.GetAPIVersion(), Kind: child.GetKind(), Namespace: child.GetNamespace(), Name: child.GetName(), Role: sourceRole}
   109  	statefulSet := &k8sapps.StatefulSet{
   110  		TypeMeta:   metav1.TypeMeta{APIVersion: child.GetAPIVersion(), Kind: child.GetKind()},
   111  		ObjectMeta: metav1.ObjectMeta{Namespace: child.GetNamespace(), Name: child.GetName()},
   112  	}
   113  
   114  	err := r.Get(ctx, client.ObjectKeyFromObject(statefulSet), statefulSet)
   115  
   116  	if err != nil && !apierrors.IsNotFound(err) {
   117  		return ref, controllerutil.OperationResultNone, fmt.Errorf("unable to fetch workload child statefulset %s: %v", vznav.GetNamespacedNameFromObjectMeta(statefulSet.ObjectMeta).Name, err)
   118  	}
   119  
   120  	if apierrors.IsNotFound(err) || statefulSet.CreationTimestamp.IsZero() {
   121  		log.Debug("Workload child statefulset not found")
   122  		return ref, controllerutil.OperationResultNone, apierrors.NewNotFound(schema.GroupResource{Group: statefulSet.APIVersion, Resource: statefulSet.Kind}, statefulSet.Name)
   123  	}
   124  
   125  	res, err := r.updateRelatedPods(ctx, trait, workload, traitDefaults, log, ref.Namespace, statefulSet.Kind, statefulSet.Name, statefulSet.APIVersion)
   126  	return ref, res, err
   127  }
   128  
   129  // updateRelatedPod updates the labels and annotations of a related workload pod.
   130  // For example WLS workloads produce related pods.
   131  func (r *Reconciler) updateRelatedPod(ctx context.Context, trait *vzapi.MetricsTrait, workload *unstructured.Unstructured, traitDefaults *vzapi.MetricsTraitSpec, child *unstructured.Unstructured, log vzlog.VerrazzanoLogger) (vzapi.QualifiedResourceRelation, controllerutil.OperationResult, error) {
   132  	log.Debug("Update workload pod %s", vznav.GetNamespacedNameFromUnstructured(child))
   133  	pod := &k8score.Pod{
   134  		TypeMeta:   metav1.TypeMeta{APIVersion: child.GetAPIVersion(), Kind: child.GetKind()},
   135  		ObjectMeta: metav1.ObjectMeta{Namespace: child.GetNamespace(), Name: child.GetName()},
   136  	}
   137  	return r.updatePod(ctx, trait, workload, traitDefaults, log, *pod)
   138  }
   139  
   140  // NewTraitDefaultsForWLSDomainWorkload creates metrics trait default values for a WLS domain workload.
   141  func (r *Reconciler) NewTraitDefaultsForWLSDomainWorkload(ctx context.Context, workload *unstructured.Unstructured) (*vzapi.MetricsTraitSpec, error) {
   142  	// Port precedence: trait, workload annotation, default
   143  	port := defaultWLSAdminScrapePort
   144  	path := defaultWLSScrapePath
   145  	secret, err := r.fetchWLSDomainCredentialsSecretName(ctx, workload)
   146  	if err != nil {
   147  		return nil, err
   148  	}
   149  	return &vzapi.MetricsTraitSpec{
   150  		Ports: []vzapi.PortSpec{{
   151  			Port: &port,
   152  			Path: &path,
   153  		}},
   154  		Path:    &path,
   155  		Secret:  secret,
   156  		Scraper: &r.Scraper}, nil
   157  }
   158  
   159  // NewTraitDefaultsForCOHWorkload creates metrics trait default values for a Coherence workload.
   160  func (r *Reconciler) NewTraitDefaultsForCOHWorkload(ctx context.Context, workload *unstructured.Unstructured) (*vzapi.MetricsTraitSpec, error) {
   161  	path := defaultScrapePath
   162  	port := defaultCohScrapePort
   163  	var secret *string
   164  
   165  	enabled, p, s, err := r.fetchCoherenceMetricsSpec(ctx, workload)
   166  	if err != nil {
   167  		return nil, err
   168  	}
   169  	if enabled == nil || *enabled {
   170  		if p != nil {
   171  			port = *p
   172  		}
   173  		if s != nil {
   174  			secret = s
   175  		}
   176  	}
   177  	return &vzapi.MetricsTraitSpec{
   178  		Ports: []vzapi.PortSpec{{
   179  			Port: &port,
   180  			Path: &path,
   181  		}},
   182  		Path:    &path,
   183  		Secret:  secret,
   184  		Scraper: &r.Scraper}, nil
   185  }
   186  
   187  // NewTraitDefaultsForGenericWorkload creates metrics trait default values for a containerized workload.
   188  func (r *Reconciler) NewTraitDefaultsForGenericWorkload() (*vzapi.MetricsTraitSpec, error) {
   189  	port := defaultScrapePort
   190  	path := defaultScrapePath
   191  	return &vzapi.MetricsTraitSpec{
   192  		Ports: []vzapi.PortSpec{{
   193  			Port: &port,
   194  			Path: &path,
   195  		}},
   196  		Path:    &path,
   197  		Secret:  nil,
   198  		Scraper: &r.Scraper}, nil
   199  }
   200  
   201  // fetchCoherenceMetricsSpec fetches the metrics configuration from the Coherence workload resource spec.
   202  // These configuration values are used in the population of the Prometheus scraper configuration.
   203  func (r *Reconciler) fetchCoherenceMetricsSpec(ctx context.Context, workload *unstructured.Unstructured) (*bool, *int, *string, error) {
   204  	// determine if metrics is enabled
   205  	enabled, found, err := unstructured.NestedBool(workload.Object, "spec", "coherence", "metrics", "enabled")
   206  	if err != nil {
   207  		return nil, nil, nil, err
   208  	}
   209  	var e *bool
   210  	if found {
   211  		e = &enabled
   212  	}
   213  
   214  	// get the metrics port
   215  	port, found, err := unstructured.NestedInt64(workload.Object, "spec", "coherence", "metrics", "port")
   216  	if err != nil {
   217  		return nil, nil, nil, err
   218  	}
   219  	var p *int
   220  	if found {
   221  		p2 := int(port)
   222  		p = &p2
   223  	}
   224  
   225  	// get the secret if ssl is enabled
   226  	enabled, found, err = unstructured.NestedBool(workload.Object, "spec", "coherence", "metrics", "ssl", "enabled")
   227  	if err != nil {
   228  		return nil, nil, nil, err
   229  	}
   230  	var s *string
   231  	if found && enabled {
   232  		secret, found, err := unstructured.NestedString(workload.Object, "spec", "coherence", "metrics", "ssl", "secrets")
   233  		if err != nil {
   234  			return nil, nil, nil, err
   235  		}
   236  		if found {
   237  			s = &secret
   238  		}
   239  	}
   240  	return e, p, s, nil
   241  }
   242  
   243  // fetchWLSDomainCredentialsSecretName fetches the credentials from the WLS workload resource (i.e. domain).
   244  // These credentials are used in the population of the Prometheus scraper configuration.
   245  func (r *Reconciler) fetchWLSDomainCredentialsSecretName(ctx context.Context, workload *unstructured.Unstructured) (*string, error) {
   246  	secretName, found, err := unstructured.NestedString(workload.Object, "spec", "webLogicCredentialsSecret", "name")
   247  	if err != nil {
   248  		return nil, err
   249  	}
   250  	if !found {
   251  		return nil, nil
   252  	}
   253  	return &secretName, nil
   254  }
   255  
   256  func (r *Reconciler) updateRelatedPods(ctx context.Context, trait *vzapi.MetricsTrait, workload *unstructured.Unstructured, traitDefaults *vzapi.MetricsTraitSpec, log vzlog.VerrazzanoLogger, namespace string, ownerKind string, ownerName string, ownerAPIVersion string) (controllerutil.OperationResult, error) {
   257  	pods := &k8score.PodList{}
   258  	err := r.List(ctx, pods, client.InNamespace(namespace))
   259  	if err != nil && !apierrors.IsNotFound(err) {
   260  		return controllerutil.OperationResultNone, fmt.Errorf("unable to fetch pods from namespace %s: %v", namespace, err)
   261  	}
   262  
   263  	if apierrors.IsNotFound(err) || len(pods.Items) == 0 {
   264  		log.Debugf("pods of %s %s not found", ownerKind, ownerName)
   265  		return controllerutil.OperationResultNone, apierrors.NewNotFound(schema.GroupResource{Group: pods.APIVersion, Resource: pods.Kind}, ownerName)
   266  	}
   267  
   268  	for _, pod := range pods.Items {
   269  		for _, podOwnerRef := range pod.GetOwnerReferences() {
   270  			if podOwnerRef.APIVersion == ownerAPIVersion && podOwnerRef.Kind == ownerKind && podOwnerRef.Name == ownerName {
   271  				_, _, err := r.updatePod(ctx, trait, workload, traitDefaults, log, pod)
   272  				if err != nil && !apierrors.IsNotFound(err) {
   273  					return controllerutil.OperationResultNone, fmt.Errorf("failed to update labels for pod %s of %s %s: %v", vznav.GetNamespacedNameFromObjectMeta(pod.ObjectMeta).Name, ownerKind, ownerName, err)
   274  				}
   275  			}
   276  
   277  		}
   278  	}
   279  
   280  	return controllerutil.OperationResultUpdated, nil
   281  
   282  }
   283  
   284  // updatePod updates the labels and annotations of a workload pod.
   285  func (r *Reconciler) updatePod(ctx context.Context, trait *vzapi.MetricsTrait, workload *unstructured.Unstructured, traitDefaults *vzapi.MetricsTraitSpec, log vzlog.VerrazzanoLogger, pod k8score.Pod) (vzapi.QualifiedResourceRelation, controllerutil.OperationResult, error) {
   286  	rel := vzapi.QualifiedResourceRelation{APIVersion: pod.APIVersion, Kind: pod.Kind, Namespace: pod.GetNamespace(), Name: pod.GetName(), Role: sourceRole}
   287  	res, err := controllerutil.CreateOrUpdate(ctx, r.Client, &pod, func() error {
   288  		// If the statefulset was not found don't attempt to create or update it.
   289  		if pod.CreationTimestamp.IsZero() {
   290  			log.Debug("Workload child pod not found")
   291  			return apierrors.NewNotFound(schema.GroupResource{Group: pod.APIVersion, Resource: pod.Kind}, pod.Name)
   292  		}
   293  		pod.ObjectMeta.Annotations = MutateAnnotations(trait, traitDefaults, pod.ObjectMeta.Annotations)
   294  		pod.ObjectMeta.Labels = MutateLabels(trait, workload, pod.ObjectMeta.Labels)
   295  		return nil
   296  	})
   297  
   298  	if err != nil && !apierrors.IsNotFound(err) {
   299  		return rel, res, log.ErrorfThrottledNewErr("Failed to update workload child pod %s: %v", vznav.GetNamespacedNameFromObjectMeta(pod.ObjectMeta), err)
   300  	}
   301  
   302  	return rel, controllerutil.OperationResultUpdated, nil
   303  }