open-cluster-management.io/governance-policy-propagator@v0.13.0/controllers/policymetrics/policymetrics_controller.go (about)

     1  // Copyright (c) 2021 Red Hat, Inc.
     2  // Copyright Contributors to the Open Cluster Management project
     3  
     4  package policymetrics
     5  
     6  import (
     7  	"context"
     8  	"strings"
     9  
    10  	"github.com/prometheus/client_golang/prometheus"
    11  	"k8s.io/apimachinery/pkg/api/errors"
    12  	"k8s.io/apimachinery/pkg/runtime"
    13  	ctrl "sigs.k8s.io/controller-runtime"
    14  	"sigs.k8s.io/controller-runtime/pkg/client"
    15  	"sigs.k8s.io/controller-runtime/pkg/controller"
    16  	"sigs.k8s.io/controller-runtime/pkg/reconcile"
    17  
    18  	policiesv1 "open-cluster-management.io/governance-policy-propagator/api/v1"
    19  	"open-cluster-management.io/governance-policy-propagator/controllers/common"
    20  )
    21  
    22  const ControllerName string = "policy-metrics"
    23  
    24  var log = ctrl.Log.WithName(ControllerName)
    25  
    26  // SetupWithManager sets up the controller with the Manager.
    27  func (r *MetricReconciler) SetupWithManager(mgr ctrl.Manager, maxConcurrentReconciles uint) error {
    28  	return ctrl.NewControllerManagedBy(mgr).
    29  		// The work queue prevents the same item being reconciled concurrently:
    30  		// https://github.com/kubernetes-sigs/controller-runtime/issues/1416#issuecomment-899833144
    31  		WithOptions(controller.Options{MaxConcurrentReconciles: int(maxConcurrentReconciles)}).
    32  		Named(ControllerName).
    33  		For(&policiesv1.Policy{}).
    34  		Complete(r)
    35  }
    36  
    37  // blank assignment to verify that ReconcilePolicy implements reconcile.Reconciler
    38  var _ reconcile.Reconciler = &MetricReconciler{}
    39  
    40  // MetricReconciler reconciles the metrics for the Policy
    41  type MetricReconciler struct {
    42  	client.Client
    43  	Scheme *runtime.Scheme
    44  }
    45  
    46  //+kubebuilder:rbac:groups=policy.open-cluster-management.io,resources=policies,verbs=get;list;watch;create;update;patch;delete
    47  //+kubebuilder:rbac:groups=policy.open-cluster-management.io,resources=policies/status,verbs=get;update;patch
    48  //+kubebuilder:rbac:groups=policy.open-cluster-management.io,resources=policies/finalizers,verbs=update
    49  
    50  // Reconcile reads the state of the cluster for the Policy object and ensures that the exported
    51  // policy metrics are accurate, updating them as necessary.
    52  func (r *MetricReconciler) Reconcile(ctx context.Context, request ctrl.Request) (ctrl.Result, error) {
    53  	log := log.WithValues("Request.Namespace", request.Namespace, "Request.Name", request.Name)
    54  	log.Info("Reconciling metric for the policy")
    55  
    56  	// Need to know if the policy is a root policy to create the correct prometheus labels
    57  	// Can't try to use a label on the policy, because the policy might have been deleted.
    58  	inClusterNs, err := common.IsInClusterNamespace(ctx, r.Client, request.Namespace)
    59  	if err != nil {
    60  		log.Error(err, "Failed to determine if the policy is a replicated policy")
    61  
    62  		return reconcile.Result{}, err
    63  	}
    64  
    65  	var promLabels map[string]string
    66  
    67  	if inClusterNs {
    68  		// propagated policies should look like <namespace>.<name>
    69  		// also note: k8s namespace names follow RFC 1123 (so no "." in it)
    70  		splitName := strings.SplitN(request.Name, ".", 2)
    71  		if len(splitName) < 2 {
    72  			// Don't do any metrics if the policy is invalid.
    73  			log.Info("Invalid policy in cluster namespace: missing root policy ns prefix")
    74  
    75  			return reconcile.Result{}, nil
    76  		}
    77  
    78  		promLabels = prometheus.Labels{
    79  			"type":              "propagated",
    80  			"policy":            splitName[1],
    81  			"policy_namespace":  splitName[0],
    82  			"cluster_namespace": request.Namespace,
    83  		}
    84  	} else {
    85  		promLabels = prometheus.Labels{
    86  			"type":              "root",
    87  			"policy":            request.Name,
    88  			"policy_namespace":  request.Namespace,
    89  			"cluster_namespace": "<null>", // this is basically a sentinel value
    90  		}
    91  	}
    92  
    93  	pol := &policiesv1.Policy{}
    94  
    95  	err = r.Get(ctx, request.NamespacedName, pol)
    96  	if err != nil {
    97  		if errors.IsNotFound(err) {
    98  			// Try to delete the gauge, but don't get hung up on errors. Log whether it was deleted.
    99  			statusGaugeDeleted := policyStatusGauge.Delete(promLabels)
   100  			log.Info("Policy not found. It must have been deleted.", "status-gauge-deleted", statusGaugeDeleted)
   101  
   102  			return reconcile.Result{}, nil
   103  		}
   104  
   105  		log.Error(err, "Failed to get Policy")
   106  
   107  		return reconcile.Result{}, err
   108  	}
   109  
   110  	log.V(2).Info("Got active state", "pol.Spec.Disabled", pol.Spec.Disabled)
   111  
   112  	if pol.Spec.Disabled {
   113  		// The policy is no longer active, so delete its metric
   114  		statusGaugeDeleted := policyStatusGauge.Delete(promLabels)
   115  		log.V(1).Info("Metric removed for non-active policy", "status-gauge-deleted", statusGaugeDeleted)
   116  
   117  		return reconcile.Result{}, nil
   118  	}
   119  
   120  	log.V(2).Info("Got ComplianceState", "pol.Status.ComplianceState", pol.Status.ComplianceState)
   121  
   122  	statusMetric, err := policyStatusGauge.GetMetricWith(promLabels)
   123  	if err != nil {
   124  		log.Error(err, "Failed to get status metric from GaugeVec")
   125  
   126  		return reconcile.Result{}, err
   127  	}
   128  
   129  	if pol.Status.ComplianceState == policiesv1.Compliant {
   130  		statusMetric.Set(0)
   131  	} else if pol.Status.ComplianceState == policiesv1.NonCompliant {
   132  		statusMetric.Set(1)
   133  	}
   134  
   135  	return reconcile.Result{}, nil
   136  }