Skip to content

feat: enforce template-level constraints for TTL and autostart #2018

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 20 commits into from
Jun 7, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
ORANGE: coderd: fix a whole bunch of tests
  • Loading branch information
johnstcn committed Jun 3, 2022
commit 65b980ed4258d9335b27b34830ceabb441818eb1
108 changes: 42 additions & 66 deletions coderd/autobuild/executor/lifecycle_executor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package executor_test

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: I ended up making a lot of changes in this test because I had been using a simple * * * * * schedule which is lower than the now-enforced default 🙃

import (
"context"
"fmt"
"os"
"strings"
"testing"
Expand All @@ -26,31 +25,25 @@ func TestExecutorAutostartOK(t *testing.T) {
t.Parallel()

var (
ctx = context.Background()
err error
sched = mustSchedule(t, "CRON_TZ=UTC 0 * * * *")
tickCh = make(chan time.Time)
statsCh = make(chan executor.Stats)
client = coderdtest.New(t, &coderdtest.Options{
AutobuildTicker: tickCh,
IncludeProvisionerD: true,
AutobuildStats: statsCh,
})
// Given: we have a user with a workspace
workspace = mustProvisionWorkspace(t, client)
// Given: we have a user with a workspace that has autostart enabled
workspace = mustProvisionWorkspace(t, client, func(cwr *codersdk.CreateWorkspaceRequest) {
cwr.AutostartSchedule = ptr.Ref(sched.String())
})
)
// Given: workspace is stopped
workspace = mustTransitionWorkspace(t, client, workspace.ID, database.WorkspaceTransitionStart, database.WorkspaceTransitionStop)

// When: we enable workspace autostart
sched, err := schedule.Weekly("* * * * *")
require.NoError(t, err)
require.NoError(t, client.UpdateWorkspaceAutostart(ctx, workspace.ID, codersdk.UpdateWorkspaceAutostartRequest{
Schedule: ptr.Ref(sched.String()),
}))

// When: the autobuild executor ticks
// When: the autobuild executor ticks after the scheduled time
go func() {
tickCh <- time.Now().UTC().Add(time.Minute)
tickCh <- sched.Next(workspace.LatestBuild.CreatedAt)
close(tickCh)
}()

Expand All @@ -66,6 +59,7 @@ func TestExecutorAutostartTemplateUpdated(t *testing.T) {
t.Parallel()

var (
sched = mustSchedule(t, "CRON_TZ=UTC 0 * * * *")
ctx = context.Background()
err error
tickCh = make(chan time.Time)
Expand All @@ -75,8 +69,10 @@ func TestExecutorAutostartTemplateUpdated(t *testing.T) {
IncludeProvisionerD: true,
AutobuildStats: statsCh,
})
// Given: we have a user with a workspace
workspace = mustProvisionWorkspace(t, client)
// Given: we have a user with a workspace that has autostart enabled
workspace = mustProvisionWorkspace(t, client, func(cwr *codersdk.CreateWorkspaceRequest) {
cwr.AutostartSchedule = ptr.Ref(sched.String())
})
)
// Given: workspace is stopped
workspace = mustTransitionWorkspace(t, client, workspace.ID, database.WorkspaceTransitionStart, database.WorkspaceTransitionStop)
Expand All @@ -92,16 +88,9 @@ func TestExecutorAutostartTemplateUpdated(t *testing.T) {
ID: newVersion.ID,
}))

// When: we enable workspace autostart
sched, err := schedule.Weekly("* * * * *")
require.NoError(t, err)
require.NoError(t, client.UpdateWorkspaceAutostart(ctx, workspace.ID, codersdk.UpdateWorkspaceAutostartRequest{
Schedule: ptr.Ref(sched.String()),
}))

// When: the autobuild executor ticks
// When: the autobuild executor ticks after the scheduled time
go func() {
tickCh <- time.Now().UTC().Add(time.Minute)
tickCh <- sched.Next(workspace.LatestBuild.CreatedAt)
close(tickCh)
}()

