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