Creators cannot depend directly on CBD dest nodes

Since a create node cannot both depend on its destroy node AND be
CreateBeforeDestroy, the same goes for its dependencies. While we do
connect resources with dependency destroy nodes so that updates are
ordered correctly, this ordering does not make sense in the
CreateBeforeDestroy case.

If resource node is CreateBeforeDestroy, we need to remove any direct
dependencies from it to destroy nodes to prevent cycles. Since we don't
know for certain if a crate node is going to be CreateBeforeDestroy at
the time the edge is added in the graph, we add it unconditionally and
prune it out later on. The pruning happens during the CBD transformer
when the CBD destroy node reverses it's own destroy edge.  The reason
this works for detecting the original edge, is that dependencies of CBD
resources are forced to be CBD themselves. This does have a false
positive where the case of the original node is NOT CBD, but this can be
taken care of later when we gather enough information in the graph to
prevent the connection in the first place.
This commit is contained in:
James Bardin 2019-11-17 09:56:44 -05:00
parent 71f4526ae5
commit 682083cdd1
2 changed files with 30 additions and 16 deletions

View File

@ -160,23 +160,27 @@ func (t *CBDEdgeTransformer) Transform(g *Graph) error {
continue
}
// Find the destroy edge. There should only be one.
// Find the resource edges
for _, e := range g.EdgesTo(v) {
// Not a destroy edge, ignore it
de, ok := e.(*DestroyEdge)
if !ok {
continue
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()))
// 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)
}
}
log.Printf("[TRACE] CBDEdgeTransformer: inverting edge: %s => %s",
dag.VertexName(de.Source()), dag.VertexName(de.Target()))
// Found it! Invert.
g.RemoveEdge(de)
applyNode := de.Source()
destroyNode := de.Target()
g.Connect(&DestroyEdge{S: destroyNode, T: applyNode})
break
}
// If the address has an index, we strip that. Our depMap creation
@ -289,7 +293,11 @@ func (t *CBDEdgeTransformer) depMap(g *Graph, destroyMap map[string][]dag.Vertex
// Keep track of the destroy nodes that this address
// needs to depend on.
key := rn.ResourceAddr().String()
depMap[key] = append(depMap[key], dns...)
// we only need to record the same dns once
if _, ok := depMap[key]; !ok {
depMap[key] = append(depMap[key], dns...)
}
}
}

View File

@ -122,6 +122,12 @@ func (t *DestroyEdgeTransformer) Transform(g *Graph) error {
for _, resAddr := range ri.StateDependencies() {
for _, desDep := range destroyersByResource[resAddr.String()] {
// TODO: don't connect this if c is CreateBeforeDestroy.
// This will require getting the actual change action at
// this point, since the lifecycle may have been forced
// by a dependent. This should prevent needing to prune
// the edge back out in CBDEdgeTransformer, and allow
// non-CBD nodes to depend on CBD destroys directly.
log.Printf("[TRACE] DestroyEdgeTransformer: %s has stored dependency of %s\n", dag.VertexName(c), dag.VertexName(desDep))
g.Connect(dag.BasicEdge(c, desDep))