Merge pull request #17397 from hashicorp/jbardin/gcs-lock-info

Fix reported GCS state lock ID
This commit is contained in:
James Bardin 2018-02-21 16:06:03 -05:00 committed by GitHub
commit 7c6072c2a0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 109 additions and 39 deletions

View File

@ -23,7 +23,8 @@ func TestLocal_impl(t *testing.T) {
func TestLocal_backend(t *testing.T) {
defer testTmpDir(t)()
b := &Local{}
backend.TestBackend(t, b, b)
backend.TestBackendStates(t, b)
backend.TestBackendStateLocks(t, b, b)
}
func checkState(t *testing.T, path, expected string) {

View File

@ -64,7 +64,7 @@ func TestBackend(t *testing.T) {
"access_key": res.accessKey,
}).(*Backend)
backend.TestBackend(t, b, nil)
backend.TestBackendStates(t, b)
}
func TestBackendLocked(t *testing.T) {
@ -88,7 +88,8 @@ func TestBackendLocked(t *testing.T) {
"access_key": res.accessKey,
}).(*Backend)
backend.TestBackend(t, b1, b2)
backend.TestBackendStateLocks(t, b1, b2)
backend.TestBackendStateForceUnlock(t, b1, b2)
}
type testResources struct {

View File

@ -63,7 +63,8 @@ func TestBackend(t *testing.T) {
})
// Test
backend.TestBackend(t, b1, b2)
backend.TestBackendStates(t, b1)
backend.TestBackendStateLocks(t, b1, b2)
}
func TestBackend_lockDisabled(t *testing.T) {
@ -83,7 +84,8 @@ func TestBackend_lockDisabled(t *testing.T) {
})
// Test
backend.TestBackend(t, b1, b2)
backend.TestBackendStates(t, b1)
backend.TestBackendStateLocks(t, b1, b2)
}
func TestBackend_gzip(t *testing.T) {
@ -95,5 +97,5 @@ func TestBackend_gzip(t *testing.T) {
})
// Test
backend.TestBackend(t, b, nil)
backend.TestBackendStates(t, b)
}

View File

@ -66,7 +66,9 @@ func TestBackend(t *testing.T) {
})
// Test
backend.TestBackend(t, b1, b2)
backend.TestBackendStates(t, b1)
backend.TestBackendStateLocks(t, b1, b2)
backend.TestBackendStateForceUnlock(t, b1, b2)
}
func TestBackend_lockDisabled(t *testing.T) {
@ -89,5 +91,5 @@ func TestBackend_lockDisabled(t *testing.T) {
})
// Test
backend.TestBackend(t, b1, b2)
backend.TestBackendStateLocks(t, b1, b2)
}

View File

