mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
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).
This commit is contained in:
@@ -190,14 +190,14 @@ func TestExecutorAutostopOK(t *testing.T) {
|
||||
})
|
||||
// Given: we have a user with a workspace
|
||||
workspace = mustProvisionWorkspace(t, client)
|
||||
ttl = *workspace.TTL
|
||||
)
|
||||
// Given: workspace is running
|
||||
require.Equal(t, codersdk.WorkspaceTransitionStart, workspace.LatestBuild.Transition)
|
||||
require.NotZero(t, workspace.LatestBuild.Deadline)
|
||||
|
||||
// When: the autobuild executor ticks *after* the TTL:
|
||||
// When: the autobuild executor ticks *after* the deadline:
|
||||
go func() {
|
||||
tickCh <- time.Now().UTC().Add(ttl + time.Minute)
|
||||
tickCh <- workspace.LatestBuild.Deadline.Add(time.Minute)
|
||||
close(tickCh)
|
||||
}()
|
||||
|
||||
@@ -209,6 +209,55 @@ func TestExecutorAutostopOK(t *testing.T) {
|
||||
require.Equal(t, codersdk.WorkspaceTransitionStop, ws.LatestBuild.Transition, "expected workspace not to be running")
|
||||
}
|
||||
|
||||
func TestExecutorAutostopExtend(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var (
|
||||
ctx = context.Background()
|
||||
tickCh = make(chan time.Time)
|
||||
client = coderdtest.New(t, &coderdtest.Options{
|
||||
AutobuildTicker: tickCh,
|
||||
IncludeProvisionerD: true,
|
||||
})
|
||||
// Given: we have a user with a workspace
|
||||
workspace = mustProvisionWorkspace(t, client)
|
||||
originalDeadline = workspace.LatestBuild.Deadline
|
||||
)
|
||||
// Given: workspace is running
|
||||
require.Equal(t, codersdk.WorkspaceTransitionStart, workspace.LatestBuild.Transition)
|
||||
require.NotZero(t, originalDeadline)
|
||||
|
||||
// Given: we extend the workspace deadline
|
||||
err := client.PutExtendWorkspace(ctx, workspace.ID, codersdk.PutExtendWorkspaceRequest{
|
||||
Deadline: originalDeadline.Add(30 * time.Minute),
|
||||
})
|
||||
require.NoError(t, err, "extend workspace deadline")
|
||||
|
||||
// When: the autobuild executor ticks *after* the original deadline:
|
||||
go func() {
|
||||
tickCh <- originalDeadline.Add(time.Minute)
|
||||
}()
|
||||
|
||||
// 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")
|
||||
|
||||
// When: the autobuild executor ticks after the *new* deadline:
|
||||
go func() {
|
||||
tickCh <- ws.LatestBuild.Deadline.Add(time.Minute)
|
||||
close(tickCh)
|
||||
}()
|
||||
|
||||
// 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")
|
||||
require.Equal(t, codersdk.ProvisionerJobSucceeded, ws.LatestBuild.Job.Status, "expected provisioner job to have succeeded")
|
||||
require.Equal(t, codersdk.WorkspaceTransitionStop, ws.LatestBuild.Transition, "expected workspace not to be running")
|
||||
}
|
||||
|
||||
func TestExecutorAutostopAlreadyStopped(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -222,7 +271,6 @@ func TestExecutorAutostopAlreadyStopped(t *testing.T) {
|
||||
workspace = mustProvisionWorkspace(t, client, func(cwr *codersdk.CreateWorkspaceRequest) {
|
||||
cwr.AutostartSchedule = nil
|
||||
})
|
||||
ttl = *workspace.TTL
|
||||
)
|
||||
|
||||
// Given: workspace is stopped
|
||||
@@ -230,7 +278,7 @@ func TestExecutorAutostopAlreadyStopped(t *testing.T) {
|
||||
|
||||
// When: the autobuild executor ticks past the TTL
|
||||
go func() {
|
||||
tickCh <- time.Now().UTC().Add(ttl + time.Minute)
|
||||
tickCh <- workspace.LatestBuild.Deadline.Add(time.Minute)
|
||||
close(tickCh)
|
||||
}()
|
||||
|
||||
@@ -264,7 +312,7 @@ func TestExecutorAutostopNotEnabled(t *testing.T) {
|
||||
|
||||
// When: the autobuild executor ticks past the TTL
|
||||
go func() {
|
||||
tickCh <- time.Now().UTC().Add(time.Minute)
|
||||
tickCh <- workspace.LatestBuild.Deadline.Add(time.Minute)
|
||||
close(tickCh)
|
||||
}()
|
||||
|
||||
@@ -352,7 +400,7 @@ func TestExecutorWorkspaceAutostartTooEarly(t *testing.T) {
|
||||
require.Equal(t, codersdk.WorkspaceTransitionStart, ws.LatestBuild.Transition, "expected workspace to be running")
|
||||
}
|
||||
|
||||
func TestExecutorWorkspaceTTLTooEarly(t *testing.T) {
|
||||
func TestExecutorWorkspaceAutostopBeforeDeadline(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var (
|
||||
@@ -367,7 +415,7 @@ func TestExecutorWorkspaceTTLTooEarly(t *testing.T) {
|
||||
|
||||
// When: the autobuild executor ticks before the TTL
|
||||
go func() {
|
||||
tickCh <- time.Now().UTC()
|
||||
tickCh <- workspace.LatestBuild.Deadline.Add(-1 * time.Minute)
|
||||
close(tickCh)
|
||||
}()
|
||||
|
||||
@@ -378,6 +426,38 @@ func TestExecutorWorkspaceTTLTooEarly(t *testing.T) {
|
||||
require.Equal(t, codersdk.WorkspaceTransitionStart, ws.LatestBuild.Transition, "expected workspace to be running")
|
||||
}
|
||||
|
||||
func TestExecutorWorkspaceAutostopNoWaitChangedMyMind(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var (
|
||||
ctx = context.Background()
|
||||
tickCh = make(chan time.Time)
|
||||
client = coderdtest.New(t, &coderdtest.Options{
|
||||
AutobuildTicker: tickCh,
|
||||
IncludeProvisionerD: true,
|
||||
})
|
||||
// Given: we have a user with a workspace
|
||||
workspace = mustProvisionWorkspace(t, client)
|
||||
)
|
||||
|
||||
// Given: the user changes their mind and decides their workspace should not auto-stop
|
||||
err := client.UpdateWorkspaceTTL(ctx, workspace.ID, codersdk.UpdateWorkspaceTTLRequest{TTL: nil})
|
||||
require.NoError(t, err)
|
||||
|
||||
// When: the autobuild executor ticks after the deadline
|
||||
go func() {
|
||||
tickCh <- workspace.LatestBuild.Deadline.Add(time.Minute)
|
||||
close(tickCh)
|
||||
}()
|
||||
|
||||
// Then: the workspace should still stop - sorry!
|
||||
<-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")
|
||||
require.Equal(t, codersdk.ProvisionerJobSucceeded, ws.LatestBuild.Job.Status, "expected provisioner job to have succeeded")
|
||||
require.Equal(t, codersdk.WorkspaceTransitionStop, ws.LatestBuild.Transition, "expected workspace not 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