github.com/graywolf-at-work-2/terraform-vendor@v1.4.5/internal/backend/remote-state/oss/client_test.go (about)

     1  package oss
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  	"testing"
     7  	"time"
     8  
     9  	"bytes"
    10  	"crypto/md5"
    11  
    12  	"github.com/hashicorp/terraform/internal/backend"
    13  	"github.com/hashicorp/terraform/internal/states/remote"
    14  	"github.com/hashicorp/terraform/internal/states/statefile"
    15  	"github.com/hashicorp/terraform/internal/states/statemgr"
    16  )
    17  
    18  // NOTE: Before running this testcase, please create a OTS instance called 'tf-oss-remote'
    19  var RemoteTestUsedOTSEndpoint = "https://tf-oss-remote.cn-hangzhou.ots.aliyuncs.com"
    20  
    21  func TestRemoteClient_impl(t *testing.T) {
    22  	var _ remote.Client = new(RemoteClient)
    23  	var _ remote.ClientLocker = new(RemoteClient)
    24  }
    25  
    26  func TestRemoteClient(t *testing.T) {
    27  	testACC(t)
    28  	bucketName := fmt.Sprintf("tf-remote-oss-test-%x", time.Now().Unix())
    29  	path := "testState"
    30  
    31  	b := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
    32  		"bucket":  bucketName,
    33  		"prefix":  path,
    34  		"encrypt": true,
    35  	})).(*Backend)
    36  
    37  	createOSSBucket(t, b.ossClient, bucketName)
    38  	defer deleteOSSBucket(t, b.ossClient, bucketName)
    39  
    40  	state, err := b.StateMgr(backend.DefaultStateName)
    41  	if err != nil {
    42  		t.Fatal(err)
    43  	}
    44  
    45  	remote.TestClient(t, state.(*remote.State).Client)
    46  }
    47  
    48  func TestRemoteClientLocks(t *testing.T) {
    49  	testACC(t)
    50  	bucketName := fmt.Sprintf("tf-remote-oss-test-%x", time.Now().Unix())
    51  	tableName := fmt.Sprintf("tfRemoteTestForce%x", time.Now().Unix())
    52  	path := "testState"
    53  
    54  	b1 := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
    55  		"bucket":              bucketName,
    56  		"prefix":              path,
    57  		"encrypt":             true,
    58  		"tablestore_table":    tableName,
    59  		"tablestore_endpoint": RemoteTestUsedOTSEndpoint,
    60  	})).(*Backend)
    61  
    62  	b2 := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
    63  		"bucket":              bucketName,
    64  		"prefix":              path,
    65  		"encrypt":             true,
    66  		"tablestore_table":    tableName,
    67  		"tablestore_endpoint": RemoteTestUsedOTSEndpoint,
    68  	})).(*Backend)
    69  
    70  	createOSSBucket(t, b1.ossClient, bucketName)
    71  	defer deleteOSSBucket(t, b1.ossClient, bucketName)
    72  	createTablestoreTable(t, b1.otsClient, tableName)
    73  	defer deleteTablestoreTable(t, b1.otsClient, tableName)
    74  
    75  	s1, err := b1.StateMgr(backend.DefaultStateName)
    76  	if err != nil {
    77  		t.Fatal(err)
    78  	}
    79  
    80  	s2, err := b2.StateMgr(backend.DefaultStateName)
    81  	if err != nil {
    82  		t.Fatal(err)
    83  	}
    84  
    85  	remote.TestRemoteLocks(t, s1.(*remote.State).Client, s2.(*remote.State).Client)
    86  }
    87  
    88  // verify that the backend can handle more than one state in the same table
    89  func TestRemoteClientLocks_multipleStates(t *testing.T) {
    90  	testACC(t)
    91  	bucketName := fmt.Sprintf("tf-remote-oss-test-force-%x", time.Now().Unix())
    92  	tableName := fmt.Sprintf("tfRemoteTestForce%x", time.Now().Unix())
    93  	path := "testState"
    94  
    95  	b1 := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
    96  		"bucket":              bucketName,
    97  		"prefix":              path,
    98  		"encrypt":             true,
    99  		"tablestore_table":    tableName,
   100  		"tablestore_endpoint": RemoteTestUsedOTSEndpoint,
   101  	})).(*Backend)
   102  
   103  	b2 := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
   104  		"bucket":              bucketName,
   105  		"prefix":              path,
   106  		"encrypt":             true,
   107  		"tablestore_table":    tableName,
   108  		"tablestore_endpoint": RemoteTestUsedOTSEndpoint,
   109  	})).(*Backend)
   110  
   111  	createOSSBucket(t, b1.ossClient, bucketName)
   112  	defer deleteOSSBucket(t, b1.ossClient, bucketName)
   113  	createTablestoreTable(t, b1.otsClient, tableName)
   114  	defer deleteTablestoreTable(t, b1.otsClient, tableName)
   115  
   116  	s1, err := b1.StateMgr("s1")
   117  	if err != nil {
   118  		t.Fatal(err)
   119  	}
   120  	if _, err := s1.Lock(statemgr.NewLockInfo()); err != nil {
   121  		t.Fatal("failed to get lock for s1:", err)
   122  	}
   123  
   124  	// s1 is now locked, s2 should not be locked as it's a different state file
   125  	s2, err := b2.StateMgr("s2")
   126  	if err != nil {
   127  		t.Fatal(err)
   128  	}
   129  	if _, err := s2.Lock(statemgr.NewLockInfo()); err != nil {
   130  		t.Fatal("failed to get lock for s2:", err)
   131  	}
   132  }
   133  
   134  // verify that we can unlock a state with an existing lock
   135  func TestRemoteForceUnlock(t *testing.T) {
   136  	testACC(t)
   137  	bucketName := fmt.Sprintf("tf-remote-oss-test-force-%x", time.Now().Unix())
   138  	tableName := fmt.Sprintf("tfRemoteTestForce%x", time.Now().Unix())
   139  	path := "testState"
   140  
   141  	b1 := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
   142  		"bucket":              bucketName,
   143  		"prefix":              path,
   144  		"encrypt":             true,
   145  		"tablestore_table":    tableName,
   146  		"tablestore_endpoint": RemoteTestUsedOTSEndpoint,
   147  	})).(*Backend)
   148  
   149  	b2 := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
   150  		"bucket":              bucketName,
   151  		"prefix":              path,
   152  		"encrypt":             true,
   153  		"tablestore_table":    tableName,
   154  		"tablestore_endpoint": RemoteTestUsedOTSEndpoint,
   155  	})).(*Backend)
   156  
   157  	createOSSBucket(t, b1.ossClient, bucketName)
   158  	defer deleteOSSBucket(t, b1.ossClient, bucketName)
   159  	createTablestoreTable(t, b1.otsClient, tableName)
   160  	defer deleteTablestoreTable(t, b1.otsClient, tableName)
   161  
   162  	// first test with default
   163  	s1, err := b1.StateMgr(backend.DefaultStateName)
   164  	if err != nil {
   165  		t.Fatal(err)
   166  	}
   167  
   168  	info := statemgr.NewLockInfo()
   169  	info.Operation = "test"
   170  	info.Who = "clientA"
   171  
   172  	lockID, err := s1.Lock(info)
   173  	if err != nil {
   174  		t.Fatal("unable to get initial lock:", err)
   175  	}
   176  
   177  	// s1 is now locked, get the same state through s2 and unlock it
   178  	s2, err := b2.StateMgr(backend.DefaultStateName)
   179  	if err != nil {
   180  		t.Fatal("failed to get default state to force unlock:", err)
   181  	}
   182  
   183  	if err := s2.Unlock(lockID); err != nil {
   184  		t.Fatal("failed to force-unlock default state")
   185  	}
   186  
   187  	// now try the same thing with a named state
   188  	// first test with default
   189  	s1, err = b1.StateMgr("test")
   190  	if err != nil {
   191  		t.Fatal(err)
   192  	}
   193  
   194  	info = statemgr.NewLockInfo()
   195  	info.Operation = "test"
   196  	info.Who = "clientA"
   197  
   198  	lockID, err = s1.Lock(info)
   199  	if err != nil {
   200  		t.Fatal("unable to get initial lock:", err)
   201  	}
   202  
   203  	// s1 is now locked, get the same state through s2 and unlock it
   204  	s2, err = b2.StateMgr("test")
   205  	if err != nil {
   206  		t.Fatal("failed to get named state to force unlock:", err)
   207  	}
   208  
   209  	if err = s2.Unlock(lockID); err != nil {
   210  		t.Fatal("failed to force-unlock named state")
   211  	}
   212  }
   213  
   214  func TestRemoteClient_clientMD5(t *testing.T) {
   215  	testACC(t)
   216  
   217  	bucketName := fmt.Sprintf("tf-remote-oss-test-%x", time.Now().Unix())
   218  	tableName := fmt.Sprintf("tfRemoteTestForce%x", time.Now().Unix())
   219  	path := "testState"
   220  
   221  	b := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
   222  		"bucket":              bucketName,
   223  		"prefix":              path,
   224  		"tablestore_table":    tableName,
   225  		"tablestore_endpoint": RemoteTestUsedOTSEndpoint,
   226  	})).(*Backend)
   227  
   228  	createOSSBucket(t, b.ossClient, bucketName)
   229  	defer deleteOSSBucket(t, b.ossClient, bucketName)
   230  	createTablestoreTable(t, b.otsClient, tableName)
   231  	defer deleteTablestoreTable(t, b.otsClient, tableName)
   232  
   233  	s, err := b.StateMgr(backend.DefaultStateName)
   234  	if err != nil {
   235  		t.Fatal(err)
   236  	}
   237  	client := s.(*remote.State).Client.(*RemoteClient)
   238  
   239  	sum := md5.Sum([]byte("test"))
   240  
   241  	if err := client.putMD5(sum[:]); err != nil {
   242  		t.Fatal(err)
   243  	}
   244  
   245  	getSum, err := client.getMD5()
   246  	if err != nil {
   247  		t.Fatal(err)
   248  	}
   249  
   250  	if !bytes.Equal(getSum, sum[:]) {
   251  		t.Fatalf("getMD5 returned the wrong checksum: expected %x, got %x", sum[:], getSum)
   252  	}
   253  
   254  	if err := client.deleteMD5(); err != nil {
   255  		t.Fatal(err)
   256  	}
   257  
   258  	if getSum, err := client.getMD5(); err == nil {
   259  		t.Fatalf("expected getMD5 error, got none. checksum: %x", getSum)
   260  	}
   261  }
   262  
   263  // verify that a client won't return a state with an incorrect checksum.
   264  func TestRemoteClient_stateChecksum(t *testing.T) {
   265  	testACC(t)
   266  
   267  	bucketName := fmt.Sprintf("tf-remote-oss-test-%x", time.Now().Unix())
   268  	tableName := fmt.Sprintf("tfRemoteTestForce%x", time.Now().Unix())
   269  	path := "testState"
   270  
   271  	b1 := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
   272  		"bucket":              bucketName,
   273  		"prefix":              path,
   274  		"tablestore_table":    tableName,
   275  		"tablestore_endpoint": RemoteTestUsedOTSEndpoint,
   276  	})).(*Backend)
   277  
   278  	createOSSBucket(t, b1.ossClient, bucketName)
   279  	defer deleteOSSBucket(t, b1.ossClient, bucketName)
   280  	createTablestoreTable(t, b1.otsClient, tableName)
   281  	defer deleteTablestoreTable(t, b1.otsClient, tableName)
   282  
   283  	s1, err := b1.StateMgr(backend.DefaultStateName)
   284  	if err != nil {
   285  		t.Fatal(err)
   286  	}
   287  	client1 := s1.(*remote.State).Client
   288  
   289  	// create an old and new state version to persist
   290  	s := statemgr.TestFullInitialState()
   291  	sf := &statefile.File{State: s}
   292  	var oldState bytes.Buffer
   293  	if err := statefile.Write(sf, &oldState); err != nil {
   294  		t.Fatal(err)
   295  	}
   296  	sf.Serial++
   297  	var newState bytes.Buffer
   298  	if err := statefile.Write(sf, &newState); err != nil {
   299  		t.Fatal(err)
   300  	}
   301  
   302  	// Use b2 without a tablestore_table to bypass the lock table to write the state directly.
   303  	// client2 will write the "incorrect" state, simulating oss eventually consistency delays
   304  	b2 := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
   305  		"bucket": bucketName,
   306  		"prefix": path,
   307  	})).(*Backend)
   308  	s2, err := b2.StateMgr(backend.DefaultStateName)
   309  	if err != nil {
   310  		t.Fatal(err)
   311  	}
   312  	client2 := s2.(*remote.State).Client
   313  
   314  	// write the new state through client2 so that there is no checksum yet
   315  	if err := client2.Put(newState.Bytes()); err != nil {
   316  		t.Fatal(err)
   317  	}
   318  
   319  	// verify that we can pull a state without a checksum
   320  	if _, err := client1.Get(); err != nil {
   321  		t.Fatal(err)
   322  	}
   323  
   324  	// write the new state back with its checksum
   325  	if err := client1.Put(newState.Bytes()); err != nil {
   326  		t.Fatal(err)
   327  	}
   328  
   329  	// put an empty state in place to check for panics during get
   330  	if err := client2.Put([]byte{}); err != nil {
   331  		t.Fatal(err)
   332  	}
   333  
   334  	// remove the timeouts so we can fail immediately
   335  	origTimeout := consistencyRetryTimeout
   336  	origInterval := consistencyRetryPollInterval
   337  	defer func() {
   338  		consistencyRetryTimeout = origTimeout
   339  		consistencyRetryPollInterval = origInterval
   340  	}()
   341  	consistencyRetryTimeout = 0
   342  	consistencyRetryPollInterval = 0
   343  
   344  	// fetching an empty state through client1 should now error out due to a
   345  	// mismatched checksum.
   346  	if _, err := client1.Get(); !strings.HasPrefix(err.Error(), errBadChecksumFmt[:80]) {
   347  		t.Fatalf("expected state checksum error: got %s", err)
   348  	}
   349  
   350  	// put the old state in place of the new, without updating the checksum
   351  	if err := client2.Put(oldState.Bytes()); err != nil {
   352  		t.Fatal(err)
   353  	}
   354  
   355  	// fetching the wrong state through client1 should now error out due to a
   356  	// mismatched checksum.
   357  	if _, err := client1.Get(); !strings.HasPrefix(err.Error(), errBadChecksumFmt[:80]) {
   358  		t.Fatalf("expected state checksum error: got %s", err)
   359  	}
   360  
   361  	// update the state with the correct one after we Get again
   362  	testChecksumHook = func() {
   363  		if err := client2.Put(newState.Bytes()); err != nil {
   364  			t.Fatal(err)
   365  		}
   366  		testChecksumHook = nil
   367  	}
   368  
   369  	consistencyRetryTimeout = origTimeout
   370  
   371  	// this final Get will fail to fail the checksum verification, the above
   372  	// callback will update the state with the correct version, and Get should
   373  	// retry automatically.
   374  	if _, err := client1.Get(); err != nil {
   375  		t.Fatal(err)
   376  	}
   377  }