github.com/graywolf-at-work-2/terraform-vendor@v1.4.5/internal/backend/remote-state/consul/client_test.go (about) 1 package consul 2 3 import ( 4 "bytes" 5 "context" 6 "encoding/json" 7 "fmt" 8 "math/rand" 9 "net" 10 "reflect" 11 "strings" 12 "sync" 13 "testing" 14 "time" 15 16 "github.com/hashicorp/terraform/internal/backend" 17 "github.com/hashicorp/terraform/internal/states/remote" 18 "github.com/hashicorp/terraform/internal/states/statemgr" 19 ) 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 srv := newConsulTestServer(t) 28 29 testCases := []string{ 30 fmt.Sprintf("tf-unit/%s", time.Now().String()), 31 fmt.Sprintf("tf-unit/%s/", time.Now().String()), 32 } 33 34 for _, path := range testCases { 35 t.Run(path, func(*testing.T) { 36 // Get the backend 37 b := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{ 38 "address": srv.HTTPAddr, 39 "path": path, 40 })) 41 42 // Grab the client 43 state, err := b.StateMgr(backend.DefaultStateName) 44 if err != nil { 45 t.Fatalf("err: %s", err) 46 } 47 48 // Test 49 remote.TestClient(t, state.(*remote.State).Client) 50 }) 51 } 52 } 53 54 // test the gzip functionality of the client 55 func TestRemoteClient_gzipUpgrade(t *testing.T) { 56 srv := newConsulTestServer(t) 57 58 statePath := fmt.Sprintf("tf-unit/%s", time.Now().String()) 59 60 // Get the backend 61 b := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{ 62 "address": srv.HTTPAddr, 63 "path": statePath, 64 })) 65 66 // Grab the client 67 state, err := b.StateMgr(backend.DefaultStateName) 68 if err != nil { 69 t.Fatalf("err: %s", err) 70 } 71 72 // Test 73 remote.TestClient(t, state.(*remote.State).Client) 74 75 // create a new backend with gzip 76 b = backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{ 77 "address": srv.HTTPAddr, 78 "path": statePath, 79 "gzip": true, 80 })) 81 82 // Grab the client 83 state, err = b.StateMgr(backend.DefaultStateName) 84 if err != nil { 85 t.Fatalf("err: %s", err) 86 } 87 88 // Test 89 remote.TestClient(t, state.(*remote.State).Client) 90 } 91 92 // TestConsul_largeState tries to write a large payload using the Consul state 93 // manager, as there is a limit to the size of the values in the KV store it 94 // will need to be split up before being saved and put back together when read. 95 func TestConsul_largeState(t *testing.T) { 96 srv := newConsulTestServer(t) 97 98 path := "tf-unit/test-large-state" 99 100 b := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{ 101 "address": srv.HTTPAddr, 102 "path": path, 103 })) 104 105 s, err := b.StateMgr(backend.DefaultStateName) 106 if err != nil { 107 t.Fatal(err) 108 } 109 110 c := s.(*remote.State).Client.(*RemoteClient) 111 c.Path = path 112 113 // testPaths fails the test if the keys found at the prefix don't match 114 // what is expected 115 testPaths := func(t *testing.T, expected []string) { 116 kv := c.Client.KV() 117 pairs, _, err := kv.List(c.Path, nil) 118 if err != nil { 119 t.Fatal(err) 120 } 121 res := make([]string, 0) 122 for _, p := range pairs { 123 res = append(res, p.Key) 124 } 125 if !reflect.DeepEqual(res, expected) { 126 t.Fatalf("Wrong keys: %#v", res) 127 } 128 } 129 130 testPayload := func(t *testing.T, data map[string]string, keys []string) { 131 payload, err := json.Marshal(data) 132 if err != nil { 133 t.Fatal(err) 134 } 135 err = c.Put(payload) 136 if err != nil { 137 t.Fatal("could not put payload", err) 138 } 139 140 remote, err := c.Get() 141 if err != nil { 142 t.Fatal(err) 143 } 144 145 if !bytes.Equal(payload, remote.Data) { 146 t.Fatal("the data do not match") 147 } 148 149 testPaths(t, keys) 150 } 151 152 // The default limit for the size of the value in Consul is 524288 bytes 153 testPayload( 154 t, 155 map[string]string{ 156 "foo": strings.Repeat("a", 524288+2), 157 }, 158 []string{ 159 "tf-unit/test-large-state", 160 "tf-unit/test-large-state/tfstate.2cb96f52c9fff8e0b56cb786ec4d2bed/0", 161 "tf-unit/test-large-state/tfstate.2cb96f52c9fff8e0b56cb786ec4d2bed/1", 162 }, 163 ) 164 165 // This payload is just short enough to be stored but will be bigger when 166 // going through the Transaction API as it will be base64 encoded 167 testPayload( 168 t, 169 map[string]string{ 170 "foo": strings.Repeat("a", 524288-10), 171 }, 172 []string{ 173 "tf-unit/test-large-state", 174 "tf-unit/test-large-state/tfstate.4f407ace136a86521fd0d366972fe5c7/0", 175 }, 176 ) 177 178 // We try to replace the payload with a small one, the old chunks should be removed 179 testPayload( 180 t, 181 map[string]string{"var": "a"}, 182 []string{"tf-unit/test-large-state"}, 183 ) 184 185 // Test with gzip and chunks 186 b = backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{ 187 "address": srv.HTTPAddr, 188 "path": path, 189 "gzip": true, 190 })) 191 192 s, err = b.StateMgr(backend.DefaultStateName) 193 if err != nil { 194 t.Fatal(err) 195 } 196 197 c = s.(*remote.State).Client.(*RemoteClient) 198 c.Path = path 199 200 // We need a long random string so it results in multiple chunks even after 201 // being gziped 202 203 // We use a fixed seed so the test can be reproductible 204 rand.Seed(1234) 205 RandStringRunes := func(n int) string { 206 var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") 207 b := make([]rune, n) 208 for i := range b { 209 b[i] = letterRunes[rand.Intn(len(letterRunes))] 210 } 211 return string(b) 212 } 213 214 testPayload( 215 t, 216 map[string]string{ 217 "bar": RandStringRunes(5 * (524288 + 2)), 218 }, 219 []string{ 220 "tf-unit/test-large-state", 221 "tf-unit/test-large-state/tfstate.58e8160335864b520b1cc7f2222a4019/0", 222 "tf-unit/test-large-state/tfstate.58e8160335864b520b1cc7f2222a4019/1", 223 "tf-unit/test-large-state/tfstate.58e8160335864b520b1cc7f2222a4019/2", 224 "tf-unit/test-large-state/tfstate.58e8160335864b520b1cc7f2222a4019/3", 225 }, 226 ) 227 228 // Deleting the state should remove all chunks 229 err = c.Delete() 230 if err != nil { 231 t.Fatal(err) 232 } 233 testPaths(t, []string{}) 234 } 235 236 func TestConsul_stateLock(t *testing.T) { 237 srv := newConsulTestServer(t) 238 239 testCases := []string{ 240 fmt.Sprintf("tf-unit/%s", time.Now().String()), 241 fmt.Sprintf("tf-unit/%s/", time.Now().String()), 242 } 243 244 for _, path := range testCases { 245 t.Run(path, func(*testing.T) { 246 // create 2 instances to get 2 remote.Clients 247 sA, err := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{ 248 "address": srv.HTTPAddr, 249 "path": path, 250 })).StateMgr(backend.DefaultStateName) 251 if err != nil { 252 t.Fatal(err) 253 } 254 255 sB, err := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{ 256 "address": srv.HTTPAddr, 257 "path": path, 258 })).StateMgr(backend.DefaultStateName) 259 if err != nil { 260 t.Fatal(err) 261 } 262 263 remote.TestRemoteLocks(t, sA.(*remote.State).Client, sB.(*remote.State).Client) 264 }) 265 } 266 } 267 268 func TestConsul_destroyLock(t *testing.T) { 269 srv := newConsulTestServer(t) 270 271 testCases := []string{ 272 fmt.Sprintf("tf-unit/%s", time.Now().String()), 273 fmt.Sprintf("tf-unit/%s/", time.Now().String()), 274 } 275 276 testLock := func(client *RemoteClient, lockPath string) { 277 // get the lock val 278 pair, _, err := client.Client.KV().Get(lockPath, nil) 279 if err != nil { 280 t.Fatal(err) 281 } 282 if pair != nil { 283 t.Fatalf("lock key not cleaned up at: %s", pair.Key) 284 } 285 } 286 287 for _, path := range testCases { 288 t.Run(path, func(*testing.T) { 289 // Get the backend 290 b := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{ 291 "address": srv.HTTPAddr, 292 "path": path, 293 })) 294 295 // Grab the client 296 s, err := b.StateMgr(backend.DefaultStateName) 297 if err != nil { 298 t.Fatalf("err: %s", err) 299 } 300 301 clientA := s.(*remote.State).Client.(*RemoteClient) 302 303 info := statemgr.NewLockInfo() 304 id, err := clientA.Lock(info) 305 if err != nil { 306 t.Fatal(err) 307 } 308 309 lockPath := clientA.Path + lockSuffix 310 311 if err := clientA.Unlock(id); err != nil { 312 t.Fatal(err) 313 } 314 315 testLock(clientA, lockPath) 316 317 // The release the lock from a second client to test the 318 // `terraform force-unlock <lock_id>` functionnality 319 s, err = b.StateMgr(backend.DefaultStateName) 320 if err != nil { 321 t.Fatalf("err: %s", err) 322 } 323 324 clientB := s.(*remote.State).Client.(*RemoteClient) 325 326 info = statemgr.NewLockInfo() 327 id, err = clientA.Lock(info) 328 if err != nil { 329 t.Fatal(err) 330 } 331 332 if err := clientB.Unlock(id); err != nil { 333 t.Fatal(err) 334 } 335 336 testLock(clientA, lockPath) 337 338 err = clientA.Unlock(id) 339 340 if err == nil { 341 t.Fatal("consul lock should have been lost") 342 } 343 if err.Error() != "consul lock was lost" { 344 t.Fatal("got wrong error", err) 345 } 346 }) 347 } 348 } 349 350 func TestConsul_lostLock(t *testing.T) { 351 srv := newConsulTestServer(t) 352 353 path := fmt.Sprintf("tf-unit/%s", time.Now().String()) 354 355 // create 2 instances to get 2 remote.Clients 356 sA, err := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{ 357 "address": srv.HTTPAddr, 358 "path": path, 359 })).StateMgr(backend.DefaultStateName) 360 if err != nil { 361 t.Fatal(err) 362 } 363 364 sB, err := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{ 365 "address": srv.HTTPAddr, 366 "path": path + "-not-used", 367 })).StateMgr(backend.DefaultStateName) 368 if err != nil { 369 t.Fatal(err) 370 } 371 372 info := statemgr.NewLockInfo() 373 info.Operation = "test-lost-lock" 374 id, err := sA.Lock(info) 375 if err != nil { 376 t.Fatal(err) 377 } 378 379 reLocked := make(chan struct{}) 380 testLockHook = func() { 381 close(reLocked) 382 testLockHook = nil 383 } 384 385 // now we use the second client to break the lock 386 kv := sB.(*remote.State).Client.(*RemoteClient).Client.KV() 387 _, err = kv.Delete(path+lockSuffix, nil) 388 if err != nil { 389 t.Fatal(err) 390 } 391 392 <-reLocked 393 394 if err := sA.Unlock(id); err != nil { 395 t.Fatal(err) 396 } 397 } 398 399 func TestConsul_lostLockConnection(t *testing.T) { 400 srv := newConsulTestServer(t) 401 402 // create an "unreliable" network by closing all the consul client's 403 // network connections 404 conns := &unreliableConns{} 405 origDialFn := dialContext 406 defer func() { 407 dialContext = origDialFn 408 }() 409 dialContext = conns.DialContext 410 411 path := fmt.Sprintf("tf-unit/%s", time.Now().String()) 412 413 b := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{ 414 "address": srv.HTTPAddr, 415 "path": path, 416 })) 417 418 s, err := b.StateMgr(backend.DefaultStateName) 419 if err != nil { 420 t.Fatal(err) 421 } 422 423 info := statemgr.NewLockInfo() 424 info.Operation = "test-lost-lock-connection" 425 id, err := s.Lock(info) 426 if err != nil { 427 t.Fatal(err) 428 } 429 430 // kill the connection a few times 431 for i := 0; i < 3; i++ { 432 dialed := conns.dialedDone() 433 // kill any open connections 434 conns.Kill() 435 // wait for a new connection to be dialed, and kill it again 436 <-dialed 437 } 438 439 if err := s.Unlock(id); err != nil { 440 t.Fatal("unlock error:", err) 441 } 442 } 443 444 type unreliableConns struct { 445 sync.Mutex 446 conns []net.Conn 447 dialCallback func() 448 } 449 450 func (u *unreliableConns) DialContext(ctx context.Context, netw, addr string) (net.Conn, error) { 451 u.Lock() 452 defer u.Unlock() 453 454 dialer := &net.Dialer{} 455 conn, err := dialer.DialContext(ctx, netw, addr) 456 if err != nil { 457 return nil, err 458 } 459 460 u.conns = append(u.conns, conn) 461 462 if u.dialCallback != nil { 463 u.dialCallback() 464 } 465 466 return conn, nil 467 } 468 469 func (u *unreliableConns) dialedDone() chan struct{} { 470 u.Lock() 471 defer u.Unlock() 472 dialed := make(chan struct{}) 473 u.dialCallback = func() { 474 defer close(dialed) 475 u.dialCallback = nil 476 } 477 478 return dialed 479 } 480 481 // Kill these with a deadline, just to make sure we don't end up with any EOFs 482 // that get ignored. 483 func (u *unreliableConns) Kill() { 484 u.Lock() 485 defer u.Unlock() 486 487 for _, conn := range u.conns { 488 conn.(*net.TCPConn).SetDeadline(time.Now()) 489 } 490 u.conns = nil 491 }