github.com/bradfeehan/terraform@v0.7.0-rc3.0.20170529055808-34b45c5ad841/backend/remote-state/s3/client_test.go (about)

     1  package s3
     2  
     3  import (
     4  	"bytes"
     5  	"crypto/md5"
     6  	"fmt"
     7  	"strings"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/hashicorp/terraform/backend"
    12  	"github.com/hashicorp/terraform/state"
    13  	"github.com/hashicorp/terraform/state/remote"
    14  	"github.com/hashicorp/terraform/terraform"
    15  )
    16  
    17  func TestRemoteClient_impl(t *testing.T) {
    18  	var _ remote.Client = new(RemoteClient)
    19  	var _ remote.ClientLocker = new(RemoteClient)
    20  }
    21  
    22  func TestRemoteClient(t *testing.T) {
    23  	testACC(t)
    24  	bucketName := fmt.Sprintf("terraform-remote-s3-test-%x", time.Now().Unix())
    25  	keyName := "testState"
    26  
    27  	b := backend.TestBackendConfig(t, New(), map[string]interface{}{
    28  		"bucket":  bucketName,
    29  		"key":     keyName,
    30  		"encrypt": true,
    31  	}).(*Backend)
    32  
    33  	createS3Bucket(t, b.s3Client, bucketName)
    34  	defer deleteS3Bucket(t, b.s3Client, bucketName)
    35  
    36  	state, err := b.State(backend.DefaultStateName)
    37  	if err != nil {
    38  		t.Fatal(err)
    39  	}
    40  
    41  	remote.TestClient(t, state.(*remote.State).Client)
    42  }
    43  
    44  func TestRemoteClientLocks(t *testing.T) {
    45  	testACC(t)
    46  	bucketName := fmt.Sprintf("terraform-remote-s3-test-%x", time.Now().Unix())
    47  	keyName := "testState"
    48  
    49  	b1 := backend.TestBackendConfig(t, New(), map[string]interface{}{
    50  		"bucket":     bucketName,
    51  		"key":        keyName,
    52  		"encrypt":    true,
    53  		"lock_table": bucketName,
    54  	}).(*Backend)
    55  
    56  	b2 := backend.TestBackendConfig(t, New(), map[string]interface{}{
    57  		"bucket":     bucketName,
    58  		"key":        keyName,
    59  		"encrypt":    true,
    60  		"lock_table": bucketName,
    61  	}).(*Backend)
    62  
    63  	createS3Bucket(t, b1.s3Client, bucketName)
    64  	defer deleteS3Bucket(t, b1.s3Client, bucketName)
    65  	createDynamoDBTable(t, b1.dynClient, bucketName)
    66  	defer deleteDynamoDBTable(t, b1.dynClient, bucketName)
    67  
    68  	s1, err := b1.State(backend.DefaultStateName)
    69  	if err != nil {
    70  		t.Fatal(err)
    71  	}
    72  
    73  	s2, err := b2.State(backend.DefaultStateName)
    74  	if err != nil {
    75  		t.Fatal(err)
    76  	}
    77  
    78  	remote.TestRemoteLocks(t, s1.(*remote.State).Client, s2.(*remote.State).Client)
    79  }
    80  
    81  // verify that we can unlock a state with an existing lock
    82  func TestForceUnlock(t *testing.T) {
    83  	testACC(t)
    84  	bucketName := fmt.Sprintf("terraform-remote-s3-test-force-%x", time.Now().Unix())
    85  	keyName := "testState"
    86  
    87  	b1 := backend.TestBackendConfig(t, New(), map[string]interface{}{
    88  		"bucket":     bucketName,
    89  		"key":        keyName,
    90  		"encrypt":    true,
    91  		"lock_table": bucketName,
    92  	}).(*Backend)
    93  
    94  	b2 := backend.TestBackendConfig(t, New(), map[string]interface{}{
    95  		"bucket":     bucketName,
    96  		"key":        keyName,
    97  		"encrypt":    true,
    98  		"lock_table": bucketName,
    99  	}).(*Backend)
   100  
   101  	createS3Bucket(t, b1.s3Client, bucketName)
   102  	defer deleteS3Bucket(t, b1.s3Client, bucketName)
   103  	createDynamoDBTable(t, b1.dynClient, bucketName)
   104  	defer deleteDynamoDBTable(t, b1.dynClient, bucketName)
   105  
   106  	// first test with default
   107  	s1, err := b1.State(backend.DefaultStateName)
   108  	if err != nil {
   109  		t.Fatal(err)
   110  	}
   111  
   112  	info := state.NewLockInfo()
   113  	info.Operation = "test"
   114  	info.Who = "clientA"
   115  
   116  	lockID, err := s1.Lock(info)
   117  	if err != nil {
   118  		t.Fatal("unable to get initial lock:", err)
   119  	}
   120  
   121  	// s1 is now locked, get the same state through s2 and unlock it
   122  	s2, err := b2.State(backend.DefaultStateName)
   123  	if err != nil {
   124  		t.Fatal("failed to get default state to force unlock:", err)
   125  	}
   126  
   127  	if err := s2.Unlock(lockID); err != nil {
   128  		t.Fatal("failed to force-unlock default state")
   129  	}
   130  
   131  	// now try the same thing with a named state
   132  	// first test with default
   133  	s1, err = b1.State("test")
   134  	if err != nil {
   135  		t.Fatal(err)
   136  	}
   137  
   138  	info = state.NewLockInfo()
   139  	info.Operation = "test"
   140  	info.Who = "clientA"
   141  
   142  	lockID, err = s1.Lock(info)
   143  	if err != nil {
   144  		t.Fatal("unable to get initial lock:", err)
   145  	}
   146  
   147  	// s1 is now locked, get the same state through s2 and unlock it
   148  	s2, err = b2.State("test")
   149  	if err != nil {
   150  		t.Fatal("failed to get named state to force unlock:", err)
   151  	}
   152  
   153  	if err = s2.Unlock(lockID); err != nil {
   154  		t.Fatal("failed to force-unlock named state")
   155  	}
   156  }
   157  
   158  func TestRemoteClient_clientMD5(t *testing.T) {
   159  	testACC(t)
   160  
   161  	bucketName := fmt.Sprintf("terraform-remote-s3-test-%x", time.Now().Unix())
   162  	keyName := "testState"
   163  
   164  	b := backend.TestBackendConfig(t, New(), map[string]interface{}{
   165  		"bucket":     bucketName,
   166  		"key":        keyName,
   167  		"lock_table": bucketName,
   168  	}).(*Backend)
   169  
   170  	createDynamoDBTable(t, b.dynClient, bucketName)
   171  	defer deleteDynamoDBTable(t, b.dynClient, bucketName)
   172  
   173  	s, err := b.State(backend.DefaultStateName)
   174  	if err != nil {
   175  		t.Fatal(err)
   176  	}
   177  	client := s.(*remote.State).Client.(*RemoteClient)
   178  
   179  	sum := md5.Sum([]byte("test"))
   180  
   181  	if err := client.putMD5(sum[:]); err != nil {
   182  		t.Fatal(err)
   183  	}
   184  
   185  	getSum, err := client.getMD5()
   186  	if err != nil {
   187  		t.Fatal(err)
   188  	}
   189  
   190  	if !bytes.Equal(getSum, sum[:]) {
   191  		t.Fatalf("getMD5 returned the wrong checksum: expected %x, got %x", sum[:], getSum)
   192  	}
   193  
   194  	if err := client.deleteMD5(); err != nil {
   195  		t.Fatal(err)
   196  	}
   197  
   198  	if getSum, err := client.getMD5(); err == nil {
   199  		t.Fatalf("expected getMD5 error, got none. checksum: %x", getSum)
   200  	}
   201  }
   202  
   203  // verify that a client won't return a state with an incorrect checksum.
   204  func TestRemoteClient_stateChecksum(t *testing.T) {
   205  	testACC(t)
   206  
   207  	bucketName := fmt.Sprintf("terraform-remote-s3-test-%x", time.Now().Unix())
   208  	keyName := "testState"
   209  
   210  	b1 := backend.TestBackendConfig(t, New(), map[string]interface{}{
   211  		"bucket":     bucketName,
   212  		"key":        keyName,
   213  		"lock_table": bucketName,
   214  	}).(*Backend)
   215  
   216  	createS3Bucket(t, b1.s3Client, bucketName)
   217  	defer deleteS3Bucket(t, b1.s3Client, bucketName)
   218  	createDynamoDBTable(t, b1.dynClient, bucketName)
   219  	defer deleteDynamoDBTable(t, b1.dynClient, bucketName)
   220  
   221  	s1, err := b1.State(backend.DefaultStateName)
   222  	if err != nil {
   223  		t.Fatal(err)
   224  	}
   225  	client1 := s1.(*remote.State).Client
   226  
   227  	// create a old and new state version to persist
   228  	s := state.TestStateInitial()
   229  	var oldState bytes.Buffer
   230  	if err := terraform.WriteState(s, &oldState); err != nil {
   231  		t.Fatal(err)
   232  	}
   233  	s.Serial++
   234  	var newState bytes.Buffer
   235  	if err := terraform.WriteState(s, &newState); err != nil {
   236  		t.Fatal(err)
   237  	}
   238  
   239  	// Use b2 without a lock_table to bypass the lock table to write the state directly.
   240  	// client2 will write the "incorrect" state, simulating s3 eventually consistency delays
   241  	b2 := backend.TestBackendConfig(t, New(), map[string]interface{}{
   242  		"bucket": bucketName,
   243  		"key":    keyName,
   244  	}).(*Backend)
   245  	s2, err := b2.State(backend.DefaultStateName)
   246  	if err != nil {
   247  		t.Fatal(err)
   248  	}
   249  	client2 := s2.(*remote.State).Client
   250  
   251  	// write the new state through client2 so that there is no checksum yet
   252  	if err := client2.Put(newState.Bytes()); err != nil {
   253  		t.Fatal(err)
   254  	}
   255  
   256  	// verify that we can pull a state without a checksum
   257  	if _, err := client1.Get(); err != nil {
   258  		t.Fatal(err)
   259  	}
   260  
   261  	// write the new state back with its checksum
   262  	if err := client1.Put(newState.Bytes()); err != nil {
   263  		t.Fatal(err)
   264  	}
   265  
   266  	// put the old state in place of the new, without updating the checksum
   267  	if err := client2.Put(oldState.Bytes()); err != nil {
   268  		t.Fatal(err)
   269  	}
   270  
   271  	// remove the timeouts so we can fail immediately
   272  	origTimeout := consistencyRetryTimeout
   273  	origInterval := consistencyRetryPollInterval
   274  	defer func() {
   275  		consistencyRetryTimeout = origTimeout
   276  		consistencyRetryPollInterval = origInterval
   277  	}()
   278  	consistencyRetryTimeout = 0
   279  	consistencyRetryPollInterval = 0
   280  
   281  	// fetching the state through client1 should now error out due to a
   282  	// mismatched checksum.
   283  	if _, err := client1.Get(); !strings.HasPrefix(err.Error(), errBadChecksumFmt[:80]) {
   284  		t.Fatalf("expected state checksum error: got %s", err)
   285  	}
   286  
   287  	// update the state with the correct one after we Get again
   288  	testChecksumHook = func() {
   289  		if err := client2.Put(newState.Bytes()); err != nil {
   290  			t.Fatal(err)
   291  		}
   292  		testChecksumHook = nil
   293  	}
   294  
   295  	consistencyRetryTimeout = origTimeout
   296  
   297  	// this final Get will fail to fail the checksum verification, the above
   298  	// callback will update the state with the correct version, and Get should
   299  	// retry automatically.
   300  	if _, err := client1.Get(); err != nil {
   301  		t.Fatal(err)
   302  	}
   303  }