From 69d47730c8ca1b1e71f329da1e6bf03436f02848 Mon Sep 17 00:00:00 2001 From: Malte Poll Date: Mon, 27 Jun 2022 12:35:02 +0200 Subject: [PATCH] [node operator] AutoscalingStrategy controller impl Signed-off-by: Malte Poll --- .../config/rbac/role.yaml | 11 +++ .../autoscalingstrategy_controller.go | 71 ++++++++++++++++--- 2 files changed, 71 insertions(+), 11 deletions(-) diff --git a/operators/constellation-node-operator/config/rbac/role.yaml b/operators/constellation-node-operator/config/rbac/role.yaml index 2d03c6c3a..7dddf935c 100644 --- a/operators/constellation-node-operator/config/rbac/role.yaml +++ b/operators/constellation-node-operator/config/rbac/role.yaml @@ -5,6 +5,17 @@ metadata: creationTimestamp: null name: manager-role rules: +- apiGroups: + - apps + resources: + - deployments + verbs: + - create + - delete + - get + - list + - update + - watch - apiGroups: - update.edgeless.systems resources: diff --git a/operators/constellation-node-operator/controllers/autoscalingstrategy_controller.go b/operators/constellation-node-operator/controllers/autoscalingstrategy_controller.go index 19dbbbbfb..3fb76fee2 100644 --- a/operators/constellation-node-operator/controllers/autoscalingstrategy_controller.go +++ b/operators/constellation-node-operator/controllers/autoscalingstrategy_controller.go @@ -4,7 +4,10 @@ package controllers import ( "context" + appsv1 "k8s.io/api/apps/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/util/retry" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/log" @@ -21,20 +24,51 @@ type AutoscalingStrategyReconciler struct { //+kubebuilder:rbac:groups=update.edgeless.systems,resources=autoscalingstrategies,verbs=get;list;watch;create;update;patch;delete //+kubebuilder:rbac:groups=update.edgeless.systems,resources=autoscalingstrategies/status,verbs=get;update;patch //+kubebuilder:rbac:groups=update.edgeless.systems,resources=autoscalingstrategies/finalizers,verbs=update +//+kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;delete -// Reconcile is part of the main kubernetes reconciliation loop which aims to -// move the current state of the cluster closer to the desired state. -// TODO(user): Modify the Reconcile function to compare the state specified by -// the AutoscalingStrategy object against the actual cluster state, and then -// perform operations to make the cluster state reflect the state specified by -// the user. -// -// For more details, check Reconcile and its Result here: -// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.11.2/pkg/reconcile +// Reconcile enabled or disables the cluster-autoscaler based on the AutoscalingStrategy spec +// by modifying the replicas of the Deployment. func (r *AutoscalingStrategyReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - _ = log.FromContext(ctx) + logr := log.FromContext(ctx) - // TODO(user): your logic here + var desiredAutoscalingStrategy updatev1alpha1.AutoscalingStrategy + if err := r.Get(ctx, req.NamespacedName, &desiredAutoscalingStrategy); err != nil { + logr.Error(err, "Unable to fetch AutoscalingStrategy") + return ctrl.Result{}, client.IgnoreNotFound(err) + } + var expectedReplicas int32 + if desiredAutoscalingStrategy.Spec.Enabled { + expectedReplicas = 1 + } + + var autoscalerDeployment appsv1.Deployment + if err := r.Get(ctx, client.ObjectKey{Namespace: desiredAutoscalingStrategy.Spec.DeploymentNamespace, Name: desiredAutoscalingStrategy.Spec.DeploymentName}, &autoscalerDeployment); err != nil { + logr.Error(err, "Unable to fetch autoscaler Deployment") + return ctrl.Result{}, client.IgnoreNotFound(err) + } + + if autoscalerDeployment.Spec.Replicas == nil || *autoscalerDeployment.Spec.Replicas == 0 { + desiredAutoscalingStrategy.Status.Enabled = false + desiredAutoscalingStrategy.Status.Replicas = 0 + } else { + desiredAutoscalingStrategy.Status.Enabled = true + desiredAutoscalingStrategy.Status.Replicas = *autoscalerDeployment.Spec.Replicas + } + + if err := r.tryUpdateStatus(ctx, req.NamespacedName, desiredAutoscalingStrategy.Status); err != nil { + logr.Error(err, "Unable to update AutoscalingStrategy status") + return ctrl.Result{}, err + } + + if autoscalerDeployment.Spec.Replicas == nil || *autoscalerDeployment.Spec.Replicas != expectedReplicas { + logr.Info("Updating autoscaling replicas", "expectedReplicas", expectedReplicas) + autoscalerDeployment.Spec.Replicas = &expectedReplicas + if err := r.Update(ctx, &autoscalerDeployment); err != nil { + logr.Error(err, "Unable to update autoscaler Deployment") + return ctrl.Result{}, err + } + return ctrl.Result{Requeue: true}, nil + } return ctrl.Result{}, nil } @@ -45,3 +79,18 @@ func (r *AutoscalingStrategyReconciler) SetupWithManager(mgr ctrl.Manager) error For(&updatev1alpha1.AutoscalingStrategy{}). Complete(r) } + +// tryUpdateStatus attempts to update the AutoscalingStrategy status field in a retry loop. +func (r *AutoscalingStrategyReconciler) tryUpdateStatus(ctx context.Context, name types.NamespacedName, status updatev1alpha1.AutoscalingStrategyStatus) error { + return retry.RetryOnConflict(retry.DefaultRetry, func() error { + var autoscalingStrategy updatev1alpha1.AutoscalingStrategy + if err := r.Get(ctx, name, &autoscalingStrategy); err != nil { + return err + } + autoscalingStrategy.Status = *status.DeepCopy() + if err := r.Status().Update(ctx, &autoscalingStrategy); err != nil { + return err + } + return nil + }) +}