2020-06-27 17:26:46 +09:00
package controllers
import (
"context"
2020-07-03 09:05:46 +09:00
"errors"
2020-06-27 17:26:46 +09:00
"fmt"
"strings"
2020-10-07 17:00:44 -07:00
"github.com/summerwind/actions-runner-controller/api/v1alpha1"
2020-06-27 17:26:46 +09:00
)
2020-07-19 18:42:06 +09:00
func ( r * HorizontalRunnerAutoscalerReconciler ) determineDesiredReplicas ( rd v1alpha1 . RunnerDeployment , hra v1alpha1 . HorizontalRunnerAutoscaler ) ( * int , error ) {
if hra . Spec . MinReplicas == nil {
return nil , fmt . Errorf ( "horizontalrunnerautoscaler %s/%s is missing minReplicas" , hra . Namespace , hra . Name )
} else if hra . Spec . MaxReplicas == nil {
return nil , fmt . Errorf ( "horizontalrunnerautoscaler %s/%s is missing maxReplicas" , hra . Namespace , hra . Name )
2020-06-27 17:26:46 +09:00
}
2020-07-03 09:05:46 +09:00
var repos [ ] [ ] string
2020-06-27 17:26:46 +09:00
repoID := rd . Spec . Template . Spec . Repository
if repoID == "" {
2020-07-03 09:05:46 +09:00
orgName := rd . Spec . Template . Spec . Organization
if orgName == "" {
return nil , fmt . Errorf ( "asserting runner deployment spec to detect bug: spec.template.organization should not be empty on this code path" )
}
2020-07-19 18:42:06 +09:00
metrics := hra . Spec . Metrics
2020-06-27 17:26:46 +09:00
2020-07-03 09:05:46 +09:00
if len ( metrics ) == 0 {
return nil , fmt . Errorf ( "validating autoscaling metrics: one or more metrics is required" )
2020-07-19 18:42:06 +09:00
} else if tpe := metrics [ 0 ] . Type ; tpe != v1alpha1 . AutoscalingMetricTypeTotalNumberOfQueuedAndInProgressWorkflowRuns {
return nil , fmt . Errorf ( "validting autoscaling metrics: unsupported metric type %q: only supported value is %s" , tpe , v1alpha1 . AutoscalingMetricTypeTotalNumberOfQueuedAndInProgressWorkflowRuns )
2020-07-03 09:05:46 +09:00
} else if len ( metrics [ 0 ] . RepositoryNames ) == 0 {
return nil , errors . New ( "validating autoscaling metrics: spec.autoscaling.metrics[].repositoryNames is required and must have one more more entries for organizational runner deployment" )
}
for _ , repoName := range metrics [ 0 ] . RepositoryNames {
repos = append ( repos , [ ] string { orgName , repoName } )
}
} else {
repo := strings . Split ( repoID , "/" )
repos = append ( repos , repo )
2020-06-27 17:26:46 +09:00
}
var total , inProgress , queued , completed , unknown int
2020-10-07 17:00:44 -07:00
type callback func ( )
listWorkflowJobs := func ( user string , repoName string , runID int64 , fallback_cb callback ) {
if runID == 0 {
fallback_cb ( )
return
}
jobs , _ , err := r . GitHubClient . Actions . ListWorkflowJobs ( context . TODO ( ) , user , repoName , runID , nil )
if err != nil {
r . Log . Error ( err , "Error listing workflow jobs" )
fallback_cb ( )
} else if len ( jobs . Jobs ) == 0 {
fallback_cb ( )
} else {
for _ , job := range jobs . Jobs {
switch job . GetStatus ( ) {
case "completed" :
// We add a case for `completed` so it is not counted in `unknown`.
// And we do not increment the counter for completed because
// that counter only refers to workflows. The reason for
// this is because we do not get a list of jobs for
// completed workflows in order to keep the number of API
// calls to a minimum.
case "in_progress" :
inProgress ++
case "queued" :
queued ++
default :
unknown ++
}
}
}
}
2020-06-27 17:26:46 +09:00
2020-07-03 09:05:46 +09:00
for _ , repo := range repos {
user , repoName := repo [ 0 ] , repo [ 1 ]
list , _ , err := r . GitHubClient . Actions . ListRepositoryWorkflowRuns ( context . TODO ( ) , user , repoName , nil )
if err != nil {
return nil , err
}
2020-10-07 17:00:44 -07:00
for _ , run := range list . WorkflowRuns {
2020-07-03 09:05:46 +09:00
total ++
// In May 2020, there are only 3 statuses.
// Follow the below links for more details:
// - https://developer.github.com/v3/actions/workflow-runs/#list-repository-workflow-runs
// - https://developer.github.com/v3/checks/runs/#create-a-check-run
2020-10-07 17:00:44 -07:00
switch run . GetStatus ( ) {
2020-07-03 09:05:46 +09:00
case "completed" :
completed ++
case "in_progress" :
2020-10-07 17:00:44 -07:00
listWorkflowJobs ( user , repoName , run . GetID ( ) , func ( ) { inProgress ++ } )
2020-07-03 09:05:46 +09:00
case "queued" :
2020-10-07 17:00:44 -07:00
listWorkflowJobs ( user , repoName , run . GetID ( ) , func ( ) { queued ++ } )
2020-07-03 09:05:46 +09:00
default :
unknown ++
}
2020-06-27 17:26:46 +09:00
}
}
2020-07-19 18:42:06 +09:00
minReplicas := * hra . Spec . MinReplicas
maxReplicas := * hra . Spec . MaxReplicas
2020-06-27 17:26:46 +09:00
necessaryReplicas := queued + inProgress
var desiredReplicas int
if necessaryReplicas < minReplicas {
desiredReplicas = minReplicas
} else if necessaryReplicas > maxReplicas {
desiredReplicas = maxReplicas
} else {
desiredReplicas = necessaryReplicas
}
rd . Status . Replicas = & desiredReplicas
2020-07-03 09:05:46 +09:00
replicas := desiredReplicas
2020-06-27 17:26:46 +09:00
r . Log . V ( 1 ) . Info (
"Calculated desired replicas" ,
"computed_replicas_desired" , desiredReplicas ,
"spec_replicas_min" , minReplicas ,
"spec_replicas_max" , maxReplicas ,
"workflow_runs_completed" , completed ,
"workflow_runs_in_progress" , inProgress ,
"workflow_runs_queued" , queued ,
"workflow_runs_unknown" , unknown ,
)
return & replicas , nil
}