Merge pull request #26924 from remilapeyre/concurrent-locks-pg

Use a global sequence to create the IDs for each workspace
This commit is contained in:
James Bardin 2021-03-16 11:28:04 -04:00 committed by GitHub
commit 1338502c7b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 90 additions and 3 deletions

View File

@ -105,10 +105,14 @@ func (b *Backend) configure(ctx context.Context) error {
} }
if !data.Get("skip_table_creation").(bool) { if !data.Get("skip_table_creation").(bool) {
if _, err := db.Exec("CREATE SEQUENCE IF NOT EXISTS public.global_states_id_seq AS bigint"); err != nil {
return err
}
query = `CREATE TABLE IF NOT EXISTS %s.%s ( query = `CREATE TABLE IF NOT EXISTS %s.%s (
id SERIAL PRIMARY KEY, id bigint NOT NULL DEFAULT nextval('public.global_states_id_seq') PRIMARY KEY,
name TEXT, name text,
data TEXT data text
)` )`
if _, err := db.Exec(fmt.Sprintf(query, b.schemaName, statesTableName)); err != nil { if _, err := db.Exec(fmt.Sprintf(query, b.schemaName, statesTableName)); err != nil {
return err return err

View File

@ -11,6 +11,7 @@ import (
"github.com/hashicorp/terraform/backend" "github.com/hashicorp/terraform/backend"
"github.com/hashicorp/terraform/states/remote" "github.com/hashicorp/terraform/states/remote"
"github.com/hashicorp/terraform/states/statemgr"
"github.com/lib/pq" "github.com/lib/pq"
_ "github.com/lib/pq" _ "github.com/lib/pq"
) )
@ -266,6 +267,88 @@ func TestBackendStateLocks(t *testing.T) {
backend.TestBackendStateLocks(t, b, bb) backend.TestBackendStateLocks(t, b, bb)
} }
func TestBackendConcurrentLock(t *testing.T) {
testACC(t)
connStr := getDatabaseUrl()
dbCleaner, err := sql.Open("postgres", connStr)
if err != nil {
t.Fatal(err)
}
getStateMgr := func(schemaName string) (statemgr.Full, *statemgr.LockInfo) {
defer dbCleaner.Query(fmt.Sprintf("DROP SCHEMA IF EXISTS %s CASCADE", schemaName))
config := backend.TestWrapConfig(map[string]interface{}{
"conn_str": connStr,
"schema_name": schemaName,
})
b := backend.TestBackendConfig(t, New(), config).(*Backend)
if b == nil {
t.Fatal("Backend could not be configured")
}
stateMgr, err := b.StateMgr(backend.DefaultStateName)
if err != nil {
t.Fatalf("Failed to get the state manager: %v", err)
}
info := statemgr.NewLockInfo()
info.Operation = "test"
info.Who = schemaName
return stateMgr, info
}
s1, i1 := getStateMgr(fmt.Sprintf("terraform_%s_1", t.Name()))
s2, i2 := getStateMgr(fmt.Sprintf("terraform_%s_2", t.Name()))
// First we need to create the workspace as the lock for creating them is
// global
lockID1, err := s1.Lock(i1)
if err != nil {
t.Fatalf("failed to lock first state: %v", err)
}
if err = s1.PersistState(); err != nil {
t.Fatalf("failed to persist state: %v", err)
}
if err := s1.Unlock(lockID1); err != nil {
t.Fatalf("failed to unlock first state: %v", err)
}
lockID2, err := s2.Lock(i2)
if err != nil {
t.Fatalf("failed to lock second state: %v", err)
}
if err = s2.PersistState(); err != nil {
t.Fatalf("failed to persist state: %v", err)
}
if err := s2.Unlock(lockID2); err != nil {
t.Fatalf("failed to unlock first state: %v", err)
}
// Now we can test concurrent lock
lockID1, err = s1.Lock(i1)
if err != nil {
t.Fatalf("failed to lock first state: %v", err)
}
lockID2, err = s2.Lock(i2)
if err != nil {
t.Fatalf("failed to lock second state: %v", err)
}
if err := s1.Unlock(lockID1); err != nil {
t.Fatalf("failed to unlock first state: %v", err)
}
if err := s2.Unlock(lockID2); err != nil {
t.Fatalf("failed to unlock first state: %v", err)
}
}
func getDatabaseUrl() string { func getDatabaseUrl() string {
return os.Getenv("DATABASE_URL") return os.Getenv("DATABASE_URL")
} }