package envoyx type ( // DepGraph provides a collection of optionally connected subgraphs // // Each subgraph is dedicated for a specific node scope. // A subgraph with scoped nodes may be connected to the subgraph with no // defined scope. DepGraph struct { graphs []*depSubgraph } // depSubgraph provides a bi-directional dependency graph depSubgraph struct { scope Scope nodes []*depNode nodeMap map[*Node]*depNode parentSubgraphs map[*depSubgraph]bool childSubgraphs map[*depSubgraph]bool } // depNode provides a wrapper around the resource for eas of use within the dep. graph depNode struct { Node *Node parents map[*depNode]bool children map[*depNode]bool // Index to keep track of missing references at build time missingReferences map[string]Ref } ) // BuildDepGraph constructs a dependency graph from the provided nodes // // We firstly group the nodes by scope, then build a subgraph for each scope, // and lastly merge the subgraphs into a single graph. func BuildDepGraph(nn ...*Node) (out *DepGraph) { scopes := scopeNodes(nn...) aux := make([]*depSubgraph, 0, len(scopes)) for _, ss := range scopes { aux = append(aux, buildDepSubgraph(ss)) } return buildDepGraph(aux...) } // scopeNodes groups the nodes by the scope // // The function returns a slice of NodeSet where each NodeSet only contains // nodes of the same scope (or no scope if none defined). func scopeNodes(nn ...*Node) (out []NodeSet) { type ( // Defining a little wrapper around the NodeSet so we can use pointers scopeWrap struct { nn NodeSet } ) var ( // resource type -> identifier -> wrap scopes = make(map[string]map[string]*scopeWrap) // Holds the nodes with no defined scope empty NodeSet ) // Bucket nodes into scopes // // Use maps to make the process as efficient as possible. for _, n := range nn { // Empty scopes are a special case if n.Scope.IsEmpty() { empty = append(empty, n) continue } // New resource type if _, ok := scopes[n.Scope.ResourceType]; !ok { scopes[n.Scope.ResourceType] = make(map[string]*scopeWrap, 4) } // Check if the identifiers of the current node scope exist in the index. // If they do, update the wrap struct by adding this node, then register // the same wrap struct to all other identifiers -- this allows for some // recovery in case some resource uses a subset of one but not the other. hasIdent := false firstIdent := "" for _, i := range n.Scope.Identifiers.Slice { hasIdent = hasIdent || scopes[n.Scope.ResourceType][i] != nil if hasIdent { firstIdent = i break } } if !hasIdent { // Not registered yet, create a new wrap for all of the identifiers w := &scopeWrap{nn: append(make(NodeSet, 0, 10), n)} for _, i := range n.Scope.Identifiers.Slice { scopes[n.Scope.ResourceType][i] = w } } else { // Already registered; update it and add missing identifiers w := scopes[n.Scope.ResourceType][firstIdent] w.nn = append(w.nn, n) for _, i := range n.Scope.Identifiers.Slice { scopes[n.Scope.ResourceType][i] = w } } } // Unpack the map into a slice of NodeSet the return expects // // @note since all identifiers are registered, the wraps would appear // duplicated so we need to filter them a bit out = make([]NodeSet, 0, 10) if len(empty) > 0 { out = append(out, empty) } seen := make(map[*scopeWrap]bool) for _, s := range scopes { for _, ss := range s { if seen[ss] { continue } seen[ss] = true out = append(out, ss.nn) } } return out } // buildDepSubgraph constructs a dep. subgraph from the provided nodes // // The function returns a bidirectional graph of the nodes where the parent // represents the dependency of the current resource (a namespace would be // a parent of a module). // // The build process indexes some data for optimal operations down the line. func buildDepSubgraph(nn NodeSet) (out *depSubgraph) { out = &depSubgraph{ scope: nn[0].Scope, nodes: make([]*depNode, len(nn)), nodeMap: make(map[*Node]*depNode, len(nn)), parentSubgraphs: make(map[*depSubgraph]bool), childSubgraphs: make(map[*depSubgraph]bool), } byIdentifier := make(map[string]map[string]*depNode, 8) byNode := make(map[*Node]*depNode, len(nn)) // 1. index all of the nodes in a map so we can trivially connect them later for i, _n := range nn { n := _n // Function blindly trusts it will be called with the correct data. // @todo consider implementing this check but make sure all of the decoders // correctly set the scopes. // if !n.Scope.Equals(nn[0].Scope) { // panic("invalid state: subgraphs can only be constructed with nodes from the same scope") // } aux := &depNode{ Node: n, // Keep track of missing references so we can figure them out optimally missingReferences: make(map[string]Ref), parents: make(map[*depNode]bool), children: make(map[*depNode]bool), } for field, ref := range n.References { aux.missingReferences[field] = ref } byNode[n] = aux out.nodes[i] = aux out.nodeMap[n] = aux if _, ok := byIdentifier[n.ResourceType]; !ok { byIdentifier[n.ResourceType] = make(map[string]*depNode, 8) } for _, i := range n.Identifiers.Slice { byIdentifier[n.ResourceType][i] = byNode[n] } } // 2. link up the node with it's dependencies for _, _n := range out.nodes { n := _n for field, ref := range n.Node.References { resource := ref.ResourceType found := false for _, i := range ref.Identifiers.Slice { ch, ok := byIdentifier[resource][i] found = found || ok if ok { delete(n.missingReferences, field) n.parents[ch] = true ch.children[n] = true break } } } } return } // buildDepGraph constructs a dependency graph from the given set of subgraphs // // The subgraphs can be connected in case a scoped node would reference a // unscoped node (unscoped nodes can not reference scoped nodes, nor can nodes // from different scopes -- unneeded and removes some complexity). func buildDepGraph(gg ...*depSubgraph) (out *DepGraph) { out = &DepGraph{ graphs: make([]*depSubgraph, 0, len(gg)), } // Get the unscoped graph // // For now, we can only xref to unscoped graphs. // This might need to be generalized. unscopedG := unscopedSubgraph(gg...) // Iterate all graphs and try to resolve unresolved deps for _, g := range gg { out.graphs = append(out.graphs, g) if unscopedG == nil { continue } // Get nodes with missing references missingNodes := g.nodesWithMissingRefs() if len(missingNodes) == 0 { continue } // Try to resolve missing refs using the unscoped graph for _, _mn := range missingNodes { mn := _mn for field, ref := range mn.missingReferences { n := depNodeForRef(ref, unscopedG.nodes...) if n == nil { continue } mn.parents[n] = true n.children[mn] = true delete(mn.missingReferences, field) if len(mn.missingReferences) == 0 { mn.missingReferences = nil } } } // xref the subgraphs g.parentSubgraphs[unscopedG] = true unscopedG.childSubgraphs[g] = true } return } // Graph traversal functions // Roots returns all nodes which are considered as root resources based on the current state // // For the most part, these are all resources with no parent resources. // If all resources define parents, then some home brew logic is ran func (g DepGraph) Roots() (out NodeSet) { for _, sg := range g.graphs { out = append(out, sg.Roots()...) } return } // ParentForRef returns a parent node of n which matches ref (nil if none) func (g DepGraph) ParentForRef(n *Node, ref Ref) (out *Node) { for _, sg := range g.graphs { out = sg.ParentForRef(n, ref) if out != nil { return } } return } // ParentForRT returns a set of parent nodes matching the resource type func (g DepGraph) ParentForRT(n *Node, rt string) (out NodeSet) { for _, sg := range g.graphs { out = sg.ParentForRT(n, rt) if out != nil { return } } return } // ChildrenForResourceType returns child nodes of n which match the resource type func (g DepGraph) ChildrenForResourceType(n *Node, rt string) (out NodeSet) { for _, sg := range g.graphs { out = sg.ChildrenForResourceType(n, rt) if out != nil { return } } return } func (g DepGraph) NodeForRef(ref Ref) (out *Node) { aux := make([]*Node, 0, 10) for _, sg := range g.graphs { for _, n := range sg.nodes { aux = append(aux, n.Node) } } return NodeForRef(ref, aux...) } // Children returns all child nodes of n func (g DepGraph) Children(n *Node) (out NodeSet) { for _, sg := range g.graphs { out = sg.Children(n) if out != nil { return } } return } // MissingRegs returns a slice of all refs that are requested but not found in the graph func (g DepGraph) MissingRefs() (out []map[string]Ref) { for _, sg := range g.graphs { for _, n := range sg.nodes { if len(n.missingReferences) == 0 { continue } out = append(out, n.missingReferences) } } return } func (g DepGraph) allNodes() (out []*depNode) { for _, sg := range g.graphs { out = append(out, sg.nodes...) } return } // Roots returns all nodes which are considered as root resources based on the current state // // For the most part, these are all resources with no parent resources. // If all resources define parents, then some home brew logic is ran // // @todo when we add more resources, we might need to expand this; for now // it should work just fine. func (g depSubgraph) Roots() (out NodeSet) { for _, n := range g.nodes { if needyResources[n.Node.ResourceType] { continue } out = append(out, n.Node) } if len(out) != 0 { return } for _, n := range g.nodes { if superNeedyResources[n.Node.ResourceType] { continue } out = append(out, n.Node) } return } // ParentForRef returns a parent node of n which matches ref (nil if none) func (g depSubgraph) ParentForRef(n *Node, ref Ref) *Node { return NodeForRef(ref, g.parent(n)...) } // ParentForRT returns a set of parent nodes matching the resource type func (g depSubgraph) ParentForRT(n *Node, rt string) NodeSet { return NodesForResourceType(rt, g.parent(n)...) } // ChildrenForResourceType returns child nodes of n which match the resource type func (g depSubgraph) ChildrenForResourceType(n *Node, rt string) (out NodeSet) { out = g.Children(n) out = NodesForResourceType(rt, out...) return } // Children returns all child nodes of n func (g depSubgraph) Children(n *Node) (out NodeSet) { aux, ok := g.nodeMap[n] if !ok { return } for n := range aux.children { out = append(out, n.Node) } return } func (g depSubgraph) parent(n *Node) (out NodeSet) { aux, ok := g.nodeMap[n] if !ok { return } for n := range aux.parents { out = append(out, n.Node) } return } // Utility functions func (g *depSubgraph) nodesWithMissingRefs() (out []*depNode) { out = make([]*depNode, 0, 3) for _, n := range g.nodes { if len(n.missingReferences) > 0 { out = append(out, n) } } return } func unscopedSubgraph(gg ...*depSubgraph) *depSubgraph { for _, g := range gg { if g.scope.IsEmpty() { return g } } return nil } // depNodeForRef returns a node which matches the ref (nil if none) func depNodeForRef(ref Ref, nn ...*depNode) (out *depNode) { for _, n := range nn { if n.Node.ResourceType != ref.ResourceType { continue } if n.Node.Identifiers.HasIntersection(ref.Identifiers) { return n } } return } // depNodesByResourceType returns a map where key is resource type, value slice of nodes func depNodesByResourceType(nn ...*depNode) (out map[string][]*depNode) { out = make(map[string][]*depNode, 4) for _, n := range nn { out[n.Node.ResourceType] = append(out[n.Node.ResourceType], n) } return } // unpackDepNodes extracts envoy Nodes from the dep. nodes // // The function does no validation nor filtering for nil values. func unpackDepNodes(nn ...*depNode) (out NodeSet) { out = make(NodeSet, 0, len(nn)) for _, n := range nn { out = append(out, n.Node) } return out }