Determine latest chosen preset using latest non-null preset selection

See https://github.com/coder/internal/issues/398

Signed-off-by: Danny Kopping <danny@coder.com>
This commit is contained in:
Danny Kopping
2025-02-21 14:36:35 +00:00
parent 44d12aa722
commit c88c04c022
5 changed files with 178 additions and 108 deletions

View File

@ -1878,7 +1878,8 @@ SELECT
NULL::timestamp with time zone AS next_start_at,
NULL::uuid AS agent_id,
NULL::workspace_agent_lifecycle_state AS lifecycle_state,
NULL::timestamp with time zone AS ready_at;
NULL::timestamp with time zone AS ready_at,
NULL::uuid AS current_preset_id;
CREATE TABLE workspace_proxies (
id uuid NOT NULL,
@ -2472,6 +2473,34 @@ CREATE OR REPLACE VIEW workspace_prebuilds AS
JOIN workspace_agents wa ON ((wa.resource_id = wr.id)))
WHERE (w.owner_id = 'c42fdf75-3097-471c-8c33-fb52454d81c0'::uuid)
GROUP BY w.id, wa.id
), current_presets AS (
SELECT w.id AS prebuild_id,
lps.template_version_preset_id
FROM (workspaces w
JOIN ( SELECT wb.id,
wb.created_at,
wb.updated_at,
wb.workspace_id,
wb.template_version_id,
wb.build_number,
wb.transition,
wb.initiator_id,
wb.provisioner_state,
wb.job_id,
wb.deadline,
wb.reason,
wb.daily_cost,
wb.max_deadline,
wb.template_version_preset_id
FROM (( SELECT tv.template_id,
wbmax_1.workspace_id,
max(wbmax_1.build_number) AS max_build_number
FROM (workspace_builds wbmax_1
JOIN template_versions tv ON ((tv.id = wbmax_1.template_version_id)))
WHERE (wbmax_1.template_version_preset_id IS NOT NULL)
GROUP BY tv.template_id, wbmax_1.workspace_id) wbmax
JOIN workspace_builds wb ON (((wb.workspace_id = wbmax.workspace_id) AND (wb.build_number = wbmax.max_build_number))))) lps ON ((lps.workspace_id = w.id)))
WHERE (w.owner_id = 'c42fdf75-3097-471c-8c33-fb52454d81c0'::uuid)
)
SELECT p.id,
p.created_at,
@ -2491,9 +2520,11 @@ CREATE OR REPLACE VIEW workspace_prebuilds AS
p.next_start_at,
a.agent_id,
a.lifecycle_state,
a.ready_at
FROM (all_prebuilds p
LEFT JOIN workspace_agents a ON ((a.workspace_id = p.id)));
a.ready_at,
cp.template_version_preset_id AS current_preset_id
FROM ((all_prebuilds p
LEFT JOIN workspace_agents a ON ((a.workspace_id = p.id)))
JOIN current_presets cp ON ((cp.prebuild_id = p.id)));
CREATE TRIGGER inhibit_enqueue_if_disabled BEFORE INSERT ON notification_messages FOR EACH ROW EXECUTE FUNCTION inhibit_enqueue_if_disabled();

View File

