github.com/opentofu/opentofu@v1.7.1/internal/backend/testing.go (about) 1 // Copyright (c) The OpenTofu Authors 2 // SPDX-License-Identifier: MPL-2.0 3 // Copyright (c) 2023 HashiCorp, Inc. 4 // SPDX-License-Identifier: MPL-2.0 5 6 package backend 7 8 import ( 9 "reflect" 10 "sort" 11 "testing" 12 13 uuid "github.com/hashicorp/go-uuid" 14 "github.com/hashicorp/hcl/v2" 15 "github.com/hashicorp/hcl/v2/hcldec" 16 17 "github.com/opentofu/opentofu/internal/addrs" 18 "github.com/opentofu/opentofu/internal/configs" 19 "github.com/opentofu/opentofu/internal/configs/hcl2shim" 20 "github.com/opentofu/opentofu/internal/states" 21 "github.com/opentofu/opentofu/internal/states/statemgr" 22 "github.com/opentofu/opentofu/internal/tfdiags" 23 ) 24 25 // TestBackendConfig validates and configures the backend with the 26 // given configuration. 27 func TestBackendConfig(t *testing.T, b Backend, c hcl.Body) Backend { 28 t.Helper() 29 30 t.Logf("TestBackendConfig on %T with %#v", b, c) 31 32 var diags tfdiags.Diagnostics 33 34 // To make things easier for test authors, we'll allow a nil body here 35 // (even though that's not normally valid) and just treat it as an empty 36 // body. 37 if c == nil { 38 c = hcl.EmptyBody() 39 } 40 41 schema := b.ConfigSchema() 42 spec := schema.DecoderSpec() 43 obj, decDiags := hcldec.Decode(c, spec, nil) 44 diags = diags.Append(decDiags) 45 46 newObj, valDiags := b.PrepareConfig(obj) 47 diags = diags.Append(valDiags.InConfigBody(c, "")) 48 49 // it's valid for a Backend to have warnings (e.g. a Deprecation) as such we should only raise on errors 50 if diags.HasErrors() { 51 t.Fatal(diags.ErrWithWarnings()) 52 } 53 54 obj = newObj 55 56 confDiags := b.Configure(obj) 57 if len(confDiags) != 0 { 58 confDiags = confDiags.InConfigBody(c, "") 59 t.Fatal(confDiags.ErrWithWarnings()) 60 } 61 62 return b 63 } 64 65 // TestWrapConfig takes a raw data structure and converts it into a 66 // synthetic hcl.Body to use for testing. 67 // 68 // The given structure should only include values that can be accepted by 69 // hcl2shim.HCL2ValueFromConfigValue. If incompatible values are given, 70 // this function will panic. 71 func TestWrapConfig(raw map[string]interface{}) hcl.Body { 72 obj := hcl2shim.HCL2ValueFromConfigValue(raw) 73 return configs.SynthBody("<TestWrapConfig>", obj.AsValueMap()) 74 } 75 76 // TestBackend will test the functionality of a Backend. The backend is 77 // assumed to already be configured. This will test state functionality. 78 // If the backend reports it doesn't support multi-state by returning the 79 // error ErrWorkspacesNotSupported, then it will not test that. 80 func TestBackendStates(t *testing.T, b Backend) { 81 t.Helper() 82 83 noDefault := false 84 if _, err := b.StateMgr(DefaultStateName); err != nil { 85 if err == ErrDefaultWorkspaceNotSupported { 86 noDefault = true 87 } else { 88 t.Fatalf("error: %v", err) 89 } 90 } 91 92 workspaces, err := b.Workspaces() 93 if err != nil { 94 if err == ErrWorkspacesNotSupported { 95 t.Logf("TestBackend: workspaces not supported in %T, skipping", b) 96 return 97 } 98 t.Fatalf("error: %v", err) 99 } 100 101 // Test it starts with only the default 102 if !noDefault && (len(workspaces) != 1 || workspaces[0] != DefaultStateName) { 103 t.Fatalf("should only have the default workspace to start: %#v", workspaces) 104 } 105 106 // Create a couple states 107 foo, err := b.StateMgr("foo") 108 if err != nil { 109 t.Fatalf("error: %s", err) 110 } 111 if err := foo.RefreshState(); err != nil { 112 t.Fatalf("bad: %s", err) 113 } 114 if v := foo.State(); v.HasManagedResourceInstanceObjects() { 115 t.Fatalf("should be empty: %s", v) 116 } 117 118 bar, err := b.StateMgr("bar") 119 if err != nil { 120 t.Fatalf("error: %s", err) 121 } 122 if err := bar.RefreshState(); err != nil { 123 t.Fatalf("bad: %s", err) 124 } 125 if v := bar.State(); v.HasManagedResourceInstanceObjects() { 126 t.Fatalf("should be empty: %s", v) 127 } 128 129 // Verify they are distinct states that can be read back from storage 130 { 131 // We'll use two distinct states here and verify that changing one 132 // does not also change the other. 133 fooState := states.NewState() 134 barState := states.NewState() 135 136 // write a known state to foo 137 if err := foo.WriteState(fooState); err != nil { 138 t.Fatal("error writing foo state:", err) 139 } 140 if err := foo.PersistState(nil); err != nil { 141 t.Fatal("error persisting foo state:", err) 142 } 143 144 // We'll make "bar" different by adding a fake resource state to it. 145 barState.SyncWrapper().SetResourceInstanceCurrent( 146 addrs.ResourceInstance{ 147 Resource: addrs.Resource{ 148 Mode: addrs.ManagedResourceMode, 149 Type: "test_thing", 150 Name: "foo", 151 }, 152 }.Absolute(addrs.RootModuleInstance), 153 &states.ResourceInstanceObjectSrc{ 154 AttrsJSON: []byte("{}"), 155 Status: states.ObjectReady, 156 SchemaVersion: 0, 157 }, 158 addrs.AbsProviderConfig{ 159 Provider: addrs.NewDefaultProvider("test"), 160 Module: addrs.RootModule, 161 }, 162 ) 163 164 // write a distinct known state to bar 165 if err := bar.WriteState(barState); err != nil { 166 t.Fatalf("bad: %s", err) 167 } 168 if err := bar.PersistState(nil); err != nil { 169 t.Fatalf("bad: %s", err) 170 } 171 172 // verify that foo is unchanged with the existing state manager 173 if err := foo.RefreshState(); err != nil { 174 t.Fatal("error refreshing foo:", err) 175 } 176 fooState = foo.State() 177 if fooState.HasManagedResourceInstanceObjects() { 178 t.Fatal("after writing a resource to bar, foo now has resources too") 179 } 180 181 // fetch foo again from the backend 182 foo, err = b.StateMgr("foo") 183 if err != nil { 184 t.Fatal("error re-fetching state:", err) 185 } 186 if err := foo.RefreshState(); err != nil { 187 t.Fatal("error refreshing foo:", err) 188 } 189 fooState = foo.State() 190 if fooState.HasManagedResourceInstanceObjects() { 191 t.Fatal("after writing a resource to bar and re-reading foo, foo now has resources too") 192 } 193 194 // fetch the bar again from the backend 195 bar, err = b.StateMgr("bar") 196 if err != nil { 197 t.Fatal("error re-fetching state:", err) 198 } 199 if err := bar.RefreshState(); err != nil { 200 t.Fatal("error refreshing bar:", err) 201 } 202 barState = bar.State() 203 if !barState.HasManagedResourceInstanceObjects() { 204 t.Fatal("after writing a resource instance object to bar and re-reading it, the object has vanished") 205 } 206 } 207 208 // Verify we can now list them 209 { 210 // we determined that named stated are supported earlier 211 workspaces, err := b.Workspaces() 212 if err != nil { 213 t.Fatalf("err: %s", err) 214 } 215 216 sort.Strings(workspaces) 217 expected := []string{"bar", "default", "foo"} 218 if noDefault { 219 expected = []string{"bar", "foo"} 220 } 221 if !reflect.DeepEqual(workspaces, expected) { 222 t.Fatalf("wrong workspaces list\ngot: %#v\nwant: %#v", workspaces, expected) 223 } 224 } 225 226 // Delete some workspaces 227 if err := b.DeleteWorkspace("foo", true); err != nil { 228 t.Fatalf("err: %s", err) 229 } 230 231 // Verify the default state can't be deleted 232 if err := b.DeleteWorkspace(DefaultStateName, true); err == nil { 233 t.Fatal("expected error") 234 } 235 236 // Create and delete the foo workspace again. 237 // Make sure that there are no leftover artifacts from a deleted state 238 // preventing re-creation. 239 foo, err = b.StateMgr("foo") 240 if err != nil { 241 t.Fatalf("error: %s", err) 242 } 243 if err := foo.RefreshState(); err != nil { 244 t.Fatalf("bad: %s", err) 245 } 246 if v := foo.State(); v.HasManagedResourceInstanceObjects() { 247 t.Fatalf("should be empty: %s", v) 248 } 249 // and delete it again 250 if err := b.DeleteWorkspace("foo", true); err != nil { 251 t.Fatalf("err: %s", err) 252 } 253 254 // Verify deletion 255 { 256 workspaces, err := b.Workspaces() 257 if err != nil { 258 t.Fatalf("err: %s", err) 259 } 260 261 sort.Strings(workspaces) 262 expected := []string{"bar", "default"} 263 if noDefault { 264 expected = []string{"bar"} 265 } 266 if !reflect.DeepEqual(workspaces, expected) { 267 t.Fatalf("wrong workspaces list\ngot: %#v\nwant: %#v", workspaces, expected) 268 } 269 } 270 } 271 272 // TestBackendStateLocks will test the locking functionality of the remote 273 // state backend. 274 func TestBackendStateLocks(t *testing.T, b1, b2 Backend) { 275 t.Helper() 276 testLocks(t, b1, b2, false) 277 } 278 279 // TestBackendStateForceUnlock verifies that the lock error is the expected 280 // type, and the lock can be unlocked using the ID reported in the error. 281 // Remote state backends that support -force-unlock should call this in at 282 // least one of the acceptance tests. 283 func TestBackendStateForceUnlock(t *testing.T, b1, b2 Backend) { 284 t.Helper() 285 testLocks(t, b1, b2, true) 286 } 287 288 // TestBackendStateLocksInWS will test the locking functionality of the remote 289 // state backend. 290 func TestBackendStateLocksInWS(t *testing.T, b1, b2 Backend, ws string) { 291 t.Helper() 292 testLocksInWorkspace(t, b1, b2, false, ws) 293 } 294 295 // TestBackendStateForceUnlockInWS verifies that the lock error is the expected 296 // type, and the lock can be unlocked using the ID reported in the error. 297 // Remote state backends that support -force-unlock should call this in at 298 // least one of the acceptance tests. 299 func TestBackendStateForceUnlockInWS(t *testing.T, b1, b2 Backend, ws string) { 300 t.Helper() 301 testLocksInWorkspace(t, b1, b2, true, ws) 302 } 303 304 func testLocks(t *testing.T, b1, b2 Backend, testForceUnlock bool) { 305 testLocksInWorkspace(t, b1, b2, testForceUnlock, DefaultStateName) 306 } 307 308 func testLocksInWorkspace(t *testing.T, b1, b2 Backend, testForceUnlock bool, workspace string) { 309 t.Helper() 310 311 // Get the default state for each 312 b1StateMgr, err := b1.StateMgr(DefaultStateName) 313 if err != nil { 314 t.Fatalf("error: %s", err) 315 } 316 if err := b1StateMgr.RefreshState(); err != nil { 317 t.Fatalf("bad: %s", err) 318 } 319 320 // Fast exit if this doesn't support locking at all 321 if _, ok := b1StateMgr.(statemgr.Locker); !ok { 322 t.Logf("TestBackend: backend %T doesn't support state locking, not testing", b1) 323 return 324 } 325 326 t.Logf("TestBackend: testing state locking for %T", b1) 327 328 b2StateMgr, err := b2.StateMgr(DefaultStateName) 329 if err != nil { 330 t.Fatalf("error: %s", err) 331 } 332 if err := b2StateMgr.RefreshState(); err != nil { 333 t.Fatalf("bad: %s", err) 334 } 335 336 // Reassign so its obvious whats happening 337 lockerA := b1StateMgr.(statemgr.Locker) 338 lockerB := b2StateMgr.(statemgr.Locker) 339 340 infoA := statemgr.NewLockInfo() 341 infoA.Operation = "test" 342 infoA.Who = "clientA" 343 344 infoB := statemgr.NewLockInfo() 345 infoB.Operation = "test" 346 infoB.Who = "clientB" 347 348 lockIDA, err := lockerA.Lock(infoA) 349 if err != nil { 350 t.Fatal("unable to get initial lock:", err) 351 } 352 353 // Make sure we can still get the statemgr.Full from another instance even 354 // when locked. This should only happen when a state is loaded via the 355 // backend, and as a remote state. 356 _, err = b2.StateMgr(DefaultStateName) 357 if err != nil { 358 t.Errorf("failed to read locked state from another backend instance: %s", err) 359 } 360 361 // If the lock ID is blank, assume locking is disabled 362 if lockIDA == "" { 363 t.Logf("TestBackend: %T: empty string returned for lock, assuming disabled", b1) 364 return 365 } 366 367 _, err = lockerB.Lock(infoB) 368 if err == nil { 369 lockerA.Unlock(lockIDA) 370 t.Fatal("client B obtained lock while held by client A") 371 } 372 373 if err := lockerA.Unlock(lockIDA); err != nil { 374 t.Fatal("error unlocking client A", err) 375 } 376 377 lockIDB, err := lockerB.Lock(infoB) 378 if err != nil { 379 t.Fatal("unable to obtain lock from client B") 380 } 381 382 if lockIDB == lockIDA { 383 t.Errorf("duplicate lock IDs: %q", lockIDB) 384 } 385 386 if err = lockerB.Unlock(lockIDB); err != nil { 387 t.Fatal("error unlocking client B:", err) 388 } 389 390 // test the equivalent of -force-unlock, by using the id from the error 391 // output. 392 if !testForceUnlock { 393 return 394 } 395 396 // get a new ID 397 infoA.ID, err = uuid.GenerateUUID() 398 if err != nil { 399 panic(err) 400 } 401 402 lockIDA, err = lockerA.Lock(infoA) 403 if err != nil { 404 t.Fatal("unable to get re lock A:", err) 405 } 406 unlock := func() { 407 err := lockerA.Unlock(lockIDA) 408 if err != nil { 409 t.Fatal(err) 410 } 411 } 412 413 _, err = lockerB.Lock(infoB) 414 if err == nil { 415 unlock() 416 t.Fatal("client B obtained lock while held by client A") 417 } 418 419 infoErr, ok := err.(*statemgr.LockError) 420 if !ok { 421 unlock() 422 t.Fatalf("expected type *statemgr.LockError, got : %#v", err) 423 } 424 425 // try to unlock with the second unlocker, using the ID from the error 426 if err := lockerB.Unlock(infoErr.Info.ID); err != nil { 427 unlock() 428 t.Fatalf("could not unlock with the reported ID %q: %s", infoErr.Info.ID, err) 429 } 430 }