Implement strict prebuilds eligibility

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

Signed-off-by: Danny Kopping <danny@coder.com>
This commit is contained in:
Danny Kopping
2025-02-18 09:39:04 +00:00
parent e9fdd86c45
commit 9dd9fedc12
6 changed files with 159 additions and 75 deletions

134
coderd/database/dump.sql generated
View File

@ -1841,46 +1841,27 @@ CREATE VIEW workspace_prebuild_builds AS
FROM workspace_builds
WHERE (workspace_builds.initiator_id = 'c42fdf75-3097-471c-8c33-fb52454d81c0'::uuid);
CREATE TABLE workspaces (
id uuid NOT NULL,
created_at timestamp with time zone NOT NULL,
updated_at timestamp with time zone NOT NULL,
owner_id uuid NOT NULL,
organization_id uuid NOT NULL,
template_id uuid NOT NULL,
deleted boolean DEFAULT false NOT NULL,
name character varying(64) NOT NULL,
autostart_schedule text,
ttl bigint,
last_used_at timestamp with time zone DEFAULT '0001-01-01 00:00:00+00'::timestamp with time zone NOT NULL,
dormant_at timestamp with time zone,
deleting_at timestamp with time zone,
automatic_updates automatic_updates DEFAULT 'never'::automatic_updates NOT NULL,
favorite boolean DEFAULT false NOT NULL,
next_start_at timestamp with time zone
);
COMMENT ON COLUMN workspaces.favorite IS 'Favorite is true if the workspace owner has favorited the workspace.';
CREATE VIEW workspace_prebuilds AS
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.next_start_at
FROM workspaces
WHERE (workspaces.owner_id = 'c42fdf75-3097-471c-8c33-fb52454d81c0'::uuid);
SELECT
NULL::uuid AS id,
NULL::timestamp with time zone AS created_at,
NULL::timestamp with time zone AS updated_at,
NULL::uuid AS owner_id,
NULL::uuid AS organization_id,
NULL::uuid AS template_id,
NULL::boolean AS deleted,
NULL::character varying(64) AS name,
NULL::text AS autostart_schedule,
NULL::bigint AS ttl,
NULL::timestamp with time zone AS last_used_at,
NULL::timestamp with time zone AS dormant_at,
NULL::timestamp with time zone AS deleting_at,
NULL::automatic_updates AS automatic_updates,
NULL::boolean AS favorite,
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;
CREATE TABLE workspace_proxies (
id uuid NOT NULL,
@ -1952,6 +1933,27 @@ CREATE TABLE workspace_resources (
module_path text
);
CREATE TABLE workspaces (
id uuid NOT NULL,
created_at timestamp with time zone NOT NULL,
updated_at timestamp with time zone NOT NULL,
owner_id uuid NOT NULL,
organization_id uuid NOT NULL,
template_id uuid NOT NULL,
deleted boolean DEFAULT false NOT NULL,
name character varying(64) NOT NULL,
autostart_schedule text,
ttl bigint,
last_used_at timestamp with time zone DEFAULT '0001-01-01 00:00:00+00'::timestamp with time zone NOT NULL,
dormant_at timestamp with time zone,
deleting_at timestamp with time zone,
automatic_updates automatic_updates DEFAULT 'never'::automatic_updates NOT NULL,
favorite boolean DEFAULT false NOT NULL,
next_start_at timestamp with time zone
);
COMMENT ON COLUMN workspaces.favorite IS 'Favorite is true if the workspace owner has favorited the workspace.';
CREATE VIEW workspaces_expanded AS
SELECT workspaces.id,
workspaces.created_at,
@ -2422,6 +2424,60 @@ CREATE OR REPLACE VIEW provisioner_job_stats AS
LEFT JOIN provisioner_job_timings pjt ON ((pjt.job_id = pj.id)))
GROUP BY pj.id, wb.workspace_id;
CREATE OR REPLACE VIEW workspace_prebuilds AS
WITH all_prebuilds AS (
SELECT w.id,
w.created_at,
w.updated_at,
w.owner_id,
w.organization_id,
w.template_id,
w.deleted,
w.name,
w.autostart_schedule,
w.ttl,
w.last_used_at,
w.dormant_at,
w.deleting_at,
w.automatic_updates,
w.favorite,
w.next_start_at
FROM workspaces w
WHERE (w.owner_id = 'c42fdf75-3097-471c-8c33-fb52454d81c0'::uuid)
), workspace_agents AS (
SELECT w.id AS workspace_id,
wa.id AS agent_id,
wa.lifecycle_state,
wa.ready_at
FROM (((workspaces w
JOIN workspace_latest_build wlb ON ((wlb.workspace_id = w.id)))
JOIN workspace_resources wr ON ((wr.job_id = wlb.job_id)))
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
)
SELECT p.id,
p.created_at,
p.updated_at,
p.owner_id,
p.organization_id,
p.template_id,
p.deleted,
p.name,
p.autostart_schedule,
p.ttl,
p.last_used_at,
p.dormant_at,
p.deleting_at,
p.automatic_updates,
p.favorite,
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)));
CREATE TRIGGER inhibit_enqueue_if_disabled BEFORE INSERT ON notification_messages FOR EACH ROW EXECUTE FUNCTION inhibit_enqueue_if_disabled();
CREATE TRIGGER remove_organization_member_custom_role BEFORE DELETE ON custom_roles FOR EACH ROW EXECUTE FUNCTION remove_organization_member_role();

