check detailed provider for destroy edge cycles

When we checked for cycles with destroy edges around providers, it was
only for providers of a different type, but one can do the same thing
with the same provider under different local aliases. Check to see if
the provider also contains an alias, or is defined absolutely in some
other way. The absolute accuracy here isn't critical, since in most
cases these edges are not required for correct results, but finding a
correct and consistent method for determining when these edges are
needed is going to take more research.

There was also an oversight fixed here where the basic
creator->destroyer edges were added _after_ the cycle checks, limiting
their utility. The ordering of the additions was swapped to make sure
all cycles are noticed.
This commit is contained in:
James Bardin 2022-09-30 09:26:38 -04:00
parent 162b7274fa
commit 036fb9c1bf

View File

@ -72,24 +72,41 @@ func (t *DestroyEdgeTransformer) tryInterProviderDestroyEdge(g *Graph, from, to
e := dag.BasicEdge(from, to) e := dag.BasicEdge(from, to)
g.Connect(e) g.Connect(e)
// getComparableProvider inspects the node to try and get the most precise
// description of the provider being used to help determine if 2 nodes are
// from the same provider instance.
getComparableProvider := func(pc GraphNodeProviderConsumer) string {
ps := pc.Provider().String()
// we don't care about `exact` here, since we're only looking for any
// clue that the providers may differ.
p, _ := pc.ProvidedBy()
switch p := p.(type) {
case addrs.AbsProviderConfig:
ps = p.String()
case addrs.LocalProviderConfig:
ps = p.String()
}
return ps
}
pc, ok := from.(GraphNodeProviderConsumer) pc, ok := from.(GraphNodeProviderConsumer)
if !ok { if !ok {
return return
} }
fromProvider := pc.Provider() fromProvider := getComparableProvider(pc)
pc, ok = to.(GraphNodeProviderConsumer) pc, ok = to.(GraphNodeProviderConsumer)
if !ok { if !ok {
return return
} }
toProvider := pc.Provider() toProvider := getComparableProvider(pc)
sameProvider := fromProvider.Equals(toProvider)
// Check for cycles, and back out the edge if there are any. // Check for cycles, and back out the edge if there are any.
// The cycles we are looking for only appears between providers, so don't // The cycles we are looking for only appears between providers, so don't
// waste time checking for cycles if both nodes use the same provider. // waste time checking for cycles if both nodes use the same provider.
if !sameProvider && len(g.Cycles()) > 0 { if fromProvider != toProvider && len(g.Cycles()) > 0 {
log.Printf("[DEBUG] DestroyEdgeTransformer: skipping inter-provider edge %s->%s which creates a cycle", log.Printf("[DEBUG] DestroyEdgeTransformer: skipping inter-provider edge %s->%s which creates a cycle",
dag.VertexName(from), dag.VertexName(to)) dag.VertexName(from), dag.VertexName(to))
g.RemoveEdge(e) g.RemoveEdge(e)
@ -138,36 +155,29 @@ func (t *DestroyEdgeTransformer) Transform(g *Graph) error {
return nil return nil
} }
// Connect destroy dependencies as stored in the state // Go through and connect creators to destroyers. Going along with
for _, ds := range destroyers { // our example, this makes: A_d => A
for _, des := range ds { for _, v := range g.Vertices() {
ri, ok := des.(GraphNodeResourceInstance) cn, ok := v.(GraphNodeCreator)
if !ok { if !ok {
continue continue
} }
for _, resAddr := range ri.StateDependencies() { addr := cn.CreateAddr()
for _, desDep := range destroyersByResource[resAddr.String()] { if addr == nil {
if !graphNodesAreResourceInstancesInDifferentInstancesOfSameModule(desDep, des) { continue
log.Printf("[TRACE] DestroyEdgeTransformer: %s has stored dependency of %s\n", dag.VertexName(desDep), dag.VertexName(des)) }
t.tryInterProviderDestroyEdge(g, desDep, des)
} else {
log.Printf("[TRACE] DestroyEdgeTransformer: skipping %s => %s inter-module-instance dependency\n", dag.VertexName(desDep), dag.VertexName(des))
}
}
// We can have some create or update nodes which were for _, d := range destroyers[addr.String()] {
// dependents of the destroy node. If they have no destroyer // For illustrating our example
// themselves, make the connection directly from the creator. a_d := d.(dag.Vertex)
for _, createDep := range creators[resAddr.String()] { a := v
if !graphNodesAreResourceInstancesInDifferentInstancesOfSameModule(createDep, des) {
log.Printf("[DEBUG] DestroyEdgeTransformer: %s has stored dependency of %s\n", dag.VertexName(createDep), dag.VertexName(des)) log.Printf(
t.tryInterProviderDestroyEdge(g, createDep, des) "[TRACE] DestroyEdgeTransformer: connecting creator %q with destroyer %q",
} else { dag.VertexName(a), dag.VertexName(a_d))
log.Printf("[TRACE] DestroyEdgeTransformer: skipping %s => %s inter-module-instance dependency\n", dag.VertexName(createDep), dag.VertexName(des))
} g.Connect(dag.BasicEdge(a, a_d))
}
}
} }
} }
@ -192,29 +202,36 @@ func (t *DestroyEdgeTransformer) Transform(g *Graph) error {
} }
} }
// Go through and connect creators to destroyers. Going along with // Connect destroy dependencies as stored in the state
// our example, this makes: A_d => A for _, ds := range destroyers {
for _, v := range g.Vertices() { for _, des := range ds {
cn, ok := v.(GraphNodeCreator) ri, ok := des.(GraphNodeResourceInstance)
if !ok { if !ok {
continue continue
} }
addr := cn.CreateAddr() for _, resAddr := range ri.StateDependencies() {
if addr == nil { for _, desDep := range destroyersByResource[resAddr.String()] {
continue if !graphNodesAreResourceInstancesInDifferentInstancesOfSameModule(desDep, des) {
} log.Printf("[TRACE] DestroyEdgeTransformer: %s has stored dependency of %s\n", dag.VertexName(desDep), dag.VertexName(des))
t.tryInterProviderDestroyEdge(g, desDep, des)
} else {
log.Printf("[TRACE] DestroyEdgeTransformer: skipping %s => %s inter-module-instance dependency\n", dag.VertexName(desDep), dag.VertexName(des))
}
}
for _, d := range destroyers[addr.String()] { // We can have some create or update nodes which were
// For illustrating our example // dependents of the destroy node. If they have no destroyer
a_d := d.(dag.Vertex) // themselves, make the connection directly from the creator.
a := v for _, createDep := range creators[resAddr.String()] {
if !graphNodesAreResourceInstancesInDifferentInstancesOfSameModule(createDep, des) {
log.Printf( log.Printf("[DEBUG] DestroyEdgeTransformer2: %s has stored dependency of %s\n", dag.VertexName(createDep), dag.VertexName(des))
"[TRACE] DestroyEdgeTransformer: connecting creator %q with destroyer %q", t.tryInterProviderDestroyEdge(g, createDep, des)
dag.VertexName(a), dag.VertexName(a_d)) } else {
log.Printf("[TRACE] DestroyEdgeTransformer2: skipping %s => %s inter-module-instance dependency\n", dag.VertexName(createDep), dag.VertexName(des))
g.Connect(dag.BasicEdge(a, a_d)) }
}
}
} }
} }