Skip to content

Commit

Permalink
ACM-6596: Initialize controller for OperatorPolicy
Browse files Browse the repository at this point in the history
ref: https://issues.redhat.com/browse/ACM-6596

Signed-off-by: Jeffrey Luo <jeluo@redhat.com>
  • Loading branch information
JeffeyL committed Aug 25, 2023
1 parent fd72cc8 commit 97ce752
Show file tree
Hide file tree
Showing 2 changed files with 169 additions and 10 deletions.
149 changes: 139 additions & 10 deletions controllers/operatorpolicy_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,41 @@ package controllers

import (
"context"
"fmt"
"time"

"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/types"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/reconcile"

policyv1 "open-cluster-management.io/config-policy-controller/api/v1"
policyv1beta1 "open-cluster-management.io/config-policy-controller/api/v1beta1"
)

const (
OperatorControllerName string = "operator-policy-controller"
)

var OpLog = ctrl.Log.WithName(OperatorControllerName)

// OperatorPolicyReconciler reconciles a OperatorPolicy object
type OperatorPolicyReconciler struct {
client.Client
Scheme *runtime.Scheme
}

// SetupWithManager sets up the controller with the Manager.
func (r *OperatorPolicyReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
Named(OperatorControllerName).
For(&policyv1beta1.OperatorPolicy{}).
Complete(r)
}

// blank assignment to verify that OperatorPolicyReconciler implements reconcile.Reconciler
var _ reconcile.Reconciler = &OperatorPolicyReconciler{}