View File

@ -4,27 +4,18 @@ VALUES ('c42fdf75-3097-471c-8c33-fb52454d81c0', 'prebuilds@system', 'prebuilds',
'active', '{}', 'none', true);
-- TODO: do we *want* to use the default org here? how do we handle multi-org?
WITH default_org AS (
SELECT id FROM organizations WHERE is_default = true LIMIT 1
)
INSERT INTO organization_members (organization_id, user_id, created_at, updated_at)
SELECT
default_org.id,
'c42fdf75-3097-471c-8c33-fb52454d81c0',
NOW(),
NOW()
WITH default_org AS (SELECT id
FROM organizations
WHERE is_default = true
LIMIT 1)
INSERT
INTO organization_members (organization_id, user_id, created_at, updated_at)
SELECT default_org.id,
'c42fdf75-3097-471c-8c33-fb52454d81c0',
NOW(),
NOW()
FROM default_org;
CREATE VIEW workspace_prebuilds AS
SELECT *
FROM workspaces
WHERE owner_id = 'c42fdf75-3097-471c-8c33-fb52454d81c0';
CREATE VIEW workspace_prebuild_builds AS
SELECT *
FROM workspace_builds
WHERE initiator_id = 'c42fdf75-3097-471c-8c33-fb52454d81c0';
CREATE VIEW workspace_latest_build AS
SELECT wb.*
FROM (SELECT tv.template_id,
@ -38,3 +29,22 @@ FROM (SELECT tv.template_id,
AND wb.build_number = wbmax.max_build_number
);
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
FROM all_prebuilds p
LEFT JOIN workspace_agents a ON a.workspace_id = p.id;
CREATE VIEW workspace_prebuild_builds AS
SELECT *
FROM workspace_builds
WHERE initiator_id = 'c42fdf75-3097-471c-8c33-fb52454d81c0';

View File

@ -3388,22 +3388,25 @@ type WorkspaceModule struct {
}
type WorkspacePrebuild struct {
ID uuid.UUID `db:"id" json:"id"`
CreatedAt time.Time `db:"created_at" json:"created_at"`
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
OwnerID uuid.UUID `db:"owner_id" json:"owner_id"`
OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"`
TemplateID uuid.UUID `db:"template_id" json:"template_id"`
Deleted bool `db:"deleted" json:"deleted"`
Name string `db:"name" json:"name"`
AutostartSchedule sql.NullString `db:"autostart_schedule" json:"autostart_schedule"`
Ttl sql.NullInt64 `db:"ttl" json:"ttl"`
LastUsedAt time.Time `db:"last_used_at" json:"last_used_at"`
DormantAt sql.NullTime `db:"dormant_at" json:"dormant_at"`
DeletingAt sql.NullTime `db:"deleting_at" json:"deleting_at"`
AutomaticUpdates AutomaticUpdates `db:"automatic_updates" json:"automatic_updates"`
Favorite bool `db:"favorite" json:"favorite"`
NextStartAt sql.NullTime `db:"next_start_at" json:"next_start_at"`
ID uuid.UUID `db:"id" json:"id"`
CreatedAt time.Time `db:"created_at" json:"created_at"`
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
OwnerID uuid.UUID `db:"owner_id" json:"owner_id"`
OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"`
TemplateID uuid.UUID `db:"template_id" json:"template_id"`
Deleted bool `db:"deleted" json:"deleted"`
Name string `db:"name" json:"name"`
AutostartSchedule sql.NullString `db:"autostart_schedule" json:"autostart_schedule"`
Ttl sql.NullInt64 `db:"ttl" json:"ttl"`
LastUsedAt time.Time `db:"last_used_at" json:"last_used_at"`
DormantAt sql.NullTime `db:"dormant_at" json:"dormant_at"`
DeletingAt sql.NullTime `db:"deleting_at" json:"deleting_at"`
AutomaticUpdates AutomaticUpdates `db:"automatic_updates" json:"automatic_updates"`
Favorite bool `db:"favorite" json:"favorite"`
NextStartAt sql.NullTime `db:"next_start_at" json:"next_start_at"`
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"`
}
type WorkspacePrebuildBuild struct {

View File

@ -5409,6 +5409,7 @@ WHERE w.id IN (SELECT p.id
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
@ -5441,6 +5442,9 @@ WITH
tvp_curr.id AS current_preset_id,
tvp_desired.id AS desired_preset_id,
COUNT(*) AS count,
SUM(CASE
WHEN p.lifecycle_state = 'ready'::workspace_agent_lifecycle_state THEN 1
ELSE 0 END) AS eligible,
STRING_AGG(p.id::text, ',') AS ids
FROM workspace_prebuilds p
INNER JOIN workspace_latest_build b ON b.workspace_id = p.id
@ -5491,6 +5495,8 @@ SELECT t.template_id,
ELSE '' END)::text AS running_prebuild_ids,
COALESCE(MAX(CASE WHEN t.using_active_version THEN p.count ELSE 0 END),
0)::int AS actual, -- running prebuilds for active version
COALESCE(MAX(CASE WHEN t.using_active_version THEN p.eligible ELSE 0 END),
0)::int AS eligible, -- prebuilds which can be claimed
MAX(CASE WHEN t.using_active_version THEN t.desired_instances ELSE 0 END)::int AS desired, -- we only care about the active version's desired instances
COALESCE(MAX(CASE
WHEN p.template_version_id = t.template_version_id AND
@ -5534,6 +5540,7 @@ type GetTemplatePrebuildStateRow struct {
IsActive bool `db:"is_active" json:"is_active"`
RunningPrebuildIds string `db:"running_prebuild_ids" json:"running_prebuild_ids"`
Actual int32 `db:"actual" json:"actual"`
Eligible int32 `db:"eligible" json:"eligible"`
Desired int32 `db:"desired" json:"desired"`
Outdated int32 `db:"outdated" json:"outdated"`
Extraneous int32 `db:"extraneous" json:"extraneous"`
@ -5560,6 +5567,7 @@ func (q *sqlQuerier) GetTemplatePrebuildState(ctx context.Context, templateID uu
&i.IsActive,
&i.RunningPrebuildIds,
&i.Actual,
&i.Eligible,
&i.Desired,
&i.Outdated,
&i.Extraneous,

View File

@ -6,6 +6,9 @@ WITH
tvp_curr.id AS current_preset_id,
tvp_desired.id AS desired_preset_id,
COUNT(*) AS count,
SUM(CASE
WHEN p.lifecycle_state = 'ready'::workspace_agent_lifecycle_state THEN 1
ELSE 0 END) AS eligible,
STRING_AGG(p.id::text, ',') AS ids
FROM workspace_prebuilds p
INNER JOIN workspace_latest_build b ON b.workspace_id = p.id
@ -56,6 +59,8 @@ SELECT t.template_id,
ELSE '' END)::text AS running_prebuild_ids,
COALESCE(MAX(CASE WHEN t.using_active_version THEN p.count ELSE 0 END),
0)::int AS actual, -- running prebuilds for active version
COALESCE(MAX(CASE WHEN t.using_active_version THEN p.eligible ELSE 0 END),
0)::int AS eligible, -- prebuilds which can be claimed
MAX(CASE WHEN t.using_active_version THEN t.desired_instances ELSE 0 END)::int AS desired, -- we only care about the active version's desired instances
COALESCE(MAX(CASE
WHEN p.template_version_id = t.template_version_id AND
@ -106,6 +111,7 @@ WHERE w.id IN (SELECT p.id
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;