core: [refactor] split WriteState EvalNodes

This is the non-DRY pass.
This commit is contained in:
Paul Hinze 2015-03-02 15:34:05 -06:00
parent d81ec2d37e
commit 426f253085
5 changed files with 230 additions and 45 deletions

View File

@ -56,6 +56,9 @@ func (n *EvalReadStateDeposed) Eval(ctx EvalContext) (interface{}, error) {
})
}
// Does the bulk of the work for the various flavors of ReadState eval nodes.
// Each node just provides a function to get from the ResourceState to the
// InstanceState, and this takes care of all the plumbing.
func readInstanceFromState(
ctx EvalContext,
resourceName string,
@ -138,17 +141,12 @@ func (n *EvalUpdateStateHook) Eval(ctx EvalContext) (interface{}, error) {
// EvalWriteState is an EvalNode implementation that reads the
// InstanceState for a specific resource out of the state.
type EvalWriteState struct {
Name string
ResourceType string
Dependencies []string
State **InstanceState
Tainted *bool
TaintedIndex int
TaintedClearPrimary bool
Deposed bool
Name string
ResourceType string
Dependencies []string
State **InstanceState
}
// TODO: test
func (n *EvalWriteState) Eval(ctx EvalContext) (interface{}, error) {
state, lock := ctx.State()
if state == nil {
@ -175,23 +173,120 @@ func (n *EvalWriteState) Eval(ctx EvalContext) (interface{}, error) {
rs.Type = n.ResourceType
rs.Dependencies = n.Dependencies
if n.Tainted != nil && *n.Tainted {
if n.TaintedIndex != -1 {
rs.Tainted[n.TaintedIndex] = *n.State
} else {
rs.Tainted = append(rs.Tainted, *n.State)
}
rs.Primary = *n.State
if n.TaintedClearPrimary {
rs.Primary = nil
}
} else if n.Deposed {
rs.Deposed = *n.State
} else {
// Set the primary state
rs.Primary = *n.State
return nil, nil
}
type EvalWriteStateTainted struct {
Name string
ResourceType string
Dependencies []string
State **InstanceState
TaintedIndex int
}
func (n *EvalWriteStateTainted) Eval(ctx EvalContext) (interface{}, error) {
state, lock := ctx.State()
if state == nil {
return nil, fmt.Errorf("cannot write state to nil state")
}
// Get a write lock so we can access this instance
lock.Lock()
defer lock.Unlock()
// Look for the module state. If we don't have one, create it.
mod := state.ModuleByPath(ctx.Path())
if mod == nil {
mod = state.AddModule(ctx.Path())
}
// Look for the resource state.
rs := mod.Resources[n.Name]
if rs == nil {
rs = &ResourceState{}
rs.init()
mod.Resources[n.Name] = rs
}
rs.Type = n.ResourceType
rs.Dependencies = n.Dependencies
if n.TaintedIndex == -1 {
rs.Tainted = append(rs.Tainted, *n.State)
} else {
rs.Tainted[n.TaintedIndex] = *n.State
}
return nil, nil
}
type EvalWriteStateDeposed struct {
Name string
ResourceType string
Dependencies []string
State **InstanceState
}
func (n *EvalWriteStateDeposed) Eval(ctx EvalContext) (interface{}, error) {
state, lock := ctx.State()
if state == nil {
return nil, fmt.Errorf("cannot write state to nil state")
}
// Get a write lock so we can access this instance
lock.Lock()
defer lock.Unlock()
// Look for the module state. If we don't have one, create it.
mod := state.ModuleByPath(ctx.Path())
if mod == nil {
mod = state.AddModule(ctx.Path())
}
// Look for the resource state.
rs := mod.Resources[n.Name]
if rs == nil {
rs = &ResourceState{}
rs.init()
mod.Resources[n.Name] = rs
}
rs.Type = n.ResourceType
rs.Dependencies = n.Dependencies
rs.Deposed = *n.State
return nil, nil
}
// EvalClearPrimaryState is an EvalNode implementation that clears the primary
// instance from a resource state.
type EvalClearPrimaryState struct {
Name string
}
func (n *EvalClearPrimaryState) Eval(ctx EvalContext) (interface{}, error) {
state, lock := ctx.State()
// Get a read lock so we can access this instance
lock.RLock()
defer lock.RUnlock()
// Look for the module state. If we don't have one, then it doesn't matter.
mod := state.ModuleByPath(ctx.Path())
if mod == nil {
return nil, nil
}
// Look for the resource state. If we don't have one, then it is okay.
rs := mod.Resources[n.Name]
if rs == nil {
return nil, nil
}
// Clear primary from the resource state
rs.Primary = nil
return nil, nil
}

View File

@ -147,3 +147,79 @@ func TestEvalReadState(t *testing.T) {
output = nil
}
}
func TestEvalWriteState(t *testing.T) {
state := &State{}
ctx := new(MockEvalContext)
ctx.StateState = state
ctx.StateLock = new(sync.RWMutex)
ctx.PathPath = rootModulePath
is := &InstanceState{ID: "i-abc123"}
node := &EvalWriteState{
Name: "restype.resname",
ResourceType: "restype",
State: &is,
}
_, err := node.Eval(ctx)
if err != nil {
t.Fatalf("Got err: %#v", err)
}
rs := state.ModuleByPath(ctx.Path()).Resources["restype.resname"]
if rs.Type != "restype" {
t.Fatalf("expected type 'restype': %#v", rs)
}
if rs.Primary.ID != "i-abc123" {
t.Fatalf("expected primary instance to have ID 'i-abc123': %#v", rs)
}
}
func TestEvalWriteStateTainted(t *testing.T) {
state := &State{}
ctx := new(MockEvalContext)
ctx.StateState = state
ctx.StateLock = new(sync.RWMutex)
ctx.PathPath = rootModulePath
is := &InstanceState{ID: "i-abc123"}
node := &EvalWriteStateTainted{
Name: "restype.resname",
ResourceType: "restype",
State: &is,
TaintedIndex: -1,
}
_, err := node.Eval(ctx)
if err != nil {
t.Fatalf("Got err: %#v", err)
}
rs := state.ModuleByPath(ctx.Path()).Resources["restype.resname"]
if len(rs.Tainted) == 1 && rs.Tainted[0].ID != "i-abc123" {
t.Fatalf("expected tainted instance to have ID 'i-abc123': %#v", rs)
}
}
func TestEvalWriteStateDeposed(t *testing.T) {
state := &State{}
ctx := new(MockEvalContext)
ctx.StateState = state
ctx.StateLock = new(sync.RWMutex)
ctx.PathPath = rootModulePath
is := &InstanceState{ID: "i-abc123"}
node := &EvalWriteStateDeposed{
Name: "restype.resname",
ResourceType: "restype",
State: &is,
}
_, err := node.Eval(ctx)
if err != nil {
t.Fatalf("Got err: %#v", err)
}
rs := state.ModuleByPath(ctx.Path()).Resources["restype.resname"]
if rs.Deposed == nil || rs.Deposed.ID != "i-abc123" {
t.Fatalf("expected deposed instance to have ID 'i-abc123': %#v", rs)
}
}

View File

@ -17,7 +17,7 @@ type DeposedTransformer struct {
func (t *DeposedTransformer) Transform(g *Graph) error {
state := t.State.ModuleByPath(g.Path)
if state == nil {
// If there is no state for our module there can't be any tainted
// If there is no state for our module there can't be any deposed
// resources, since they live in the state.
return nil
}
@ -27,7 +27,7 @@ func (t *DeposedTransformer) Transform(g *Graph) error {
state = state.View(t.View)
}
// Go through all the resources in our state to look for tainted resources
// Go through all the resources in our state to look for deposed resources
for k, rs := range state.Resources {
if rs.Deposed == nil {
continue
@ -86,11 +86,10 @@ func (n *graphNodeDeposedResource) EvalTree() EvalNode {
State: &state,
Output: &state,
},
&EvalWriteState{
&EvalWriteStateDeposed{
Name: n.ResourceName,
ResourceType: n.ResourceType,
State: &state,
Deposed: true,
},
},
},
@ -100,7 +99,6 @@ func (n *graphNodeDeposedResource) EvalTree() EvalNode {
var diff *InstanceDiff
var err error
var emptyState *InstanceState
tainted := true
seq.Nodes = append(seq.Nodes, &EvalOpFilter{
Ops: []walkOperation{walkApply},
Node: &EvalSequence{
@ -129,19 +127,17 @@ func (n *graphNodeDeposedResource) EvalTree() EvalNode {
// Always write the resource back to the state tainted... if it
// successfully destroyed it will be pruned. If it did not, it will
// remain tainted.
&EvalWriteState{
&EvalWriteStateTainted{
Name: n.ResourceName,
ResourceType: n.ResourceType,
State: &state,
Tainted: &tainted,
TaintedIndex: -1,
},
// Then clear the deposed state.
&EvalWriteState{
&EvalWriteStateDeposed{
Name: n.ResourceName,
ResourceType: n.ResourceType,
State: &emptyState,
Deposed: true,
},
&EvalReturnError{
Error: &err,

View File

@ -395,14 +395,35 @@ func (n *graphNodeExpandedResource) EvalTree() EvalNode {
Diff: nil,
},
&EvalWriteState{
Name: n.stateId(),
ResourceType: n.Resource.Type,
Dependencies: n.DependentOn(),
State: &state,
Tainted: &tainted,
TaintedIndex: -1,
TaintedClearPrimary: !n.Resource.Lifecycle.CreateBeforeDestroy,
&EvalIf{
If: func(ctx EvalContext) (bool, error) {
return tainted, nil
},
Then: &EvalSequence{
Nodes: []EvalNode{
&EvalWriteStateTainted{
Name: n.stateId(),
ResourceType: n.Resource.Type,
Dependencies: n.DependentOn(),
State: &state,
TaintedIndex: -1,
},
&EvalIf{
If: func(ctx EvalContext) (bool, error) {
return !n.Resource.Lifecycle.CreateBeforeDestroy, nil
},
Then: &EvalClearPrimaryState{
Name: n.stateId(),
},
},
},
},
Else: &EvalWriteState{
Name: n.stateId(),
ResourceType: n.Resource.Type,
Dependencies: n.DependentOn(),
State: &state,
},
},
&EvalApplyPost{
Info: info,

View File

@ -71,7 +71,6 @@ func (n *graphNodeTaintedResource) ProvidedBy() []string {
func (n *graphNodeTaintedResource) EvalTree() EvalNode {
var provider ResourceProvider
var state *InstanceState
tainted := true
seq := &EvalSequence{Nodes: make([]EvalNode, 0, 5)}
@ -99,11 +98,10 @@ func (n *graphNodeTaintedResource) EvalTree() EvalNode {
State: &state,
Output: &state,
},
&EvalWriteState{
&EvalWriteStateTainted{
Name: n.ResourceName,
ResourceType: n.ResourceType,
State: &state,
Tainted: &tainted,
TaintedIndex: n.Index,
},
},
@ -137,11 +135,10 @@ func (n *graphNodeTaintedResource) EvalTree() EvalNode {
Provider: &provider,
Output: &state,
},
&EvalWriteState{
&EvalWriteStateTainted{
Name: n.ResourceName,
ResourceType: n.ResourceType,
State: &state,
Tainted: &tainted,
TaintedIndex: n.Index,
},
&EvalUpdateStateHook{},