From c2bf60060359b0570a16f30686a4bea63083bf11 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 23 Feb 2015 21:26:33 -0800 Subject: [PATCH] state: only change serial if changed --- state/cache.go | 2 ++ state/inmem.go | 1 + state/local.go | 10 ++++++++-- state/testing.go | 44 +++++++++++++++++++++++++++++++++++++---- terraform/state.go | 16 ++++++++++++--- terraform/state_test.go | 13 ------------ 6 files changed, 64 insertions(+), 22 deletions(-) diff --git a/state/cache.go b/state/cache.go index 3322ab5ac..0f0f306a0 100644 --- a/state/cache.go +++ b/state/cache.go @@ -104,6 +104,8 @@ func (s *CacheState) RefreshState() error { s.refreshResult = CacheRefreshNoop return err } + + cached = durable } s.state = cached diff --git a/state/inmem.go b/state/inmem.go index 82385a6df..68c2ad0c3 100644 --- a/state/inmem.go +++ b/state/inmem.go @@ -18,6 +18,7 @@ func (s *InmemState) RefreshState() error { } func (s *InmemState) WriteState(state *terraform.State) error { + state.IncrementSerialMaybe(s.state) s.state = state return nil } diff --git a/state/local.go b/state/local.go index 1840cae1a..30c3093aa 100644 --- a/state/local.go +++ b/state/local.go @@ -15,13 +15,15 @@ type LocalState struct { Path string PathOut string - state *terraform.State - written bool + state *terraform.State + readState *terraform.State + written bool } // SetState will force a specific state in-memory for this local state. func (s *LocalState) SetState(state *terraform.State) { s.state = state + s.readState = state } // StateReader impl. @@ -61,6 +63,9 @@ func (s *LocalState) WriteState(state *terraform.State) error { } defer f.Close() + s.state.IncrementSerialMaybe(s.readState) + s.readState = s.state + if err := terraform.WriteState(s.state, f); err != nil { return err } @@ -105,5 +110,6 @@ func (s *LocalState) RefreshState() error { } s.state = state + s.readState = state return nil } diff --git a/state/testing.go b/state/testing.go index 3ada81e40..7efd782d8 100644 --- a/state/testing.go +++ b/state/testing.go @@ -28,9 +28,7 @@ func TestState(t *testing.T, s interface{}) { current := TestStateInitial() // Check that the initial state is correct - state := reader.State() - current.Serial = state.Serial - if !reflect.DeepEqual(state, current) { + if state := reader.State(); !reflect.DeepEqual(state, current) { t.Fatalf("not initial: %#v\n\n%#v", state, current) } @@ -67,11 +65,49 @@ func TestState(t *testing.T, s interface{}) { // Just set the serials the same... Then compare. actual := reader.State() - actual.Serial = current.Serial if !reflect.DeepEqual(actual, current) { t.Fatalf("bad: %#v\n\n%#v", actual, current) } } + + // If we can write and persist then verify that the serial + // is only implemented on change. + writer, writeOk := s.(StateWriter) + persister, persistOk := s.(StatePersister) + if writeOk && persistOk { + // Same serial + serial := current.Serial + if err := writer.WriteState(current); err != nil { + t.Fatalf("err: %s", err) + } + if err := persister.PersistState(); err != nil { + t.Fatalf("err: %s", err) + } + + if reader.State().Serial != serial { + t.Fatalf("bad: expected %d, got %d", serial, reader.State().Serial) + } + + // Change the serial + currentCopy := *current + current = ¤tCopy + current.Modules = []*terraform.ModuleState{ + &terraform.ModuleState{ + Path: []string{"root", "somewhere"}, + Outputs: map[string]string{"serialCheck": "true"}, + }, + } + if err := writer.WriteState(current); err != nil { + t.Fatalf("err: %s", err) + } + if err := persister.PersistState(); err != nil { + t.Fatalf("err: %s", err) + } + + if reader.State().Serial <= serial { + t.Fatalf("bad: expected %d, got %d", serial, reader.State().Serial) + } + } } // TestStateInitial is the initial state that a State should have diff --git a/terraform/state.go b/terraform/state.go index 3ef3f3afe..68b5c11c6 100644 --- a/terraform/state.go +++ b/terraform/state.go @@ -161,6 +161,11 @@ func (s *State) RootModule() *ModuleState { // Equal tests if one state is equal to another. func (s *State) Equal(other *State) bool { + // If one is nil, we do a direct check + if s == nil || other == nil { + return s == other + } + // If the versions are different, they're certainly not equal if s.Version != other.Version { return false @@ -183,6 +188,14 @@ func (s *State) Equal(other *State) bool { return true } +// IncrementSerialMaybe increments the serial number of this state +// if it different from the other state. +func (s *State) IncrementSerialMaybe(other *State) { + if !s.Equal(other) { + s.Serial++ + } +} + func (s *State) init() { if s.Version == 0 { s.Version = StateVersion @@ -951,9 +964,6 @@ func WriteState(d *State, dst io.Writer) error { // Ensure the version is set d.Version = StateVersion - // Always increment the serial number - d.Serial++ - // Encode the data in a human-friendly way data, err := json.MarshalIndent(d, "", " ") if err != nil { diff --git a/terraform/state_test.go b/terraform/state_test.go index fc969e973..aaa049f4b 100644 --- a/terraform/state_test.go +++ b/terraform/state_test.go @@ -537,15 +537,6 @@ func TestReadWriteState(t *testing.T) { t.Fatalf("bad version number: %d", state.Version) } - // Verify the serial number is incremented - if state.Serial != 10 { - t.Fatalf("bad serial: %d", state.Serial) - } - - // Remove the changes or the checksum will fail - state.Version = 0 - state.Serial = 9 - // Checksum after the write chksumAfter := checksumStruct(t, state) if chksumAfter != chksum { @@ -557,10 +548,6 @@ func TestReadWriteState(t *testing.T) { t.Fatalf("err: %s", err) } - // Verify the changes came through - state.Version = StateVersion - state.Serial = 10 - // ReadState should not restore sensitive information! mod := state.RootModule() mod.Resources["foo"].Primary.Ephemeral = EphemeralState{}