Expand All @@ -119,32 +108,26 @@ func TestExecutorAutostartAlreadyRunning(t *testing.T) {
t.Parallel()

var (
ctx = context.Background()
err error
sched = mustSchedule(t, "CRON_TZ=UTC 0 * * * *")
tickCh = make(chan time.Time)
statsCh = make(chan executor.Stats)
client = coderdtest.New(t, &coderdtest.Options{
AutobuildTicker: tickCh,
IncludeProvisionerD: true,
AutobuildStats: statsCh,
})
// Given: we have a user with a workspace
workspace = mustProvisionWorkspace(t, client)
// Given: we have a user with a workspace that has autostart enabled
workspace = mustProvisionWorkspace(t, client, func(cwr *codersdk.CreateWorkspaceRequest) {
cwr.AutostartSchedule = ptr.Ref(sched.String())
})
)

// Given: we ensure the workspace is running
require.Equal(t, codersdk.WorkspaceTransitionStart, workspace.LatestBuild.Transition)

// When: we enable workspace autostart
sched, err := schedule.Weekly("* * * * *")
require.NoError(t, err)
require.NoError(t, client.UpdateWorkspaceAutostart(ctx, workspace.ID, codersdk.UpdateWorkspaceAutostartRequest{
Schedule: ptr.Ref(sched.String()),
}))

// When: the autobuild executor ticks
go func() {
tickCh <- time.Now().UTC().Add(time.Minute)
tickCh <- sched.Next(workspace.LatestBuild.CreatedAt)
close(tickCh)
}()

Expand All @@ -165,7 +148,7 @@ func TestExecutorAutostartNotEnabled(t *testing.T) {
IncludeProvisionerD: true,
AutobuildStats: statsCh,
})
// Given: we have a user with a workspace
// Given: we have a user with a workspace that does not have autostart enabled
workspace = mustProvisionWorkspace(t, client, func(cwr *codersdk.CreateWorkspaceRequest) {
cwr.AutostartSchedule = nil
})
Expand All @@ -177,9 +160,9 @@ func TestExecutorAutostartNotEnabled(t *testing.T) {
// Given: workspace is stopped
workspace = mustTransitionWorkspace(t, client, workspace.ID, database.WorkspaceTransitionStart, database.WorkspaceTransitionStop)

// When: the autobuild executor ticks
// When: the autobuild executor ticks way into the future
go func() {
tickCh <- time.Now().UTC().Add(time.Minute)
tickCh <- workspace.LatestBuild.CreatedAt.Add(24 * time.Hour)
close(tickCh)
}()

Expand Down Expand Up @@ -343,32 +326,26 @@ func TestExecutorWorkspaceDeleted(t *testing.T) {
t.Parallel()

var (
ctx = context.Background()
err error
sched = mustSchedule(t, "CRON_TZ=UTC 0 * * * *")
tickCh = make(chan time.Time)
statsCh = make(chan executor.Stats)
client = coderdtest.New(t, &coderdtest.Options{
AutobuildTicker: tickCh,
IncludeProvisionerD: true,
AutobuildStats: statsCh,
})
// Given: we have a user with a workspace
workspace = mustProvisionWorkspace(t, client)
// Given: we have a user with a workspace that has autostart enabled
workspace = mustProvisionWorkspace(t, client, func(cwr *codersdk.CreateWorkspaceRequest) {
cwr.AutostartSchedule = ptr.Ref(sched.String())
})
)

// When: we enable workspace autostart
sched, err := schedule.Weekly("* * * * *")
require.NoError(t, err)
require.NoError(t, client.UpdateWorkspaceAutostart(ctx, workspace.ID, codersdk.UpdateWorkspaceAutostartRequest{
Schedule: ptr.Ref(sched.String()),
}))

// Given: workspace is deleted
workspace = mustTransitionWorkspace(t, client, workspace.ID, database.WorkspaceTransitionStart, database.WorkspaceTransitionDelete)

// When: the autobuild executor ticks
go func() {
tickCh <- time.Now().UTC().Add(time.Minute)
tickCh <- sched.Next(workspace.LatestBuild.CreatedAt)
close(tickCh)
}()

Expand All @@ -382,33 +359,25 @@ func TestExecutorWorkspaceAutostartTooEarly(t *testing.T) {
t.Parallel()

var (
ctx = context.Background()
err error
sched = mustSchedule(t, "CRON_TZ=UTC 0 * * * *")
tickCh = make(chan time.Time)
statsCh = make(chan executor.Stats)
client = coderdtest.New(t, &coderdtest.Options{
AutobuildTicker: tickCh,
IncludeProvisionerD: true,
AutobuildStats: statsCh,
})
futureTime = time.Now().Add(time.Hour)
futureTimeCron = fmt.Sprintf("%d %d * * *", futureTime.Minute(), futureTime.Hour())
// futureTime = time.Now().Add(time.Hour)
// futureTimeCron = fmt.Sprintf("%d %d * * *", futureTime.Minute(), futureTime.Hour())
// Given: we have a user with a workspace configured to autostart some time in the future
workspace = mustProvisionWorkspace(t, client, func(cwr *codersdk.CreateWorkspaceRequest) {
cwr.AutostartSchedule = &futureTimeCron
cwr.AutostartSchedule = ptr.Ref(sched.String())
})
)

// When: we enable workspace autostart with some time in the future
sched, err := schedule.Weekly(futureTimeCron)
require.NoError(t, err)
require.NoError(t, client.UpdateWorkspaceAutostart(ctx, workspace.ID, codersdk.UpdateWorkspaceAutostartRequest{
Schedule: ptr.Ref(sched.String()),
}))

// When: the autobuild executor ticks
// When: the autobuild executor ticks before the next scheduled time
go func() {
tickCh <- time.Now().UTC()
tickCh <- sched.Next(workspace.LatestBuild.CreatedAt).Add(-time.Minute)
close(tickCh)
}()

Expand Down Expand Up @@ -573,6 +542,13 @@ func mustWorkspace(t *testing.T, client *codersdk.Client, workspaceID uuid.UUID)
return ws
}

func mustSchedule(t *testing.T, s string) *schedule.Schedule {
t.Helper()
sched, err := schedule.Weekly(s)
require.NoError(t, err)
return sched
}

func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
8 changes: 8 additions & 0 deletions coderd/database/databasefake/databasefake.go
Original file line number Diff line number Diff line change
Expand Up @@ -1224,6 +1224,14 @@ func (q *fakeQuerier) InsertTemplate(_ context.Context, arg database.InsertTempl
q.mutex.Lock()
defer q.mutex.Unlock()

// default values
if arg.MaxTtl == 0 {
arg.MaxTtl = int64(168 * time.Hour)
}
if arg.MinAutostartInterval == 0 {
arg.MinAutostartInterval = int64(time.Hour)
}

//nolint:gosimple
template := database.Template{
ID: arg.ID,
Expand Down
35 changes: 27 additions & 8 deletions coderd/templates.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"net/http"
"time"

"github.com/go-chi/chi/v5"
"github.com/google/uuid"
Expand All @@ -14,9 +15,15 @@ import (
"github.com/coder/coder/coderd/httpapi"
"github.com/coder/coder/coderd/httpmw"
"github.com/coder/coder/coderd/rbac"
"github.com/coder/coder/coderd/util/ptr"
"github.com/coder/coder/codersdk"
)

var (
maxTTLDefault = 24 * 7 * time.Hour
minAutostartIntervalDefault = time.Hour
)

// Returns a single template.
func (api *API) template(rw http.ResponseWriter, r *http.Request) {
template := httpmw.TemplateParam(r)
Expand Down Expand Up @@ -135,18 +142,30 @@ func (api *API) postTemplateByOrganization(rw http.ResponseWriter, r *http.Reque
return
}

maxTTL := maxTTLDefault
if !ptr.NilOrZero(createTemplate.MaxTTLMillis) {
maxTTL = time.Duration(*createTemplate.MaxTTLMillis) * time.Millisecond
}

minAutostartInterval := minAutostartIntervalDefault
if !ptr.NilOrZero(createTemplate.MinAutostartIntervalMillis) {
minAutostartInterval = time.Duration(*createTemplate.MinAutostartIntervalMillis) * time.Millisecond
}

var template codersdk.Template
err = api.Database.InTx(func(db database.Store) error {
now := database.Now()
dbTemplate, err := db.InsertTemplate(r.Context(), database.InsertTemplateParams{
ID: uuid.New(),
CreatedAt: now,
UpdatedAt: now,
OrganizationID: organization.ID,
Name: createTemplate.Name,
Provisioner: importJob.Provisioner,
ActiveVersionID: templateVersion.ID,
Description: createTemplate.Description,
ID: uuid.New(),
CreatedAt: now,
UpdatedAt: now,
OrganizationID: organization.ID,
Name: createTemplate.Name,
Provisioner: importJob.Provisioner,
ActiveVersionID: templateVersion.ID,
Description: createTemplate.Description,
MaxTtl: int64(maxTTL),
MinAutostartInterval: int64(minAutostartInterval),
})
if err != nil {
return xerrors.Errorf("insert template: %s", err)
Expand Down
Loading