@ -30,19 +30,45 @@ FROM (SELECT tv.template_id,
);
CREATE VIEW workspace_prebuilds AS
WITH all_prebuilds AS (SELECT w.*
FROM workspaces w
WHERE w.owner_id = 'c42fdf75-3097-471c-8c33-fb52454d81c0'),
workspace_agents AS (SELECT w.id AS workspace_id, wa.id AS agent_id, wa.lifecycle_state, wa.ready_at
FROM workspaces w
INNER JOIN workspace_latest_build wlb ON wlb.workspace_id = w.id
INNER JOIN workspace_resources wr ON wr.job_id = wlb.job_id
INNER JOIN workspace_agents wa ON wa.resource_id = wr.id
WHERE w.owner_id = 'c42fdf75-3097-471c-8c33-fb52454d81c0'
GROUP BY w.id, wa.id)
SELECT p.*, a.agent_id, a.lifecycle_state, a.ready_at
WITH
-- All workspaces owned by the "prebuilds" user.
all_prebuilds AS (SELECT w.*
FROM workspaces w
WHERE w.owner_id = 'c42fdf75-3097-471c-8c33-fb52454d81c0'),
-- All workspace agents belonging to the workspaces owned by the "prebuilds" user.
workspace_agents AS (SELECT w.id AS workspace_id, wa.id AS agent_id, wa.lifecycle_state, wa.ready_at
FROM workspaces w
INNER JOIN workspace_latest_build wlb ON wlb.workspace_id = w.id
INNER JOIN workspace_resources wr ON wr.job_id = wlb.job_id
INNER JOIN workspace_agents wa ON wa.resource_id = wr.id
WHERE w.owner_id = 'c42fdf75-3097-471c-8c33-fb52454d81c0'
GROUP BY w.id, wa.id),
-- We can't rely on the template_version_preset_id in the workspace_builds table because this value is only set on the
-- initial workspace creation. Subsequent stop/start transitions will not have a value for template_version_preset_id,
-- and therefore we can't rely on (say) the latest build's chosen template_version_preset_id.
--
-- See https://github.com/coder/internal/issues/398
current_presets AS (SELECT w.id AS prebuild_id, lps.template_version_preset_id
FROM workspaces w
INNER JOIN (
-- The latest workspace build which had a preset explicitly selected
SELECT wb.*
FROM (SELECT tv.template_id,
wbmax.workspace_id,
MAX(wbmax.build_number) as max_build_number
FROM workspace_builds wbmax
JOIN template_versions tv ON (tv.id = wbmax.template_version_id)
WHERE wbmax.template_version_preset_id IS NOT NULL
GROUP BY tv.template_id, wbmax.workspace_id) wbmax
JOIN workspace_builds wb ON (
wb.workspace_id = wbmax.workspace_id
AND wb.build_number = wbmax.max_build_number
)) lps ON lps.workspace_id = w.id
WHERE w.owner_id = 'c42fdf75-3097-471c-8c33-fb52454d81c0')
SELECT p.*, a.agent_id, a.lifecycle_state, a.ready_at, cp.template_version_preset_id AS current_preset_id
FROM all_prebuilds p
LEFT JOIN workspace_agents a ON a.workspace_id = p.id;
LEFT JOIN workspace_agents a ON a.workspace_id = p.id
INNER JOIN current_presets cp ON cp.prebuild_id = p.id;
CREATE VIEW workspace_prebuild_builds AS
SELECT *

View File

