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:
commit
1338502c7b
|
@ -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
|
||||||
|
|
|
@ -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")
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue