fix: make GetWorkspacesEligibleForTransition return less false-positives (#15429)

Relates to https://github.com/coder/coder/issues/15082

The old implementation of `GetWorkspacesEligibleForTransition` returns
many workspaces that are not actually eligible for transition. This new
implementation reduces this number significantly (at least on our
dogfood instance).
This commit is contained in:
Danielle Maywood
2024-11-13 10:24:20 +00:00
committed by GitHub
parent fa69d1ca74
commit f2fe379bd2
7 changed files with 223 additions and 129 deletions
+81 -42
View File
@@ -6868,60 +6868,99 @@ func (q *FakeQuerier) GetWorkspacesAndAgentsByOwnerID(ctx context.Context, owner
return q.GetAuthorizedWorkspacesAndAgentsByOwnerID(ctx, ownerID, nil)
}
func (q *FakeQuerier) GetWorkspacesEligibleForTransition(ctx context.Context, now time.Time) ([]database.WorkspaceTable, error) {
func (q *FakeQuerier) GetWorkspacesEligibleForTransition(ctx context.Context, now time.Time) ([]database.GetWorkspacesEligibleForTransitionRow, error) {
q.mutex.RLock()
defer q.mutex.RUnlock()
workspaces := []database.WorkspaceTable{}
workspaces := []database.GetWorkspacesEligibleForTransitionRow{}
for _, workspace := range q.workspaces {
build, err := q.getLatestWorkspaceBuildByWorkspaceIDNoLock(ctx, workspace.ID)
if err != nil {
return nil, err
}
if build.Transition == database.WorkspaceTransitionStart &&
!build.Deadline.IsZero() &&
build.Deadline.Before(now) &&
!workspace.DormantAt.Valid {
workspaces = append(workspaces, workspace)
continue
}
if build.Transition == database.WorkspaceTransitionStop &&
workspace.AutostartSchedule.Valid &&
!workspace.DormantAt.Valid {
workspaces = append(workspaces, workspace)
continue
}
job, err := q.getProvisionerJobByIDNoLock(ctx, build.JobID)
if err != nil {
return nil, xerrors.Errorf("get provisioner job by ID: %w", err)
}
if codersdk.ProvisionerJobStatus(job.JobStatus) == codersdk.ProvisionerJobFailed {
workspaces = append(workspaces, workspace)
continue
}
template, err := q.getTemplateByIDNoLock(ctx, workspace.TemplateID)
if err != nil {
return nil, xerrors.Errorf("get template by ID: %w", err)
}
if !workspace.DormantAt.Valid && template.TimeTilDormant > 0 {
workspaces = append(workspaces, workspace)
continue
}
if workspace.DormantAt.Valid && template.TimeTilDormantAutoDelete > 0 {
workspaces = append(workspaces, workspace)
continue
return nil, xerrors.Errorf("get workspace build by ID: %w", err)
}
user, err := q.getUserByIDNoLock(workspace.OwnerID)
if err != nil {
return nil, xerrors.Errorf("get user by ID: %w", err)
}
if user.Status == database.UserStatusSuspended && build.Transition == database.WorkspaceTransitionStart {
workspaces = append(workspaces, workspace)
job, err := q.getProvisionerJobByIDNoLock(ctx, build.JobID)
if err != nil {
return nil, xerrors.Errorf("get provisioner job by ID: %w", err)
}
template, err := q.getTemplateByIDNoLock(ctx, workspace.TemplateID)
if err != nil {
return nil, xerrors.Errorf("get template by ID: %w", err)
}
if workspace.Deleted {
continue
}
if job.JobStatus != database.ProvisionerJobStatusFailed &&
!workspace.DormantAt.Valid &&
build.Transition == database.WorkspaceTransitionStart &&
(user.Status == database.UserStatusSuspended || (!build.Deadline.IsZero() && build.Deadline.Before(now))) {
workspaces = append(workspaces, database.GetWorkspacesEligibleForTransitionRow{
ID: workspace.ID,
Name: workspace.Name,
})
continue
}
if user.Status == database.UserStatusActive &&
job.JobStatus != database.ProvisionerJobStatusFailed &&
build.Transition == database.WorkspaceTransitionStop &&
workspace.AutostartSchedule.Valid {
workspaces = append(workspaces, database.GetWorkspacesEligibleForTransitionRow{
ID: workspace.ID,
Name: workspace.Name,
})
continue
}
if !workspace.DormantAt.Valid &&
template.TimeTilDormant > 0 &&
now.Sub(workspace.LastUsedAt) > time.Duration(template.TimeTilDormant) {
workspaces = append(workspaces, database.GetWorkspacesEligibleForTransitionRow{
ID: workspace.ID,
Name: workspace.Name,
})
continue
}
if workspace.DormantAt.Valid &&
workspace.DeletingAt.Valid &&
workspace.DeletingAt.Time.Before(now) &&
template.TimeTilDormantAutoDelete > 0 {
if build.Transition == database.WorkspaceTransitionDelete &&
job.JobStatus == database.ProvisionerJobStatusFailed {
if job.CanceledAt.Valid && now.Sub(job.CanceledAt.Time) <= 24*time.Hour {
continue
}
if job.CompletedAt.Valid && now.Sub(job.CompletedAt.Time) <= 24*time.Hour {
continue
}
}
workspaces = append(workspaces, database.GetWorkspacesEligibleForTransitionRow{
ID: workspace.ID,
Name: workspace.Name,
})
continue
}
if template.FailureTTL > 0 &&
build.Transition == database.WorkspaceTransitionStart &&
job.JobStatus == database.ProvisionerJobStatusFailed &&
job.CompletedAt.Valid &&
now.Sub(job.CompletedAt.Time) > time.Duration(template.FailureTTL) {
workspaces = append(workspaces, database.GetWorkspacesEligibleForTransitionRow{
ID: workspace.ID,
Name: workspace.Name,
})
continue
}
}