mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
chore: fix concurrent CommitQuota transactions for unrelated users/orgs (#15261)
The failure condition being fixed is `w1` and `w2` could belong to different users, organizations, and templates and still cause a serializable failure if run concurrently. This is because the old query did a `seq scan` on the `workspace_builds` table. Since that is the table being updated, we really want to prevent that. So before this would fail for any 2 workspaces. Now it only fails if `w1` and `w2` are owned by the same user and organization.
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
package dbfake
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/coder/coder/v2/coderd/database"
|
||||
"github.com/coder/coder/v2/coderd/database/dbauthz"
|
||||
"github.com/coder/coder/v2/coderd/database/dbgen"
|
||||
"github.com/coder/coder/v2/coderd/database/dbtime"
|
||||
"github.com/coder/coder/v2/testutil"
|
||||
)
|
||||
|
||||
type OrganizationBuilder struct {
|
||||
t *testing.T
|
||||
db database.Store
|
||||
seed database.Organization
|
||||
allUsersAllowance int32
|
||||
members []uuid.UUID
|
||||
groups map[database.Group][]uuid.UUID
|
||||
}
|
||||
|
||||
func Organization(t *testing.T, db database.Store) OrganizationBuilder {
|
||||
return OrganizationBuilder{
|
||||
t: t,
|
||||
db: db,
|
||||
members: []uuid.UUID{},
|
||||
groups: make(map[database.Group][]uuid.UUID),
|
||||
}
|
||||
}
|
||||
|
||||
type OrganizationResponse struct {
|
||||
Org database.Organization
|
||||
AllUsersGroup database.Group
|
||||
Members []database.OrganizationMember
|
||||
Groups []database.Group
|
||||
}
|
||||
|
||||
func (b OrganizationBuilder) EveryoneAllowance(allowance int) OrganizationBuilder {
|
||||
//nolint: revive // returns modified struct
|
||||
b.allUsersAllowance = int32(allowance)
|
||||
return b
|
||||
}
|
||||
|
||||
func (b OrganizationBuilder) Seed(seed database.Organization) OrganizationBuilder {
|
||||
//nolint: revive // returns modified struct
|
||||
b.seed = seed
|
||||
return b
|
||||
}
|
||||
|
||||
func (b OrganizationBuilder) Members(users ...database.User) OrganizationBuilder {
|
||||
for _, u := range users {
|
||||
//nolint: revive // returns modified struct
|
||||
b.members = append(b.members, u.ID)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func (b OrganizationBuilder) Group(seed database.Group, members ...database.User) OrganizationBuilder {
|
||||
//nolint: revive // returns modified struct
|
||||
b.groups[seed] = []uuid.UUID{}
|
||||
for _, u := range members {
|
||||
//nolint: revive // returns modified struct
|
||||
b.groups[seed] = append(b.groups[seed], u.ID)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func (b OrganizationBuilder) Do() OrganizationResponse {
|
||||
org := dbgen.Organization(b.t, b.db, b.seed)
|
||||
|
||||
ctx := testutil.Context(b.t, testutil.WaitShort)
|
||||
//nolint:gocritic // builder code needs perms
|
||||
ctx = dbauthz.AsSystemRestricted(ctx)
|
||||
everyone, err := b.db.InsertAllUsersGroup(ctx, org.ID)
|
||||
require.NoError(b.t, err)
|
||||
|
||||
if b.allUsersAllowance > 0 {
|
||||
everyone, err = b.db.UpdateGroupByID(ctx, database.UpdateGroupByIDParams{
|
||||
Name: everyone.Name,
|
||||
DisplayName: everyone.DisplayName,
|
||||
AvatarURL: everyone.AvatarURL,
|
||||
QuotaAllowance: b.allUsersAllowance,
|
||||
ID: everyone.ID,
|
||||
})
|
||||
require.NoError(b.t, err)
|
||||
}
|
||||
|
||||
members := make([]database.OrganizationMember, 0)
|
||||
if len(b.members) > 0 {
|
||||
for _, u := range b.members {
|
||||
newMem := dbgen.OrganizationMember(b.t, b.db, database.OrganizationMember{
|
||||
UserID: u,
|
||||
OrganizationID: org.ID,
|
||||
CreatedAt: dbtime.Now(),
|
||||
UpdatedAt: dbtime.Now(),
|
||||
Roles: nil,
|
||||
})
|
||||
members = append(members, newMem)
|
||||
}
|
||||
}
|
||||
|
||||
groups := make([]database.Group, 0)
|
||||
if len(b.groups) > 0 {
|
||||
for g, users := range b.groups {
|
||||
g.OrganizationID = org.ID
|
||||
group := dbgen.Group(b.t, b.db, g)
|
||||
groups = append(groups, group)
|
||||
|
||||
for _, u := range users {
|
||||
dbgen.GroupMember(b.t, b.db, database.GroupMemberTable{
|
||||
UserID: u,
|
||||
GroupID: group.ID,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return OrganizationResponse{
|
||||
Org: org,
|
||||
AllUsersGroup: everyone,
|
||||
Members: members,
|
||||
Groups: groups,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user