terraform: starting CBD, destroy edge for the destroy relationship

This commit is contained in:
Mitchell Hashimoto 2016-09-21 10:55:07 -07:00
parent 4988378ccb
commit 7baf64f806
No known key found for this signature in database
GPG Key ID: 744E147AA52F5B0A
6 changed files with 208 additions and 2 deletions

17
terraform/edge_destroy.go Normal file
View File

@ -0,0 +1,17 @@
package terraform
import (
"fmt"
"github.com/hashicorp/terraform/dag"
)
// DestroyEdge is an edge that represents a standard "destroy" relationship:
// Target depends on Source because Source is destroying.
type DestroyEdge struct {
S, T dag.Vertex
}
func (e *DestroyEdge) Hashcode() interface{} { return fmt.Sprintf("%p-%p", e.S, e.T) }
func (e *DestroyEdge) Source() dag.Vertex { return e.S }
func (e *DestroyEdge) Target() dag.Vertex { return e.T }

View File

@ -90,6 +90,11 @@ func (n *NodeApplyableResource) ProvisionedBy() []string {
return result
}
// GraphNodeCreator
func (n *NodeApplyableResource) CreateAddr() *ResourceAddress {
return n.Addr
}
// GraphNodeAttachResourceState
func (n *NodeApplyableResource) ResourceAddr() *ResourceAddress {
return n.Addr

View File

@ -0,0 +1,67 @@
package terraform
import (
"log"
"github.com/hashicorp/terraform/config/module"
)
// GraphNodeDestroyerCBD must be implemented by nodes that might be
// create-before-destroy destroyers.
type GraphNodeDestroyerCBD interface {
GraphNodeDestroyer
// CreateBeforeDestroy returns true if this node represents a node
// that is doing a CBD.
CreateBeforeDestroy() bool
}
// CBDEdgeTransformer modifies the edges of CBD nodes that went through
// the DestroyEdgeTransformer to have the right dependencies. There are
// two real tasks here:
//
// 1. With CBD, the destroy edge is inverted: the destroy depends on
// the creation.
//
// 2. A_d must depend on resources that depend on A. This is to enable
// the destroy to only happen once nodes that depend on A successfully
// update to A. Example: adding a web server updates the load balancer
// before deleting the old web server.
//
type CBDEdgeTransformer struct {
// Module and State are only needed to look up dependencies in
// any way possible. Either can be nil if not availabile.
Module *module.Tree
State *State
}
func (t *CBDEdgeTransformer) Transform(g *Graph) error {
log.Printf("[TRACE] CBDEdgeTransformer: Beginning CBD transformation...")
// Go through and reverse any destroy edges
for _, v := range g.Vertices() {
dn, ok := v.(GraphNodeDestroyerCBD)
if !ok {
continue
}
if !dn.CreateBeforeDestroy() {
continue
}
// Find the destroy edge. There should only be one.
for _, e := range g.DownEdges(v).List() {
// Not a destroy edge, ignore it
de, ok := e.(*DestroyEdge)
if !ok {
continue
}
// Found it! Invert.
g.RemoveEdge(de)
g.Connect(&DestroyEdge{S: de.Target(), T: de.Source()})
}
}
return nil
}

View File

@ -0,0 +1,40 @@
package terraform
import (
"strings"
"testing"
)
func TestCBDEdgeTransformer(t *testing.T) {
g := Graph{Path: RootModulePath}
g.Add(&graphNodeDestroyerTest{AddrString: "test.A"})
g.Add(&graphNodeDestroyerTest{AddrString: "test.B"})
{
tf := &DestroyEdgeTransformer{
Module: testModule(t, "transform-destroy-edge-basic"),
}
if err := tf.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
}
{
tf := &CBDEdgeTransformer{}
if err := tf.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
}
actual := strings.TrimSpace(g.String())
expected := strings.TrimSpace(testTransformCBDEdgeBasicStr)
if actual != expected {
t.Fatalf("bad:\n\n%s", actual)
}
}
const testTransformCBDEdgeBasicStr = `
test.A (destroy)
test.B (destroy)
test.B (destroy)
`

View File

@ -17,6 +17,12 @@ type GraphNodeDestroyer interface {
DestroyAddr() *ResourceAddress
}
// GraphNodeCreator must be implemented by nodes that create OR update resources.
type GraphNodeCreator interface {
// ResourceAddr is the address of the resource being created or updated
CreateAddr() *ResourceAddress
}
// DestroyEdgeTransformer is a GraphTransformer that creates the proper
// references for destroy resources. Destroy resources are more complex
// in that they must be depend on the destruction of resources that
@ -69,6 +75,34 @@ func (t *DestroyEdgeTransformer) Transform(g *Graph) error {
return nil
}
// Go through and connect creators to destroyers. Going along with
// our example, this makes: A_d => A
for _, v := range g.Vertices() {
cn, ok := v.(GraphNodeCreator)
if !ok {
continue
}
addr := cn.CreateAddr()
if addr == nil {
continue
}
key := addr.String()
ds := destroyers[key]
if len(ds) == 0 {
continue
}
for _, d := range ds {
// For illustrating our example
a_d := d.(dag.Vertex)
a := v
g.Connect(&DestroyEdge{S: a, T: a_d})
}
}
// This is strange but is the easiest way to get the dependencies
// of a node that is being destroyed. We use another graph to make sure
// the resource is in the graph and ask for references. We have to do this

View File

@ -23,6 +23,25 @@ func TestDestroyEdgeTransformer(t *testing.T) {
}
}
func TestDestroyEdgeTransformer_create(t *testing.T) {
g := Graph{Path: RootModulePath}
g.Add(&graphNodeDestroyerTest{AddrString: "test.A"})
g.Add(&graphNodeDestroyerTest{AddrString: "test.B"})
g.Add(&graphNodeCreatorTest{AddrString: "test.A"})
tf := &DestroyEdgeTransformer{
Module: testModule(t, "transform-destroy-edge-basic"),
}
if err := tf.Transform(&g); err != nil {
t.Fatalf("err: %s", err)
}
actual := strings.TrimSpace(g.String())
expected := strings.TrimSpace(testTransformDestroyEdgeCreatorStr)
if actual != expected {
t.Fatalf("bad:\n\n%s", actual)
}
}
func TestDestroyEdgeTransformer_multi(t *testing.T) {
g := Graph{Path: RootModulePath}
g.Add(&graphNodeDestroyerTest{AddrString: "test.A"})
@ -42,11 +61,27 @@ func TestDestroyEdgeTransformer_multi(t *testing.T) {
}
}
type graphNodeDestroyerTest struct {
type graphNodeCreatorTest struct {
AddrString string
}
func (n *graphNodeDestroyerTest) Name() string { return n.DestroyAddr().String() + " (destroy)" }
func (n *graphNodeCreatorTest) Name() string { return n.CreateAddr().String() }
func (n *graphNodeCreatorTest) CreateAddr() *ResourceAddress {
addr, err := ParseResourceAddress(n.AddrString)
if err != nil {
panic(err)
}
return addr
}
type graphNodeDestroyerTest struct {
AddrString string
CBD bool
}
func (n *graphNodeDestroyerTest) Name() string { return n.DestroyAddr().String() + " (destroy)" }
func (n *graphNodeDestroyerTest) CreateBeforeDestroy() bool { return n.CBD }
func (n *graphNodeDestroyerTest) DestroyAddr() *ResourceAddress {
addr, err := ParseResourceAddress(n.AddrString)
if err != nil {
@ -62,6 +97,14 @@ test.A (destroy)
test.B (destroy)
`
const testTransformDestroyEdgeCreatorStr = `
test.A
test.A (destroy)
test.A (destroy)
test.B (destroy)
test.B (destroy)
`
const testTransformDestroyEdgeMultiStr = `
test.A (destroy)
test.B (destroy)