@ -136,8 +136,11 @@ func TestBackend(t *testing.T) {
be1 := setupBackend(t, bucket, noPrefix, noEncryptionKey)
backend.TestBackend(t, be0, be1)
backend.TestBackendStates(t, be0)
backend.TestBackendStateLocks(t, be0, be1)
backend.TestBackendStateForceUnlock(t, be0, be1)
}
func TestBackendWithPrefix(t *testing.T) {
t.Parallel()
@ -149,7 +152,8 @@ func TestBackendWithPrefix(t *testing.T) {
be1 := setupBackend(t, bucket, prefix+"/", noEncryptionKey)
backend.TestBackend(t, be0, be1)
backend.TestBackendStates(t, be0)
backend.TestBackendStateLocks(t, be0, be1)
}
func TestBackendWithEncryption(t *testing.T) {
t.Parallel()
@ -161,7 +165,8 @@ func TestBackendWithEncryption(t *testing.T) {
be1 := setupBackend(t, bucket, noPrefix, encryptionKey)
backend.TestBackend(t, be0, be1)
backend.TestBackendStates(t, be0)
backend.TestBackendStateLocks(t, be0, be1)
}
// setupBackend returns a new GCS backend.

View File

@ -80,6 +80,10 @@ func (c *remoteClient) Delete() error {
// Lock writes to a lock file, ensuring file creation. Returns the generation
// number, which must be passed to Unlock().
func (c *remoteClient) Lock(info *state.LockInfo) (string, error) {
// update the path we're using
// we can't set the ID until the info is written
info.Path = c.lockFileURL()
infoJson, err := json.Marshal(info)
if err != nil {
return "", err
@ -93,12 +97,12 @@ func (c *remoteClient) Lock(info *state.LockInfo) (string, error) {
}
return w.Close()
}()
if err != nil {
return "", c.lockError(fmt.Errorf("writing %q failed: %v", c.lockFileURL(), err))
}
info.ID = strconv.FormatInt(w.Attrs().Generation, 10)
info.Path = c.lockFileURL()
return info.ID, nil
}
@ -149,6 +153,15 @@ func (c *remoteClient) lockInfo() (*state.LockInfo, error) {
return nil, err
}
// We use the Generation as the ID, so overwrite the ID in the json.
// This can't be written into the Info, since the generation isn't known
// until it's written.
attrs, err := c.lockFile().Attrs(c.storageContext)
if err != nil {
return nil, err
}
info.ID = strconv.FormatInt(attrs.Generation, 10)
return info, nil
}

View File

@ -40,7 +40,7 @@ func TestBackendConfig(t *testing.T) {
func TestBackend(t *testing.T) {
defer Reset()
b := backend.TestBackendConfig(t, New(), nil).(*Backend)
backend.TestBackend(t, b, nil)
backend.TestBackendStates(t, b)
}
func TestBackendLocked(t *testing.T) {
@ -48,7 +48,7 @@ func TestBackendLocked(t *testing.T) {
b1 := backend.TestBackendConfig(t, New(), nil).(*Backend)
b2 := backend.TestBackendConfig(t, New(), nil).(*Backend)
backend.TestBackend(t, b1, b2)
backend.TestBackendStateLocks(t, b1, b2)
}
// use the this backen to test the remote.State implementation

View File

@ -38,7 +38,7 @@ func TestBackend(t *testing.T) {
createMantaFolder(t, b.storageClient, directory)
defer deleteMantaFolder(t, b.storageClient, directory)
backend.TestBackend(t, b, nil)
backend.TestBackendStates(t, b)
}
func TestBackendLocked(t *testing.T) {
@ -60,7 +60,8 @@ func TestBackendLocked(t *testing.T) {
createMantaFolder(t, b1.storageClient, directory)
defer deleteMantaFolder(t, b1.storageClient, directory)
backend.TestBackend(t, b1, b2)
backend.TestBackendStateLocks(t, b1, b2)
backend.TestBackendStateForceUnlock(t, b1, b2)
}
func createMantaFolder(t *testing.T, mantaClient *storage.StorageClient, directoryName string) {

View File

@ -103,7 +103,7 @@ func TestBackend(t *testing.T) {
createS3Bucket(t, b.s3Client, bucketName)
defer deleteS3Bucket(t, b.s3Client, bucketName)
backend.TestBackend(t, b, nil)
backend.TestBackendStates(t, b)
}
func TestBackendLocked(t *testing.T) {
@ -131,7 +131,8 @@ func TestBackendLocked(t *testing.T) {
createDynamoDBTable(t, b1.dynClient, bucketName)
defer deleteDynamoDBTable(t, b1.dynClient, bucketName)
backend.TestBackend(t, b1, b2)
backend.TestBackendStateLocks(t, b1, b2)
backend.TestBackendStateForceUnlock(t, b1, b2)
}
// add some extra junk in S3 to try and confuse the env listing.
@ -334,9 +335,9 @@ func TestKeyEnv(t *testing.T) {
t.Fatal(err)
}
backend.TestBackend(t, b0, nil)
backend.TestBackend(t, b1, nil)
backend.TestBackend(t, b2, nil)
backend.TestBackendStates(t, b0)
backend.TestBackendStates(t, b1)
backend.TestBackendStates(t, b2)
}
func testGetWorkspaceForKey(b *Backend, key string, expected string) error {

View File

@ -67,7 +67,7 @@ func TestBackend(t *testing.T) {
defer deleteSwiftContainer(t, b.client, container)
backend.TestBackend(t, b, nil)
backend.TestBackendStates(t, b)
}
func TestBackendPath(t *testing.T) {

View File

@ -5,6 +5,7 @@ import (
"sort"
"testing"
uuid "github.com/hashicorp/go-uuid"
"github.com/hashicorp/terraform/config"
"github.com/hashicorp/terraform/state"
"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.
// If the backend reports it doesn't support multi-state by returning the
// error ErrNamedStatesNotSupported, then it will not test that.
//
// 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) {
func TestBackendStates(t *testing.T, b Backend) {
t.Helper()
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()
// Get the default state for each
@ -286,7 +290,7 @@ func testBackendStateLock(t *testing.T, b1, b2 Backend) {
// backend, and as a remote state.
_, err = b2.State(DefaultStateName)
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
@ -311,11 +315,51 @@ func testBackendStateLock(t *testing.T, b1, b2 Backend) {
}
if lockIDB == lockIDA {
t.Fatalf("duplicate lock IDs: %q", lockIDB)
t.Errorf("duplicate lock IDs: %q", lockIDB)
}
if err = lockerB.Unlock(lockIDB); err != nil {
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)
}
}