2015-02-21 20:52:55 +01:00
|
|
|
package state
|
|
|
|
|
|
|
|
import (
|
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
2017-01-10 00:15:48 +01:00
|
|
|
"os/exec"
|
2017-05-25 17:05:48 +02:00
|
|
|
"sync"
|
2015-02-21 20:52:55 +01:00
|
|
|
"testing"
|
|
|
|
|
|
|
|
"github.com/hashicorp/terraform/terraform"
|
|
|
|
)
|
|
|
|
|
|
|
|
func TestLocalState(t *testing.T) {
|
2015-02-21 21:25:10 +01:00
|
|
|
ls := testLocalState(t)
|
|
|
|
defer os.Remove(ls.Path)
|
|
|
|
TestState(t, ls)
|
|
|
|
}
|
|
|
|
|
2017-05-25 17:05:48 +02:00
|
|
|
func TestLocalStateRace(t *testing.T) {
|
|
|
|
ls := testLocalState(t)
|
|
|
|
defer os.Remove(ls.Path)
|
|
|
|
|
|
|
|
current := TestStateInitial()
|
|
|
|
|
|
|
|
var wg sync.WaitGroup
|
|
|
|
for i := 0; i < 100; i++ {
|
|
|
|
wg.Add(1)
|
|
|
|
go func() {
|
|
|
|
defer wg.Done()
|
|
|
|
ls.WriteState(current)
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-01-10 00:15:48 +01:00
|
|
|
func TestLocalStateLocks(t *testing.T) {
|
|
|
|
s := testLocalState(t)
|
|
|
|
defer os.Remove(s.Path)
|
|
|
|
|
|
|
|
// lock first
|
2017-02-15 16:25:04 +01:00
|
|
|
info := NewLockInfo()
|
|
|
|
info.Operation = "test"
|
2017-02-15 01:00:36 +01:00
|
|
|
lockID, err := s.Lock(info)
|
|
|
|
if err != nil {
|
2017-01-10 00:15:48 +01:00
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
out, err := exec.Command("go", "run", "testdata/lockstate.go", s.Path).CombinedOutput()
|
|
|
|
if err != nil {
|
2017-02-15 01:00:36 +01:00
|
|
|
t.Fatal("unexpected lock failure", err, string(out))
|
2017-01-10 00:15:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if string(out) != "lock failed" {
|
|
|
|
t.Fatal("expected 'locked failed', got", string(out))
|
|
|
|
}
|
|
|
|
|
|
|
|
// check our lock info
|
|
|
|
lockInfo, err := s.lockInfo()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
2017-02-15 01:00:36 +01:00
|
|
|
if lockInfo.Operation != "test" {
|
2017-01-10 00:15:48 +01:00
|
|
|
t.Fatalf("invalid lock info %#v\n", lockInfo)
|
|
|
|
}
|
|
|
|
|
|
|
|
// a noop, since we unlock on exit
|
2017-02-15 01:00:36 +01:00
|
|
|
if err := s.Unlock(lockID); err != nil {
|
2017-01-10 00:15:48 +01:00
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// local locks can re-lock
|
2017-02-15 01:00:36 +01:00
|
|
|
lockID, err = s.Lock(info)
|
|
|
|
if err != nil {
|
2017-01-10 00:15:48 +01:00
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
2017-02-15 01:00:36 +01:00
|
|
|
if err := s.Unlock(lockID); err != nil {
|
2017-01-10 00:15:48 +01:00
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2017-02-15 16:53:04 +01:00
|
|
|
|
|
|
|
// we should not be able to unlock the same lock twice
|
|
|
|
if err := s.Unlock(lockID); err == nil {
|
|
|
|
t.Fatal("unlocking an unlocked state should fail")
|
2017-01-10 00:15:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// make sure lock info is gone
|
|
|
|
lockInfoPath := s.lockInfoPath()
|
|
|
|
if _, err := os.Stat(lockInfoPath); !os.IsNotExist(err) {
|
|
|
|
t.Fatal("lock info not removed")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-17 19:29:13 +01:00
|
|
|
// Verify that we can write to the state file, as Windows' mandatory locking
|
|
|
|
// will prevent writing to a handle different than the one that hold the lock.
|
|
|
|
func TestLocalState_writeWhileLocked(t *testing.T) {
|
|
|
|
s := testLocalState(t)
|
|
|
|
defer os.Remove(s.Path)
|
|
|
|
|
|
|
|
// lock first
|
|
|
|
info := NewLockInfo()
|
|
|
|
info.Operation = "test"
|
|
|
|
lockID, err := s.Lock(info)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
defer func() {
|
|
|
|
if err := s.Unlock(lockID); err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
if err := s.WriteState(TestStateInitial()); err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-02-22 01:11:47 +01:00
|
|
|
func TestLocalState_pathOut(t *testing.T) {
|
|
|
|
f, err := ioutil.TempFile("", "tf")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %s", err)
|
|
|
|
}
|
|
|
|
f.Close()
|
|
|
|
defer os.Remove(f.Name())
|
|
|
|
|
|
|
|
ls := testLocalState(t)
|
|
|
|
ls.PathOut = f.Name()
|
|
|
|
defer os.Remove(ls.Path)
|
|
|
|
|
|
|
|
TestState(t, ls)
|
|
|
|
}
|
|
|
|
|
2015-02-22 01:06:27 +01:00
|
|
|
func TestLocalState_nonExist(t *testing.T) {
|
|
|
|
ls := &LocalState{Path: "ishouldntexist"}
|
|
|
|
if err := ls.RefreshState(); err != nil {
|
|
|
|
t.Fatalf("err: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if state := ls.State(); state != nil {
|
|
|
|
t.Fatalf("bad: %#v", state)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-02-21 21:25:10 +01:00
|
|
|
func TestLocalState_impl(t *testing.T) {
|
|
|
|
var _ StateReader = new(LocalState)
|
|
|
|
var _ StateWriter = new(LocalState)
|
|
|
|
var _ StatePersister = new(LocalState)
|
|
|
|
var _ StateRefresher = new(LocalState)
|
|
|
|
}
|
|
|
|
|
|
|
|
func testLocalState(t *testing.T) *LocalState {
|
2015-02-21 20:52:55 +01:00
|
|
|
f, err := ioutil.TempFile("", "tf")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %s", err)
|
|
|
|
}
|
|
|
|
|
2015-02-21 21:25:10 +01:00
|
|
|
err = terraform.WriteState(TestStateInitial(), f)
|
2015-02-21 20:52:55 +01:00
|
|
|
f.Close()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %s", err)
|
|
|
|
}
|
|
|
|
|
2015-02-21 21:25:10 +01:00
|
|
|
ls := &LocalState{Path: f.Name()}
|
|
|
|
if err := ls.RefreshState(); err != nil {
|
|
|
|
t.Fatalf("bad: %s", err)
|
|
|
|
}
|
2015-02-21 20:52:55 +01:00
|
|
|
|
2015-02-21 21:25:10 +01:00
|
|
|
return ls
|
2015-02-21 20:52:55 +01:00
|
|
|
}
|
2018-03-19 22:09:53 +01:00
|
|
|
|
|
|
|
// Make sure we can refresh while the state is locked
|
|
|
|
func TestLocalState_refreshWhileLocked(t *testing.T) {
|
|
|
|
f, err := ioutil.TempFile("", "tf")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
err = terraform.WriteState(TestStateInitial(), f)
|
|
|
|
f.Close()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
s := &LocalState{Path: f.Name()}
|
|
|
|
defer os.Remove(s.Path)
|
|
|
|
|
|
|
|
// lock first
|
|
|
|
info := NewLockInfo()
|
|
|
|
info.Operation = "test"
|
|
|
|
lockID, err := s.Lock(info)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
defer func() {
|
|
|
|
if err := s.Unlock(lockID); err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
if err := s.RefreshState(); err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
readState := s.State()
|
|
|
|
if readState == nil || readState.Lineage == "" {
|
|
|
|
t.Fatal("missing state")
|
|
|
|
}
|
|
|
|
}
|