296 lines
5.4 KiB
Go
296 lines
5.4 KiB
Go
package envoy
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
|
|
"github.com/cortezaproject/corteza-server/pkg/envoy/resource"
|
|
)
|
|
|
|
type (
|
|
nodeMap map[*node]bool
|
|
|
|
graph struct {
|
|
nn nodeSet
|
|
resIndex map[resource.Interface]*node
|
|
|
|
// Config flags
|
|
inverted bool
|
|
|
|
processed nodeMap
|
|
conflicting nodeMap
|
|
}
|
|
|
|
ResourceState struct {
|
|
Res resource.Interface
|
|
Conflicting bool
|
|
DepResources []resource.Interface
|
|
ParentResources []resource.Interface
|
|
}
|
|
)
|
|
|
|
func newGraph() *graph {
|
|
return &graph{
|
|
nn: make(nodeSet, 0, 100),
|
|
resIndex: make(map[resource.Interface]*node),
|
|
inverted: false,
|
|
processed: make(nodeMap),
|
|
conflicting: make(nodeMap),
|
|
}
|
|
}
|
|
|
|
func (g *graph) addNode(nn ...*node) {
|
|
for _, n := range nn {
|
|
g.resIndex[n.res] = n
|
|
}
|
|
g.nn = g.nn.add(nn...)
|
|
}
|
|
|
|
func (g *graph) removeNode(nn ...*node) {
|
|
g.nn = g.nn.remove(nn...)
|
|
for _, n := range nn {
|
|
g.removeChild(n)
|
|
g.removeParent(n)
|
|
}
|
|
}
|
|
|
|
func (g *graph) invert() {
|
|
g.inverted = !g.inverted
|
|
}
|
|
|
|
func (g *graph) childNodes(n *node) nodeSet {
|
|
if g.inverted {
|
|
return n.pp
|
|
}
|
|
|
|
return n.cc
|
|
}
|
|
|
|
func (g *graph) parentNodes(n *node) nodeSet {
|
|
if g.inverted {
|
|
return n.cc
|
|
}
|
|
|
|
return n.pp
|
|
}
|
|
|
|
func (g *graph) nodes() nodeSet {
|
|
return g.nn
|
|
}
|
|
|
|
func (g *graph) markProcessed(n *node) {
|
|
g.processed = g.processed.add(n)
|
|
}
|
|
func (g *graph) markConflicting(n *node) {
|
|
g.conflicting = g.conflicting.add(n)
|
|
}
|
|
|
|
func (g *graph) NextInverted(ctx context.Context) (s *ResourceState, err error) {
|
|
g.inverted = true
|
|
defer func() {
|
|
g.inverted = false
|
|
}()
|
|
|
|
return g.Next(ctx)
|
|
}
|
|
|
|
func (g *graph) Next(ctx context.Context) (s *ResourceState, err error) {
|
|
upNN := g.removeProcessed(g.nodes())
|
|
|
|
// We are done here
|
|
if len(upNN) <= 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
var nx *node
|
|
|
|
for _, upN := range upNN {
|
|
// We should not take into account conflicted parent nodes,
|
|
// as they already resolved the conflict.
|
|
if len(g.removeProcessed(g.removeConflicting(g.parentNodes(upN)))) <= 0 {
|
|
nx = upN
|
|
break
|
|
}
|
|
}
|
|
|
|
if nx != nil {
|
|
// Prepare the required context for the processing.
|
|
// Perform some basic cleanup.
|
|
es := g.prepExecState(nx, false)
|
|
g.markProcessed(nx)
|
|
|
|
return es, nil
|
|
}
|
|
|
|
// There are only conflicting nodes
|
|
// Try to get a cycle node to resolve the conflict
|
|
nx = g.findCycleNode(g.removeConflicting(upNN))
|
|
|
|
if nx == nil {
|
|
// This is basically impossible, unless I've messed up the algorithm
|
|
return nil, errors.New("could not determine non-conflicting node")
|
|
}
|
|
|
|
// Prepare the required context for the processing.
|
|
es := g.prepExecState(nx, true)
|
|
g.markConflicting(nx)
|
|
|
|
return es, nil
|
|
}
|
|
|
|
// findCycleNode returns the first graph node that caused a cycle
|
|
//
|
|
// General outline:
|
|
// * DFS from a start node(s)
|
|
// * if a child node is already in path, return that node
|
|
// * else return nil and cleanup the path until the first node with
|
|
// unprocessed child nodes
|
|
//
|
|
// @note we could complicate this further by doing cycle enumeration algorithms.
|
|
// I might do it when no one is watching :)
|
|
func (g *graph) findCycleNode(nn nodeSet) *node {
|
|
path := make(nodeMap)
|
|
processed := make(nodeMap)
|
|
|
|
for _, n := range nn {
|
|
if !processed.has(n) {
|
|
m := g.traverse(n, path, processed)
|
|
if m != nil {
|
|
return m
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (g *graph) traverse(n *node, path, processed nodeMap) *node {
|
|
processed.add(n)
|
|
|
|
// Found ourselves a cycle
|
|
if path.has(n) {
|
|
return n
|
|
}
|
|
cnn := g.removeProcessed(g.removeConflicting(g.childNodes(n)))
|
|
// Nothing else to look at
|
|
if len(cnn) == 0 {
|
|
return nil
|
|
}
|
|
|
|
path.add(n)
|
|
|
|
for _, c := range cnn {
|
|
m := g.traverse(c, path, processed)
|
|
if m != nil {
|
|
return m
|
|
}
|
|
}
|
|
|
|
path.remove(n)
|
|
return nil
|
|
}
|
|
|
|
// util
|
|
|
|
func (g *graph) removeProcessed(nn nodeSet) nodeSet {
|
|
mm := make(nodeSet, 0, len(nn))
|
|
|
|
for _, n := range nn {
|
|
if !g.processed.has(n) {
|
|
mm = mm.add(n)
|
|
}
|
|
}
|
|
|
|
return mm
|
|
}
|
|
|
|
func (g *graph) removeConflicting(nn nodeSet) nodeSet {
|
|
mm := make(nodeSet, 0, len(nn))
|
|
|
|
for _, n := range nn {
|
|
if !g.conflicting.has(n) {
|
|
mm = mm.add(n)
|
|
}
|
|
}
|
|
|
|
return mm
|
|
}
|
|
|
|
func (g *graph) addChild(n *node, mm ...*node) {
|
|
if g.inverted {
|
|
n.pp = n.pp.add(mm...)
|
|
} else {
|
|
n.cc = n.cc.add(mm...)
|
|
}
|
|
}
|
|
|
|
func (g *graph) removeChild(n *node, mm ...*node) {
|
|
if len(mm) <= 0 {
|
|
if g.inverted {
|
|
n.pp = make(nodeSet, 0, 10)
|
|
} else {
|
|
n.cc = make(nodeSet, 0, 10)
|
|
}
|
|
} else {
|
|
if g.inverted {
|
|
n.pp = n.pp.remove(mm...)
|
|
} else {
|
|
n.cc = n.cc.remove(mm...)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (g *graph) addParent(n *node, mm ...*node) {
|
|
if g.inverted {
|
|
n.cc = n.cc.add(mm...)
|
|
} else {
|
|
n.pp = n.pp.add(mm...)
|
|
}
|
|
}
|
|
|
|
func (g *graph) removeParent(n *node, mm ...*node) {
|
|
if len(mm) <= 0 {
|
|
if g.inverted {
|
|
n.cc = make(nodeSet, 0, 10)
|
|
} else {
|
|
n.pp = make(nodeSet, 0, 10)
|
|
}
|
|
} else {
|
|
if g.inverted {
|
|
n.cc = n.cc.remove(mm...)
|
|
} else {
|
|
n.pp = n.pp.remove(mm...)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (g *graph) nodeResource(nn ...*node) []resource.Interface {
|
|
rr := make([]resource.Interface, 0, len(nn))
|
|
for _, n := range nn {
|
|
rr = append(rr, n.res)
|
|
}
|
|
|
|
return rr
|
|
}
|
|
|
|
func (g *graph) prepExecState(n *node, conflicting bool) *ResourceState {
|
|
return &ResourceState{
|
|
Res: n.res,
|
|
DepResources: g.nodeResource(g.childNodes(n)...),
|
|
ParentResources: g.nodeResource(g.parentNodes(n)...),
|
|
Conflicting: conflicting,
|
|
}
|
|
}
|
|
|
|
func (nm nodeMap) add(n *node) nodeMap {
|
|
nm[n] = true
|
|
return nm
|
|
}
|
|
func (nm nodeMap) has(n *node) bool {
|
|
return nm[n]
|
|
}
|
|
func (nm nodeMap) remove(n *node) nodeMap {
|
|
delete(nm, n)
|
|
return nm
|
|
}
|