Skip to content

Commit 560cf84

Browse files
authored
fix: prevent activity bump for prebuilt workspaces (#19263)
## Description This PR ensures that activity-based deadline extensions ("activity bumping") are not applied to prebuilt workspaces. Prebuilds are managed by the reconciliation loop and must not have `deadline` or `max_deadline` values set or extended, as they are not part of the regular lifecycle executor path. ## Changes - Update `ActivityBumpWorkspace` SQL query to discard prebuilt workspaces - Update application layer to avoid calling activity bump logic on prebuilt workspaces Related with: * Issue: #18898 * PR: #19252
1 parent cd1faff commit 560cf84

File tree

4 files changed

+148
-28
lines changed

4 files changed

+148
-28
lines changed

coderd/database/queries.sql.go

Lines changed: 6 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/database/queries/activitybump.sql

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ WITH latest AS (
2222
-- be as if the workspace auto started at the given time and the
2323
-- original TTL was applied.
2424
--
25-
-- Sadly we can't define `activity_bump_interval` above since
25+
-- Sadly we can't define 'activity_bump_interval' above since
2626
-- it won't be available for this CASE statement, so we have to
2727
-- copy the cast twice.
2828
WHEN NOW() + (templates.activity_bump / 1000 / 1000 / 1000 || ' seconds')::interval > @next_autostart :: timestamptz
@@ -52,7 +52,11 @@ WITH latest AS (
5252
ON workspaces.id = workspace_builds.workspace_id
5353
JOIN templates
5454
ON templates.id = workspaces.template_id
55-
WHERE workspace_builds.workspace_id = @workspace_id::uuid
55+
WHERE
56+
workspace_builds.workspace_id = @workspace_id::uuid
57+
-- Prebuilt workspaces (identified by having the prebuilds system user as owner_id)
58+
-- are managed by the reconciliation loop and not subject to activity bumping
59+
AND workspaces.owner_id != 'c42fdf75-3097-471c-8c33-fb52454d81c0'::UUID
5660
ORDER BY workspace_builds.build_number DESC
5761
LIMIT 1
5862
)

coderd/workspacestats/reporter.go

Lines changed: 27 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -149,33 +149,36 @@ func (r *Reporter) ReportAgentStats(ctx context.Context, now time.Time, workspac
149149
return nil
150150
}
151151

152-
// check next autostart
153-
var nextAutostart time.Time
154-
if workspace.AutostartSchedule.String != "" {
155-
templateSchedule, err := (*(r.opts.TemplateScheduleStore.Load())).Get(ctx, r.opts.Database, workspace.TemplateID)
156-
// If the template schedule fails to load, just default to bumping
157-
// without the next transition and log it.
158-
switch {
159-
case err == nil:
160-
next, allowed := schedule.NextAutostart(now, workspace.AutostartSchedule.String, templateSchedule)
161-
if allowed {
162-
nextAutostart = next
152+
// Prebuilds are not subject to activity-based deadline bumps
153+
if !workspace.IsPrebuild() {
154+
// check next autostart
155+
var nextAutostart time.Time
156+
if workspace.AutostartSchedule.String != "" {
157+
templateSchedule, err := (*(r.opts.TemplateScheduleStore.Load())).Get(ctx, r.opts.Database, workspace.TemplateID)
158+
// If the template schedule fails to load, just default to bumping
159+
// without the next transition and log it.
160+
switch {
161+
case err == nil:
162+
next, allowed := schedule.NextAutostart(now, workspace.AutostartSchedule.String, templateSchedule)
163+
if allowed {
164+
nextAutostart = next
165+
}
166+
case database.IsQueryCanceledError(err):
167+
r.opts.Logger.Debug(ctx, "query canceled while loading template schedule",
168+
slog.F("workspace_id", workspace.ID),
169+
slog.F("template_id", workspace.TemplateID))
170+
default:
171+
r.opts.Logger.Error(ctx, "failed to load template schedule bumping activity, defaulting to bumping by 60min",
172+
slog.F("workspace_id", workspace.ID),
173+
slog.F("template_id", workspace.TemplateID),
174+
slog.Error(err),
175+
)
163176
}
164-
case database.IsQueryCanceledError(err):
165-
r.opts.Logger.Debug(ctx, "query canceled while loading template schedule",
166-
slog.F("workspace_id", workspace.ID),
167-
slog.F("template_id", workspace.TemplateID))
168-
default:
169-
r.opts.Logger.Error(ctx, "failed to load template schedule bumping activity, defaulting to bumping by 60min",
170-
slog.F("workspace_id", workspace.ID),
171-
slog.F("template_id", workspace.TemplateID),
172-
slog.Error(err),
173-
)
174177
}
175-
}
176178

177-
// bump workspace activity
178-
ActivityBumpWorkspace(ctx, r.opts.Logger.Named("activity_bump"), r.opts.Database, workspace.ID, nextAutostart)
179+
// bump workspace activity
180+
ActivityBumpWorkspace(ctx, r.opts.Logger.Named("activity_bump"), r.opts.Database, workspace.ID, nextAutostart)
181+
}
179182

180183
// bump workspace last_used_at
181184
r.opts.UsageTracker.Add(workspace.ID)

enterprise/coderd/workspaces_test.go

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import (
4242
agplschedule "github.com/coder/coder/v2/coderd/schedule"
4343
"github.com/coder/coder/v2/coderd/schedule/cron"
4444
"github.com/coder/coder/v2/coderd/util/ptr"
45+
"github.com/coder/coder/v2/coderd/workspacestats"
4546
"github.com/coder/coder/v2/codersdk"
4647
entaudit "github.com/coder/coder/v2/enterprise/audit"
4748
"github.com/coder/coder/v2/enterprise/audit/backends"
@@ -2767,6 +2768,114 @@ func TestPrebuildUpdateLifecycleParams(t *testing.T) {
27672768
}
27682769
}
27692770

2771+
func TestPrebuildActivityBump(t *testing.T) {
2772+
t.Parallel()
2773+
2774+
clock := quartz.NewMock(t)
2775+
clock.Set(dbtime.Now())
2776+
2777+
// Setup
2778+
log := testutil.Logger(t)
2779+
client, db, owner := coderdenttest.NewWithDatabase(t, &coderdenttest.Options{
2780+
Options: &coderdtest.Options{
2781+
IncludeProvisionerDaemon: true,
2782+
Clock: clock,
2783+
},
2784+
LicenseOptions: &coderdenttest.LicenseOptions{
2785+
Features: license.Features{
2786+
codersdk.FeatureWorkspacePrebuilds: 1,
2787+
},
2788+
},
2789+
})
2790+
2791+
// Given: a template and a template version with preset and a prebuilt workspace
2792+
presetID := uuid.New()
2793+
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, nil)
2794+
_ = coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
2795+
// Configure activity bump on the template
2796+
activityBump := time.Hour
2797+
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) {
2798+
ctr.ActivityBumpMillis = ptr.Ref[int64](activityBump.Milliseconds())
2799+
})
2800+
dbgen.Preset(t, db, database.InsertPresetParams{
2801+
ID: presetID,
2802+
TemplateVersionID: version.ID,
2803+
DesiredInstances: sql.NullInt32{Int32: 1, Valid: true},
2804+
})
2805+
// Given: a prebuild with an expired Deadline
2806+
deadline := clock.Now().Add(-30 * time.Minute)
2807+
wb := dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{
2808+
OwnerID: database.PrebuildsSystemUserID,
2809+
TemplateID: template.ID,
2810+
}).Seed(database.WorkspaceBuild{
2811+
TemplateVersionID: version.ID,
2812+
TemplateVersionPresetID: uuid.NullUUID{
2813+
UUID: presetID,
2814+
Valid: true,
2815+
},
2816+
Deadline: deadline,
2817+
}).WithAgent(func(agent []*proto.Agent) []*proto.Agent {
2818+
return agent
2819+
}).Do()
2820+
2821+
// Mark the prebuilt workspace's agent as ready so the prebuild can be claimed
2822+
// nolint:gocritic
2823+
ctx := dbauthz.AsSystemRestricted(testutil.Context(t, testutil.WaitLong))
2824+
agent, err := db.GetWorkspaceAgentAndLatestBuildByAuthToken(ctx, uuid.MustParse(wb.AgentToken))
2825+
require.NoError(t, err)
2826+
err = db.UpdateWorkspaceAgentLifecycleStateByID(ctx, database.UpdateWorkspaceAgentLifecycleStateByIDParams{
2827+
ID: agent.WorkspaceAgent.ID,
2828+
LifecycleState: database.WorkspaceAgentLifecycleStateReady,
2829+
})
2830+
require.NoError(t, err)
2831+
2832+
// Given: a prebuilt workspace with a Deadline and an empty MaxDeadline
2833+
prebuild := coderdtest.MustWorkspace(t, client, wb.Workspace.ID)
2834+
require.Equal(t, deadline.UTC(), prebuild.LatestBuild.Deadline.Time.UTC())
2835+
require.Zero(t, prebuild.LatestBuild.MaxDeadline)
2836+
2837+
// When: activity bump is applied to an unclaimed prebuild
2838+
workspacestats.ActivityBumpWorkspace(ctx, log, db, prebuild.ID, clock.Now().Add(10*time.Hour))
2839+
2840+
// Then: prebuild Deadline/MaxDeadline remain unchanged
2841+
prebuild = coderdtest.MustWorkspace(t, client, wb.Workspace.ID)
2842+
require.Equal(t, deadline.UTC(), prebuild.LatestBuild.Deadline.Time.UTC())
2843+
require.Zero(t, prebuild.LatestBuild.MaxDeadline)
2844+
2845+
// Given: the prebuilt workspace is claimed by a user
2846+
user, err := client.User(ctx, "testUser")
2847+
require.NoError(t, err)
2848+
claimedWorkspace, err := client.CreateUserWorkspace(ctx, user.ID.String(), codersdk.CreateWorkspaceRequest{
2849+
TemplateVersionID: version.ID,
2850+
TemplateVersionPresetID: presetID,
2851+
Name: coderdtest.RandomUsername(t),
2852+
})
2853+
require.NoError(t, err)
2854+
coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, claimedWorkspace.LatestBuild.ID)
2855+
workspace := coderdtest.MustWorkspace(t, client, claimedWorkspace.ID)
2856+
require.Equal(t, prebuild.ID, workspace.ID)
2857+
// Claimed workspaces have an empty Deadline and MaxDeadline
2858+
require.Zero(t, workspace.LatestBuild.Deadline)
2859+
require.Zero(t, workspace.LatestBuild.MaxDeadline)
2860+
2861+
// Given: the claimed workspace has an expired Deadline
2862+
err = db.UpdateWorkspaceBuildDeadlineByID(ctx, database.UpdateWorkspaceBuildDeadlineByIDParams{
2863+
ID: workspace.LatestBuild.ID,
2864+
Deadline: deadline,
2865+
UpdatedAt: clock.Now(),
2866+
})
2867+
require.NoError(t, err)
2868+
workspace = coderdtest.MustWorkspace(t, client, claimedWorkspace.ID)
2869+
2870+
// When: activity bump is applied to a claimed prebuild
2871+
workspacestats.ActivityBumpWorkspace(ctx, log, db, workspace.ID, clock.Now().Add(10*time.Hour))
2872+
2873+
// Then: Deadline is extended by the activity bump, MaxDeadline remains unset
2874+
workspace = coderdtest.MustWorkspace(t, client, claimedWorkspace.ID)
2875+
require.WithinDuration(t, clock.Now().Add(activityBump).UTC(), workspace.LatestBuild.Deadline.Time.UTC(), testutil.WaitMedium)
2876+
require.Zero(t, workspace.LatestBuild.MaxDeadline)
2877+
}
2878+
27702879
// TestWorkspaceTemplateParamsChange tests a workspace with a parameter that
27712880
// validation changes on apply. The params used in create workspace are invalid
27722881
// according to the static params on import.

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy