Skip to content

Commit d4d338c

Browse files
committed
feat(controller): scale runner pools from webhook demand annotation
1 parent 78c2110 commit d4d338c

2 files changed

Lines changed: 74 additions & 0 deletions

File tree

internal/controller/impvmrunnerpool_controller.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package controller
1919
import (
2020
"context"
2121
"fmt"
22+
"strconv"
2223
"time"
2324

2425
corev1 "k8s.io/api/core/v1"
@@ -50,6 +51,12 @@ type RunnerDriverFactory func(
5051
pool *impv1alpha1.ImpVMRunnerPool,
5152
) (runnerQueueDepthReader, error)
5253

54+
const (
55+
// AnnotationRunnerDemand is an optional immediate demand signal set by a webhook
56+
// handler or external controller. Value is desired queued jobs as an int.
57+
AnnotationRunnerDemand = "imp.dev/runner-demand"
58+
)
59+
5360
// +kubebuilder:rbac:groups=imp.dev,resources=impvmrunnerpools,verbs=get;list;watch;create;update;patch;delete
5461
// +kubebuilder:rbac:groups=imp.dev,resources=impvmrunnerpools/status,verbs=get;update;patch
5562
// +kubebuilder:rbac:groups=imp.dev,resources=impvmtemplates,verbs=get;list;watch
@@ -135,6 +142,9 @@ func (r *ImpVMRunnerPoolReconciler) Reconcile(ctx context.Context, req ctrl.Requ
135142
} else if int32(queueDepth) > desiredCount {
136143
desiredCount = int32(queueDepth)
137144
}
145+
if webhookDemand := runnerDemandFromAnnotation(pool); webhookDemand > desiredCount {
146+
desiredCount = webhookDemand
147+
}
138148

139149
toCreate := desiredCount - activeCount
140150
if toCreate < 0 {
@@ -313,3 +323,20 @@ func pickSecretValue(m map[string][]byte, preferredKey string) string {
313323
}
314324
return ""
315325
}
326+
327+
func runnerDemandFromAnnotation(pool *impv1alpha1.ImpVMRunnerPool) int32 {
328+
if pool.Spec.JobDetection == nil ||
329+
pool.Spec.JobDetection.Webhook == nil ||
330+
!pool.Spec.JobDetection.Webhook.Enabled {
331+
return 0
332+
}
333+
raw := pool.Annotations[AnnotationRunnerDemand]
334+
if raw == "" {
335+
return 0
336+
}
337+
n, err := strconv.Atoi(raw)
338+
if err != nil || n < 0 {
339+
return 0
340+
}
341+
return int32(n)
342+
}

internal/controller/impvmrunnerpool_controller_test.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,53 @@ func TestRunnerPoolReconciler_scalesFromQueueDepth(t *testing.T) {
248248
}
249249
}
250250

251+
func TestRunnerPoolReconciler_scalesFromWebhookDemandAnnotation(t *testing.T) {
252+
pool := &impv1alpha1.ImpVMRunnerPool{
253+
ObjectMeta: metav1.ObjectMeta{
254+
Name: "ci-pool",
255+
Namespace: "ci",
256+
Annotations: map[string]string{
257+
AnnotationRunnerDemand: "2",
258+
},
259+
},
260+
Spec: impv1alpha1.ImpVMRunnerPoolSpec{
261+
TemplateName: "ubuntu-runner",
262+
Platform: impv1alpha1.RunnerPlatformSpec{
263+
Type: "github-actions",
264+
CredentialsSecret: "gh-creds",
265+
},
266+
Scaling: &impv1alpha1.RunnerScalingSpec{MinIdle: 0, MaxConcurrent: 5},
267+
JobDetection: &impv1alpha1.RunnerJobDetectionSpec{
268+
Webhook: &impv1alpha1.RunnerWebhookSpec{Enabled: true},
269+
},
270+
},
271+
}
272+
tpl := &impv1alpha1.ImpVMTemplate{
273+
ObjectMeta: metav1.ObjectMeta{Name: "ubuntu-runner", Namespace: "ci"},
274+
Spec: impv1alpha1.ImpVMTemplateSpec{ClassRef: impv1alpha1.ClusterObjectRef{Name: "standard"}, Image: "ubuntu:22.04"},
275+
}
276+
277+
scheme := newRunnerPoolTestScheme(t)
278+
c := fake.NewClientBuilder().WithScheme(scheme).
279+
WithObjects(pool, tpl).WithStatusSubresource(pool).Build()
280+
r := &ImpVMRunnerPoolReconciler{Client: c, Scheme: scheme}
281+
282+
_, err := r.Reconcile(context.Background(), reconcile.Request{
283+
NamespacedName: types.NamespacedName{Name: "ci-pool", Namespace: "ci"},
284+
})
285+
if err != nil {
286+
t.Fatalf("unexpected error: %v", err)
287+
}
288+
289+
vmList := &impv1alpha1.ImpVMList{}
290+
_ = c.List(context.Background(), vmList,
291+
client.InNamespace("ci"),
292+
client.MatchingLabels{impv1alpha1.LabelRunnerPool: "ci-pool"})
293+
if len(vmList.Items) != 2 {
294+
t.Fatalf("expected 2 webhook-demand VMs, got %d", len(vmList.Items))
295+
}
296+
}
297+
251298
type stubRunnerQueueDepthReader struct {
252299
queueDepth int
253300
err error

0 commit comments

Comments
 (0)