@@ -3,6 +3,7 @@ package prebuilds_test
3
3
import (
4
4
"context"
5
5
"database/sql"
6
+ "errors"
6
7
"strings"
7
8
"sync/atomic"
8
9
"testing"
@@ -66,6 +67,32 @@ func (m *storeSpy) ClaimPrebuiltWorkspace(ctx context.Context, arg database.Clai
66
67
return result , err
67
68
}
68
69
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
+
69
96
func TestClaimPrebuild (t * testing.T ) {
70
97
t .Parallel ()
71
98
@@ -284,6 +311,176 @@ func TestClaimPrebuild(t *testing.T) {
284
311
}
285
312
}
286
313
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
+
287
484
func templateWithAgentAndPresetsWithPrebuilds (desiredInstances int32 ) * echo.Responses {
288
485
return & echo.Responses {
289
486
Parse : echo .ParseComplete ,
0 commit comments