Skip to content

Commit fcdbba8

Browse files
test: add failure testcase scenario
1 parent ff8d3de commit fcdbba8

File tree

1 file changed

+197
-0
lines changed

1 file changed

+197
-0
lines changed

enterprise/coderd/prebuilds/claim_test.go

+197
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package prebuilds_test
33
import (
44
"context"
55
"database/sql"
6+
"errors"
67
"strings"
78
"sync/atomic"
89
"testing"
@@ -66,6 +67,32 @@ func (m *storeSpy) ClaimPrebuiltWorkspace(ctx context.Context, arg database.Clai
6667
return result, err
6768
}
6869

70+
type errorStore struct {
71+
claimingErr error
72+
73+
database.Store
74+
}
75+
76+
func newErrorStore(db database.Store, claimingErr error) *errorStore {
77+
return &errorStore{
78+
Store: db,
79+
claimingErr: claimingErr,
80+
}
81+
}
82+
83+
func (es *errorStore) InTx(fn func(store database.Store) error, opts *database.TxOptions) error {
84+
// Pass failure store down into transaction store.
85+
return es.Store.InTx(func(store database.Store) error {
86+
newES := newErrorStore(store, es.claimingErr)
87+
88+
return fn(newES)
89+
}, opts)
90+
}
91+
92+
func (es *errorStore) ClaimPrebuiltWorkspace(ctx context.Context, arg database.ClaimPrebuiltWorkspaceParams) (database.ClaimPrebuiltWorkspaceRow, error) {
93+
return database.ClaimPrebuiltWorkspaceRow{}, es.claimingErr
94+
}
95+
6996
func TestClaimPrebuild(t *testing.T) {
7097
t.Parallel()
7198

@@ -284,6 +311,176 @@ func TestClaimPrebuild(t *testing.T) {
284311
}
285312
}
286313

314+
func TestClaimPrebuild_CheckDifferentErrors(t *testing.T) {
315+
t.Parallel()
316+
317+
if !dbtestutil.WillUsePostgres() {
318+
t.Skip("This test requires postgres")
319+
}
320+
321+
const (
322+
desiredInstances = 1
323+
presetCount = 2
324+
325+
expectedPrebuildsCount = desiredInstances * presetCount
326+
)
327+
328+
cases := map[string]struct {
329+
claimingErr error
330+
checkFn func(
331+
t *testing.T,
332+
ctx context.Context,
333+
store database.Store,
334+
userClient *codersdk.Client,
335+
user codersdk.User,
336+
templateVersionID uuid.UUID,
337+
presetID uuid.UUID,
338+
)
339+
}{
340+
"ErrNoClaimablePrebuiltWorkspaces is returned": {
341+
claimingErr: agplprebuilds.ErrNoClaimablePrebuiltWorkspaces,
342+
checkFn: func(
343+
t *testing.T,
344+
ctx context.Context,
345+
store database.Store,
346+
userClient *codersdk.Client,
347+
user codersdk.User,
348+
templateVersionID uuid.UUID,
349+
presetID uuid.UUID,
350+
) {
351+
// When: a user creates a new workspace with a preset for which prebuilds are configured.
352+
workspaceName := strings.ReplaceAll(testutil.GetRandomName(t), "_", "-")
353+
userWorkspace, err := userClient.CreateUserWorkspace(ctx, user.Username, codersdk.CreateWorkspaceRequest{
354+
TemplateVersionID: templateVersionID,
355+
Name: workspaceName,
356+
TemplateVersionPresetID: presetID,
357+
})
358+
359+
require.NoError(t, err)
360+
coderdtest.AwaitWorkspaceBuildJobCompleted(t, userClient, userWorkspace.LatestBuild.ID)
361+
362+
// Then: the number of running prebuilds hasn't changed because claiming prebuild is failed and we fallback to creating new workspace.
363+
currentPrebuilds, err := store.GetRunningPrebuiltWorkspaces(ctx)
364+
require.NoError(t, err)
365+
require.Equal(t, expectedPrebuildsCount, len(currentPrebuilds))
366+
},
367+
},
368+
"unexpected error during claim is returned": {
369+
claimingErr: errors.New("unexpected error during claim"),
370+
checkFn: func(
371+
t *testing.T,
372+
ctx context.Context,
373+
store database.Store,
374+
userClient *codersdk.Client,
375+
user codersdk.User,
376+
templateVersionID uuid.UUID,
377+
presetID uuid.UUID,
378+
) {
379+
// When: a user creates a new workspace with a preset for which prebuilds are configured.
380+
workspaceName := strings.ReplaceAll(testutil.GetRandomName(t), "_", "-")
381+
_, err := userClient.CreateUserWorkspace(ctx, user.Username, codersdk.CreateWorkspaceRequest{
382+
TemplateVersionID: templateVersionID,
383+
Name: workspaceName,
384+
TemplateVersionPresetID: presetID,
385+
})
386+
387+
// Then: unexpected error happened and was propagated all the way to the caller
388+
require.Error(t, err)
389+
require.ErrorContains(t, err, "unexpected error during claim")
390+
391+
// Then: the number of running prebuilds hasn't changed because claiming prebuild is failed.
392+
currentPrebuilds, err := store.GetRunningPrebuiltWorkspaces(ctx)
393+
require.NoError(t, err)
394+
require.Equal(t, expectedPrebuildsCount, len(currentPrebuilds))
395+
},
396+
},
397+
}
398+
399+
for name, tc := range cases {
400+
t.Run(name, func(t *testing.T) {
401+
t.Parallel()
402+
403+
// Setup.
404+
ctx := testutil.Context(t, testutil.WaitMedium)
405+
db, pubsub := dbtestutil.NewDB(t)
406+
errorStore := newErrorStore(db, tc.claimingErr)
407+
408+
logger := testutil.Logger(t)
409+
client, _, api, owner := coderdenttest.NewWithAPI(t, &coderdenttest.Options{
410+
Options: &coderdtest.Options{
411+
IncludeProvisionerDaemon: true,
412+
Database: errorStore,
413+
Pubsub: pubsub,
414+
},
415+
416+
EntitlementsUpdateInterval: time.Second,
417+
})
418+
419+
reconciler := prebuilds.NewStoreReconciler(errorStore, pubsub, codersdk.PrebuildsConfig{}, logger, quartz.NewMock(t))
420+
var claimer agplprebuilds.Claimer = &prebuilds.EnterpriseClaimer{}
421+
api.AGPL.PrebuildsClaimer.Store(&claimer)
422+
423+
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, templateWithAgentAndPresetsWithPrebuilds(desiredInstances))
424+
_ = coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
425+
coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID)
426+
presets, err := client.TemplateVersionPresets(ctx, version.ID)
427+
require.NoError(t, err)
428+
require.Len(t, presets, presetCount)
429+
430+
userClient, user := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID, rbac.RoleMember())
431+
432+
ctx = dbauthz.AsPrebuildsOrchestrator(ctx)
433+
434+
// Given: the reconciliation state is snapshot.
435+
state, err := reconciler.SnapshotState(ctx, errorStore)
436+
require.NoError(t, err)
437+
require.Len(t, state.Presets, presetCount)
438+
439+
// When: a reconciliation is setup for each preset.
440+
for _, preset := range presets {
441+
ps, err := state.FilterByPreset(preset.ID)
442+
require.NoError(t, err)
443+
require.NotNil(t, ps)
444+
actions, err := reconciler.CalculateActions(ctx, *ps)
445+
require.NoError(t, err)
446+
require.NotNil(t, actions)
447+
448+
require.NoError(t, reconciler.ReconcilePreset(ctx, *ps))
449+
}
450+
451+
// Given: a set of running, eligible prebuilds eventually starts up.
452+
runningPrebuilds := make(map[uuid.UUID]database.GetRunningPrebuiltWorkspacesRow, desiredInstances*presetCount)
453+
require.Eventually(t, func() bool {
454+
rows, err := errorStore.GetRunningPrebuiltWorkspaces(ctx)
455+
require.NoError(t, err)
456+
457+
for _, row := range rows {
458+
runningPrebuilds[row.CurrentPresetID.UUID] = row
459+
460+
agents, err := db.GetWorkspaceAgentsInLatestBuildByWorkspaceID(ctx, row.ID)
461+
require.NoError(t, err)
462+
463+
// Workspaces are eligible once its agent is marked "ready".
464+
for _, agent := range agents {
465+
require.NoError(t, db.UpdateWorkspaceAgentLifecycleStateByID(ctx, database.UpdateWorkspaceAgentLifecycleStateByIDParams{
466+
ID: agent.ID,
467+
LifecycleState: database.WorkspaceAgentLifecycleStateReady,
468+
StartedAt: sql.NullTime{Time: time.Now().Add(time.Hour), Valid: true},
469+
ReadyAt: sql.NullTime{Time: time.Now().Add(-1 * time.Hour), Valid: true},
470+
}))
471+
}
472+
}
473+
474+
t.Logf("found %d running prebuilds so far, want %d", len(runningPrebuilds), expectedPrebuildsCount)
475+
476+
return len(runningPrebuilds) == expectedPrebuildsCount
477+
}, testutil.WaitSuperLong, testutil.IntervalSlow)
478+
479+
tc.checkFn(t, ctx, errorStore, userClient, user, version.ID, presets[0].ID)
480+
})
481+
}
482+
}
483+
287484
func templateWithAgentAndPresetsWithPrebuilds(desiredInstances int32) *echo.Responses {
288485
return &echo.Responses{
289486
Parse: echo.ParseComplete,

0 commit comments

Comments
 (0)