From 7baf64f806a987fa403d75fa98f94fb2e2925daf Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 21 Sep 2016 10:55:07 -0700 Subject: [PATCH] terraform: starting CBD, destroy edge for the destroy relationship --- terraform/edge_destroy.go | 17 ++++++ terraform/node_resource_apply.go | 5 ++ terraform/transform_destroy_cbd.go | 67 ++++++++++++++++++++++++ terraform/transform_destroy_cbd_test.go | 40 ++++++++++++++ terraform/transform_destroy_edge.go | 34 ++++++++++++ terraform/transform_destroy_edge_test.go | 47 ++++++++++++++++- 6 files changed, 208 insertions(+), 2 deletions(-) create mode 100644 terraform/edge_destroy.go create mode 100644 terraform/transform_destroy_cbd.go create mode 100644 terraform/transform_destroy_cbd_test.go diff --git a/terraform/edge_destroy.go b/terraform/edge_destroy.go new file mode 100644 index 000000000..bc9d638aa --- /dev/null +++ b/terraform/edge_destroy.go @@ -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 } diff --git a/terraform/node_resource_apply.go b/terraform/node_resource_apply.go index b3dc4bd53..b5676bcbe 100644 --- a/terraform/node_resource_apply.go +++ b/terraform/node_resource_apply.go @@ -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 diff --git a/terraform/transform_destroy_cbd.go b/terraform/transform_destroy_cbd.go new file mode 100644 index 000000000..b398920bc --- /dev/null +++ b/terraform/transform_destroy_cbd.go @@ -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 +} diff --git a/terraform/transform_destroy_cbd_test.go b/terraform/transform_destroy_cbd_test.go new file mode 100644 index 000000000..7df75dc3d --- /dev/null +++ b/terraform/transform_destroy_cbd_test.go @@ -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) +` diff --git a/terraform/transform_destroy_edge.go b/terraform/transform_destroy_edge.go index 2b863bcc5..462258df0 100644 --- a/terraform/transform_destroy_edge.go +++ b/terraform/transform_destroy_edge.go @@ -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 diff --git a/terraform/transform_destroy_edge_test.go b/terraform/transform_destroy_edge_test.go index 5d124bfc8..e2c1a8a37 100644 --- a/terraform/transform_destroy_edge_test.go +++ b/terraform/transform_destroy_edge_test.go @@ -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)