Add HRA printer column "SCHEDULE" (#561)
Adds a column to help the operator see if they configured HRA.Spec.ScheduledOverrides correctly, in a form of "next override schedule recognized by the controller": ``` $ k get horizontalrunnerautoscaler NAME MIN MAX DESIRED SCHEDULE actions-runner-aos-autoscaler 0 5 0 org 0 5 0 min=0 time=2021-05-21 15:00:00 +0000 UTC ``` Ref https://github.com/actions-runner-controller/actions-runner-controller/issues/484
This commit is contained in:
parent
fbb24c8c0a
commit
cb14d7530b
|
|
@ -201,6 +201,11 @@ type HorizontalRunnerAutoscalerStatus struct {
|
|||
|
||||
// +optional
|
||||
CacheEntries []CacheEntry `json:"cacheEntries,omitempty"`
|
||||
|
||||
// ScheduledOverridesSummary is the summary of active and upcoming scheduled overrides to be shown in e.g. a column of a `kubectl get hra` output
|
||||
// for observability.
|
||||
// +optional
|
||||
ScheduledOverridesSummary *string `json:"scheduledOverridesSummary,omitempty"`
|
||||
}
|
||||
|
||||
const CacheEntryKeyDesiredReplicas = "desiredReplicas"
|
||||
|
|
@ -216,6 +221,7 @@ type CacheEntry struct {
|
|||
// +kubebuilder:printcolumn:JSONPath=".spec.minReplicas",name=Min,type=number
|
||||
// +kubebuilder:printcolumn:JSONPath=".spec.maxReplicas",name=Max,type=number
|
||||
// +kubebuilder:printcolumn:JSONPath=".status.desiredReplicas",name=Desired,type=number
|
||||
// +kubebuilder:printcolumn:JSONPath=".status.scheduledOverridesSummary",name=Schedule,type=string
|
||||
|
||||
// HorizontalRunnerAutoscaler is the Schema for the horizontalrunnerautoscaler API
|
||||
type HorizontalRunnerAutoscaler struct {
|
||||
|
|
|
|||
|
|
@ -250,6 +250,11 @@ func (in *HorizontalRunnerAutoscalerStatus) DeepCopyInto(out *HorizontalRunnerAu
|
|||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
if in.ScheduledOverridesSummary != nil {
|
||||
in, out := &in.ScheduledOverridesSummary, &out.ScheduledOverridesSummary
|
||||
*out = new(string)
|
||||
**out = **in
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HorizontalRunnerAutoscalerStatus.
|
||||
|
|
|
|||
|
|
@ -18,6 +18,9 @@ spec:
|
|||
- JSONPath: .status.desiredReplicas
|
||||
name: Desired
|
||||
type: number
|
||||
- JSONPath: .status.scheduledOverridesSummary
|
||||
name: Schedule
|
||||
type: string
|
||||
group: actions.summerwind.dev
|
||||
names:
|
||||
kind: HorizontalRunnerAutoscaler
|
||||
|
|
@ -265,6 +268,11 @@ spec:
|
|||
which is updated on mutation by the API Server.
|
||||
format: int64
|
||||
type: integer
|
||||
scheduledOverridesSummary:
|
||||
description: ScheduledOverridesSummary is the summary of active and
|
||||
upcoming scheduled overrides to be shown in e.g. a column of a `kubectl
|
||||
get hra` output for observability.
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
version: v1alpha1
|
||||
|
|
|
|||
|
|
@ -18,6 +18,9 @@ spec:
|
|||
- JSONPath: .status.desiredReplicas
|
||||
name: Desired
|
||||
type: number
|
||||
- JSONPath: .status.scheduledOverridesSummary
|
||||
name: Schedule
|
||||
type: string
|
||||
group: actions.summerwind.dev
|
||||
names:
|
||||
kind: HorizontalRunnerAutoscaler
|
||||
|
|
@ -265,6 +268,11 @@ spec:
|
|||
which is updated on mutation by the API Server.
|
||||
format: int64
|
||||
type: integer
|
||||
scheduledOverridesSummary:
|
||||
description: ScheduledOverridesSummary is the summary of active and
|
||||
upcoming scheduled overrides to be shown in e.g. a column of a `kubectl
|
||||
get hra` output for observability.
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
version: v1alpha1
|
||||
|
|
|
|||
|
|
@ -209,7 +209,7 @@ func TestDetermineDesiredReplicas_RepositoryRunner(t *testing.T) {
|
|||
Replicas: tc.fixed,
|
||||
},
|
||||
Status: v1alpha1.RunnerDeploymentStatus{
|
||||
Replicas: tc.sReplicas,
|
||||
DesiredReplicas: tc.sReplicas,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -224,7 +224,12 @@ func TestDetermineDesiredReplicas_RepositoryRunner(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
got, _, _, err := h.computeReplicasWithCache(log, metav1Now.Time, rd, hra)
|
||||
minReplicas, _, _, err := h.getMinReplicas(log, metav1Now.Time, hra)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
got, _, _, err := h.computeReplicasWithCache(log, metav1Now.Time, rd, hra, minReplicas)
|
||||
if err != nil {
|
||||
if tc.err == "" {
|
||||
t.Fatalf("unexpected error: expected none, got %v", err)
|
||||
|
|
@ -459,7 +464,7 @@ func TestDetermineDesiredReplicas_OrganizationalRunner(t *testing.T) {
|
|||
Replicas: tc.fixed,
|
||||
},
|
||||
Status: v1alpha1.RunnerDeploymentStatus{
|
||||
Replicas: tc.sReplicas,
|
||||
DesiredReplicas: tc.sReplicas,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -483,7 +488,12 @@ func TestDetermineDesiredReplicas_OrganizationalRunner(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
got, _, _, err := h.computeReplicasWithCache(log, metav1Now.Time, rd, hra)
|
||||
minReplicas, _, _, err := h.getMinReplicas(log, metav1Now.Time, hra)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
got, _, _, err := h.computeReplicasWithCache(log, metav1Now.Time, rd, hra, minReplicas)
|
||||
if err != nil {
|
||||
if tc.err == "" {
|
||||
t.Fatalf("unexpected error: expected none, got %v", err)
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ package controllers
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
|
|
@ -91,7 +92,14 @@ func (r *HorizontalRunnerAutoscalerReconciler) Reconcile(req ctrl.Request) (ctrl
|
|||
|
||||
now := time.Now()
|
||||
|
||||
newDesiredReplicas, computedReplicas, computedReplicasFromCache, err := r.computeReplicasWithCache(log, now, rd, hra)
|
||||
minReplicas, active, upcoming, err := r.getMinReplicas(log, now, hra)
|
||||
if err != nil {
|
||||
log.Error(err, "Could not compute min replicas")
|
||||
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
newDesiredReplicas, computedReplicas, computedReplicasFromCache, err := r.computeReplicasWithCache(log, now, rd, hra, minReplicas)
|
||||
if err != nil {
|
||||
r.Recorder.Event(&hra, corev1.EventTypeNormal, "RunnerAutoscalingFailure", err.Error())
|
||||
|
||||
|
|
@ -112,11 +120,9 @@ func (r *HorizontalRunnerAutoscalerReconciler) Reconcile(req ctrl.Request) (ctrl
|
|||
}
|
||||
}
|
||||
|
||||
var updated *v1alpha1.HorizontalRunnerAutoscaler
|
||||
updated := hra.DeepCopy()
|
||||
|
||||
if hra.Status.DesiredReplicas == nil || *hra.Status.DesiredReplicas != newDesiredReplicas {
|
||||
updated = hra.DeepCopy()
|
||||
|
||||
if (hra.Status.DesiredReplicas == nil && newDesiredReplicas > 1) ||
|
||||
(hra.Status.DesiredReplicas != nil && newDesiredReplicas > *hra.Status.DesiredReplicas) {
|
||||
|
||||
|
|
@ -127,10 +133,6 @@ func (r *HorizontalRunnerAutoscalerReconciler) Reconcile(req ctrl.Request) (ctrl
|
|||
}
|
||||
|
||||
if computedReplicasFromCache == nil {
|
||||
if updated == nil {
|
||||
updated = hra.DeepCopy()
|
||||
}
|
||||
|
||||
cacheEntries := getValidCacheEntries(updated, now)
|
||||
|
||||
var cacheDuration time.Duration
|
||||
|
|
@ -148,11 +150,34 @@ func (r *HorizontalRunnerAutoscalerReconciler) Reconcile(req ctrl.Request) (ctrl
|
|||
})
|
||||
}
|
||||
|
||||
if updated != nil {
|
||||
var overridesSummary string
|
||||
|
||||
if (active != nil && upcoming == nil) || (active != nil && upcoming != nil && active.Period.EndTime.Before(upcoming.Period.StartTime)) {
|
||||
after := defaultReplicas
|
||||
if hra.Spec.MinReplicas != nil && *hra.Spec.MinReplicas >= 0 {
|
||||
after = *hra.Spec.MinReplicas
|
||||
}
|
||||
|
||||
overridesSummary = fmt.Sprintf("min=%d time=%s", after, active.Period.EndTime)
|
||||
}
|
||||
|
||||
if active == nil && upcoming != nil || (active != nil && upcoming != nil && active.Period.EndTime.After(upcoming.Period.StartTime)) {
|
||||
if upcoming.ScheduledOverride.MinReplicas != nil {
|
||||
overridesSummary = fmt.Sprintf("min=%d time=%s", *upcoming.ScheduledOverride.MinReplicas, upcoming.Period.StartTime)
|
||||
}
|
||||
}
|
||||
|
||||
if overridesSummary != "" {
|
||||
updated.Status.ScheduledOverridesSummary = &overridesSummary
|
||||
} else {
|
||||
updated.Status.ScheduledOverridesSummary = nil
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(hra.Status, updated.Status) {
|
||||
metrics.SetHorizontalRunnerAutoscalerStatus(updated.ObjectMeta, updated.Status)
|
||||
|
||||
if err := r.Status().Patch(ctx, updated, client.MergeFrom(&hra)); err != nil {
|
||||
return ctrl.Result{}, fmt.Errorf("patching horizontalrunnerautoscaler status to add cache entry: %w", err)
|
||||
return ctrl.Result{}, fmt.Errorf("patching horizontalrunnerautoscaler status: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -185,9 +210,14 @@ func (r *HorizontalRunnerAutoscalerReconciler) SetupWithManager(mgr ctrl.Manager
|
|||
Complete(r)
|
||||
}
|
||||
|
||||
func (r *HorizontalRunnerAutoscalerReconciler) matchScheduledOverrides(log logr.Logger, now time.Time, hra v1alpha1.HorizontalRunnerAutoscaler) (*int, *Period, *Period, error) {
|
||||
type Override struct {
|
||||
ScheduledOverride v1alpha1.ScheduledOverride
|
||||
Period Period
|
||||
}
|
||||
|
||||
func (r *HorizontalRunnerAutoscalerReconciler) matchScheduledOverrides(log logr.Logger, now time.Time, hra v1alpha1.HorizontalRunnerAutoscaler) (*int, *Override, *Override, error) {
|
||||
var minReplicas *int
|
||||
var active, upcoming *Period
|
||||
var active, upcoming *Override
|
||||
|
||||
for _, o := range hra.Spec.ScheduledOverrides {
|
||||
log.V(1).Info(
|
||||
|
|
@ -213,7 +243,7 @@ func (r *HorizontalRunnerAutoscalerReconciler) matchScheduledOverrides(log logr.
|
|||
// Use the first when there are two or more active scheduled overrides,
|
||||
// as the spec defines that the earlier scheduled override is prioritized higher than later ones.
|
||||
if a != nil && active == nil {
|
||||
active = a
|
||||
active = &Override{Period: *a, ScheduledOverride: o}
|
||||
|
||||
if o.MinReplicas != nil {
|
||||
minReplicas = o.MinReplicas
|
||||
|
|
@ -227,8 +257,8 @@ func (r *HorizontalRunnerAutoscalerReconciler) matchScheduledOverrides(log logr.
|
|||
}
|
||||
}
|
||||
|
||||
if u != nil && (upcoming == nil || u.StartTime.Before(upcoming.StartTime)) {
|
||||
upcoming = u
|
||||
if u != nil && (upcoming == nil || u.StartTime.Before(upcoming.Period.StartTime)) {
|
||||
upcoming = &Override{Period: *u, ScheduledOverride: o}
|
||||
|
||||
log.V(1).Info(
|
||||
"Found upcoming scheduled override",
|
||||
|
|
@ -242,18 +272,23 @@ func (r *HorizontalRunnerAutoscalerReconciler) matchScheduledOverrides(log logr.
|
|||
return minReplicas, active, upcoming, nil
|
||||
}
|
||||
|
||||
func (r *HorizontalRunnerAutoscalerReconciler) computeReplicasWithCache(log logr.Logger, now time.Time, rd v1alpha1.RunnerDeployment, hra v1alpha1.HorizontalRunnerAutoscaler) (int, int, *int, error) {
|
||||
func (r *HorizontalRunnerAutoscalerReconciler) getMinReplicas(log logr.Logger, now time.Time, hra v1alpha1.HorizontalRunnerAutoscaler) (int, *Override, *Override, error) {
|
||||
minReplicas := defaultReplicas
|
||||
if hra.Spec.MinReplicas != nil && *hra.Spec.MinReplicas >= 0 {
|
||||
minReplicas = *hra.Spec.MinReplicas
|
||||
}
|
||||
|
||||
if m, _, _, err := r.matchScheduledOverrides(log, now, hra); err != nil {
|
||||
return 0, 0, nil, err
|
||||
m, active, upcoming, err := r.matchScheduledOverrides(log, now, hra)
|
||||
if err != nil {
|
||||
return 0, nil, nil, err
|
||||
} else if m != nil {
|
||||
minReplicas = *m
|
||||
}
|
||||
|
||||
return minReplicas, active, upcoming, nil
|
||||
}
|
||||
|
||||
func (r *HorizontalRunnerAutoscalerReconciler) computeReplicasWithCache(log logr.Logger, now time.Time, rd v1alpha1.RunnerDeployment, hra v1alpha1.HorizontalRunnerAutoscaler, minReplicas int) (int, int, *int, error) {
|
||||
var suggestedReplicas int
|
||||
|
||||
suggestedReplicasFromCache := r.fetchSuggestedReplicasFromCache(hra)
|
||||
|
|
|
|||
Loading…
Reference in New Issue