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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy