terraform: tainted destroy nodes

This commit is contained in:
Mitchell Hashimoto 2015-02-16 12:20:53 -08:00
parent 991611857a
commit 4789f16796
5 changed files with 132 additions and 77 deletions

View File

@ -105,7 +105,7 @@ func (b *BuiltinGraphBuilder) Steps() []GraphTransformer {
// Create the destruction nodes
&DestroyTransformer{},
&CreateBeforeDestroyTransformer{},
//&PruneDestroyTransformer{Diff: b.Diff, State: b.State},
&PruneDestroyTransformer{Diff: b.Diff, State: b.State},
// Make sure we create one root
&RootTransformer{},

View File

@ -176,10 +176,9 @@ func (n *GraphNodeConfigProvider) ProviderName() string {
type GraphNodeConfigResource struct {
Resource *config.Resource
// If set to true, this represents a resource that can only be
// destroyed. It doesn't mean that the resource WILL be destroyed, only
// that logically this node is where it would happen.
Destroy bool
// If this is set to anything other than destroyModeNone, then this
// resource represents a resource that will be destroyed in some way.
DestroyMode GraphNodeDestroyMode
}
func (n *GraphNodeConfigResource) DependableName() []string {
@ -222,8 +221,14 @@ func (n *GraphNodeConfigResource) DependentOn() []string {
func (n *GraphNodeConfigResource) Name() string {
result := n.Resource.Id()
if n.Destroy {
switch n.DestroyMode {
case DestroyNone:
case DestroyPrimary:
result += " (destroy)"
case DestroyTainted:
result += " (destroy tainted)"
default:
result += " (unknown destroy type)"
}
return result
@ -231,29 +236,52 @@ func (n *GraphNodeConfigResource) Name() string {
// GraphNodeDynamicExpandable impl.
func (n *GraphNodeConfigResource) DynamicExpand(ctx EvalContext) (*Graph, error) {
// Start creating the steps
steps := make([]GraphTransformer, 0, 5)
steps = append(steps, &ResourceCountTransformer{
Resource: n.Resource,
Destroy: n.Destroy,
})
// If we're destroying, then we care about adding orphans to
// the graph. Orphans in this case are the leftover resources when
// we decrease count.
if n.Destroy {
state, lock := ctx.State()
lock.RLock()
defer lock.RUnlock()
// Start creating the steps
steps := make([]GraphTransformer, 0, 5)
// Primary and non-destroy modes are responsible for creating/destroying
// all the nodes, expanding counts.
switch n.DestroyMode {
case DestroyNone:
fallthrough
case DestroyPrimary:
steps = append(steps, &ResourceCountTransformer{
Resource: n.Resource,
Destroy: n.DestroyMode != DestroyNone,
})
}
// Additional destroy modifications.
switch n.DestroyMode {
case DestroyPrimary:
// If we're destroying the primary instance, then we want to
// expand orphans, which have all the same semantics in a destroy
// as a primary.
steps = append(steps, &OrphanTransformer{
State: state,
View: n.Resource.Id(),
})
// If we're only destroying tainted resources, then we only
// want to find tainted resources and destroy them here.
steps = append(steps, &TaintedTransformer{
State: state,
View: n.Resource.Id(),
Deposed: n.Resource.Lifecycle.CreateBeforeDestroy,
DeposedInclude: true,
})
case DestroyTainted:
// If we're only destroying tainted resources, then we only
// want to find tainted resources and destroy them here.
steps = append(steps, &TaintedTransformer{
State: state,
View: n.Resource.Id(),
Deposed: n.Resource.Lifecycle.CreateBeforeDestroy,
DeposedInclude: false,
})
}
@ -295,19 +323,17 @@ func (n *GraphNodeConfigResource) ProvisionedBy() []string {
}
// GraphNodeDestroyable
func (n *GraphNodeConfigResource) DestroyNode() GraphNodeDestroy {
func (n *GraphNodeConfigResource) DestroyNode(mode GraphNodeDestroyMode) GraphNodeDestroy {
// If we're already a destroy node, then don't do anything
if n.Destroy {
if n.DestroyMode != DestroyNone {
return nil
}
// Just make a copy that is set to destroy
result := &graphNodeResourceDestroy{
GraphNodeConfigResource: *n,
Original: n,
}
result.Destroy = true
result.DestroyMode = mode
return result
}
@ -320,7 +346,13 @@ type graphNodeResourceDestroy struct {
}
func (n *graphNodeResourceDestroy) CreateBeforeDestroy() bool {
return n.Original.Resource.Lifecycle.CreateBeforeDestroy
// CBD is enabled if the resource enables it in addition to us
// being responsible for destroying the primary state. The primary
// state destroy node is the only destroy node that needs to be
// "shuffled" according to the CBD rules, since tainted resources
// don't have the same inverse dependencies.
return n.Original.Resource.Lifecycle.CreateBeforeDestroy &&
n.DestroyMode == DestroyPrimary
}
func (n *graphNodeResourceDestroy) CreateNode() dag.Vertex {

View File

@ -6,13 +6,22 @@ import (
"github.com/hashicorp/terraform/dag"
)
type GraphNodeDestroyMode byte
const (
DestroyNone GraphNodeDestroyMode = 0
DestroyPrimary GraphNodeDestroyMode = 1 << iota
DestroyTainted
)
// GraphNodeDestroyable is the interface that nodes that can be destroyed
// must implement. This is used to automatically handle the creation of
// destroy nodes in the graph and the dependency ordering of those destroys.
type GraphNodeDestroyable interface {
// DestroyNode returns the node used for the destroy. This should
// return a new destroy node that isn't in the graph.
DestroyNode() GraphNodeDestroy
// DestroyNode returns the node used for the destroy with the given
// mode. If this returns nil, then a destroy node for that mode
// will not be added.
DestroyNode(GraphNodeDestroyMode) GraphNodeDestroy
}
// GraphNodeDestroy is the interface that must implemented by
@ -43,24 +52,51 @@ type GraphNodeDiffPrunable interface {
type DestroyTransformer struct{}
func (t *DestroyTransformer) Transform(g *Graph) error {
nodes := make(map[dag.Vertex]struct{}, len(g.Vertices()))
var connect, remove []dag.Edge
modes := []GraphNodeDestroyMode{DestroyPrimary, DestroyTainted}
for _, m := range modes {
connectMode, removeMode, err := t.transform(g, m)
if err != nil {
return err
}
connect = append(connect, connectMode...)
remove = append(remove, removeMode...)
}
// Atomatically add/remove the edges
for _, e := range connect {
g.Connect(e)
}
for _, e := range remove {
g.RemoveEdge(e)
}
return nil
}
func (t *DestroyTransformer) transform(
g *Graph, mode GraphNodeDestroyMode) ([]dag.Edge, []dag.Edge, error) {
var connect, remove []dag.Edge
nodeToCn := make(map[dag.Vertex]dag.Vertex, len(g.Vertices()))
nodeToDn := make(map[dag.Vertex]dag.Vertex, len(g.Vertices()))
for _, v := range g.Vertices() {
// If it is not a destroyable, we don't care
dn, ok := v.(GraphNodeDestroyable)
cn, ok := v.(GraphNodeDestroyable)
if !ok {
continue
}
// Grab the destroy side of the node and connect it through
n := dn.DestroyNode()
n := cn.DestroyNode(mode)
if n == nil {
continue
}
// Store it
nodes[n] = struct{}{}
nodeToDn[dn] = n
nodeToCn[n] = cn
nodeToDn[cn] = n
// Add it to the graph
g.Add(n)
@ -73,33 +109,33 @@ func (t *DestroyTransformer) Transform(g *Graph) error {
// Add a new edge to connect the node to be created to
// the destroy node.
g.Connect(dag.BasicEdge(v, n))
connect = append(connect, dag.BasicEdge(v, n))
}
// Go through the nodes we added and determine if they depend
// on any nodes with a destroy node. If so, depend on that instead.
for n, _ := range nodes {
for n, _ := range nodeToCn {
for _, downRaw := range g.DownEdges(n).List() {
target := downRaw.(dag.Vertex)
dn, ok := target.(GraphNodeDestroyable)
cn2, ok := target.(GraphNodeDestroyable)
if !ok {
continue
}
newTarget := nodeToDn[dn]
newTarget := nodeToDn[cn2]
if newTarget == nil {
continue
}
// Make the new edge and transpose
g.Connect(dag.BasicEdge(newTarget, n))
connect = append(connect, dag.BasicEdge(newTarget, n))
// Remove the old edge
g.RemoveEdge(dag.BasicEdge(n, target))
remove = append(remove, dag.BasicEdge(n, target))
}
}
return nil
return connect, remove, nil
}
// CreateBeforeDestroyTransformer is a GraphTransformer that modifies

View File

@ -30,31 +30,6 @@ func TestDestroyTransformer(t *testing.T) {
}
}
func TestDestroyTransformer_deps(t *testing.T) {
mod := testModule(t, "transform-destroy-deps")
g := Graph{Path: RootModulePath}
{
tf := &ConfigTransformer{Module: mod}
if err := tf.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
}
{
tf := &DestroyTransformer{}
if err := tf.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
}
actual := strings.TrimSpace(g.String())
expected := strings.TrimSpace(testTransformDestroyDepsStr)
if actual != expected {
t.Fatalf("bad:\n\n%s", actual)
}
}
func TestCreateBeforeDestroyTransformer(t *testing.T) {
mod := testModule(t, "transform-create-before-destroy-basic")
@ -231,26 +206,20 @@ func TestPruneDestroyTransformer_count(t *testing.T) {
const testTransformDestroyBasicStr = `
aws_instance.bar
aws_instance.bar (destroy tainted)
aws_instance.bar (destroy)
aws_instance.foo
aws_instance.bar (destroy tainted)
aws_instance.bar (destroy)
aws_instance.foo
aws_instance.foo (destroy tainted)
aws_instance.foo (destroy)
aws_instance.foo (destroy tainted)
aws_instance.bar (destroy tainted)
aws_instance.foo (destroy)
aws_instance.bar (destroy)
`
const testTransformDestroyDepsStr = `
aws_asg.bar
aws_asg.bar (destroy)
aws_lc.foo
aws_asg.bar (destroy)
aws_lc.foo
aws_lc.foo (destroy)
aws_lc.foo (destroy)
aws_asg.bar (destroy)
`
const testTransformPruneDestroyBasicStr = `
aws_instance.bar
aws_instance.foo

View File

@ -14,6 +14,12 @@ type TaintedTransformer struct {
// View, if non-empty, is the ModuleState.View used around the state
// to find tainted resources.
View string
// Deposed, if set to true, assumes that the last tainted index
// represents a "deposed" resource, or a resource that was previously
// a primary but is now tainted since it is demoted.
Deposed bool
DeposedInclude bool
}
func (t *TaintedTransformer) Transform(g *Graph) error {
@ -35,8 +41,20 @@ func (t *TaintedTransformer) Transform(g *Graph) error {
if len(rs.Tainted) == 0 {
continue
}
tainted := rs.Tainted
for i, _ := range rs.Tainted {
// If we expect a deposed resource, then shuffle a bit
if t.Deposed {
if t.DeposedInclude {
// Only include the deposed resource
tainted = rs.Tainted[len(rs.Tainted)-1:]
} else {
// Exclude the deposed resource
tainted = rs.Tainted[:len(rs.Tainted)-1]
}
}
for i, _ := range tainted {
// Add the graph node and make the connection from any untainted
// resources with this name to the tainted resource, so that
// the tainted resource gets destroyed first.