Skip to content

Commit

Permalink
Merge pull request #26924 from remilapeyre/concurrent-locks-pg
Browse files Browse the repository at this point in the history
Use a global sequence to create the IDs for each workspace
  • Loading branch information
jbardin authored Mar 16, 2021
2 parents d4e7a74 + d81d521 commit 1338502
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 3 deletions.
10 changes: 7 additions & 3 deletions backend/remote-state/pg/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
83 changes: 83 additions & 0 deletions backend/remote-state/pg/backend_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down Expand Up @@ -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")
}

0 comments on commit 1338502

Please sign in to comment.