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 }