|
|
|
@ -2,11 +2,13 @@ package coderd_test
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"database/sql"
|
|
|
|
|
"net/http"
|
|
|
|
|
"sync/atomic"
|
|
|
|
|
"testing"
|
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
|
|
|
|
|
|
"cdr.dev/slog"
|
|
|
|
@ -17,8 +19,10 @@ import (
|
|
|
|
|
"github.com/coder/coder/v2/coderd/autobuild"
|
|
|
|
|
"github.com/coder/coder/v2/coderd/coderdtest"
|
|
|
|
|
"github.com/coder/coder/v2/coderd/database"
|
|
|
|
|
"github.com/coder/coder/v2/coderd/database/dbauthz"
|
|
|
|
|
"github.com/coder/coder/v2/coderd/database/dbfake"
|
|
|
|
|
"github.com/coder/coder/v2/coderd/database/dbtestutil"
|
|
|
|
|
"github.com/coder/coder/v2/coderd/database/dbtime"
|
|
|
|
|
"github.com/coder/coder/v2/coderd/notifications"
|
|
|
|
|
"github.com/coder/coder/v2/coderd/rbac"
|
|
|
|
|
agplschedule "github.com/coder/coder/v2/coderd/schedule"
|
|
|
|
@ -32,6 +36,7 @@ import (
|
|
|
|
|
"github.com/coder/coder/v2/enterprise/coderd/schedule"
|
|
|
|
|
"github.com/coder/coder/v2/provisioner/echo"
|
|
|
|
|
"github.com/coder/coder/v2/testutil"
|
|
|
|
|
"github.com/coder/quartz"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// agplUserQuietHoursScheduleStore is passed to
|
|
|
|
@ -295,7 +300,7 @@ func TestWorkspaceAutobuild(t *testing.T) {
|
|
|
|
|
AutobuildTicker: ticker,
|
|
|
|
|
IncludeProvisionerDaemon: true,
|
|
|
|
|
AutobuildStats: statCh,
|
|
|
|
|
TemplateScheduleStore: schedule.NewEnterpriseTemplateScheduleStore(agplUserQuietHoursScheduleStore(), notifications.NewNoopEnqueuer(), logger),
|
|
|
|
|
TemplateScheduleStore: schedule.NewEnterpriseTemplateScheduleStore(agplUserQuietHoursScheduleStore(), notifications.NewNoopEnqueuer(), logger, nil),
|
|
|
|
|
},
|
|
|
|
|
LicenseOptions: &coderdenttest.LicenseOptions{
|
|
|
|
|
Features: license.Features{codersdk.FeatureAdvancedTemplateScheduling: 1},
|
|
|
|
@ -342,7 +347,7 @@ func TestWorkspaceAutobuild(t *testing.T) {
|
|
|
|
|
AutobuildTicker: ticker,
|
|
|
|
|
IncludeProvisionerDaemon: true,
|
|
|
|
|
AutobuildStats: statCh,
|
|
|
|
|
TemplateScheduleStore: schedule.NewEnterpriseTemplateScheduleStore(agplUserQuietHoursScheduleStore(), notifications.NewNoopEnqueuer(), logger),
|
|
|
|
|
TemplateScheduleStore: schedule.NewEnterpriseTemplateScheduleStore(agplUserQuietHoursScheduleStore(), notifications.NewNoopEnqueuer(), logger, nil),
|
|
|
|
|
},
|
|
|
|
|
LicenseOptions: &coderdenttest.LicenseOptions{
|
|
|
|
|
Features: license.Features{codersdk.FeatureAdvancedTemplateScheduling: 1},
|
|
|
|
@ -388,7 +393,7 @@ func TestWorkspaceAutobuild(t *testing.T) {
|
|
|
|
|
AutobuildTicker: ticker,
|
|
|
|
|
IncludeProvisionerDaemon: true,
|
|
|
|
|
AutobuildStats: statCh,
|
|
|
|
|
TemplateScheduleStore: schedule.NewEnterpriseTemplateScheduleStore(agplUserQuietHoursScheduleStore(), notifications.NewNoopEnqueuer(), logger),
|
|
|
|
|
TemplateScheduleStore: schedule.NewEnterpriseTemplateScheduleStore(agplUserQuietHoursScheduleStore(), notifications.NewNoopEnqueuer(), logger, nil),
|
|
|
|
|
},
|
|
|
|
|
LicenseOptions: &coderdenttest.LicenseOptions{
|
|
|
|
|
Features: license.Features{codersdk.FeatureAdvancedTemplateScheduling: 1},
|
|
|
|
@ -432,7 +437,7 @@ func TestWorkspaceAutobuild(t *testing.T) {
|
|
|
|
|
Options: &coderdtest.Options{
|
|
|
|
|
AutobuildTicker: ticker,
|
|
|
|
|
AutobuildStats: statCh,
|
|
|
|
|
TemplateScheduleStore: schedule.NewEnterpriseTemplateScheduleStore(agplUserQuietHoursScheduleStore(), notifications.NewNoopEnqueuer(), logger),
|
|
|
|
|
TemplateScheduleStore: schedule.NewEnterpriseTemplateScheduleStore(agplUserQuietHoursScheduleStore(), notifications.NewNoopEnqueuer(), logger, nil),
|
|
|
|
|
Auditor: auditRecorder,
|
|
|
|
|
},
|
|
|
|
|
LicenseOptions: &coderdenttest.LicenseOptions{
|
|
|
|
@ -527,7 +532,7 @@ func TestWorkspaceAutobuild(t *testing.T) {
|
|
|
|
|
Options: &coderdtest.Options{
|
|
|
|
|
AutobuildTicker: ticker,
|
|
|
|
|
AutobuildStats: statCh,
|
|
|
|
|
TemplateScheduleStore: schedule.NewEnterpriseTemplateScheduleStore(agplUserQuietHoursScheduleStore(), notifications.NewNoopEnqueuer(), logger),
|
|
|
|
|
TemplateScheduleStore: schedule.NewEnterpriseTemplateScheduleStore(agplUserQuietHoursScheduleStore(), notifications.NewNoopEnqueuer(), logger, nil),
|
|
|
|
|
Database: db,
|
|
|
|
|
Pubsub: pubsub,
|
|
|
|
|
Auditor: auditor,
|
|
|
|
@ -585,7 +590,7 @@ func TestWorkspaceAutobuild(t *testing.T) {
|
|
|
|
|
AutobuildTicker: ticker,
|
|
|
|
|
IncludeProvisionerDaemon: true,
|
|
|
|
|
AutobuildStats: statCh,
|
|
|
|
|
TemplateScheduleStore: schedule.NewEnterpriseTemplateScheduleStore(agplUserQuietHoursScheduleStore(), notifications.NewNoopEnqueuer(), logger),
|
|
|
|
|
TemplateScheduleStore: schedule.NewEnterpriseTemplateScheduleStore(agplUserQuietHoursScheduleStore(), notifications.NewNoopEnqueuer(), logger, nil),
|
|
|
|
|
},
|
|
|
|
|
LicenseOptions: &coderdenttest.LicenseOptions{
|
|
|
|
|
Features: license.Features{codersdk.FeatureAdvancedTemplateScheduling: 1},
|
|
|
|
@ -628,7 +633,7 @@ func TestWorkspaceAutobuild(t *testing.T) {
|
|
|
|
|
AutobuildTicker: ticker,
|
|
|
|
|
IncludeProvisionerDaemon: true,
|
|
|
|
|
AutobuildStats: statCh,
|
|
|
|
|
TemplateScheduleStore: schedule.NewEnterpriseTemplateScheduleStore(agplUserQuietHoursScheduleStore(), notifications.NewNoopEnqueuer(), logger),
|
|
|
|
|
TemplateScheduleStore: schedule.NewEnterpriseTemplateScheduleStore(agplUserQuietHoursScheduleStore(), notifications.NewNoopEnqueuer(), logger, nil),
|
|
|
|
|
},
|
|
|
|
|
LicenseOptions: &coderdenttest.LicenseOptions{
|
|
|
|
|
Features: license.Features{codersdk.FeatureAdvancedTemplateScheduling: 1},
|
|
|
|
@ -671,7 +676,7 @@ func TestWorkspaceAutobuild(t *testing.T) {
|
|
|
|
|
AutobuildTicker: ticker,
|
|
|
|
|
IncludeProvisionerDaemon: true,
|
|
|
|
|
AutobuildStats: statCh,
|
|
|
|
|
TemplateScheduleStore: schedule.NewEnterpriseTemplateScheduleStore(agplUserQuietHoursScheduleStore(), notifications.NewNoopEnqueuer(), logger),
|
|
|
|
|
TemplateScheduleStore: schedule.NewEnterpriseTemplateScheduleStore(agplUserQuietHoursScheduleStore(), notifications.NewNoopEnqueuer(), logger, nil),
|
|
|
|
|
},
|
|
|
|
|
LicenseOptions: &coderdenttest.LicenseOptions{
|
|
|
|
|
Features: license.Features{codersdk.FeatureAdvancedTemplateScheduling: 1},
|
|
|
|
@ -725,7 +730,7 @@ func TestWorkspaceAutobuild(t *testing.T) {
|
|
|
|
|
AutobuildTicker: ticker,
|
|
|
|
|
IncludeProvisionerDaemon: true,
|
|
|
|
|
AutobuildStats: statCh,
|
|
|
|
|
TemplateScheduleStore: schedule.NewEnterpriseTemplateScheduleStore(agplUserQuietHoursScheduleStore(), notifications.NewNoopEnqueuer(), logger),
|
|
|
|
|
TemplateScheduleStore: schedule.NewEnterpriseTemplateScheduleStore(agplUserQuietHoursScheduleStore(), notifications.NewNoopEnqueuer(), logger, nil),
|
|
|
|
|
},
|
|
|
|
|
LicenseOptions: &coderdenttest.LicenseOptions{
|
|
|
|
|
Features: license.Features{codersdk.FeatureAdvancedTemplateScheduling: 1},
|
|
|
|
@ -797,7 +802,7 @@ func TestWorkspaceAutobuild(t *testing.T) {
|
|
|
|
|
AutobuildTicker: ticker,
|
|
|
|
|
IncludeProvisionerDaemon: true,
|
|
|
|
|
AutobuildStats: statCh,
|
|
|
|
|
TemplateScheduleStore: schedule.NewEnterpriseTemplateScheduleStore(agplUserQuietHoursScheduleStore(), notifications.NewNoopEnqueuer(), logger),
|
|
|
|
|
TemplateScheduleStore: schedule.NewEnterpriseTemplateScheduleStore(agplUserQuietHoursScheduleStore(), notifications.NewNoopEnqueuer(), logger, nil),
|
|
|
|
|
},
|
|
|
|
|
LicenseOptions: &coderdenttest.LicenseOptions{
|
|
|
|
|
Features: license.Features{codersdk.FeatureAdvancedTemplateScheduling: 1},
|
|
|
|
@ -861,7 +866,7 @@ func TestWorkspaceAutobuild(t *testing.T) {
|
|
|
|
|
AutobuildTicker: tickCh,
|
|
|
|
|
IncludeProvisionerDaemon: true,
|
|
|
|
|
AutobuildStats: statsCh,
|
|
|
|
|
TemplateScheduleStore: schedule.NewEnterpriseTemplateScheduleStore(agplUserQuietHoursScheduleStore(), notifications.NewNoopEnqueuer(), logger),
|
|
|
|
|
TemplateScheduleStore: schedule.NewEnterpriseTemplateScheduleStore(agplUserQuietHoursScheduleStore(), notifications.NewNoopEnqueuer(), logger, nil),
|
|
|
|
|
},
|
|
|
|
|
LicenseOptions: &coderdenttest.LicenseOptions{
|
|
|
|
|
Features: license.Features{codersdk.FeatureAdvancedTemplateScheduling: 1},
|
|
|
|
@ -941,7 +946,7 @@ func TestWorkspaceAutobuild(t *testing.T) {
|
|
|
|
|
AutobuildTicker: ticker,
|
|
|
|
|
IncludeProvisionerDaemon: true,
|
|
|
|
|
AutobuildStats: statCh,
|
|
|
|
|
TemplateScheduleStore: schedule.NewEnterpriseTemplateScheduleStore(agplUserQuietHoursScheduleStore(), notifications.NewNoopEnqueuer(), logger),
|
|
|
|
|
TemplateScheduleStore: schedule.NewEnterpriseTemplateScheduleStore(agplUserQuietHoursScheduleStore(), notifications.NewNoopEnqueuer(), logger, nil),
|
|
|
|
|
},
|
|
|
|
|
LicenseOptions: &coderdenttest.LicenseOptions{
|
|
|
|
|
Features: license.Features{codersdk.FeatureAdvancedTemplateScheduling: 1},
|
|
|
|
@ -1027,7 +1032,7 @@ func TestWorkspaceAutobuild(t *testing.T) {
|
|
|
|
|
AutobuildTicker: tickCh,
|
|
|
|
|
IncludeProvisionerDaemon: true,
|
|
|
|
|
AutobuildStats: statsCh,
|
|
|
|
|
TemplateScheduleStore: schedule.NewEnterpriseTemplateScheduleStore(agplUserQuietHoursScheduleStore(), notifications.NewNoopEnqueuer(), logger),
|
|
|
|
|
TemplateScheduleStore: schedule.NewEnterpriseTemplateScheduleStore(agplUserQuietHoursScheduleStore(), notifications.NewNoopEnqueuer(), logger, nil),
|
|
|
|
|
},
|
|
|
|
|
LicenseOptions: &coderdenttest.LicenseOptions{
|
|
|
|
|
Features: license.Features{codersdk.FeatureAccessControl: 1},
|
|
|
|
@ -1102,6 +1107,245 @@ func TestWorkspaceAutobuild(t *testing.T) {
|
|
|
|
|
ws = coderdtest.MustWorkspace(t, client, ws.ID)
|
|
|
|
|
require.Equal(t, version2.ID, ws.LatestBuild.TemplateVersionID)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("NextStartAtIsValid", func(t *testing.T) {
|
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
|
|
var (
|
|
|
|
|
tickCh = make(chan time.Time)
|
|
|
|
|
statsCh = make(chan autobuild.Stats)
|
|
|
|
|
clock = quartz.NewMock(t)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// Set the clock to 8AM Monday, 1st January, 2024 to keep
|
|
|
|
|
// this test deterministic.
|
|
|
|
|
clock.Set(time.Date(2024, 1, 1, 8, 0, 0, 0, time.UTC))
|
|
|
|
|
|
|
|
|
|
logger := slogtest.Make(t, &slogtest.Options{IgnoreErrors: true}).Leveled(slog.LevelDebug)
|
|
|
|
|
client, user := coderdenttest.New(t, &coderdenttest.Options{
|
|
|
|
|
Options: &coderdtest.Options{
|
|
|
|
|
AutobuildTicker: tickCh,
|
|
|
|
|
IncludeProvisionerDaemon: true,
|
|
|
|
|
AutobuildStats: statsCh,
|
|
|
|
|
Logger: &logger,
|
|
|
|
|
Clock: clock,
|
|
|
|
|
TemplateScheduleStore: schedule.NewEnterpriseTemplateScheduleStore(agplUserQuietHoursScheduleStore(), notifications.NewNoopEnqueuer(), logger, nil),
|
|
|
|
|
},
|
|
|
|
|
LicenseOptions: &coderdenttest.LicenseOptions{
|
|
|
|
|
Features: license.Features{codersdk.FeatureAdvancedTemplateScheduling: 1},
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
version1 := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
|
|
|
|
|
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version1.ID)
|
|
|
|
|
|
|
|
|
|
// First create a template that only supports Monday-Friday
|
|
|
|
|
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version1.ID, func(ctr *codersdk.CreateTemplateRequest) {
|
|
|
|
|
ctr.AutostartRequirement = &codersdk.TemplateAutostartRequirement{DaysOfWeek: codersdk.BitmapToWeekdays(0b00011111)}
|
|
|
|
|
})
|
|
|
|
|
require.Equal(t, version1.ID, template.ActiveVersionID)
|
|
|
|
|
|
|
|
|
|
// Then create a workspace with a schedule Sunday-Saturday
|
|
|
|
|
sched, err := cron.Weekly("CRON_TZ=UTC 0 9 * * 0-6")
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
ws := coderdtest.CreateWorkspace(t, client, template.ID, func(cwr *codersdk.CreateWorkspaceRequest) {
|
|
|
|
|
cwr.AutostartSchedule = ptr.Ref(sched.String())
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, ws.LatestBuild.ID)
|
|
|
|
|
ws = coderdtest.MustTransitionWorkspace(t, client, ws.ID, database.WorkspaceTransitionStart, database.WorkspaceTransitionStop)
|
|
|
|
|
next := ws.LatestBuild.CreatedAt
|
|
|
|
|
|
|
|
|
|
// For each day of the week (Monday-Sunday)
|
|
|
|
|
// We iterate through each day of the week to ensure the behavior of each
|
|
|
|
|
// day of the week is as expected.
|
|
|
|
|
for range 7 {
|
|
|
|
|
next = sched.Next(next)
|
|
|
|
|
|
|
|
|
|
clock.Set(next)
|
|
|
|
|
tickCh <- next
|
|
|
|
|
stats := <-statsCh
|
|
|
|
|
ws = coderdtest.MustWorkspace(t, client, ws.ID)
|
|
|
|
|
|
|
|
|
|
// Our cron schedule specifies Sunday-Saturday but the template only allows
|
|
|
|
|
// Monday-Friday so we expect there to be no transitions on the weekend.
|
|
|
|
|
if next.Weekday() == time.Saturday || next.Weekday() == time.Sunday {
|
|
|
|
|
assert.Len(t, stats.Errors, 0)
|
|
|
|
|
assert.Len(t, stats.Transitions, 0)
|
|
|
|
|
|
|
|
|
|
ws = coderdtest.MustWorkspace(t, client, ws.ID)
|
|
|
|
|
} else {
|
|
|
|
|
assert.Len(t, stats.Errors, 0)
|
|
|
|
|
assert.Len(t, stats.Transitions, 1)
|
|
|
|
|
assert.Contains(t, stats.Transitions, ws.ID)
|
|
|
|
|
assert.Equal(t, database.WorkspaceTransitionStart, stats.Transitions[ws.ID])
|
|
|
|
|
|
|
|
|
|
coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, ws.LatestBuild.ID)
|
|
|
|
|
ws = coderdtest.MustTransitionWorkspace(t, client, ws.ID, database.WorkspaceTransitionStart, database.WorkspaceTransitionStop)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Ensure that there is a valid next start at and that is is after
|
|
|
|
|
// the preivous start.
|
|
|
|
|
require.NotNil(t, ws.NextStartAt)
|
|
|
|
|
require.Greater(t, *ws.NextStartAt, next)
|
|
|
|
|
|
|
|
|
|
// Our autostart requirement disallows sundays and saturdays so
|
|
|
|
|
// the next start at should never land on these days.
|
|
|
|
|
require.NotEqual(t, time.Saturday, ws.NextStartAt.Weekday())
|
|
|
|
|
require.NotEqual(t, time.Sunday, ws.NextStartAt.Weekday())
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("NextStartAtIsUpdatedWhenTemplateAutostartRequirementsChange", func(t *testing.T) {
|
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
|
|
var (
|
|
|
|
|
tickCh = make(chan time.Time)
|
|
|
|
|
statsCh = make(chan autobuild.Stats)
|
|
|
|
|
clock = quartz.NewMock(t)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// Set the clock to 8AM Monday, 1st January, 2024 to keep
|
|
|
|
|
// this test deterministic.
|
|
|
|
|
clock.Set(time.Date(2024, 1, 1, 8, 0, 0, 0, time.UTC))
|
|
|
|
|
|
|
|
|
|
logger := slogtest.Make(t, &slogtest.Options{IgnoreErrors: true}).Leveled(slog.LevelDebug)
|
|
|
|
|
templateScheduleStore := schedule.NewEnterpriseTemplateScheduleStore(agplUserQuietHoursScheduleStore(), notifications.NewNoopEnqueuer(), logger, nil)
|
|
|
|
|
templateScheduleStore.Clock = clock
|
|
|
|
|
client, user := coderdenttest.New(t, &coderdenttest.Options{
|
|
|
|
|
Options: &coderdtest.Options{
|
|
|
|
|
AutobuildTicker: tickCh,
|
|
|
|
|
IncludeProvisionerDaemon: true,
|
|
|
|
|
AutobuildStats: statsCh,
|
|
|
|
|
Logger: &logger,
|
|
|
|
|
Clock: clock,
|
|
|
|
|
TemplateScheduleStore: templateScheduleStore,
|
|
|
|
|
},
|
|
|
|
|
LicenseOptions: &coderdenttest.LicenseOptions{
|
|
|
|
|
Features: license.Features{codersdk.FeatureAdvancedTemplateScheduling: 1},
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
version1 := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
|
|
|
|
|
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version1.ID)
|
|
|
|
|
|
|
|
|
|
// First create a template that only supports Monday-Friday
|
|
|
|
|
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version1.ID, func(ctr *codersdk.CreateTemplateRequest) {
|
|
|
|
|
ctr.AllowUserAutostart = ptr.Ref(true)
|
|
|
|
|
ctr.AutostartRequirement = &codersdk.TemplateAutostartRequirement{DaysOfWeek: codersdk.BitmapToWeekdays(0b00011111)}
|
|
|
|
|
})
|
|
|
|
|
require.Equal(t, version1.ID, template.ActiveVersionID)
|
|
|
|
|
|
|
|
|
|
// Then create a workspace with a schedule Monday-Friday
|
|
|
|
|
sched, err := cron.Weekly("CRON_TZ=UTC 0 9 * * 1-5")
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
ws := coderdtest.CreateWorkspace(t, client, template.ID, func(cwr *codersdk.CreateWorkspaceRequest) {
|
|
|
|
|
cwr.AutostartSchedule = ptr.Ref(sched.String())
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, ws.LatestBuild.ID)
|
|
|
|
|
ws = coderdtest.MustTransitionWorkspace(t, client, ws.ID, database.WorkspaceTransitionStart, database.WorkspaceTransitionStop)
|
|
|
|
|
|
|
|
|
|
// Our next start at should be Monday
|
|
|
|
|
require.NotNil(t, ws.NextStartAt)
|
|
|
|
|
require.Equal(t, time.Monday, ws.NextStartAt.Weekday())
|
|
|
|
|
|
|
|
|
|
// Now update the template to only allow Tuesday-Friday
|
|
|
|
|
coderdtest.UpdateTemplateMeta(t, client, template.ID, codersdk.UpdateTemplateMeta{
|
|
|
|
|
AutostartRequirement: &codersdk.TemplateAutostartRequirement{
|
|
|
|
|
DaysOfWeek: codersdk.BitmapToWeekdays(0b00011110),
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// Verify that our next start at has been updated to Tuesday
|
|
|
|
|
ws = coderdtest.MustWorkspace(t, client, ws.ID)
|
|
|
|
|
require.NotNil(t, ws.NextStartAt)
|
|
|
|
|
require.Equal(t, time.Tuesday, ws.NextStartAt.Weekday())
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("NextStartAtIsNullifiedOnScheduleChange", func(t *testing.T) {
|
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
|
|
if !dbtestutil.WillUsePostgres() {
|
|
|
|
|
t.Skip("this test uses triggers so does not work with dbmem.go")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var (
|
|
|
|
|
tickCh = make(chan time.Time)
|
|
|
|
|
statsCh = make(chan autobuild.Stats)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
logger := slogtest.Make(t, &slogtest.Options{IgnoreErrors: true}).Leveled(slog.LevelDebug)
|
|
|
|
|
client, db, user := coderdenttest.NewWithDatabase(t, &coderdenttest.Options{
|
|
|
|
|
Options: &coderdtest.Options{
|
|
|
|
|
AutobuildTicker: tickCh,
|
|
|
|
|
IncludeProvisionerDaemon: true,
|
|
|
|
|
AutobuildStats: statsCh,
|
|
|
|
|
Logger: &logger,
|
|
|
|
|
TemplateScheduleStore: schedule.NewEnterpriseTemplateScheduleStore(agplUserQuietHoursScheduleStore(), notifications.NewNoopEnqueuer(), logger, nil),
|
|
|
|
|
},
|
|
|
|
|
LicenseOptions: &coderdenttest.LicenseOptions{
|
|
|
|
|
Features: license.Features{codersdk.FeatureAdvancedTemplateScheduling: 1},
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
|
|
|
|
|
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
|
|
|
|
|
|
|
|
|
// Create a template that allows autostart Monday-Sunday
|
|
|
|
|
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) {
|
|
|
|
|
ctr.AutostartRequirement = &codersdk.TemplateAutostartRequirement{DaysOfWeek: codersdk.AllDaysOfWeek}
|
|
|
|
|
})
|
|
|
|
|
require.Equal(t, version.ID, template.ActiveVersionID)
|
|
|
|
|
|
|
|
|
|
// Create a workspace with a schedule Sunday-Saturday
|
|
|
|
|
sched, err := cron.Weekly("CRON_TZ=UTC 0 9 * * 0-6")
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
ws := coderdtest.CreateWorkspace(t, client, template.ID, func(cwr *codersdk.CreateWorkspaceRequest) {
|
|
|
|
|
cwr.AutostartSchedule = ptr.Ref(sched.String())
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, ws.LatestBuild.ID)
|
|
|
|
|
ws = coderdtest.MustTransitionWorkspace(t, client, ws.ID, database.WorkspaceTransitionStart, database.WorkspaceTransitionStop)
|
|
|
|
|
|
|
|
|
|
// Check we have a 'NextStartAt'
|
|
|
|
|
require.NotNil(t, ws.NextStartAt)
|
|
|
|
|
|
|
|
|
|
// Create a new slightly different cron schedule that could
|
|
|
|
|
// potentially make NextStartAt invalid.
|
|
|
|
|
sched, err = cron.Weekly("CRON_TZ=UTC 0 9 * * 1-6")
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
ctx := testutil.Context(t, testutil.WaitShort)
|
|
|
|
|
|
|
|
|
|
// We want to test the database nullifies the NextStartAt so we
|
|
|
|
|
// make a raw DB call here. We pass in NextStartAt here so we
|
|
|
|
|
// can test the database will nullify it and not us.
|
|
|
|
|
//nolint: gocritic // We need system context to modify this.
|
|
|
|
|
err = db.UpdateWorkspaceAutostart(dbauthz.AsSystemRestricted(ctx), database.UpdateWorkspaceAutostartParams{
|
|
|
|
|
ID: ws.ID,
|
|
|
|
|
AutostartSchedule: sql.NullString{Valid: true, String: sched.String()},
|
|
|
|
|
NextStartAt: sql.NullTime{Valid: true, Time: *ws.NextStartAt},
|
|
|
|
|
})
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
ws = coderdtest.MustWorkspace(t, client, ws.ID)
|
|
|
|
|
|
|
|
|
|
// Check 'NextStartAt' has been nullified
|
|
|
|
|
require.Nil(t, ws.NextStartAt)
|
|
|
|
|
|
|
|
|
|
// Now we let the lifecycle executor run. This should spot that the
|
|
|
|
|
// NextStartAt is null and update it for us.
|
|
|
|
|
next := dbtime.Now()
|
|
|
|
|
tickCh <- next
|
|
|
|
|
stats := <-statsCh
|
|
|
|
|
assert.Len(t, stats.Errors, 0)
|
|
|
|
|
assert.Len(t, stats.Transitions, 0)
|
|
|
|
|
|
|
|
|
|
// Ensure NextStartAt has been set, and is the expected value
|
|
|
|
|
ws = coderdtest.MustWorkspace(t, client, ws.ID)
|
|
|
|
|
require.NotNil(t, ws.NextStartAt)
|
|
|
|
|
require.Equal(t, sched.Next(next), ws.NextStartAt.UTC())
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestTemplateDoesNotAllowUserAutostop(t *testing.T) {
|
|
|
|
@ -1112,7 +1356,7 @@ func TestTemplateDoesNotAllowUserAutostop(t *testing.T) {
|
|
|
|
|
logger := slogtest.Make(t, &slogtest.Options{IgnoreErrors: true}).Leveled(slog.LevelDebug)
|
|
|
|
|
client := coderdtest.New(t, &coderdtest.Options{
|
|
|
|
|
IncludeProvisionerDaemon: true,
|
|
|
|
|
TemplateScheduleStore: schedule.NewEnterpriseTemplateScheduleStore(agplUserQuietHoursScheduleStore(), notifications.NewNoopEnqueuer(), logger),
|
|
|
|
|
TemplateScheduleStore: schedule.NewEnterpriseTemplateScheduleStore(agplUserQuietHoursScheduleStore(), notifications.NewNoopEnqueuer(), logger, nil),
|
|
|
|
|
})
|
|
|
|
|
user := coderdtest.CreateFirstUser(t, client)
|
|
|
|
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
|
|
|
|
@ -1151,7 +1395,7 @@ func TestTemplateDoesNotAllowUserAutostop(t *testing.T) {
|
|
|
|
|
logger := slogtest.Make(t, &slogtest.Options{IgnoreErrors: true}).Leveled(slog.LevelDebug)
|
|
|
|
|
client := coderdtest.New(t, &coderdtest.Options{
|
|
|
|
|
IncludeProvisionerDaemon: true,
|
|
|
|
|
TemplateScheduleStore: schedule.NewEnterpriseTemplateScheduleStore(agplUserQuietHoursScheduleStore(), notifications.NewNoopEnqueuer(), logger),
|
|
|
|
|
TemplateScheduleStore: schedule.NewEnterpriseTemplateScheduleStore(agplUserQuietHoursScheduleStore(), notifications.NewNoopEnqueuer(), logger, nil),
|
|
|
|
|
})
|
|
|
|
|
user := coderdtest.CreateFirstUser(t, client)
|
|
|
|
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
|
|
|
|
@ -1203,7 +1447,7 @@ func TestExecutorAutostartBlocked(t *testing.T) {
|
|
|
|
|
AutobuildTicker: tickCh,
|
|
|
|
|
IncludeProvisionerDaemon: true,
|
|
|
|
|
AutobuildStats: statsCh,
|
|
|
|
|
TemplateScheduleStore: schedule.NewEnterpriseTemplateScheduleStore(agplUserQuietHoursScheduleStore(), notifications.NewNoopEnqueuer(), logger),
|
|
|
|
|
TemplateScheduleStore: schedule.NewEnterpriseTemplateScheduleStore(agplUserQuietHoursScheduleStore(), notifications.NewNoopEnqueuer(), logger, nil),
|
|
|
|
|
},
|
|
|
|
|
LicenseOptions: &coderdenttest.LicenseOptions{
|
|
|
|
|
Features: license.Features{codersdk.FeatureAdvancedTemplateScheduling: 1},
|
|
|
|
@ -1225,9 +1469,9 @@ func TestExecutorAutostartBlocked(t *testing.T) {
|
|
|
|
|
// Given: workspace is stopped
|
|
|
|
|
workspace = coderdtest.MustTransitionWorkspace(t, client, workspace.ID, database.WorkspaceTransitionStart, database.WorkspaceTransitionStop)
|
|
|
|
|
|
|
|
|
|
// When: the autobuild executor ticks way into the future
|
|
|
|
|
// When: the autobuild executor ticks into the future
|
|
|
|
|
go func() {
|
|
|
|
|
tickCh <- workspace.LatestBuild.CreatedAt.Add(24 * time.Hour)
|
|
|
|
|
tickCh <- workspace.LatestBuild.CreatedAt.Add(2 * time.Hour)
|
|
|
|
|
close(tickCh)
|
|
|
|
|
}()
|
|
|
|
|
|
|
|
|
@ -1247,7 +1491,7 @@ func TestWorkspacesFiltering(t *testing.T) {
|
|
|
|
|
logger := slogtest.Make(t, &slogtest.Options{IgnoreErrors: true}).Leveled(slog.LevelDebug)
|
|
|
|
|
client, db, owner := coderdenttest.NewWithDatabase(t, &coderdenttest.Options{
|
|
|
|
|
Options: &coderdtest.Options{
|
|
|
|
|
TemplateScheduleStore: schedule.NewEnterpriseTemplateScheduleStore(agplUserQuietHoursScheduleStore(), notifications.NewNoopEnqueuer(), logger),
|
|
|
|
|
TemplateScheduleStore: schedule.NewEnterpriseTemplateScheduleStore(agplUserQuietHoursScheduleStore(), notifications.NewNoopEnqueuer(), logger, nil),
|
|
|
|
|
},
|
|
|
|
|
LicenseOptions: &coderdenttest.LicenseOptions{
|
|
|
|
|
Features: license.Features{codersdk.FeatureAdvancedTemplateScheduling: 1},
|
|
|
|
@ -1362,7 +1606,7 @@ func TestWorkspaceLock(t *testing.T) {
|
|
|
|
|
client, user = coderdenttest.New(t, &coderdenttest.Options{
|
|
|
|
|
Options: &coderdtest.Options{
|
|
|
|
|
IncludeProvisionerDaemon: true,
|
|
|
|
|
TemplateScheduleStore: &schedule.EnterpriseTemplateScheduleStore{},
|
|
|
|
|
TemplateScheduleStore: &schedule.EnterpriseTemplateScheduleStore{Clock: quartz.NewReal()},
|
|
|
|
|
},
|
|
|
|
|
LicenseOptions: &coderdenttest.LicenseOptions{
|
|
|
|
|
Features: license.Features{
|
|
|
|
@ -1423,7 +1667,7 @@ func TestResolveAutostart(t *testing.T) {
|
|
|
|
|
|
|
|
|
|
ownerClient, db, owner := coderdenttest.NewWithDatabase(t, &coderdenttest.Options{
|
|
|
|
|
Options: &coderdtest.Options{
|
|
|
|
|
TemplateScheduleStore: &schedule.EnterpriseTemplateScheduleStore{},
|
|
|
|
|
TemplateScheduleStore: &schedule.EnterpriseTemplateScheduleStore{Clock: quartz.NewReal()},
|
|
|
|
|
},
|
|
|
|
|
LicenseOptions: &coderdenttest.LicenseOptions{
|
|
|
|
|
Features: license.Features{
|
|
|
|
|