add test for lock error and force-unlock

This adds a general test to verify that a remote state backend returns
the expected error type when it cannot lock a state. It then extracts
the ID reported in the error, and attempts to unlock the state using
that ID, which simulated the force-unlock scenario. This is a separate
test, since not all backends have persistent locks that can be unlocked
later.

We also split out the backend test to be called individually as needed.
This commit is contained in:
James Bardin 2018-02-20 20:32:07 -05:00
parent 1d0f5fdac9
commit 85d6b1d9cc
1 changed files with 61 additions and 17 deletions

View File

@ -5,6 +5,7 @@ import (
"sort" "sort"
"testing" "testing"
uuid "github.com/hashicorp/go-uuid"
"github.com/hashicorp/terraform/config" "github.com/hashicorp/terraform/config"
"github.com/hashicorp/terraform/state" "github.com/hashicorp/terraform/state"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
@ -43,20 +44,7 @@ func TestBackendConfig(t *testing.T, b Backend, c map[string]interface{}) Backen
// assumed to already be configured. This will test state functionality. // assumed to already be configured. This will test state functionality.
// If the backend reports it doesn't support multi-state by returning the // If the backend reports it doesn't support multi-state by returning the
// error ErrNamedStatesNotSupported, then it will not test that. // error ErrNamedStatesNotSupported, then it will not test that.
// func TestBackendStates(t *testing.T, b Backend) {
// If you want to test locking, two backends must be given. If b2 is nil,
// then state locking won't be tested.
func TestBackend(t *testing.T, b1, b2 Backend) {
t.Helper()
testBackendStates(t, b1)
if b2 != nil {
testBackendStateLock(t, b1, b2)
}
}
func testBackendStates(t *testing.T, b Backend) {
t.Helper() t.Helper()
states, err := b.States() states, err := b.States()
@ -236,7 +224,23 @@ func testBackendStates(t *testing.T, b Backend) {
} }
} }
func testBackendStateLock(t *testing.T, b1, b2 Backend) { // TestBackendStateLocks will test the locking functionality of the remote
// state backend.
func TestBackendStateLocks(t *testing.T, b1, b2 Backend) {
t.Helper()
testLocks(t, b1, b2, false)
}
// TestBackendStateForceUnlock verifies that the lock error is the expected
// type, and the lock can be unlocked using the ID reported in the error.
// Remote state backends that support -force-unlock should call this in at
// least one of the acceptance tests.
func TestBackendStateForceUnlock(t *testing.T, b1, b2 Backend) {
t.Helper()
testLocks(t, b1, b2, true)
}
func testLocks(t *testing.T, b1, b2 Backend, testForceUnlock bool) {
t.Helper() t.Helper()
// Get the default state for each // Get the default state for each
@ -286,7 +290,7 @@ func testBackendStateLock(t *testing.T, b1, b2 Backend) {
// backend, and as a remote state. // backend, and as a remote state.
_, err = b2.State(DefaultStateName) _, err = b2.State(DefaultStateName)
if err != nil { if err != nil {
t.Fatalf("failed to read locked state from another backend instance: %s", err) t.Errorf("failed to read locked state from another backend instance: %s", err)
} }
// If the lock ID is blank, assume locking is disabled // If the lock ID is blank, assume locking is disabled
@ -311,11 +315,51 @@ func testBackendStateLock(t *testing.T, b1, b2 Backend) {
} }
if lockIDB == lockIDA { if lockIDB == lockIDA {
t.Fatalf("duplicate lock IDs: %q", lockIDB) t.Errorf("duplicate lock IDs: %q", lockIDB)
} }
if err = lockerB.Unlock(lockIDB); err != nil { if err = lockerB.Unlock(lockIDB); err != nil {
t.Fatal("error unlocking client B:", err) t.Fatal("error unlocking client B:", err)
} }
// test the equivalent of -force-unlock, by using the id from the error
// output.
if !testForceUnlock {
return
}
// get a new ID
infoA.ID, err = uuid.GenerateUUID()
if err != nil {
panic(err)
}
lockIDA, err = lockerA.Lock(infoA)
if err != nil {
t.Fatal("unable to get re lock A:", err)
}
unlock := func() {
err := lockerA.Unlock(lockIDA)
if err != nil {
t.Fatal(err)
}
}
_, err = lockerB.Lock(infoB)
if err == nil {
unlock()
t.Fatal("client B obtained lock while held by client A")
}
infoErr, ok := err.(*state.LockError)
if !ok {
unlock()
t.Fatalf("expected type *state.LockError, got : %#v", err)
}
// try to unlock with the second unlocker, using the ID from the error
if err := lockerB.Unlock(infoErr.Info.ID); err != nil {
unlock()
t.Fatalf("could not unlock with the reported ID %q: %s", infoErr.Info.ID, err)
}
} }