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 }