Skip to content

Commit bb45324

Browse files
Merge pull request #459 from gabriel-samfira/account-for-job-id
Handle new jobID for scale sets
2 parents d26973d + a984782 commit bb45324

8 files changed

Lines changed: 107 additions & 59 deletions

File tree

database/sql/jobs.go

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ func sqlWorkflowJobToParamsJob(job WorkflowJob) (params.Job, error) {
4141

4242
jobParam := params.Job{
4343
ID: job.ID,
44+
WorkflowJobID: job.WorkflowJobID,
45+
ScaleSetJobID: job.ScaleSetJobID,
4446
RunID: job.RunID,
4547
Action: job.Action,
4648
Status: job.Status,
@@ -75,7 +77,8 @@ func (s *sqlDatabase) paramsJobToWorkflowJob(ctx context.Context, job params.Job
7577
}
7678

7779
workflofJob := WorkflowJob{
78-
ID: job.ID,
80+
ScaleSetJobID: job.ScaleSetJobID,
81+
WorkflowJobID: job.ID,
7982
RunID: job.RunID,
8083
Action: job.Action,
8184
Status: job.Status,
@@ -109,14 +112,27 @@ func (s *sqlDatabase) paramsJobToWorkflowJob(ctx context.Context, job params.Job
109112
}
110113

111114
func (s *sqlDatabase) DeleteJob(_ context.Context, jobID int64) (err error) {
115+
var workflowJob WorkflowJob
116+
q := s.conn.Where("workflow_job_id = ?", jobID).Preload("Instance").First(&workflowJob)
117+
if q.Error != nil {
118+
if errors.Is(q.Error, gorm.ErrRecordNotFound) {
119+
return nil
120+
}
121+
return errors.Wrap(q.Error, "fetching job")
122+
}
123+
removedJob, err := sqlWorkflowJobToParamsJob(workflowJob)
124+
if err != nil {
125+
return errors.Wrap(err, "converting job")
126+
}
127+
112128
defer func() {
113129
if err == nil {
114-
if notifyErr := s.sendNotify(common.JobEntityType, common.DeleteOperation, params.Job{ID: jobID}); notifyErr != nil {
130+
if notifyErr := s.sendNotify(common.JobEntityType, common.DeleteOperation, removedJob); notifyErr != nil {
115131
slog.With(slog.Any("error", notifyErr)).Error("failed to send notify")
116132
}
117133
}
118134
}()
119-
q := s.conn.Delete(&WorkflowJob{}, jobID)
135+
q = s.conn.Delete(&workflowJob)
120136
if q.Error != nil {
121137
if errors.Is(q.Error, gorm.ErrRecordNotFound) {
122138
return nil
@@ -132,7 +148,7 @@ func (s *sqlDatabase) LockJob(_ context.Context, jobID int64, entityID string) e
132148
return errors.Wrap(err, "parsing entity id")
133149
}
134150
var workflowJob WorkflowJob
135-
q := s.conn.Clauses(clause.Locking{Strength: "UPDATE"}).Preload("Instance").Where("id = ?", jobID).First(&workflowJob)
151+
q := s.conn.Preload("Instance").Where("id = ?", jobID).First(&workflowJob)
136152

137153
if q.Error != nil {
138154
if errors.Is(q.Error, gorm.ErrRecordNotFound) {
@@ -167,7 +183,7 @@ func (s *sqlDatabase) LockJob(_ context.Context, jobID int64, entityID string) e
167183

168184
func (s *sqlDatabase) BreakLockJobIsQueued(_ context.Context, jobID int64) (err error) {
169185
var workflowJob WorkflowJob
170-
q := s.conn.Clauses(clause.Locking{Strength: "UPDATE"}).Preload("Instance").Where("id = ? and status = ?", jobID, params.JobStatusQueued).First(&workflowJob)
186+
q := s.conn.Clauses(clause.Locking{Strength: "UPDATE"}).Preload("Instance").Where("workflow_job_id = ? and status = ?", jobID, params.JobStatusQueued).First(&workflowJob)
171187

172188
if q.Error != nil {
173189
if errors.Is(q.Error, gorm.ErrRecordNotFound) {
@@ -195,7 +211,7 @@ func (s *sqlDatabase) BreakLockJobIsQueued(_ context.Context, jobID int64) (err
195211

196212
func (s *sqlDatabase) UnlockJob(_ context.Context, jobID int64, entityID string) error {
197213
var workflowJob WorkflowJob
198-
q := s.conn.Clauses(clause.Locking{Strength: "UPDATE"}).Where("id = ?", jobID).First(&workflowJob)
214+
q := s.conn.Clauses(clause.Locking{Strength: "UPDATE"}).Where("workflow_job_id = ?", jobID).First(&workflowJob)
199215

200216
if q.Error != nil {
201217
if errors.Is(q.Error, gorm.ErrRecordNotFound) {
@@ -229,7 +245,14 @@ func (s *sqlDatabase) UnlockJob(_ context.Context, jobID int64, entityID string)
229245
func (s *sqlDatabase) CreateOrUpdateJob(ctx context.Context, job params.Job) (params.Job, error) {
230246
var workflowJob WorkflowJob
231247
var err error
232-
q := s.conn.Clauses(clause.Locking{Strength: "UPDATE"}).Preload("Instance").Where("id = ?", job.ID).First(&workflowJob)
248+
249+
searchField := "workflow_job_id = ?"
250+
var searchVal any = job.ID
251+
if job.ScaleSetJobID != "" {
252+
searchField = "scale_set_job_id = ?"
253+
searchVal = job.ScaleSetJobID
254+
}
255+
q := s.conn.Preload("Instance").Where(searchField, searchVal).First(&workflowJob)
233256

234257
if q.Error != nil {
235258
if !errors.Is(q.Error, gorm.ErrRecordNotFound) {
@@ -249,6 +272,9 @@ func (s *sqlDatabase) CreateOrUpdateJob(ctx context.Context, job params.Job) (pa
249272
workflowJob.GithubRunnerID = job.GithubRunnerID
250273
workflowJob.RunnerGroupID = job.RunnerGroupID
251274
workflowJob.RunnerGroupName = job.RunnerGroupName
275+
if job.RunID != 0 && workflowJob.RunID == 0 {
276+
workflowJob.RunID = job.RunID
277+
}
252278

253279
if job.LockedBy != uuid.Nil {
254280
workflowJob.LockedBy = job.LockedBy
@@ -327,7 +353,11 @@ func (s *sqlDatabase) ListEntityJobsByStatus(_ context.Context, entityType param
327353
}
328354

329355
var jobs []WorkflowJob
330-
query := s.conn.Model(&WorkflowJob{}).Preload("Instance").Where("status = ?", status)
356+
query := s.conn.
357+
Model(&WorkflowJob{}).
358+
Preload("Instance").
359+
Where("status = ?", status).
360+
Where("workflow_job_id > 0")
331361

332362
switch entityType {
333363
case params.ForgeEntityTypeOrganization:
@@ -381,7 +411,7 @@ func (s *sqlDatabase) ListAllJobs(_ context.Context) ([]params.Job, error) {
381411
// GetJobByID gets a job by id.
382412
func (s *sqlDatabase) GetJobByID(_ context.Context, jobID int64) (params.Job, error) {
383413
var job WorkflowJob
384-
query := s.conn.Model(&WorkflowJob{}).Preload("Instance").Where("id = ?", jobID)
414+
query := s.conn.Model(&WorkflowJob{}).Preload("Instance").Where("workflow_job_id = ?", jobID)
385415

386416
if err := query.First(&job); err.Error != nil {
387417
if errors.Is(err.Error, gorm.ErrRecordNotFound) {

database/sql/models.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,12 @@ type User struct {
319319
type WorkflowJob struct {
320320
// ID is the ID of the job.
321321
ID int64 `gorm:"index"`
322+
323+
// WorkflowJobID is the ID of the workflow job.
324+
WorkflowJobID int64 `gorm:"index:workflow_job_id_idx"`
325+
// ScaleSetJobID is the job ID for a scaleset job.
326+
ScaleSetJobID string `gorm:"index:scaleset_job_id_idx"`
327+
322328
// RunID is the ID of the workflow run. A run may have multiple jobs.
323329
RunID int64
324330
// Action is the specific activity that triggered the event.

database/sql/sql.go

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,22 @@ func (s *sqlDatabase) migrateCredentialsToDB() (err error) {
374374
return nil
375375
}
376376

377+
func (s *sqlDatabase) migrateWorkflow() error {
378+
if s.conn.Migrator().HasTable(&WorkflowJob{}) {
379+
if s.conn.Migrator().HasColumn(&WorkflowJob{}, "runner_name") {
380+
// Remove jobs that are not in "queued" status. We really only care about queued jobs. Once they transition
381+
// to something else, we don't really consume them anyway.
382+
if err := s.conn.Exec("delete from workflow_jobs where status is not 'queued'").Error; err != nil {
383+
return errors.Wrap(err, "updating workflow_jobs")
384+
}
385+
if err := s.conn.Migrator().DropColumn(&WorkflowJob{}, "runner_name"); err != nil {
386+
return errors.Wrap(err, "updating workflow_jobs")
387+
}
388+
}
389+
}
390+
return nil
391+
}
392+
377393
func (s *sqlDatabase) migrateDB() error {
378394
if s.conn.Migrator().HasIndex(&Organization{}, "idx_organizations_name") {
379395
if err := s.conn.Migrator().DropIndex(&Organization{}, "idx_organizations_name"); err != nil {
@@ -405,17 +421,8 @@ func (s *sqlDatabase) migrateDB() error {
405421
}
406422
}
407423

408-
if s.conn.Migrator().HasTable(&WorkflowJob{}) {
409-
if s.conn.Migrator().HasColumn(&WorkflowJob{}, "runner_name") {
410-
// Remove jobs that are not in "queued" status. We really only care about queued jobs. Once they transition
411-
// to something else, we don't really consume them anyway.
412-
if err := s.conn.Exec("delete from workflow_jobs where status is not 'queued'").Error; err != nil {
413-
return errors.Wrap(err, "updating workflow_jobs")
414-
}
415-
if err := s.conn.Migrator().DropColumn(&WorkflowJob{}, "runner_name"); err != nil {
416-
return errors.Wrap(err, "updating workflow_jobs")
417-
}
418-
}
424+
if err := s.migrateWorkflow(); err != nil {
425+
return errors.Wrap(err, "migrating workflows")
419426
}
420427

421428
if s.conn.Migrator().HasTable(&GithubEndpoint{}) {

params/github.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -420,7 +420,6 @@ func (r RunnerScaleSetMessage) GetJobsFromBody() ([]ScaleSetJobMessage, error) {
420420
if r.Body == "" {
421421
return nil, fmt.Errorf("no body specified")
422422
}
423-
424423
if err := json.Unmarshal([]byte(r.Body), &body); err != nil {
425424
return nil, fmt.Errorf("failed to unmarshal body: %w", err)
426425
}
@@ -519,6 +518,7 @@ type RunnerGroupList struct {
519518

520519
type ScaleSetJobMessage struct {
521520
MessageType string `json:"messageType,omitempty"`
521+
JobID string `json:"jobId,omitempty"`
522522
RunnerRequestID int64 `json:"runnerRequestId,omitempty"`
523523
RepositoryName string `json:"repositoryName,omitempty"`
524524
OwnerName string `json:"ownerName,omitempty"`
@@ -552,7 +552,7 @@ func (s ScaleSetJobMessage) MessageTypeToStatus() JobStatus {
552552

553553
func (s ScaleSetJobMessage) ToJob() Job {
554554
return Job{
555-
ID: s.RunnerRequestID,
555+
ScaleSetJobID: s.JobID,
556556
Action: s.EventName,
557557
RunID: s.WorkflowRunID,
558558
Status: string(s.MessageTypeToStatus()),

params/params.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1035,6 +1035,10 @@ func (p RunnerPrefix) GetRunnerPrefix() string {
10351035
type Job struct {
10361036
// ID is the ID of the job.
10371037
ID int64 `json:"id,omitempty"`
1038+
1039+
WorkflowJobID int64 `json:"workflow_job_id,omitempty"`
1040+
// ScaleSetJobID is the job ID when generated for a scale set.
1041+
ScaleSetJobID string `json:"scaleset_job_id,omitempty"`
10381042
// RunID is the ID of the workflow run. A run may have multiple jobs.
10391043
RunID int64 `json:"run_id,omitempty"`
10401044
// Action is the specific activity that triggered the event.

runner/pool/pool.go

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -176,19 +176,19 @@ func (r *basePoolManager) HandleWorkflowJob(job params.WorkflowJob) error {
176176

177177
var triggeredBy int64
178178
defer func() {
179-
if jobParams.ID == 0 {
179+
if jobParams.WorkflowJobID == 0 {
180180
return
181181
}
182182
// we're updating the job in the database, regardless of whether it was successful or not.
183183
// or if it was meant for this pool or not. Github will send the same job data to all hierarchies
184184
// that have been configured to work with garm. Updating the job at all levels should yield the same
185185
// outcome in the db.
186-
_, err := r.store.GetJobByID(r.ctx, jobParams.ID)
186+
_, err := r.store.GetJobByID(r.ctx, jobParams.WorkflowJobID)
187187
if err != nil {
188188
if !errors.Is(err, runnerErrors.ErrNotFound) {
189189
slog.With(slog.Any("error", err)).ErrorContext(
190190
r.ctx, "failed to get job",
191-
"job_id", jobParams.ID)
191+
"job_id", jobParams.WorkflowJobID)
192192
return
193193
}
194194
// This job is new to us. Check if we have a pool that can handle it.
@@ -203,10 +203,10 @@ func (r *basePoolManager) HandleWorkflowJob(job params.WorkflowJob) error {
203203

204204
if _, jobErr := r.store.CreateOrUpdateJob(r.ctx, jobParams); jobErr != nil {
205205
slog.With(slog.Any("error", jobErr)).ErrorContext(
206-
r.ctx, "failed to update job", "job_id", jobParams.ID)
206+
r.ctx, "failed to update job", "job_id", jobParams.WorkflowJobID)
207207
}
208208

209-
if triggeredBy != 0 && jobParams.ID != triggeredBy {
209+
if triggeredBy != 0 && jobParams.WorkflowJobID != triggeredBy {
210210
// The triggeredBy value is only set by the "in_progress" webhook. The runner that
211211
// transitioned to in_progress was created as a result of a different queued job. If that job is
212212
// still queued and we don't remove the lock, it will linger until the lock timeout is reached.
@@ -970,7 +970,7 @@ func (r *basePoolManager) paramsWorkflowJobToParamsJob(job params.WorkflowJob) (
970970
}
971971

972972
jobParams := params.Job{
973-
ID: job.WorkflowJob.ID,
973+
WorkflowJobID: job.WorkflowJob.ID,
974974
Action: job.Action,
975975
RunID: job.WorkflowJob.RunID,
976976
Status: job.WorkflowJob.Status,
@@ -1106,10 +1106,10 @@ func (r *basePoolManager) scaleDownOnePool(ctx context.Context, pool params.Pool
11061106

11071107
for _, job := range queued {
11081108
if time.Since(job.CreatedAt).Minutes() > 10 && pool.HasRequiredLabels(job.Labels) {
1109-
if err := r.store.DeleteJob(ctx, job.ID); err != nil && !errors.Is(err, runnerErrors.ErrNotFound) {
1109+
if err := r.store.DeleteJob(ctx, job.WorkflowJobID); err != nil && !errors.Is(err, runnerErrors.ErrNotFound) {
11101110
slog.With(slog.Any("error", err)).ErrorContext(
11111111
ctx, "failed to delete job",
1112-
"job_id", job.ID)
1112+
"job_id", job.WorkflowJobID)
11131113
}
11141114
}
11151115
}
@@ -1760,7 +1760,7 @@ func (r *basePoolManager) consumeQueuedJobs() error {
17601760
// Job was handled by us or another entity.
17611761
slog.DebugContext(
17621762
r.ctx, "job is locked",
1763-
"job_id", job.ID,
1763+
"job_id", job.WorkflowJobID,
17641764
"locking_entity", job.LockedBy.String())
17651765
continue
17661766
}
@@ -1769,20 +1769,20 @@ func (r *basePoolManager) consumeQueuedJobs() error {
17691769
// give the idle runners a chance to pick up the job.
17701770
slog.DebugContext(
17711771
r.ctx, "job backoff not reached", "backoff_interval", r.controllerInfo.MinimumJobAgeBackoff,
1772-
"job_id", job.ID)
1772+
"job_id", job.WorkflowJobID)
17731773
continue
17741774
}
17751775

17761776
if time.Since(job.UpdatedAt) >= time.Minute*10 {
17771777
// Job is still queued in our db, 10 minutes after a matching runner
17781778
// was spawned. Unlock it and try again. A different job may have picked up
17791779
// the runner.
1780-
if err := r.store.UnlockJob(r.ctx, job.ID, r.ID()); err != nil {
1780+
if err := r.store.UnlockJob(r.ctx, job.WorkflowJobID, r.ID()); err != nil {
17811781
// nolint:golangci-lint,godox
17821782
// TODO: Implament a cache? Should we return here?
17831783
slog.With(slog.Any("error", err)).ErrorContext(
17841784
r.ctx, "failed to unlock job",
1785-
"job_id", job.ID)
1785+
"job_id", job.WorkflowJobID)
17861786
continue
17871787
}
17881788
}
@@ -1795,7 +1795,7 @@ func (r *basePoolManager) consumeQueuedJobs() error {
17951795
// runner.
17961796
slog.DebugContext(
17971797
r.ctx, "job is locked by us",
1798-
"job_id", job.ID)
1798+
"job_id", job.WorkflowJobID)
17991799
continue
18001800
}
18011801

@@ -1816,29 +1816,29 @@ func (r *basePoolManager) consumeQueuedJobs() error {
18161816
}
18171817

18181818
runnerCreated := false
1819-
if err := r.store.LockJob(r.ctx, job.ID, r.ID()); err != nil {
1819+
if err := r.store.LockJob(r.ctx, job.WorkflowJobID, r.ID()); err != nil {
18201820
slog.With(slog.Any("error", err)).ErrorContext(
18211821
r.ctx, "could not lock job",
1822-
"job_id", job.ID)
1822+
"job_id", job.WorkflowJobID)
18231823
continue
18241824
}
18251825

18261826
jobLabels := []string{
1827-
fmt.Sprintf("%s=%d", jobLabelPrefix, job.ID),
1827+
fmt.Sprintf("%s=%d", jobLabelPrefix, job.WorkflowJobID),
18281828
}
18291829
for i := 0; i < poolRR.Len(); i++ {
18301830
pool, err := poolRR.Next()
18311831
if err != nil {
18321832
slog.With(slog.Any("error", err)).ErrorContext(
18331833
r.ctx, "could not find a pool to create a runner for job",
1834-
"job_id", job.ID)
1834+
"job_id", job.WorkflowJobID)
18351835
break
18361836
}
18371837

18381838
slog.InfoContext(
18391839
r.ctx, "attempting to create a runner in pool",
18401840
"pool_id", pool.ID,
1841-
"job_id", job.ID)
1841+
"job_id", job.WorkflowJobID)
18421842
if err := r.addRunnerToPool(pool, jobLabels); err != nil {
18431843
slog.With(slog.Any("error", err)).ErrorContext(
18441844
r.ctx, "could not add runner to pool",
@@ -1847,19 +1847,19 @@ func (r *basePoolManager) consumeQueuedJobs() error {
18471847
}
18481848
slog.DebugContext(r.ctx, "a new runner was added as a response to queued job",
18491849
"pool_id", pool.ID,
1850-
"job_id", job.ID)
1850+
"job_id", job.WorkflowJobID)
18511851
runnerCreated = true
18521852
break
18531853
}
18541854

18551855
if !runnerCreated {
18561856
slog.WarnContext(
18571857
r.ctx, "could not create a runner for job; unlocking",
1858-
"job_id", job.ID)
1859-
if err := r.store.UnlockJob(r.ctx, job.ID, r.ID()); err != nil {
1858+
"job_id", job.WorkflowJobID)
1859+
if err := r.store.UnlockJob(r.ctx, job.WorkflowJobID, r.ID()); err != nil {
18601860
slog.With(slog.Any("error", err)).ErrorContext(
18611861
r.ctx, "failed to unlock job",
1862-
"job_id", job.ID)
1862+
"job_id", job.WorkflowJobID)
18631863
return errors.Wrap(err, "unlocking job")
18641864
}
18651865
}

workers/scaleset/scaleset_helper.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ func (w *Worker) recordOrUpdateJob(job params.ScaleSetJobMessage) error {
8080
case params.ForgeEntityTypeOrganization:
8181
jobParams.OrgID = &asUUID
8282
default:
83-
return fmt.Errorf("unknown entity type: %s", entity.EntityType)
83+
return fmt.Errorf("unknown entity type: %s --> %s", entity.EntityType, entity)
8484
}
8585

8686
if _, jobErr := w.store.CreateOrUpdateJob(w.ctx, jobParams); jobErr != nil {
@@ -163,6 +163,7 @@ func (w *Worker) HandleJobsStarted(jobs []params.ScaleSetJobMessage) (err error)
163163
}
164164

165165
func (w *Worker) HandleJobsAvailable(jobs []params.ScaleSetJobMessage) error {
166+
slog.DebugContext(w.ctx, "handling jobs available", "jobs", jobs)
166167
for _, job := range jobs {
167168
if err := w.recordOrUpdateJob(job); err != nil {
168169
// recording scale set jobs are purely informational for now.

0 commit comments

Comments
 (0)