mirror of
https://github.com/coder/coder.git
synced 2026-09-21 20:51:01 +08:00
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:
@@ -2803,7 +2803,7 @@ func (q *querier) GetWorkspacesAndAgentsByOwnerID(ctx context.Context, ownerID u
|
||||
return q.db.GetAuthorizedWorkspacesAndAgentsByOwnerID(ctx, ownerID, prep)
|
||||
}
|
||||
|
||||
func (q *querier) GetWorkspacesEligibleForTransition(ctx context.Context, now time.Time) ([]database.WorkspaceTable, error) {
|
||||
func (q *querier) GetWorkspacesEligibleForTransition(ctx context.Context, now time.Time) ([]database.GetWorkspacesEligibleForTransitionRow, error) {
|
||||
return q.db.GetWorkspacesEligibleForTransition(ctx, now)
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1659,7 +1659,7 @@ func (m queryMetricsStore) GetWorkspacesAndAgentsByOwnerID(ctx context.Context,
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) GetWorkspacesEligibleForTransition(ctx context.Context, now time.Time) ([]database.WorkspaceTable, error) {
|
||||
func (m queryMetricsStore) GetWorkspacesEligibleForTransition(ctx context.Context, now time.Time) ([]database.GetWorkspacesEligibleForTransitionRow, error) {
|
||||
start := time.Now()
|
||||
workspaces, err := m.s.GetWorkspacesEligibleForTransition(ctx, now)
|
||||
m.queryLatencies.WithLabelValues("GetWorkspacesEligibleForAutoStartStop").Observe(time.Since(start).Seconds())
|
||||
|
||||
@@ -3503,10 +3503,10 @@ func (mr *MockStoreMockRecorder) GetWorkspacesAndAgentsByOwnerID(arg0, arg1 any)
|
||||
}
|
||||
|
||||
// GetWorkspacesEligibleForTransition mocks base method.
|
||||
func (m *MockStore) GetWorkspacesEligibleForTransition(arg0 context.Context, arg1 time.Time) ([]database.WorkspaceTable, error) {
|
||||
func (m *MockStore) GetWorkspacesEligibleForTransition(arg0 context.Context, arg1 time.Time) ([]database.GetWorkspacesEligibleForTransitionRow, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetWorkspacesEligibleForTransition", arg0, arg1)
|
||||
ret0, _ := ret[0].([]database.WorkspaceTable)
|
||||
ret0, _ := ret[0].([]database.GetWorkspacesEligibleForTransitionRow)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
@@ -346,7 +346,7 @@ type sqlcQuerier interface {
|
||||
// be used in a WHERE clause.
|
||||
GetWorkspaces(ctx context.Context, arg GetWorkspacesParams) ([]GetWorkspacesRow, error)
|
||||
GetWorkspacesAndAgentsByOwnerID(ctx context.Context, ownerID uuid.UUID) ([]GetWorkspacesAndAgentsByOwnerIDRow, error)
|
||||
GetWorkspacesEligibleForTransition(ctx context.Context, now time.Time) ([]WorkspaceTable, error)
|
||||
GetWorkspacesEligibleForTransition(ctx context.Context, now time.Time) ([]GetWorkspacesEligibleForTransitionRow, error)
|
||||
InsertAPIKey(ctx context.Context, arg InsertAPIKeyParams) (APIKey, error)
|
||||
// We use the organization_id as the id
|
||||
// for simplicity since all users is
|
||||
|
||||
@@ -15363,7 +15363,8 @@ func (q *sqlQuerier) GetWorkspacesAndAgentsByOwnerID(ctx context.Context, ownerI
|
||||
|
||||
const getWorkspacesEligibleForTransition = `-- name: GetWorkspacesEligibleForTransition :many
|
||||
SELECT
|
||||
workspaces.id, workspaces.created_at, workspaces.updated_at, workspaces.owner_id, workspaces.organization_id, workspaces.template_id, workspaces.deleted, workspaces.name, workspaces.autostart_schedule, workspaces.ttl, workspaces.last_used_at, workspaces.dormant_at, workspaces.deleting_at, workspaces.automatic_updates, workspaces.favorite
|
||||
workspaces.id,
|
||||
workspaces.name
|
||||
FROM
|
||||
workspaces
|
||||
LEFT JOIN
|
||||
@@ -15385,82 +15386,104 @@ WHERE
|
||||
) AND
|
||||
|
||||
(
|
||||
-- If the workspace build was a start transition, the workspace is
|
||||
-- potentially eligible for autostop if it's past the deadline. The
|
||||
-- deadline is computed at build time upon success and is bumped based
|
||||
-- on activity (up the max deadline if set). We don't need to check
|
||||
-- license here since that's done when the values are written to the build.
|
||||
-- A workspace may be eligible for autostop if the following are true:
|
||||
-- * The provisioner job has not failed.
|
||||
-- * The workspace is not dormant.
|
||||
-- * The workspace build was a start transition.
|
||||
-- * The workspace's owner is suspended OR the workspace build deadline has passed.
|
||||
(
|
||||
workspace_builds.transition = 'start'::workspace_transition AND
|
||||
workspace_builds.deadline IS NOT NULL AND
|
||||
workspace_builds.deadline < $1 :: timestamptz
|
||||
provisioner_jobs.job_status != 'failed'::provisioner_job_status AND
|
||||
workspaces.dormant_at IS NULL AND
|
||||
workspace_builds.transition = 'start'::workspace_transition AND (
|
||||
users.status = 'suspended'::user_status OR (
|
||||
workspace_builds.deadline != '0001-01-01 00:00:00+00'::timestamptz AND
|
||||
workspace_builds.deadline < $1 :: timestamptz
|
||||
)
|
||||
)
|
||||
) OR
|
||||
|
||||
-- If the workspace build was a stop transition, the workspace is
|
||||
-- potentially eligible for autostart if it has a schedule set. The
|
||||
-- caller must check if the template allows autostart in a license-aware
|
||||
-- fashion as we cannot check it here.
|
||||
-- A workspace may be eligible for autostart if the following are true:
|
||||
-- * The workspace's owner is active.
|
||||
-- * The provisioner job did not fail.
|
||||
-- * The workspace build was a stop transition.
|
||||
-- * The workspace has an autostart schedule.
|
||||
(
|
||||
users.status = 'active'::user_status AND
|
||||
provisioner_jobs.job_status != 'failed'::provisioner_job_status AND
|
||||
workspace_builds.transition = 'stop'::workspace_transition AND
|
||||
workspaces.autostart_schedule IS NOT NULL
|
||||
) OR
|
||||
|
||||
-- If the workspace's most recent job resulted in an error
|
||||
-- it may be eligible for failed stop.
|
||||
(
|
||||
provisioner_jobs.error IS NOT NULL AND
|
||||
provisioner_jobs.error != '' AND
|
||||
workspace_builds.transition = 'start'::workspace_transition
|
||||
) OR
|
||||
|
||||
-- If the workspace's template has an inactivity_ttl set
|
||||
-- it may be eligible for dormancy.
|
||||
-- A workspace may be eligible for dormant stop if the following are true:
|
||||
-- * The workspace is not dormant.
|
||||
-- * The template has set a time 'til dormant.
|
||||
-- * The workspace has been unused for longer than the time 'til dormancy.
|
||||
(
|
||||
workspaces.dormant_at IS NULL AND
|
||||
templates.time_til_dormant > 0 AND
|
||||
workspaces.dormant_at IS NULL
|
||||
($1 :: timestamptz) - workspaces.last_used_at > (INTERVAL '1 millisecond' * (templates.time_til_dormant / 1000000))
|
||||
) OR
|
||||
|
||||
-- If the workspace's template has a time_til_dormant_autodelete set
|
||||
-- and the workspace is already dormant.
|
||||
-- A workspace may be eligible for deletion if the following are true:
|
||||
-- * The workspace is dormant.
|
||||
-- * The workspace is scheduled to be deleted.
|
||||
-- * If there was a prior attempt to delete the workspace that failed:
|
||||
-- * This attempt was at least 24 hours ago.
|
||||
(
|
||||
workspaces.dormant_at IS NOT NULL AND
|
||||
workspaces.deleting_at IS NOT NULL AND
|
||||
workspaces.deleting_at < $1 :: timestamptz AND
|
||||
templates.time_til_dormant_autodelete > 0 AND
|
||||
workspaces.dormant_at IS NOT NULL
|
||||
CASE
|
||||
WHEN (
|
||||
workspace_builds.transition = 'delete'::workspace_transition AND
|
||||
provisioner_jobs.job_status = 'failed'::provisioner_job_status
|
||||
) THEN (
|
||||
(
|
||||
provisioner_jobs.canceled_at IS NOT NULL OR
|
||||
provisioner_jobs.completed_at IS NOT NULL
|
||||
) AND (
|
||||
($1 :: timestamptz) - (CASE
|
||||
WHEN provisioner_jobs.canceled_at IS NOT NULL THEN provisioner_jobs.canceled_at
|
||||
ELSE provisioner_jobs.completed_at
|
||||
END) > INTERVAL '24 hours'
|
||||
)
|
||||
)
|
||||
ELSE true
|
||||
END
|
||||
) OR
|
||||
|
||||
-- If the user account is suspended, and the workspace is running.
|
||||
-- A workspace may be eligible for failed stop if the following are true:
|
||||
-- * The template has a failure ttl set.
|
||||
-- * The workspace build was a start transition.
|
||||
-- * The provisioner job failed.
|
||||
-- * The provisioner job had completed.
|
||||
-- * The provisioner job has been completed for longer than the failure ttl.
|
||||
(
|
||||
users.status = 'suspended'::user_status AND
|
||||
workspace_builds.transition = 'start'::workspace_transition
|
||||
templates.failure_ttl > 0 AND
|
||||
workspace_builds.transition = 'start'::workspace_transition AND
|
||||
provisioner_jobs.job_status = 'failed'::provisioner_job_status AND
|
||||
provisioner_jobs.completed_at IS NOT NULL AND
|
||||
($1 :: timestamptz) - provisioner_jobs.completed_at > (INTERVAL '1 millisecond' * (templates.failure_ttl / 1000000))
|
||||
)
|
||||
) AND workspaces.deleted = 'false'
|
||||
`
|
||||
|
||||
func (q *sqlQuerier) GetWorkspacesEligibleForTransition(ctx context.Context, now time.Time) ([]WorkspaceTable, error) {
|
||||
type GetWorkspacesEligibleForTransitionRow struct {
|
||||
ID uuid.UUID `db:"id" json:"id"`
|
||||
Name string `db:"name" json:"name"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) GetWorkspacesEligibleForTransition(ctx context.Context, now time.Time) ([]GetWorkspacesEligibleForTransitionRow, error) {
|
||||
rows, err := q.db.QueryContext(ctx, getWorkspacesEligibleForTransition, now)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []WorkspaceTable
|
||||
var items []GetWorkspacesEligibleForTransitionRow
|
||||
for rows.Next() {
|
||||
var i WorkspaceTable
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.OwnerID,
|
||||
&i.OrganizationID,
|
||||
&i.TemplateID,
|
||||
&i.Deleted,
|
||||
&i.Name,
|
||||
&i.AutostartSchedule,
|
||||
&i.Ttl,
|
||||
&i.LastUsedAt,
|
||||
&i.DormantAt,
|
||||
&i.DeletingAt,
|
||||
&i.AutomaticUpdates,
|
||||
&i.Favorite,
|
||||
); err != nil {
|
||||
var i GetWorkspacesEligibleForTransitionRow
|
||||
if err := rows.Scan(&i.ID, &i.Name); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
|
||||
@@ -557,7 +557,8 @@ FROM pending_workspaces, building_workspaces, running_workspaces, failed_workspa
|
||||
|
||||
-- name: GetWorkspacesEligibleForTransition :many
|
||||
SELECT
|
||||
workspaces.*
|
||||
workspaces.id,
|
||||
workspaces.name
|
||||
FROM
|
||||
workspaces
|
||||
LEFT JOIN
|
||||
@@ -579,52 +580,85 @@ WHERE
|
||||
) AND
|
||||
|
||||
(
|
||||
-- If the workspace build was a start transition, the workspace is
|
||||
-- potentially eligible for autostop if it's past the deadline. The
|
||||
-- deadline is computed at build time upon success and is bumped based
|
||||
-- on activity (up the max deadline if set). We don't need to check
|
||||
-- license here since that's done when the values are written to the build.
|
||||
-- A workspace may be eligible for autostop if the following are true:
|
||||
-- * The provisioner job has not failed.
|
||||
-- * The workspace is not dormant.
|
||||
-- * The workspace build was a start transition.
|
||||
-- * The workspace's owner is suspended OR the workspace build deadline has passed.
|
||||
(
|
||||
workspace_builds.transition = 'start'::workspace_transition AND
|
||||
workspace_builds.deadline IS NOT NULL AND
|
||||
workspace_builds.deadline < @now :: timestamptz
|
||||
provisioner_jobs.job_status != 'failed'::provisioner_job_status AND
|
||||
workspaces.dormant_at IS NULL AND
|
||||
workspace_builds.transition = 'start'::workspace_transition AND (
|
||||
users.status = 'suspended'::user_status OR (
|
||||
workspace_builds.deadline != '0001-01-01 00:00:00+00'::timestamptz AND
|
||||
workspace_builds.deadline < @now :: timestamptz
|
||||
)
|
||||
)
|
||||
) OR
|
||||
|
||||
-- If the workspace build was a stop transition, the workspace is
|
||||
-- potentially eligible for autostart if it has a schedule set. The
|
||||
-- caller must check if the template allows autostart in a license-aware
|
||||
-- fashion as we cannot check it here.
|
||||
-- A workspace may be eligible for autostart if the following are true:
|
||||
-- * The workspace's owner is active.
|
||||
-- * The provisioner job did not fail.
|
||||
-- * The workspace build was a stop transition.
|
||||
-- * The workspace has an autostart schedule.
|
||||
(
|
||||
users.status = 'active'::user_status AND
|
||||
provisioner_jobs.job_status != 'failed'::provisioner_job_status AND
|
||||
workspace_builds.transition = 'stop'::workspace_transition AND
|
||||
workspaces.autostart_schedule IS NOT NULL
|
||||
) OR
|
||||
|
||||
-- If the workspace's most recent job resulted in an error
|
||||
-- it may be eligible for failed stop.
|
||||
(
|
||||
provisioner_jobs.error IS NOT NULL AND
|
||||
provisioner_jobs.error != '' AND
|
||||
workspace_builds.transition = 'start'::workspace_transition
|
||||
) OR
|
||||
|
||||
-- If the workspace's template has an inactivity_ttl set
|
||||
-- it may be eligible for dormancy.
|
||||
-- A workspace may be eligible for dormant stop if the following are true:
|
||||
-- * The workspace is not dormant.
|
||||
-- * The template has set a time 'til dormant.
|
||||
-- * The workspace has been unused for longer than the time 'til dormancy.
|
||||
(
|
||||
workspaces.dormant_at IS NULL AND
|
||||
templates.time_til_dormant > 0 AND
|
||||
workspaces.dormant_at IS NULL
|
||||
(@now :: timestamptz) - workspaces.last_used_at > (INTERVAL '1 millisecond' * (templates.time_til_dormant / 1000000))
|
||||
) OR
|
||||
|
||||
-- If the workspace's template has a time_til_dormant_autodelete set
|
||||
-- and the workspace is already dormant.
|
||||
-- A workspace may be eligible for deletion if the following are true:
|
||||
-- * The workspace is dormant.
|
||||
-- * The workspace is scheduled to be deleted.
|
||||
-- * If there was a prior attempt to delete the workspace that failed:
|
||||
-- * This attempt was at least 24 hours ago.
|
||||
(
|
||||
workspaces.dormant_at IS NOT NULL AND
|
||||
workspaces.deleting_at IS NOT NULL AND
|
||||
workspaces.deleting_at < @now :: timestamptz AND
|
||||
templates.time_til_dormant_autodelete > 0 AND
|
||||
workspaces.dormant_at IS NOT NULL
|
||||
CASE
|
||||
WHEN (
|
||||
workspace_builds.transition = 'delete'::workspace_transition AND
|
||||
provisioner_jobs.job_status = 'failed'::provisioner_job_status
|
||||
) THEN (
|
||||
(
|
||||
provisioner_jobs.canceled_at IS NOT NULL OR
|
||||
provisioner_jobs.completed_at IS NOT NULL
|
||||
) AND (
|
||||
(@now :: timestamptz) - (CASE
|
||||
WHEN provisioner_jobs.canceled_at IS NOT NULL THEN provisioner_jobs.canceled_at
|
||||
ELSE provisioner_jobs.completed_at
|
||||
END) > INTERVAL '24 hours'
|
||||
)
|
||||
)
|
||||
ELSE true
|
||||
END
|
||||
) OR
|
||||
|
||||
-- If the user account is suspended, and the workspace is running.
|
||||
-- A workspace may be eligible for failed stop if the following are true:
|
||||
-- * The template has a failure ttl set.
|
||||
-- * The workspace build was a start transition.
|
||||
-- * The provisioner job failed.
|
||||
-- * The provisioner job had completed.
|
||||
-- * The provisioner job has been completed for longer than the failure ttl.
|
||||
(
|
||||
users.status = 'suspended'::user_status AND
|
||||
workspace_builds.transition = 'start'::workspace_transition
|
||||
templates.failure_ttl > 0 AND
|
||||
workspace_builds.transition = 'start'::workspace_transition AND
|
||||
provisioner_jobs.job_status = 'failed'::provisioner_job_status AND
|
||||
provisioner_jobs.completed_at IS NOT NULL AND
|
||||
(@now :: timestamptz) - provisioner_jobs.completed_at > (INTERVAL '1 millisecond' * (templates.failure_ttl / 1000000))
|
||||
)
|
||||
) AND workspaces.deleted = 'false';
|
||||
|
||||
@@ -727,5 +761,3 @@ WHERE
|
||||
-- Authorize Filter clause will be injected below in GetAuthorizedWorkspacesAndAgentsByOwnerID
|
||||
-- @authorize_filter
|
||||
GROUP BY workspaces.id, workspaces.name, latest_build.job_status, latest_build.job_id, latest_build.transition;
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user