//+kubebuilder:rbac:groups=policy.open-cluster-management.io,resources=operatorpolicies,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=policy.open-cluster-management.io,resources=operatorpolicies/status,verbs=get;update;patch
//+kubebuilder:rbac:groups=policy.open-cluster-management.io,resources=operatorpolicies/finalizers,verbs=update
Expand All @@ -34,16 +55,124 @@ type OperatorPolicyReconciler struct {
// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.14.4/pkg/reconcile
func (r *OperatorPolicyReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
policy := &policyv1beta1.OperatorPolicy{}
_ = r.Get(ctx, req.NamespacedName, policy)

// (user): your logic here
err := r.Get(ctx, req.NamespacedName, policy)
if err != nil {
if errors.IsNotFound(err) {
OpLog.Info("Operator policy could not be found")

return ctrl.Result{}, nil
return reconcile.Result{}, nil
}
}

return reconcile.Result{}, nil
}

// SetupWithManager sets up the controller with the Manager.
func (r *OperatorPolicyReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&policyv1beta1.OperatorPolicy{}).
Complete(r)
// PeriodicallyExecOperatorPolicies loops through all operatorpolicies in the target namespace and triggers
// template handling for each one. This function drives all the work the operator policy controller does.
func (r *OperatorPolicyReconciler) PeriodicallyExecOperatorPolicies(freq uint, elected <-chan struct{},
) {
OpLog.Info("Waiting for leader election before periodically evaluating operator policies")
<-elected

exiting := false
for !exiting {
start := time.Now()

var skipLoop bool
if !skipLoop {
policiesList := policyv1beta1.OperatorPolicyList{}

err := r.List(context.TODO(), &policiesList)
if err != nil {
OpLog.Error(err, "Failed to list the OperatorPolicy objects to evaluate")
} else {
for i := range policiesList.Items {
policy := policiesList.Items[i]
if !r.shouldEvaluatePolicy(&policy) {
continue
}

// handle policy
err := r.handleSinglePolicy(&policy)
if err != nil {
OpLog.Error(err, "Error while evaluating operator policy")
}
}
}
}

elapsed := time.Since(start).Seconds()
if float64(freq) > elapsed {
remainingSleep := float64(freq) - elapsed
sleepTime := time.Duration(remainingSleep) * time.Second
OpLog.V(2).Info("Sleeping before reprocessing the operator policies", "seconds", sleepTime)
time.Sleep(sleepTime)
}
}
}

// handleSinglePolicy encapsulates the logic for processing a single operatorPolicy.
// Currently, this just means setting the status to Compliant and logging the name
// of the operatorPolicy.
//
// In the future, more reconciliation logic will be added. For reference:
// https://github.com/JustinKuli/ocm-enhancements/blob/89-operator-policy/enhancements/sig-policy/89-operator-policy-kind/README.md
func (r *OperatorPolicyReconciler) handleSinglePolicy(
policy *policyv1beta1.OperatorPolicy,
) error {
policy.Status.ComplianceState = policyv1.Compliant

err := r.updatePolicyStatus(policy)
if err != nil {
OpLog.Info("error while updating policy status")

return err
}

OpLog.Info("Logging operator policy", "policy", policy.Name)

return nil
}

// updatePolicyStatus updates the status of the operatorPolicy.
//
// In the future, a condition should be added as well, and this should generate events.
func (r *OperatorPolicyReconciler) updatePolicyStatus(
policy *policyv1beta1.OperatorPolicy,
) error {
updatedStatus := policy.Status

err := r.Get(context.TODO(), types.NamespacedName{Namespace: policy.Namespace, Name: policy.Name}, policy)
if err != nil {
OpLog.Info(fmt.Sprintf("Failed to refresh policy; using previously fetched version: %s", err))
} else {
policy.Status = updatedStatus
}

err = r.Status().Update(context.TODO(), policy)
if err != nil {
OpLog.Info(fmt.Sprintf("Failed to update policy status: %s", err))

return err
}

return nil
}

// shouldEvaluatePolicy will determine if the policy is ready for evaluation by checking
// for the compliance status. If it is already compliant, then evaluation will be skipped.
// It will be evaluated otherwise.
//
// In the future, other mechanisms for determining evaluation should be considered.
func (r *OperatorPolicyReconciler) shouldEvaluatePolicy(
policy *policyv1beta1.OperatorPolicy,
) bool {
if policy.Status.ComplianceState == policyv1.Compliant {
OpLog.Info(fmt.Sprintf("%s is already compliant, skipping evaluation", policy.Name))

return false
}

return true
}
30 changes: 30 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/manager"

policyv1 "open-cluster-management.io/config-policy-controller/api/v1"
policyv1beta1 "open-cluster-management.io/config-policy-controller/api/v1beta1"
"open-cluster-management.io/config-policy-controller/controllers"
"open-cluster-management.io/config-policy-controller/pkg/common"
"open-cluster-management.io/config-policy-controller/pkg/triggeruninstall"
Expand All @@ -64,6 +65,7 @@ func init() {
utilruntime.Must(clientgoscheme.AddToScheme(scheme))
//+kubebuilder:scaffold:scheme
utilruntime.Must(policyv1.AddToScheme(scheme))
utilruntime.Must(policyv1beta1.AddToScheme(scheme))
utilruntime.Must(extensionsv1.AddToScheme(scheme))
utilruntime.Must(extensionsv1beta1.AddToScheme(scheme))
}
Expand All @@ -82,6 +84,7 @@ type ctrlOpts struct {
enableLease bool
enableLeaderElection bool
enableMetrics bool
enableOperatorPolicy bool
}

func main() {
Expand Down Expand Up @@ -335,10 +338,23 @@ func main() {
SelectorReconciler: &nsSelReconciler,
EnableMetrics: opts.enableMetrics,
}

OpReconciler := controllers.OperatorPolicyReconciler{
Client: mgr.GetClient(),
}

if err = reconciler.SetupWithManager(mgr); err != nil {
log.Error(err, "Unable to create controller", "controller", "ConfigurationPolicy")
os.Exit(1)
}

if opts.enableOperatorPolicy {
if err = OpReconciler.SetupWithManager(mgr); err != nil {
log.Error(err, "Unable to create controller", "controller", "OperatorPolicy")
os.Exit(1)
}
}

//+kubebuilder:scaffold:builder

if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {
Expand All @@ -362,6 +378,13 @@ func main() {
managerCancel()
}()

if opts.enableOperatorPolicy {
go func() {
OpReconciler.PeriodicallyExecOperatorPolicies(opts.frequency, mgr.Elected())
managerCancel()
}()
}

// This lease is not related to leader election. This is to report the status of the controller
// to the addon framework. This can be seen in the "status" section of the ManagedClusterAddOn
// resource objects.
Expand Down Expand Up @@ -590,6 +613,13 @@ func parseOpts(flags *pflag.FlagSet, args []string) *ctrlOpts {
"Will scale with concurrency, if not explicitly set.",
)

flags.BoolVar(
&opts.enableOperatorPolicy,
"enable-operator-policy",
false,
"Enable operator policy controller",
)

_ = flags.Parse(args)

// Scale QPS and Burst with concurrency, when they aren't explicitly set.
Expand Down

0 comments on commit 97ce752

Please sign in to comment.