mirror of
https://github.com/coder/coder.git
synced 2025-07-06 15:41:45 +00:00
add tests for prebuilds
finalise database integration tests for prebuilds reintegrate with danny's latest changes add back assertions for deletion integration tests of prebuilds tidy up prebuilds tests
This commit is contained in:
@ -5800,9 +5800,8 @@ FROM workspace_latest_build wlb
|
|||||||
INNER JOIN provisioner_jobs pj ON wlb.job_id = pj.id
|
INNER JOIN provisioner_jobs pj ON wlb.job_id = pj.id
|
||||||
INNER JOIN workspace_prebuild_builds wpb ON wpb.id = wlb.id
|
INNER JOIN workspace_prebuild_builds wpb ON wpb.id = wlb.id
|
||||||
INNER JOIN templates t ON t.active_version_id = wlb.template_version_id
|
INNER JOIN templates t ON t.active_version_id = wlb.template_version_id
|
||||||
WHERE pj.job_status NOT IN -- Jobs that are not in terminal states.
|
WHERE wlb.transition = 'start'::workspace_transition
|
||||||
('succeeded'::provisioner_job_status, 'canceled'::provisioner_job_status,
|
AND pj.job_status IN ('pending'::provisioner_job_status, 'running'::provisioner_job_status)
|
||||||
'failed'::provisioner_job_status)
|
|
||||||
GROUP BY t.id, wpb.template_version_id, wpb.transition
|
GROUP BY t.id, wpb.template_version_id, wpb.transition
|
||||||
`
|
`
|
||||||
|
|
||||||
@ -5861,8 +5860,7 @@ FROM workspace_prebuilds p
|
|||||||
ON tvp_curr.id = p.current_preset_id -- See https://github.com/coder/internal/issues/398.
|
ON tvp_curr.id = p.current_preset_id -- See https://github.com/coder/internal/issues/398.
|
||||||
WHERE (b.transition = 'start'::workspace_transition
|
WHERE (b.transition = 'start'::workspace_transition
|
||||||
-- Jobs that are not in terminal states.
|
-- Jobs that are not in terminal states.
|
||||||
AND pj.job_status NOT IN ('failed'::provisioner_job_status, 'canceled'::provisioner_job_status,
|
AND pj.job_status = 'succeeded'::provisioner_job_status)
|
||||||
'unknown'::provisioner_job_status))
|
|
||||||
`
|
`
|
||||||
|
|
||||||
type GetRunningPrebuildsRow struct {
|
type GetRunningPrebuildsRow struct {
|
||||||
|
@ -18,8 +18,7 @@ FROM workspace_prebuilds p
|
|||||||
ON tvp_curr.id = p.current_preset_id -- See https://github.com/coder/internal/issues/398.
|
ON tvp_curr.id = p.current_preset_id -- See https://github.com/coder/internal/issues/398.
|
||||||
WHERE (b.transition = 'start'::workspace_transition
|
WHERE (b.transition = 'start'::workspace_transition
|
||||||
-- Jobs that are not in terminal states.
|
-- Jobs that are not in terminal states.
|
||||||
AND pj.job_status NOT IN ('failed'::provisioner_job_status, 'canceled'::provisioner_job_status,
|
AND pj.job_status = 'succeeded'::provisioner_job_status);
|
||||||
'unknown'::provisioner_job_status));
|
|
||||||
|
|
||||||
-- name: GetTemplatePresetsWithPrebuilds :many
|
-- name: GetTemplatePresetsWithPrebuilds :many
|
||||||
SELECT t.id AS template_id,
|
SELECT t.id AS template_id,
|
||||||
@ -42,9 +41,7 @@ FROM workspace_latest_build wlb
|
|||||||
INNER JOIN provisioner_jobs pj ON wlb.job_id = pj.id
|
INNER JOIN provisioner_jobs pj ON wlb.job_id = pj.id
|
||||||
INNER JOIN workspace_prebuild_builds wpb ON wpb.id = wlb.id
|
INNER JOIN workspace_prebuild_builds wpb ON wpb.id = wlb.id
|
||||||
INNER JOIN templates t ON t.active_version_id = wlb.template_version_id
|
INNER JOIN templates t ON t.active_version_id = wlb.template_version_id
|
||||||
WHERE pj.job_status NOT IN -- Jobs that are not in terminal states.
|
WHERE pj.job_status IN ('pending'::provisioner_job_status, 'running'::provisioner_job_status)
|
||||||
('succeeded'::provisioner_job_status, 'canceled'::provisioner_job_status,
|
|
||||||
'failed'::provisioner_job_status)
|
|
||||||
GROUP BY t.id, wpb.template_version_id, wpb.transition;
|
GROUP BY t.id, wpb.template_version_id, wpb.transition;
|
||||||
|
|
||||||
-- name: ClaimPrebuild :one
|
-- name: ClaimPrebuild :one
|
||||||
|
@ -8,10 +8,11 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/coder/serpent"
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/coder/serpent"
|
||||||
|
|
||||||
"github.com/coder/coder/v2/coderd/coderdtest"
|
"github.com/coder/coder/v2/coderd/coderdtest"
|
||||||
"github.com/coder/coder/v2/coderd/database"
|
"github.com/coder/coder/v2/coderd/database"
|
||||||
"github.com/coder/coder/v2/coderd/database/dbauthz"
|
"github.com/coder/coder/v2/coderd/database/dbauthz"
|
||||||
|
@ -3,13 +3,17 @@ package prebuilds_test
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/coder/serpent"
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/coder/serpent"
|
||||||
|
|
||||||
|
"tailscale.com/types/ptr"
|
||||||
|
|
||||||
"github.com/coder/coder/v2/coderd/database"
|
"github.com/coder/coder/v2/coderd/database"
|
||||||
"github.com/coder/coder/v2/coderd/database/dbgen"
|
"github.com/coder/coder/v2/coderd/database/dbgen"
|
||||||
"github.com/coder/coder/v2/coderd/database/dbtestutil"
|
"github.com/coder/coder/v2/coderd/database/dbtestutil"
|
||||||
@ -119,6 +123,260 @@ func TestNoReconciliationActionsIfNoPrebuilds(t *testing.T) {
|
|||||||
require.Empty(t, jobs)
|
require.Empty(t, jobs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPrebuildReconciliation(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
if !dbtestutil.WillUsePostgres() {
|
||||||
|
t.Skip("This test requires postgres")
|
||||||
|
}
|
||||||
|
|
||||||
|
type testCase struct {
|
||||||
|
name string
|
||||||
|
prebuildLatestTransitions []database.WorkspaceTransition
|
||||||
|
prebuildJobStatuses []database.ProvisionerJobStatus
|
||||||
|
templateVersionActive []bool
|
||||||
|
shouldCreateNewPrebuild *bool
|
||||||
|
shouldDeleteOldPrebuild *bool
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases := []testCase{
|
||||||
|
{
|
||||||
|
name: "never create prebuilds for inactive template versions",
|
||||||
|
prebuildLatestTransitions: []database.WorkspaceTransition{
|
||||||
|
database.WorkspaceTransitionStart,
|
||||||
|
database.WorkspaceTransitionStop,
|
||||||
|
database.WorkspaceTransitionDelete,
|
||||||
|
},
|
||||||
|
prebuildJobStatuses: []database.ProvisionerJobStatus{
|
||||||
|
database.ProvisionerJobStatusSucceeded,
|
||||||
|
database.ProvisionerJobStatusCanceled,
|
||||||
|
database.ProvisionerJobStatusFailed,
|
||||||
|
database.ProvisionerJobStatusPending,
|
||||||
|
database.ProvisionerJobStatusRunning,
|
||||||
|
database.ProvisionerJobStatusCanceling,
|
||||||
|
},
|
||||||
|
templateVersionActive: []bool{false},
|
||||||
|
shouldCreateNewPrebuild: ptr.To(false),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no need to create a new prebuild if one is already running",
|
||||||
|
prebuildLatestTransitions: []database.WorkspaceTransition{
|
||||||
|
database.WorkspaceTransitionStart,
|
||||||
|
},
|
||||||
|
prebuildJobStatuses: []database.ProvisionerJobStatus{
|
||||||
|
database.ProvisionerJobStatusSucceeded,
|
||||||
|
},
|
||||||
|
templateVersionActive: []bool{true},
|
||||||
|
shouldCreateNewPrebuild: ptr.To(false),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "don't create a new prebuild if one is queued to build or already building",
|
||||||
|
prebuildLatestTransitions: []database.WorkspaceTransition{
|
||||||
|
database.WorkspaceTransitionStart,
|
||||||
|
},
|
||||||
|
prebuildJobStatuses: []database.ProvisionerJobStatus{
|
||||||
|
database.ProvisionerJobStatusPending,
|
||||||
|
database.ProvisionerJobStatusRunning,
|
||||||
|
},
|
||||||
|
templateVersionActive: []bool{true},
|
||||||
|
shouldCreateNewPrebuild: ptr.To(false),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "create a new prebuild if one is in a state that disqualifies it from ever being claimed",
|
||||||
|
prebuildLatestTransitions: []database.WorkspaceTransition{
|
||||||
|
database.WorkspaceTransitionStop,
|
||||||
|
database.WorkspaceTransitionDelete,
|
||||||
|
},
|
||||||
|
prebuildJobStatuses: []database.ProvisionerJobStatus{
|
||||||
|
database.ProvisionerJobStatusPending,
|
||||||
|
database.ProvisionerJobStatusRunning,
|
||||||
|
database.ProvisionerJobStatusCanceling,
|
||||||
|
database.ProvisionerJobStatusSucceeded,
|
||||||
|
},
|
||||||
|
templateVersionActive: []bool{true},
|
||||||
|
shouldCreateNewPrebuild: ptr.To(true),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "create a new prebuild if one is in any kind of exceptional state",
|
||||||
|
prebuildLatestTransitions: []database.WorkspaceTransition{
|
||||||
|
database.WorkspaceTransitionStart,
|
||||||
|
database.WorkspaceTransitionStop,
|
||||||
|
database.WorkspaceTransitionDelete,
|
||||||
|
},
|
||||||
|
prebuildJobStatuses: []database.ProvisionerJobStatus{
|
||||||
|
database.ProvisionerJobStatusCanceled,
|
||||||
|
database.ProvisionerJobStatusFailed,
|
||||||
|
},
|
||||||
|
templateVersionActive: []bool{true},
|
||||||
|
shouldCreateNewPrebuild: ptr.To(true),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "never attempt to interfere with active builds",
|
||||||
|
// The workspace builder does not allow scheduling a new build if there is already a build
|
||||||
|
// pending, running, or canceling. As such, we should never attempt to start, stop or delete
|
||||||
|
// such prebuilds. Rather, we should wait for the existing build to complete and reconcile
|
||||||
|
// again in the next cycle.
|
||||||
|
prebuildLatestTransitions: []database.WorkspaceTransition{
|
||||||
|
database.WorkspaceTransitionStart,
|
||||||
|
database.WorkspaceTransitionStop,
|
||||||
|
database.WorkspaceTransitionDelete,
|
||||||
|
},
|
||||||
|
prebuildJobStatuses: []database.ProvisionerJobStatus{
|
||||||
|
database.ProvisionerJobStatusPending,
|
||||||
|
database.ProvisionerJobStatusRunning,
|
||||||
|
database.ProvisionerJobStatusCanceling,
|
||||||
|
},
|
||||||
|
templateVersionActive: []bool{true, false},
|
||||||
|
shouldDeleteOldPrebuild: ptr.To(false),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "never delete prebuilds in an exceptional state",
|
||||||
|
// We don't want to destroy evidence that might be useful to operators
|
||||||
|
// when troubleshooting issues. So we leave these prebuilds in place.
|
||||||
|
// Operators are expected to manually delete these prebuilds.
|
||||||
|
prebuildLatestTransitions: []database.WorkspaceTransition{
|
||||||
|
database.WorkspaceTransitionStart,
|
||||||
|
database.WorkspaceTransitionStop,
|
||||||
|
database.WorkspaceTransitionDelete,
|
||||||
|
},
|
||||||
|
prebuildJobStatuses: []database.ProvisionerJobStatus{
|
||||||
|
database.ProvisionerJobStatusCanceled,
|
||||||
|
database.ProvisionerJobStatusFailed,
|
||||||
|
},
|
||||||
|
templateVersionActive: []bool{true, false},
|
||||||
|
shouldDeleteOldPrebuild: ptr.To(false),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "delete running prebuilds for inactive template versions",
|
||||||
|
// We only support prebuilds for active template versions.
|
||||||
|
// If a template version is inactive, we should delete any prebuilds
|
||||||
|
// that are running.
|
||||||
|
prebuildLatestTransitions: []database.WorkspaceTransition{
|
||||||
|
database.WorkspaceTransitionStart,
|
||||||
|
},
|
||||||
|
prebuildJobStatuses: []database.ProvisionerJobStatus{
|
||||||
|
database.ProvisionerJobStatusSucceeded,
|
||||||
|
},
|
||||||
|
templateVersionActive: []bool{false},
|
||||||
|
shouldDeleteOldPrebuild: ptr.To(true),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "don't delete running prebuilds for active template versions",
|
||||||
|
prebuildLatestTransitions: []database.WorkspaceTransition{
|
||||||
|
database.WorkspaceTransitionStart,
|
||||||
|
},
|
||||||
|
prebuildJobStatuses: []database.ProvisionerJobStatus{
|
||||||
|
database.ProvisionerJobStatusSucceeded,
|
||||||
|
},
|
||||||
|
templateVersionActive: []bool{true},
|
||||||
|
shouldDeleteOldPrebuild: ptr.To(false),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "don't delete stopped or already deleted prebuilds",
|
||||||
|
// We don't ever stop prebuilds. A stopped prebuild is an exceptional state.
|
||||||
|
// As such we keep it, to allow operators to investigate the cause.
|
||||||
|
prebuildLatestTransitions: []database.WorkspaceTransition{
|
||||||
|
database.WorkspaceTransitionStop,
|
||||||
|
database.WorkspaceTransitionDelete,
|
||||||
|
},
|
||||||
|
prebuildJobStatuses: []database.ProvisionerJobStatus{
|
||||||
|
database.ProvisionerJobStatusSucceeded,
|
||||||
|
},
|
||||||
|
templateVersionActive: []bool{true, false},
|
||||||
|
shouldDeleteOldPrebuild: ptr.To(false),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
tc := tc
|
||||||
|
for _, templateVersionActive := range tc.templateVersionActive {
|
||||||
|
templateVersionActive := templateVersionActive
|
||||||
|
for _, prebuildLatestTransition := range tc.prebuildLatestTransitions {
|
||||||
|
prebuildLatestTransition := prebuildLatestTransition
|
||||||
|
for _, prebuildJobStatus := range tc.prebuildJobStatuses {
|
||||||
|
t.Run(fmt.Sprintf("%s - %s - %s", tc.name, prebuildLatestTransition, prebuildJobStatus), func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
ctx := testutil.Context(t, testutil.WaitShort)
|
||||||
|
cfg := codersdk.PrebuildsConfig{}
|
||||||
|
logger := testutil.Logger(t)
|
||||||
|
db, pubsub := dbtestutil.NewDB(t)
|
||||||
|
controller := prebuilds.NewStoreReconciler(db, pubsub, cfg, logger)
|
||||||
|
|
||||||
|
orgID, userID, templateID := setupTestDBTemplate(t, db)
|
||||||
|
templateVersionID := setupTestDBTemplateVersion(
|
||||||
|
t,
|
||||||
|
ctx,
|
||||||
|
db,
|
||||||
|
pubsub,
|
||||||
|
orgID,
|
||||||
|
userID,
|
||||||
|
templateID,
|
||||||
|
)
|
||||||
|
_, prebuildID := setupTestDBPrebuild(
|
||||||
|
t,
|
||||||
|
ctx,
|
||||||
|
db,
|
||||||
|
pubsub,
|
||||||
|
prebuildLatestTransition,
|
||||||
|
prebuildJobStatus,
|
||||||
|
orgID,
|
||||||
|
templateID,
|
||||||
|
templateVersionID,
|
||||||
|
)
|
||||||
|
|
||||||
|
if !templateVersionActive {
|
||||||
|
// Create a new template version and mark it as active
|
||||||
|
// This marks the template version that we care about as inactive
|
||||||
|
setupTestDBTemplateVersion(
|
||||||
|
t,
|
||||||
|
ctx,
|
||||||
|
db,
|
||||||
|
pubsub,
|
||||||
|
orgID,
|
||||||
|
userID,
|
||||||
|
templateID,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the reconciliation multiple times to ensure idempotency
|
||||||
|
// 8 was arbitrary, but large enough to reasonably trust the result
|
||||||
|
for range 8 {
|
||||||
|
controller.ReconcileAll(ctx)
|
||||||
|
|
||||||
|
if tc.shouldCreateNewPrebuild != nil {
|
||||||
|
newPrebuildCount := 0
|
||||||
|
workspaces, err := db.GetWorkspacesByTemplateID(ctx, templateID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
for _, workspace := range workspaces {
|
||||||
|
if workspace.ID != prebuildID {
|
||||||
|
newPrebuildCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// This test configures a preset that desires one prebuild.
|
||||||
|
// In cases where new prebuilds should be created, there should be exactly one.
|
||||||
|
require.Equal(t, *tc.shouldCreateNewPrebuild, newPrebuildCount == 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tc.shouldDeleteOldPrebuild != nil {
|
||||||
|
builds, err := db.GetWorkspaceBuildsByWorkspaceID(ctx, database.GetWorkspaceBuildsByWorkspaceIDParams{
|
||||||
|
WorkspaceID: prebuildID,
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
if *tc.shouldDeleteOldPrebuild {
|
||||||
|
require.Equal(t, 2, len(builds))
|
||||||
|
require.Equal(t, database.WorkspaceTransitionDelete, builds[0].Transition)
|
||||||
|
} else {
|
||||||
|
require.Equal(t, 1, len(builds))
|
||||||
|
require.Equal(t, prebuildLatestTransition, builds[0].Transition)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func setupTestDBTemplate(
|
func setupTestDBTemplate(
|
||||||
t *testing.T,
|
t *testing.T,
|
||||||
db database.Store,
|
db database.Store,
|
||||||
@ -138,20 +396,17 @@ func setupTestDBTemplate(
|
|||||||
|
|
||||||
return org.ID, user.ID, template.ID
|
return org.ID, user.ID, template.ID
|
||||||
}
|
}
|
||||||
func setupTestDBPrebuild(
|
|
||||||
|
func setupTestDBTemplateVersion(
|
||||||
t *testing.T,
|
t *testing.T,
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
db database.Store,
|
db database.Store,
|
||||||
pubsub pubsub.Pubsub,
|
pubsub pubsub.Pubsub,
|
||||||
prebuildStatus database.WorkspaceStatus,
|
|
||||||
orgID uuid.UUID,
|
orgID uuid.UUID,
|
||||||
userID uuid.UUID,
|
userID uuid.UUID,
|
||||||
templateID uuid.UUID,
|
templateID uuid.UUID,
|
||||||
) (
|
) uuid.UUID {
|
||||||
templateVersionID uuid.UUID,
|
t.Helper()
|
||||||
presetID uuid.UUID,
|
|
||||||
prebuildID uuid.UUID,
|
|
||||||
) {
|
|
||||||
templateVersionJob := dbgen.ProvisionerJob(t, db, pubsub, database.ProvisionerJob{
|
templateVersionJob := dbgen.ProvisionerJob(t, db, pubsub, database.ProvisionerJob{
|
||||||
ID: uuid.New(),
|
ID: uuid.New(),
|
||||||
CreatedAt: time.Now().Add(-2 * time.Hour),
|
CreatedAt: time.Now().Add(-2 * time.Hour),
|
||||||
@ -169,8 +424,26 @@ func setupTestDBPrebuild(
|
|||||||
ID: templateID,
|
ID: templateID,
|
||||||
ActiveVersionID: templateVersion.ID,
|
ActiveVersionID: templateVersion.ID,
|
||||||
})
|
})
|
||||||
|
return templateVersion.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupTestDBPrebuild(
|
||||||
|
t *testing.T,
|
||||||
|
ctx context.Context,
|
||||||
|
db database.Store,
|
||||||
|
pubsub pubsub.Pubsub,
|
||||||
|
transition database.WorkspaceTransition,
|
||||||
|
prebuildStatus database.ProvisionerJobStatus,
|
||||||
|
orgID uuid.UUID,
|
||||||
|
templateID uuid.UUID,
|
||||||
|
templateVersionID uuid.UUID,
|
||||||
|
) (
|
||||||
|
presetID uuid.UUID,
|
||||||
|
prebuildID uuid.UUID,
|
||||||
|
) {
|
||||||
|
t.Helper()
|
||||||
preset, err := db.InsertPreset(ctx, database.InsertPresetParams{
|
preset, err := db.InsertPreset(ctx, database.InsertPresetParams{
|
||||||
TemplateVersionID: templateVersion.ID,
|
TemplateVersionID: templateVersionID,
|
||||||
Name: "test",
|
Name: "test",
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@ -187,30 +460,29 @@ func setupTestDBPrebuild(
|
|||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
completedAt := sql.NullTime{}
|
|
||||||
cancelledAt := sql.NullTime{}
|
cancelledAt := sql.NullTime{}
|
||||||
transition := database.WorkspaceTransitionStart
|
completedAt := sql.NullTime{}
|
||||||
deleted := false
|
|
||||||
|
startedAt := sql.NullTime{}
|
||||||
|
if prebuildStatus != database.ProvisionerJobStatusPending {
|
||||||
|
startedAt = sql.NullTime{Time: time.Now().Add(-2 * time.Hour), Valid: true}
|
||||||
|
}
|
||||||
|
|
||||||
buildError := sql.NullString{}
|
buildError := sql.NullString{}
|
||||||
switch prebuildStatus {
|
if prebuildStatus == database.ProvisionerJobStatusFailed {
|
||||||
case database.WorkspaceStatusRunning:
|
|
||||||
completedAt = sql.NullTime{Time: time.Now().Add(-time.Hour), Valid: true}
|
|
||||||
case database.WorkspaceStatusStopped:
|
|
||||||
completedAt = sql.NullTime{Time: time.Now().Add(-time.Hour), Valid: true}
|
|
||||||
transition = database.WorkspaceTransitionStop
|
|
||||||
case database.WorkspaceStatusFailed:
|
|
||||||
completedAt = sql.NullTime{Time: time.Now().Add(-time.Hour), Valid: true}
|
completedAt = sql.NullTime{Time: time.Now().Add(-time.Hour), Valid: true}
|
||||||
buildError = sql.NullString{String: "build failed", Valid: true}
|
buildError = sql.NullString{String: "build failed", Valid: true}
|
||||||
case database.WorkspaceStatusCanceled:
|
}
|
||||||
|
|
||||||
|
deleted := false
|
||||||
|
switch prebuildStatus {
|
||||||
|
case database.ProvisionerJobStatusCanceling:
|
||||||
|
cancelledAt = sql.NullTime{Time: time.Now().Add(-time.Hour), Valid: true}
|
||||||
|
case database.ProvisionerJobStatusCanceled:
|
||||||
completedAt = sql.NullTime{Time: time.Now().Add(-time.Hour), Valid: true}
|
completedAt = sql.NullTime{Time: time.Now().Add(-time.Hour), Valid: true}
|
||||||
cancelledAt = sql.NullTime{Time: time.Now().Add(-time.Hour), Valid: true}
|
cancelledAt = sql.NullTime{Time: time.Now().Add(-time.Hour), Valid: true}
|
||||||
case database.WorkspaceStatusDeleted:
|
case database.ProvisionerJobStatusSucceeded:
|
||||||
completedAt = sql.NullTime{Time: time.Now().Add(-time.Hour), Valid: true}
|
completedAt = sql.NullTime{Time: time.Now().Add(-time.Hour), Valid: true}
|
||||||
transition = database.WorkspaceTransitionDelete
|
|
||||||
deleted = true
|
|
||||||
case database.WorkspaceStatusPending:
|
|
||||||
completedAt = sql.NullTime{}
|
|
||||||
transition = database.WorkspaceTransitionStart
|
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -223,6 +495,7 @@ func setupTestDBPrebuild(
|
|||||||
job := dbgen.ProvisionerJob(t, db, pubsub, database.ProvisionerJob{
|
job := dbgen.ProvisionerJob(t, db, pubsub, database.ProvisionerJob{
|
||||||
InitiatorID: prebuilds.OwnerID,
|
InitiatorID: prebuilds.OwnerID,
|
||||||
CreatedAt: time.Now().Add(-2 * time.Hour),
|
CreatedAt: time.Now().Add(-2 * time.Hour),
|
||||||
|
StartedAt: startedAt,
|
||||||
CompletedAt: completedAt,
|
CompletedAt: completedAt,
|
||||||
CanceledAt: cancelledAt,
|
CanceledAt: cancelledAt,
|
||||||
OrganizationID: orgID,
|
OrganizationID: orgID,
|
||||||
@ -231,145 +504,13 @@ func setupTestDBPrebuild(
|
|||||||
dbgen.WorkspaceBuild(t, db, database.WorkspaceBuild{
|
dbgen.WorkspaceBuild(t, db, database.WorkspaceBuild{
|
||||||
WorkspaceID: workspace.ID,
|
WorkspaceID: workspace.ID,
|
||||||
InitiatorID: prebuilds.OwnerID,
|
InitiatorID: prebuilds.OwnerID,
|
||||||
TemplateVersionID: templateVersion.ID,
|
TemplateVersionID: templateVersionID,
|
||||||
JobID: job.ID,
|
JobID: job.ID,
|
||||||
TemplateVersionPresetID: uuid.NullUUID{UUID: preset.ID, Valid: true},
|
TemplateVersionPresetID: uuid.NullUUID{UUID: preset.ID, Valid: true},
|
||||||
Transition: transition,
|
Transition: transition,
|
||||||
})
|
})
|
||||||
|
|
||||||
return templateVersion.ID, preset.ID, workspace.ID
|
return preset.ID, workspace.ID
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestActiveTemplateVersionPrebuilds(t *testing.T) {
|
|
||||||
if !dbtestutil.WillUsePostgres() {
|
|
||||||
t.Skip("This test requires postgres")
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
type testCase struct {
|
|
||||||
name string
|
|
||||||
prebuildStatus database.WorkspaceStatus
|
|
||||||
shouldCreateNewPrebuild bool
|
|
||||||
shouldDeleteOldPrebuild bool
|
|
||||||
}
|
|
||||||
|
|
||||||
testCases := []testCase{
|
|
||||||
{
|
|
||||||
name: "running prebuild",
|
|
||||||
prebuildStatus: database.WorkspaceStatusRunning,
|
|
||||||
shouldCreateNewPrebuild: false,
|
|
||||||
shouldDeleteOldPrebuild: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "stopped prebuild",
|
|
||||||
prebuildStatus: database.WorkspaceStatusStopped,
|
|
||||||
shouldCreateNewPrebuild: true,
|
|
||||||
shouldDeleteOldPrebuild: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "failed prebuild",
|
|
||||||
prebuildStatus: database.WorkspaceStatusFailed,
|
|
||||||
shouldCreateNewPrebuild: true,
|
|
||||||
shouldDeleteOldPrebuild: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "canceled prebuild",
|
|
||||||
prebuildStatus: database.WorkspaceStatusCanceled,
|
|
||||||
shouldCreateNewPrebuild: true,
|
|
||||||
shouldDeleteOldPrebuild: false,
|
|
||||||
},
|
|
||||||
// {
|
|
||||||
// name: "deleted prebuild",
|
|
||||||
// prebuildStatus: database.WorkspaceStatusDeleted,
|
|
||||||
// shouldConsiderPrebuildRunning: false,
|
|
||||||
// shouldConsiderPrebuildInProgress: false,
|
|
||||||
// shouldCreateNewPrebuild: true,
|
|
||||||
// shouldDeleteOldPrebuild: false,
|
|
||||||
// },
|
|
||||||
{
|
|
||||||
name: "pending prebuild",
|
|
||||||
prebuildStatus: database.WorkspaceStatusPending,
|
|
||||||
shouldCreateNewPrebuild: false,
|
|
||||||
shouldDeleteOldPrebuild: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tc := range testCases {
|
|
||||||
tc := tc
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
ctx := testutil.Context(t, testutil.WaitShort)
|
|
||||||
db, pubsub := dbtestutil.NewDB(t)
|
|
||||||
cfg := codersdk.PrebuildsConfig{}
|
|
||||||
logger := testutil.Logger(t)
|
|
||||||
controller := prebuilds.NewStoreReconciler(db, pubsub, cfg, logger)
|
|
||||||
|
|
||||||
orgID, userID, templateID := setupTestDBTemplate(t, db)
|
|
||||||
_, _, prebuildID := setupTestDBPrebuild(
|
|
||||||
t,
|
|
||||||
ctx,
|
|
||||||
db,
|
|
||||||
pubsub,
|
|
||||||
tc.prebuildStatus,
|
|
||||||
orgID,
|
|
||||||
userID,
|
|
||||||
templateID,
|
|
||||||
)
|
|
||||||
|
|
||||||
require.NoError(t, controller.ReconcileAll(ctx))
|
|
||||||
|
|
||||||
createdNewPrebuild := false
|
|
||||||
deletedOldPrebuild := true
|
|
||||||
workspaces, err := db.GetWorkspacesByTemplateID(ctx, templateID)
|
|
||||||
require.NoError(t, err)
|
|
||||||
for _, workspace := range workspaces {
|
|
||||||
if workspace.ID == prebuildID {
|
|
||||||
deletedOldPrebuild = false
|
|
||||||
}
|
|
||||||
|
|
||||||
if workspace.ID != prebuildID {
|
|
||||||
createdNewPrebuild = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
require.Equal(t, tc.shouldCreateNewPrebuild, createdNewPrebuild)
|
|
||||||
require.Equal(t, tc.shouldDeleteOldPrebuild, deletedOldPrebuild)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestInactiveTemplateVersionPrebuilds(t *testing.T) {
|
|
||||||
// Scenario: Prebuilds are never created and always deleted if the template version is inactive
|
|
||||||
t.Parallel()
|
|
||||||
t.Skip("todo")
|
|
||||||
|
|
||||||
ctx := testutil.Context(t, testutil.WaitShort)
|
|
||||||
db, pubsub := dbtestutil.NewDB(t)
|
|
||||||
cfg := codersdk.PrebuildsConfig{}
|
|
||||||
logger := testutil.Logger(t)
|
|
||||||
controller := prebuilds.NewStoreReconciler(db, pubsub, cfg, logger)
|
|
||||||
|
|
||||||
// when does a prebuild get deleted?
|
|
||||||
// * when it is in some way permanently ineligible to be claimed
|
|
||||||
// * this could be because the build failed or was canceled
|
|
||||||
// * or it belongs to a template version that is no longer active
|
|
||||||
// * or it belongs to a template version that is deprecated
|
|
||||||
// * when there are more prebuilds than the preset desires
|
|
||||||
// * someone could have manually created a workspace for the prebuild user
|
|
||||||
// * any workspaces that were created for the prebuilds user and don't match a preset should be deleted - deferred
|
|
||||||
|
|
||||||
// given a preset that desires 2 prebuilds
|
|
||||||
// and there are 3 running prebuilds for the preset
|
|
||||||
// and there are 4 non-running prebuilds for the preset
|
|
||||||
// * one is not running because its latest build was a stop transition
|
|
||||||
// * another is not running because its latest build was a delete transition
|
|
||||||
// * a third is not running because its latest build was a start transition but the build failed
|
|
||||||
// * a fourth is not running because its latest build was a start transition but the build was canceled
|
|
||||||
// when we trigger the reconciliation loop for all templates
|
|
||||||
require.NoError(t, controller.ReconcileAll(ctx))
|
|
||||||
// then the four non running prebuilds are deleted
|
|
||||||
// and 1 of the running prebuilds is deleted
|
|
||||||
// because stopped, deleted and failed builds are not considered running in terms of the definition of "running" above.
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO (sasswart): test idempotency of reconciliation
|
|
||||||
// TODO (sasswart): test mutual exclusion
|
// TODO (sasswart): test mutual exclusion
|
||||||
|
@ -176,7 +176,6 @@ func (p presetState) calculateActions() (*reconciliationActions, error) {
|
|||||||
return 0
|
return 0
|
||||||
})
|
})
|
||||||
|
|
||||||
var victims []uuid.UUID
|
|
||||||
for i := 0; i < int(extraneous); i++ {
|
for i := 0; i < int(extraneous); i++ {
|
||||||
if i >= len(p.running) {
|
if i >= len(p.running) {
|
||||||
// This should never happen.
|
// This should never happen.
|
||||||
@ -187,11 +186,9 @@ func (p presetState) calculateActions() (*reconciliationActions, error) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
victims = append(victims, p.running[i].WorkspaceID)
|
actions.deleteIDs = append(actions.deleteIDs, p.running[i].WorkspaceID)
|
||||||
}
|
}
|
||||||
|
|
||||||
actions.deleteIDs = append(actions.deleteIDs, victims...)
|
|
||||||
|
|
||||||
// TODO: move up
|
// TODO: move up
|
||||||
// c.logger.Warn(ctx, "found extra prebuilds running, picking random victim(s)",
|
// c.logger.Warn(ctx, "found extra prebuilds running, picking random victim(s)",
|
||||||
// slog.F("template_id", p.preset.TemplateID.String()), slog.F("desired", desired), slog.F("actual", actual), slog.F("extra", extraneous),
|
// slog.F("template_id", p.preset.TemplateID.String()), slog.F("desired", desired), slog.F("actual", actual), slog.F("extra", extraneous),
|
||||||
|
Reference in New Issue
Block a user