mirror of
https://github.com/coder/coder.git
synced 2025-07-15 22:20:27 +00:00
refactor: workspace autostop_schedule -> ttl (#1578)
Co-authored-by: G r e y <grey@coder.com>
This commit is contained in:
@ -50,7 +50,7 @@ func (e *Executor) Run() {
|
||||
func (e *Executor) runOnce(t time.Time) error {
|
||||
currentTick := t.Truncate(time.Minute)
|
||||
return e.db.InTx(func(db database.Store) error {
|
||||
eligibleWorkspaces, err := db.GetWorkspacesAutostartAutostop(e.ctx)
|
||||
eligibleWorkspaces, err := db.GetWorkspacesAutostart(e.ctx)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("get eligible workspaces for autostart or autostop: %w", err)
|
||||
}
|
||||
@ -84,21 +84,25 @@ func (e *Executor) runOnce(t time.Time) error {
|
||||
}
|
||||
|
||||
var validTransition database.WorkspaceTransition
|
||||
var sched *schedule.Schedule
|
||||
var nextTransition time.Time
|
||||
switch priorHistory.Transition {
|
||||
case database.WorkspaceTransitionStart:
|
||||
validTransition = database.WorkspaceTransitionStop
|
||||
sched, err = schedule.Weekly(ws.AutostopSchedule.String)
|
||||
if err != nil {
|
||||
e.log.Warn(e.ctx, "workspace has invalid autostop schedule, skipping",
|
||||
if !ws.Ttl.Valid || ws.Ttl.Int64 == 0 {
|
||||
e.log.Debug(e.ctx, "invalid or zero ws ttl, skipping",
|
||||
slog.F("workspace_id", ws.ID),
|
||||
slog.F("autostart_schedule", ws.AutostopSchedule.String),
|
||||
slog.F("ttl", time.Duration(ws.Ttl.Int64)),
|
||||
)
|
||||
continue
|
||||
}
|
||||
ttl := time.Duration(ws.Ttl.Int64)
|
||||
// Measure TTL from the time the workspace finished building.
|
||||
// Truncate to nearest minute for consistency with autostart
|
||||
// behavior, and add one minute for padding.
|
||||
nextTransition = priorHistory.UpdatedAt.Truncate(time.Minute).Add(ttl + time.Minute)
|
||||
case database.WorkspaceTransitionStop:
|
||||
validTransition = database.WorkspaceTransitionStart
|
||||
sched, err = schedule.Weekly(ws.AutostartSchedule.String)
|
||||
sched, err := schedule.Weekly(ws.AutostartSchedule.String)
|
||||
if err != nil {
|
||||
e.log.Warn(e.ctx, "workspace has invalid autostart schedule, skipping",
|
||||
slog.F("workspace_id", ws.ID),
|
||||
@ -106,6 +110,9 @@ func (e *Executor) runOnce(t time.Time) error {
|
||||
)
|
||||
continue
|
||||
}
|
||||
// Round down to the nearest minute, as this is the finest granularity cron supports.
|
||||
// Truncate is probably not necessary here, but doing it anyway to be sure.
|
||||
nextTransition = sched.Next(priorHistory.CreatedAt).Truncate(time.Minute)
|
||||
default:
|
||||
e.log.Debug(e.ctx, "last transition not valid for autostart or autostop",
|
||||
slog.F("workspace_id", ws.ID),
|
||||
@ -114,13 +121,10 @@ func (e *Executor) runOnce(t time.Time) error {
|
||||
continue
|
||||
}
|
||||
|
||||
// Round time down to the nearest minute, as this is the finest granularity cron supports.
|
||||
// Truncate is probably not necessary here, but doing it anyway to be sure.
|
||||
nextTransitionAt := sched.Next(priorHistory.CreatedAt).Truncate(time.Minute)
|
||||
if currentTick.Before(nextTransitionAt) {
|
||||
if currentTick.Before(nextTransition) {
|
||||
e.log.Debug(e.ctx, "skipping workspace: too early",
|
||||
slog.F("workspace_id", ws.ID),
|
||||
slog.F("next_transition_at", nextTransitionAt),
|
||||
slog.F("next_transition_at", nextTransition),
|
||||
slog.F("transition", validTransition),
|
||||
slog.F("current_tick", currentTick),
|
||||
)
|
||||
|
@ -194,27 +194,27 @@ func TestExecutorAutostopOK(t *testing.T) {
|
||||
})
|
||||
// Given: we have a user with a workspace
|
||||
workspace = mustProvisionWorkspace(t, client)
|
||||
ttl = time.Minute
|
||||
)
|
||||
// Given: workspace is running
|
||||
require.Equal(t, codersdk.WorkspaceTransitionStart, workspace.LatestBuild.Transition)
|
||||
|
||||
// Given: the workspace initially has autostop disabled
|
||||
require.Empty(t, workspace.AutostopSchedule)
|
||||
require.Nil(t, workspace.TTL)
|
||||
|
||||
// When: we enable workspace autostop
|
||||
sched, err := schedule.Weekly("* * * * *")
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, client.UpdateWorkspaceAutostop(ctx, workspace.ID, codersdk.UpdateWorkspaceAutostopRequest{
|
||||
Schedule: sched.String(),
|
||||
require.NoError(t, client.UpdateWorkspaceTTL(ctx, workspace.ID, codersdk.UpdateWorkspaceTTLRequest{
|
||||
TTL: &ttl,
|
||||
}))
|
||||
|
||||
// When: the autobuild executor ticks
|
||||
// When: the autobuild executor ticks *after* the TTL:
|
||||
go func() {
|
||||
tickCh <- time.Now().UTC().Add(time.Minute)
|
||||
tickCh <- time.Now().UTC().Add(ttl + time.Minute)
|
||||
close(tickCh)
|
||||
}()
|
||||
|
||||
// Then: the workspace should be started
|
||||
// Then: the workspace should be stopped
|
||||
<-time.After(5 * time.Second)
|
||||
ws := mustWorkspace(t, client, workspace.ID)
|
||||
require.NotEqual(t, workspace.LatestBuild.ID, ws.LatestBuild.ID, "expected a workspace build to occur")
|
||||
@ -234,24 +234,24 @@ func TestExecutorAutostopAlreadyStopped(t *testing.T) {
|
||||
})
|
||||
// Given: we have a user with a workspace
|
||||
workspace = mustProvisionWorkspace(t, client)
|
||||
ttl = time.Minute
|
||||
)
|
||||
|
||||
// Given: workspace is stopped
|
||||
workspace = mustTransitionWorkspace(t, client, workspace.ID, database.WorkspaceTransitionStart, database.WorkspaceTransitionStop)
|
||||
|
||||
// Given: the workspace initially has autostop disabled
|
||||
require.Empty(t, workspace.AutostopSchedule)
|
||||
require.Nil(t, workspace.TTL)
|
||||
|
||||
// When: we enable workspace autostart
|
||||
sched, err := schedule.Weekly("* * * * *")
|
||||
// When: we set the TTL on the workspace
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, client.UpdateWorkspaceAutostop(ctx, workspace.ID, codersdk.UpdateWorkspaceAutostopRequest{
|
||||
Schedule: sched.String(),
|
||||
require.NoError(t, client.UpdateWorkspaceTTL(ctx, workspace.ID, codersdk.UpdateWorkspaceTTLRequest{
|
||||
TTL: &ttl,
|
||||
}))
|
||||
|
||||
// When: the autobuild executor ticks
|
||||
// When: the autobuild executor ticks past the TTL
|
||||
go func() {
|
||||
tickCh <- time.Now().UTC().Add(time.Minute)
|
||||
tickCh <- time.Now().UTC().Add(ttl)
|
||||
close(tickCh)
|
||||
}()
|
||||
|
||||
@ -278,7 +278,7 @@ func TestExecutorAutostopNotEnabled(t *testing.T) {
|
||||
require.Equal(t, codersdk.WorkspaceTransitionStart, workspace.LatestBuild.Transition)
|
||||
|
||||
// Given: the workspace has autostop disabled
|
||||
require.Empty(t, workspace.AutostopSchedule)
|
||||
require.Empty(t, workspace.TTL)
|
||||
|
||||
// When: the autobuild executor ticks
|
||||
go func() {
|
||||
@ -308,12 +308,12 @@ func TestExecutorWorkspaceDeleted(t *testing.T) {
|
||||
)
|
||||
|
||||
// Given: the workspace initially has autostart disabled
|
||||
require.Empty(t, workspace.AutostopSchedule)
|
||||
require.Empty(t, workspace.AutostartSchedule)
|
||||
|
||||
// When: we enable workspace autostart
|
||||
sched, err := schedule.Weekly("* * * * *")
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, client.UpdateWorkspaceAutostop(ctx, workspace.ID, codersdk.UpdateWorkspaceAutostopRequest{
|
||||
require.NoError(t, client.UpdateWorkspaceAutostart(ctx, workspace.ID, codersdk.UpdateWorkspaceAutostartRequest{
|
||||
Schedule: sched.String(),
|
||||
}))
|
||||
|
||||
@ -333,7 +333,7 @@ func TestExecutorWorkspaceDeleted(t *testing.T) {
|
||||
require.Equal(t, codersdk.WorkspaceTransitionDelete, ws.LatestBuild.Transition, "expected workspace to be deleted")
|
||||
}
|
||||
|
||||
func TestExecutorWorkspaceTooEarly(t *testing.T) {
|
||||
func TestExecutorWorkspaceAutostartTooEarly(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var (
|
||||
@ -348,14 +348,14 @@ func TestExecutorWorkspaceTooEarly(t *testing.T) {
|
||||
)
|
||||
|
||||
// Given: the workspace initially has autostart disabled
|
||||
require.Empty(t, workspace.AutostopSchedule)
|
||||
require.Empty(t, workspace.AutostartSchedule)
|
||||
|
||||
// When: we enable workspace autostart with some time in the future
|
||||
futureTime := time.Now().Add(time.Hour)
|
||||
futureTimeCron := fmt.Sprintf("%d %d * * *", futureTime.Minute(), futureTime.Hour())
|
||||
sched, err := schedule.Weekly(futureTimeCron)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, client.UpdateWorkspaceAutostop(ctx, workspace.ID, codersdk.UpdateWorkspaceAutostopRequest{
|
||||
require.NoError(t, client.UpdateWorkspaceAutostart(ctx, workspace.ID, codersdk.UpdateWorkspaceAutostartRequest{
|
||||
Schedule: sched.String(),
|
||||
}))
|
||||
|
||||
@ -372,6 +372,41 @@ func TestExecutorWorkspaceTooEarly(t *testing.T) {
|
||||
require.Equal(t, codersdk.WorkspaceTransitionStart, ws.LatestBuild.Transition, "expected workspace to be running")
|
||||
}
|
||||
|
||||
func TestExecutorWorkspaceTTLTooEarly(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var (
|
||||
ctx = context.Background()
|
||||
tickCh = make(chan time.Time)
|
||||
client = coderdtest.New(t, &coderdtest.Options{
|
||||
AutobuildTicker: tickCh,
|
||||
})
|
||||
// Given: we have a user with a workspace
|
||||
workspace = mustProvisionWorkspace(t, client)
|
||||
ttl = time.Hour
|
||||
)
|
||||
|
||||
// Given: the workspace initially has TTL unset
|
||||
require.Nil(t, workspace.TTL)
|
||||
|
||||
// When: we set the TTL to some time in the distant future
|
||||
require.NoError(t, client.UpdateWorkspaceTTL(ctx, workspace.ID, codersdk.UpdateWorkspaceTTLRequest{
|
||||
TTL: &ttl,
|
||||
}))
|
||||
|
||||
// When: the autobuild executor ticks
|
||||
go func() {
|
||||
tickCh <- time.Now().UTC()
|
||||
close(tickCh)
|
||||
}()
|
||||
|
||||
// Then: nothing should happen
|
||||
<-time.After(5 * time.Second)
|
||||
ws := mustWorkspace(t, client, workspace.ID)
|
||||
require.Equal(t, workspace.LatestBuild.ID, ws.LatestBuild.ID, "expected no further workspace builds to occur")
|
||||
require.Equal(t, codersdk.WorkspaceTransitionStart, ws.LatestBuild.Transition, "expected workspace to be running")
|
||||
}
|
||||
|
||||
func TestExecutorAutostartMultipleOK(t *testing.T) {
|
||||
if os.Getenv("DB") == "" {
|
||||
t.Skip(`This test only really works when using a "real" database, similar to a HA setup`)
|
||||
|
Reference in New Issue
Block a user