diff --git a/coderd/autobuild/executor/lifecycle_executor_test.go b/coderd/autobuild/executor/lifecycle_executor_test.go index 330662f3fc..51f3864e16 100644 --- a/coderd/autobuild/executor/lifecycle_executor_test.go +++ b/coderd/autobuild/executor/lifecycle_executor_test.go @@ -3,6 +3,7 @@ package executor_test import ( "context" "fmt" + "os" "testing" "time" @@ -25,7 +26,7 @@ func TestExecutorAutostartOK(t *testing.T) { err error tickCh = make(chan time.Time) client = coderdtest.New(t, &coderdtest.Options{ - LifecycleTicker: tickCh, + AutobuildTicker: tickCh, }) // Given: we have a user with a workspace workspace = mustProvisionWorkspace(t, client) @@ -65,7 +66,7 @@ func TestExecutorAutostartTemplateUpdated(t *testing.T) { err error tickCh = make(chan time.Time) client = coderdtest.New(t, &coderdtest.Options{ - LifecycleTicker: tickCh, + AutobuildTicker: tickCh, }) // Given: we have a user with a workspace workspace = mustProvisionWorkspace(t, client) @@ -117,7 +118,7 @@ func TestExecutorAutostartAlreadyRunning(t *testing.T) { err error tickCh = make(chan time.Time) client = coderdtest.New(t, &coderdtest.Options{ - LifecycleTicker: tickCh, + AutobuildTicker: tickCh, }) // Given: we have a user with a workspace workspace = mustProvisionWorkspace(t, client) @@ -155,7 +156,7 @@ func TestExecutorAutostartNotEnabled(t *testing.T) { var ( tickCh = make(chan time.Time) client = coderdtest.New(t, &coderdtest.Options{ - LifecycleTicker: tickCh, + AutobuildTicker: tickCh, }) // Given: we have a user with a workspace workspace = mustProvisionWorkspace(t, client) @@ -188,7 +189,7 @@ func TestExecutorAutostopOK(t *testing.T) { err error tickCh = make(chan time.Time) client = coderdtest.New(t, &coderdtest.Options{ - LifecycleTicker: tickCh, + AutobuildTicker: tickCh, }) // Given: we have a user with a workspace workspace = mustProvisionWorkspace(t, client) @@ -228,7 +229,7 @@ func TestExecutorAutostopAlreadyStopped(t *testing.T) { err error tickCh = make(chan time.Time) client = coderdtest.New(t, &coderdtest.Options{ - LifecycleTicker: tickCh, + AutobuildTicker: tickCh, }) // Given: we have a user with a workspace workspace = mustProvisionWorkspace(t, client) @@ -266,7 +267,7 @@ func TestExecutorAutostopNotEnabled(t *testing.T) { var ( tickCh = make(chan time.Time) client = coderdtest.New(t, &coderdtest.Options{ - LifecycleTicker: tickCh, + AutobuildTicker: tickCh, }) // Given: we have a user with a workspace workspace = mustProvisionWorkspace(t, client) @@ -299,7 +300,7 @@ func TestExecutorWorkspaceDeleted(t *testing.T) { err error tickCh = make(chan time.Time) client = coderdtest.New(t, &coderdtest.Options{ - LifecycleTicker: tickCh, + AutobuildTicker: tickCh, }) // Given: we have a user with a workspace workspace = mustProvisionWorkspace(t, client) @@ -339,7 +340,7 @@ func TestExecutorWorkspaceTooEarly(t *testing.T) { err error tickCh = make(chan time.Time) client = coderdtest.New(t, &coderdtest.Options{ - LifecycleTicker: tickCh, + AutobuildTicker: tickCh, }) // Given: we have a user with a workspace workspace = mustProvisionWorkspace(t, client) @@ -370,6 +371,60 @@ func TestExecutorWorkspaceTooEarly(t *testing.T) { require.Equal(t, database.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`) + } + + t.Parallel() + + var ( + ctx = context.Background() + err error + tickCh = make(chan time.Time) + tickCh2 = make(chan time.Time) + client = coderdtest.New(t, &coderdtest.Options{ + AutobuildTicker: tickCh, + }) + _ = coderdtest.New(t, &coderdtest.Options{ + AutobuildTicker: tickCh2, + }) + // Given: we have a user with a workspace + workspace = mustProvisionWorkspace(t, client) + ) + // Given: workspace is stopped + workspace = mustTransitionWorkspace(t, client, workspace.ID, database.WorkspaceTransitionStart, database.WorkspaceTransitionStop) + + // Given: the workspace initially has autostart disabled + require.Empty(t, workspace.AutostartSchedule) + + // When: we enable workspace autostart + sched, err := schedule.Weekly("* * * * *") + require.NoError(t, err) + require.NoError(t, client.UpdateWorkspaceAutostart(ctx, workspace.ID, codersdk.UpdateWorkspaceAutostartRequest{ + Schedule: sched.String(), + })) + + // When: the autobuild executor ticks + go func() { + tickCh <- time.Now().UTC().Add(time.Minute) + tickCh2 <- time.Now().UTC().Add(time.Minute) + close(tickCh) + close(tickCh2) + }() + + // Then: the workspace should be started + <-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, database.WorkspaceTransitionStart, ws.LatestBuild.Transition, "expected latest transition to be start") + builds, err := client.WorkspaceBuilds(ctx, ws.ID) + require.NoError(t, err, "fetch list of workspace builds from primary") + // One build to start, one stop transition, and one autostart. No more. + require.Len(t, builds, 3, "unexpected number of builds for workspace from primary") +} + func mustProvisionWorkspace(t *testing.T, client *codersdk.Client) codersdk.Workspace { t.Helper() coderdtest.NewProvisionerDaemon(t, client) diff --git a/coderd/coderdtest/coderdtest.go b/coderd/coderdtest/coderdtest.go index bcf45e43ed..75f49cea88 100644 --- a/coderd/coderdtest/coderdtest.go +++ b/coderd/coderdtest/coderdtest.go @@ -61,7 +61,7 @@ type Options struct { GoogleTokenValidator *idtoken.Validator SSHKeygenAlgorithm gitsshkey.Algorithm APIRateLimit int - LifecycleTicker <-chan time.Time + AutobuildTicker <-chan time.Time } // New constructs an in-memory coderd instance and returns @@ -77,9 +77,9 @@ func New(t *testing.T, options *Options) *codersdk.Client { options.GoogleTokenValidator, err = idtoken.NewValidator(ctx, option.WithoutAuthentication()) require.NoError(t, err) } - if options.LifecycleTicker == nil { + if options.AutobuildTicker == nil { ticker := make(chan time.Time) - options.LifecycleTicker = ticker + options.AutobuildTicker = ticker t.Cleanup(func() { close(ticker) }) } @@ -111,7 +111,7 @@ func New(t *testing.T, options *Options) *codersdk.Client { ctx, db, slogtest.Make(t, nil).Named("autobuild.executor").Leveled(slog.LevelDebug), - options.LifecycleTicker, + options.AutobuildTicker, ) lifecycleExecutor.Run()