Skip to content

chore: add a unit test to ensure correct behaviour with multiple replicas and rename LifecycleTicker to AutobuildTicker #1420

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 2 commits into from
May 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
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
73 changes: 64 additions & 9 deletions coderd/autobuild/executor/lifecycle_executor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package executor_test
import (
"context"
"fmt"
"os"
"testing"
"time"

Expand All @@ -25,7 +26,7 @@ func TestExecutorAutostartOK(t *testing.T) {
err error
tickCh = make(chan time.Time)
client = coderdtest.New(t, &coderdtest.Options{
LifecycleTicker: tickCh,
AutobuildTicker: tickCh,
})
// Given: we have a user with a workspace
workspace = mustProvisionWorkspace(t, client)
Expand Down Expand Up @@ -65,7 +66,7 @@ func TestExecutorAutostartTemplateUpdated(t *testing.T) {
err error
tickCh = make(chan time.Time)
client = coderdtest.New(t, &coderdtest.Options{
LifecycleTicker: tickCh,
AutobuildTicker: tickCh,
})
// Given: we have a user with a workspace
workspace = mustProvisionWorkspace(t, client)
Expand Down Expand Up @@ -117,7 +118,7 @@ func TestExecutorAutostartAlreadyRunning(t *testing.T) {
err error
tickCh = make(chan time.Time)
client = coderdtest.New(t, &coderdtest.Options{
LifecycleTicker: tickCh,
AutobuildTicker: tickCh,
})
// Given: we have a user with a workspace
workspace = mustProvisionWorkspace(t, client)
Expand Down Expand Up @@ -155,7 +156,7 @@ func TestExecutorAutostartNotEnabled(t *testing.T) {
var (
tickCh = make(chan time.Time)
client = coderdtest.New(t, &coderdtest.Options{
LifecycleTicker: tickCh,
AutobuildTicker: tickCh,
})
// Given: we have a user with a workspace
workspace = mustProvisionWorkspace(t, client)
Expand Down Expand Up @@ -188,7 +189,7 @@ func TestExecutorAutostopOK(t *testing.T) {
err error
tickCh = make(chan time.Time)
client = coderdtest.New(t, &coderdtest.Options{
LifecycleTicker: tickCh,
AutobuildTicker: tickCh,
})
// Given: we have a user with a workspace
workspace = mustProvisionWorkspace(t, client)
Expand Down Expand Up @@ -228,7 +229,7 @@ func TestExecutorAutostopAlreadyStopped(t *testing.T) {
err error
tickCh = make(chan time.Time)
client = coderdtest.New(t, &coderdtest.Options{
LifecycleTicker: tickCh,
AutobuildTicker: tickCh,
})
// Given: we have a user with a workspace
workspace = mustProvisionWorkspace(t, client)
Expand Down Expand Up @@ -266,7 +267,7 @@ func TestExecutorAutostopNotEnabled(t *testing.T) {
var (
tickCh = make(chan time.Time)
client = coderdtest.New(t, &coderdtest.Options{
LifecycleTicker: tickCh,
AutobuildTicker: tickCh,
})
// Given: we have a user with a workspace
workspace = mustProvisionWorkspace(t, client)
Expand Down Expand Up @@ -299,7 +300,7 @@ func TestExecutorWorkspaceDeleted(t *testing.T) {
err error
tickCh = make(chan time.Time)
client = coderdtest.New(t, &coderdtest.Options{
LifecycleTicker: tickCh,
AutobuildTicker: tickCh,
})
// Given: we have a user with a workspace
workspace = mustProvisionWorkspace(t, client)
Expand Down Expand Up @@ -339,7 +340,7 @@ func TestExecutorWorkspaceTooEarly(t *testing.T) {
err error
tickCh = make(chan time.Time)
client = coderdtest.New(t, &coderdtest.Options{
LifecycleTicker: tickCh,
AutobuildTicker: tickCh,
})
// Given: we have a user with a workspace
workspace = mustProvisionWorkspace(t, client)
Expand Down Expand Up @@ -370,6 +371,60 @@ func TestExecutorWorkspaceTooEarly(t *testing.T) {
require.Equal(t, database.WorkspaceTransitionStart, ws.LatestBuild.Transition, "expected workspace to be running")
}

func TestExecutorAutostartMultipleOK(t *testing.T) {
if os.Getenv("DB") == "" {
t.Skip(`This test only really works when using a "real" database, similar to a HA setup`)
}

t.Parallel()

var (
ctx = context.Background()
err error
tickCh = make(chan time.Time)
tickCh2 = make(chan time.Time)
client = coderdtest.New(t, &coderdtest.Options{
AutobuildTicker: tickCh,
})
_ = coderdtest.New(t, &coderdtest.Options{
AutobuildTicker: tickCh2,
})
// Given: we have a user with a workspace
workspace = mustProvisionWorkspace(t, client)
)
// Given: workspace is stopped
workspace = mustTransitionWorkspace(t, client, workspace.ID, database.WorkspaceTransitionStart, database.WorkspaceTransitionStop)

// Given: the workspace initially has autostart disabled
require.Empty(t, workspace.AutostartSchedule)

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

// When: the autobuild executor ticks
go func() {
tickCh <- time.Now().UTC().Add(time.Minute)
tickCh2 <- time.Now().UTC().Add(time.Minute)
close(tickCh)
close(tickCh2)
}()

// Then: the workspace should be started
<-time.After(5 * time.Second)
ws := mustWorkspace(t, client, workspace.ID)
require.NotEqual(t, workspace.LatestBuild.ID, ws.LatestBuild.ID, "expected a workspace build to occur")
require.Equal(t, codersdk.ProvisionerJobSucceeded, ws.LatestBuild.Job.Status, "expected provisioner job to have succeeded")
require.Equal(t, database.WorkspaceTransitionStart, ws.LatestBuild.Transition, "expected latest transition to be start")
builds, err := client.WorkspaceBuilds(ctx, ws.ID)
require.NoError(t, err, "fetch list of workspace builds from primary")
// One build to start, one stop transition, and one autostart. No more.
require.Len(t, builds, 3, "unexpected number of builds for workspace from primary")
}

func mustProvisionWorkspace(t *testing.T, client *codersdk.Client) codersdk.Workspace {
t.Helper()
coderdtest.NewProvisionerDaemon(t, client)
Expand Down
8 changes: 4 additions & 4 deletions coderd/coderdtest/coderdtest.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ type Options struct {
GoogleTokenValidator *idtoken.Validator
SSHKeygenAlgorithm gitsshkey.Algorithm
APIRateLimit int
LifecycleTicker <-chan time.Time
AutobuildTicker <-chan time.Time
}

// New constructs an in-memory coderd instance and returns
Expand All @@ -77,9 +77,9 @@ func New(t *testing.T, options *Options) *codersdk.Client {
options.GoogleTokenValidator, err = idtoken.NewValidator(ctx, option.WithoutAuthentication())
require.NoError(t, err)
}
if options.LifecycleTicker == nil {
if options.AutobuildTicker == nil {
ticker := make(chan time.Time)
options.LifecycleTicker = ticker
options.AutobuildTicker = ticker
t.Cleanup(func() { close(ticker) })
}

Expand Down Expand Up @@ -111,7 +111,7 @@ func New(t *testing.T, options *Options) *codersdk.Client {
ctx,
db,
slogtest.Make(t, nil).Named("autobuild.executor").Leveled(slog.LevelDebug),
options.LifecycleTicker,
options.AutobuildTicker,
)
lifecycleExecutor.Run()

Expand Down