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 22, 2023
1 parent fd72cc8 commit faf8e59
Show file tree
Hide file tree
Showing 2 changed files with 181 additions and 9 deletions.
171 changes: 162 additions & 9 deletions controllers/operatorpolicy_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,52 @@ package controllers

import (
"context"
"fmt"
"time"

"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/tools/record"
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 = "configuration-policy-controller"
OperatorCRDName string = "configurationpolicies.policy.open-cluster-management.io"
)

var (
OpLog = ctrl.Log.WithName(OperatorControllerName)
// OperatorPlcChan a channel used to pass operator policies ready for update.
OperatorPlcChan chan *policyv1beta1.OperatorPolicy
// testing flag
testFlag = false
)

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

// 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 +66,137 @@ 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 reconcile.Result{}, nil
}
}

return ctrl.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)
// PeriodicallyExecConfigPolicies loops through all configurationpolicies in the target namespace and triggers
// template handling for each one. This function drives all the work the configuration policy controller does.
func (r *OperatorPolicyReconciler) PeriodicallyExecConfigPolicies(
ctx context.Context, freq uint, elected <-chan struct{},
) {
_ = ctx // currently unused

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 {
// when testing, evaluate at every interval regardless of policy status
if testFlag {
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

maxRetries := 3
for i := 1; i <= maxRetries; i++ {
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 {
if i == maxRetries {
return err
}

OpLog.Info(fmt.Sprintf("Failed to update policy status. Retrying (attempt %d/%d): %s", i, maxRetries, err))
} else {
break
}
}

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
}
19 changes: 19 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 Down Expand Up @@ -335,10 +337,22 @@ func main() {
SelectorReconciler: &nsSelReconciler,
EnableMetrics: opts.enableMetrics,
}

OpReconciler := controllers.OperatorPolicyReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
Recorder: mgr.GetEventRecorderFor(controllers.ControllerName),
}

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

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 +376,11 @@ func main() {
managerCancel()
}()

go func() {
OpReconciler.PeriodicallyExecConfigPolicies(terminatingCtx, 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

0 comments on commit faf8e59

Please sign in to comment.