@@ -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 ,
0 commit comments