Skip to content

Commit ff9e54b

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

File tree

1 file changed

+192
-0
lines changed

1 file changed

+192
-0
lines changed

enterprise/coderd/prebuilds/claim_test.go

Lines changed: 192 additions & 0 deletions
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"
@@ -27,6 +28,32 @@ import (
2728
"github.com/coder/coder/v2/testutil"
2829
)
2930

31+
type errorStore struct {
32+
claimingErr error
33+
34+
database.Store
35+
}
36+
37+
func newErrorStore(db database.Store, claimingErr error) *errorStore {
38+
return &errorStore{
39+
Store: db,
40+
claimingErr: claimingErr,
41+
}
42+
}
43+
44+
func (es *errorStore) InTx(fn func(store database.Store) error, opts *database.TxOptions) error {
45+
// Pass failure store down into transaction store.
46+
return es.Store.InTx(func(store database.Store) error {
47+
newES := newErrorStore(store, es.claimingErr)
48+
49+
return fn(newES)
50+
}, opts)
51+
}
52+
53+
func (es *errorStore) ClaimPrebuiltWorkspace(ctx context.Context, arg database.ClaimPrebuiltWorkspaceParams) (database.ClaimPrebuiltWorkspaceRow, error) {
54+
return database.ClaimPrebuiltWorkspaceRow{}, es.claimingErr
55+
}
56+
3057
type storeSpy struct {
3158
database.Store
3259

@@ -66,6 +93,171 @@ func (m *storeSpy) ClaimPrebuiltWorkspace(ctx context.Context, arg database.Clai
6693
return result, err
6794
}
6895

96+
func TestClaimPrebuild_CheckDifferentErrors(t *testing.T) {
97+
t.Parallel()
98+
99+
if !dbtestutil.WillUsePostgres() {
100+
t.Skip("This test requires postgres")
101+
}
102+
103+
const (
104+
desiredInstances = 1
105+
presetCount = 2
106+
107+
expectedPrebuildsCount = desiredInstances * presetCount
108+
)
109+
110+
cases := map[string]struct {
111+
claimingErr error
112+
checkFn func(
113+
t *testing.T,
114+
ctx context.Context,
115+
store database.Store,
116+
userClient *codersdk.Client,
117+
user codersdk.User,
118+
templateVersionID uuid.UUID,
119+
presetID uuid.UUID,
120+
)
121+
}{
122+
"ErrNoClaimablePrebuiltWorkspaces is returned": {
123+
claimingErr: agplprebuilds.ErrNoClaimablePrebuiltWorkspaces,
124+
checkFn: func(
125+
t *testing.T,
126+
ctx context.Context,
127+
store database.Store,
128+
userClient *codersdk.Client,
129+
user codersdk.User,
130+
templateVersionID uuid.UUID,
131+
presetID uuid.UUID,
132+
) {
133+
// When: a user creates a new workspace with a preset for which prebuilds are configured.
134+
workspaceName := strings.ReplaceAll(testutil.GetRandomName(t), "_", "-")
135+
userWorkspace, err := userClient.CreateUserWorkspace(ctx, user.Username, codersdk.CreateWorkspaceRequest{
136+
TemplateVersionID: templateVersionID,
137+
Name: workspaceName,
138+
TemplateVersionPresetID: presetID,
139+
})
140+
141+
require.NoError(t, err)
142+
coderdtest.AwaitWorkspaceBuildJobCompleted(t, userClient, userWorkspace.LatestBuild.ID)
143+
144+
// Then: the number of running prebuilds hasn't changed because claiming prebuild is failed and we fallback to creating new workspace.
145+
currentPrebuilds, err := store.GetRunningPrebuiltWorkspaces(ctx)
146+
require.NoError(t, err)
147+
require.Equal(t, expectedPrebuildsCount, len(currentPrebuilds))
148+
},
149+
},
150+
"unexpected error during claim is returned": {
151+
claimingErr: errors.New("unexpected error during claim"),
152+
checkFn: func(
153+
t *testing.T,
154+
ctx context.Context,
155+
store database.Store,
156+
userClient *codersdk.Client,
157+
user codersdk.User,
158+
templateVersionID uuid.UUID,
159+
presetID uuid.UUID,
160+
) {
161+
// When: a user creates a new workspace with a preset for which prebuilds are configured.
162+
workspaceName := strings.ReplaceAll(testutil.GetRandomName(t), "_", "-")
163+
_, err := userClient.CreateUserWorkspace(ctx, user.Username, codersdk.CreateWorkspaceRequest{
164+
TemplateVersionID: templateVersionID,
165+
Name: workspaceName,
166+
TemplateVersionPresetID: presetID,
167+
})
168+
169+
// Then: unexpected error happened and was propagated all the way to the caller
170+
require.Error(t, err)
171+
require.ErrorContains(t, err, "unexpected error during claim")
172+
},
173+
},
174+
}
175+
176+
for name, tc := range cases {
177+
t.Run(name, func(t *testing.T) {
178+
t.Parallel()
179+
180+
// Setup.
181+
ctx := testutil.Context(t, testutil.WaitMedium)
182+
db, pubsub := dbtestutil.NewDB(t)
183+
failureStore := newErrorStore(db, tc.claimingErr)
184+
185+
logger := testutil.Logger(t)
186+
client, _, api, owner := coderdenttest.NewWithAPI(t, &coderdenttest.Options{
187+
Options: &coderdtest.Options{
188+
IncludeProvisionerDaemon: true,
189+
Database: failureStore,
190+
Pubsub: pubsub,
191+
},
192+
193+
EntitlementsUpdateInterval: time.Second,
194+
})
195+
196+
reconciler := prebuilds.NewStoreReconciler(failureStore, pubsub, codersdk.PrebuildsConfig{}, logger, quartz.NewMock(t))
197+
var claimer agplprebuilds.Claimer = &prebuilds.EnterpriseClaimer{}
198+
api.AGPL.PrebuildsClaimer.Store(&claimer)
199+
200+
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, templateWithAgentAndPresetsWithPrebuilds(desiredInstances))
201+
_ = coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
202+
coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID)
203+
presets, err := client.TemplateVersionPresets(ctx, version.ID)
204+
require.NoError(t, err)
205+
require.Len(t, presets, presetCount)
206+
207+
userClient, user := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID, rbac.RoleMember())
208+
209+
ctx = dbauthz.AsPrebuildsOrchestrator(ctx)
210+
211+
// Given: the reconciliation state is snapshot.
212+
state, err := reconciler.SnapshotState(ctx, failureStore)
213+
require.NoError(t, err)
214+
require.Len(t, state.Presets, presetCount)
215+
216+
// When: a reconciliation is setup for each preset.
217+
for _, preset := range presets {
218+
ps, err := state.FilterByPreset(preset.ID)
219+
require.NoError(t, err)
220+
require.NotNil(t, ps)
221+
actions, err := reconciler.CalculateActions(ctx, *ps)
222+
require.NoError(t, err)
223+
require.NotNil(t, actions)
224+
225+
require.NoError(t, reconciler.ReconcilePreset(ctx, *ps))
226+
}
227+
228+
// Given: a set of running, eligible prebuilds eventually starts up.
229+
runningPrebuilds := make(map[uuid.UUID]database.GetRunningPrebuiltWorkspacesRow, desiredInstances*presetCount)
230+
require.Eventually(t, func() bool {
231+
rows, err := failureStore.GetRunningPrebuiltWorkspaces(ctx)
232+
require.NoError(t, err)
233+
234+
for _, row := range rows {
235+
runningPrebuilds[row.CurrentPresetID.UUID] = row
236+
237+
agents, err := db.GetWorkspaceAgentsInLatestBuildByWorkspaceID(ctx, row.ID)
238+
require.NoError(t, err)
239+
240+
// Workspaces are eligible once its agent is marked "ready".
241+
for _, agent := range agents {
242+
require.NoError(t, db.UpdateWorkspaceAgentLifecycleStateByID(ctx, database.UpdateWorkspaceAgentLifecycleStateByIDParams{
243+
ID: agent.ID,
244+
LifecycleState: database.WorkspaceAgentLifecycleStateReady,
245+
StartedAt: sql.NullTime{Time: time.Now().Add(time.Hour), Valid: true},
246+
ReadyAt: sql.NullTime{Time: time.Now().Add(-1 * time.Hour), Valid: true},
247+
}))
248+
}
249+
}
250+
251+
t.Logf("found %d running prebuilds so far, want %d", len(runningPrebuilds), expectedPrebuildsCount)
252+
253+
return len(runningPrebuilds) == expectedPrebuildsCount
254+
}, testutil.WaitSuperLong, testutil.IntervalSlow)
255+
256+
tc.checkFn(t, ctx, failureStore, userClient, user, version.ID, presets[0].ID)
257+
})
258+
}
259+
}
260+
69261
func TestClaimPrebuild(t *testing.T) {
70262
t.Parallel()
71263

0 commit comments

Comments
 (0)