simplify CBD transformation

Start by removing the DestroyEdge type altogether. This is only used to
detect the natural edge between a resource's create and destroy nodes,
but that's not necessary for any transformations. The custom edge type
also interferes with normal graph manipulations, because you can't
delete an arbitrary edge without knowing the type, so deletion of the
edge based only on the endpoints is often done incorrectly. The dag
package itself does this incorrectly in TransitiveReduction, which
always assumes the BasicEdge type.

Now that inter-resource destroy dependencies are already connected in the
DestroyEdgeTransformer (from the stored deps in state), there's no need
to search out all dependant resources in the CBD transformation, as they
should all be connected. This makes the CBD transformation rule quite
simple: reverse any edges from create nodes.
This commit is contained in:
James Bardin 2019-11-19 17:31:38 -05:00
parent 84d1f5c688
commit b5517b53ec
3 changed files with 11 additions and 154 deletions

View File

@ -145,14 +145,12 @@ func (t *CBDEdgeTransformer) Transform(g *Graph) error {
}
// Go through and reverse any destroy edges
destroyMap := make(map[string][]dag.Vertex)
for _, v := range g.Vertices() {
dn, ok := v.(GraphNodeDestroyerCBD)
if !ok {
continue
}
dern, ok := v.(GraphNodeDestroyer)
if !ok {
if _, ok = v.(GraphNodeDestroyer); !ok {
continue
}
@ -162,158 +160,17 @@ func (t *CBDEdgeTransformer) Transform(g *Graph) error {
// Find the resource edges
for _, e := range g.EdgesTo(v) {
switch de := e.(type) {
case *DestroyEdge:
// we need to invert the destroy edge from the create node
log.Printf("[TRACE] CBDEdgeTransformer: inverting edge: %s => %s",
dag.VertexName(de.Source()), dag.VertexName(de.Target()))
src := e.Source()
// Found it! Invert.
g.RemoveEdge(de)
applyNode := de.Source()
destroyNode := de.Target()
g.Connect(&DestroyEdge{S: destroyNode, T: applyNode})
default:
// We cannot have any direct dependencies from creators when
// the node is CBD without inducing a cycle.
if _, ok := e.Source().(GraphNodeCreator); ok {
log.Printf("[TRACE] CBDEdgeTransformer: removing non DestroyEdge to CBD destroy node: %s => %s", dag.VertexName(e.Source()), dag.VertexName(e.Target()))
g.RemoveEdge(e)
}
// If source is a create node, invert the edge.
// This covers both the node's own creator, as well as reversing
// any dependants' edges.
if _, ok := src.(GraphNodeCreator); ok {
log.Printf("[TRACE] CBDEdgeTransformer: reversing edge %s -> %s", dag.VertexName(src), dag.VertexName(v))
g.RemoveEdge(e)
g.Connect(dag.BasicEdge(v, src))
}
}
// If the address has an index, we strip that. Our depMap creation
// graph doesn't expand counts so we don't currently get _exact_
// dependencies. One day when we limit dependencies more exactly
// this will have to change. We have a test case covering this
// (depNonCBDCountBoth) so it'll be caught.
addr := dern.DestroyAddr()
key := addr.ContainingResource().String()
// Add this to the list of nodes that we need to fix up
// the edges for (step 2 above in the docs).
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.
depMap, err := t.depMap(g, 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()
// If the address has an index, we strip that. Our depMap creation
// graph doesn't expand counts so we don't currently get _exact_
// dependencies. One day when we limit dependencies more exactly
// this will have to change. We have a test case covering this
// (depNonCBDCount) so it'll be caught.
key := addr.ContainingResource().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
}
func (t *CBDEdgeTransformer) depMap(g *Graph, destroyMap map[string][]dag.Vertex) (map[string][]dag.Vertex, error) {
// 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.
// Use a nested map to remove duplicate edges.
depMap := make(map[string]map[dag.Vertex]struct{})
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. Since dependencies can span modules, walk all
// descendents of the resource.
des, _ := g.Descendents(v)
for _, v := range des.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()
deps, ok := depMap[key]
if !ok {
deps = make(map[dag.Vertex]struct{})
}
for _, d := range dns {
deps[d] = struct{}{}
}
depMap[key] = deps
}
}
result := map[string][]dag.Vertex{}
for k, m := range depMap {
for v := range m {
result[k] = append(result[k], v)
}
}
return result, nil
}

View File

@ -151,7 +151,7 @@ func (t *DestroyEdgeTransformer) Transform(g *Graph) error {
"[TRACE] DestroyEdgeTransformer: connecting creator %q with destroyer %q",
dag.VertexName(a), dag.VertexName(a_d))
g.Connect(&DestroyEdge{S: a, T: a_d})
g.Connect(dag.BasicEdge(a, a_d))
// Attach the destroy node to the creator
// There really shouldn't be more than one destroyer, but even if

View File

@ -205,7 +205,7 @@ func (t *DestroyValueReferenceTransformer) Transform(g *Graph) error {
log.Printf("[TRACE] output dep: %s", dag.VertexName(target))
g.RemoveEdge(e)
g.Connect(&DestroyEdge{S: target, T: v})
g.Connect(dag.BasicEdge(target, v))
}
}