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 _, 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 (
id SERIAL PRIMARY KEY,
name TEXT,
data TEXT
id bigint NOT NULL DEFAULT nextval('public.global_states_id_seq') PRIMARY KEY,
name text,
data text
)`
if _, err := db.Exec(fmt.Sprintf(query, b.schemaName, statesTableName)); err != nil {
return err

View File

@ -11,6 +11,7 @@ import (
"github.com/hashicorp/terraform/backend"
"github.com/hashicorp/terraform/states/remote"
"github.com/hashicorp/terraform/states/statemgr"
"github.com/lib/pq"
_ "github.com/lib/pq"
)
@ -266,6 +267,88 @@ func TestBackendStateLocks(t *testing.T) {
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 {
return os.Getenv("DATABASE_URL")
}