mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: add deleting_at column to workspaces (#8333)
This commit is contained in:
@@ -103,8 +103,19 @@ func (*EnterpriseTemplateScheduleStore) Set(ctx context.Context, db database.Sto
|
||||
return xerrors.Errorf("update template schedule: %w", err)
|
||||
}
|
||||
|
||||
// TODO: update all workspace max_deadlines to be within new bounds
|
||||
// If we updated the locked_ttl we need to update all the workspaces deleting_at
|
||||
// to ensure workspaces are being cleaned up correctly. Similarly if we are
|
||||
// disabling it (by passing 0), then we want to delete nullify the deleting_at
|
||||
// fields of all the template workspaces.
|
||||
err = db.UpdateWorkspacesDeletingAtByTemplateID(ctx, database.UpdateWorkspacesDeletingAtByTemplateIDParams{
|
||||
TemplateID: tpl.ID,
|
||||
LockedTtlMs: opts.LockedTTL.Milliseconds(),
|
||||
})
|
||||
if err != nil {
|
||||
return xerrors.Errorf("update deleting_at of all workspaces for new locked_ttl %q: %w", opts.LockedTTL, err)
|
||||
}
|
||||
|
||||
// TODO: update all workspace max_deadlines to be within new bounds
|
||||
template, err = db.GetTemplateByID(ctx, tpl.ID)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("get updated template schedule: %w", err)
|
||||
|
||||
@@ -235,6 +235,78 @@ func TestTemplates(t *testing.T) {
|
||||
require.Equal(t, inactivityTTL, updated.InactivityTTLMillis)
|
||||
require.Equal(t, lockedTTL, updated.LockedTTLMillis)
|
||||
})
|
||||
|
||||
t.Run("UpdateLockedTTL", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx := testutil.Context(t, testutil.WaitMedium)
|
||||
client, user := coderdenttest.New(t, &coderdenttest.Options{
|
||||
Options: &coderdtest.Options{
|
||||
IncludeProvisionerDaemon: true,
|
||||
},
|
||||
LicenseOptions: &coderdenttest.LicenseOptions{
|
||||
Features: license.Features{
|
||||
codersdk.FeatureAdvancedTemplateScheduling: 1,
|
||||
},
|
||||
},
|
||||
})
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
|
||||
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
|
||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
||||
|
||||
unlockedWorkspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
|
||||
lockedWorkspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
|
||||
require.Nil(t, unlockedWorkspace.DeletingAt)
|
||||
require.Nil(t, lockedWorkspace.DeletingAt)
|
||||
|
||||
_ = coderdtest.AwaitWorkspaceBuildJob(t, client, unlockedWorkspace.LatestBuild.ID)
|
||||
_ = coderdtest.AwaitWorkspaceBuildJob(t, client, lockedWorkspace.LatestBuild.ID)
|
||||
|
||||
err := client.UpdateWorkspaceLock(ctx, lockedWorkspace.ID, codersdk.UpdateWorkspaceLock{
|
||||
Lock: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
lockedWorkspace = coderdtest.MustWorkspace(t, client, lockedWorkspace.ID)
|
||||
require.NotNil(t, lockedWorkspace.LockedAt)
|
||||
// The deleting_at field should be nil since there is no template locked_ttl set.
|
||||
require.Nil(t, lockedWorkspace.DeletingAt)
|
||||
|
||||
lockedTTL := time.Minute
|
||||
updated, err := client.UpdateTemplateMeta(ctx, template.ID, codersdk.UpdateTemplateMeta{
|
||||
LockedTTLMillis: lockedTTL.Milliseconds(),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, lockedTTL.Milliseconds(), updated.LockedTTLMillis)
|
||||
|
||||
unlockedWorkspace = coderdtest.MustWorkspace(t, client, unlockedWorkspace.ID)
|
||||
require.Nil(t, unlockedWorkspace.LockedAt)
|
||||
require.Nil(t, unlockedWorkspace.DeletingAt)
|
||||
|
||||
lockedWorkspace = coderdtest.MustWorkspace(t, client, lockedWorkspace.ID)
|
||||
require.NotNil(t, lockedWorkspace.LockedAt)
|
||||
require.NotNil(t, lockedWorkspace.DeletingAt)
|
||||
require.Equal(t, lockedWorkspace.LockedAt.Add(lockedTTL), *lockedWorkspace.DeletingAt)
|
||||
|
||||
// Disable the locked_ttl on the template, then we can assert that the workspaces
|
||||
// no longer have a deleting_at field.
|
||||
updated, err = client.UpdateTemplateMeta(ctx, template.ID, codersdk.UpdateTemplateMeta{
|
||||
LockedTTLMillis: 0,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, 0, updated.LockedTTLMillis)
|
||||
|
||||
// The unlocked workspace should remain unchanged.
|
||||
unlockedWorkspace = coderdtest.MustWorkspace(t, client, unlockedWorkspace.ID)
|
||||
require.Nil(t, unlockedWorkspace.LockedAt)
|
||||
require.Nil(t, unlockedWorkspace.DeletingAt)
|
||||
|
||||
// Fetch the locked workspace. It should still be locked, but it should no
|
||||
// longer be scheduled for deletion.
|
||||
lockedWorkspace = coderdtest.MustWorkspace(t, client, lockedWorkspace.ID)
|
||||
require.NotNil(t, lockedWorkspace.LockedAt)
|
||||
require.Nil(t, lockedWorkspace.DeletingAt)
|
||||
})
|
||||
}
|
||||
|
||||
func TestTemplateACL(t *testing.T) {
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"cdr.dev/slog/sloggers/slogtest"
|
||||
@@ -625,10 +624,10 @@ func TestWorkspaceAutobuild(t *testing.T) {
|
||||
func TestWorkspacesFiltering(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("FilterQueryHasDeletingByAndLicensed", func(t *testing.T) {
|
||||
t.Run("DeletingBy", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
inactivityTTL := 1 * 24 * time.Hour
|
||||
lockedTTL := 24 * time.Hour
|
||||
|
||||
client, user := coderdenttest.New(t, &coderdenttest.Options{
|
||||
Options: &coderdtest.Options{
|
||||
@@ -642,34 +641,103 @@ func TestWorkspacesFiltering(t *testing.T) {
|
||||
})
|
||||
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
|
||||
_ = coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
|
||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
||||
|
||||
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
|
||||
|
||||
// update template with inactivity ttl
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||
defer cancel()
|
||||
|
||||
template, err := client.UpdateTemplateMeta(ctx, template.ID, codersdk.UpdateTemplateMeta{
|
||||
InactivityTTLMillis: inactivityTTL.Milliseconds(),
|
||||
LockedTTLMillis: lockedTTL.Milliseconds(),
|
||||
})
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, inactivityTTL.Milliseconds(), template.InactivityTTLMillis)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, lockedTTL.Milliseconds(), template.LockedTTLMillis)
|
||||
|
||||
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
|
||||
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
|
||||
_ = coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
|
||||
|
||||
// stop build so workspace is inactive
|
||||
stopBuild := coderdtest.CreateWorkspaceBuild(t, client, workspace, database.WorkspaceTransitionStop)
|
||||
coderdtest.AwaitWorkspaceBuildJob(t, client, stopBuild.ID)
|
||||
err = client.UpdateWorkspaceLock(ctx, workspace.ID, codersdk.UpdateWorkspaceLock{
|
||||
Lock: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
workspace = coderdtest.MustWorkspace(t, client, workspace.ID)
|
||||
require.NotNil(t, workspace.DeletingAt)
|
||||
|
||||
res, err := client.Workspaces(ctx, codersdk.WorkspaceFilter{
|
||||
// adding a second to time.Now() to give some buffer in case test runs quickly
|
||||
FilterQuery: fmt.Sprintf("deleting_by:%s", time.Now().Add(time.Second).Add(inactivityTTL).Format("2006-01-02")),
|
||||
FilterQuery: fmt.Sprintf("deleting_by:%s", time.Now().Add(time.Second).Add(lockedTTL).Format("2006-01-02")),
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, res.Workspaces, 1)
|
||||
assert.Equal(t, workspace.ID, res.Workspaces[0].ID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, res.Workspaces, 1)
|
||||
require.Equal(t, workspace.ID, res.Workspaces[0].ID)
|
||||
})
|
||||
}
|
||||
|
||||
func TestWorkspaceLock(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("TemplateLockedTTL", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
var (
|
||||
client, user = coderdenttest.New(t, &coderdenttest.Options{
|
||||
Options: &coderdtest.Options{
|
||||
IncludeProvisionerDaemon: true,
|
||||
TemplateScheduleStore: &schedule.EnterpriseTemplateScheduleStore{},
|
||||
},
|
||||
LicenseOptions: &coderdenttest.LicenseOptions{
|
||||
Features: license.Features{
|
||||
codersdk.FeatureAdvancedTemplateScheduling: 1,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
version = coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
|
||||
_ = coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
|
||||
lockedTTL = time.Minute
|
||||
)
|
||||
|
||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) {
|
||||
ctr.LockedTTLMillis = ptr.Ref[int64](lockedTTL.Milliseconds())
|
||||
})
|
||||
|
||||
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
|
||||
_ = coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||
defer cancel()
|
||||
|
||||
lastUsedAt := workspace.LastUsedAt
|
||||
err := client.UpdateWorkspaceLock(ctx, workspace.ID, codersdk.UpdateWorkspaceLock{
|
||||
Lock: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
workspace = coderdtest.MustWorkspace(t, client, workspace.ID)
|
||||
require.NoError(t, err, "fetch provisioned workspace")
|
||||
require.NotNil(t, workspace.DeletingAt)
|
||||
require.NotNil(t, workspace.LockedAt)
|
||||
require.Equal(t, workspace.LockedAt.Add(lockedTTL), *workspace.DeletingAt)
|
||||
require.WithinRange(t, *workspace.LockedAt, time.Now().Add(-time.Second*10), time.Now())
|
||||
// Locking a workspace shouldn't update the last_used_at.
|
||||
require.Equal(t, lastUsedAt, workspace.LastUsedAt)
|
||||
|
||||
workspace = coderdtest.MustWorkspace(t, client, workspace.ID)
|
||||
lastUsedAt = workspace.LastUsedAt
|
||||
err = client.UpdateWorkspaceLock(ctx, workspace.ID, codersdk.UpdateWorkspaceLock{
|
||||
Lock: false,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
workspace, err = client.Workspace(ctx, workspace.ID)
|
||||
require.NoError(t, err, "fetch provisioned workspace")
|
||||
require.Nil(t, workspace.LockedAt)
|
||||
// Unlocking a workspace should cause the deleting_at to be unset.
|
||||
require.Nil(t, workspace.DeletingAt)
|
||||
// The last_used_at should get updated when we unlock the workspace.
|
||||
require.True(t, workspace.LastUsedAt.After(lastUsedAt))
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user