terraform: CBD makes the proper edge connections for dependent resources
This commit is contained in:
parent
3d4937b784
commit
4aa84a2071
|
@ -9,6 +9,13 @@ import (
|
||||||
// abstract resource to a concrete one of some type.
|
// abstract resource to a concrete one of some type.
|
||||||
type ConcreteResourceNodeFunc func(*NodeAbstractResource) dag.Vertex
|
type ConcreteResourceNodeFunc func(*NodeAbstractResource) dag.Vertex
|
||||||
|
|
||||||
|
// GraphNodeResource is implemented by any nodes that represent a resource.
|
||||||
|
// The type of operation cannot be assumed, only that this node represents
|
||||||
|
// the given resource.
|
||||||
|
type GraphNodeResource interface {
|
||||||
|
ResourceAddr() *ResourceAddress
|
||||||
|
}
|
||||||
|
|
||||||
// NodeAbstractResource represents a resource that has no associated
|
// NodeAbstractResource represents a resource that has no associated
|
||||||
// operations. It registers all the interfaces for a resource that common
|
// operations. It registers all the interfaces for a resource that common
|
||||||
// across multiple operation types.
|
// across multiple operation types.
|
||||||
|
@ -99,7 +106,7 @@ func (n *NodeAbstractResource) ProvisionedBy() []string {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
// GraphNodeAttachResourceState
|
// GraphNodeResource, GraphNodeAttachResourceState
|
||||||
func (n *NodeAbstractResource) ResourceAddr() *ResourceAddress {
|
func (n *NodeAbstractResource) ResourceAddr() *ResourceAddress {
|
||||||
return n.Addr
|
return n.Addr
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,10 @@ import (
|
||||||
// to the graph. The module used to configure this transformer must be
|
// to the graph. The module used to configure this transformer must be
|
||||||
// the root module.
|
// the root module.
|
||||||
//
|
//
|
||||||
// In relation to ConfigTransformer: this is a newer generation config
|
// This transform adds the nodes but doesn't connect any of the references.
|
||||||
|
// The ReferenceTransformer should be used for that.
|
||||||
|
//
|
||||||
|
// NOTE: In relation to ConfigTransformer: this is a newer generation config
|
||||||
// transformer. It puts the _entire_ config into the graph (there is no
|
// transformer. It puts the _entire_ config into the graph (there is no
|
||||||
// "flattening" step as before).
|
// "flattening" step as before).
|
||||||
type FlatConfigTransformer struct {
|
type FlatConfigTransformer struct {
|
||||||
|
@ -59,7 +62,12 @@ func (t *FlatConfigTransformer) transform(g *Graph, m *module.Tree) error {
|
||||||
}
|
}
|
||||||
addr.Path = m.Path()
|
addr.Path = m.Path()
|
||||||
|
|
||||||
abstract := &NodeAbstractResource{Addr: addr}
|
// Build the abstract resource. We have the config already so
|
||||||
|
// we'll just pre-populate that.
|
||||||
|
abstract := &NodeAbstractResource{
|
||||||
|
Addr: addr,
|
||||||
|
Config: r,
|
||||||
|
}
|
||||||
var node dag.Vertex = abstract
|
var node dag.Vertex = abstract
|
||||||
if f := t.Concrete; f != nil {
|
if f := t.Concrete; f != nil {
|
||||||
node = f(abstract)
|
node = f(abstract)
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/config/module"
|
"github.com/hashicorp/terraform/config/module"
|
||||||
|
"github.com/hashicorp/terraform/dag"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GraphNodeDestroyerCBD must be implemented by nodes that might be
|
// GraphNodeDestroyerCBD must be implemented by nodes that might be
|
||||||
|
@ -39,6 +40,7 @@ func (t *CBDEdgeTransformer) Transform(g *Graph) error {
|
||||||
log.Printf("[TRACE] CBDEdgeTransformer: Beginning CBD transformation...")
|
log.Printf("[TRACE] CBDEdgeTransformer: Beginning CBD transformation...")
|
||||||
|
|
||||||
// Go through and reverse any destroy edges
|
// Go through and reverse any destroy edges
|
||||||
|
destroyMap := make(map[string][]dag.Vertex)
|
||||||
for _, v := range g.Vertices() {
|
for _, v := range g.Vertices() {
|
||||||
dn, ok := v.(GraphNodeDestroyerCBD)
|
dn, ok := v.(GraphNodeDestroyerCBD)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -57,11 +59,132 @@ func (t *CBDEdgeTransformer) Transform(g *Graph) error {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Printf("[TRACE] CBDEdgeTransformer: inverting edge: %s => %s",
|
||||||
|
dag.VertexName(de.Source()), dag.VertexName(de.Target()))
|
||||||
|
|
||||||
// Found it! Invert.
|
// Found it! Invert.
|
||||||
g.RemoveEdge(de)
|
g.RemoveEdge(de)
|
||||||
g.Connect(&DestroyEdge{S: de.Target(), T: de.Source()})
|
g.Connect(&DestroyEdge{S: de.Target(), T: de.Source()})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add this to the list of nodes that we need to fix up
|
||||||
|
// the edges for (step 2 above in the docs).
|
||||||
|
key := dn.DestroyAddr().String()
|
||||||
|
destroyMap[key] = append(destroyMap[key], v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have no CBD nodes, then our work here is done
|
||||||
|
if len(destroyMap) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// We have CBD nodes. We now have to move on to the much more difficult
|
||||||
|
// task of connecting dependencies of the creation side of the destroy
|
||||||
|
// to the destruction node. The easiest way to explain this is an example:
|
||||||
|
//
|
||||||
|
// Given a pre-destroy dependence of: A => B
|
||||||
|
// And A has CBD set.
|
||||||
|
//
|
||||||
|
// The resulting graph should be: A => B => A_d
|
||||||
|
//
|
||||||
|
// They key here is that B happens before A is destroyed. This is to
|
||||||
|
// facilitate the primary purpose for CBD: making sure that downstreams
|
||||||
|
// are properly updated to avoid downtime before the resource is destroyed.
|
||||||
|
//
|
||||||
|
// We can't trust that the resource being destroyed or anything that
|
||||||
|
// depends on it is actually in our current graph so we make a new
|
||||||
|
// graph in order to determine those dependencies and add them in.
|
||||||
|
log.Printf("[TRACE] CBDEdgeTransformer: building graph to find dependencies...")
|
||||||
|
depMap, err := t.depMap(destroyMap)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// We now have the mapping of resource addresses to the destroy
|
||||||
|
// nodes they need to depend on. We now go through our own vertices to
|
||||||
|
// find any matching these addresses and make the connection.
|
||||||
|
for _, v := range g.Vertices() {
|
||||||
|
// We're looking for creators
|
||||||
|
rn, ok := v.(GraphNodeCreator)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the address
|
||||||
|
addr := rn.CreateAddr()
|
||||||
|
key := addr.String()
|
||||||
|
|
||||||
|
// If there is nothing this resource should depend on, ignore it
|
||||||
|
dns, ok := depMap[key]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// We have nodes! Make the connection
|
||||||
|
for _, dn := range dns {
|
||||||
|
log.Printf("[TRACE] CBDEdgeTransformer: destroy depends on dependence: %s => %s",
|
||||||
|
dag.VertexName(dn), dag.VertexName(v))
|
||||||
|
g.Connect(dag.BasicEdge(dn, v))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *CBDEdgeTransformer) depMap(
|
||||||
|
destroyMap map[string][]dag.Vertex) (map[string][]dag.Vertex, error) {
|
||||||
|
// Build the graph of our config, this ensures that all resources
|
||||||
|
// are present in the graph.
|
||||||
|
g, err := (&BasicGraphBuilder{
|
||||||
|
Steps: []GraphTransformer{
|
||||||
|
&FlatConfigTransformer{Module: t.Module},
|
||||||
|
&AttachResourceConfigTransformer{Module: t.Module},
|
||||||
|
&AttachStateTransformer{State: t.State},
|
||||||
|
&ReferenceTransformer{},
|
||||||
|
},
|
||||||
|
}).Build(nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Using this graph, build the list of destroy nodes that each resource
|
||||||
|
// address should depend on. For example, when we find B, we map the
|
||||||
|
// address of B to A_d in the "depMap" variable below.
|
||||||
|
depMap := make(map[string][]dag.Vertex)
|
||||||
|
for _, v := range g.Vertices() {
|
||||||
|
// We're looking for resources.
|
||||||
|
rn, ok := v.(GraphNodeResource)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the address
|
||||||
|
addr := rn.ResourceAddr()
|
||||||
|
key := addr.String()
|
||||||
|
|
||||||
|
// Get the destroy nodes that are destroying this resource.
|
||||||
|
// If there aren't any, then we don't need to worry about
|
||||||
|
// any connections.
|
||||||
|
dns, ok := destroyMap[key]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the nodes that depend on this on. In the example above:
|
||||||
|
// finding B in A => B.
|
||||||
|
for _, v := range g.UpEdges(v).List() {
|
||||||
|
// We're looking for resources.
|
||||||
|
rn, ok := v.(GraphNodeResource)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep track of the destroy nodes that this address
|
||||||
|
// needs to depend on.
|
||||||
|
key := rn.ResourceAddr().String()
|
||||||
|
depMap[key] = append(depMap[key], dns...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return depMap, nil
|
||||||
|
}
|
||||||
|
|
|
@ -11,9 +11,11 @@ func TestCBDEdgeTransformer(t *testing.T) {
|
||||||
g.Add(&graphNodeCreatorTest{AddrString: "test.B"})
|
g.Add(&graphNodeCreatorTest{AddrString: "test.B"})
|
||||||
g.Add(&graphNodeDestroyerTest{AddrString: "test.A", CBD: true})
|
g.Add(&graphNodeDestroyerTest{AddrString: "test.A", CBD: true})
|
||||||
|
|
||||||
|
module := testModule(t, "transform-destroy-edge-basic")
|
||||||
|
|
||||||
{
|
{
|
||||||
tf := &DestroyEdgeTransformer{
|
tf := &DestroyEdgeTransformer{
|
||||||
Module: testModule(t, "transform-destroy-edge-basic"),
|
Module: module,
|
||||||
}
|
}
|
||||||
if err := tf.Transform(&g); err != nil {
|
if err := tf.Transform(&g); err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
|
@ -21,7 +23,7 @@ func TestCBDEdgeTransformer(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
tf := &CBDEdgeTransformer{}
|
tf := &CBDEdgeTransformer{Module: module}
|
||||||
if err := tf.Transform(&g); err != nil {
|
if err := tf.Transform(&g); err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue