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

Commit8f0a5a8

Browse files
authored
feat: add API/SDK support for autostop extension (#1778)
* Adds deadline column to workspace_builds, associated DB/API plumbing* database: Upon inserting a row into workspace_builds, deadline will initially be zero.* autobuild: Executor now checks the Deadline field of the workspace_build for the purpose of autostop logic.* coderd: Adds a new route /api/v2/workspaces/:workspace/extend which allows updating the deadline of the currently active workspace build. The new deadline must be after the existing deadline, and not the zero time.* provisionerd: updates workspace_build.deadline upon successful workspace build completion (equal to now plus workspace TTL, if it exists).
1 parentc04d045 commit8f0a5a8

18 files changed

+306
-34
lines changed

‎coderd/autobuild/executor/lifecycle_executor.go

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,18 @@ func (e *Executor) Run() {
5050
func (e*Executor)runOnce(t time.Time)error {
5151
currentTick:=t.Truncate(time.Minute)
5252
returne.db.InTx(func(db database.Store)error {
53+
// TTL is set at the workspace level, and deadline at the workspace build level.
54+
// When a workspace build is created, its deadline initially starts at zero.
55+
// When provisionerd successfully completes a provision job, the deadline is
56+
// set to now + TTL if the associated workspace has a TTL set. This deadline
57+
// is what we compare against when performing autostop operations, rounded down
58+
// to the minute.
59+
//
60+
// NOTE: Currently, if a workspace build is created with a given TTL and then
61+
// the user either changes or unsets the TTL, the deadline for the workspace
62+
// build will not have changed. So, autostop will still happen at the
63+
// original TTL value from when the workspace build was created.
64+
// Whether this is expected behavior from a user's perspective is not yet known.
5365
eligibleWorkspaces,err:=db.GetWorkspacesAutostart(e.ctx)
5466
iferr!=nil {
5567
returnxerrors.Errorf("get eligible workspaces for autostart or autostop: %w",err)
@@ -88,18 +100,15 @@ func (e *Executor) runOnce(t time.Time) error {
88100
switchpriorHistory.Transition {
89101
casedatabase.WorkspaceTransitionStart:
90102
validTransition=database.WorkspaceTransitionStop
91-
if!ws.Ttl.Valid||ws.Ttl.Int64==0 {
92-
e.log.Debug(e.ctx,"invalid or zero ws ttl, skipping",
103+
ifpriorHistory.Deadline.IsZero() {
104+
e.log.Debug(e.ctx,"latest workspace build has zero deadline, skipping",
93105
slog.F("workspace_id",ws.ID),
94-
slog.F("ttl",time.Duration(ws.Ttl.Int64)),
106+
slog.F("workspace_build_id",priorHistory.ID),
95107
)
96108
continue
97109
}
98-
ttl:=time.Duration(ws.Ttl.Int64)
99-
// Measure TTL from the time the workspace finished building.
100-
// Truncate to nearest minute for consistency with autostart
101-
// behavior, and add one minute for padding.
102-
nextTransition=priorHistory.UpdatedAt.Truncate(time.Minute).Add(ttl+time.Minute)
110+
// Truncate to nearest minute for consistency with autostart behavior
111+
nextTransition=priorHistory.Deadline.Truncate(time.Minute)
103112
casedatabase.WorkspaceTransitionStop:
104113
validTransition=database.WorkspaceTransitionStart
105114
sched,err:=schedule.Weekly(ws.AutostartSchedule.String)

‎coderd/autobuild/executor/lifecycle_executor_test.go

Lines changed: 88 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -190,14 +190,14 @@ func TestExecutorAutostopOK(t *testing.T) {
190190
})
191191
// Given: we have a user with a workspace
192192
workspace=mustProvisionWorkspace(t,client)
193-
ttl=*workspace.TTL
194193
)
195194
// Given: workspace is running
196195
require.Equal(t,codersdk.WorkspaceTransitionStart,workspace.LatestBuild.Transition)
196+
require.NotZero(t,workspace.LatestBuild.Deadline)
197197

198-
// When: the autobuild executor ticks *after* theTTL:
198+
// When: the autobuild executor ticks *after* thedeadline:
199199
gofunc() {
200-
tickCh<-time.Now().UTC().Add(ttl+time.Minute)
200+
tickCh<-workspace.LatestBuild.Deadline.Add(time.Minute)
201201
close(tickCh)
202202
}()
203203

@@ -209,6 +209,55 @@ func TestExecutorAutostopOK(t *testing.T) {
209209
require.Equal(t,codersdk.WorkspaceTransitionStop,ws.LatestBuild.Transition,"expected workspace not to be running")
210210
}
211211

212+
funcTestExecutorAutostopExtend(t*testing.T) {
213+
t.Parallel()
214+
215+
var (
216+
ctx=context.Background()
217+
tickCh=make(chan time.Time)
218+
client=coderdtest.New(t,&coderdtest.Options{
219+
AutobuildTicker:tickCh,
220+
IncludeProvisionerD:true,
221+
})
222+
// Given: we have a user with a workspace
223+
workspace=mustProvisionWorkspace(t,client)
224+
originalDeadline=workspace.LatestBuild.Deadline
225+
)
226+
// Given: workspace is running
227+
require.Equal(t,codersdk.WorkspaceTransitionStart,workspace.LatestBuild.Transition)
228+
require.NotZero(t,originalDeadline)
229+
230+
// Given: we extend the workspace deadline
231+
err:=client.PutExtendWorkspace(ctx,workspace.ID, codersdk.PutExtendWorkspaceRequest{
232+
Deadline:originalDeadline.Add(30*time.Minute),
233+
})
234+
require.NoError(t,err,"extend workspace deadline")
235+
236+
// When: the autobuild executor ticks *after* the original deadline:
237+
gofunc() {
238+
tickCh<-originalDeadline.Add(time.Minute)
239+
}()
240+
241+
// Then: nothing should happen
242+
<-time.After(5*time.Second)
243+
ws:=mustWorkspace(t,client,workspace.ID)
244+
require.Equal(t,workspace.LatestBuild.ID,ws.LatestBuild.ID,"expected no further workspace builds to occur")
245+
require.Equal(t,codersdk.WorkspaceTransitionStart,ws.LatestBuild.Transition,"expected workspace to be running")
246+
247+
// When: the autobuild executor ticks after the *new* deadline:
248+
gofunc() {
249+
tickCh<-ws.LatestBuild.Deadline.Add(time.Minute)
250+
close(tickCh)
251+
}()
252+
253+
// Then: the workspace should be stopped
254+
<-time.After(5*time.Second)
255+
ws=mustWorkspace(t,client,workspace.ID)
256+
require.NotEqual(t,workspace.LatestBuild.ID,ws.LatestBuild.ID,"expected a workspace build to occur")
257+
require.Equal(t,codersdk.ProvisionerJobSucceeded,ws.LatestBuild.Job.Status,"expected provisioner job to have succeeded")
258+
require.Equal(t,codersdk.WorkspaceTransitionStop,ws.LatestBuild.Transition,"expected workspace not to be running")
259+
}
260+
212261
funcTestExecutorAutostopAlreadyStopped(t*testing.T) {
213262
t.Parallel()
214263

@@ -222,15 +271,14 @@ func TestExecutorAutostopAlreadyStopped(t *testing.T) {
222271
workspace=mustProvisionWorkspace(t,client,func(cwr*codersdk.CreateWorkspaceRequest) {
223272
cwr.AutostartSchedule=nil
224273
})
225-
ttl=*workspace.TTL
226274
)
227275

228276
// Given: workspace is stopped
229277
workspace=mustTransitionWorkspace(t,client,workspace.ID,database.WorkspaceTransitionStart,database.WorkspaceTransitionStop)
230278

231279
// When: the autobuild executor ticks past the TTL
232280
gofunc() {
233-
tickCh<-time.Now().UTC().Add(ttl+time.Minute)
281+
tickCh<-workspace.LatestBuild.Deadline.Add(time.Minute)
234282
close(tickCh)
235283
}()
236284

@@ -264,7 +312,7 @@ func TestExecutorAutostopNotEnabled(t *testing.T) {
264312

265313
// When: the autobuild executor ticks past the TTL
266314
gofunc() {
267-
tickCh<-time.Now().UTC().Add(time.Minute)
315+
tickCh<-workspace.LatestBuild.Deadline.Add(time.Minute)
268316
close(tickCh)
269317
}()
270318

@@ -352,7 +400,7 @@ func TestExecutorWorkspaceAutostartTooEarly(t *testing.T) {
352400
require.Equal(t,codersdk.WorkspaceTransitionStart,ws.LatestBuild.Transition,"expected workspace to be running")
353401
}
354402

355-
funcTestExecutorWorkspaceTTLTooEarly(t*testing.T) {
403+
funcTestExecutorWorkspaceAutostopBeforeDeadline(t*testing.T) {
356404
t.Parallel()
357405

358406
var (
@@ -367,7 +415,7 @@ func TestExecutorWorkspaceTTLTooEarly(t *testing.T) {
367415

368416
// When: the autobuild executor ticks before the TTL
369417
gofunc() {
370-
tickCh<-time.Now().UTC()
418+
tickCh<-workspace.LatestBuild.Deadline.Add(-1*time.Minute)
371419
close(tickCh)
372420
}()
373421

@@ -378,6 +426,38 @@ func TestExecutorWorkspaceTTLTooEarly(t *testing.T) {
378426
require.Equal(t,codersdk.WorkspaceTransitionStart,ws.LatestBuild.Transition,"expected workspace to be running")
379427
}
380428

429+
funcTestExecutorWorkspaceAutostopNoWaitChangedMyMind(t*testing.T) {
430+
t.Parallel()
431+
432+
var (
433+
ctx=context.Background()
434+
tickCh=make(chan time.Time)
435+
client=coderdtest.New(t,&coderdtest.Options{
436+
AutobuildTicker:tickCh,
437+
IncludeProvisionerD:true,
438+
})
439+
// Given: we have a user with a workspace
440+
workspace=mustProvisionWorkspace(t,client)
441+
)
442+
443+
// Given: the user changes their mind and decides their workspace should not auto-stop
444+
err:=client.UpdateWorkspaceTTL(ctx,workspace.ID, codersdk.UpdateWorkspaceTTLRequest{TTL:nil})
445+
require.NoError(t,err)
446+
447+
// When: the autobuild executor ticks after the deadline
448+
gofunc() {
449+
tickCh<-workspace.LatestBuild.Deadline.Add(time.Minute)
450+
close(tickCh)
451+
}()
452+
453+
// Then: the workspace should still stop - sorry!
454+
<-time.After(5*time.Second)
455+
ws:=mustWorkspace(t,client,workspace.ID)
456+
require.NotEqual(t,workspace.LatestBuild.ID,ws.LatestBuild.ID,"expected a workspace build to occur")
457+
require.Equal(t,codersdk.ProvisionerJobSucceeded,ws.LatestBuild.Job.Status,"expected provisioner job to have succeeded")
458+
require.Equal(t,codersdk.WorkspaceTransitionStop,ws.LatestBuild.Transition,"expected workspace not to be running")
459+
}
460+
381461
funcTestExecutorAutostartMultipleOK(t*testing.T) {
382462
ifos.Getenv("DB")=="" {
383463
t.Skip(`This test only really works when using a "real" database, similar to a HA setup`)

‎coderd/coderd.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,7 @@ func New(options *Options) *API {
315315
r.Put("/",api.putWorkspaceTTL)
316316
})
317317
r.Get("/watch",api.watchWorkspace)
318+
r.Put("/extend",api.putExtendWorkspace)
318319
})
319320
})
320321
r.Route("/workspacebuilds/{workspacebuild}",func(r chi.Router) {

‎coderd/database/databasefake/databasefake.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1485,6 +1485,7 @@ func (q *fakeQuerier) InsertWorkspaceBuild(_ context.Context, arg database.Inser
14851485
InitiatorID:arg.InitiatorID,
14861486
JobID:arg.JobID,
14871487
ProvisionerState:arg.ProvisionerState,
1488+
Deadline:arg.Deadline,
14881489
}
14891490
q.workspaceBuilds=append(q.workspaceBuilds,workspaceBuild)
14901491
returnworkspaceBuild,nil
@@ -1693,6 +1694,7 @@ func (q *fakeQuerier) UpdateWorkspaceBuildByID(_ context.Context, arg database.U
16931694
}
16941695
workspaceBuild.UpdatedAt=arg.UpdatedAt
16951696
workspaceBuild.ProvisionerState=arg.ProvisionerState
1697+
workspaceBuild.Deadline=arg.Deadline
16961698
q.workspaceBuilds[index]=workspaceBuild
16971699
returnnil
16981700
}

‎coderd/database/dump.sql

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more aboutcustomizing how changed files appear on GitHub.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ALTERTABLE ONLY workspace_builds DROP COLUMN deadline;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ALTERTABLE ONLY workspace_builds ADD COLUMN deadlineTIMESTAMPTZNOT NULL DEFAULTTIMESTAMPTZ'0001-01-01 00:00:00+00:00';

‎coderd/database/models.go

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more aboutcustomizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp