@@ -61,6 +61,10 @@ func verifyQuota(ctx context.Context, t *testing.T, client *codersdk.Client, org
61
61
func TestWorkspaceQuota (t * testing.T ) {
62
62
t .Parallel ()
63
63
64
+ if ! dbtestutil .WillUsePostgres () {
65
+ t .Fatal ("We should only run this test with postgres" )
66
+ }
67
+
64
68
// This first test verifies the behavior of creating and deleting workspaces.
65
69
// It also tests multi-group quota stacking and the everyone group.
66
70
t .Run ("CreateDelete" , func (t * testing.T ) {
@@ -381,13 +385,71 @@ func TestWorkspaceSerialization(t *testing.T) {
381
385
382
386
// UpdateBuildDeadline bumps a workspace deadline while doing a quota
383
387
// commit.
384
- // pq: could not serialize access due to concurrent update
385
388
//
386
389
// Note: This passes if the interrupt is run before 'GetQuota()'
387
390
// Passing orders:
388
391
// - BeginTX -> Bump! -> GetQuota -> GetAllowance -> UpdateCost -> EndTx
389
392
// - BeginTX -> GetQuota -> GetAllowance -> UpdateCost -> Bump! -> EndTx
390
393
t .Run ("UpdateBuildDeadline" , func (t * testing.T ) {
394
+ // +------------------------------+------------------+
395
+ // | Begin Tx | |
396
+ // +------------------------------+------------------+
397
+ // | GetQuota(user) | |
398
+ // +------------------------------+------------------+
399
+ // | | BumpDeadline(w1) |
400
+ // +------------------------------+------------------+
401
+ // | GetAllowance(user) | |
402
+ // +------------------------------+------------------+
403
+ // | UpdateWorkspaceBuildCost(w1) | |
404
+ // +------------------------------+------------------+
405
+ // | CommitTx() | |
406
+ // +------------------------------+------------------+
407
+ // pq: could not serialize access due to concurrent update
408
+ ctx := testutil .Context (t , testutil .WaitLong )
409
+ ctx = dbauthz .AsSystemRestricted (ctx )
410
+
411
+ bumpDeadline := func () {
412
+ err = db .InTx (func (db database.Store ) error {
413
+ err := db .UpdateWorkspaceBuildDeadlineByID (ctx , database.UpdateWorkspaceBuildDeadlineByIDParams {
414
+ Deadline : dbtime .Now (),
415
+ MaxDeadline : dbtime .Now (),
416
+ UpdatedAt : dbtime .Now (),
417
+ ID : workspaceResp .Build .ID ,
418
+ })
419
+ return err
420
+ }, & sql.TxOptions {
421
+ Isolation : sql .LevelSerializable ,
422
+ })
423
+ assert .NoError (t , err )
424
+
425
+ }
426
+
427
+ // Start TX
428
+ // Run order
429
+
430
+ quota := newCommitter (t , db , workspace , workspaceResp .Build )
431
+ quota .GetQuota (ctx , t ) // Step 1
432
+ bumpDeadline () // Interrupt
433
+ quota .GetAllowance (ctx , t ) // Step 2
434
+ quota .UpdateWorkspaceBuildCostByID (ctx , t , 10 ) // Step 3
435
+ // End commit
436
+ require .NoError (t , quota .Done ())
437
+ })
438
+
439
+ t .Run ("UpdateOtherBuildDeadline" , func (t * testing.T ) {
440
+ // +------------------------------+------------------+
441
+ // | Begin Tx | |
442
+ // +------------------------------+------------------+
443
+ // | GetQuota(user) | |
444
+ // +------------------------------+------------------+
445
+ // | | BumpDeadline(w2) |
446
+ // +------------------------------+------------------+
447
+ // | GetAllowance(user) | |
448
+ // +------------------------------+------------------+
449
+ // | UpdateWorkspaceBuildCost(w1) | |
450
+ // +------------------------------+------------------+
451
+ // | CommitTx() | |
452
+ // +------------------------------+------------------+
391
453
ctx := testutil .Context (t , testutil .WaitLong )
392
454
ctx = dbauthz .AsSystemRestricted (ctx )
393
455
@@ -396,7 +458,7 @@ func TestWorkspaceSerialization(t *testing.T) {
396
458
Deadline : dbtime .Now (),
397
459
MaxDeadline : dbtime .Now (),
398
460
UpdatedAt : dbtime .Now (),
399
- ID : workspaceResp .Build .ID ,
461
+ ID : workspaceTwoResp .Build .ID ,
400
462
})
401
463
require .NoError (t , err )
402
464
}
@@ -464,6 +526,30 @@ func TestWorkspaceSerialization(t *testing.T) {
464
526
})
465
527
466
528
t .Run ("DoubleCommit" , func (t * testing.T ) {
529
+ // +---------------------+---------------------+
530
+ // | W1 Quota Tx | W2 Quota Tx |
531
+ // +---------------------+---------------------+
532
+ // | Begin Tx | |
533
+ // +---------------------+---------------------+
534
+ // | | Begin Tx |
535
+ // +---------------------+---------------------+
536
+ // | GetQuota(w1) | |
537
+ // +---------------------+---------------------+
538
+ // | GetAllowance(w1) | |
539
+ // +---------------------+---------------------+
540
+ // | UpdateBuildCost(w1) | |
541
+ // +---------------------+---------------------+
542
+ // | | UpdateBuildCost(w2) |
543
+ // +---------------------+---------------------+
544
+ // | | GetQuota(w2) |
545
+ // +---------------------+---------------------+
546
+ // | | GetAllowance(w2) |
547
+ // +---------------------+---------------------+
548
+ // | CommitTx() | |
549
+ // +---------------------+---------------------+
550
+ // | | CommitTx() |
551
+ // +---------------------+---------------------+
552
+ // pq: could not serialize access due to read/write dependencies among transactions
467
553
ctx := testutil .Context (t , testutil .WaitLong )
468
554
ctx = dbauthz .AsSystemRestricted (ctx )
469
555
@@ -476,82 +562,155 @@ func TestWorkspaceSerialization(t *testing.T) {
476
562
one .GetAllowance (ctx , t )
477
563
478
564
one .UpdateWorkspaceBuildCostByID (ctx , t , 10 )
479
- two .UpdateWorkspaceBuildCostByID (ctx , t , 10 )
480
565
481
566
two .GetQuota (ctx , t )
482
567
two .GetAllowance (ctx , t )
568
+ two .UpdateWorkspaceBuildCostByID (ctx , t , 10 )
483
569
484
570
// End commit
485
571
require .NoError (t , one .Done ())
486
572
require .NoError (t , two .Done ())
487
573
})
488
574
489
575
t .Run ("BumpLastUsedAt" , func (t * testing.T ) {
576
+ // +---------------------+----------------------------------+
577
+ // | W1 Quota Tx | |
578
+ // +---------------------+----------------------------------+
579
+ // | Begin Tx | |
580
+ // +---------------------+----------------------------------+
581
+ // | GetQuota(w1) | |
582
+ // +---------------------+----------------------------------+
583
+ // | GetAllowance(w1) | |
584
+ // +---------------------+----------------------------------+
585
+ // | | UpdateWorkspaceBuildDeadline(w1) |
586
+ // +---------------------+----------------------------------+
587
+ // | UpdateBuildCost(w1) | |
588
+ // +---------------------+----------------------------------+
589
+ // | CommitTx() | |
590
+ // +---------------------+----------------------------------+
591
+ // pq: could not serialize access due to concurrent update
490
592
ctx := testutil .Context (t , testutil .WaitShort )
491
593
ctx = dbauthz .AsSystemRestricted (ctx )
492
594
493
- two := dbtestutil .StartTx (t , db , & sql.TxOptions {
494
- Isolation : sql .LevelSerializable ,
495
- ReadOnly : false ,
496
- })
497
595
one := newCommitter (t , db , workspace , workspaceResp .Build )
498
596
499
- //two := newCommitter(t, db, workspaceTwo, workspaceResp.Build)
500
-
501
597
// Run order
502
598
one .GetQuota (ctx , t )
503
599
one .GetAllowance (ctx , t )
504
600
505
- //two.UpdateWorkspaceBuildCostByID(ctx, t, 10)
601
+ err = db .UpdateWorkspaceBuildDeadlineByID (ctx , database.UpdateWorkspaceBuildDeadlineByIDParams {
602
+ Deadline : dbtime .Now (),
603
+ MaxDeadline : dbtime .Now (),
604
+ UpdatedAt : dbtime .Now (),
605
+ ID : workspaceResp .Build .ID ,
606
+ })
607
+ assert .NoError (t , err )
506
608
507
- //err := two.UpdateWorkspaceBuildCostByID(ctx, database.UpdateWorkspaceBuildCostByIDParams{
508
- // ID: workspaceResp.Build.ID,
509
- // DailyCost: 30,
510
- //})
511
- //require.NoError(t, err)
609
+ one .UpdateWorkspaceBuildCostByID (ctx , t , 10 )
512
610
513
- //q, err := two.GetQuotaConsumedForUser(ctx, database.GetQuotaConsumedForUserParams{
514
- // OwnerID: user.ID,
515
- // OrganizationID: workspace.OrganizationID,
516
- //})
517
- //require.NoError(t, err)
518
- //fmt.Println(q)
611
+ // End commit
612
+ assert .NoError (t , one .Done ())
613
+ })
614
+
615
+ t .Run ("ActivityBump" , func (t * testing.T ) {
616
+ // +---------------------+----------------------------------+
617
+ // | W1 Quota Tx | |
618
+ // +---------------------+----------------------------------+
619
+ // | Begin Tx | |
620
+ // +---------------------+----------------------------------+
621
+ // | GetQuota(w1) | |
622
+ // +---------------------+----------------------------------+
623
+ // | GetAllowance(w1) | |
624
+ // +---------------------+----------------------------------+
625
+ // | | ActivityBump(w1) |
626
+ // +---------------------+----------------------------------+
627
+ // | UpdateBuildCost(w1) | |
628
+ // +---------------------+----------------------------------+
629
+ // | CommitTx() | |
630
+ // +---------------------+----------------------------------+
631
+ // pq: could not serialize access due to concurrent update
632
+ ctx := testutil .Context (t , testutil .WaitShort )
633
+ ctx = dbauthz .AsSystemRestricted (ctx )
634
+
635
+ one := newCommitter (t , db , workspace , workspaceResp .Build )
636
+
637
+ // Run order
638
+ one .GetQuota (ctx , t )
639
+ one .GetAllowance (ctx , t )
640
+
641
+ err = db .ActivityBumpWorkspace (ctx , database.ActivityBumpWorkspaceParams {
642
+ NextAutostart : time .Now (),
643
+ WorkspaceID : workspaceResp .Workspace .ID ,
644
+ })
645
+
646
+ assert .NoError (t , err )
519
647
520
648
one .UpdateWorkspaceBuildCostByID (ctx , t , 10 )
521
649
522
- wg := sync.WaitGroup {}
523
- wg .Add (1 )
524
- go func () {
525
- defer wg .Done ()
526
- err = two .UpdateWorkspaceBuildDeadlineByID (ctx , database.UpdateWorkspaceBuildDeadlineByIDParams {
527
- Deadline : dbtime .Now (),
528
- MaxDeadline : dbtime .Now (),
529
- UpdatedAt : dbtime .Now (),
530
- ID : workspaceResp .Build .ID ,
531
- })
532
- require .NoError (t , err )
533
- }()
534
- time .Sleep (time .Millisecond * 800 )
650
+ // End commit
651
+ assert .NoError (t , one .Done ())
652
+ })
535
653
536
- //err = db.UpdateWorkspaceLastUsedAt(ctx, database.UpdateWorkspaceLastUsedAtParams{
537
- // ID: workspace.ID,
538
- // LastUsedAt: dbtime.Now(),
539
- //})
540
- //require.NoError(t, err)
541
- //
542
- //err = db.UpdateWorkspaceBuildCostByID(ctx, database.UpdateWorkspaceBuildCostByIDParams{
543
- // ID: workspaceResp.Build.ID,
544
- // DailyCost: 20,
545
- //})
546
- //require.NoError(t, err)
654
+ t .Run ("UserMod" , func (t * testing.T ) {
655
+ // +---------------------+----------------------------------+
656
+ // | W1 Quota Tx | |
657
+ // +---------------------+----------------------------------+
658
+ // | Begin Tx | |
659
+ // +---------------------+----------------------------------+
660
+ // | GetQuota(w1) | |
661
+ // +---------------------+----------------------------------+
662
+ // | GetAllowance(w1) | |
663
+ // +---------------------+----------------------------------+
664
+ // | | UpdateWorkspaceBuildDeadline(w1) |
665
+ // +---------------------+----------------------------------+
666
+ // | UpdateBuildCost(w1) | |
667
+ // +---------------------+----------------------------------+
668
+ // | CommitTx() | |
669
+ // +---------------------+----------------------------------+
670
+ // pq: could not serialize access due to concurrent update
671
+ ctx := testutil .Context (t , testutil .WaitShort )
672
+ ctx = dbauthz .AsSystemRestricted (ctx )
673
+
674
+ db .InsertOrganizationMember (ctx , database.InsertOrganizationMemberParams {
675
+ OrganizationID : org .ID ,
676
+ UserID : user .ID ,
677
+ })
678
+
679
+ one := newCommitter (t , db , workspace , workspaceResp .Build )
680
+
681
+ // Run order
682
+
683
+ err = db .RemoveUserFromAllGroups (ctx , user .ID )
684
+ assert .NoError (t , err )
685
+
686
+ err = db .DeleteOrganizationMember (ctx , database.DeleteOrganizationMemberParams {
687
+ OrganizationID : org .ID ,
688
+ UserID : user .ID ,
689
+ })
690
+ assert .NoError (t , err )
691
+
692
+ err = db .ActivityBumpWorkspace (ctx , database.ActivityBumpWorkspaceParams {
693
+ NextAutostart : time .Now (),
694
+ WorkspaceID : workspaceResp .Workspace .ID ,
695
+ })
696
+
697
+ one .GetQuota (ctx , t )
698
+ one .GetAllowance (ctx , t )
547
699
548
- //two.GetQuota(ctx, t)
549
- //two.GetAllowance(ctx, t)
700
+ err = db .ActivityBumpWorkspace (ctx , database.ActivityBumpWorkspaceParams {
701
+ NextAutostart : time .Now (),
702
+ WorkspaceID : workspaceResp .Workspace .ID ,
703
+ })
704
+
705
+ one .UpdateWorkspaceBuildCostByID (ctx , t , 10 )
706
+
707
+ err = db .ActivityBumpWorkspace (ctx , database.ActivityBumpWorkspaceParams {
708
+ NextAutostart : time .Now (),
709
+ WorkspaceID : workspaceResp .Workspace .ID ,
710
+ })
550
711
551
712
// End commit
552
- require .NoError (t , one .Done ())
553
- wg .Wait ()
554
- require .NoError (t , two .Done ())
713
+ assert .NoError (t , one .Done ())
555
714
})
556
715
557
716
// TODO: Try to fail a non-repeatable read only transaction
@@ -577,13 +736,13 @@ func TestWorkspaceSerialization(t *testing.T) {
577
736
two .GetAllowance (ctx , t )
578
737
579
738
// End commit
580
- require .NoError (t , one .Done ())
739
+ assert .NoError (t , one .Done ())
581
740
582
741
fmt .Println ("2a" , two .GetQuota (ctx , t ))
583
742
two .GetAllowance (ctx , t )
584
743
585
- require .NoError (t , two .Done ())
586
- require .NoError (t , three .Done ())
744
+ assert .NoError (t , two .Done ())
745
+ assert .NoError (t , three .Done ())
587
746
588
747
//allow, err = db.GetQuotaConsumedForUser(ctx, database.GetQuotaConsumedForUserParams{
589
748
// OwnerID: user.ID,
@@ -778,7 +937,12 @@ func newCommitter(t *testing.T, db database.Store, workspace database.WorkspaceT
778
937
return & committer {DBTx : quotaTX , w : workspace , b : build }
779
938
}
780
939
940
+ // GetQuota touches:
941
+ // - workspace_builds
942
+ // - workspaces
781
943
func (c * committer ) GetQuota (ctx context.Context , t * testing.T ) int64 {
944
+ t .Helper ()
945
+
782
946
consumed , err := c .DBTx .GetQuotaConsumedForUser (ctx , database.GetQuotaConsumedForUserParams {
783
947
OwnerID : c .w .OwnerID ,
784
948
OrganizationID : c .w .OrganizationID ,
@@ -787,7 +951,14 @@ func (c *committer) GetQuota(ctx context.Context, t *testing.T) int64 {
787
951
return consumed
788
952
}
789
953
954
+ // GetAllowance touches:
955
+ // - group_members_expanded
956
+ // - users
957
+ // - groups
958
+ // - org_members
790
959
func (c * committer ) GetAllowance (ctx context.Context , t * testing.T ) int64 {
960
+ t .Helper ()
961
+
791
962
allowance , err := c .DBTx .GetQuotaAllowanceForUser (ctx , database.GetQuotaAllowanceForUserParams {
792
963
UserID : c .w .OwnerID ,
793
964
OrganizationID : c .w .OrganizationID ,
@@ -796,12 +967,14 @@ func (c *committer) GetAllowance(ctx context.Context, t *testing.T) int64 {
796
967
return allowance
797
968
}
798
969
799
- func (c * committer ) UpdateWorkspaceBuildCostByID (ctx context.Context , t * testing.T , cost int32 ) {
970
+ func (c * committer ) UpdateWorkspaceBuildCostByID (ctx context.Context , t * testing.T , cost int32 ) bool {
971
+ t .Helper ()
972
+
800
973
err := c .DBTx .UpdateWorkspaceBuildCostByID (ctx , database.UpdateWorkspaceBuildCostByIDParams {
801
974
ID : c .b .ID ,
802
975
DailyCost : cost ,
803
976
})
804
- require .NoError (t , err )
977
+ return assert .NoError (t , err )
805
978
}
806
979
807
980
func (c * committer ) Done () error {
0 commit comments