Skip to content

Commit

Permalink
Merge pull request #274 from gabriel-samfira/make-durations-configura…
Browse files Browse the repository at this point in the history
…tble

Allow configuration of job backoff interval
  • Loading branch information
gabriel-samfira authored Jul 2, 2024
2 parents 8f0d447 + 892a62b commit dcee092
Show file tree
Hide file tree
Showing 8 changed files with 90 additions and 31 deletions.
10 changes: 8 additions & 2 deletions cmd/garm-cli/cmd/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,9 +113,13 @@ up the GARM controller URLs as:
params.WebhookURL = &webhookURL
}

if params.WebhookURL == nil && params.MetadataURL == nil && params.CallbackURL == nil {
if cmd.Flags().Changed("minimum-job-age-backoff") {
params.MinimumJobAgeBackoff = &minimumJobAgeBackoff
}

if params.WebhookURL == nil && params.MetadataURL == nil && params.CallbackURL == nil && params.MinimumJobAgeBackoff == nil {
cmd.Help()
return fmt.Errorf("at least one of metadata-url, callback-url or webhook-url must be provided")
return fmt.Errorf("at least one of minimum-job-age-backoff, metadata-url, callback-url or webhook-url must be provided")
}

updateUrlsReq := apiClientController.NewUpdateControllerParams()
Expand Down Expand Up @@ -151,6 +155,7 @@ func renderControllerInfoTable(info params.ControllerInfo) string {
t.AppendRow(table.Row{"Callback URL", info.CallbackURL})
t.AppendRow(table.Row{"Webhook Base URL", info.WebhookURL})
t.AppendRow(table.Row{"Controller Webhook URL", info.ControllerWebhookURL})
t.AppendRow(table.Row{"Minimum Job Age Backoff", info.MinimumJobAgeBackoff})
return t.Render()
}

Expand All @@ -163,6 +168,7 @@ func init() {
controllerUpdateCmd.Flags().StringVarP(&metadataURL, "metadata-url", "m", "", "The metadata URL for the controller (ie. https://garm.example.com/api/v1/metadata)")
controllerUpdateCmd.Flags().StringVarP(&callbackURL, "callback-url", "c", "", "The callback URL for the controller (ie. https://garm.example.com/api/v1/callbacks)")
controllerUpdateCmd.Flags().StringVarP(&webhookURL, "webhook-url", "w", "", "The webhook URL for the controller (ie. https://garm.example.com/webhooks)")
controllerUpdateCmd.Flags().UintVarP(&minimumJobAgeBackoff, "minimum-job-age-backoff", "b", 0, "The minimum job age backoff for the controller")

controllerCmd.AddCommand(
controllerShowCmd,
Expand Down
7 changes: 4 additions & 3 deletions cmd/garm-cli/cmd/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,10 @@ import (
)

var (
callbackURL string
metadataURL string
webhookURL string
callbackURL string
metadataURL string
webhookURL string
minimumJobAgeBackoff uint
)

// initCmd represents the init command
Expand Down
8 changes: 7 additions & 1 deletion database/sql/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ func dbControllerToCommonController(dbInfo ControllerInfo) (params.ControllerInf
WebhookURL: dbInfo.WebhookBaseURL,
ControllerWebhookURL: url,
CallbackURL: dbInfo.CallbackURL,
MinimumJobAgeBackoff: dbInfo.MinimumJobAgeBackoff,
}, nil
}

Expand Down Expand Up @@ -70,7 +71,8 @@ func (s *sqlDatabase) InitController() (params.ControllerInfo, error) {
}

newInfo := ControllerInfo{
ControllerID: newID,
ControllerID: newID,
MinimumJobAgeBackoff: 30,
}

q := s.conn.Save(&newInfo)
Expand Down Expand Up @@ -115,6 +117,10 @@ func (s *sqlDatabase) UpdateController(info params.UpdateControllerParams) (para
dbInfo.WebhookBaseURL = *info.WebhookURL
}

if info.MinimumJobAgeBackoff != nil {
dbInfo.MinimumJobAgeBackoff = *info.MinimumJobAgeBackoff
}

q = tx.Save(&dbInfo)
if q.Error != nil {
return errors.Wrap(q.Error, "saving controller info")
Expand Down
6 changes: 6 additions & 0 deletions database/sql/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,12 @@ type ControllerInfo struct {
CallbackURL string
MetadataURL string
WebhookBaseURL string
// MinimumJobAgeBackoff is the minimum time that a job must be in the queue
// before GARM will attempt to allocate a runner to service it. This backoff
// is useful if you have idle runners in various pools that could potentially
// pick up the job. GARM would allow this amount of time for runners to react
// before spinning up a new one and potentially having to scale down later.
MinimumJobAgeBackoff uint
}

type WorkflowJob struct {
Expand Down
20 changes: 20 additions & 0 deletions database/sql/sql.go
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,12 @@ func (s *sqlDatabase) migrateDB() error {
if !s.conn.Migrator().HasTable(&GithubCredentials{}) || !s.conn.Migrator().HasTable(&GithubEndpoint{}) {
needsCredentialMigration = true
}

var hasMinAgeField bool
if s.conn.Migrator().HasTable(&ControllerInfo{}) && s.conn.Migrator().HasColumn(&ControllerInfo{}, "minimum_job_age_backoff") {
hasMinAgeField = true
}

s.conn.Exec("PRAGMA foreign_keys = OFF")
if err := s.conn.AutoMigrate(
&User{},
Expand All @@ -427,6 +433,20 @@ func (s *sqlDatabase) migrateDB() error {
}
s.conn.Exec("PRAGMA foreign_keys = ON")

if !hasMinAgeField {
var controller ControllerInfo
if err := s.conn.First(&controller).Error; err != nil {
if !errors.Is(err, gorm.ErrRecordNotFound) {
return errors.Wrap(err, "updating controller info")
}
} else {
controller.MinimumJobAgeBackoff = 30
if err := s.conn.Save(&controller).Error; err != nil {
return errors.Wrap(err, "updating controller info")
}
}
}

if err := s.ensureGithubEndpoint(); err != nil {
return errors.Wrap(err, "ensuring github endpoint")
}
Expand Down
59 changes: 39 additions & 20 deletions params/params.go
Original file line number Diff line number Diff line change
Expand Up @@ -382,20 +382,6 @@ func (p *Pool) HasRequiredLabels(set []string) bool {
// used by swagger client generated code
type Pools []Pool

type Internal struct {
ControllerID string `json:"controller_id"`
InstanceCallbackURL string `json:"instance_callback_url"`
InstanceMetadataURL string `json:"instance_metadata_url"`
BaseWebhookURL string `json:"base_webhook_url"`
ControllerWebhookURL string `json:"controller_webhook_url"`

JWTSecret string `json:"jwt_secret"`
// GithubCredentialsDetails contains all info about the credentials, except the
// token, which is added above.
GithubCredentialsDetails GithubCredentials `json:"gh_creds_details"`
PoolBalancerType PoolBalancerType `json:"pool_balancing_type"`
}

type Repository struct {
ID string `json:"id"`
Owner string `json:"owner"`
Expand Down Expand Up @@ -569,12 +555,45 @@ type JWTResponse struct {
}

type ControllerInfo struct {
ControllerID uuid.UUID `json:"controller_id"`
Hostname string `json:"hostname"`
MetadataURL string `json:"metadata_url"`
CallbackURL string `json:"callback_url"`
WebhookURL string `json:"webhook_url"`
ControllerWebhookURL string `json:"controller_webhook_url"`
// ControllerID is the unique ID of this controller. This ID gets generated
// automatically on controller init.
ControllerID uuid.UUID `json:"controller_id"`
// Hostname is the hostname of the machine that runs this controller. In the
// future, this field will be migrated to a separate table that will keep track
// of each the controller nodes that are part of a cluster. This will happen when
// we implement controller scale-out capability.
Hostname string `json:"hostname"`
// MetadataURL is the public metadata URL of the GARM instance. This URL is used
// by instances to fetch information they need to set themselves up. The URL itself
// may be made available to runners via a reverse proxy or a load balancer. That
// means that the user is responsible for telling GARM what the public URL is, by
// setting this field.
MetadataURL string `json:"metadata_url"`
// CallbackURL is the URL where instances can send updates back to the controller.
// This URL is used by instances to send status updates back to the controller. The
// URL itself may be made available to instances via a reverse proxy or a load balancer.
// That means that the user is responsible for telling GARM what the public URL is, by
// setting this field.
CallbackURL string `json:"callback_url"`
// WebhookURL is the base URL where the controller will receive webhooks from github.
// When webhook management is used, this URL is used as a base to which the controller
// UUID is appended and which will receive the webhooks.
// The URL itself may be made available to instances via a reverse proxy or a load balancer.
// That means that the user is responsible for telling GARM what the public URL is, by
// setting this field.
WebhookURL string `json:"webhook_url"`
// ControllerWebhookURL is the controller specific URL where webhooks will be received.
// This field holds the WebhookURL defined above to which we append the ControllerID.
// Functionally it is the same as WebhookURL, but it allows us to safely manage webhooks
// from GARM without accidentally removing webhooks from other services or GARM controllers.
ControllerWebhookURL string `json:"controller_webhook_url"`
// MinimumJobAgeBackoff is the minimum time in seconds that a job must be in queued state
// before GARM will attempt to allocate a runner for it. When set to a non zero value,
// GARM will ignore the job until the job's age is greater than this value. When using
// the min_idle_runners feature of a pool, this gives enough time for potential idle
// runners to pick up the job before GARM attempts to allocate a new runner, thus avoiding
// the need to potentially scale down runners later.
MinimumJobAgeBackoff uint `json:"minimum_job_age_backoff"`
}

type GithubCredentials struct {
Expand Down
7 changes: 4 additions & 3 deletions params/requests.go
Original file line number Diff line number Diff line change
Expand Up @@ -503,9 +503,10 @@ func (u UpdateGithubCredentialsParams) Validate() error {
}

type UpdateControllerParams struct {
MetadataURL *string `json:"metadata_url,omitempty"`
CallbackURL *string `json:"callback_url,omitempty"`
WebhookURL *string `json:"webhook_url,omitempty"`
MetadataURL *string `json:"metadata_url,omitempty"`
CallbackURL *string `json:"callback_url,omitempty"`
WebhookURL *string `json:"webhook_url,omitempty"`
MinimumJobAgeBackoff *uint `json:"minimum_job_age_backoff,omitempty"`
}

func (u UpdateControllerParams) Validate() error {
Expand Down
4 changes: 2 additions & 2 deletions runner/pool/pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -1673,10 +1673,10 @@ func (r *basePoolManager) consumeQueuedJobs() error {
continue
}

if time.Since(job.UpdatedAt) < time.Second*30 {
if time.Since(job.UpdatedAt) < time.Second*time.Duration(r.controllerInfo.MinimumJobAgeBackoff) {
// give the idle runners a chance to pick up the job.
slog.DebugContext(
r.ctx, "job was updated less than 30 seconds ago. Skipping",
r.ctx, "job backoff not reached", "backoff_interval", r.controllerInfo.MinimumJobAgeBackoff,
"job_id", job.ID)
continue
}
Expand Down

0 comments on commit dcee092

Please sign in to comment.