2023-01-12 18:24:11 -05:00
package actionssummerwindnet
2020-06-27 17:26:46 +09:00
import (
2021-06-23 20:25:03 +09:00
"context"
2020-06-27 17:26:46 +09:00
"fmt"
2020-10-07 17:00:44 -07:00
"net/http/httptest"
"net/url"
"testing"
2022-12-28 01:38:34 +01:00
"github.com/actions/actions-runner-controller/apis/actions.summerwind.net/v1alpha1"
"github.com/actions/actions-runner-controller/github"
"github.com/actions/actions-runner-controller/github/fake"
2020-06-27 17:26:46 +09:00
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
)
func newGithubClient ( server * httptest . Server ) * github . Client {
2020-10-28 15:15:53 +02:00
c := github . Config {
Token : "token" ,
}
client , err := c . NewClient ( )
2020-06-27 17:26:46 +09:00
if err != nil {
panic ( err )
}
baseURL , err := url . Parse ( server . URL + "/" )
if err != nil {
panic ( err )
}
client . Client . BaseURL = baseURL
return client
}
func TestDetermineDesiredReplicas_RepositoryRunner ( t * testing . T ) {
intPtr := func ( v int ) * int {
return & v
}
metav1Now := metav1 . Now ( )
testcases := [ ] struct {
2022-04-24 14:41:34 +09:00
description string
repo string
org string
labels [ ] string
2021-02-16 18:55:55 +09:00
fixed * int
max * int
min * int
sReplicas * int
sTime * metav1 . Time
workflowRuns string
workflowRuns_queued string
workflowRuns_in_progress string
2020-10-07 17:00:44 -07:00
workflowJobs map [ int ] string
2020-06-27 17:26:46 +09:00
want int
err string
} {
2023-07-25 02:04:54 +02:00
// case_0
2020-10-07 17:00:44 -07:00
// Legacy functionality
2023-07-25 02:04:54 +02:00
// 0 demanded due to zero runID, min at 2
2020-06-27 17:26:46 +09:00
{
2021-02-16 18:55:55 +09:00
repo : "test/valid" ,
min : intPtr ( 2 ) ,
max : intPtr ( 3 ) ,
workflowRuns : ` { "total_count": 4, "workflow_runs":[ { "status":"queued"}, { "status":"in_progress"}, { "status":"in_progress"}, { "status":"completed"}]}" ` ,
workflowRuns_queued : ` { "total_count": 1, "workflow_runs":[ { "status":"queued"}]}" ` ,
workflowRuns_in_progress : ` { "total_count": 2, "workflow_runs":[ { "status":"in_progress"}, { "status":"in_progress"}]}" ` ,
2023-07-25 02:04:54 +02:00
want : 2 ,
2020-06-27 17:26:46 +09:00
} ,
2023-07-25 02:04:54 +02:00
// case_1
// Explicitly specified the default `self-hosted` label which is ignored by the simulator,
2022-04-24 14:41:34 +09:00
// as we assume that GitHub Actions automatically associates the `self-hosted` label to every self-hosted runner.
// 3 demanded, max at 3
{
repo : "test/valid" ,
labels : [ ] string { "self-hosted" } ,
min : intPtr ( 2 ) ,
max : intPtr ( 3 ) ,
2023-07-25 02:04:54 +02:00
workflowRuns : ` { "total_count": 4, "workflow_runs":[ { "id": 1, "status":"queued"}, { "id": 2, "status":"in_progress"}, { "id": 3, "status":"in_progress"}, { "status":"completed"}]}" ` ,
workflowRuns_queued : ` { "total_count": 1, "workflow_runs":[ { "id": 1, "status":"queued"}]}" ` ,
workflowRuns_in_progress : ` { "total_count": 2, "workflow_runs":[ { "id": 2, "status":"in_progress"}, { "id": 3, "status":"in_progress"}]}" ` ,
workflowJobs : map [ int ] string {
1 : ` { "jobs": [ { "status": "queued", "labels":["self-hosted"]}]} ` ,
2 : ` { "jobs": [ { "status": "in_progress", "labels":["self-hosted"]}]} ` ,
3 : ` { "jobs": [ { "status": "in_progress", "labels":["self-hosted"]}]} ` ,
} ,
want : 3 ,
2022-04-24 14:41:34 +09:00
} ,
2023-07-25 02:04:54 +02:00
// case_2
2020-06-27 17:26:46 +09:00
// 2 demanded, max at 3, currently 3, delay scaling down due to grace period
{
2021-02-16 18:55:55 +09:00
repo : "test/valid" ,
min : intPtr ( 2 ) ,
max : intPtr ( 3 ) ,
sReplicas : intPtr ( 3 ) ,
sTime : & metav1Now ,
workflowRuns : ` { "total_count": 3, "workflow_runs":[ { "status":"queued"}, { "status":"in_progress"}, { "status":"completed"}]}" ` ,
workflowRuns_queued : ` { "total_count": 1, "workflow_runs":[ { "status":"queued"}]}" ` ,
workflowRuns_in_progress : ` { "total_count": 1, "workflow_runs":[ { "status":"in_progress"}]}" ` ,
want : 3 ,
2020-06-27 17:26:46 +09:00
} ,
2023-07-25 02:04:54 +02:00
// case_3
2020-06-27 17:26:46 +09:00
// 3 demanded, max at 2
{
2021-02-16 18:55:55 +09:00
repo : "test/valid" ,
min : intPtr ( 2 ) ,
max : intPtr ( 2 ) ,
workflowRuns : ` { "total_count": 4, "workflow_runs":[ { "status":"queued"}, { "status":"in_progress"}, { "status":"in_progress"}, { "status":"completed"}]}" ` ,
workflowRuns_queued : ` { "total_count": 1, "workflow_runs":[ { "status":"queued"}]}" ` ,
workflowRuns_in_progress : ` { "total_count": 2, "workflow_runs":[ { "status":"in_progress"}, { "status":"in_progress"}]}" ` ,
want : 2 ,
2020-06-27 17:26:46 +09:00
} ,
2023-07-25 02:04:54 +02:00
// case_4
2020-06-27 17:26:46 +09:00
// 2 demanded, min at 2
{
2021-02-16 18:55:55 +09:00
repo : "test/valid" ,
min : intPtr ( 2 ) ,
max : intPtr ( 3 ) ,
workflowRuns : ` { "total_count": 3, "workflow_runs":[ { "status":"queued"}, { "status":"in_progress"}, { "status":"completed"}]}" ` ,
workflowRuns_queued : ` { "total_count": 1, "workflow_runs":[ { "status":"queued"}]}" ` ,
workflowRuns_in_progress : ` { "total_count": 1, "workflow_runs":[ { "status":"in_progress"}]}" ` ,
want : 2 ,
2020-06-27 17:26:46 +09:00
} ,
2023-07-25 02:04:54 +02:00
// case_5
2020-06-27 17:26:46 +09:00
// 1 demanded, min at 2
{
2021-02-16 18:55:55 +09:00
repo : "test/valid" ,
min : intPtr ( 2 ) ,
max : intPtr ( 3 ) ,
workflowRuns : ` { "total_count": 2, "workflow_runs":[ { "status":"queued"}, { "status":"completed"}]}" ` ,
workflowRuns_queued : ` { "total_count": 1, "workflow_runs":[ { "status":"queued"}]}" ` ,
workflowRuns_in_progress : ` { "total_count": 0, "workflow_runs":[]}" ` ,
want : 2 ,
2020-06-27 17:26:46 +09:00
} ,
2023-07-25 02:04:54 +02:00
// case_6
2020-06-27 17:26:46 +09:00
// 1 demanded, min at 2
{
2021-02-16 18:55:55 +09:00
repo : "test/valid" ,
min : intPtr ( 2 ) ,
max : intPtr ( 3 ) ,
workflowRuns : ` { "total_count": 2, "workflow_runs":[ { "status":"in_progress"}, { "status":"completed"}]}" ` ,
workflowRuns_queued : ` { "total_count": 0, "workflow_runs":[]}" ` ,
workflowRuns_in_progress : ` { "total_count": 1, "workflow_runs":[ { "status":"in_progress"}]}" ` ,
want : 2 ,
2020-06-27 17:26:46 +09:00
} ,
2023-07-25 02:04:54 +02:00
// case_7
2020-06-27 17:26:46 +09:00
// 1 demanded, min at 1
{
2021-02-16 18:55:55 +09:00
repo : "test/valid" ,
min : intPtr ( 1 ) ,
max : intPtr ( 3 ) ,
workflowRuns : ` { "total_count": 2, "workflow_runs":[ { "status":"queued"}, { "status":"completed"}]}" ` ,
workflowRuns_queued : ` { "total_count": 1, "workflow_runs":[ { "status":"queued"}]}" ` ,
workflowRuns_in_progress : ` { "total_count": 0, "workflow_runs":[]}" ` ,
want : 1 ,
2020-06-27 17:26:46 +09:00
} ,
2023-07-25 02:04:54 +02:00
// case_8
2020-06-27 17:26:46 +09:00
// 1 demanded, min at 1
{
2021-02-16 18:55:55 +09:00
repo : "test/valid" ,
min : intPtr ( 1 ) ,
max : intPtr ( 3 ) ,
workflowRuns : ` { "total_count": 2, "workflow_runs":[ { "status":"in_progress"}, { "status":"completed"}]}" ` ,
workflowRuns_queued : ` { "total_count": 0, "workflow_runs":[]}" ` ,
workflowRuns_in_progress : ` { "total_count": 1, "workflow_runs":[ { "status":"in_progress"}]}" ` ,
want : 1 ,
2020-06-27 17:26:46 +09:00
} ,
2023-07-25 02:04:54 +02:00
// case_9
2020-06-27 17:26:46 +09:00
// fixed at 3
{
2021-02-16 18:55:55 +09:00
repo : "test/valid" ,
min : intPtr ( 1 ) ,
max : intPtr ( 3 ) ,
fixed : intPtr ( 3 ) ,
workflowRuns : ` { "total_count": 4, "workflow_runs":[ { "status":"in_progress"}, { "status":"in_progress"}, { "status":"in_progress"}, { "status":"completed"}]}" ` ,
workflowRuns_queued : ` { "total_count": 0, "workflow_runs":[]}" ` ,
workflowRuns_in_progress : ` { "total_count": 3, "workflow_runs":[ { "status":"in_progress"}, { "status":"in_progress"}, { "status":"in_progress"}]}" ` ,
2023-07-25 02:04:54 +02:00
want : 1 ,
} ,
// Case for empty GitHub Actions reponse - should not trigger scale up
{
description : "GitHub Actions Jobs Array is empty - no scale up" ,
repo : "test/valid" ,
min : intPtr ( 0 ) ,
max : intPtr ( 3 ) ,
workflowRuns : ` { "total_count": 2, "workflow_runs":[ { "status":"queued"}, { "status":"completed"}]}" ` ,
workflowRuns_queued : ` { "total_count": 1, "workflow_runs":[ { "status":"queued"}]}" ` ,
workflowRuns_in_progress : ` { "total_count": 0, "workflow_runs":[]}" ` ,
workflowJobs : map [ int ] string {
1 : ` { "jobs": []} ` ,
} ,
want : 0 ,
} ,
// Case for hosted GitHub Actions run
{
description : "Hosted GitHub Actions run - no scale up" ,
repo : "test/valid" ,
min : intPtr ( 0 ) ,
max : intPtr ( 3 ) ,
workflowRuns : ` { "total_count": 2, "workflow_runs":[ { "id": 1, "status":"queued"}, { "status":"completed"}]}" ` ,
workflowRuns_queued : ` { "total_count": 1, "workflow_runs":[ { "id": 1, "status":"queued"}]}" ` ,
workflowRuns_in_progress : ` { "total_count": 0, "workflow_runs":[]}" ` ,
workflowJobs : map [ int ] string {
1 : ` { "jobs": [ { "status":"queued"}]} ` ,
} ,
want : 0 ,
2020-06-27 17:26:46 +09:00
} ,
2020-10-07 17:00:44 -07:00
{
2022-04-28 00:24:21 +09:00
description : "Job-level autoscaling with no explicit runner label (runners have implicit self-hosted, requested self-hosted, 5 jobs from 3 workflows)" ,
repo : "test/valid" ,
min : intPtr ( 2 ) ,
max : intPtr ( 10 ) ,
workflowRuns : ` { "total_count": 4, "workflow_runs":[ { "id": 1, "status":"queued"}, { "id": 2, "status":"in_progress"}, { "id": 3, "status":"in_progress"}, { "status":"completed"}]}" ` ,
workflowRuns_queued : ` { "total_count": 1, "workflow_runs":[ { "id": 1, "status":"queued"}]}" ` ,
workflowRuns_in_progress : ` { "total_count": 2, "workflow_runs":[ { "id": 2, "status":"in_progress"}, { "id": 3, "status":"in_progress"}]}" ` ,
workflowJobs : map [ int ] string {
1 : ` { "jobs": [ { "status":"queued", "labels":["self-hosted"]}, { "status":"queued", "labels":["self-hosted"]}]} ` ,
2 : ` { "jobs": [ { "status": "in_progress", "labels":["self-hosted"]}, { "status":"completed", "labels":["self-hosted"]}]} ` ,
3 : ` { "jobs": [ { "status": "in_progress", "labels":["self-hosted"]}, { "status":"queued", "labels":["self-hosted"]}]} ` ,
} ,
want : 5 ,
} ,
{
description : "Skipped job-level autoscaling with no explicit runner label (runners have implicit self-hosted, requested self-hosted+custom, 0 jobs from 3 workflows)" ,
2022-04-24 14:41:34 +09:00
repo : "test/valid" ,
min : intPtr ( 2 ) ,
max : intPtr ( 10 ) ,
workflowRuns : ` { "total_count": 4, "workflow_runs":[ { "id": 1, "status":"queued"}, { "id": 2, "status":"in_progress"}, { "id": 3, "status":"in_progress"}, { "status":"completed"}]}" ` ,
workflowRuns_queued : ` { "total_count": 1, "workflow_runs":[ { "id": 1, "status":"queued"}]}" ` ,
workflowRuns_in_progress : ` { "total_count": 2, "workflow_runs":[ { "id": 2, "status":"in_progress"}, { "id": 3, "status":"in_progress"}]}" ` ,
workflowJobs : map [ int ] string {
1 : ` { "jobs": [ { "status":"queued", "labels":["self-hosted", "custom"]}, { "status":"queued", "labels":["self-hosted", "custom"]}]} ` ,
2 : ` { "jobs": [ { "status": "in_progress", "labels":["self-hosted", "custom"]}, { "status":"completed", "labels":["self-hosted", "custom"]}]} ` ,
3 : ` { "jobs": [ { "status": "in_progress", "labels":["self-hosted", "custom"]}, { "status":"queued", "labels":["self-hosted", "custom"]}]} ` ,
} ,
2022-04-28 00:24:21 +09:00
want : 2 ,
2022-04-24 14:41:34 +09:00
} ,
{
2022-04-28 00:24:21 +09:00
description : "Skipped job-level autoscaling with no label (runners have implicit self-hosted, jobs had no labels, 0 jobs from 3 workflows)" ,
2021-02-16 18:55:55 +09:00
repo : "test/valid" ,
min : intPtr ( 2 ) ,
max : intPtr ( 10 ) ,
workflowRuns : ` { "total_count": 4, "workflow_runs":[ { "id": 1, "status":"queued"}, { "id": 2, "status":"in_progress"}, { "id": 3, "status":"in_progress"}, { "status":"completed"}]}" ` ,
workflowRuns_queued : ` { "total_count": 1, "workflow_runs":[ { "id": 1, "status":"queued"}]}" ` ,
workflowRuns_in_progress : ` { "total_count": 2, "workflow_runs":[ { "id": 2, "status":"in_progress"}, { "id": 3, "status":"in_progress"}]}" ` ,
2020-10-07 17:00:44 -07:00
workflowJobs : map [ int ] string {
1 : ` { "jobs": [ { "status":"queued"}, { "status":"queued"}]} ` ,
2 : ` { "jobs": [ { "status": "in_progress"}, { "status":"completed"}]} ` ,
3 : ` { "jobs": [ { "status": "in_progress"}, { "status":"queued"}]} ` ,
} ,
2022-04-24 14:41:34 +09:00
want : 2 ,
} ,
{
2022-04-28 00:24:21 +09:00
description : "Skipped job-level autoscaling with default runner label (runners have self-hosted only, requested self-hosted+custom, 0 jobs from 3 workflows)" ,
2022-04-24 14:41:34 +09:00
repo : "test/valid" ,
labels : [ ] string { "self-hosted" } ,
min : intPtr ( 2 ) ,
max : intPtr ( 10 ) ,
workflowRuns : ` { "total_count": 4, "workflow_runs":[ { "id": 1, "status":"queued"}, { "id": 2, "status":"in_progress"}, { "id": 3, "status":"in_progress"}, { "status":"completed"}]}" ` ,
workflowRuns_queued : ` { "total_count": 1, "workflow_runs":[ { "id": 1, "status":"queued"}]}" ` ,
workflowRuns_in_progress : ` { "total_count": 2, "workflow_runs":[ { "id": 2, "status":"in_progress"}, { "id": 3, "status":"in_progress"}]}" ` ,
workflowJobs : map [ int ] string {
1 : ` { "jobs": [ { "status":"queued", "labels":["self-hosted", "custom"]}, { "status":"queued", "labels":["self-hosted", "custom"]}]} ` ,
2 : ` { "jobs": [ { "status": "in_progress", "labels":["self-hosted", "custom"]}, { "status":"completed", "labels":["self-hosted", "custom"]}]} ` ,
3 : ` { "jobs": [ { "status": "in_progress", "labels":["self-hosted", "custom"]}, { "status":"queued", "labels":["self-hosted", "custom"]}]} ` ,
} ,
2022-04-28 00:24:21 +09:00
want : 2 ,
2022-04-24 14:41:34 +09:00
} ,
{
2022-04-28 00:24:21 +09:00
description : "Skipped job-level autoscaling with custom runner label (runners have custom2, requested self-hosted+custom, 0 jobs from 5 workflows" ,
2022-04-24 14:41:34 +09:00
repo : "test/valid" ,
labels : [ ] string { "custom2" } ,
min : intPtr ( 2 ) ,
max : intPtr ( 10 ) ,
workflowRuns : ` { "total_count": 4, "workflow_runs":[ { "id": 1, "status":"queued"}, { "id": 2, "status":"in_progress"}, { "id": 3, "status":"in_progress"}, { "status":"completed"}]}" ` ,
workflowRuns_queued : ` { "total_count": 1, "workflow_runs":[ { "id": 1, "status":"queued"}]}" ` ,
workflowRuns_in_progress : ` { "total_count": 2, "workflow_runs":[ { "id": 2, "status":"in_progress"}, { "id": 3, "status":"in_progress"}]}" ` ,
workflowJobs : map [ int ] string {
1 : ` { "jobs": [ { "status":"queued", "labels":["self-hosted", "custom"]}, { "status":"queued", "labels":["self-hosted", "custom"]}]} ` ,
2 : ` { "jobs": [ { "status": "in_progress", "labels":["self-hosted", "custom"]}, { "status":"completed", "labels":["self-hosted", "custom"]}]} ` ,
3 : ` { "jobs": [ { "status": "in_progress", "labels":["self-hosted", "custom"]}, { "status":"queued", "labels":["self-hosted", "custom"]}]} ` ,
} ,
want : 2 ,
} ,
{
2022-04-28 00:24:21 +09:00
description : "Skipped job-level autoscaling with default runner label (runners have self-hosted, requested managed-runner-label, 0 jobs from 3 runs)" ,
2022-04-24 14:41:34 +09:00
repo : "test/valid" ,
labels : [ ] string { "self-hosted" } ,
min : intPtr ( 2 ) ,
max : intPtr ( 10 ) ,
workflowRuns : ` { "total_count": 4, "workflow_runs":[ { "id": 1, "status":"queued"}, { "id": 2, "status":"in_progress"}, { "id": 3, "status":"in_progress"}, { "status":"completed"}]}" ` ,
workflowRuns_queued : ` { "total_count": 1, "workflow_runs":[ { "id": 1, "status":"queued"}]}" ` ,
workflowRuns_in_progress : ` { "total_count": 2, "workflow_runs":[ { "id": 2, "status":"in_progress"}, { "id": 3, "status":"in_progress"}]}" ` ,
workflowJobs : map [ int ] string {
1 : ` { "jobs": [ { "status":"queued", "labels":["managed-runner-label"]}, { "status":"queued", "labels":["managed-runner-label"]}]} ` ,
2 : ` { "jobs": [ { "status": "in_progress", "labels":["managed-runner-label"]}, { "status":"completed", "labels":["managed-runner-label"]}]} ` ,
3 : ` { "jobs": [ { "status": "in_progress", "labels":["managed-runner-label"]}, { "status":"queued", "labels":["managed-runner-label"]}]} ` ,
} ,
want : 2 ,
} ,
{
2022-04-28 00:24:21 +09:00
description : "Job-level autoscaling with default + custom runner label (runners have self-hosted+custom, requested self-hosted+custom, 5 jobs from 3 workflows)" ,
2022-04-24 14:41:34 +09:00
repo : "test/valid" ,
labels : [ ] string { "self-hosted" , "custom" } ,
min : intPtr ( 2 ) ,
max : intPtr ( 10 ) ,
workflowRuns : ` { "total_count": 4, "workflow_runs":[ { "id": 1, "status":"queued"}, { "id": 2, "status":"in_progress"}, { "id": 3, "status":"in_progress"}, { "status":"completed"}]}" ` ,
workflowRuns_queued : ` { "total_count": 1, "workflow_runs":[ { "id": 1, "status":"queued"}]}" ` ,
workflowRuns_in_progress : ` { "total_count": 2, "workflow_runs":[ { "id": 2, "status":"in_progress"}, { "id": 3, "status":"in_progress"}]}" ` ,
workflowJobs : map [ int ] string {
1 : ` { "jobs": [ { "status":"queued", "labels":["self-hosted", "custom"]}, { "status":"queued", "labels":["self-hosted", "custom"]}]} ` ,
2 : ` { "jobs": [ { "status": "in_progress", "labels":["self-hosted", "custom"]}, { "status":"completed", "labels":["self-hosted", "custom"]}]} ` ,
3 : ` { "jobs": [ { "status": "in_progress", "labels":["self-hosted", "custom"]}, { "status":"queued", "labels":["self-hosted", "custom"]}]} ` ,
} ,
want : 5 ,
} ,
{
2022-04-28 00:24:21 +09:00
description : "Job-level autoscaling with custom runner label (runners have custom, requested self-hosted+custom, 5 jobs from 3 workflows)" ,
2022-04-24 14:41:34 +09:00
repo : "test/valid" ,
labels : [ ] string { "custom" } ,
min : intPtr ( 2 ) ,
max : intPtr ( 10 ) ,
workflowRuns : ` { "total_count": 4, "workflow_runs":[ { "id": 1, "status":"queued"}, { "id": 2, "status":"in_progress"}, { "id": 3, "status":"in_progress"}, { "status":"completed"}]}" ` ,
workflowRuns_queued : ` { "total_count": 1, "workflow_runs":[ { "id": 1, "status":"queued"}]}" ` ,
workflowRuns_in_progress : ` { "total_count": 2, "workflow_runs":[ { "id": 2, "status":"in_progress"}, { "id": 3, "status":"in_progress"}]}" ` ,
workflowJobs : map [ int ] string {
1 : ` { "jobs": [ { "status":"queued", "labels":["self-hosted", "custom"]}, { "status":"queued", "labels":["self-hosted", "custom"]}]} ` ,
2 : ` { "jobs": [ { "status": "in_progress", "labels":["self-hosted", "custom"]}, { "status":"completed", "labels":["self-hosted", "custom"]}]} ` ,
3 : ` { "jobs": [ { "status": "in_progress", "labels":["self-hosted", "custom"]}, { "status":"queued", "labels":["self-hosted", "custom"]}]} ` ,
} ,
2020-10-07 17:00:44 -07:00
want : 5 ,
} ,
2020-07-03 09:05:46 +09:00
}
for i := range testcases {
tc := testcases [ i ]
log := zap . New ( func ( o * zap . Options ) {
o . Development = true
} )
scheme := runtime . NewScheme ( )
_ = clientgoscheme . AddToScheme ( scheme )
_ = v1alpha1 . AddToScheme ( scheme )
2022-04-24 14:41:34 +09:00
testName := fmt . Sprintf ( "case %d" , i )
if tc . description != "" {
testName = tc . description
}
t . Run ( testName , func ( t * testing . T ) {
2021-02-07 17:37:27 +09:00
server := fake . NewServer (
2021-02-16 18:55:55 +09:00
fake . WithListRepositoryWorkflowRunsResponse ( 200 , tc . workflowRuns , tc . workflowRuns_queued , tc . workflowRuns_in_progress ) ,
2021-02-07 17:37:27 +09:00
fake . WithListWorkflowJobsResponse ( 200 , tc . workflowJobs ) ,
fake . WithListRunnersResponse ( 200 , fake . RunnersListBody ) ,
)
2020-07-03 09:05:46 +09:00
defer server . Close ( )
client := newGithubClient ( server )
2020-07-19 18:42:06 +09:00
h := & HorizontalRunnerAutoscalerReconciler {
2022-04-20 03:09:09 +01:00
Log : log ,
Scheme : scheme ,
DefaultScaleDownDelay : DefaultScaleDownDelay ,
2020-07-03 09:05:46 +09:00
}
rd := v1alpha1 . RunnerDeployment {
TypeMeta : metav1 . TypeMeta { } ,
2020-07-19 18:42:06 +09:00
ObjectMeta : metav1 . ObjectMeta {
Name : "testrd" ,
} ,
2020-07-03 09:05:46 +09:00
Spec : v1alpha1 . RunnerDeploymentSpec {
Template : v1alpha1 . RunnerTemplate {
Spec : v1alpha1 . RunnerSpec {
2021-06-22 17:10:09 +09:00
RunnerConfig : v1alpha1 . RunnerConfig {
Repository : tc . repo ,
2022-04-24 14:41:34 +09:00
Labels : tc . labels ,
2021-06-22 17:10:09 +09:00
} ,
2020-07-03 09:05:46 +09:00
} ,
} ,
2020-07-19 18:42:06 +09:00
Replicas : tc . fixed ,
} ,
Status : v1alpha1 . RunnerDeploymentStatus {
2021-05-22 08:29:53 +09:00
DesiredReplicas : tc . sReplicas ,
2020-07-19 18:42:06 +09:00
} ,
}
hra := v1alpha1 . HorizontalRunnerAutoscaler {
Spec : v1alpha1 . HorizontalRunnerAutoscalerSpec {
2020-07-03 09:05:46 +09:00
MaxReplicas : tc . max ,
MinReplicas : tc . min ,
2022-04-24 13:36:42 +09:00
Metrics : [ ] v1alpha1 . MetricSpec {
{
Type : "TotalNumberOfQueuedAndInProgressWorkflowRuns" ,
} ,
} ,
2020-07-03 09:05:46 +09:00
} ,
2020-07-19 18:42:06 +09:00
Status : v1alpha1 . HorizontalRunnerAutoscalerStatus {
DesiredReplicas : tc . sReplicas ,
2020-07-03 09:05:46 +09:00
LastSuccessfulScaleOutTime : tc . sTime ,
} ,
}
2021-05-22 08:29:53 +09:00
minReplicas , _ , _ , err := h . getMinReplicas ( log , metav1Now . Time , hra )
if err != nil {
t . Fatalf ( "unexpected error: %v" , err )
}
2021-06-23 20:25:03 +09:00
st := h . scaleTargetFromRD ( context . Background ( ) , rd )
2022-07-12 09:45:00 +09:00
got , err := h . computeReplicasWithCache ( client , log , metav1Now . Time , st , hra , minReplicas )
2020-07-03 09:05:46 +09:00
if err != nil {
if tc . err == "" {
t . Fatalf ( "unexpected error: expected none, got %v" , err )
} else if err . Error ( ) != tc . err {
t . Fatalf ( "unexpected error: expected %v, got %v" , tc . err , err )
}
return
}
Do not delay min/maxReplicas propagation from HRA to RD due to caching (#406)
As part of #282, I have introduced some caching mechanism to avoid excessive GitHub API calls due to the autoscaling calculation involving GitHub API calls is executed on each Webhook event.
Apparently, it was saving the wrong value in the cache- The value was one after applying `HRA.Spec.{Max,Min}Replicas` so manual changes to {Max,Min}Replicas doesn't affect RunnerDeployment.Spec.Replicas until the cache expires. This isn't what I had wanted.
This patch fixes that, by changing the value being cached to one before applying {Min,Max}Replicas.
Additionally, I've also updated logging so that you observe which number was fetched from cache, and what number was suggested by either TotalNumberOfQueuedAndInProgressWorkflowRuns or PercentageRunnersBusy, and what was the final number used as the desired-replicas(after applying {Min,Max}Replicas).
Follow-up for #282
2021-03-19 12:58:02 +09:00
if got != tc . want {
t . Errorf ( "%d: incorrect desired replicas: want %d, got %d" , i , tc . want , got )
2020-07-03 09:05:46 +09:00
}
} )
}
}
func TestDetermineDesiredReplicas_OrganizationalRunner ( t * testing . T ) {
intPtr := func ( v int ) * int {
return & v
}
metav1Now := metav1 . Now ( )
testcases := [ ] struct {
2022-04-24 14:41:34 +09:00
description string
repos [ ] string
org string
labels [ ] string
2021-02-16 18:55:55 +09:00
fixed * int
max * int
min * int
sReplicas * int
sTime * metav1 . Time
workflowRuns string
workflowRuns_queued string
workflowRuns_in_progress string
2020-10-07 17:00:44 -07:00
workflowJobs map [ int ] string
2020-07-03 09:05:46 +09:00
want int
err string
} {
2023-07-25 02:04:54 +02:00
// case_0
// 0 demanded due to zero runID, min at 2
2020-07-03 09:05:46 +09:00
{
2021-02-16 18:55:55 +09:00
org : "test" ,
repos : [ ] string { "valid" } ,
min : intPtr ( 2 ) ,
max : intPtr ( 3 ) ,
workflowRuns : ` { "total_count": 4, "workflow_runs":[ { "status":"queued"}, { "status":"in_progress"}, { "status":"in_progress"}, { "status":"completed"}]}" ` ,
workflowRuns_queued : ` { "total_count": 1, "workflow_runs":[ { "status":"queued"}]}" ` ,
workflowRuns_in_progress : ` { "total_count": 2, "workflow_runs":[ { "status":"in_progress"}, { "status":"in_progress"}]}" ` ,
2023-07-25 02:04:54 +02:00
want : 2 ,
2020-07-03 09:05:46 +09:00
} ,
2023-07-25 02:04:54 +02:00
// case_1
2020-07-03 09:05:46 +09:00
// 2 demanded, max at 3, currently 3, delay scaling down due to grace period
{
2021-02-16 18:55:55 +09:00
org : "test" ,
repos : [ ] string { "valid" } ,
min : intPtr ( 2 ) ,
max : intPtr ( 3 ) ,
sReplicas : intPtr ( 3 ) ,
sTime : & metav1Now ,
workflowRuns : ` { "total_count": 4, "workflow_runs":[ { "status":"queued"}, { "status":"in_progress"}, { "status":"completed"}]}" ` ,
workflowRuns_queued : ` { "total_count": 1, "workflow_runs":[ { "status":"queued"}]}" ` ,
workflowRuns_in_progress : ` { "total_count": 1, "workflow_runs":[ { "status":"in_progress"}]}" ` ,
want : 3 ,
2020-07-03 09:05:46 +09:00
} ,
2023-07-25 02:04:54 +02:00
// case_2
2020-07-03 09:05:46 +09:00
// 3 demanded, max at 2
{
2021-02-16 18:55:55 +09:00
org : "test" ,
repos : [ ] string { "valid" } ,
min : intPtr ( 2 ) ,
max : intPtr ( 2 ) ,
workflowRuns : ` { "total_count": 4, "workflow_runs":[ { "status":"queued"}, { "status":"in_progress"}, { "status":"in_progress"}, { "status":"completed"}]}" ` ,
workflowRuns_queued : ` { "total_count": 1, "workflow_runs":[ { "status":"queued"}]}" ` ,
workflowRuns_in_progress : ` { "total_count": 2, "workflow_runs":[ { "status":"in_progress"}, { "status":"in_progress"}]}" ` ,
want : 2 ,
2020-07-03 09:05:46 +09:00
} ,
2023-07-25 02:04:54 +02:00
// case_3
2020-07-03 09:05:46 +09:00
// 2 demanded, min at 2
{
2021-02-16 18:55:55 +09:00
org : "test" ,
repos : [ ] string { "valid" } ,
min : intPtr ( 2 ) ,
max : intPtr ( 3 ) ,
workflowRuns : ` { "total_count": 3, "workflow_runs":[ { "status":"queued"}, { "status":"in_progress"}, { "status":"completed"}]}" ` ,
workflowRuns_queued : ` { "total_count": 1, "workflow_runs":[ { "status":"queued"}]}" ` ,
workflowRuns_in_progress : ` { "total_count": 1, "workflow_runs":[ { "status":"in_progress"}]}" ` ,
want : 2 ,
2020-07-03 09:05:46 +09:00
} ,
2023-07-25 02:04:54 +02:00
// case_4
2020-07-03 09:05:46 +09:00
// 1 demanded, min at 2
{
2021-02-16 18:55:55 +09:00
org : "test" ,
repos : [ ] string { "valid" } ,
min : intPtr ( 2 ) ,
max : intPtr ( 3 ) ,
workflowRuns : ` { "total_count": 2, "workflow_runs":[ { "status":"queued"}, { "status":"completed"}]}" ` ,
workflowRuns_queued : ` { "total_count": 1, "workflow_runs":[ { "status":"queued"}]}" ` ,
workflowRuns_in_progress : ` { "total_count": 0, "workflow_runs":[]}" ` ,
want : 2 ,
2020-07-03 09:05:46 +09:00
} ,
2023-07-25 02:04:54 +02:00
// case_5
2020-07-03 09:05:46 +09:00
// 1 demanded, min at 2
{
2021-02-16 18:55:55 +09:00
org : "test" ,
repos : [ ] string { "valid" } ,
min : intPtr ( 2 ) ,
max : intPtr ( 3 ) ,
workflowRuns : ` { "total_count": 2, "workflow_runs":[ { "status":"in_progress"}, { "status":"completed"}]}" ` ,
workflowRuns_queued : ` { "total_count": 0, "workflow_runs":[]}" ` ,
workflowRuns_in_progress : ` { "total_count": 1, "workflow_runs":[ { "status":"in_progress"}]}" ` ,
want : 2 ,
2020-07-03 09:05:46 +09:00
} ,
// 1 demanded, min at 1
{
2021-02-16 18:55:55 +09:00
org : "test" ,
repos : [ ] string { "valid" } ,
min : intPtr ( 1 ) ,
max : intPtr ( 3 ) ,
workflowRuns : ` { "total_count": 2, "workflow_runs":[ { "status":"queued"}, { "status":"completed"}]}" ` ,
workflowRuns_queued : ` { "total_count": 1, "workflow_runs":[ { "status":"queued"}]}" ` ,
workflowRuns_in_progress : ` { "total_count": 0, "workflow_runs":[]}" ` ,
want : 1 ,
2020-07-03 09:05:46 +09:00
} ,
// 1 demanded, min at 1
{
2021-02-16 18:55:55 +09:00
org : "test" ,
repos : [ ] string { "valid" } ,
min : intPtr ( 1 ) ,
max : intPtr ( 3 ) ,
workflowRuns : ` { "total_count": 2, "workflow_runs":[ { "status":"in_progress"}, { "status":"completed"}]}" ` ,
workflowRuns_queued : ` { "total_count": 0, "workflow_runs":[]}" ` ,
workflowRuns_in_progress : ` { "total_count": 1, "workflow_runs":[ { "status":"in_progress"}]}" ` ,
want : 1 ,
2020-07-03 09:05:46 +09:00
} ,
2023-07-25 02:04:54 +02:00
// case_6
2020-07-03 09:05:46 +09:00
// fixed at 3
{
2021-02-16 18:55:55 +09:00
org : "test" ,
repos : [ ] string { "valid" } ,
fixed : intPtr ( 1 ) ,
min : intPtr ( 1 ) ,
max : intPtr ( 3 ) ,
workflowRuns : ` { "total_count": 4, "workflow_runs":[ { "status":"in_progress"}, { "status":"in_progress"}, { "status":"in_progress"}, { "status":"completed"}]}" ` ,
workflowRuns_queued : ` { "total_count": 0, "workflow_runs":[]}" ` ,
workflowRuns_in_progress : ` { "total_count": 3, "workflow_runs":[ { "status":"in_progress"}, { "status":"in_progress"}, { "status":"in_progress"}]}" ` ,
2023-07-25 02:04:54 +02:00
want : 1 ,
2020-07-03 09:05:46 +09:00
} ,
2023-07-25 02:04:54 +02:00
// case_7
2020-06-27 17:26:46 +09:00
// org runner, fixed at 3
{
2021-02-16 18:55:55 +09:00
org : "test" ,
repos : [ ] string { "valid" } ,
fixed : intPtr ( 1 ) ,
min : intPtr ( 1 ) ,
max : intPtr ( 3 ) ,
workflowRuns : ` { "total_count": 4, "workflow_runs":[ { "status":"in_progress"}, { "status":"in_progress"}, { "status":"in_progress"}, { "status":"completed"}]}" ` ,
workflowRuns_queued : ` { "total_count": 0, "workflow_runs":[]}" ` ,
workflowRuns_in_progress : ` { "total_count": 3, "workflow_runs":[ { "status":"in_progress"}, { "status":"in_progress"}, { "status":"in_progress"}]}" ` ,
2023-07-25 02:04:54 +02:00
want : 1 ,
2020-06-27 17:26:46 +09:00
} ,
2023-07-25 02:04:54 +02:00
// case_8
2020-07-03 09:05:46 +09:00
// org runner, 1 demanded, min at 1, no repos
2020-06-27 17:26:46 +09:00
{
2021-02-16 18:55:55 +09:00
org : "test" ,
min : intPtr ( 1 ) ,
max : intPtr ( 3 ) ,
workflowRuns : ` { "total_count": 2, "workflow_runs":[ { "status":"in_progress"}, { "status":"completed"}]}" ` ,
workflowRuns_queued : ` { "total_count": 0, "workflow_runs":[]}" ` ,
workflowRuns_in_progress : ` { "total_count": 1, "workflow_runs":[ { "status":"in_progress"}]}" ` ,
err : "validating autoscaling metrics: spec.autoscaling.metrics[].repositoryNames is required and must have one more more entries for organizational runner deployment" ,
2020-06-27 17:26:46 +09:00
} ,
2020-10-07 17:00:44 -07:00
{
2022-04-28 00:24:21 +09:00
description : "Job-level autoscaling (runners have implicit self-hosted, requested self-hosted, 5 jobs from 3 runs)" ,
org : "test" ,
repos : [ ] string { "valid" } ,
min : intPtr ( 2 ) ,
max : intPtr ( 10 ) ,
workflowRuns : ` { "total_count": 4, "workflow_runs":[ { "id": 1, "status":"queued"}, { "id": 2, "status":"in_progress"}, { "id": 3, "status":"in_progress"}, { "status":"completed"}]}" ` ,
workflowRuns_queued : ` { "total_count": 1, "workflow_runs":[ { "id": 1, "status":"queued"}]}" ` ,
workflowRuns_in_progress : ` { "total_count": 2, "workflow_runs":[ { "id": 2, "status":"in_progress"}, { "id": 3, "status":"in_progress"}, { "status":"completed"}]}" ` ,
workflowJobs : map [ int ] string {
1 : ` { "jobs": [ { "status":"queued", "labels":["self-hosted"]}, { "status":"queued", "labels":["self-hosted"]}]} ` ,
2 : ` { "jobs": [ { "status": "in_progress", "labels":["self-hosted"]}, { "status":"completed", "labels":["self-hosted"]}]} ` ,
3 : ` { "jobs": [ { "status": "in_progress", "labels":["self-hosted"]}, { "status":"queued", "labels":["self-hosted"]}]} ` ,
} ,
want : 5 ,
} ,
{
description : "Job-level autoscaling (runners have explicit self-hosted, requested self-hosted, 5 jobs from 3 runs)" ,
org : "test" ,
repos : [ ] string { "valid" } ,
labels : [ ] string { "self-hosted" } ,
min : intPtr ( 2 ) ,
max : intPtr ( 10 ) ,
workflowRuns : ` { "total_count": 4, "workflow_runs":[ { "id": 1, "status":"queued"}, { "id": 2, "status":"in_progress"}, { "id": 3, "status":"in_progress"}, { "status":"completed"}]}" ` ,
workflowRuns_queued : ` { "total_count": 1, "workflow_runs":[ { "id": 1, "status":"queued"}]}" ` ,
workflowRuns_in_progress : ` { "total_count": 2, "workflow_runs":[ { "id": 2, "status":"in_progress"}, { "id": 3, "status":"in_progress"}, { "status":"completed"}]}" ` ,
workflowJobs : map [ int ] string {
1 : ` { "jobs": [ { "status":"queued", "labels":["self-hosted"]}, { "status":"queued", "labels":["self-hosted"]}]} ` ,
2 : ` { "jobs": [ { "status": "in_progress", "labels":["self-hosted"]}, { "status":"completed", "labels":["self-hosted"]}]} ` ,
3 : ` { "jobs": [ { "status": "in_progress", "labels":["self-hosted"]}, { "status":"queued", "labels":["self-hosted"]}]} ` ,
} ,
want : 5 ,
} ,
{
description : "Skipped job-level autoscaling (jobs lack labels, 0 requested from 3 workflows)" ,
2021-02-16 18:55:55 +09:00
org : "test" ,
repos : [ ] string { "valid" } ,
min : intPtr ( 2 ) ,
max : intPtr ( 10 ) ,
workflowRuns : ` { "total_count": 4, "workflow_runs":[ { "id": 1, "status":"queued"}, { "id": 2, "status":"in_progress"}, { "id": 3, "status":"in_progress"}, { "status":"completed"}]}" ` ,
workflowRuns_queued : ` { "total_count": 1, "workflow_runs":[ { "id": 1, "status":"queued"}]}" ` ,
workflowRuns_in_progress : ` { "total_count": 2, "workflow_runs":[ { "id": 2, "status":"in_progress"}, { "id": 3, "status":"in_progress"}, { "status":"completed"}]}" ` ,
2020-10-07 17:00:44 -07:00
workflowJobs : map [ int ] string {
1 : ` { "jobs": [ { "status":"queued"}, { "status":"queued"}]} ` ,
2 : ` { "jobs": [ { "status": "in_progress"}, { "status":"completed"}]} ` ,
3 : ` { "jobs": [ { "status": "in_progress"}, { "status":"queued"}]} ` ,
} ,
2022-04-24 14:41:34 +09:00
want : 2 ,
} ,
{
2022-04-28 00:24:21 +09:00
description : "Skipped job-level autoscaling (runners have valid and implicit self-hosted, requested self-hosted+custom, 0 jobs from 3 runs)" ,
2022-04-24 14:41:34 +09:00
org : "test" ,
repos : [ ] string { "valid" } ,
min : intPtr ( 2 ) ,
max : intPtr ( 10 ) ,
workflowRuns : ` { "total_count": 4, "workflow_runs":[ { "id": 1, "status":"queued"}, { "id": 2, "status":"in_progress"}, { "id": 3, "status":"in_progress"}, { "status":"completed"}]}" ` ,
workflowRuns_queued : ` { "total_count": 1, "workflow_runs":[ { "id": 1, "status":"queued"}]}" ` ,
workflowRuns_in_progress : ` { "total_count": 2, "workflow_runs":[ { "id": 2, "status":"in_progress"}, { "id": 3, "status":"in_progress"}, { "status":"completed"}]}" ` ,
workflowJobs : map [ int ] string {
1 : ` { "jobs": [ { "status":"queued", "labels":["self-hosted", "custom"]}, { "status":"queued", "labels":["self-hosted", "custom"]}]} ` ,
2 : ` { "jobs": [ { "status": "in_progress", "labels":["self-hosted", "custom"]}, { "status":"completed", "labels":["self-hosted", "custom"]}]} ` ,
3 : ` { "jobs": [ { "status": "in_progress", "labels":["self-hosted", "custom"]}, { "status":"queued", "labels":["self-hosted", "custom"]}]} ` ,
} ,
2022-04-28 00:24:21 +09:00
want : 2 ,
2020-10-07 17:00:44 -07:00
} ,
2022-04-24 14:41:34 +09:00
{
2022-04-28 00:24:21 +09:00
description : "Skipped job-level autoscaling (runners have self-hosted, requested self-hosted+custom, 0 jobs from 3 workflows)" ,
2022-04-24 14:41:34 +09:00
org : "test" ,
repos : [ ] string { "valid" } ,
labels : [ ] string { "self-hosted" } ,
min : intPtr ( 2 ) ,
max : intPtr ( 10 ) ,
workflowRuns : ` { "total_count": 4, "workflow_runs":[ { "id": 1, "status":"queued"}, { "id": 2, "status":"in_progress"}, { "id": 3, "status":"in_progress"}, { "status":"completed"}]}" ` ,
workflowRuns_queued : ` { "total_count": 1, "workflow_runs":[ { "id": 1, "status":"queued"}]}" ` ,
workflowRuns_in_progress : ` { "total_count": 2, "workflow_runs":[ { "id": 2, "status":"in_progress"}, { "id": 3, "status":"in_progress"}, { "status":"completed"}]}" ` ,
workflowJobs : map [ int ] string {
1 : ` { "jobs": [ { "status":"queued", "labels":["self-hosted", "custom"]}, { "status":"queued", "labels":["self-hosted", "custom"]}]} ` ,
2 : ` { "jobs": [ { "status": "in_progress", "labels":["self-hosted", "custom"]}, { "status":"completed", "labels":["self-hosted", "custom"]}]} ` ,
3 : ` { "jobs": [ { "status": "in_progress", "labels":["self-hosted", "custom"]}, { "status":"queued", "labels":["self-hosted", "custom"]}]} ` ,
} ,
2022-04-28 00:24:21 +09:00
want : 2 ,
2022-04-24 14:41:34 +09:00
} ,
{
2022-04-28 00:24:21 +09:00
description : "Job-level autoscaling (runners have custom, requested self-hosted+custom, 5 requested from 3 workflows)" ,
2022-04-24 14:41:34 +09:00
org : "test" ,
repos : [ ] string { "valid" } ,
labels : [ ] string { "custom" } ,
min : intPtr ( 2 ) ,
max : intPtr ( 10 ) ,
workflowRuns : ` { "total_count": 4, "workflow_runs":[ { "id": 1, "status":"queued"}, { "id": 2, "status":"in_progress"}, { "id": 3, "status":"in_progress"}, { "status":"completed"}]}" ` ,
workflowRuns_queued : ` { "total_count": 1, "workflow_runs":[ { "id": 1, "status":"queued"}]}" ` ,
workflowRuns_in_progress : ` { "total_count": 2, "workflow_runs":[ { "id": 2, "status":"in_progress"}, { "id": 3, "status":"in_progress"}, { "status":"completed"}]}" ` ,
workflowJobs : map [ int ] string {
1 : ` { "jobs": [ { "status":"queued", "labels":["self-hosted", "custom"]}, { "status":"queued", "labels":["self-hosted", "custom"]}]} ` ,
2 : ` { "jobs": [ { "status": "in_progress", "labels":["self-hosted", "custom"]}, { "status":"completed", "labels":["self-hosted", "custom"]}]} ` ,
3 : ` { "jobs": [ { "status": "in_progress", "labels":["self-hosted", "custom"]}, { "status":"queued", "labels":["self-hosted", "custom"]}]} ` ,
} ,
want : 5 ,
} ,
2022-04-28 00:24:21 +09:00
{
description : "Job-level autoscaling (runners have custom, requested custom, 5 requested from 3 workflows)" ,
org : "test" ,
repos : [ ] string { "valid" } ,
labels : [ ] string { "custom" } ,
min : intPtr ( 2 ) ,
max : intPtr ( 10 ) ,
workflowRuns : ` { "total_count": 4, "workflow_runs":[ { "id": 1, "status":"queued"}, { "id": 2, "status":"in_progress"}, { "id": 3, "status":"in_progress"}, { "status":"completed"}]}" ` ,
workflowRuns_queued : ` { "total_count": 1, "workflow_runs":[ { "id": 1, "status":"queued"}]}" ` ,
workflowRuns_in_progress : ` { "total_count": 2, "workflow_runs":[ { "id": 2, "status":"in_progress"}, { "id": 3, "status":"in_progress"}, { "status":"completed"}]}" ` ,
workflowJobs : map [ int ] string {
1 : ` { "jobs": [ { "status":"queued", "labels":["custom"]}, { "status":"queued", "labels":["custom"]}]} ` ,
2 : ` { "jobs": [ { "status": "in_progress", "labels":["custom"]}, { "status":"completed", "labels":["custom"]}]} ` ,
3 : ` { "jobs": [ { "status": "in_progress", "labels":["custom"]}, { "status":"queued", "labels":["custom"]}]} ` ,
} ,
want : 5 ,
} ,
2022-04-24 14:41:34 +09:00
{
description : "Skipped job-level autoscaling (specified custom2, 0 requested from 3 workflows)" ,
org : "test" ,
repos : [ ] string { "valid" } ,
labels : [ ] string { "custom2" } ,
min : intPtr ( 2 ) ,
max : intPtr ( 10 ) ,
workflowRuns : ` { "total_count": 4, "workflow_runs":[ { "id": 1, "status":"queued"}, { "id": 2, "status":"in_progress"}, { "id": 3, "status":"in_progress"}, { "status":"completed"}]}" ` ,
workflowRuns_queued : ` { "total_count": 1, "workflow_runs":[ { "id": 1, "status":"queued"}]}" ` ,
workflowRuns_in_progress : ` { "total_count": 2, "workflow_runs":[ { "id": 2, "status":"in_progress"}, { "id": 3, "status":"in_progress"}, { "status":"completed"}]}" ` ,
workflowJobs : map [ int ] string {
1 : ` { "jobs": [ { "status":"queued", "labels":["self-hosted", "custom"]}, { "status":"queued", "labels":["self-hosted", "custom"]}]} ` ,
2 : ` { "jobs": [ { "status": "in_progress", "labels":["self-hosted", "custom"]}, { "status":"completed", "labels":["self-hosted", "custom"]}]} ` ,
3 : ` { "jobs": [ { "status": "in_progress", "labels":["self-hosted", "custom"]}, { "status":"queued", "labels":["self-hosted", "custom"]}]} ` ,
} ,
want : 2 ,
} ,
2020-06-27 17:26:46 +09:00
}
for i := range testcases {
tc := testcases [ i ]
log := zap . New ( func ( o * zap . Options ) {
o . Development = true
} )
scheme := runtime . NewScheme ( )
_ = clientgoscheme . AddToScheme ( scheme )
_ = v1alpha1 . AddToScheme ( scheme )
2022-04-24 14:41:34 +09:00
testName := fmt . Sprintf ( "case %d" , i )
if tc . description != "" {
testName = tc . description
}
t . Run ( testName , func ( t * testing . T ) {
Do not delay min/maxReplicas propagation from HRA to RD due to caching (#406)
As part of #282, I have introduced some caching mechanism to avoid excessive GitHub API calls due to the autoscaling calculation involving GitHub API calls is executed on each Webhook event.
Apparently, it was saving the wrong value in the cache- The value was one after applying `HRA.Spec.{Max,Min}Replicas` so manual changes to {Max,Min}Replicas doesn't affect RunnerDeployment.Spec.Replicas until the cache expires. This isn't what I had wanted.
This patch fixes that, by changing the value being cached to one before applying {Min,Max}Replicas.
Additionally, I've also updated logging so that you observe which number was fetched from cache, and what number was suggested by either TotalNumberOfQueuedAndInProgressWorkflowRuns or PercentageRunnersBusy, and what was the final number used as the desired-replicas(after applying {Min,Max}Replicas).
Follow-up for #282
2021-03-19 12:58:02 +09:00
t . Helper ( )
2021-02-07 17:37:27 +09:00
server := fake . NewServer (
2021-02-16 18:55:55 +09:00
fake . WithListRepositoryWorkflowRunsResponse ( 200 , tc . workflowRuns , tc . workflowRuns_queued , tc . workflowRuns_in_progress ) ,
2021-02-07 17:37:27 +09:00
fake . WithListWorkflowJobsResponse ( 200 , tc . workflowJobs ) ,
fake . WithListRunnersResponse ( 200 , fake . RunnersListBody ) ,
)
2020-06-27 17:26:46 +09:00
defer server . Close ( )
client := newGithubClient ( server )
2020-07-19 18:42:06 +09:00
h := & HorizontalRunnerAutoscalerReconciler {
2022-04-20 03:09:09 +01:00
Log : log ,
Scheme : scheme ,
DefaultScaleDownDelay : DefaultScaleDownDelay ,
2020-06-27 17:26:46 +09:00
}
rd := v1alpha1 . RunnerDeployment {
2020-07-19 18:42:06 +09:00
ObjectMeta : metav1 . ObjectMeta {
Name : "testrd" ,
} ,
2020-06-27 17:26:46 +09:00
Spec : v1alpha1 . RunnerDeploymentSpec {
2021-03-05 10:15:39 +09:00
Selector : & metav1 . LabelSelector {
MatchLabels : map [ string ] string {
"foo" : "bar" ,
} ,
} ,
2020-06-27 17:26:46 +09:00
Template : v1alpha1 . RunnerTemplate {
2021-03-05 10:15:39 +09:00
ObjectMeta : metav1 . ObjectMeta {
Labels : map [ string ] string {
"foo" : "bar" ,
} ,
} ,
2020-06-27 17:26:46 +09:00
Spec : v1alpha1 . RunnerSpec {
2021-06-22 17:10:09 +09:00
RunnerConfig : v1alpha1 . RunnerConfig {
Organization : tc . org ,
2022-04-24 14:41:34 +09:00
Labels : tc . labels ,
2021-06-22 17:10:09 +09:00
} ,
2020-07-03 09:05:46 +09:00
} ,
} ,
2020-07-19 18:42:06 +09:00
Replicas : tc . fixed ,
} ,
Status : v1alpha1 . RunnerDeploymentStatus {
2021-05-22 08:29:53 +09:00
DesiredReplicas : tc . sReplicas ,
2020-07-19 18:42:06 +09:00
} ,
}
hra := v1alpha1 . HorizontalRunnerAutoscaler {
Spec : v1alpha1 . HorizontalRunnerAutoscalerSpec {
ScaleTargetRef : v1alpha1 . ScaleTargetRef {
Name : "testrd" ,
2020-06-27 17:26:46 +09:00
} ,
MaxReplicas : tc . max ,
MinReplicas : tc . min ,
2020-07-19 18:42:06 +09:00
Metrics : [ ] v1alpha1 . MetricSpec {
{
Type : v1alpha1 . AutoscalingMetricTypeTotalNumberOfQueuedAndInProgressWorkflowRuns ,
RepositoryNames : tc . repos ,
} ,
} ,
2020-06-27 17:26:46 +09:00
} ,
2020-07-19 18:42:06 +09:00
Status : v1alpha1 . HorizontalRunnerAutoscalerStatus {
DesiredReplicas : tc . sReplicas ,
2020-06-27 17:26:46 +09:00
LastSuccessfulScaleOutTime : tc . sTime ,
} ,
}
2021-05-22 08:29:53 +09:00
minReplicas , _ , _ , err := h . getMinReplicas ( log , metav1Now . Time , hra )
if err != nil {
t . Fatalf ( "unexpected error: %v" , err )
}
2021-06-23 20:25:03 +09:00
st := h . scaleTargetFromRD ( context . Background ( ) , rd )
2022-07-12 09:45:00 +09:00
got , err := h . computeReplicasWithCache ( client , log , metav1Now . Time , st , hra , minReplicas )
2020-06-27 17:26:46 +09:00
if err != nil {
if tc . err == "" {
t . Fatalf ( "unexpected error: expected none, got %v" , err )
} else if err . Error ( ) != tc . err {
t . Fatalf ( "unexpected error: expected %v, got %v" , tc . err , err )
}
return
}
Do not delay min/maxReplicas propagation from HRA to RD due to caching (#406)
As part of #282, I have introduced some caching mechanism to avoid excessive GitHub API calls due to the autoscaling calculation involving GitHub API calls is executed on each Webhook event.
Apparently, it was saving the wrong value in the cache- The value was one after applying `HRA.Spec.{Max,Min}Replicas` so manual changes to {Max,Min}Replicas doesn't affect RunnerDeployment.Spec.Replicas until the cache expires. This isn't what I had wanted.
This patch fixes that, by changing the value being cached to one before applying {Min,Max}Replicas.
Additionally, I've also updated logging so that you observe which number was fetched from cache, and what number was suggested by either TotalNumberOfQueuedAndInProgressWorkflowRuns or PercentageRunnersBusy, and what was the final number used as the desired-replicas(after applying {Min,Max}Replicas).
Follow-up for #282
2021-03-19 12:58:02 +09:00
if got != tc . want {
t . Errorf ( "%d: incorrect desired replicas: want %d, got %d" , i , tc . want , got )
2020-06-27 17:26:46 +09:00
}
} )
}
}