Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitd79a779

Browse files
authored
fix: exclude prebuilt workspaces from template-level lifecycle updates (#19265)
## DescriptionThis PR ensures that lifecycle-related changes made via templateschedule updates do **not affect prebuilt workspaces**. Since prebuildsare managed by the reconciliation loop and do not participate in theregular lifecycle executor flow, they must be excluded from any updatestriggered by template configuration changes.This includes changes to TTL, dormant-deletion scheduling, deadline andautostart scheduling.## Changes- Updated SQL query `UpdateWorkspacesTTLByTemplateID` to excludeprebuilt workspaces- Updated SQL query `UpdateWorkspacesDormantDeletingAtByTemplateID` toexclude prebuilt workspaces- Updated application-layer logic to skip any updates to lifecycleparameters if a workspace is a prebuild- Preserved all existing update behavior for regular user workspacesThis change guarantees that only lifecycle-managed workspaces areaffected when template-level configurations are modified, preservingstrict boundaries between prebuild and user workspace lifecycles.Related with: * Issue:#18898* PR:#19252
1 parente879526 commitd79a779

File tree

4 files changed

+282
-8
lines changed

4 files changed

+282
-8
lines changed

‎coderd/database/queries.sql.go‎

Lines changed: 10 additions & 3 deletions
Some generated files are not rendered by default. Learn more aboutcustomizing how changed files appear on GitHub.

‎coderd/database/queries/workspaces.sql‎

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -579,7 +579,11 @@ UPDATE
579579
SET
580580
ttl= $2
581581
WHERE
582-
template_id= $1;
582+
template_id= $1
583+
-- Prebuilt workspaces (identified by having the prebuilds system user as owner_id)
584+
-- should not have their TTL updated, as they are handled by the prebuilds
585+
-- reconciliation loop.
586+
ANDworkspaces.owner_id!='c42fdf75-3097-471c-8c33-fb52454d81c0'::UUID;
583587

584588
-- name: UpdateWorkspaceLastUsedAt :exec
585589
UPDATE
@@ -824,14 +828,17 @@ UPDATE workspaces
824828
SET
825829
deleting_at= CASE
826830
WHEN @time_til_dormant_autodelete_ms::bigint=0 THENNULL
827-
WHEN @dormant_at::timestamptz>'0001-01-01 00:00:00+00'::timestamptz THEN(@dormant_at::timestamptz)+ interval'1 milliseconds'* @time_til_dormant_autodelete_ms::bigint
831+
WHEN @dormant_at::timestamptz>'0001-01-01 00:00:00+00'::timestamptz THEN (@dormant_at::timestamptz)+ interval'1 milliseconds'* @time_til_dormant_autodelete_ms::bigint
828832
ELSE dormant_at+ interval'1 milliseconds'* @time_til_dormant_autodelete_ms::bigint
829833
END,
830834
dormant_at= CASE WHEN @dormant_at::timestamptz>'0001-01-01 00:00:00+00'::timestamptz THEN @dormant_at::timestamptz ELSE dormant_at END
831835
WHERE
832836
template_id= @template_id
833-
AND
834-
dormant_atIS NOT NULL
837+
AND dormant_atIS NOT NULL
838+
-- Prebuilt workspaces (identified by having the prebuilds system user as owner_id)
839+
-- should not have their dormant or deleting at set, as these are handled by the
840+
-- prebuilds reconciliation loop.
841+
ANDworkspaces.owner_id!='c42fdf75-3097-471c-8c33-fb52454d81c0'::UUID
835842
RETURNING*;
836843

837844
-- name: UpdateTemplateWorkspacesLastUsedAt :exec

‎enterprise/coderd/schedule/template.go‎

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,10 @@ func (s *EnterpriseTemplateScheduleStore) Set(ctx context.Context, db database.S
242242
nextStartAts:= []time.Time{}
243243

244244
for_,workspace:=rangeworkspaces {
245+
// Skip prebuilt workspaces
246+
ifworkspace.IsPrebuild() {
247+
continue
248+
}
245249
nextStartAt:= time.Time{}
246250
ifworkspace.AutostartSchedule.Valid {
247251
next,err:=agpl.NextAllowedAutostart(s.now(),workspace.AutostartSchedule.String,templateSchedule)
@@ -254,7 +258,7 @@ func (s *EnterpriseTemplateScheduleStore) Set(ctx context.Context, db database.S
254258
nextStartAts=append(nextStartAts,nextStartAt)
255259
}
256260

257-
//nolint:gocritic // We need to be able to update information aboutall workspaces.
261+
//nolint:gocritic // We need to be able to update information aboutregular user workspaces.
258262
iferr:=db.BatchUpdateWorkspaceNextStartAt(dbauthz.AsSystemRestricted(ctx), database.BatchUpdateWorkspaceNextStartAtParams{
259263
IDs:workspaceIDs,
260264
NextStartAts:nextStartAts,
@@ -334,6 +338,11 @@ func (s *EnterpriseTemplateScheduleStore) updateWorkspaceBuild(ctx context.Conte
334338
returnxerrors.Errorf("get workspace %q: %w",build.WorkspaceID,err)
335339
}
336340

341+
// Skip lifecycle updates for prebuilt workspaces
342+
ifworkspace.IsPrebuild() {
343+
returnnil
344+
}
345+
337346
job,err:=db.GetProvisionerJobByID(ctx,build.JobID)
338347
iferr!=nil {
339348
returnxerrors.Errorf("get provisioner job %q: %w",build.JobID,err)

‎enterprise/coderd/schedule/template_test.go‎

Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package schedule_test
22

33
import (
4+
"context"
45
"database/sql"
56
"encoding/json"
67
"fmt"
@@ -17,14 +18,18 @@ import (
1718

1819
"github.com/coder/coder/v2/coderd/database"
1920
"github.com/coder/coder/v2/coderd/database/dbauthz"
21+
"github.com/coder/coder/v2/coderd/database/dbfake"
2022
"github.com/coder/coder/v2/coderd/database/dbgen"
2123
"github.com/coder/coder/v2/coderd/database/dbtestutil"
2224
"github.com/coder/coder/v2/coderd/database/dbtime"
2325
"github.com/coder/coder/v2/coderd/notifications"
2426
"github.com/coder/coder/v2/coderd/notifications/notificationstest"
2527
agplschedule"github.com/coder/coder/v2/coderd/schedule"
28+
"github.com/coder/coder/v2/coderd/schedule/cron"
29+
"github.com/coder/coder/v2/codersdk"
2630
"github.com/coder/coder/v2/cryptorand"
2731
"github.com/coder/coder/v2/enterprise/coderd/schedule"
32+
"github.com/coder/coder/v2/provisionersdk/proto"
2833
"github.com/coder/coder/v2/testutil"
2934
"github.com/coder/quartz"
3035
)
@@ -979,6 +984,252 @@ func TestTemplateTTL(t *testing.T) {
979984
})
980985
}
981986

987+
funcTestTemplateUpdatePrebuilds(t*testing.T) {
988+
t.Parallel()
989+
990+
// Dormant auto-delete configured to 10 hours
991+
dormantAutoDelete:=10*time.Hour
992+
993+
// TTL configured to 8 hours
994+
ttl:=8*time.Hour
995+
996+
// Autostop configuration set to everyday at midnight
997+
autostopWeekdays,err:=codersdk.WeekdaysToBitmap(codersdk.AllDaysOfWeek)
998+
require.NoError(t,err)
999+
1000+
// Autostart configuration set to everyday at midnight
1001+
autostartSchedule,err:=cron.Weekly("CRON_TZ=UTC 0 0 * * *")
1002+
require.NoError(t,err)
1003+
autostartWeekdays,err:=codersdk.WeekdaysToBitmap(codersdk.AllDaysOfWeek)
1004+
require.NoError(t,err)
1005+
1006+
cases:= []struct {
1007+
namestring
1008+
templateSchedule agplschedule.TemplateScheduleOptions
1009+
workspaceUpdatefunc(*testing.T, context.Context, database.Store, time.Time, database.ClaimPrebuiltWorkspaceRow)
1010+
assertWorkspacefunc(*testing.T, context.Context, database.Store, time.Time,bool, database.Workspace)
1011+
}{
1012+
{
1013+
name:"TemplateDormantAutoDeleteUpdatePrebuildAfterClaim",
1014+
templateSchedule: agplschedule.TemplateScheduleOptions{
1015+
// Template level TimeTilDormantAutodelete set to 10 hours
1016+
TimeTilDormantAutoDelete:dormantAutoDelete,
1017+
},
1018+
workspaceUpdate:func(t*testing.T,ctx context.Context,db database.Store,now time.Time,
1019+
workspace database.ClaimPrebuiltWorkspaceRow,
1020+
) {
1021+
// When: the workspace is marked dormant
1022+
dormantWorkspace,err:=db.UpdateWorkspaceDormantDeletingAt(ctx, database.UpdateWorkspaceDormantDeletingAtParams{
1023+
ID:workspace.ID,
1024+
DormantAt: sql.NullTime{
1025+
Time:now,
1026+
Valid:true,
1027+
},
1028+
})
1029+
require.NoError(t,err)
1030+
require.NotNil(t,dormantWorkspace.DormantAt)
1031+
},
1032+
assertWorkspace:func(t*testing.T,ctx context.Context,db database.Store,now time.Time,
1033+
isPrebuildbool,workspace database.Workspace,
1034+
) {
1035+
ifisPrebuild {
1036+
// The unclaimed prebuild should have an empty DormantAt and DeletingAt
1037+
require.True(t,workspace.DormantAt.Time.IsZero())
1038+
require.True(t,workspace.DeletingAt.Time.IsZero())
1039+
}else {
1040+
// The claimed workspace should have its DormantAt and DeletingAt updated
1041+
require.False(t,workspace.DormantAt.Time.IsZero())
1042+
require.False(t,workspace.DeletingAt.Time.IsZero())
1043+
require.WithinDuration(t,now.UTC(),workspace.DormantAt.Time.UTC(),time.Second)
1044+
require.WithinDuration(t,now.Add(dormantAutoDelete).UTC(),workspace.DeletingAt.Time.UTC(),time.Second)
1045+
}
1046+
},
1047+
},
1048+
{
1049+
name:"TemplateTTLUpdatePrebuildAfterClaim",
1050+
templateSchedule: agplschedule.TemplateScheduleOptions{
1051+
// Template level TTL can only be set if autostop is disabled for users
1052+
DefaultTTL:ttl,
1053+
UserAutostopEnabled:false,
1054+
},
1055+
workspaceUpdate:func(t*testing.T,ctx context.Context,db database.Store,now time.Time,
1056+
workspace database.ClaimPrebuiltWorkspaceRow) {
1057+
},
1058+
assertWorkspace:func(t*testing.T,ctx context.Context,db database.Store,now time.Time,
1059+
isPrebuildbool,workspace database.Workspace,
1060+
) {
1061+
ifisPrebuild {
1062+
// The unclaimed prebuild should have an empty TTL
1063+
require.Equal(t, sql.NullInt64{},workspace.Ttl)
1064+
}else {
1065+
// The claimed workspace should have its TTL updated
1066+
require.Equal(t, sql.NullInt64{Int64:int64(ttl),Valid:true},workspace.Ttl)
1067+
}
1068+
},
1069+
},
1070+
{
1071+
name:"TemplateAutostopUpdatePrebuildAfterClaim",
1072+
templateSchedule: agplschedule.TemplateScheduleOptions{
1073+
// Template level Autostop set for everyday
1074+
AutostopRequirement: agplschedule.TemplateAutostopRequirement{
1075+
DaysOfWeek:autostopWeekdays,
1076+
Weeks:0,
1077+
},
1078+
},
1079+
workspaceUpdate:func(t*testing.T,ctx context.Context,db database.Store,now time.Time,
1080+
workspace database.ClaimPrebuiltWorkspaceRow) {
1081+
},
1082+
assertWorkspace:func(t*testing.T,ctx context.Context,db database.Store,now time.Time,isPrebuildbool,workspace database.Workspace) {
1083+
ifisPrebuild {
1084+
// The unclaimed prebuild should have an empty MaxDeadline
1085+
prebuildBuild,err:=db.GetLatestWorkspaceBuildByWorkspaceID(ctx,workspace.ID)
1086+
require.NoError(t,err)
1087+
require.True(t,prebuildBuild.MaxDeadline.IsZero())
1088+
}else {
1089+
// The claimed workspace should have its MaxDeadline updated
1090+
workspaceBuild,err:=db.GetLatestWorkspaceBuildByWorkspaceID(ctx,workspace.ID)
1091+
require.NoError(t,err)
1092+
require.False(t,workspaceBuild.MaxDeadline.IsZero())
1093+
}
1094+
},
1095+
},
1096+
{
1097+
name:"TemplateAutostartUpdatePrebuildAfterClaim",
1098+
templateSchedule: agplschedule.TemplateScheduleOptions{
1099+
// Template level Autostart set for everyday
1100+
UserAutostartEnabled:true,
1101+
AutostartRequirement: agplschedule.TemplateAutostartRequirement{
1102+
DaysOfWeek:autostartWeekdays,
1103+
},
1104+
},
1105+
workspaceUpdate:func(t*testing.T,ctx context.Context,db database.Store,now time.Time,workspace database.ClaimPrebuiltWorkspaceRow) {
1106+
// To compute NextStartAt, the workspace must have a valid autostart schedule
1107+
err=db.UpdateWorkspaceAutostart(ctx, database.UpdateWorkspaceAutostartParams{
1108+
ID:workspace.ID,
1109+
AutostartSchedule: sql.NullString{
1110+
String:autostartSchedule.String(),
1111+
Valid:true,
1112+
},
1113+
})
1114+
require.NoError(t,err)
1115+
},
1116+
assertWorkspace:func(t*testing.T,ctx context.Context,db database.Store,now time.Time,isPrebuildbool,workspace database.Workspace) {
1117+
ifisPrebuild {
1118+
// The unclaimed prebuild should have an empty NextStartAt
1119+
require.True(t,workspace.NextStartAt.Time.IsZero())
1120+
}else {
1121+
// The claimed workspace should have its NextStartAt updated
1122+
require.False(t,workspace.NextStartAt.Time.IsZero())
1123+
}
1124+
},
1125+
},
1126+
}
1127+
1128+
for_,tc:=rangecases {
1129+
tc:=tc
1130+
t.Run(tc.name,func(t*testing.T) {
1131+
t.Parallel()
1132+
1133+
clock:=quartz.NewMock(t)
1134+
clock.Set(dbtime.Now())
1135+
1136+
// Setup
1137+
var (
1138+
logger=slogtest.Make(t,&slogtest.Options{IgnoreErrors:true}).Leveled(slog.LevelDebug)
1139+
db,_=dbtestutil.NewDB(t,dbtestutil.WithDumpOnFailure())
1140+
ctx=testutil.Context(t,testutil.WaitLong)
1141+
user=dbgen.User(t,db, database.User{})
1142+
)
1143+
1144+
// Setup the template schedule store
1145+
notifyEnq:=notifications.NewNoopEnqueuer()
1146+
constuserQuietHoursSchedule="CRON_TZ=UTC 0 0 * * *"// midnight UTC
1147+
userQuietHoursStore,err:=schedule.NewEnterpriseUserQuietHoursScheduleStore(userQuietHoursSchedule,true)
1148+
require.NoError(t,err)
1149+
userQuietHoursStorePtr:=&atomic.Pointer[agplschedule.UserQuietHoursScheduleStore]{}
1150+
userQuietHoursStorePtr.Store(&userQuietHoursStore)
1151+
templateScheduleStore:=schedule.NewEnterpriseTemplateScheduleStore(userQuietHoursStorePtr,notifyEnq,logger,clock)
1152+
1153+
// Given: a template and a template version with preset and a prebuilt workspace
1154+
presetID:=uuid.New()
1155+
org:=dbfake.Organization(t,db).Do()
1156+
tv:=dbfake.TemplateVersion(t,db).Seed(database.TemplateVersion{
1157+
OrganizationID:org.Org.ID,
1158+
CreatedBy:user.ID,
1159+
}).Preset(database.TemplateVersionPreset{
1160+
ID:presetID,
1161+
DesiredInstances: sql.NullInt32{
1162+
Int32:1,
1163+
Valid:true,
1164+
},
1165+
}).Do()
1166+
workspaceBuild:=dbfake.WorkspaceBuild(t,db, database.WorkspaceTable{
1167+
OwnerID:database.PrebuildsSystemUserID,
1168+
TemplateID:tv.Template.ID,
1169+
OrganizationID:tv.Template.OrganizationID,
1170+
}).Seed(database.WorkspaceBuild{
1171+
TemplateVersionID:tv.TemplateVersion.ID,
1172+
TemplateVersionPresetID: uuid.NullUUID{
1173+
UUID:presetID,
1174+
Valid:true,
1175+
},
1176+
}).WithAgent(func(agent []*proto.Agent) []*proto.Agent {
1177+
returnagent
1178+
}).Do()
1179+
1180+
// Mark the prebuilt workspace's agent as ready so the prebuild can be claimed
1181+
// nolint:gocritic
1182+
agentCtx:=dbauthz.AsSystemRestricted(testutil.Context(t,testutil.WaitLong))
1183+
agent,err:=db.GetWorkspaceAgentAndLatestBuildByAuthToken(agentCtx,uuid.MustParse(workspaceBuild.AgentToken))
1184+
require.NoError(t,err)
1185+
err=db.UpdateWorkspaceAgentLifecycleStateByID(agentCtx, database.UpdateWorkspaceAgentLifecycleStateByIDParams{
1186+
ID:agent.WorkspaceAgent.ID,
1187+
LifecycleState:database.WorkspaceAgentLifecycleStateReady,
1188+
})
1189+
require.NoError(t,err)
1190+
1191+
// Given: a prebuilt workspace
1192+
prebuild,err:=db.GetWorkspaceByID(ctx,workspaceBuild.Workspace.ID)
1193+
require.NoError(t,err)
1194+
tc.assertWorkspace(t,ctx,db,clock.Now(),true,prebuild)
1195+
1196+
// When: the template schedule is updated
1197+
_,err=templateScheduleStore.Set(ctx,db,tv.Template,tc.templateSchedule)
1198+
require.NoError(t,err)
1199+
1200+
// Then: lifecycle parameters must remain unset while the prebuild is unclaimed
1201+
prebuild,err=db.GetWorkspaceByID(ctx,workspaceBuild.Workspace.ID)
1202+
require.NoError(t,err)
1203+
tc.assertWorkspace(t,ctx,db,clock.Now(),true,prebuild)
1204+
1205+
// Given: the prebuilt workspace is claimed by a user
1206+
claimedWorkspace:=dbgen.ClaimPrebuild(
1207+
t,db,
1208+
clock.Now(),
1209+
user.ID,
1210+
"claimedWorkspace-autostop",
1211+
presetID,
1212+
sql.NullString{},
1213+
sql.NullTime{},
1214+
sql.NullInt64{})
1215+
require.Equal(t,prebuild.ID,claimedWorkspace.ID)
1216+
1217+
// Given: the workspace level configurations are properly set in order to ensure the
1218+
// lifecycle parameters are updated
1219+
tc.workspaceUpdate(t,ctx,db,clock.Now(),claimedWorkspace)
1220+
1221+
// When: the template schedule is updated
1222+
_,err=templateScheduleStore.Set(ctx,db,tv.Template,tc.templateSchedule)
1223+
require.NoError(t,err)
1224+
1225+
// Then: the workspace should have its lifecycle parameters updated
1226+
workspace,err:=db.GetWorkspaceByID(ctx,claimedWorkspace.ID)
1227+
require.NoError(t,err)
1228+
tc.assertWorkspace(t,ctx,db,clock.Now(),false,workspace)
1229+
})
1230+
}
1231+
}
1232+
9821233
funcmust[Vany](vV,errerror)V {
9831234
iferr!=nil {
9841235
panic(err)

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp