mirror of
https://github.com/coder/coder.git
synced 2025-07-06 15:41:45 +00:00
feat: autostop workspaces owned by suspended users (#13790)
This commit is contained in:
@ -316,7 +316,7 @@ func getNextTransition(
|
||||
error,
|
||||
) {
|
||||
switch {
|
||||
case isEligibleForAutostop(ws, latestBuild, latestJob, currentTick):
|
||||
case isEligibleForAutostop(user, ws, latestBuild, latestJob, currentTick):
|
||||
return database.WorkspaceTransitionStop, database.BuildReasonAutostop, nil
|
||||
case isEligibleForAutostart(user, ws, latestBuild, latestJob, templateSchedule, currentTick):
|
||||
return database.WorkspaceTransitionStart, database.BuildReasonAutostart, nil
|
||||
@ -376,8 +376,8 @@ func isEligibleForAutostart(user database.User, ws database.Workspace, build dat
|
||||
return !currentTick.Before(nextTransition)
|
||||
}
|
||||
|
||||
// isEligibleForAutostart returns true if the workspace should be autostopped.
|
||||
func isEligibleForAutostop(ws database.Workspace, build database.WorkspaceBuild, job database.ProvisionerJob, currentTick time.Time) bool {
|
||||
// isEligibleForAutostop returns true if the workspace should be autostopped.
|
||||
func isEligibleForAutostop(user database.User, ws database.Workspace, build database.WorkspaceBuild, job database.ProvisionerJob, currentTick time.Time) bool {
|
||||
if job.JobStatus == database.ProvisionerJobStatusFailed {
|
||||
return false
|
||||
}
|
||||
@ -387,6 +387,10 @@ func isEligibleForAutostop(ws database.Workspace, build database.WorkspaceBuild,
|
||||
return false
|
||||
}
|
||||
|
||||
if build.Transition == database.WorkspaceTransitionStart && user.Status == database.UserStatusSuspended {
|
||||
return true
|
||||
}
|
||||
|
||||
// A workspace must be started in order for it to be auto-stopped.
|
||||
return build.Transition == database.WorkspaceTransitionStart &&
|
||||
!build.Deadline.IsZero() &&
|
||||
|
@ -563,6 +563,52 @@ func TestExecutorWorkspaceAutostopBeforeDeadline(t *testing.T) {
|
||||
assert.Len(t, stats.Transitions, 0)
|
||||
}
|
||||
|
||||
func TestExecuteAutostopSuspendedUser(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var (
|
||||
ctx = testutil.Context(t, testutil.WaitShort)
|
||||
tickCh = make(chan time.Time)
|
||||
statsCh = make(chan autobuild.Stats)
|
||||
client = coderdtest.New(t, &coderdtest.Options{
|
||||
AutobuildTicker: tickCh,
|
||||
IncludeProvisionerDaemon: true,
|
||||
AutobuildStats: statsCh,
|
||||
})
|
||||
)
|
||||
|
||||
admin := coderdtest.CreateFirstUser(t, client)
|
||||
version := coderdtest.CreateTemplateVersion(t, client, admin.OrganizationID, nil)
|
||||
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
||||
template := coderdtest.CreateTemplate(t, client, admin.OrganizationID, version.ID)
|
||||
userClient, user := coderdtest.CreateAnotherUser(t, client, admin.OrganizationID)
|
||||
workspace := coderdtest.CreateWorkspace(t, userClient, admin.OrganizationID, template.ID)
|
||||
coderdtest.AwaitWorkspaceBuildJobCompleted(t, userClient, workspace.LatestBuild.ID)
|
||||
|
||||
// Given: workspace is running, and the user is suspended.
|
||||
workspace = coderdtest.MustWorkspace(t, userClient, workspace.ID)
|
||||
require.Equal(t, codersdk.WorkspaceStatusRunning, workspace.LatestBuild.Status)
|
||||
_, err := client.UpdateUserStatus(ctx, user.ID.String(), codersdk.UserStatusSuspended)
|
||||
require.NoError(t, err, "update user status")
|
||||
|
||||
// When: the autobuild executor ticks after the scheduled time
|
||||
go func() {
|
||||
tickCh <- time.Unix(0, 0) // the exact time is not important
|
||||
close(tickCh)
|
||||
}()
|
||||
|
||||
// Then: the workspace should be stopped
|
||||
stats := <-statsCh
|
||||
assert.Len(t, stats.Errors, 0)
|
||||
assert.Len(t, stats.Transitions, 1)
|
||||
assert.Equal(t, stats.Transitions[workspace.ID], database.WorkspaceTransitionStop)
|
||||
|
||||
// Wait for stop to complete
|
||||
workspace = coderdtest.MustWorkspace(t, client, workspace.ID)
|
||||
workspaceBuild := coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, workspace.LatestBuild.ID)
|
||||
assert.Equal(t, codersdk.WorkspaceStatusStopped, workspaceBuild.Status)
|
||||
}
|
||||
|
||||
func TestExecutorWorkspaceAutostopNoWaitChangedMyMind(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
@ -5844,6 +5844,15 @@ func (q *FakeQuerier) GetWorkspacesEligibleForTransition(ctx context.Context, no
|
||||
workspaces = append(workspaces, workspace)
|
||||
continue
|
||||
}
|
||||
|
||||
user, err := q.getUserByIDNoLock(workspace.OwnerID)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("get user by ID: %w", err)
|
||||
}
|
||||
if user.Status == database.UserStatusSuspended && build.Transition == database.WorkspaceTransitionStart {
|
||||
workspaces = append(workspaces, workspace)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
return workspaces, nil
|
||||
|
@ -13426,6 +13426,8 @@ INNER JOIN
|
||||
provisioner_jobs ON workspace_builds.job_id = provisioner_jobs.id
|
||||
INNER JOIN
|
||||
templates ON workspaces.template_id = templates.id
|
||||
INNER JOIN
|
||||
users ON workspaces.owner_id = users.id
|
||||
WHERE
|
||||
workspace_builds.build_number = (
|
||||
SELECT
|
||||
@ -13477,6 +13479,12 @@ WHERE
|
||||
(
|
||||
templates.time_til_dormant_autodelete > 0 AND
|
||||
workspaces.dormant_at IS NOT NULL
|
||||
) OR
|
||||
|
||||
-- If the user account is suspended, and the workspace is running.
|
||||
(
|
||||
users.status = 'suspended'::user_status AND
|
||||
workspace_builds.transition = 'start'::workspace_transition
|
||||
)
|
||||
) AND workspaces.deleted = 'false'
|
||||
`
|
||||
|
@ -557,6 +557,8 @@ INNER JOIN
|
||||
provisioner_jobs ON workspace_builds.job_id = provisioner_jobs.id
|
||||
INNER JOIN
|
||||
templates ON workspaces.template_id = templates.id
|
||||
INNER JOIN
|
||||
users ON workspaces.owner_id = users.id
|
||||
WHERE
|
||||
workspace_builds.build_number = (
|
||||
SELECT
|
||||
@ -608,6 +610,12 @@ WHERE
|
||||
(
|
||||
templates.time_til_dormant_autodelete > 0 AND
|
||||
workspaces.dormant_at IS NOT NULL
|
||||
) OR
|
||||
|
||||
-- If the user account is suspended, and the workspace is running.
|
||||
(
|
||||
users.status = 'suspended'::user_status AND
|
||||
workspace_builds.transition = 'start'::workspace_transition
|
||||
)
|
||||
) AND workspaces.deleted = 'false';
|
||||
|
||||
|
Reference in New Issue
Block a user