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

Commit845407f

Browse files
authored
chore: cover deadline crossing autostart border on start (#13115)
When starting a workspace, if the deadline crosses an autostart boundary, the deadline is set to autostart + TTL. This copies the behavior in `ActivityBumpWorkspace`, but does not require activity.
1 parent71a03a8 commit845407f

File tree

8 files changed

+211
-39
lines changed

8 files changed

+211
-39
lines changed

‎coderd/agentapi/stats.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import (
1313

1414
"cdr.dev/slog"
1515
agentproto"github.com/coder/coder/v2/agent/proto"
16-
"github.com/coder/coder/v2/coderd/autobuild"
1716
"github.com/coder/coder/v2/coderd/database"
1817
"github.com/coder/coder/v2/coderd/database/dbtime"
1918
"github.com/coder/coder/v2/coderd/database/pubsub"
@@ -84,7 +83,7 @@ func (a *StatsAPI) UpdateStats(ctx context.Context, req *agentproto.UpdateStatsR
8483
slog.Error(err),
8584
)
8685
}else {
87-
next,allowed:=autobuild.NextAutostartSchedule(now,workspace.AutostartSchedule.String,templateSchedule)
86+
next,allowed:=schedule.NextAutostart(now,workspace.AutostartSchedule.String,templateSchedule)
8887
ifallowed {
8988
nextAutostart=next
9089
}

‎coderd/autobuild/lifecycle_executor.go

Lines changed: 1 addition & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import (
2020
"github.com/coder/coder/v2/coderd/database/provisionerjobs"
2121
"github.com/coder/coder/v2/coderd/database/pubsub"
2222
"github.com/coder/coder/v2/coderd/schedule"
23-
"github.com/coder/coder/v2/coderd/schedule/cron"
2423
"github.com/coder/coder/v2/coderd/wsbuilder"
2524
)
2625

@@ -368,7 +367,7 @@ func isEligibleForAutostart(user database.User, ws database.Workspace, build dat
368367
returnfalse
369368
}
370369

371-
nextTransition,allowed:=NextAutostartSchedule(build.CreatedAt,ws.AutostartSchedule.String,templateSchedule)
370+
nextTransition,allowed:=schedule.NextAutostart(build.CreatedAt,ws.AutostartSchedule.String,templateSchedule)
372371
if!allowed {
373372
returnfalse
374373
}
@@ -377,29 +376,6 @@ func isEligibleForAutostart(user database.User, ws database.Workspace, build dat
377376
return!currentTick.Before(nextTransition)
378377
}
379378

380-
// NextAutostartSchedule takes the workspace and template schedule and returns the next autostart schedule
381-
// after "at". The boolean returned is if the autostart should be allowed to start based on the template
382-
// schedule.
383-
funcNextAutostartSchedule(at time.Time,wsSchedulestring,templateSchedule schedule.TemplateScheduleOptions) (time.Time,bool) {
384-
sched,err:=cron.Weekly(wsSchedule)
385-
iferr!=nil {
386-
return time.Time{},false
387-
}
388-
389-
// Round down to the nearest minute, as this is the finest granularity cron supports.
390-
// Truncate is probably not necessary here, but doing it anyway to be sure.
391-
nextTransition:=sched.Next(at).Truncate(time.Minute)
392-
393-
// The nextTransition is when the auto start should kick off. If it lands on a
394-
// forbidden day, do not allow the auto start. We use the time location of the
395-
// schedule to determine the weekday. So if "Saturday" is disallowed, the
396-
// definition of "Saturday" depends on the location of the schedule.
397-
zonedTransition:=nextTransition.In(sched.Location())
398-
allowed:=templateSchedule.AutostartRequirement.DaysMap()[zonedTransition.Weekday()]
399-
400-
returnzonedTransition,allowed
401-
}
402-
403379
// isEligibleForAutostart returns true if the workspace should be autostopped.
404380
funcisEligibleForAutostop(ws database.Workspace,build database.WorkspaceBuild,job database.ProvisionerJob,currentTick time.Time)bool {
405381
ifjob.JobStatus==database.ProvisionerJobStatusFailed {

‎coderd/provisionerdserver/provisionerdserver.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1257,6 +1257,8 @@ func (s *server) CompleteJob(ctx context.Context, completed *proto.CompletedJob)
12571257
UserQuietHoursScheduleStore:*s.UserQuietHoursScheduleStore.Load(),
12581258
Now:now,
12591259
Workspace:workspace,
1260+
// Allowed to be the empty string.
1261+
WorkspaceAutostart:workspace.AutostartSchedule.String,
12601262
})
12611263
iferr!=nil {
12621264
returnxerrors.Errorf("calculate auto stop: %w",err)

‎coderd/schedule/autostart.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package schedule
2+
3+
import (
4+
"time"
5+
6+
"github.com/coder/coder/v2/coderd/schedule/cron"
7+
)
8+
9+
// NextAutostart takes the workspace and template schedule and returns the next autostart schedule
10+
// after "at". The boolean returned is if the autostart should be allowed to start based on the template
11+
// schedule.
12+
funcNextAutostart(at time.Time,wsSchedulestring,templateScheduleTemplateScheduleOptions) (time.Time,bool) {
13+
sched,err:=cron.Weekly(wsSchedule)
14+
iferr!=nil {
15+
return time.Time{},false
16+
}
17+
18+
// Round down to the nearest minute, as this is the finest granularity cron supports.
19+
// Truncate is probably not necessary here, but doing it anyway to be sure.
20+
nextTransition:=sched.Next(at).Truncate(time.Minute)
21+
22+
// The nextTransition is when the auto start should kick off. If it lands on a
23+
// forbidden day, do not allow the auto start. We use the time location of the
24+
// schedule to determine the weekday. So if "Saturday" is disallowed, the
25+
// definition of "Saturday" depends on the location of the schedule.
26+
zonedTransition:=nextTransition.In(sched.Location())
27+
allowed:=templateSchedule.AutostartRequirement.DaysMap()[zonedTransition.Weekday()]
28+
29+
returnzonedTransition,allowed
30+
}

‎coderd/schedule/autostop.go

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,11 @@ type CalculateAutostopParams struct {
4444
Database database.Store
4545
TemplateScheduleStoreTemplateScheduleStore
4646
UserQuietHoursScheduleStoreUserQuietHoursScheduleStore
47+
// WorkspaceAutostart can be the empty string if no workspace autostart
48+
// is configured.
49+
// If configured, this is expected to be a cron weekly event parsable
50+
// by autobuild.NextAutostart
51+
WorkspaceAutostartstring
4752

4853
Now time.Time
4954
Workspace database.Workspace
@@ -90,6 +95,14 @@ func CalculateAutostop(ctx context.Context, params CalculateAutostopParams) (Aut
9095
autostopAutostopTime
9196
)
9297

98+
varttl time.Duration
99+
ifworkspace.Ttl.Valid {
100+
// When the workspace is made it copies the template's TTL, and the user
101+
// can unset it to disable it (unless the template has
102+
// UserAutoStopEnabled set to false, see below).
103+
ttl=time.Duration(workspace.Ttl.Int64)
104+
}
105+
93106
ifworkspace.Ttl.Valid {
94107
// When the workspace is made it copies the template's TTL, and the user
95108
// can unset it to disable it (unless the template has
@@ -104,9 +117,30 @@ func CalculateAutostop(ctx context.Context, params CalculateAutostopParams) (Aut
104117
if!templateSchedule.UserAutostopEnabled {
105118
// The user is not permitted to set their own TTL, so use the template
106119
// default.
107-
autostop.Deadline=time.Time{}
120+
ttl=0
108121
iftemplateSchedule.DefaultTTL>0 {
109-
autostop.Deadline=now.Add(templateSchedule.DefaultTTL)
122+
ttl=templateSchedule.DefaultTTL
123+
}
124+
}
125+
126+
ifttl>0 {
127+
// Only apply non-zero TTLs.
128+
autostop.Deadline=now.Add(ttl)
129+
ifparams.WorkspaceAutostart!="" {
130+
// If the deadline passes the next autostart, we need to extend the deadline to
131+
// autostart + deadline. ActivityBumpWorkspace already covers this case
132+
// when extending the deadline.
133+
//
134+
// Situation this is solving.
135+
// 1. User has workspace with auto-start at 9:00am, 12 hour auto-stop.
136+
// 2. Coder stops workspace at 9pm
137+
// 3. User starts workspace at 9:45pm.
138+
//- The initial deadline is calculated to be 9:45am
139+
//- This crosses the autostart deadline, so the deadline is extended to 9pm
140+
nextAutostart,ok:=NextAutostart(params.Now,params.WorkspaceAutostart,templateSchedule)
141+
ifok&&autostop.Deadline.After(nextAutostart) {
142+
autostop.Deadline=nextAutostart.Add(ttl)
143+
}
110144
}
111145
}
112146

‎coderd/schedule/autostop_test.go

Lines changed: 136 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@ func TestCalculateAutoStop(t *testing.T) {
2525

2626
now:=time.Now()
2727

28+
chicago,err:=time.LoadLocation("America/Chicago")
29+
require.NoError(t,err,"loading chicago time location")
30+
31+
// pastDateNight is 9:45pm on a wednesday
32+
pastDateNight:=time.Date(2024,2,14,21,45,0,0,chicago)
33+
2834
// Wednesday the 8th of February 2023 at midnight. This date was
2935
// specifically chosen as it doesn't fall on a applicable week for both
3036
// fortnightly and triweekly autostop requirements.
@@ -70,8 +76,12 @@ func TestCalculateAutoStop(t *testing.T) {
7076
t.Log("saturdayMidnightAfterDstOut",saturdayMidnightAfterDstOut)
7177

7278
cases:= []struct {
73-
namestring
74-
now time.Time
79+
namestring
80+
now time.Time
81+
82+
wsAutostartstring
83+
templateAutoStart schedule.TemplateAutostartRequirement
84+
7585
templateAllowAutostopbool
7686
templateDefaultTTL time.Duration
7787
templateAutostopRequirement schedule.TemplateAutostopRequirement
@@ -364,6 +374,115 @@ func TestCalculateAutoStop(t *testing.T) {
364374
// expectedDeadline is copied from expectedMaxDeadline.
365375
expectedMaxDeadline:dstOutQuietHoursExpectedTime,
366376
},
377+
{
378+
// A user expects this workspace to be online from 9am -> 9pm.
379+
// So if a deadline is going to land in the middle of this range,
380+
// we should bump it to the end.
381+
// This is already done on `ActivityBumpWorkspace`, but that requires
382+
// activity on the workspace.
383+
name:"AutostopCrossAutostartBorder",
384+
// Starting at 9:45pm, with the autostart at 9am.
385+
now:pastDateNight,
386+
templateAllowAutostop:false,
387+
templateDefaultTTL:time.Hour*12,
388+
workspaceTTL:time.Hour*12,
389+
// At 9am every morning
390+
wsAutostart:"CRON_TZ=America/Chicago 0 9 * * *",
391+
392+
// No quiet hours
393+
templateAutoStart: schedule.TemplateAutostartRequirement{
394+
// Just allow all days of the week
395+
DaysOfWeek:0b01111111,
396+
},
397+
templateAutostopRequirement: schedule.TemplateAutostopRequirement{},
398+
userQuietHoursSchedule:"",
399+
400+
expectedDeadline:time.Date(pastDateNight.Year(),pastDateNight.Month(),pastDateNight.Day()+1,21,0,0,0,chicago),
401+
expectedMaxDeadline: time.Time{},
402+
errContains:"",
403+
},
404+
{
405+
// Same as AutostopCrossAutostartBorder, but just misses the autostart.
406+
name:"AutostopCrossMissAutostartBorder",
407+
// Starting at 8:45pm, with the autostart at 9am.
408+
now:time.Date(pastDateNight.Year(),pastDateNight.Month(),pastDateNight.Day(),20,30,0,0,chicago),
409+
templateAllowAutostop:false,
410+
templateDefaultTTL:time.Hour*12,
411+
workspaceTTL:time.Hour*12,
412+
// At 9am every morning
413+
wsAutostart:"CRON_TZ=America/Chicago 0 9 * * *",
414+
415+
// No quiet hours
416+
templateAutoStart: schedule.TemplateAutostartRequirement{
417+
// Just allow all days of the week
418+
DaysOfWeek:0b01111111,
419+
},
420+
templateAutostopRequirement: schedule.TemplateAutostopRequirement{},
421+
userQuietHoursSchedule:"",
422+
423+
expectedDeadline:time.Date(pastDateNight.Year(),pastDateNight.Month(),pastDateNight.Day()+1,8,30,0,0,chicago),
424+
expectedMaxDeadline: time.Time{},
425+
errContains:"",
426+
},
427+
{
428+
// Same as AutostopCrossAutostartBorderMaxEarlyDeadline with max deadline to limit it.
429+
// The autostop deadline is before the autostart threshold.
430+
name:"AutostopCrossAutostartBorderMaxEarlyDeadline",
431+
// Starting at 9:45pm, with the autostart at 9am.
432+
now:pastDateNight,
433+
templateAllowAutostop:false,
434+
templateDefaultTTL:time.Hour*12,
435+
workspaceTTL:time.Hour*12,
436+
// At 9am every morning
437+
wsAutostart:"CRON_TZ=America/Chicago 0 9 * * *",
438+
439+
// No quiet hours
440+
templateAutoStart: schedule.TemplateAutostartRequirement{
441+
// Just allow all days of the week
442+
DaysOfWeek:0b01111111,
443+
},
444+
templateAutostopRequirement: schedule.TemplateAutostopRequirement{
445+
// Autostop every day
446+
DaysOfWeek:0b01111111,
447+
Weeks:0,
448+
},
449+
// 6am quiet hours
450+
userQuietHoursSchedule:"CRON_TZ=America/Chicago 0 6 * * *",
451+
452+
expectedDeadline:time.Date(pastDateNight.Year(),pastDateNight.Month(),pastDateNight.Day()+1,6,0,0,0,chicago),
453+
expectedMaxDeadline:time.Date(pastDateNight.Year(),pastDateNight.Month(),pastDateNight.Day()+1,6,0,0,0,chicago),
454+
errContains:"",
455+
},
456+
{
457+
// Same as AutostopCrossAutostartBorder with max deadline to limit it.
458+
// The autostop deadline is after autostart threshold.
459+
// So the deadline is > 12 hours, but stops at the max deadline.
460+
name:"AutostopCrossAutostartBorderMaxDeadline",
461+
// Starting at 9:45pm, with the autostart at 9am.
462+
now:pastDateNight,
463+
templateAllowAutostop:false,
464+
templateDefaultTTL:time.Hour*12,
465+
workspaceTTL:time.Hour*12,
466+
// At 9am every morning
467+
wsAutostart:"CRON_TZ=America/Chicago 0 9 * * *",
468+
469+
// No quiet hours
470+
templateAutoStart: schedule.TemplateAutostartRequirement{
471+
// Just allow all days of the week
472+
DaysOfWeek:0b01111111,
473+
},
474+
templateAutostopRequirement: schedule.TemplateAutostopRequirement{
475+
// Autostop every day
476+
DaysOfWeek:0b01111111,
477+
Weeks:0,
478+
},
479+
// 11am quiet hours, yea this is werid case.
480+
userQuietHoursSchedule:"CRON_TZ=America/Chicago 0 11 * * *",
481+
482+
expectedDeadline:time.Date(pastDateNight.Year(),pastDateNight.Month(),pastDateNight.Day()+1,11,0,0,0,chicago),
483+
expectedMaxDeadline:time.Date(pastDateNight.Year(),pastDateNight.Month(),pastDateNight.Day()+1,11,0,0,0,chicago),
484+
errContains:"",
485+
},
367486
}
368487

369488
for_,c:=rangecases {
@@ -382,6 +501,7 @@ func TestCalculateAutoStop(t *testing.T) {
382501
UserAutostopEnabled:c.templateAllowAutostop,
383502
DefaultTTL:c.templateDefaultTTL,
384503
AutostopRequirement:c.templateAutostopRequirement,
504+
AutostartRequirement:c.templateAutoStart,
385505
},nil
386506
},
387507
}
@@ -433,11 +553,20 @@ func TestCalculateAutoStop(t *testing.T) {
433553
Valid:true,
434554
}
435555
}
556+
557+
autostart:= sql.NullString{}
558+
ifc.wsAutostart!="" {
559+
autostart= sql.NullString{
560+
String:c.wsAutostart,
561+
Valid:true,
562+
}
563+
}
436564
workspace:=dbgen.Workspace(t,db, database.Workspace{
437-
TemplateID:template.ID,
438-
OrganizationID:org.ID,
439-
OwnerID:user.ID,
440-
Ttl:workspaceTTL,
565+
TemplateID:template.ID,
566+
OrganizationID:org.ID,
567+
OwnerID:user.ID,
568+
Ttl:workspaceTTL,
569+
AutostartSchedule:autostart,
441570
})
442571

443572
autostop,err:=schedule.CalculateAutostop(ctx, schedule.CalculateAutostopParams{
@@ -446,6 +575,7 @@ func TestCalculateAutoStop(t *testing.T) {
446575
UserQuietHoursScheduleStore:userQuietHoursScheduleStore,
447576
Now:c.now,
448577
Workspace:workspace,
578+
WorkspaceAutostart:c.wsAutostart,
449579
})
450580
ifc.errContains!="" {
451581
require.Error(t,err)

‎coderd/workspaceagents.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ import (
2727
"cdr.dev/slog"
2828
agentproto"github.com/coder/coder/v2/agent/proto"
2929
"github.com/coder/coder/v2/coderd/agentapi"
30-
"github.com/coder/coder/v2/coderd/autobuild"
3130
"github.com/coder/coder/v2/coderd/database"
3231
"github.com/coder/coder/v2/coderd/database/db2sdk"
3332
"github.com/coder/coder/v2/coderd/database/dbauthz"
@@ -37,6 +36,7 @@ import (
3736
"github.com/coder/coder/v2/coderd/httpmw"
3837
"github.com/coder/coder/v2/coderd/prometheusmetrics"
3938
"github.com/coder/coder/v2/coderd/rbac"
39+
"github.com/coder/coder/v2/coderd/schedule"
4040
"github.com/coder/coder/v2/codersdk"
4141
"github.com/coder/coder/v2/codersdk/agentsdk"
4242
"github.com/coder/coder/v2/codersdk/workspacesdk"
@@ -1186,7 +1186,7 @@ func (api *API) workspaceAgentReportStats(rw http.ResponseWriter, r *http.Reques
11861186
slog.Error(err),
11871187
)
11881188
}else {
1189-
next,allowed:=autobuild.NextAutostartSchedule(time.Now(),workspace.AutostartSchedule.String,templateSchedule)
1189+
next,allowed:=schedule.NextAutostart(time.Now(),workspace.AutostartSchedule.String,templateSchedule)
11901190
ifallowed {
11911191
nextAutostart=next
11921192
}

‎enterprise/coderd/schedule/template.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -263,8 +263,9 @@ func (s *EnterpriseTemplateScheduleStore) updateWorkspaceBuild(ctx context.Conte
263263
TemplateScheduleStore:s,
264264
UserQuietHoursScheduleStore:*s.UserQuietHoursScheduleStore.Load(),
265265
// Use the job completion time as the time we calculate autostop from.
266-
Now:job.CompletedAt.Time,
267-
Workspace:workspace,
266+
Now:job.CompletedAt.Time,
267+
Workspace:workspace,
268+
WorkspaceAutostart:workspace.AutostartSchedule.String,
268269
})
269270
iferr!=nil {
270271
returnxerrors.Errorf("calculate new autostop for workspace %q: %w",workspace.ID,err)

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp