@@ -3,6 +3,7 @@ package prebuilds_test
3
3
import (
4
4
"context"
5
5
"database/sql"
6
+ "errors"
6
7
"slices"
7
8
"strings"
8
9
"sync/atomic"
@@ -35,21 +36,25 @@ type storeSpy struct {
35
36
claims * atomic.Int32
36
37
claimParams * atomic.Pointer [database.ClaimPrebuiltWorkspaceParams ]
37
38
claimedWorkspace * atomic.Pointer [database.ClaimPrebuiltWorkspaceRow ]
39
+
40
+ // if claimingErr is not nil - error will be returned when ClaimPrebuiltWorkspace is called
41
+ claimingErr error
38
42
}
39
43
40
- func newStoreSpy (db database.Store )* storeSpy {
44
+ func newStoreSpy (db database.Store , claimingErr error )* storeSpy {
41
45
return & storeSpy {
42
46
Store :db ,
43
47
claims :& atomic.Int32 {},
44
48
claimParams :& atomic.Pointer [database.ClaimPrebuiltWorkspaceParams ]{},
45
49
claimedWorkspace :& atomic.Pointer [database.ClaimPrebuiltWorkspaceRow ]{},
50
+ claimingErr :claimingErr ,
46
51
}
47
52
}
48
53
49
54
func (m * storeSpy )InTx (fn func (store database.Store )error ,opts * database.TxOptions )error {
50
55
// Pass spy down into transaction store.
51
56
return m .Store .InTx (func (store database.Store )error {
52
- spy := newStoreSpy (store )
57
+ spy := newStoreSpy (store , m . claimingErr )
53
58
spy .claims = m .claims
54
59
spy .claimParams = m .claimParams
55
60
spy .claimedWorkspace = m .claimedWorkspace
@@ -59,6 +64,10 @@ func (m *storeSpy) InTx(fn func(store database.Store) error, opts *database.TxOp
59
64
}
60
65
61
66
func (m * storeSpy )ClaimPrebuiltWorkspace (ctx context.Context ,arg database.ClaimPrebuiltWorkspaceParams ) (database.ClaimPrebuiltWorkspaceRow ,error ) {
67
+ if m .claimingErr != nil {
68
+ return database.ClaimPrebuiltWorkspaceRow {},m .claimingErr
69
+ }
70
+
62
71
m .claims .Add (1 )
63
72
m .claimParams .Store (& arg )
64
73
result ,err := m .Store .ClaimPrebuiltWorkspace (ctx ,arg )
@@ -68,32 +77,6 @@ func (m *storeSpy) ClaimPrebuiltWorkspace(ctx context.Context, arg database.Clai
68
77
return result ,err
69
78
}
70
79
71
- type errorStore struct {
72
- claimingErr error
73
-
74
- database.Store
75
- }
76
-
77
- func newErrorStore (db database.Store ,claimingErr error )* errorStore {
78
- return & errorStore {
79
- Store :db ,
80
- claimingErr :claimingErr ,
81
- }
82
- }
83
-
84
- func (es * errorStore )InTx (fn func (store database.Store )error ,opts * database.TxOptions )error {
85
- // Pass failure store down into transaction store.
86
- return es .Store .InTx (func (store database.Store )error {
87
- newES := newErrorStore (store ,es .claimingErr )
88
-
89
- return fn (newES )
90
- },opts )
91
- }
92
-
93
- func (es * errorStore )ClaimPrebuiltWorkspace (ctx context.Context ,arg database.ClaimPrebuiltWorkspaceParams ) (database.ClaimPrebuiltWorkspaceRow ,error ) {
94
- return database.ClaimPrebuiltWorkspaceRow {},es .claimingErr
95
- }
96
-
97
80
func TestClaimPrebuild (t * testing.T ) {
98
81
t .Parallel ()
99
82
@@ -106,9 +89,13 @@ func TestClaimPrebuild(t *testing.T) {
106
89
presetCount = 2
107
90
)
108
91
92
+ unexpectedClaimingError := xerrors .New ("unexpected claiming error" )
93
+
109
94
cases := map [string ]struct {
110
95
expectPrebuildClaimed bool
111
96
markPrebuildsClaimable bool
97
+ // if claimingErr is not nil - error will be returned when ClaimPrebuiltWorkspace is called
98
+ claimingErr error
112
99
}{
113
100
"no eligible prebuilds to claim" : {
114
101
expectPrebuildClaimed :false ,
@@ -118,6 +105,17 @@ func TestClaimPrebuild(t *testing.T) {
118
105
expectPrebuildClaimed :true ,
119
106
markPrebuildsClaimable :true ,
120
107
},
108
+
109
+ "no claimable prebuilt workspaces error is returned" : {
110
+ expectPrebuildClaimed :false ,
111
+ markPrebuildsClaimable :true ,
112
+ claimingErr :agplprebuilds .ErrNoClaimablePrebuiltWorkspaces ,
113
+ },
114
+ "unexpected claiming error is returned" : {
115
+ expectPrebuildClaimed :false ,
116
+ markPrebuildsClaimable :true ,
117
+ claimingErr :unexpectedClaimingError ,
118
+ },
121
119
}
122
120
123
121
for name ,tc := range cases {
@@ -129,7 +127,8 @@ func TestClaimPrebuild(t *testing.T) {
129
127
// Setup.
130
128
ctx := testutil .Context (t ,testutil .WaitSuperLong )
131
129
db ,pubsub := dbtestutil .NewDB (t )
132
- spy := newStoreSpy (db )
130
+
131
+ spy := newStoreSpy (db ,tc .claimingErr )
133
132
expectedPrebuildsCount := desiredInstances * presetCount
134
133
135
134
logger := testutil .Logger (t )
@@ -225,8 +224,35 @@ func TestClaimPrebuild(t *testing.T) {
225
224
TemplateVersionPresetID :presets [0 ].ID ,
226
225
})
227
226
228
- require .NoError (t ,err )
229
- coderdtest .AwaitWorkspaceBuildJobCompleted (t ,userClient ,userWorkspace .LatestBuild .ID )
227
+ switch {
228
+ case tc .claimingErr != nil && errors .Is (tc .claimingErr ,agplprebuilds .ErrNoClaimablePrebuiltWorkspaces ):
229
+ require .NoError (t ,err )
230
+ coderdtest .AwaitWorkspaceBuildJobCompleted (t ,userClient ,userWorkspace .LatestBuild .ID )
231
+
232
+ // Then: the number of running prebuilds hasn't changed because claiming prebuild is failed and we fallback to creating new workspace.
233
+ currentPrebuilds ,err := spy .GetRunningPrebuiltWorkspaces (ctx )
234
+ require .NoError (t ,err )
235
+ require .Equal (t ,expectedPrebuildsCount ,len (currentPrebuilds ))
236
+ return
237
+
238
+ case tc .claimingErr != nil && errors .Is (tc .claimingErr ,unexpectedClaimingError ):
239
+ // Then: unexpected error happened and was propagated all the way to the caller
240
+ require .Error (t ,err )
241
+ require .ErrorContains (t ,err ,unexpectedClaimingError .Error ())
242
+
243
+ // Then: the number of running prebuilds hasn't changed because claiming prebuild is failed.
244
+ currentPrebuilds ,err := spy .GetRunningPrebuiltWorkspaces (ctx )
245
+ require .NoError (t ,err )
246
+ require .Equal (t ,expectedPrebuildsCount ,len (currentPrebuilds ))
247
+ return
248
+
249
+ default :
250
+ // tc.claimingErr is nil scenario
251
+ require .NoError (t ,err )
252
+ coderdtest .AwaitWorkspaceBuildJobCompleted (t ,userClient ,userWorkspace .LatestBuild .ID )
253
+ }
254
+
255
+ // at this point we know that tc.claimingErr is nil
230
256
231
257
// Then: a prebuild should have been claimed.
232
258
require .EqualValues (t ,spy .claims .Load (),1 )
@@ -315,181 +341,6 @@ func TestClaimPrebuild(t *testing.T) {
315
341
}
316
342
}
317
343
318
- func TestClaimPrebuild_CheckDifferentErrors (t * testing.T ) {
319
- t .Parallel ()
320
-
321
- if ! dbtestutil .WillUsePostgres () {
322
- t .Skip ("This test requires postgres" )
323
- }
324
-
325
- const (
326
- desiredInstances = 1
327
- presetCount = 2
328
-
329
- expectedPrebuildsCount = desiredInstances * presetCount
330
- )
331
-
332
- cases := map [string ]struct {
333
- claimingErr error
334
- checkFn func (
335
- t * testing.T ,
336
- ctx context.Context ,
337
- store database.Store ,
338
- userClient * codersdk.Client ,
339
- user codersdk.User ,
340
- templateVersionID uuid.UUID ,
341
- presetID uuid.UUID ,
342
- )
343
- }{
344
- "ErrNoClaimablePrebuiltWorkspaces is returned" : {
345
- claimingErr :agplprebuilds .ErrNoClaimablePrebuiltWorkspaces ,
346
- checkFn :func (
347
- t * testing.T ,
348
- ctx context.Context ,
349
- store database.Store ,
350
- userClient * codersdk.Client ,
351
- user codersdk.User ,
352
- templateVersionID uuid.UUID ,
353
- presetID uuid.UUID ,
354
- ) {
355
- // When: a user creates a new workspace with a preset for which prebuilds are configured.
356
- workspaceName := strings .ReplaceAll (testutil .GetRandomName (t ),"_" ,"-" )
357
- userWorkspace ,err := userClient .CreateUserWorkspace (ctx ,user .Username , codersdk.CreateWorkspaceRequest {
358
- TemplateVersionID :templateVersionID ,
359
- Name :workspaceName ,
360
- TemplateVersionPresetID :presetID ,
361
- })
362
-
363
- require .NoError (t ,err )
364
- coderdtest .AwaitWorkspaceBuildJobCompleted (t ,userClient ,userWorkspace .LatestBuild .ID )
365
-
366
- // Then: the number of running prebuilds hasn't changed because claiming prebuild is failed and we fallback to creating new workspace.
367
- currentPrebuilds ,err := store .GetRunningPrebuiltWorkspaces (ctx )
368
- require .NoError (t ,err )
369
- require .Equal (t ,expectedPrebuildsCount ,len (currentPrebuilds ))
370
- },
371
- },
372
- "unexpected error during claim is returned" : {
373
- claimingErr :xerrors .New ("unexpected error during claim" ),
374
- checkFn :func (
375
- t * testing.T ,
376
- ctx context.Context ,
377
- store database.Store ,
378
- userClient * codersdk.Client ,
379
- user codersdk.User ,
380
- templateVersionID uuid.UUID ,
381
- presetID uuid.UUID ,
382
- ) {
383
- // When: a user creates a new workspace with a preset for which prebuilds are configured.
384
- workspaceName := strings .ReplaceAll (testutil .GetRandomName (t ),"_" ,"-" )
385
- _ ,err := userClient .CreateUserWorkspace (ctx ,user .Username , codersdk.CreateWorkspaceRequest {
386
- TemplateVersionID :templateVersionID ,
387
- Name :workspaceName ,
388
- TemplateVersionPresetID :presetID ,
389
- })
390
-
391
- // Then: unexpected error happened and was propagated all the way to the caller
392
- require .Error (t ,err )
393
- require .ErrorContains (t ,err ,"unexpected error during claim" )
394
-
395
- // Then: the number of running prebuilds hasn't changed because claiming prebuild is failed.
396
- currentPrebuilds ,err := store .GetRunningPrebuiltWorkspaces (ctx )
397
- require .NoError (t ,err )
398
- require .Equal (t ,expectedPrebuildsCount ,len (currentPrebuilds ))
399
- },
400
- },
401
- }
402
-
403
- for name ,tc := range cases {
404
- t .Run (name ,func (t * testing.T ) {
405
- t .Parallel ()
406
-
407
- // Setup.
408
- ctx := testutil .Context (t ,testutil .WaitSuperLong )
409
- db ,pubsub := dbtestutil .NewDB (t )
410
- errorStore := newErrorStore (db ,tc .claimingErr )
411
-
412
- logger := testutil .Logger (t )
413
- client ,_ ,api ,owner := coderdenttest .NewWithAPI (t ,& coderdenttest.Options {
414
- Options :& coderdtest.Options {
415
- IncludeProvisionerDaemon :true ,
416
- Database :errorStore ,
417
- Pubsub :pubsub ,
418
- },
419
-
420
- EntitlementsUpdateInterval :time .Second ,
421
- })
422
-
423
- reconciler := prebuilds .NewStoreReconciler (errorStore ,pubsub , codersdk.PrebuildsConfig {},logger ,quartz .NewMock (t ),api .PrometheusRegistry )
424
- var claimer agplprebuilds.Claimer = prebuilds .NewEnterpriseClaimer (errorStore )
425
- api .AGPL .PrebuildsClaimer .Store (& claimer )
426
-
427
- version := coderdtest .CreateTemplateVersion (t ,client ,owner .OrganizationID ,templateWithAgentAndPresetsWithPrebuilds (desiredInstances ))
428
- _ = coderdtest .AwaitTemplateVersionJobCompleted (t ,client ,version .ID )
429
- coderdtest .CreateTemplate (t ,client ,owner .OrganizationID ,version .ID )
430
- presets ,err := client .TemplateVersionPresets (ctx ,version .ID )
431
- require .NoError (t ,err )
432
- require .Len (t ,presets ,presetCount )
433
-
434
- userClient ,user := coderdtest .CreateAnotherUser (t ,client ,owner .OrganizationID ,rbac .RoleMember ())
435
-
436
- // Given: the reconciliation state is snapshot.
437
- state ,err := reconciler .SnapshotState (ctx ,errorStore )
438
- require .NoError (t ,err )
439
- require .Len (t ,state .Presets ,presetCount )
440
-
441
- // When: a reconciliation is setup for each preset.
442
- for _ ,preset := range presets {
443
- ps ,err := state .FilterByPreset (preset .ID )
444
- require .NoError (t ,err )
445
- require .NotNil (t ,ps )
446
- actions ,err := reconciler .CalculateActions (ctx ,* ps )
447
- require .NoError (t ,err )
448
- require .NotNil (t ,actions )
449
-
450
- require .NoError (t ,reconciler .ReconcilePreset (ctx ,* ps ))
451
- }
452
-
453
- // Given: a set of running, eligible prebuilds eventually starts up.
454
- runningPrebuilds := make (map [uuid.UUID ]database.GetRunningPrebuiltWorkspacesRow ,desiredInstances * presetCount )
455
- require .Eventually (t ,func ()bool {
456
- rows ,err := errorStore .GetRunningPrebuiltWorkspaces (ctx )
457
- if err != nil {
458
- return false
459
- }
460
-
461
- for _ ,row := range rows {
462
- runningPrebuilds [row .CurrentPresetID .UUID ]= row
463
-
464
- agents ,err := db .GetWorkspaceAgentsInLatestBuildByWorkspaceID (ctx ,row .ID )
465
- if err != nil {
466
- return false
467
- }
468
-
469
- // Workspaces are eligible once its agent is marked "ready".
470
- for _ ,agent := range agents {
471
- err = db .UpdateWorkspaceAgentLifecycleStateByID (ctx , database.UpdateWorkspaceAgentLifecycleStateByIDParams {
472
- ID :agent .ID ,
473
- LifecycleState :database .WorkspaceAgentLifecycleStateReady ,
474
- StartedAt : sql.NullTime {Time :time .Now ().Add (time .Hour ),Valid :true },
475
- ReadyAt : sql.NullTime {Time :time .Now ().Add (- 1 * time .Hour ),Valid :true },
476
- })
477
- if err != nil {
478
- return false
479
- }
480
- }
481
- }
482
-
483
- t .Logf ("found %d running prebuilds so far, want %d" ,len (runningPrebuilds ),expectedPrebuildsCount )
484
-
485
- return len (runningPrebuilds )== expectedPrebuildsCount
486
- },testutil .WaitSuperLong ,testutil .IntervalSlow )
487
-
488
- tc .checkFn (t ,ctx ,errorStore ,userClient ,user ,version .ID ,presets [0 ].ID )
489
- })
490
- }
491
- }
492
-
493
344
func templateWithAgentAndPresetsWithPrebuilds (desiredInstances int32 )* echo.Responses {
494
345
return & echo.Responses {
495
346
Parse :echo .ParseComplete ,