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

     1  // Copyright (c) 2020, 2022, 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 navigation
     5  
     6  import (
     7  	"context"
     8  	"errors"
     9  	"fmt"
    10  	"github.com/verrazzano/verrazzano/pkg/log/vzlog"
    11  	"strings"
    12  
    13  	oamv1 "github.com/crossplane/oam-kubernetes-runtime/apis/core/v1alpha2"
    14  	"github.com/crossplane/oam-kubernetes-runtime/pkg/oam"
    15  	vzapi "github.com/verrazzano/verrazzano/application-operator/apis/oam/v1alpha1"
    16  	vzconst "github.com/verrazzano/verrazzano/pkg/constants"
    17  	"go.uber.org/zap"
    18  	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    19  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    20  	"k8s.io/apimachinery/pkg/runtime/schema"
    21  	"k8s.io/apimachinery/pkg/types"
    22  	"sigs.k8s.io/controller-runtime/pkg/client"
    23  )
    24  
    25  // workloadToContainedGVKMap maps Verrazzano workload GroupVersionKind strings to schema.GroupVersionKind
    26  // structs of the resources that the workloads contain. This is needed because the embedded resources
    27  // do not have API version and kind fields.
    28  var workloadToContainedGVKMap = map[string]schema.GroupVersionKind{
    29  	"oam.verrazzano.io/v1alpha1.VerrazzanoWebLogicWorkload":  {Group: "weblogic.oracle", Version: "v9", Kind: "Domain"},
    30  	"oam.verrazzano.io/v1alpha1.VerrazzanoCoherenceWorkload": {Group: "coherence.oracle.com", Version: "v1", Kind: "Coherence"},
    31  }
    32  
    33  // FetchWorkloadDefinition fetches the workload definition of the provided workload.
    34  // The definition is found by converting the workload APIVersion and Kind to a CRD resource name.
    35  // for example core.oam.dev/v1alpha2.ContainerizedWorkload would be converted to
    36  // containerizedworkloads.core.oam.dev.  Workload definitions are always found in the default
    37  // namespace.
    38  func FetchWorkloadDefinition(ctx context.Context, cli client.Reader, log vzlog.VerrazzanoLogger, workload *unstructured.Unstructured) (*oamv1.WorkloadDefinition, error) {
    39  	if workload == nil {
    40  		return nil, fmt.Errorf("invalid workload reference")
    41  	}
    42  	workloadAPIVer, _, _ := unstructured.NestedString(workload.Object, "apiVersion")
    43  	workloadKind, _, _ := unstructured.NestedString(workload.Object, "kind")
    44  	workloadName := GetDefinitionOfResource(workloadAPIVer, workloadKind)
    45  	workloadDef := oamv1.WorkloadDefinition{}
    46  	if err := cli.Get(ctx, workloadName, &workloadDef); err != nil {
    47  		log.Errorf("Failed to fetch workload %s definition: %v", workloadName, err)
    48  		return nil, err
    49  	}
    50  	return &workloadDef, nil
    51  }
    52  
    53  // FetchWorkloadChildren finds the children resource of a workload resource.
    54  // Both the workload and the returned array of children are unstructured maps of primitives.
    55  // Finding children is done by first looking to the workflow definition of the provided workload.
    56  // The workload definition contains a set of child resource types supported by the workload.
    57  // The namespace of the workload is then searched for child resources of the supported types.
    58  func FetchWorkloadChildren(ctx context.Context, cli client.Reader, log vzlog.VerrazzanoLogger, workload *unstructured.Unstructured) ([]*unstructured.Unstructured, error) {
    59  	var err error
    60  	var workloadDefinition *oamv1.WorkloadDefinition
    61  
    62  	// Attempt to fetch workload definition based on the workload GVK.
    63  	if workloadDefinition, err = FetchWorkloadDefinition(ctx, cli, log, workload); err != nil {
    64  		log.Debug("Workload definition not found")
    65  		return nil, err
    66  	}
    67  	// If the workload definition is found then fetch child resources of the declared child types
    68  	var children []*unstructured.Unstructured
    69  	if children, err = FetchUnstructuredChildResourcesByAPIVersionKinds(ctx, cli, log, workload.GetNamespace(), workload.GetUID(), workloadDefinition.Spec.ChildResourceKinds); err != nil {
    70  		return nil, err
    71  	}
    72  	return children, nil
    73  }
    74  
    75  // ComponentFromWorkloadLabels returns the OAM component from the application configuration that references
    76  // the workload. The workload lookup is done using the OAM labels from the workload metadata.
    77  func ComponentFromWorkloadLabels(ctx context.Context, cli client.Reader, namespace string, labels map[string]string) (*oamv1.ApplicationConfigurationComponent, error) {
    78  	// look up the OAM application that aggregates this workload
    79  	componentName, ok := labels[oam.LabelAppComponent]
    80  	if !ok {
    81  		return nil, errors.New("OAM component label missing from metadata")
    82  	}
    83  	appName, ok := labels[oam.LabelAppName]
    84  	if !ok {
    85  		return nil, errors.New("OAM app name label missing from metadata")
    86  	}
    87  
    88  	appConfig := oamv1.ApplicationConfiguration{}
    89  	name := types.NamespacedName{
    90  		Namespace: namespace,
    91  		Name:      appName,
    92  	}
    93  
    94  	if err := cli.Get(ctx, name, &appConfig); err != nil {
    95  		return nil, err
    96  	}
    97  
    98  	// find our component in the app config components collection
    99  	for _, c := range appConfig.Spec.Components {
   100  		if c.ComponentName == componentName {
   101  			return &c, nil
   102  		}
   103  	}
   104  
   105  	return nil, errors.New("unable to find application component for workload")
   106  }
   107  
   108  // LoggingTraitFromWorkloadLabels returns the LoggingTrait object associated with the workload or nil if
   109  // there is no associated logging trait for the workload. If there is an associated logging trait and the lookup of the
   110  // trait fails, an error is returned and the reconcile should be retried.
   111  func LoggingTraitFromWorkloadLabels(ctx context.Context, cli client.Reader, log vzlog.VerrazzanoLogger, namespace string, workloadMeta v1.ObjectMeta) (*vzapi.LoggingTrait, error) {
   112  	log.Debugf("Getting logging trait from OAM labels: %v", workloadMeta.Labels)
   113  	component, err := ComponentFromWorkloadLabels(ctx, cli, namespace, workloadMeta.Labels)
   114  	if err != nil {
   115  		return nil, err
   116  	}
   117  
   118  	hasLoggingTrait := false
   119  	for _, t := range component.Traits {
   120  		u, err := ConvertRawExtensionToUnstructured(&t.Trait)
   121  		if err != nil {
   122  			return nil, err
   123  		}
   124  
   125  		if u.GetKind() == vzapi.LoggingTraitKind {
   126  			hasLoggingTrait = true
   127  			loggingTraitList := &vzapi.LoggingTraitList{}
   128  			loggingTraitList.APIVersion = u.GetAPIVersion()
   129  			loggingTraitList.Kind = u.GetKind()
   130  
   131  			if err := cli.List(ctx, loggingTraitList, client.InNamespace(namespace)); err != nil {
   132  				return nil, err
   133  			}
   134  
   135  			ownerUIDs := make(map[types.UID]struct{}, len(workloadMeta.OwnerReferences))
   136  			for _, owner := range workloadMeta.OwnerReferences {
   137  				ownerUIDs[owner.UID] = struct{}{}
   138  			}
   139  			log.Debugf("Workload owner UID's: %v", ownerUIDs)
   140  
   141  			for _, item := range loggingTraitList.Items {
   142  				for _, owner := range item.GetOwnerReferences() {
   143  					log.Debugf("Comparing logging trait owner with UID: %s and name: %s", owner.UID, item.Spec.WorkloadReference.Name)
   144  					if _, ok := ownerUIDs[owner.UID]; ok {
   145  						if workloadMeta.Name == item.Spec.WorkloadReference.Name {
   146  							log.Debug("Matched Trait")
   147  							return &item, nil
   148  						}
   149  					}
   150  				}
   151  			}
   152  		}
   153  	}
   154  
   155  	if hasLoggingTrait {
   156  		log.Debugf("Unable to lookup associated LoggingTrait for workload %s", workloadMeta.Name)
   157  		return nil, fmt.Errorf("lookup of LoggingTrait failed for workload %s", workloadMeta.Name)
   158  	}
   159  	log.Debugf("Workload %s has no associated logging trait", workloadMeta.Name)
   160  	return nil, nil
   161  }
   162  
   163  // MetricsTraitFromWorkloadLabels returns the MetricsTrait object associated with the workload or nil if
   164  // there is no associated metrics trait for the workload. If there is an associated metrics trait and the lookup of the
   165  // trait fails, an error is returned and the reconcile should be retried.
   166  func MetricsTraitFromWorkloadLabels(ctx context.Context, cli client.Reader, log *zap.SugaredLogger, namespace string, workloadMeta v1.ObjectMeta) (*vzapi.MetricsTrait, error) {
   167  	log.Debug(fmt.Sprintf("Getting metrics trait from OAM labels: %v", workloadMeta.Labels))
   168  	component, err := ComponentFromWorkloadLabels(ctx, cli, namespace, workloadMeta.Labels)
   169  	if err != nil {
   170  		return nil, err
   171  	}
   172  
   173  	hasMetricsTrait := false
   174  	for _, t := range component.Traits {
   175  		u, err := ConvertRawExtensionToUnstructured(&t.Trait)
   176  		if err != nil {
   177  			return nil, err
   178  		}
   179  
   180  		if u.GetKind() == vzapi.MetricsTraitKind {
   181  			hasMetricsTrait = true
   182  			metricsTraitList := &vzapi.MetricsTraitList{}
   183  			metricsTraitList.APIVersion = u.GetAPIVersion()
   184  			metricsTraitList.Kind = u.GetKind()
   185  
   186  			if err := cli.List(ctx, metricsTraitList, client.InNamespace(namespace)); err != nil {
   187  				return nil, err
   188  			}
   189  
   190  			ownerUIDs := make(map[types.UID]struct{}, len(workloadMeta.OwnerReferences))
   191  			for _, owner := range workloadMeta.OwnerReferences {
   192  				ownerUIDs[owner.UID] = struct{}{}
   193  			}
   194  			log.Debugf("Workload owner UID's: %v", ownerUIDs)
   195  
   196  			for _, item := range metricsTraitList.Items {
   197  				for _, owner := range item.GetOwnerReferences() {
   198  					log.Debugf("Comparing metrics trait owner with UID: %s and name: %s", owner.UID, item.Spec.WorkloadReference.Name)
   199  					if _, ok := ownerUIDs[owner.UID]; ok {
   200  						if workloadMeta.Name == item.Spec.WorkloadReference.Name {
   201  							log.Debug("Matched Trait")
   202  							return &item, nil
   203  						}
   204  					}
   205  				}
   206  			}
   207  		}
   208  	}
   209  
   210  	if hasMetricsTrait {
   211  		log.Debugf("Unable to lookup associated MetricTrait for workload %s", workloadMeta.Name)
   212  		return nil, fmt.Errorf("lookup of MetricTrait failed for workload %s", workloadMeta.Name)
   213  	}
   214  	log.Debugf("Workload %s has no associated metric trait", workloadMeta.Name)
   215  	return nil, nil
   216  }
   217  
   218  // IsVerrazzanoWorkloadKind returns true if the workload is a Verrazzano workload kind
   219  // (e.g. VerrazzanoWebLogicWorkload), false otherwise.
   220  func IsVerrazzanoWorkloadKind(workload *unstructured.Unstructured) bool {
   221  	kind := workload.GetKind()
   222  	return strings.HasPrefix(kind, "Verrazzano") && strings.HasSuffix(kind, "Workload")
   223  }
   224  
   225  // IsOwnedByVerrazzanoWorkloadKind returns true if the workloads owner is a Verrazzano workload kind
   226  func IsOwnedByVerrazzanoWorkloadKind(workload *unstructured.Unstructured) bool {
   227  	for _, owner := range workload.GetOwnerReferences() {
   228  		if strings.HasPrefix(owner.Kind, "Verrazzano") && strings.HasSuffix(owner.Kind, "Workload") {
   229  			return true
   230  		}
   231  	}
   232  	return false
   233  }
   234  
   235  // APIVersionAndKindToContainedGVK returns the GroupVersionKind of the contained resource
   236  // for the given wrapper resource API version and kind.
   237  func APIVersionAndKindToContainedGVK(apiVersion string, kind string) *schema.GroupVersionKind {
   238  	key := fmt.Sprintf("%s.%s", apiVersion, kind)
   239  	gvk, ok := workloadToContainedGVKMap[key]
   240  	if ok {
   241  		return &gvk
   242  	}
   243  	return nil
   244  }
   245  
   246  // WorkloadToContainedGVK returns the GroupVersionKind of the contained resource
   247  // for the type wrapped by the provided Verrazzano workload.
   248  func WorkloadToContainedGVK(workload *unstructured.Unstructured) *schema.GroupVersionKind {
   249  	if workload.GetKind() == vzconst.VerrazzanoWebLogicWorkloadKind {
   250  		apiVersion, found, _ := unstructured.NestedString(workload.Object, "spec", "template", "apiVersion")
   251  		var gvk schema.GroupVersionKind
   252  		if found {
   253  			gvk = schema.FromAPIVersionAndKind(apiVersion, "Domain")
   254  		} else {
   255  			gvk = schema.GroupVersionKind{Group: "weblogic.oracle", Version: "v8", Kind: "Domain"}
   256  		}
   257  		return &gvk
   258  	}
   259  
   260  	return APIVersionAndKindToContainedGVK(workload.GetAPIVersion(), workload.GetKind())
   261  }
   262  
   263  // GetContainedWorkloadVersionKindName returns the API version, kind, and name of the contained workload
   264  // inside a Verrazzano*Workload.
   265  func GetContainedWorkloadVersionKindName(workload *unstructured.Unstructured) (string, string, string, error) {
   266  	gvk := WorkloadToContainedGVK(workload)
   267  	if gvk == nil {
   268  		return "", "", "", fmt.Errorf("unable to find contained GroupVersionKind for workload: %v", workload)
   269  	}
   270  
   271  	apiVersion, kind := gvk.ToAPIVersionAndKind()
   272  
   273  	// NOTE: this may need to change if we do not allow the user to set the name or if we do and default it
   274  	// to the workload or component name
   275  	name, found, err := unstructured.NestedString(workload.Object, "spec", "template", "metadata", "name")
   276  	if !found || err != nil {
   277  		return "", "", "", errors.New("unable to find metadata name in contained workload")
   278  	}
   279  
   280  	return apiVersion, kind, name, nil
   281  }
   282  
   283  // FetchContainedWorkload takes a Verrazzano workload and fetches the contained workload as unstructured.
   284  func FetchContainedWorkload(ctx context.Context, cli client.Reader, workload *unstructured.Unstructured) (*unstructured.Unstructured, error) {
   285  	apiVersion, kind, name, err := GetContainedWorkloadVersionKindName(workload)
   286  	if err != nil {
   287  		return nil, err
   288  	}
   289  
   290  	u := &unstructured.Unstructured{}
   291  	u.SetAPIVersion(apiVersion)
   292  	u.SetKind(kind)
   293  
   294  	err = cli.Get(ctx, client.ObjectKey{Namespace: workload.GetNamespace(), Name: name}, u)
   295  	if err != nil {
   296  		return nil, err
   297  	}
   298  
   299  	return u, nil
   300  }