@ -3489,6 +3489,7 @@ type WorkspacePrebuild struct {
AgentID uuid.NullUUID `db:"agent_id" json:"agent_id"`
LifecycleState NullWorkspaceAgentLifecycleState `db:"lifecycle_state" json:"lifecycle_state"`
ReadyAt sql.NullTime `db:"ready_at" json:"ready_at"`
CurrentPresetID uuid.NullUUID `db:"current_preset_id" json:"current_preset_id"`
}
type WorkspacePrebuildBuild struct {

View File

@ -5406,20 +5406,20 @@ func (q *sqlQuerier) GetParameterSchemasByJobID(ctx context.Context, jobID uuid.
const claimPrebuild = `-- name: ClaimPrebuild :one
UPDATE workspaces w
SET owner_id = $1::uuid,
name = $2::text,
updated_at = NOW()
name = $2::text,
updated_at = NOW()
WHERE w.id IN (SELECT p.id
FROM workspace_prebuilds p
INNER JOIN workspace_latest_build b ON b.workspace_id = p.id
INNER JOIN provisioner_jobs pj ON b.job_id = pj.id
INNER JOIN templates t ON p.template_id = t.id
WHERE (b.transition = 'start'::workspace_transition
AND pj.job_status IN ('succeeded'::provisioner_job_status))
AND b.template_version_id = t.active_version_id
AND b.template_version_preset_id = $3::uuid
AND p.lifecycle_state = 'ready'::workspace_agent_lifecycle_state
ORDER BY random()
LIMIT 1 FOR UPDATE OF p SKIP LOCKED)
FROM workspace_prebuilds p
INNER JOIN workspace_latest_build b ON b.workspace_id = p.id
INNER JOIN provisioner_jobs pj ON b.job_id = pj.id
INNER JOIN templates t ON p.template_id = t.id
WHERE (b.transition = 'start'::workspace_transition
AND pj.job_status IN ('succeeded'::provisioner_job_status))
AND b.template_version_id = t.active_version_id
AND b.template_version_preset_id = $3::uuid
AND p.lifecycle_state = 'ready'::workspace_agent_lifecycle_state
ORDER BY random()
LIMIT 1 FOR UPDATE OF p SKIP LOCKED)
RETURNING w.id, w.name
`
@ -5445,11 +5445,11 @@ func (q *sqlQuerier) ClaimPrebuild(ctx context.Context, arg ClaimPrebuildParams)
const getPrebuildsInProgress = `-- name: GetPrebuildsInProgress :many
SELECT wpb.template_version_id, wpb.transition, COUNT(wpb.transition) AS count
FROM workspace_latest_build wlb
INNER JOIN provisioner_jobs pj ON wlb.job_id = pj.id
INNER JOIN workspace_prebuild_builds wpb ON wpb.id = wlb.id
INNER JOIN provisioner_jobs pj ON wlb.job_id = pj.id
INNER JOIN workspace_prebuild_builds wpb ON wpb.id = wlb.id
WHERE pj.job_status NOT IN
('succeeded'::provisioner_job_status, 'canceled'::provisioner_job_status,
'failed'::provisioner_job_status)
('succeeded'::provisioner_job_status, 'canceled'::provisioner_job_status,
'failed'::provisioner_job_status)
GROUP BY wpb.template_version_id, wpb.transition
`
@ -5484,36 +5484,42 @@ func (q *sqlQuerier) GetPrebuildsInProgress(ctx context.Context) ([]GetPrebuilds
const getRunningPrebuilds = `-- name: GetRunningPrebuilds :many
SELECT p.id AS workspace_id,
p.template_id,
b.template_version_id,
tvp_curr.id AS current_preset_id,
tvp_desired.id AS desired_preset_id,
CASE
WHEN p.lifecycle_state = 'ready'::workspace_agent_lifecycle_state THEN TRUE
ELSE FALSE END AS eligible
p.name AS workspace_name,
p.template_id,
b.template_version_id,
tvp_curr.id AS current_preset_id,
tvp_desired.id AS desired_preset_id,
-- TODO: just because a prebuild is in a ready state doesn't mean it's eligible; if the prebuild is due to be
-- deleted to reconcile state then it MUST NOT be eligible for claiming. We'll need some kind of lock here.
CASE
WHEN p.lifecycle_state = 'ready'::workspace_agent_lifecycle_state THEN TRUE
ELSE FALSE END AS ready,
p.created_at
FROM workspace_prebuilds p
INNER JOIN workspace_latest_build b ON b.workspace_id = p.id
INNER JOIN provisioner_jobs pj ON b.job_id = pj.id
INNER JOIN templates t ON p.template_id = t.id
LEFT JOIN template_version_presets tvp_curr
ON tvp_curr.id = b.template_version_preset_id
LEFT JOIN template_version_presets tvp_desired
ON tvp_desired.template_version_id = t.active_version_id
INNER JOIN workspace_latest_build b ON b.workspace_id = p.id
INNER JOIN provisioner_jobs pj ON b.job_id = pj.id
INNER JOIN templates t ON p.template_id = t.id
LEFT JOIN template_version_presets tvp_curr
ON tvp_curr.id = p.current_preset_id -- See https://github.com/coder/internal/issues/398.
LEFT JOIN template_version_presets tvp_desired
ON tvp_desired.template_version_id = t.active_version_id
WHERE (b.transition = 'start'::workspace_transition
-- if a deletion job fails, the workspace will still be running
OR pj.job_status IN ('failed'::provisioner_job_status, 'canceled'::provisioner_job_status,
'unknown'::provisioner_job_status))
-- if a deletion job fails, the workspace will still be running
OR pj.job_status IN ('failed'::provisioner_job_status, 'canceled'::provisioner_job_status,
'unknown'::provisioner_job_status))
AND (tvp_curr.name = tvp_desired.name
OR tvp_desired.id IS NULL)
OR tvp_desired.id IS NULL)
`
type GetRunningPrebuildsRow struct {
WorkspaceID uuid.UUID `db:"workspace_id" json:"workspace_id"`
WorkspaceName string `db:"workspace_name" json:"workspace_name"`
TemplateID uuid.UUID `db:"template_id" json:"template_id"`
TemplateVersionID uuid.UUID `db:"template_version_id" json:"template_version_id"`
CurrentPresetID uuid.NullUUID `db:"current_preset_id" json:"current_preset_id"`
DesiredPresetID uuid.NullUUID `db:"desired_preset_id" json:"desired_preset_id"`
Eligible bool `db:"eligible" json:"eligible"`
Ready bool `db:"ready" json:"ready"`
CreatedAt time.Time `db:"created_at" json:"created_at"`
}
func (q *sqlQuerier) GetRunningPrebuilds(ctx context.Context) ([]GetRunningPrebuildsRow, error) {
@ -5527,11 +5533,13 @@ func (q *sqlQuerier) GetRunningPrebuilds(ctx context.Context) ([]GetRunningPrebu
var i GetRunningPrebuildsRow
if err := rows.Scan(
&i.WorkspaceID,
&i.WorkspaceName,
&i.TemplateID,
&i.TemplateVersionID,
&i.CurrentPresetID,
&i.DesiredPresetID,
&i.Eligible,
&i.Ready,
&i.CreatedAt,
); err != nil {
return nil, err
}
@ -5548,17 +5556,17 @@ func (q *sqlQuerier) GetRunningPrebuilds(ctx context.Context) ([]GetRunningPrebu
const getTemplatePresetsWithPrebuilds = `-- name: GetTemplatePresetsWithPrebuilds :many
SELECT t.id AS template_id,
tv.id AS template_version_id,
tv.id = t.active_version_id AS using_active_version,
tvpp.preset_id,
tvp.name,
tvpp.desired_instances AS desired_instances,
t.deleted,
t.deprecated != '' AS deprecated
tv.id AS template_version_id,
tv.id = t.active_version_id AS using_active_version,
tvpp.preset_id,
tvp.name,
tvpp.desired_instances AS desired_instances,
t.deleted,
t.deprecated != '' AS deprecated
FROM templates t
INNER JOIN template_versions tv ON tv.template_id = t.id
INNER JOIN template_version_presets tvp ON tvp.template_version_id = tv.id
INNER JOIN template_version_preset_prebuilds tvpp ON tvpp.preset_id = tvp.id
INNER JOIN template_versions tv ON tv.template_id = t.id
INNER JOIN template_version_presets tvp ON tvp.template_version_id = tv.id
INNER JOIN template_version_preset_prebuilds tvpp ON tvpp.preset_id = tvp.id
WHERE (t.id = $1::uuid OR $1 IS NULL)
`

View File

@ -1,70 +1,74 @@
-- name: GetRunningPrebuilds :many
SELECT p.id AS workspace_id,
p.template_id,
b.template_version_id,
tvp_curr.id AS current_preset_id,
tvp_desired.id AS desired_preset_id,
CASE
WHEN p.lifecycle_state = 'ready'::workspace_agent_lifecycle_state THEN TRUE
ELSE FALSE END AS eligible
p.name AS workspace_name,
p.template_id,
b.template_version_id,
tvp_curr.id AS current_preset_id,
tvp_desired.id AS desired_preset_id,
-- TODO: just because a prebuild is in a ready state doesn't mean it's eligible; if the prebuild is due to be
-- deleted to reconcile state then it MUST NOT be eligible for claiming. We'll need some kind of lock here.
CASE
WHEN p.lifecycle_state = 'ready'::workspace_agent_lifecycle_state THEN TRUE
ELSE FALSE END AS ready,
p.created_at
FROM workspace_prebuilds p
INNER JOIN workspace_latest_build b ON b.workspace_id = p.id
INNER JOIN provisioner_jobs pj ON b.job_id = pj.id
INNER JOIN templates t ON p.template_id = t.id
LEFT JOIN template_version_presets tvp_curr
ON tvp_curr.id = b.template_version_preset_id
LEFT JOIN template_version_presets tvp_desired
ON tvp_desired.template_version_id = t.active_version_id
INNER JOIN workspace_latest_build b ON b.workspace_id = p.id
INNER JOIN provisioner_jobs pj ON b.job_id = pj.id
INNER JOIN templates t ON p.template_id = t.id
LEFT JOIN template_version_presets tvp_curr
ON tvp_curr.id = p.current_preset_id -- See https://github.com/coder/internal/issues/398.
LEFT JOIN template_version_presets tvp_desired
ON tvp_desired.template_version_id = t.active_version_id
WHERE (b.transition = 'start'::workspace_transition
-- if a deletion job fails, the workspace will still be running
OR pj.job_status IN ('failed'::provisioner_job_status, 'canceled'::provisioner_job_status,
'unknown'::provisioner_job_status))
-- if a deletion job fails, the workspace will still be running
OR pj.job_status IN ('failed'::provisioner_job_status, 'canceled'::provisioner_job_status,
'unknown'::provisioner_job_status))
AND (tvp_curr.name = tvp_desired.name
OR tvp_desired.id IS NULL);
OR tvp_desired.id IS NULL);
-- name: GetTemplatePresetsWithPrebuilds :many
SELECT t.id AS template_id,
tv.id AS template_version_id,
tv.id = t.active_version_id AS using_active_version,
tvpp.preset_id,
tvp.name,
tvpp.desired_instances AS desired_instances,
t.deleted,
t.deprecated != '' AS deprecated
tv.id AS template_version_id,
tv.id = t.active_version_id AS using_active_version,
tvpp.preset_id,
tvp.name,
tvpp.desired_instances AS desired_instances,
t.deleted,
t.deprecated != '' AS deprecated
FROM templates t
INNER JOIN template_versions tv ON tv.template_id = t.id
INNER JOIN template_version_presets tvp ON tvp.template_version_id = tv.id
INNER JOIN template_version_preset_prebuilds tvpp ON tvpp.preset_id = tvp.id
INNER JOIN template_versions tv ON tv.template_id = t.id
INNER JOIN template_version_presets tvp ON tvp.template_version_id = tv.id
INNER JOIN template_version_preset_prebuilds tvpp ON tvpp.preset_id = tvp.id
WHERE (t.id = sqlc.narg('template_id')::uuid OR sqlc.narg('template_id') IS NULL);
-- name: GetPrebuildsInProgress :many
SELECT wpb.template_version_id, wpb.transition, COUNT(wpb.transition) AS count
FROM workspace_latest_build wlb
INNER JOIN provisioner_jobs pj ON wlb.job_id = pj.id
INNER JOIN workspace_prebuild_builds wpb ON wpb.id = wlb.id
INNER JOIN provisioner_jobs pj ON wlb.job_id = pj.id
INNER JOIN workspace_prebuild_builds wpb ON wpb.id = wlb.id
WHERE pj.job_status NOT IN
('succeeded'::provisioner_job_status, 'canceled'::provisioner_job_status,
'failed'::provisioner_job_status)
('succeeded'::provisioner_job_status, 'canceled'::provisioner_job_status,
'failed'::provisioner_job_status)
GROUP BY wpb.template_version_id, wpb.transition;
-- name: ClaimPrebuild :one
-- TODO: rewrite to use named CTE instead?
UPDATE workspaces w
SET owner_id = @new_user_id::uuid,
name = @new_name::text,
updated_at = NOW()
name = @new_name::text,
updated_at = NOW()
WHERE w.id IN (SELECT p.id
FROM workspace_prebuilds p
INNER JOIN workspace_latest_build b ON b.workspace_id = p.id
INNER JOIN provisioner_jobs pj ON b.job_id = pj.id
INNER JOIN templates t ON p.template_id = t.id
WHERE (b.transition = 'start'::workspace_transition
AND pj.job_status IN ('succeeded'::provisioner_job_status))
AND b.template_version_id = t.active_version_id
AND b.template_version_preset_id = @preset_id::uuid
AND p.lifecycle_state = 'ready'::workspace_agent_lifecycle_state
ORDER BY random()
LIMIT 1 FOR UPDATE OF p SKIP LOCKED)
FROM workspace_prebuilds p
INNER JOIN workspace_latest_build b ON b.workspace_id = p.id
INNER JOIN provisioner_jobs pj ON b.job_id = pj.id
INNER JOIN templates t ON p.template_id = t.id
WHERE (b.transition = 'start'::workspace_transition
AND pj.job_status IN ('succeeded'::provisioner_job_status))
AND b.template_version_id = t.active_version_id
AND b.template_version_preset_id = @preset_id::uuid
AND p.lifecycle_state = 'ready'::workspace_agent_lifecycle_state
ORDER BY random()
LIMIT 1 FOR UPDATE OF p SKIP LOCKED)
RETURNING w.id, w.name;
-- name: InsertPresetPrebuild :one