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 }