Set listener qps and burst (#4558)

This commit is contained in:
Nikola Jokic
2026-09-08 15:49:58 +02:00
committed by GitHub
parent bc677d306c
commit 7c68e1d318
27 changed files with 1161 additions and 226 deletions
@@ -123,6 +123,22 @@ spec:
type: object
x-kubernetes-map-type: atomic
type: array
listenerConfig:
description: ListenerConfig holds configuration for the ghalistener
pod.
properties:
scaler:
description: ScalerConfig configures the Kubernetes client used
by the ghalistener scaler.
properties:
burst:
minimum: 1
type: integer
qps:
minimum: 1
type: integer
type: object
type: object
maxRunners:
minimum: 0
type: integer
@@ -145,6 +145,20 @@ spec:
x-kubernetes-map-type: atomic
type: object
type: object
listenerConfig:
description: ListenerConfig holds configuration for the ghalistener pod.
properties:
scaler:
description: ScalerConfig configures the Kubernetes client used by the ghalistener scaler.
properties:
burst:
minimum: 1
type: integer
qps:
minimum: 1
type: integer
type: object
type: object
listenerConfigSecretMetadata:
description: ResourceMeta carries metadata common to all internal resources
properties:
@@ -123,6 +123,22 @@ spec:
type: object
x-kubernetes-map-type: atomic
type: array
listenerConfig:
description: ListenerConfig holds configuration for the ghalistener
pod.
properties:
scaler:
description: ScalerConfig configures the Kubernetes client used
by the ghalistener scaler.
properties:
burst:
minimum: 1
type: integer
qps:
minimum: 1
type: integer
type: object
type: object
maxRunners:
minimum: 0
type: integer
@@ -145,6 +145,20 @@ spec:
x-kubernetes-map-type: atomic
type: object
type: object
listenerConfig:
description: ListenerConfig holds configuration for the ghalistener pod.
properties:
scaler:
description: ScalerConfig configures the Kubernetes client used by the ghalistener scaler.
properties:
burst:
minimum: 1
type: integer
qps:
minimum: 1
type: integer
type: object
type: object
listenerConfigSecretMetadata:
description: ResourceMeta carries metadata common to all internal resources
properties:
@@ -1,8 +1,8 @@
{{- define "listener-template.pod" -}}
{{- $metadata := .Values.listenerPodTemplate.metadata | default dict -}}
{{- $spec := .Values.listenerPodTemplate.spec | default dict -}}
{{- $metadata := .metadata | default dict -}}
{{- $spec := .spec | default dict -}}
{{- if and (empty $metadata) (empty $spec) -}}
{{- fail "listenerPodTemplate must have at least metadata or spec defined" -}}
{{- fail ".Values.listener.podTemplate must have at least metadata or spec defined" -}}
{{- end -}}
{{- with $metadata -}}
metadata:
@@ -5,6 +5,55 @@
{{- $kubeDefaults := (index $kubeMode "default" | default true) }}
{{- $kubeServiceAccountName := (index $kubeMode "serviceAccountName" | default "") }}
{{- $usesKubernetesSecrets := or (not .Values.secretResolution) (eq .Values.secretResolution.type "kubernetes") }}
{{- /* All listener configuration lives under .Values.listener */ -}}
{{- if hasKey .Values "listenerMetrics" }}
{{- fail ".Values.listenerMetrics has moved to .Values.listener.metrics" }}
{{- end }}
{{- if hasKey .Values "listenerPodTemplate" }}
{{- fail ".Values.listenerPodTemplate has moved to .Values.listener.podTemplate" }}
{{- end }}
{{- $listener := .Values.listener | default dict }}
{{- if not (kindIs "map" $listener) }}
{{- fail ".Values.listener must be an object" }}
{{- end }}
{{- $listenerKeys := list "metrics" "podTemplate" "scaler" }}
{{- range $name, $_ := $listener }}
{{- if not (has $name $listenerKeys) }}
{{- fail (printf ".Values.listener.%s is not a supported key; supported keys are: %s" $name (join ", " $listenerKeys)) }}
{{- end }}
{{- end }}
{{- range $name := list "metrics" "podTemplate" }}
{{- $value := index $listener $name | default dict }}
{{- if not (kindIs "map" $value) }}
{{- fail (printf ".Values.listener.%s must be an object" $name) }}
{{- end }}
{{- end }}
{{- $scaler := $listener.scaler | default dict }}
{{- if not (kindIs "map" $scaler) }}
{{- fail ".Values.listener.scaler must be an object" }}
{{- end }}
{{- $scalerKeys := list "burst" "qps" }}
{{- range $name, $_ := $scaler }}
{{- if not (has $name $scalerKeys) }}
{{- fail (printf ".Values.listener.scaler.%s is not a supported key; supported keys are: %s" $name (join ", " $scalerKeys)) }}
{{- end }}
{{- end }}
{{- range $name := $scalerKeys }}
{{- if hasKey $scaler $name }}
{{- $value := index $scaler $name }}
{{- if not (or (kindIs "int" $value) (kindIs "int64" $value) (kindIs "float64" $value)) }}
{{- fail (printf ".Values.listener.scaler.%s must be an integer greater than 0" $name) }}
{{- else }}
{{- $number := float64 $value }}
{{- if ne $number (floor $number) }}
{{- fail (printf ".Values.listener.scaler.%s must be an integer greater than 0" $name) }}
{{- else if lt $number (float64 1) }}
{{- fail (printf ".Values.listener.scaler.%s must be greater than 0" $name) }}
{{- end }}
{{- end }}
{{- end }}
{{- end }}
{{- $listenerPodTemplate := $listener.podTemplate | default dict }}
{{- $runnerPod := (index $runner "pod" | default dict) -}}
{{- if not (kindIs "map" $runnerPod) -}}
@@ -165,12 +214,17 @@ spec:
minRunners: {{ .Values.scaleset.minRunners | int }}
{{- end }}
{{- if and .Values.listenerPodTemplate (or .Values.listenerPodTemplate.metadata .Values.listenerPodTemplate.spec) }}
{{- if or $listenerPodTemplate.metadata $listenerPodTemplate.spec }}
listenerTemplate:
{{- include "listener-template.pod" . | nindent 4}}
{{- include "listener-template.pod" $listenerPodTemplate | nindent 4}}
{{- end }}
{{- with .Values.listenerMetrics }}
{{- with $listener.scaler }}
listenerConfig:
scaler:
{{- toYaml . | nindent 6 }}
{{- end }}
{{- with $listener.metrics }}
listenerMetrics:
{{- toYaml . | nindent 4 }}
{{- end }}
@@ -1,7 +1,111 @@
suite: "Test AutoscalingRunnerSet Listener Metrics"
suite: "Test AutoscalingRunnerSet Listener"
templates:
- autoscalingrunnserset.yaml
tests:
- it: should render default scaler values when listener.scaler is not overridden
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- equal:
path: spec.listenerConfig.scaler.qps
value: 50
- equal:
path: spec.listenerConfig.scaler.burst
value: 100
- it: should render custom scaler values when overridden
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
listener:
scaler:
qps: 100
burst: 200
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- equal:
path: spec.listenerConfig.scaler.qps
value: 100
- equal:
path: spec.listenerConfig.scaler.burst
value: 200
- it: should render qps override while keeping default burst
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
listener:
scaler:
qps: 75
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- equal:
path: spec.listenerConfig.scaler.qps
value: 75
- equal:
path: spec.listenerConfig.scaler.burst
value: 100
- it: should render scaler and metrics together
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
listener:
scaler:
qps: 75
metrics:
counters:
gha_started_jobs_total:
labels:
- repository
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- equal:
path: spec.listenerConfig.scaler.qps
value: 75
- exists:
path: spec.listenerMetrics
- equal:
path: spec.listenerMetrics.counters.gha_started_jobs_total.labels[0]
value: repository
- it: should not render listenerConfig when listener is null
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
listener: null
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- notExists:
path: spec.listenerConfig
- it: should not render listenerMetrics when not configured
set:
scaleset.name: "test"
@@ -23,18 +127,19 @@ tests:
auth.githubToken: "gh_token12345"
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
listenerMetrics:
counters:
gha_started_jobs_total:
labels:
- repository
- organization
histograms:
gha_job_startup_duration_seconds:
buckets:
- 0.1
- 1
- 2.5
listener:
metrics:
counters:
gha_started_jobs_total:
labels:
- repository
- organization
histograms:
gha_job_startup_duration_seconds:
buckets:
- 0.1
- 1
- 2.5
release:
name: "test-name"
namespace: "test-namespace"
@@ -1,21 +1,22 @@
suite: "AutoscalingRunnerSet listenerPodTemplate"
suite: "AutoscalingRunnerSet listener.podTemplate"
templates:
- autoscalingrunnserset.yaml
tests:
- it: should render listenerTemplate from listenerPodTemplate
- it: should render listenerTemplate from listener.podTemplate
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
listenerPodTemplate:
spec:
containers:
- name: listener
image: "ghcr.io/actions/actions-runner-controller/actionsmetricsserver:latest"
securityContext:
runAsUser: 1000
listener:
podTemplate:
spec:
containers:
- name: listener
image: "ghcr.io/actions/actions-runner-controller/actionsmetricsserver:latest"
securityContext:
runAsUser: 1000
release:
name: "test-name"
namespace: "test-namespace"
@@ -34,9 +35,10 @@ tests:
auth.githubToken: "gh_token12345"
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
listenerPodTemplate:
spec:
restartPolicy: Always
listener:
podTemplate:
spec:
restartPolicy: Always
release:
name: "test-name"
namespace: "test-namespace"
@@ -44,3 +46,49 @@ tests:
- equal:
path: spec.listenerTemplate.spec.containers[0].name
value: listener
- it: should render podTemplate alongside scaler and metrics
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
listener:
scaler:
qps: 75
podTemplate:
spec:
restartPolicy: Always
metrics:
counters:
gha_started_jobs_total:
labels:
- repository
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- equal:
path: spec.listenerConfig.scaler.qps
value: 75
- equal:
path: spec.listenerTemplate.spec.containers[0].name
value: listener
- equal:
path: spec.listenerMetrics.counters.gha_started_jobs_total.labels[0]
value: repository
- it: should not render listenerTemplate when podTemplate is empty
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- notExists:
path: spec.listenerTemplate
@@ -0,0 +1,122 @@
suite: "Test AutoscalingRunnerSet Listener Validation"
templates:
- autoscalingrunnserset.yaml
set: &base
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
tests:
- it: rejects zero qps
set:
<<: *base
listener.scaler.qps: 0
asserts:
- failedTemplate:
errorMessage: ".Values.listener.scaler.qps must be greater than 0"
- it: rejects negative burst
set:
<<: *base
listener.scaler.burst: -1
asserts:
- failedTemplate:
errorMessage: ".Values.listener.scaler.burst must be greater than 0"
- it: rejects fractional qps
set:
<<: *base
listener.scaler.qps: 1.5
asserts:
- failedTemplate:
errorMessage: ".Values.listener.scaler.qps must be an integer greater than 0"
- it: rejects a string burst
set:
<<: *base
listener.scaler.burst: "100"
asserts:
- failedTemplate:
errorMessage: ".Values.listener.scaler.burst must be an integer greater than 0"
- it: rejects a boolean qps
set:
<<: *base
listener.scaler.qps: true
asserts:
- failedTemplate:
errorMessage: ".Values.listener.scaler.qps must be an integer greater than 0"
- it: rejects a scaler that is not an object
set:
<<: *base
listener.scaler: "nope"
asserts:
- failedTemplate:
errorMessage: ".Values.listener.scaler must be an object"
- it: rejects a listener that is not an object
set:
<<: *base
listener: "nope"
asserts:
- failedTemplate:
errorMessage: ".Values.listener must be an object"
- it: rejects a metrics value that is not an object
set:
<<: *base
listener.metrics: "nope"
asserts:
- failedTemplate:
errorMessage: ".Values.listener.metrics must be an object"
- it: rejects a podTemplate that is not an object
set:
<<: *base
listener.podTemplate: "nope"
asserts:
- failedTemplate:
errorMessage: ".Values.listener.podTemplate must be an object"
- it: rejects a misspelled key under listener
set:
<<: *base
listener.metrcs.counters: {}
asserts:
- failedTemplate:
errorMessage: ".Values.listener.metrcs is not a supported key; supported keys are: metrics, podTemplate, scaler"
- it: rejects a misspelled key under listener.scaler
set:
<<: *base
listener.scaler.qbs: 10
asserts:
- failedTemplate:
errorMessage: ".Values.listener.scaler.qbs is not a supported key; supported keys are: burst, qps"
- it: rejects the legacy top-level listenerMetrics key
set:
<<: *base
listenerMetrics.counters: {}
asserts:
- failedTemplate:
errorMessage: ".Values.listenerMetrics has moved to .Values.listener.metrics"
- it: rejects the legacy top-level listenerPodTemplate key
set:
<<: *base
listenerPodTemplate.spec.restartPolicy: Always
asserts:
- failedTemplate:
errorMessage: ".Values.listenerPodTemplate has moved to .Values.listener.podTemplate"
- it: treats a podTemplate with neither metadata nor spec as a no-op
set:
<<: *base
listener.podTemplate.spec: {}
listener.podTemplate.metadata: {}
asserts:
- notExists:
path: spec.listenerTemplate
@@ -75,23 +75,6 @@ secretResolution:
# - example.com
# - example.org
## listenerTemplate is the PodSpec for each listener Pod
## For reference: https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#PodSpec
# listenerPodTemplate:
# spec:
# containers:
# # Use this section to append additional configuration to the listener container.
# # If you change the name of the container, the configuration will not be applied to the listener,
# # and it will be treated as a side-car container.
# - name: listener
# securityContext:
# runAsUser: 1000
# # Use this section to add the configuration of a side-car container.
# # Comment it out or remove it if you don't need it.
# # Spec for this container will be applied as is without any modifications.
# - name: side-car
# image: example-sidecar
## Resource object allows modifying resources created by the chart itself
resource:
# Specifies metadata that will be applied to all resources managed by ARC
@@ -312,158 +295,183 @@ controllerServiceAccount:
namespace: ""
name: ""
## listenerMetrics are configurable metrics applied to the listener.
## In order to avoid helm merging these fields, we left the metrics commented out.
## When configuring metrics, please uncomment the listenerMetrics object below.
## You can modify the configuration to remove the label or specify custom buckets for histogram.
##
## If the buckets field is not specified, the default buckets will be applied. Default buckets are
## provided here for documentation purposes
# listenerMetrics:
# counters:
# gha_started_jobs_total:
# labels:
# ["repository", "organization", "enterprise", "job_name", "event_name", "job_workflow_ref", "job_workflow_name", "job_workflow_target"]
# gha_completed_jobs_total:
# labels:
# [
# "repository",
# "organization",
# "enterprise",
# "job_name",
# "event_name",
# "job_result",
# "job_workflow_ref",
# "job_workflow_name",
# "job_workflow_target",
# ]
# gauges:
# gha_assigned_jobs:
# labels: ["name", "namespace", "repository", "organization", "enterprise"]
# gha_running_jobs:
# labels: ["name", "namespace", "repository", "organization", "enterprise"]
# gha_registered_runners:
# labels: ["name", "namespace", "repository", "organization", "enterprise"]
# gha_busy_runners:
# labels: ["name", "namespace", "repository", "organization", "enterprise"]
# gha_min_runners:
# labels: ["name", "namespace", "repository", "organization", "enterprise"]
# gha_max_runners:
# labels: ["name", "namespace", "repository", "organization", "enterprise"]
# gha_desired_runners:
# labels: ["name", "namespace", "repository", "organization", "enterprise"]
# gha_idle_runners:
# labels: ["name", "namespace", "repository", "organization", "enterprise"]
# histograms:
# gha_job_startup_duration_seconds:
# labels:
# ["repository", "organization", "enterprise", "job_name", "event_name","job_workflow_ref", "job_workflow_name", "job_workflow_target"]
# buckets:
# [
# 0.01,
# 0.05,
# 0.1,
# 0.5,
# 1.0,
# 2.0,
# 3.0,
# 4.0,
# 5.0,
# 6.0,
# 7.0,
# 8.0,
# 9.0,
# 10.0,
# 12.0,
# 15.0,
# 18.0,
# 20.0,
# 25.0,
# 30.0,
# 40.0,
# 50.0,
# 60.0,
# 70.0,
# 80.0,
# 90.0,
# 100.0,
# 110.0,
# 120.0,
# 150.0,
# 180.0,
# 210.0,
# 240.0,
# 300.0,
# 360.0,
# 420.0,
# 480.0,
# 540.0,
# 600.0,
# 900.0,
# 1200.0,
# 1800.0,
# 2400.0,
# 3000.0,
# 3600.0,
# ]
# gha_job_execution_duration_seconds:
# labels:
# [
# "repository",
# "organization",
# "enterprise",
# "job_name",
# "event_name",
# "job_result",
# "job_workflow_ref",
# "job_workflow_name",
# "job_workflow_target"
# ]
# buckets:
# [
# 0.01,
# 0.05,
# 0.1,
# 0.5,
# 1.0,
# 2.0,
# 3.0,
# 4.0,
# 5.0,
# 6.0,
# 7.0,
# 8.0,
# 9.0,
# 10.0,
# 12.0,
# 15.0,
# 18.0,
# 20.0,
# 25.0,
# 30.0,
# 40.0,
# 50.0,
# 60.0,
# 70.0,
# 80.0,
# 90.0,
# 100.0,
# 110.0,
# 120.0,
# 150.0,
# 180.0,
# 210.0,
# 240.0,
# 300.0,
# 360.0,
# 420.0,
# 480.0,
# 540.0,
# 600.0,
# 900.0,
# 1200.0,
# 1800.0,
# 2400.0,
# 3000.0,
# 3600.0,
# ]
# listener specific configuration. This configuration is applied to the listener component of the chart.
listener:
# scaler is config applied to the kubernetes client component of the listener.
# Both values must be integers greater than 0.
scaler:
# qps is the sustained rate of requests the listener may issue to the Kubernetes API server.
qps: 50
# burst is the number of requests the listener may issue to the Kubernetes API server in a burst.
burst: 100
## podTemplate is the PodSpec for each listener Pod
## For reference: https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#PodSpec
# podTemplate:
# spec:
# containers:
# # Use this section to append additional configuration to the listener container.
# # If you change the name of the container, the configuration will not be applied to the listener,
# # and it will be treated as a side-car container.
# - name: listener
# securityContext:
# runAsUser: 1000
# # Use this section to add the configuration of a side-car container.
# # Comment it out or remove it if you don't need it.
# # Spec for this container will be applied as is without any modifications.
# - name: side-car
# image: example-sidecar
## metrics configuration for the listener.
## In order to avoid helm merging these fields, we left the metrics commented out.
## When configuring metrics, please uncomment the metrics object below.
## You can modify the configuration to remove the label or specify custom buckets for histogram.
##
## If the buckets field is not specified, the default buckets will be applied. Default buckets are
## provided here for documentation purposes
# metrics:
# counters:
# gha_started_jobs_total:
# labels:
# ["repository", "organization", "enterprise", "job_name", "event_name", "job_workflow_ref", "job_workflow_name", "job_workflow_target"]
# gha_completed_jobs_total:
# labels:
# [
# "repository",
# "organization",
# "enterprise",
# "job_name",
# "event_name",
# "job_result",
# "job_workflow_ref",
# "job_workflow_name",
# "job_workflow_target",
# ]
# gauges:
# gha_assigned_jobs:
# labels: ["name", "namespace", "repository", "organization", "enterprise"]
# gha_running_jobs:
# labels: ["name", "namespace", "repository", "organization", "enterprise"]
# gha_registered_runners:
# labels: ["name", "namespace", "repository", "organization", "enterprise"]
# gha_busy_runners:
# labels: ["name", "namespace", "repository", "organization", "enterprise"]
# gha_min_runners:
# labels: ["name", "namespace", "repository", "organization", "enterprise"]
# gha_max_runners:
# labels: ["name", "namespace", "repository", "organization", "enterprise"]
# gha_desired_runners:
# labels: ["name", "namespace", "repository", "organization", "enterprise"]
# gha_idle_runners:
# labels: ["name", "namespace", "repository", "organization", "enterprise"]
# histograms:
# gha_job_startup_duration_seconds:
# labels:
# ["repository", "organization", "enterprise", "job_name", "event_name","job_workflow_ref", "job_workflow_name", "job_workflow_target"]
# buckets:
# [
# 0.01,
# 0.05,
# 0.1,
# 0.5,
# 1.0,
# 2.0,
# 3.0,
# 4.0,
# 5.0,
# 6.0,
# 7.0,
# 8.0,
# 9.0,
# 10.0,
# 12.0,
# 15.0,
# 18.0,
# 20.0,
# 25.0,
# 30.0,
# 40.0,
# 50.0,
# 60.0,
# 70.0,
# 80.0,
# 90.0,
# 100.0,
# 110.0,
# 120.0,
# 150.0,
# 180.0,
# 210.0,
# 240.0,
# 300.0,
# 360.0,
# 420.0,
# 480.0,
# 540.0,
# 600.0,
# 900.0,
# 1200.0,
# 1800.0,
# 2400.0,
# 3000.0,
# 3600.0,
# ]
# gha_job_execution_duration_seconds:
# labels:
# [
# "repository",
# "organization",
# "enterprise",
# "job_name",
# "event_name",
# "job_result",
# "job_workflow_ref",
# "job_workflow_name",
# "job_workflow_target"
# ]
# buckets:
# [
# 0.01,
# 0.05,
# 0.1,
# 0.5,
# 1.0,
# 2.0,
# 3.0,
# 4.0,
# 5.0,
# 6.0,
# 7.0,
# 8.0,
# 9.0,
# 10.0,
# 12.0,
# 15.0,
# 18.0,
# 20.0,
# 25.0,
# 30.0,
# 40.0,
# 50.0,
# 60.0,
# 70.0,
# 80.0,
# 90.0,
# 100.0,
# 110.0,
# 120.0,
# 150.0,
# 180.0,
# 210.0,
# 240.0,
# 300.0,
# 360.0,
# 420.0,
# 480.0,
# 540.0,
# 600.0,
# 900.0,
# 1200.0,
# 1800.0,
# 2400.0,
# 3000.0,
# 3600.0,
# ]
@@ -1,5 +1,6 @@
{{- $resourceMeta := default (dict) .Values.resourceMeta }}
{{- $hasCustomResourceMeta := (and .Values.resourceMeta .Values.resourceMeta.autoscalingRunnerSet) }}
{{- /* .Values.listenerConfig is validated by values.schema.json */ -}}
apiVersion: actions.github.com/v1alpha1
kind: AutoscalingRunnerSet
metadata:
@@ -167,6 +168,11 @@ spec:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- with .Values.listenerConfig }}
listenerConfig:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- with (index $resourceMeta "autoscalingListener") }}
autoscalingListener:
{{- include "gha-runner-scale-set.resourceMetaSpec" . | nindent 4 }}
@@ -18,6 +18,7 @@ import (
"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1"
"k8s.io/utils/ptr"
)
func TestTemplateRenderedGitHubSecretWithGitHubToken(t *testing.T) {
@@ -141,6 +142,170 @@ func TestTemplateRenderedGitHubSecretErrorWithMissingAppInput(t *testing.T) {
assert.ErrorContains(t, err, "provide .Values.githubConfigSecret.github_app_installation_id and .Values.githubConfigSecret.github_app_private_key")
}
func TestTemplateListenerScalerValidation(t *testing.T) {
t.Parallel()
helmChartPath, err := filepath.Abs("../../gha-runner-scale-set")
require.NoError(t, err)
baseValues := map[string]string{
"githubConfigUrl": "https://github.com/actions",
"githubConfigSecret.github_token": "gh_token12345",
"controllerServiceAccount.name": "arc",
"controllerServiceAccount.namespace": "arc-system",
}
tests := []struct {
name string
setValues map[string]string
setStrValues map[string]string
wantErrorText string
}{
{
name: "zero qps",
setValues: map[string]string{"listenerConfig.scaler.qps": "0"},
wantErrorText: "at '/listenerConfig/scaler/qps': minimum: got 0, want 1",
},
{
name: "negative burst",
setValues: map[string]string{"listenerConfig.scaler.burst": "-1"},
wantErrorText: "at '/listenerConfig/scaler/burst': minimum: got -1, want 1",
},
{
name: "fractional qps",
setValues: map[string]string{"listenerConfig.scaler.qps": "1.5"},
wantErrorText: "at '/listenerConfig/scaler/qps'",
},
{
name: "string burst",
setStrValues: map[string]string{"listenerConfig.scaler.burst": "100"},
wantErrorText: "at '/listenerConfig/scaler/burst': got string, want integer",
},
{
name: "boolean qps",
setValues: map[string]string{"listenerConfig.scaler.qps": "true"},
wantErrorText: "at '/listenerConfig/scaler/qps': got boolean, want integer",
},
{
name: "scaler is not an object",
setValues: map[string]string{"listenerConfig.scaler[0]": "a"},
wantErrorText: "at '/listenerConfig/scaler': got array, want null or object",
},
{
name: "listenerConfig is not an object",
setStrValues: map[string]string{"listenerConfig": "foo"},
wantErrorText: "at '/listenerConfig': got string, want null or object",
},
{
name: "misspelled scaler key",
setValues: map[string]string{"listenerConfig.scalar.qps": "5"},
wantErrorText: "at '/listenerConfig': additional properties 'scalar' not allowed",
},
{
name: "unknown key under scaler",
setStrValues: map[string]string{"listenerConfig.scaler.foo": "bar"},
wantErrorText: "at '/listenerConfig/scaler': additional properties 'foo' not allowed",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
setValues := make(map[string]string, len(baseValues)+len(tt.setValues))
for key, value := range baseValues {
setValues[key] = value
}
for key, value := range tt.setValues {
setValues[key] = value
}
options := &helm.Options{
Logger: logger.Discard,
SetValues: setValues,
SetStrValues: tt.setStrValues,
KubectlOptions: k8s.NewKubectlOptions("", "", "test"),
}
_, err := helm.RenderTemplateContextE(t, t.Context(), options, helmChartPath, "test-runners", []string{"templates/autoscalingrunnerset.yaml"})
require.Error(t, err)
assert.ErrorContains(t, err, tt.wantErrorText)
})
}
}
func TestTemplateListenerScalerConfig(t *testing.T) {
t.Parallel()
helmChartPath, err := filepath.Abs("../../gha-runner-scale-set")
require.NoError(t, err)
baseValues := map[string]string{
"githubConfigUrl": "https://github.com/actions",
"githubConfigSecret.github_token": "gh_token12345",
"controllerServiceAccount.name": "arc",
"controllerServiceAccount.namespace": "arc-system",
}
tests := []struct {
name string
setValues map[string]string
wantQPS *int
wantBurst *int
}{
{
name: "defaults from values.yaml",
wantQPS: ptr.To(50),
wantBurst: ptr.To(100),
},
{
name: "both overridden",
setValues: map[string]string{"listenerConfig.scaler.qps": "100", "listenerConfig.scaler.burst": "200"},
wantQPS: ptr.To(100),
wantBurst: ptr.To(200),
},
{
name: "qps overridden keeps default burst",
setValues: map[string]string{"listenerConfig.scaler.qps": "75"},
wantQPS: ptr.To(75),
wantBurst: ptr.To(100),
},
{
name: "listenerConfig disabled",
setValues: map[string]string{"listenerConfig": "null"},
wantQPS: nil,
wantBurst: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
setValues := make(map[string]string, len(baseValues)+len(tt.setValues))
for key, value := range baseValues {
setValues[key] = value
}
for key, value := range tt.setValues {
setValues[key] = value
}
options := &helm.Options{
Logger: logger.Discard,
SetValues: setValues,
KubectlOptions: k8s.NewKubectlOptions("", "", "test"),
}
output := helm.RenderTemplateContext(t, t.Context(), options, helmChartPath, "test-runners", []string{"templates/autoscalingrunnerset.yaml"})
var ars v1alpha1.AutoscalingRunnerSet
helm.UnmarshalK8SYaml(t, output, &ars)
if tt.wantQPS == nil && tt.wantBurst == nil {
assert.Nil(t, ars.Spec.ListenerConfig.GetScaler())
return
}
scaler := ars.Spec.ListenerConfig.GetScaler()
require.NotNil(t, scaler)
assert.Equal(t, tt.wantQPS, scaler.QPS)
assert.Equal(t, tt.wantBurst, scaler.Burst)
})
}
}
func TestTemplateNotRenderedGitHubSecretWithPredefinedSecret(t *testing.T) {
t.Parallel()
@@ -0,0 +1,32 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "gha-runner-scale-set values",
"description": "Only values that require strict validation are described here. Any value not listed is intentionally unconstrained.",
"type": "object",
"properties": {
"listenerConfig": {
"description": "Configuration for the ghalistener pod.",
"type": ["object", "null"],
"additionalProperties": false,
"properties": {
"scaler": {
"description": "Configuration for the Kubernetes client used by the ghalistener scaler.",
"type": ["object", "null"],
"additionalProperties": false,
"properties": {
"qps": {
"description": "Queries per second the listener may issue to the Kubernetes API server.",
"type": "integer",
"minimum": 1
},
"burst": {
"description": "Burst of queries the listener may issue to the Kubernetes API server.",
"type": "integer",
"minimum": 1
}
}
}
}
}
}
}
+6
View File
@@ -146,6 +146,12 @@ githubConfigSecret:
# - name: side-car
# image: example-sidecar
## listenerConfig holds configuration for the ghalistener pod.
listenerConfig:
scaler:
qps: 50
burst: 100
## listenerMetrics are configurable metrics applied to the listener.
## In order to avoid helm merging these fields, we left the metrics commented out.
## When configuring metrics, please uncomment the listenerMetrics object below.