diff --git a/coderd/autobuild/lifecycle_executor_test.go b/coderd/autobuild/lifecycle_executor_test.go index 89805429b9880..8e16982e36b7c 100644 --- a/coderd/autobuild/lifecycle_executor_test.go +++ b/coderd/autobuild/lifecycle_executor_test.go @@ -65,8 +65,8 @@ func TestExecutorAutostartOK(t *testing.T) { p, err := coderdtest.GetProvisionerForTags(db, time.Now(), workspace.OrganizationID, map[string]string{}) require.NoError(t, err) // When: the autobuild executor ticks after the scheduled time + tickTime := coderdtest.NextAutostartTick(t, workspace) go func() { - tickTime := sched.Next(workspace.LatestBuild.CreatedAt) coderdtest.UpdateProvisionerLastSeenAt(t, db, p.ID, tickTime) tickCh <- tickTime close(tickCh) @@ -127,7 +127,7 @@ func TestMultipleLifecycleExecutors(t *testing.T) { p, err := coderdtest.GetProvisionerForTags(db, time.Now(), workspace.OrganizationID, nil) require.NoError(t, err) // Get both clients to perform a lifecycle execution tick - next := sched.Next(workspace.LatestBuild.CreatedAt) + next := coderdtest.NextAutostartTick(t, workspace) coderdtest.UpdateProvisionerLastSeenAt(t, db, p.ID, next) startCh := make(chan struct{}) @@ -237,7 +237,7 @@ func TestExecutorBuildNumberRaceIsHandled(t *testing.T) { p, err := coderdtest.GetProvisionerForTags(realDB, time.Now(), workspace.OrganizationID, nil) require.NoError(t, err) - next := sched.Next(workspace.LatestBuild.CreatedAt) + next := coderdtest.NextAutostartTick(t, workspace) coderdtest.UpdateProvisionerLastSeenAt(t, realDB, p.ID, next) tickCh <- next @@ -351,8 +351,8 @@ func TestExecutorAutostartTemplateUpdated(t *testing.T) { t.Log("sending autobuild tick") // When: the autobuild executor ticks after the scheduled time + tickTime := coderdtest.NextAutostartTick(t, workspace) go func() { - tickTime := sched.Next(workspace.LatestBuild.CreatedAt) coderdtest.UpdateProvisionerLastSeenAt(t, db, p.ID, tickTime) tickCh <- tickTime close(tickCh) @@ -984,8 +984,8 @@ func TestExecutorAutostartMultipleOK(t *testing.T) { require.NoError(t, err) // When: the autobuild executor ticks past the scheduled time + tickTime := coderdtest.NextAutostartTick(t, workspace) go func() { - tickTime := sched.Next(workspace.LatestBuild.CreatedAt) coderdtest.UpdateProvisionerLastSeenAt(t, db, p.ID, tickTime) tickCh <- tickTime tickCh2 <- tickTime @@ -1054,8 +1054,8 @@ func TestExecutorAutostartWithParameters(t *testing.T) { require.NoError(t, err) // When: the autobuild executor ticks after the scheduled time + tickTime := coderdtest.NextAutostartTick(t, workspace) go func() { - tickTime := sched.Next(workspace.LatestBuild.CreatedAt) coderdtest.UpdateProvisionerLastSeenAt(t, db, p.ID, tickTime) tickCh <- tickTime close(tickCh) @@ -1927,7 +1927,7 @@ func TestExecutorAutostartSkipsWhenNoProvisionersAvailable(t *testing.T) { p, err = coderdtest.GetProvisionerForTags(db, time.Now(), workspace.OrganizationID, provisionerDaemonTags) require.NoError(t, err, "Error getting provisioner for workspace") - next = sched.Next(workspace.LatestBuild.CreatedAt) + next = coderdtest.NextAutostartTick(t, workspace) notStaleTime := next.Add((-1 * provisionerdserver.StaleInterval) + 10*time.Second) coderdtest.UpdateProvisionerLastSeenAt(t, db, p.ID, notStaleTime) // Require that the provisioner time has actually been updated to the expected value. @@ -2051,8 +2051,8 @@ func TestExecutorTaskWorkspace(t *testing.T) { require.NoError(t, err) // When: the autobuild executor ticks after the scheduled time + tickTime := coderdtest.NextAutostartTick(t, workspace) go func() { - tickTime := sched.Next(workspace.LatestBuild.CreatedAt) coderdtest.UpdateProvisionerLastSeenAt(t, db, p.ID, tickTime) tickCh <- tickTime close(tickCh) diff --git a/coderd/coderdtest/coderdtest.go b/coderd/coderdtest/coderdtest.go index 94c6fde72f603..5b46450c3cdd4 100644 --- a/coderd/coderdtest/coderdtest.go +++ b/coderd/coderdtest/coderdtest.go @@ -1843,6 +1843,18 @@ func UpdateProvisionerLastSeenAt(t *testing.T, db database.Store, id uuid.UUID, t.Logf("Successfully updated provisioner LastSeenAt") } +// NextAutostartTick returns workspace.NextStartAt for use as the autobuild +// tick. The executor's eligibility query checks next_start_at <= tick. +// Computing from build.CreatedAt is racy: next_start_at derives from build +// completion time, so it can advance past sched.Next(build.CreatedAt) and +// the workspace misses the eligibility window. +func NextAutostartTick(t testing.TB, workspace codersdk.Workspace) time.Time { + t.Helper() + require.NotNil(t, workspace.NextStartAt, + "workspace next_start_at is nil; ensure autostart is enabled and the latest build has completed before calling NextAutostartTick") + return *workspace.NextStartAt +} + func MustWaitForAnyProvisioner(t *testing.T, db database.Store) { t.Helper() ctx := ctxWithProvisionerPermissions(testutil.Context(t, testutil.WaitShort)) diff --git a/coderd/workspaces_test.go b/coderd/workspaces_test.go index b03253b76ba6a..c3731496c8a6f 100644 --- a/coderd/workspaces_test.go +++ b/coderd/workspaces_test.go @@ -6212,8 +6212,8 @@ func TestWorkspaceBuildsEnqueuedMetric(t *testing.T) { p, err := coderdtest.GetProvisionerForTags(db, time.Now(), workspace.OrganizationID, map[string]string{}) require.NoError(t, err) + tickTime := coderdtest.NextAutostartTick(t, workspace) go func() { - tickTime := sched.Next(workspace.LatestBuild.CreatedAt) coderdtest.UpdateProvisionerLastSeenAt(t, db, p.ID, tickTime) tickCh <- tickTime close(tickCh) diff --git a/enterprise/coderd/workspaces_test.go b/enterprise/coderd/workspaces_test.go index 95bf50e74fda0..ef71a7227ecaf 100644 --- a/enterprise/coderd/workspaces_test.go +++ b/enterprise/coderd/workspaces_test.go @@ -1315,7 +1315,7 @@ func TestWorkspaceAutobuild(t *testing.T) { ws = coderdtest.MustTransitionWorkspace(t, client, ws.ID, codersdk.WorkspaceTransitionStart, codersdk.WorkspaceTransitionStop) // Assert that autostart works when the workspace isn't dormant.. - tickTime := sched.Next(ws.LatestBuild.CreatedAt) + tickTime := coderdtest.NextAutostartTick(t, ws) p, err := coderdtest.GetProvisionerForTags(db, time.Now(), ws.OrganizationID, nil) require.NoError(t, err) coderdtest.UpdateProvisionerLastSeenAt(t, db, p.ID, tickTime) @@ -1518,7 +1518,7 @@ func TestWorkspaceAutobuild(t *testing.T) { require.NoError(t, err) // Kick of an autostart build. - tickTime := sched.Next(ws.LatestBuild.CreatedAt) + tickTime := coderdtest.NextAutostartTick(t, ws) p, err := coderdtest.GetProvisionerForTags(db, time.Now(), ws.OrganizationID, nil) require.NoError(t, err) coderdtest.UpdateProvisionerLastSeenAt(t, db, p.ID, tickTime) @@ -1545,12 +1545,12 @@ func TestWorkspaceAutobuild(t *testing.T) { // Reset the workspace to the stopped state so we can try // to autostart again. - coderdtest.MustTransitionWorkspace(t, client, ws.ID, codersdk.WorkspaceTransitionStart, codersdk.WorkspaceTransitionStop, func(req *codersdk.CreateWorkspaceBuildRequest) { + ws = coderdtest.MustTransitionWorkspace(t, client, ws.ID, codersdk.WorkspaceTransitionStart, codersdk.WorkspaceTransitionStop, func(req *codersdk.CreateWorkspaceBuildRequest) { req.TemplateVersionID = ws.LatestBuild.TemplateVersionID }) // Force an autostart transition again. - tickTime2 := sched.Next(firstBuild.CreatedAt) + tickTime2 := coderdtest.NextAutostartTick(t, ws) coderdtest.UpdateProvisionerLastSeenAt(t, db, p.ID, tickTime2) tickCh <- tickTime2 stats = <-statsCh