mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: add activity bumping to template scheduling (#9040)
This commit is contained in:
@@ -113,11 +113,11 @@ func (s *EnterpriseTemplateScheduleStore) Set(ctx context.Context, db database.S
|
||||
}
|
||||
|
||||
var template database.Template
|
||||
err = db.InTx(func(db database.Store) error {
|
||||
err = db.InTx(func(tx database.Store) error {
|
||||
ctx, span := tracing.StartSpanWithName(ctx, "(*schedule.EnterpriseTemplateScheduleStore).Set()-InTx()")
|
||||
defer span.End()
|
||||
|
||||
err := db.UpdateTemplateScheduleByID(ctx, database.UpdateTemplateScheduleByIDParams{
|
||||
err := tx.UpdateTemplateScheduleByID(ctx, database.UpdateTemplateScheduleByIDParams{
|
||||
ID: tpl.ID,
|
||||
UpdatedAt: s.now(),
|
||||
AllowUserAutostart: opts.UserAutostartEnabled,
|
||||
@@ -134,19 +134,36 @@ func (s *EnterpriseTemplateScheduleStore) Set(ctx context.Context, db database.S
|
||||
return xerrors.Errorf("update template schedule: %w", err)
|
||||
}
|
||||
|
||||
var lockedAt time.Time
|
||||
if opts.UpdateWorkspaceLockedAt {
|
||||
lockedAt = database.Now()
|
||||
}
|
||||
|
||||
// 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{
|
||||
err = tx.UpdateWorkspacesLockedDeletingAtByTemplateID(ctx, database.UpdateWorkspacesLockedDeletingAtByTemplateIDParams{
|
||||
TemplateID: tpl.ID,
|
||||
LockedTtlMs: opts.LockedTTL.Milliseconds(),
|
||||
LockedAt: lockedAt,
|
||||
})
|
||||
if err != nil {
|
||||
return xerrors.Errorf("update deleting_at of all workspaces for new locked_ttl %q: %w", opts.LockedTTL, err)
|
||||
}
|
||||
|
||||
template, err = db.GetTemplateByID(ctx, tpl.ID)
|
||||
if opts.UpdateWorkspaceLastUsedAt {
|
||||
err = tx.UpdateTemplateWorkspacesLastUsedAt(ctx, database.UpdateTemplateWorkspacesLastUsedAtParams{
|
||||
TemplateID: tpl.ID,
|
||||
LastUsedAt: database.Now(),
|
||||
})
|
||||
if err != nil {
|
||||
return xerrors.Errorf("update template workspaces last_used_at: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: update all workspace max_deadlines to be within new bounds
|
||||
template, err = tx.GetTemplateByID(ctx, tpl.ID)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("get updated template schedule: %w", err)
|
||||
}
|
||||
@@ -154,7 +171,7 @@ func (s *EnterpriseTemplateScheduleStore) Set(ctx context.Context, db database.S
|
||||
// Recalculate max_deadline and deadline for all running workspace
|
||||
// builds on this template.
|
||||
if s.UseRestartRequirement.Load() {
|
||||
err = s.updateWorkspaceBuilds(ctx, db, template)
|
||||
err = s.updateWorkspaceBuilds(ctx, tx, template)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("update workspace builds: %w", err)
|
||||
}
|
||||
|
||||
@@ -283,10 +283,11 @@ func TestTemplates(t *testing.T) {
|
||||
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)
|
||||
updatedLockedWorkspace := coderdtest.MustWorkspace(t, client, lockedWorkspace.ID)
|
||||
require.NotNil(t, updatedLockedWorkspace.LockedAt)
|
||||
require.NotNil(t, updatedLockedWorkspace.DeletingAt)
|
||||
require.Equal(t, updatedLockedWorkspace.LockedAt.Add(lockedTTL), *updatedLockedWorkspace.DeletingAt)
|
||||
require.Equal(t, updatedLockedWorkspace.LockedAt, lockedWorkspace.LockedAt)
|
||||
|
||||
// Disable the locked_ttl on the template, then we can assert that the workspaces
|
||||
// no longer have a deleting_at field.
|
||||
@@ -307,6 +308,119 @@ func TestTemplates(t *testing.T) {
|
||||
require.NotNil(t, lockedWorkspace.LockedAt)
|
||||
require.Nil(t, lockedWorkspace.DeletingAt)
|
||||
})
|
||||
|
||||
t.Run("UpdateLockedAt", 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(),
|
||||
UpdateWorkspaceLockedAt: true,
|
||||
})
|
||||
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)
|
||||
|
||||
updatedLockedWorkspace := coderdtest.MustWorkspace(t, client, lockedWorkspace.ID)
|
||||
require.NotNil(t, updatedLockedWorkspace.LockedAt)
|
||||
require.NotNil(t, updatedLockedWorkspace.DeletingAt)
|
||||
// Validate that the workspace locked_at value is updated.
|
||||
require.True(t, updatedLockedWorkspace.LockedAt.After(*lockedWorkspace.LockedAt))
|
||||
require.Equal(t, updatedLockedWorkspace.LockedAt.Add(lockedTTL), *updatedLockedWorkspace.DeletingAt)
|
||||
})
|
||||
|
||||
t.Run("UpdateLastUsedAt", 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)
|
||||
|
||||
inactivityTTL := time.Minute
|
||||
updated, err := client.UpdateTemplateMeta(ctx, template.ID, codersdk.UpdateTemplateMeta{
|
||||
InactivityTTLMillis: inactivityTTL.Milliseconds(),
|
||||
UpdateWorkspaceLastUsedAt: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, inactivityTTL.Milliseconds(), updated.InactivityTTLMillis)
|
||||
|
||||
updatedUnlockedWS := coderdtest.MustWorkspace(t, client, unlockedWorkspace.ID)
|
||||
require.Nil(t, updatedUnlockedWS.LockedAt)
|
||||
require.Nil(t, updatedUnlockedWS.DeletingAt)
|
||||
require.True(t, updatedUnlockedWS.LastUsedAt.After(unlockedWorkspace.LastUsedAt))
|
||||
|
||||
updatedLockedWorkspace := coderdtest.MustWorkspace(t, client, lockedWorkspace.ID)
|
||||
require.NotNil(t, updatedLockedWorkspace.LockedAt)
|
||||
require.Nil(t, updatedLockedWorkspace.DeletingAt)
|
||||
// Validate that the workspace locked_at value is updated.
|
||||
require.Equal(t, updatedLockedWorkspace.LockedAt, lockedWorkspace.LockedAt)
|
||||
require.True(t, updatedLockedWorkspace.LastUsedAt.After(lockedWorkspace.LastUsedAt))
|
||||
})
|
||||
}
|
||||
|
||||
func TestTemplateACL(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user