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 }