github.com/kcburge/terraform@v0.11.12-beta1/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  		"dynamodb_table": bucketName,
    54  	}).(*Backend)
    55  
    56  	b2 := backend.TestBackendConfig(t, New(), map[string]interface{}{
    57  		"bucket":         bucketName,
    58  		"key":            keyName,
    59  		"encrypt":        true,
    60  		"dynamodb_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  		"dynamodb_table": bucketName,
    92  	}).(*Backend)
    93  
    94  	b2 := backend.TestBackendConfig(t, New(), map[string]interface{}{
    95  		"bucket":         bucketName,
    96  		"key":            keyName,
    97  		"encrypt":        true,
    98  		"dynamodb_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  		"dynamodb_table": bucketName,
   168  	}).(*Backend)
   169  
   170  	createS3Bucket(t, b.s3Client, bucketName)
   171  	defer deleteS3Bucket(t, b.s3Client, bucketName)
   172  	createDynamoDBTable(t, b.dynClient, bucketName)
   173  	defer deleteDynamoDBTable(t, b.dynClient, bucketName)
   174  
   175  	s, err := b.State(backend.DefaultStateName)
   176  	if err != nil {
   177  		t.Fatal(err)
   178  	}
   179  	client := s.(*remote.State).Client.(*RemoteClient)
   180  
   181  	sum := md5.Sum([]byte("test"))
   182  
   183  	if err := client.putMD5(sum[:]); err != nil {
   184  		t.Fatal(err)
   185  	}
   186  
   187  	getSum, err := client.getMD5()
   188  	if err != nil {
   189  		t.Fatal(err)
   190  	}
   191  
   192  	if !bytes.Equal(getSum, sum[:]) {
   193  		t.Fatalf("getMD5 returned the wrong checksum: expected %x, got %x", sum[:], getSum)
   194  	}
   195  
   196  	if err := client.deleteMD5(); err != nil {
   197  		t.Fatal(err)
   198  	}
   199  
   200  	if getSum, err := client.getMD5(); err == nil {
   201  		t.Fatalf("expected getMD5 error, got none. checksum: %x", getSum)
   202  	}
   203  }
   204  
   205  // verify that a client won't return a state with an incorrect checksum.
   206  func TestRemoteClient_stateChecksum(t *testing.T) {
   207  	testACC(t)
   208  
   209  	bucketName := fmt.Sprintf("terraform-remote-s3-test-%x", time.Now().Unix())
   210  	keyName := "testState"
   211  
   212  	b1 := backend.TestBackendConfig(t, New(), map[string]interface{}{
   213  		"bucket":         bucketName,
   214  		"key":            keyName,
   215  		"dynamodb_table": bucketName,
   216  	}).(*Backend)
   217  
   218  	createS3Bucket(t, b1.s3Client, bucketName)
   219  	defer deleteS3Bucket(t, b1.s3Client, bucketName)
   220  	createDynamoDBTable(t, b1.dynClient, bucketName)
   221  	defer deleteDynamoDBTable(t, b1.dynClient, bucketName)
   222  
   223  	s1, err := b1.State(backend.DefaultStateName)
   224  	if err != nil {
   225  		t.Fatal(err)
   226  	}
   227  	client1 := s1.(*remote.State).Client
   228  
   229  	// create a old and new state version to persist
   230  	s := state.TestStateInitial()
   231  	var oldState bytes.Buffer
   232  	if err := terraform.WriteState(s, &oldState); err != nil {
   233  		t.Fatal(err)
   234  	}
   235  	s.Serial++
   236  	var newState bytes.Buffer
   237  	if err := terraform.WriteState(s, &newState); err != nil {
   238  		t.Fatal(err)
   239  	}
   240  
   241  	// Use b2 without a dynamodb_table to bypass the lock table to write the state directly.
   242  	// client2 will write the "incorrect" state, simulating s3 eventually consistency delays
   243  	b2 := backend.TestBackendConfig(t, New(), map[string]interface{}{
   244  		"bucket": bucketName,
   245  		"key":    keyName,
   246  	}).(*Backend)
   247  	s2, err := b2.State(backend.DefaultStateName)
   248  	if err != nil {
   249  		t.Fatal(err)
   250  	}
   251  	client2 := s2.(*remote.State).Client
   252  
   253  	// write the new state through client2 so that there is no checksum yet
   254  	if err := client2.Put(newState.Bytes()); err != nil {
   255  		t.Fatal(err)
   256  	}
   257  
   258  	// verify that we can pull a state without a checksum
   259  	if _, err := client1.Get(); err != nil {
   260  		t.Fatal(err)
   261  	}
   262  
   263  	// write the new state back with its checksum
   264  	if err := client1.Put(newState.Bytes()); err != nil {
   265  		t.Fatal(err)
   266  	}
   267  
   268  	// put an empty state in place to check for panics during get
   269  	if err := client2.Put([]byte{}); err != nil {
   270  		t.Fatal(err)
   271  	}
   272  
   273  	// remove the timeouts so we can fail immediately
   274  	origTimeout := consistencyRetryTimeout
   275  	origInterval := consistencyRetryPollInterval
   276  	defer func() {
   277  		consistencyRetryTimeout = origTimeout
   278  		consistencyRetryPollInterval = origInterval
   279  	}()
   280  	consistencyRetryTimeout = 0
   281  	consistencyRetryPollInterval = 0
   282  
   283  	// fetching an empty state through client1 should now error out due to a
   284  	// mismatched checksum.
   285  	if _, err := client1.Get(); !strings.HasPrefix(err.Error(), errBadChecksumFmt[:80]) {
   286  		t.Fatalf("expected state checksum error: got %s", err)
   287  	}
   288  
   289  	// put the old state in place of the new, without updating the checksum
   290  	if err := client2.Put(oldState.Bytes()); err != nil {
   291  		t.Fatal(err)
   292  	}
   293  
   294  	// fetching the wrong state through client1 should now error out due to a
   295  	// mismatched checksum.
   296  	if _, err := client1.Get(); !strings.HasPrefix(err.Error(), errBadChecksumFmt[:80]) {
   297  		t.Fatalf("expected state checksum error: got %s", err)
   298  	}
   299  
   300  	// update the state with the correct one after we Get again
   301  	testChecksumHook = func() {
   302  		if err := client2.Put(newState.Bytes()); err != nil {
   303  			t.Fatal(err)
   304  		}
   305  		testChecksumHook = nil
   306  	}
   307  
   308  	consistencyRetryTimeout = origTimeout
   309  
   310  	// this final Get will fail to fail the checksum verification, the above
   311  	// callback will update the state with the correct version, and Get should
   312  	// retry automatically.
   313  	if _, err := client1.Get(); err != nil {
   314  		t.Fatal(err)
   315  	}
   316  }