pgraph, resources: Major refactor to remove pgraph to resource dep
This is the mechanical port of the remaining bits. Next to clean it up a bit.
This commit is contained in:
26
lib/main.go
26
lib/main.go
@@ -426,7 +426,7 @@ func (obj *Main) Run() error {
|
|||||||
// run graph vertex LOCK...
|
// run graph vertex LOCK...
|
||||||
if !first { // TODO: we can flatten this check out I think
|
if !first { // TODO: we can flatten this check out I think
|
||||||
converger.Pause() // FIXME: add sync wait?
|
converger.Pause() // FIXME: add sync wait?
|
||||||
G.Pause(false) // sync
|
resources.Pause(G, false) // sync
|
||||||
|
|
||||||
//G.UnGroup() // FIXME: implement me if needed!
|
//G.UnGroup() // FIXME: implement me if needed!
|
||||||
}
|
}
|
||||||
@@ -437,7 +437,7 @@ func (obj *Main) Run() error {
|
|||||||
log.Printf("Main: Error creating new graph: %v", err)
|
log.Printf("Main: Error creating new graph: %v", err)
|
||||||
// unpause!
|
// unpause!
|
||||||
if !first {
|
if !first {
|
||||||
G.Start(first) // sync
|
resources.Start(G, first) // sync
|
||||||
converger.Start() // after G.Start()
|
converger.Start() // after G.Start()
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
@@ -470,13 +470,13 @@ func (obj *Main) Run() error {
|
|||||||
// changes to the resources so our efficient GraphSync
|
// changes to the resources so our efficient GraphSync
|
||||||
// will be able to re-use and cmp to the old graph.
|
// will be able to re-use and cmp to the old graph.
|
||||||
log.Printf("Main: GraphSync...")
|
log.Printf("Main: GraphSync...")
|
||||||
newFullGraph, err := newGraph.GraphSync(oldGraph)
|
newFullGraph, err := resources.GraphSync(newGraph, oldGraph)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Main: Error running graph sync: %v", err)
|
log.Printf("Main: Error running graph sync: %v", err)
|
||||||
// unpause!
|
// unpause!
|
||||||
if !first {
|
if !first {
|
||||||
G.Start(first) // sync
|
resources.Start(G, first) // sync
|
||||||
converger.Start() // after G.Start()
|
converger.Start() // after Start(G)
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -484,7 +484,7 @@ func (obj *Main) Run() error {
|
|||||||
G = oldGraph.Copy() // copy to active graph
|
G = oldGraph.Copy() // copy to active graph
|
||||||
|
|
||||||
resources.AutoEdges(G) // add autoedges; modifies the graph
|
resources.AutoEdges(G) // add autoedges; modifies the graph
|
||||||
G.AutoGroup() // run autogroup; modifies the graph
|
resources.AutoGroup(G, &resources.NonReachabilityGrouper{}) // run autogroup; modifies the graph
|
||||||
// TODO: do we want to do a transitive reduction?
|
// TODO: do we want to do a transitive reduction?
|
||||||
// FIXME: run a type checker that verifies all the send->recv relationships
|
// FIXME: run a type checker that verifies all the send->recv relationships
|
||||||
|
|
||||||
@@ -493,13 +493,13 @@ func (obj *Main) Run() error {
|
|||||||
if err := prom.UpdatePgraphStartTime(); err != nil {
|
if err := prom.UpdatePgraphStartTime(); err != nil {
|
||||||
log.Printf("Main: Prometheus.UpdatePgraphStartTime() errored: %v", err)
|
log.Printf("Main: Prometheus.UpdatePgraphStartTime() errored: %v", err)
|
||||||
}
|
}
|
||||||
// G.Start(...) needs to be synchronous or wait,
|
// Start(G) needs to be synchronous or wait,
|
||||||
// because if half of the nodes are started and
|
// because if half of the nodes are started and
|
||||||
// some are not ready yet and the EtcdWatch
|
// some are not ready yet and the EtcdWatch
|
||||||
// loops, we'll cause G.Pause(...) before we
|
// loops, we'll cause Pause(G) before we
|
||||||
// even got going, thus causing nil pointer errors
|
// even got going, thus causing nil pointer errors
|
||||||
G.Start(first) // sync
|
resources.Start(G, first) // sync
|
||||||
converger.Start() // after G.Start()
|
converger.Start() // after Start(G)
|
||||||
|
|
||||||
log.Printf("Main: Graph: %v", G) // show graph
|
log.Printf("Main: Graph: %v", G) // show graph
|
||||||
if obj.Graphviz != "" {
|
if obj.Graphviz != "" {
|
||||||
@@ -590,7 +590,7 @@ func (obj *Main) Run() error {
|
|||||||
// tell inner main loop to exit
|
// tell inner main loop to exit
|
||||||
close(exitchan)
|
close(exitchan)
|
||||||
|
|
||||||
G.Exit() // tells all the children to exit, and waits for them to do so
|
resources.Exit(G) // tells all the children to exit, and waits for them to do so
|
||||||
|
|
||||||
// cleanup etcd main loop last so it can process everything first
|
// cleanup etcd main loop last so it can process everything first
|
||||||
if err := EmbdEtcd.Destroy(); err != nil { // shutdown and cleanup etcd
|
if err := EmbdEtcd.Destroy(); err != nil { // shutdown and cleanup etcd
|
||||||
@@ -619,7 +619,7 @@ func (obj *Main) Run() error {
|
|||||||
func graphMetas(g *pgraph.Graph) []*resources.MetaParams {
|
func graphMetas(g *pgraph.Graph) []*resources.MetaParams {
|
||||||
metas := []*resources.MetaParams{}
|
metas := []*resources.MetaParams{}
|
||||||
for _, v := range g.Vertices() { // loop through the vertices (resources)
|
for _, v := range g.Vertices() { // loop through the vertices (resources)
|
||||||
res := v.Res // resource
|
res := resources.VtoR(v) // resource
|
||||||
meta := res.Meta()
|
meta := res.Meta()
|
||||||
metas = append(metas, meta)
|
metas = append(metas, meta)
|
||||||
}
|
}
|
||||||
@@ -632,6 +632,6 @@ func associateData(g *pgraph.Graph, data *resources.Data) {
|
|||||||
g.SetValue("prometheus", data.Prometheus)
|
g.SetValue("prometheus", data.Prometheus)
|
||||||
|
|
||||||
for _, v := range g.Vertices() {
|
for _, v := range g.Vertices() {
|
||||||
*v.Res.Data() = *data
|
*resources.VtoR(v).Data() = *data
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,486 +0,0 @@
|
|||||||
// Mgmt
|
|
||||||
// Copyright (C) 2013-2017+ James Shubin and the project contributors
|
|
||||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Affero General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Affero General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
|
||||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package pgraph
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
// all of the following test cases are laid out with the following semantics:
|
|
||||||
// * vertices which start with the same single letter are considered "like"
|
|
||||||
// * "like" elements should be merged
|
|
||||||
// * vertices can have any integer after their single letter "family" type
|
|
||||||
// * grouped vertices should have a name with a comma separated list of names
|
|
||||||
// * edges follow the same conventions about grouping
|
|
||||||
|
|
||||||
// empty graph
|
|
||||||
func TestPgraphGrouping1(t *testing.T) {
|
|
||||||
g1, _ := NewGraph("g1") // original graph
|
|
||||||
g2, _ := NewGraph("g2") // expected result
|
|
||||||
runGraphCmp(t, g1, g2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// single vertex
|
|
||||||
func TestPgraphGrouping2(t *testing.T) {
|
|
||||||
g1, _ := NewGraph("g1") // original graph
|
|
||||||
{ // grouping to limit variable scope
|
|
||||||
a1 := NewVertex(NewNoopResTest("a1"))
|
|
||||||
g1.AddVertex(a1)
|
|
||||||
}
|
|
||||||
g2, _ := NewGraph("g2") // expected result
|
|
||||||
{
|
|
||||||
a1 := NewVertex(NewNoopResTest("a1"))
|
|
||||||
g2.AddVertex(a1)
|
|
||||||
}
|
|
||||||
runGraphCmp(t, g1, g2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// two vertices
|
|
||||||
func TestPgraphGrouping3(t *testing.T) {
|
|
||||||
g1, _ := NewGraph("g1") // original graph
|
|
||||||
{
|
|
||||||
a1 := NewVertex(NewNoopResTest("a1"))
|
|
||||||
b1 := NewVertex(NewNoopResTest("b1"))
|
|
||||||
g1.AddVertex(a1, b1)
|
|
||||||
}
|
|
||||||
g2, _ := NewGraph("g2") // expected result
|
|
||||||
{
|
|
||||||
a1 := NewVertex(NewNoopResTest("a1"))
|
|
||||||
b1 := NewVertex(NewNoopResTest("b1"))
|
|
||||||
g2.AddVertex(a1, b1)
|
|
||||||
}
|
|
||||||
runGraphCmp(t, g1, g2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// two vertices merge
|
|
||||||
func TestPgraphGrouping4(t *testing.T) {
|
|
||||||
g1, _ := NewGraph("g1") // original graph
|
|
||||||
{
|
|
||||||
a1 := NewVertex(NewNoopResTest("a1"))
|
|
||||||
a2 := NewVertex(NewNoopResTest("a2"))
|
|
||||||
g1.AddVertex(a1, a2)
|
|
||||||
}
|
|
||||||
g2, _ := NewGraph("g2") // expected result
|
|
||||||
{
|
|
||||||
a := NewVertex(NewNoopResTest("a1,a2"))
|
|
||||||
g2.AddVertex(a)
|
|
||||||
}
|
|
||||||
runGraphCmp(t, g1, g2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// three vertices merge
|
|
||||||
func TestPgraphGrouping5(t *testing.T) {
|
|
||||||
g1, _ := NewGraph("g1") // original graph
|
|
||||||
{
|
|
||||||
a1 := NewVertex(NewNoopResTest("a1"))
|
|
||||||
a2 := NewVertex(NewNoopResTest("a2"))
|
|
||||||
a3 := NewVertex(NewNoopResTest("a3"))
|
|
||||||
g1.AddVertex(a1, a2, a3)
|
|
||||||
}
|
|
||||||
g2, _ := NewGraph("g2") // expected result
|
|
||||||
{
|
|
||||||
a := NewVertex(NewNoopResTest("a1,a2,a3"))
|
|
||||||
g2.AddVertex(a)
|
|
||||||
}
|
|
||||||
runGraphCmp(t, g1, g2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// three vertices, two merge
|
|
||||||
func TestPgraphGrouping6(t *testing.T) {
|
|
||||||
g1, _ := NewGraph("g1") // original graph
|
|
||||||
{
|
|
||||||
a1 := NewVertex(NewNoopResTest("a1"))
|
|
||||||
a2 := NewVertex(NewNoopResTest("a2"))
|
|
||||||
b1 := NewVertex(NewNoopResTest("b1"))
|
|
||||||
g1.AddVertex(a1, a2, b1)
|
|
||||||
}
|
|
||||||
g2, _ := NewGraph("g2") // expected result
|
|
||||||
{
|
|
||||||
a := NewVertex(NewNoopResTest("a1,a2"))
|
|
||||||
b1 := NewVertex(NewNoopResTest("b1"))
|
|
||||||
g2.AddVertex(a, b1)
|
|
||||||
}
|
|
||||||
runGraphCmp(t, g1, g2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// four vertices, three merge
|
|
||||||
func TestPgraphGrouping7(t *testing.T) {
|
|
||||||
g1, _ := NewGraph("g1") // original graph
|
|
||||||
{
|
|
||||||
a1 := NewVertex(NewNoopResTest("a1"))
|
|
||||||
a2 := NewVertex(NewNoopResTest("a2"))
|
|
||||||
a3 := NewVertex(NewNoopResTest("a3"))
|
|
||||||
b1 := NewVertex(NewNoopResTest("b1"))
|
|
||||||
g1.AddVertex(a1, a2, a3, b1)
|
|
||||||
}
|
|
||||||
g2, _ := NewGraph("g2") // expected result
|
|
||||||
{
|
|
||||||
a := NewVertex(NewNoopResTest("a1,a2,a3"))
|
|
||||||
b1 := NewVertex(NewNoopResTest("b1"))
|
|
||||||
g2.AddVertex(a, b1)
|
|
||||||
}
|
|
||||||
runGraphCmp(t, g1, g2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// four vertices, two&two merge
|
|
||||||
func TestPgraphGrouping8(t *testing.T) {
|
|
||||||
g1, _ := NewGraph("g1") // original graph
|
|
||||||
{
|
|
||||||
a1 := NewVertex(NewNoopResTest("a1"))
|
|
||||||
a2 := NewVertex(NewNoopResTest("a2"))
|
|
||||||
b1 := NewVertex(NewNoopResTest("b1"))
|
|
||||||
b2 := NewVertex(NewNoopResTest("b2"))
|
|
||||||
g1.AddVertex(a1, a2, b1, b2)
|
|
||||||
}
|
|
||||||
g2, _ := NewGraph("g2") // expected result
|
|
||||||
{
|
|
||||||
a := NewVertex(NewNoopResTest("a1,a2"))
|
|
||||||
b := NewVertex(NewNoopResTest("b1,b2"))
|
|
||||||
g2.AddVertex(a, b)
|
|
||||||
}
|
|
||||||
runGraphCmp(t, g1, g2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// five vertices, two&three merge
|
|
||||||
func TestPgraphGrouping9(t *testing.T) {
|
|
||||||
g1, _ := NewGraph("g1") // original graph
|
|
||||||
{
|
|
||||||
a1 := NewVertex(NewNoopResTest("a1"))
|
|
||||||
a2 := NewVertex(NewNoopResTest("a2"))
|
|
||||||
b1 := NewVertex(NewNoopResTest("b1"))
|
|
||||||
b2 := NewVertex(NewNoopResTest("b2"))
|
|
||||||
b3 := NewVertex(NewNoopResTest("b3"))
|
|
||||||
g1.AddVertex(a1, a2, b1, b2, b3)
|
|
||||||
}
|
|
||||||
g2, _ := NewGraph("g2") // expected result
|
|
||||||
{
|
|
||||||
a := NewVertex(NewNoopResTest("a1,a2"))
|
|
||||||
b := NewVertex(NewNoopResTest("b1,b2,b3"))
|
|
||||||
g2.AddVertex(a, b)
|
|
||||||
}
|
|
||||||
runGraphCmp(t, g1, g2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// three unique vertices
|
|
||||||
func TestPgraphGrouping10(t *testing.T) {
|
|
||||||
g1, _ := NewGraph("g1") // original graph
|
|
||||||
{
|
|
||||||
a1 := NewVertex(NewNoopResTest("a1"))
|
|
||||||
b1 := NewVertex(NewNoopResTest("b1"))
|
|
||||||
c1 := NewVertex(NewNoopResTest("c1"))
|
|
||||||
g1.AddVertex(a1, b1, c1)
|
|
||||||
}
|
|
||||||
g2, _ := NewGraph("g2") // expected result
|
|
||||||
{
|
|
||||||
a1 := NewVertex(NewNoopResTest("a1"))
|
|
||||||
b1 := NewVertex(NewNoopResTest("b1"))
|
|
||||||
c1 := NewVertex(NewNoopResTest("c1"))
|
|
||||||
g2.AddVertex(a1, b1, c1)
|
|
||||||
}
|
|
||||||
runGraphCmp(t, g1, g2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// three unique vertices, two merge
|
|
||||||
func TestPgraphGrouping11(t *testing.T) {
|
|
||||||
g1, _ := NewGraph("g1") // original graph
|
|
||||||
{
|
|
||||||
a1 := NewVertex(NewNoopResTest("a1"))
|
|
||||||
b1 := NewVertex(NewNoopResTest("b1"))
|
|
||||||
b2 := NewVertex(NewNoopResTest("b2"))
|
|
||||||
c1 := NewVertex(NewNoopResTest("c1"))
|
|
||||||
g1.AddVertex(a1, b1, b2, c1)
|
|
||||||
}
|
|
||||||
g2, _ := NewGraph("g2") // expected result
|
|
||||||
{
|
|
||||||
a1 := NewVertex(NewNoopResTest("a1"))
|
|
||||||
b := NewVertex(NewNoopResTest("b1,b2"))
|
|
||||||
c1 := NewVertex(NewNoopResTest("c1"))
|
|
||||||
g2.AddVertex(a1, b, c1)
|
|
||||||
}
|
|
||||||
runGraphCmp(t, g1, g2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// simple merge 1
|
|
||||||
// a1 a2 a1,a2
|
|
||||||
// \ / >>> | (arrows point downwards)
|
|
||||||
// b b
|
|
||||||
func TestPgraphGrouping12(t *testing.T) {
|
|
||||||
g1, _ := NewGraph("g1") // original graph
|
|
||||||
{
|
|
||||||
a1 := NewVertex(NewNoopResTest("a1"))
|
|
||||||
a2 := NewVertex(NewNoopResTest("a2"))
|
|
||||||
b1 := NewVertex(NewNoopResTest("b1"))
|
|
||||||
e1 := NewEdge("e1")
|
|
||||||
e2 := NewEdge("e2")
|
|
||||||
g1.AddEdge(a1, b1, e1)
|
|
||||||
g1.AddEdge(a2, b1, e2)
|
|
||||||
}
|
|
||||||
g2, _ := NewGraph("g2") // expected result
|
|
||||||
{
|
|
||||||
a := NewVertex(NewNoopResTest("a1,a2"))
|
|
||||||
b1 := NewVertex(NewNoopResTest("b1"))
|
|
||||||
e := NewEdge("e1,e2")
|
|
||||||
g2.AddEdge(a, b1, e)
|
|
||||||
}
|
|
||||||
runGraphCmp(t, g1, g2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// simple merge 2
|
|
||||||
// b b
|
|
||||||
// / \ >>> | (arrows point downwards)
|
|
||||||
// a1 a2 a1,a2
|
|
||||||
func TestPgraphGrouping13(t *testing.T) {
|
|
||||||
g1, _ := NewGraph("g1") // original graph
|
|
||||||
{
|
|
||||||
a1 := NewVertex(NewNoopResTest("a1"))
|
|
||||||
a2 := NewVertex(NewNoopResTest("a2"))
|
|
||||||
b1 := NewVertex(NewNoopResTest("b1"))
|
|
||||||
e1 := NewEdge("e1")
|
|
||||||
e2 := NewEdge("e2")
|
|
||||||
g1.AddEdge(b1, a1, e1)
|
|
||||||
g1.AddEdge(b1, a2, e2)
|
|
||||||
}
|
|
||||||
g2, _ := NewGraph("g2") // expected result
|
|
||||||
{
|
|
||||||
a := NewVertex(NewNoopResTest("a1,a2"))
|
|
||||||
b1 := NewVertex(NewNoopResTest("b1"))
|
|
||||||
e := NewEdge("e1,e2")
|
|
||||||
g2.AddEdge(b1, a, e)
|
|
||||||
}
|
|
||||||
runGraphCmp(t, g1, g2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// triple merge
|
|
||||||
// a1 a2 a3 a1,a2,a3
|
|
||||||
// \ | / >>> | (arrows point downwards)
|
|
||||||
// b b
|
|
||||||
func TestPgraphGrouping14(t *testing.T) {
|
|
||||||
g1, _ := NewGraph("g1") // original graph
|
|
||||||
{
|
|
||||||
a1 := NewVertex(NewNoopResTest("a1"))
|
|
||||||
a2 := NewVertex(NewNoopResTest("a2"))
|
|
||||||
a3 := NewVertex(NewNoopResTest("a3"))
|
|
||||||
b1 := NewVertex(NewNoopResTest("b1"))
|
|
||||||
e1 := NewEdge("e1")
|
|
||||||
e2 := NewEdge("e2")
|
|
||||||
e3 := NewEdge("e3")
|
|
||||||
g1.AddEdge(a1, b1, e1)
|
|
||||||
g1.AddEdge(a2, b1, e2)
|
|
||||||
g1.AddEdge(a3, b1, e3)
|
|
||||||
}
|
|
||||||
g2, _ := NewGraph("g2") // expected result
|
|
||||||
{
|
|
||||||
a := NewVertex(NewNoopResTest("a1,a2,a3"))
|
|
||||||
b1 := NewVertex(NewNoopResTest("b1"))
|
|
||||||
e := NewEdge("e1,e2,e3")
|
|
||||||
g2.AddEdge(a, b1, e)
|
|
||||||
}
|
|
||||||
runGraphCmp(t, g1, g2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// chain merge
|
|
||||||
// a1 a1
|
|
||||||
// / \ |
|
|
||||||
// b1 b2 >>> b1,b2 (arrows point downwards)
|
|
||||||
// \ / |
|
|
||||||
// c1 c1
|
|
||||||
func TestPgraphGrouping15(t *testing.T) {
|
|
||||||
g1, _ := NewGraph("g1") // original graph
|
|
||||||
{
|
|
||||||
a1 := NewVertex(NewNoopResTest("a1"))
|
|
||||||
b1 := NewVertex(NewNoopResTest("b1"))
|
|
||||||
b2 := NewVertex(NewNoopResTest("b2"))
|
|
||||||
c1 := NewVertex(NewNoopResTest("c1"))
|
|
||||||
e1 := NewEdge("e1")
|
|
||||||
e2 := NewEdge("e2")
|
|
||||||
e3 := NewEdge("e3")
|
|
||||||
e4 := NewEdge("e4")
|
|
||||||
g1.AddEdge(a1, b1, e1)
|
|
||||||
g1.AddEdge(a1, b2, e2)
|
|
||||||
g1.AddEdge(b1, c1, e3)
|
|
||||||
g1.AddEdge(b2, c1, e4)
|
|
||||||
}
|
|
||||||
g2, _ := NewGraph("g2") // expected result
|
|
||||||
{
|
|
||||||
a1 := NewVertex(NewNoopResTest("a1"))
|
|
||||||
b := NewVertex(NewNoopResTest("b1,b2"))
|
|
||||||
c1 := NewVertex(NewNoopResTest("c1"))
|
|
||||||
e1 := NewEdge("e1,e2")
|
|
||||||
e2 := NewEdge("e3,e4")
|
|
||||||
g2.AddEdge(a1, b, e1)
|
|
||||||
g2.AddEdge(b, c1, e2)
|
|
||||||
}
|
|
||||||
runGraphCmp(t, g1, g2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// re-attach 1 (outer)
|
|
||||||
// technically the second possibility is valid too, depending on which order we
|
|
||||||
// merge edges in, and if we don't filter out any unnecessary edges afterwards!
|
|
||||||
// a1 a2 a1,a2 a1,a2
|
|
||||||
// | / | | \
|
|
||||||
// b1 / >>> b1 OR b1 / (arrows point downwards)
|
|
||||||
// | / | | /
|
|
||||||
// c1 c1 c1
|
|
||||||
func TestPgraphGrouping16(t *testing.T) {
|
|
||||||
g1, _ := NewGraph("g1") // original graph
|
|
||||||
{
|
|
||||||
a1 := NewVertex(NewNoopResTest("a1"))
|
|
||||||
a2 := NewVertex(NewNoopResTest("a2"))
|
|
||||||
b1 := NewVertex(NewNoopResTest("b1"))
|
|
||||||
c1 := NewVertex(NewNoopResTest("c1"))
|
|
||||||
e1 := NewEdge("e1")
|
|
||||||
e2 := NewEdge("e2")
|
|
||||||
e3 := NewEdge("e3")
|
|
||||||
g1.AddEdge(a1, b1, e1)
|
|
||||||
g1.AddEdge(b1, c1, e2)
|
|
||||||
g1.AddEdge(a2, c1, e3)
|
|
||||||
}
|
|
||||||
g2, _ := NewGraph("g2") // expected result
|
|
||||||
{
|
|
||||||
a := NewVertex(NewNoopResTest("a1,a2"))
|
|
||||||
b1 := NewVertex(NewNoopResTest("b1"))
|
|
||||||
c1 := NewVertex(NewNoopResTest("c1"))
|
|
||||||
e1 := NewEdge("e1,e3")
|
|
||||||
e2 := NewEdge("e2,e3") // e3 gets "merged through" to BOTH edges!
|
|
||||||
g2.AddEdge(a, b1, e1)
|
|
||||||
g2.AddEdge(b1, c1, e2)
|
|
||||||
}
|
|
||||||
runGraphCmp(t, g1, g2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// re-attach 2 (inner)
|
|
||||||
// a1 b2 a1
|
|
||||||
// | / |
|
|
||||||
// b1 / >>> b1,b2 (arrows point downwards)
|
|
||||||
// | / |
|
|
||||||
// c1 c1
|
|
||||||
func TestPgraphGrouping17(t *testing.T) {
|
|
||||||
g1, _ := NewGraph("g1") // original graph
|
|
||||||
{
|
|
||||||
a1 := NewVertex(NewNoopResTest("a1"))
|
|
||||||
b1 := NewVertex(NewNoopResTest("b1"))
|
|
||||||
b2 := NewVertex(NewNoopResTest("b2"))
|
|
||||||
c1 := NewVertex(NewNoopResTest("c1"))
|
|
||||||
e1 := NewEdge("e1")
|
|
||||||
e2 := NewEdge("e2")
|
|
||||||
e3 := NewEdge("e3")
|
|
||||||
g1.AddEdge(a1, b1, e1)
|
|
||||||
g1.AddEdge(b1, c1, e2)
|
|
||||||
g1.AddEdge(b2, c1, e3)
|
|
||||||
}
|
|
||||||
g2, _ := NewGraph("g2") // expected result
|
|
||||||
{
|
|
||||||
a1 := NewVertex(NewNoopResTest("a1"))
|
|
||||||
b := NewVertex(NewNoopResTest("b1,b2"))
|
|
||||||
c1 := NewVertex(NewNoopResTest("c1"))
|
|
||||||
e1 := NewEdge("e1")
|
|
||||||
e2 := NewEdge("e2,e3")
|
|
||||||
g2.AddEdge(a1, b, e1)
|
|
||||||
g2.AddEdge(b, c1, e2)
|
|
||||||
}
|
|
||||||
runGraphCmp(t, g1, g2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// re-attach 3 (double)
|
|
||||||
// similar to "re-attach 1", technically there is a second possibility for this
|
|
||||||
// a2 a1 b2 a1,a2
|
|
||||||
// \ | / |
|
|
||||||
// \ b1 / >>> b1,b2 (arrows point downwards)
|
|
||||||
// \ | / |
|
|
||||||
// c1 c1
|
|
||||||
func TestPgraphGrouping18(t *testing.T) {
|
|
||||||
g1, _ := NewGraph("g1") // original graph
|
|
||||||
{
|
|
||||||
a1 := NewVertex(NewNoopResTest("a1"))
|
|
||||||
a2 := NewVertex(NewNoopResTest("a2"))
|
|
||||||
b1 := NewVertex(NewNoopResTest("b1"))
|
|
||||||
b2 := NewVertex(NewNoopResTest("b2"))
|
|
||||||
c1 := NewVertex(NewNoopResTest("c1"))
|
|
||||||
e1 := NewEdge("e1")
|
|
||||||
e2 := NewEdge("e2")
|
|
||||||
e3 := NewEdge("e3")
|
|
||||||
e4 := NewEdge("e4")
|
|
||||||
g1.AddEdge(a1, b1, e1)
|
|
||||||
g1.AddEdge(b1, c1, e2)
|
|
||||||
g1.AddEdge(a2, c1, e3)
|
|
||||||
g1.AddEdge(b2, c1, e4)
|
|
||||||
}
|
|
||||||
g2, _ := NewGraph("g2") // expected result
|
|
||||||
{
|
|
||||||
a := NewVertex(NewNoopResTest("a1,a2"))
|
|
||||||
b := NewVertex(NewNoopResTest("b1,b2"))
|
|
||||||
c1 := NewVertex(NewNoopResTest("c1"))
|
|
||||||
e1 := NewEdge("e1,e3")
|
|
||||||
e2 := NewEdge("e2,e3,e4") // e3 gets "merged through" to BOTH edges!
|
|
||||||
g2.AddEdge(a, b, e1)
|
|
||||||
g2.AddEdge(b, c1, e2)
|
|
||||||
}
|
|
||||||
runGraphCmp(t, g1, g2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// connected merge 0, (no change!)
|
|
||||||
// a1 a1
|
|
||||||
// \ >>> \ (arrows point downwards)
|
|
||||||
// a2 a2
|
|
||||||
func TestPgraphGroupingConnected0(t *testing.T) {
|
|
||||||
g1, _ := NewGraph("g1") // original graph
|
|
||||||
{
|
|
||||||
a1 := NewVertex(NewNoopResTest("a1"))
|
|
||||||
a2 := NewVertex(NewNoopResTest("a2"))
|
|
||||||
e1 := NewEdge("e1")
|
|
||||||
g1.AddEdge(a1, a2, e1)
|
|
||||||
}
|
|
||||||
g2, _ := NewGraph("g2") // expected result ?
|
|
||||||
{
|
|
||||||
a1 := NewVertex(NewNoopResTest("a1"))
|
|
||||||
a2 := NewVertex(NewNoopResTest("a2"))
|
|
||||||
e1 := NewEdge("e1")
|
|
||||||
g2.AddEdge(a1, a2, e1)
|
|
||||||
}
|
|
||||||
runGraphCmp(t, g1, g2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// connected merge 1, (no change!)
|
|
||||||
// a1 a1
|
|
||||||
// \ \
|
|
||||||
// b >>> b (arrows point downwards)
|
|
||||||
// \ \
|
|
||||||
// a2 a2
|
|
||||||
func TestPgraphGroupingConnected1(t *testing.T) {
|
|
||||||
g1, _ := NewGraph("g1") // original graph
|
|
||||||
{
|
|
||||||
a1 := NewVertex(NewNoopResTest("a1"))
|
|
||||||
b := NewVertex(NewNoopResTest("b"))
|
|
||||||
a2 := NewVertex(NewNoopResTest("a2"))
|
|
||||||
e1 := NewEdge("e1")
|
|
||||||
e2 := NewEdge("e2")
|
|
||||||
g1.AddEdge(a1, b, e1)
|
|
||||||
g1.AddEdge(b, a2, e2)
|
|
||||||
}
|
|
||||||
g2, _ := NewGraph("g2") // expected result ?
|
|
||||||
{
|
|
||||||
a1 := NewVertex(NewNoopResTest("a1"))
|
|
||||||
b := NewVertex(NewNoopResTest("b"))
|
|
||||||
a2 := NewVertex(NewNoopResTest("a2"))
|
|
||||||
e1 := NewEdge("e1")
|
|
||||||
e2 := NewEdge("e2")
|
|
||||||
g2.AddEdge(a1, b, e1)
|
|
||||||
g2.AddEdge(b, a2, e2)
|
|
||||||
}
|
|
||||||
runGraphCmp(t, g1, g2)
|
|
||||||
}
|
|
||||||
@@ -45,15 +45,15 @@ func (g *Graph) Graphviz() (out string) {
|
|||||||
out += fmt.Sprintf("\tlabel=\"%s\";\n", g.GetName())
|
out += fmt.Sprintf("\tlabel=\"%s\";\n", g.GetName())
|
||||||
//out += "\tnode [shape=box];\n"
|
//out += "\tnode [shape=box];\n"
|
||||||
str := ""
|
str := ""
|
||||||
for i := range g.adjacency { // reverse paths
|
for i := range g.Adjacency() { // reverse paths
|
||||||
out += fmt.Sprintf("\t\"%s\" [label=\"%s[%s]\"];\n", i.GetName(), i.GetKind(), i.GetName())
|
out += fmt.Sprintf("\t\"%s\" [label=\"%s\"];\n", i, i)
|
||||||
for j := range g.adjacency[i] {
|
for j := range g.Adjacency()[i] {
|
||||||
k := g.adjacency[i][j]
|
k := g.Adjacency()[i][j]
|
||||||
// use str for clearer output ordering
|
// use str for clearer output ordering
|
||||||
if k.Notify {
|
if k.Notify {
|
||||||
str += fmt.Sprintf("\t\"%s\" -> \"%s\" [label=\"%s\",style=bold];\n", i.GetName(), j.GetName(), k.Name)
|
str += fmt.Sprintf("\t\"%s\" -> \"%s\" [label=\"%s\",style=bold];\n", i, j, k.Name)
|
||||||
} else {
|
} else {
|
||||||
str += fmt.Sprintf("\t\"%s\" -> \"%s\" [label=\"%s\"];\n", i.GetName(), j.GetName(), k.Name)
|
str += fmt.Sprintf("\t\"%s\" -> \"%s\" [label=\"%s\"];\n", i, j, k.Name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
258
pgraph/pgraph.go
258
pgraph/pgraph.go
@@ -21,10 +21,6 @@ package pgraph
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"sort"
|
"sort"
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/purpleidea/mgmt/event"
|
|
||||||
"github.com/purpleidea/mgmt/resources"
|
|
||||||
|
|
||||||
errwrap "github.com/pkg/errors"
|
errwrap "github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
@@ -38,17 +34,14 @@ import (
|
|||||||
type Graph struct {
|
type Graph struct {
|
||||||
Name string
|
Name string
|
||||||
|
|
||||||
adjacency map[*Vertex]map[*Vertex]*Edge // *Vertex -> *Vertex (edge)
|
adjacency map[Vertex]map[Vertex]*Edge // Vertex -> Vertex (edge)
|
||||||
kv map[string]interface{} // some values associated with the graph
|
kv map[string]interface{} // some values associated with the graph
|
||||||
|
|
||||||
// legacy
|
|
||||||
fastPause bool // used to disable pokes for a fast pause
|
|
||||||
wg *sync.WaitGroup
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Vertex is the primary vertex struct in this library.
|
// Vertex is the primary vertex struct in this library. It can be anything that
|
||||||
type Vertex struct {
|
// implements Stringer. The string output must be stable and unique in a graph.
|
||||||
resources.Res // anonymous field
|
type Vertex interface {
|
||||||
|
fmt.Stringer // String() string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Edge is the primary edge struct in this library.
|
// Edge is the primary edge struct in this library.
|
||||||
@@ -65,12 +58,8 @@ func (g *Graph) Init() error {
|
|||||||
return fmt.Errorf("can't initialize graph with empty name")
|
return fmt.Errorf("can't initialize graph with empty name")
|
||||||
}
|
}
|
||||||
|
|
||||||
g.adjacency = make(map[*Vertex]map[*Vertex]*Edge)
|
g.adjacency = make(map[Vertex]map[Vertex]*Edge)
|
||||||
g.kv = make(map[string]interface{})
|
g.kv = make(map[string]interface{})
|
||||||
|
|
||||||
// legacy
|
|
||||||
// ptr b/c: Mutex/WaitGroup must not be copied after first use
|
|
||||||
g.wg = &sync.WaitGroup{}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,11 +74,11 @@ func NewGraph(name string) (*Graph, error) {
|
|||||||
return g, nil
|
return g, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewVertex returns a new graph vertex struct with a contained resource.
|
// NewVertex returns whatever was passed in. This is for compatibility with the
|
||||||
func NewVertex(r resources.Res) *Vertex {
|
// usage of the old NewVertex method. This is considered deprecated.
|
||||||
return &Vertex{
|
// FIXME: remove me
|
||||||
Res: r,
|
func NewVertex(x Vertex) Vertex {
|
||||||
}
|
return x
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewEdge returns a new graph edge struct.
|
// NewEdge returns a new graph edge struct.
|
||||||
@@ -120,16 +109,12 @@ func (g *Graph) SetValue(key string, val interface{}) {
|
|||||||
g.kv[key] = val
|
g.kv[key] = val
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy makes a copy of the graph struct
|
// Copy makes a copy of the graph struct.
|
||||||
func (g *Graph) Copy() *Graph {
|
func (g *Graph) Copy() *Graph {
|
||||||
newGraph := &Graph{
|
newGraph := &Graph{
|
||||||
Name: g.Name,
|
Name: g.Name,
|
||||||
adjacency: make(map[*Vertex]map[*Vertex]*Edge, len(g.adjacency)),
|
adjacency: make(map[Vertex]map[Vertex]*Edge, len(g.adjacency)),
|
||||||
kv: g.kv,
|
kv: g.kv,
|
||||||
|
|
||||||
// legacy
|
|
||||||
wg: g.wg,
|
|
||||||
fastPause: g.fastPause,
|
|
||||||
}
|
}
|
||||||
for k, v := range g.adjacency {
|
for k, v := range g.adjacency {
|
||||||
newGraph.adjacency[k] = v // copy
|
newGraph.adjacency[k] = v // copy
|
||||||
@@ -147,17 +132,17 @@ func (g *Graph) SetName(name string) {
|
|||||||
g.Name = name
|
g.Name = name
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddVertex uses variadic input to add all listed vertices to the graph
|
// AddVertex uses variadic input to add all listed vertices to the graph.
|
||||||
func (g *Graph) AddVertex(xv ...*Vertex) {
|
func (g *Graph) AddVertex(xv ...Vertex) {
|
||||||
for _, v := range xv {
|
for _, v := range xv {
|
||||||
if _, exists := g.adjacency[v]; !exists {
|
if _, exists := g.adjacency[v]; !exists {
|
||||||
g.adjacency[v] = make(map[*Vertex]*Edge)
|
g.adjacency[v] = make(map[Vertex]*Edge)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteVertex deletes a particular vertex from the graph.
|
// DeleteVertex deletes a particular vertex from the graph.
|
||||||
func (g *Graph) DeleteVertex(v *Vertex) {
|
func (g *Graph) DeleteVertex(v Vertex) {
|
||||||
delete(g.adjacency, v)
|
delete(g.adjacency, v)
|
||||||
for k := range g.adjacency {
|
for k := range g.adjacency {
|
||||||
delete(g.adjacency[k], v)
|
delete(g.adjacency[k], v)
|
||||||
@@ -165,7 +150,7 @@ func (g *Graph) DeleteVertex(v *Vertex) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// AddEdge adds a directed edge to the graph from v1 to v2.
|
// AddEdge adds a directed edge to the graph from v1 to v2.
|
||||||
func (g *Graph) AddEdge(v1, v2 *Vertex, e *Edge) {
|
func (g *Graph) AddEdge(v1, v2 Vertex, e *Edge) {
|
||||||
// NOTE: this doesn't allow more than one edge between two vertexes...
|
// NOTE: this doesn't allow more than one edge between two vertexes...
|
||||||
g.AddVertex(v1, v2) // supports adding N vertices now
|
g.AddVertex(v1, v2) // supports adding N vertices now
|
||||||
// TODO: check if an edge exists to avoid overwriting it!
|
// TODO: check if an edge exists to avoid overwriting it!
|
||||||
@@ -188,7 +173,7 @@ func (g *Graph) DeleteEdge(e *Edge) {
|
|||||||
// VertexMatchFn searches for a vertex in the graph and returns the vertex if
|
// VertexMatchFn searches for a vertex in the graph and returns the vertex if
|
||||||
// one matches. It uses a user defined function to match. That function must
|
// one matches. It uses a user defined function to match. That function must
|
||||||
// return true on match, and an error if anything goes wrong.
|
// return true on match, and an error if anything goes wrong.
|
||||||
func (g *Graph) VertexMatchFn(fn func(*Vertex) (bool, error)) (*Vertex, error) {
|
func (g *Graph) VertexMatchFn(fn func(Vertex) (bool, error)) (Vertex, error) {
|
||||||
for v := range g.adjacency {
|
for v := range g.adjacency {
|
||||||
if b, err := fn(v); err != nil {
|
if b, err := fn(v); err != nil {
|
||||||
return nil, errwrap.Wrapf(err, "fn in VertexMatchFn() errored")
|
return nil, errwrap.Wrapf(err, "fn in VertexMatchFn() errored")
|
||||||
@@ -199,19 +184,8 @@ func (g *Graph) VertexMatchFn(fn func(*Vertex) (bool, error)) (*Vertex, error) {
|
|||||||
return nil, nil // nothing found
|
return nil, nil // nothing found
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: consider adding a mutate API.
|
|
||||||
//func (g *Graph) MutateMatch(obj resources.Res) *Vertex {
|
|
||||||
// for v := range g.adjacency {
|
|
||||||
// if err := v.Res.Mutate(obj); err == nil {
|
|
||||||
// // transmogrified!
|
|
||||||
// return v
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// return nil
|
|
||||||
//}
|
|
||||||
|
|
||||||
// HasVertex returns if the input vertex exists in the graph.
|
// HasVertex returns if the input vertex exists in the graph.
|
||||||
func (g *Graph) HasVertex(v *Vertex) bool {
|
func (g *Graph) HasVertex(v Vertex) bool {
|
||||||
if _, exists := g.adjacency[v]; exists {
|
if _, exists := g.adjacency[v]; exists {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -234,14 +208,15 @@ func (g *Graph) NumEdges() int {
|
|||||||
|
|
||||||
// Adjacency returns the adjacency map representing this graph. This is useful
|
// Adjacency returns the adjacency map representing this graph. This is useful
|
||||||
// for users who which to operate on the raw data structure more efficiently.
|
// for users who which to operate on the raw data structure more efficiently.
|
||||||
func (g *Graph) Adjacency() map[*Vertex]map[*Vertex]*Edge {
|
// This works because maps are reference types so we can edit this at will.
|
||||||
|
func (g *Graph) Adjacency() map[Vertex]map[Vertex]*Edge {
|
||||||
return g.adjacency
|
return g.adjacency
|
||||||
}
|
}
|
||||||
|
|
||||||
// Vertices returns a randomly sorted slice of all vertices in the graph.
|
// Vertices returns a randomly sorted slice of all vertices in the graph.
|
||||||
// The order is random, because the map implementation is intentionally so!
|
// The order is random, because the map implementation is intentionally so!
|
||||||
func (g *Graph) Vertices() []*Vertex {
|
func (g *Graph) Vertices() []Vertex {
|
||||||
var vertices []*Vertex
|
var vertices []Vertex
|
||||||
for k := range g.adjacency {
|
for k := range g.adjacency {
|
||||||
vertices = append(vertices, k)
|
vertices = append(vertices, k)
|
||||||
}
|
}
|
||||||
@@ -249,9 +224,9 @@ func (g *Graph) Vertices() []*Vertex {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// VerticesChan returns a channel of all vertices in the graph.
|
// VerticesChan returns a channel of all vertices in the graph.
|
||||||
func (g *Graph) VerticesChan() chan *Vertex {
|
func (g *Graph) VerticesChan() chan Vertex {
|
||||||
ch := make(chan *Vertex)
|
ch := make(chan Vertex)
|
||||||
go func(ch chan *Vertex) {
|
go func(ch chan Vertex) {
|
||||||
for k := range g.adjacency {
|
for k := range g.adjacency {
|
||||||
ch <- k
|
ch <- k
|
||||||
}
|
}
|
||||||
@@ -261,7 +236,7 @@ func (g *Graph) VerticesChan() chan *Vertex {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// VertexSlice is a linear list of vertices. It can be sorted.
|
// VertexSlice is a linear list of vertices. It can be sorted.
|
||||||
type VertexSlice []*Vertex
|
type VertexSlice []Vertex
|
||||||
|
|
||||||
func (vs VertexSlice) Len() int { return len(vs) }
|
func (vs VertexSlice) Len() int { return len(vs) }
|
||||||
func (vs VertexSlice) Swap(i, j int) { vs[i], vs[j] = vs[j], vs[i] }
|
func (vs VertexSlice) Swap(i, j int) { vs[i], vs[j] = vs[j], vs[i] }
|
||||||
@@ -269,8 +244,8 @@ func (vs VertexSlice) Less(i, j int) bool { return vs[i].String() < vs[j].String
|
|||||||
|
|
||||||
// VerticesSorted returns a sorted slice of all vertices in the graph
|
// VerticesSorted returns a sorted slice of all vertices in the graph
|
||||||
// The order is sorted by String() to avoid the non-determinism in the map type
|
// The order is sorted by String() to avoid the non-determinism in the map type
|
||||||
func (g *Graph) VerticesSorted() []*Vertex {
|
func (g *Graph) VerticesSorted() []Vertex {
|
||||||
var vertices []*Vertex
|
var vertices []Vertex
|
||||||
for k := range g.adjacency {
|
for k := range g.adjacency {
|
||||||
vertices = append(vertices, k)
|
vertices = append(vertices, k)
|
||||||
}
|
}
|
||||||
@@ -283,17 +258,12 @@ func (g *Graph) String() string {
|
|||||||
return fmt.Sprintf("Vertices(%d), Edges(%d)", g.NumVertices(), g.NumEdges())
|
return fmt.Sprintf("Vertices(%d), Edges(%d)", g.NumVertices(), g.NumEdges())
|
||||||
}
|
}
|
||||||
|
|
||||||
// String returns the canonical form for a vertex
|
|
||||||
func (v *Vertex) String() string {
|
|
||||||
return fmt.Sprintf("%s[%s]", v.Res.GetKind(), v.Res.GetName())
|
|
||||||
}
|
|
||||||
|
|
||||||
// IncomingGraphVertices returns an array (slice) of all directed vertices to
|
// IncomingGraphVertices returns an array (slice) of all directed vertices to
|
||||||
// vertex v (??? -> v). OKTimestamp should probably use this.
|
// vertex v (??? -> v). OKTimestamp should probably use this.
|
||||||
func (g *Graph) IncomingGraphVertices(v *Vertex) []*Vertex {
|
func (g *Graph) IncomingGraphVertices(v Vertex) []Vertex {
|
||||||
// TODO: we might be able to implement this differently by reversing
|
// TODO: we might be able to implement this differently by reversing
|
||||||
// the Adjacency graph and then looping through it again...
|
// the Adjacency graph and then looping through it again...
|
||||||
var s []*Vertex
|
var s []Vertex
|
||||||
for k := range g.adjacency { // reverse paths
|
for k := range g.adjacency { // reverse paths
|
||||||
for w := range g.adjacency[k] {
|
for w := range g.adjacency[k] {
|
||||||
if w == v {
|
if w == v {
|
||||||
@@ -306,8 +276,8 @@ func (g *Graph) IncomingGraphVertices(v *Vertex) []*Vertex {
|
|||||||
|
|
||||||
// OutgoingGraphVertices returns an array (slice) of all vertices that vertex v
|
// OutgoingGraphVertices returns an array (slice) of all vertices that vertex v
|
||||||
// points to (v -> ???). Poke should probably use this.
|
// points to (v -> ???). Poke should probably use this.
|
||||||
func (g *Graph) OutgoingGraphVertices(v *Vertex) []*Vertex {
|
func (g *Graph) OutgoingGraphVertices(v Vertex) []Vertex {
|
||||||
var s []*Vertex
|
var s []Vertex
|
||||||
for k := range g.adjacency[v] { // forward paths
|
for k := range g.adjacency[v] { // forward paths
|
||||||
s = append(s, k)
|
s = append(s, k)
|
||||||
}
|
}
|
||||||
@@ -316,15 +286,15 @@ func (g *Graph) OutgoingGraphVertices(v *Vertex) []*Vertex {
|
|||||||
|
|
||||||
// GraphVertices returns an array (slice) of all vertices that connect to vertex v.
|
// GraphVertices returns an array (slice) of all vertices that connect to vertex v.
|
||||||
// This is the union of IncomingGraphVertices and OutgoingGraphVertices.
|
// This is the union of IncomingGraphVertices and OutgoingGraphVertices.
|
||||||
func (g *Graph) GraphVertices(v *Vertex) []*Vertex {
|
func (g *Graph) GraphVertices(v Vertex) []Vertex {
|
||||||
var s []*Vertex
|
var s []Vertex
|
||||||
s = append(s, g.IncomingGraphVertices(v)...)
|
s = append(s, g.IncomingGraphVertices(v)...)
|
||||||
s = append(s, g.OutgoingGraphVertices(v)...)
|
s = append(s, g.OutgoingGraphVertices(v)...)
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
// IncomingGraphEdges returns all of the edges that point to vertex v (??? -> v).
|
// IncomingGraphEdges returns all of the edges that point to vertex v (??? -> v).
|
||||||
func (g *Graph) IncomingGraphEdges(v *Vertex) []*Edge {
|
func (g *Graph) IncomingGraphEdges(v Vertex) []*Edge {
|
||||||
var edges []*Edge
|
var edges []*Edge
|
||||||
for v1 := range g.adjacency { // reverse paths
|
for v1 := range g.adjacency { // reverse paths
|
||||||
for v2, e := range g.adjacency[v1] {
|
for v2, e := range g.adjacency[v1] {
|
||||||
@@ -337,7 +307,7 @@ func (g *Graph) IncomingGraphEdges(v *Vertex) []*Edge {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// OutgoingGraphEdges returns all of the edges that point from vertex v (v -> ???).
|
// OutgoingGraphEdges returns all of the edges that point from vertex v (v -> ???).
|
||||||
func (g *Graph) OutgoingGraphEdges(v *Vertex) []*Edge {
|
func (g *Graph) OutgoingGraphEdges(v Vertex) []*Edge {
|
||||||
var edges []*Edge
|
var edges []*Edge
|
||||||
for _, e := range g.adjacency[v] { // forward paths
|
for _, e := range g.adjacency[v] { // forward paths
|
||||||
edges = append(edges, e)
|
edges = append(edges, e)
|
||||||
@@ -347,7 +317,7 @@ func (g *Graph) OutgoingGraphEdges(v *Vertex) []*Edge {
|
|||||||
|
|
||||||
// GraphEdges returns an array (slice) of all edges that connect to vertex v.
|
// GraphEdges returns an array (slice) of all edges that connect to vertex v.
|
||||||
// This is the union of IncomingGraphEdges and OutgoingGraphEdges.
|
// This is the union of IncomingGraphEdges and OutgoingGraphEdges.
|
||||||
func (g *Graph) GraphEdges(v *Vertex) []*Edge {
|
func (g *Graph) GraphEdges(v Vertex) []*Edge {
|
||||||
var edges []*Edge
|
var edges []*Edge
|
||||||
edges = append(edges, g.IncomingGraphEdges(v)...)
|
edges = append(edges, g.IncomingGraphEdges(v)...)
|
||||||
edges = append(edges, g.OutgoingGraphEdges(v)...)
|
edges = append(edges, g.OutgoingGraphEdges(v)...)
|
||||||
@@ -355,9 +325,9 @@ func (g *Graph) GraphEdges(v *Vertex) []*Edge {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DFS returns a depth first search for the graph, starting at the input vertex.
|
// DFS returns a depth first search for the graph, starting at the input vertex.
|
||||||
func (g *Graph) DFS(start *Vertex) []*Vertex {
|
func (g *Graph) DFS(start Vertex) []Vertex {
|
||||||
var d []*Vertex // discovered
|
var d []Vertex // discovered
|
||||||
var s []*Vertex // stack
|
var s []Vertex // stack
|
||||||
if _, exists := g.adjacency[start]; !exists {
|
if _, exists := g.adjacency[start]; !exists {
|
||||||
return nil // TODO: error
|
return nil // TODO: error
|
||||||
}
|
}
|
||||||
@@ -378,7 +348,7 @@ func (g *Graph) DFS(start *Vertex) []*Vertex {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// FilterGraph builds a new graph containing only vertices from the list.
|
// FilterGraph builds a new graph containing only vertices from the list.
|
||||||
func (g *Graph) FilterGraph(name string, vertices []*Vertex) (*Graph, error) {
|
func (g *Graph) FilterGraph(name string, vertices []Vertex) (*Graph, error) {
|
||||||
newGraph := &Graph{Name: name}
|
newGraph := &Graph{Name: name}
|
||||||
if err := newGraph.Init(); err != nil {
|
if err := newGraph.Init(); err != nil {
|
||||||
return nil, errwrap.Wrapf(err, "could not run FilterGraph() properly")
|
return nil, errwrap.Wrapf(err, "could not run FilterGraph() properly")
|
||||||
@@ -397,8 +367,8 @@ func (g *Graph) FilterGraph(name string, vertices []*Vertex) (*Graph, error) {
|
|||||||
// DisconnectedGraphs returns a list containing the N disconnected graphs.
|
// DisconnectedGraphs returns a list containing the N disconnected graphs.
|
||||||
func (g *Graph) DisconnectedGraphs() ([]*Graph, error) {
|
func (g *Graph) DisconnectedGraphs() ([]*Graph, error) {
|
||||||
graphs := []*Graph{}
|
graphs := []*Graph{}
|
||||||
var start *Vertex
|
var start Vertex
|
||||||
var d []*Vertex // discovered
|
var d []Vertex // discovered
|
||||||
c := g.NumVertices()
|
c := g.NumVertices()
|
||||||
for len(d) < c {
|
for len(d) < c {
|
||||||
|
|
||||||
@@ -429,8 +399,8 @@ func (g *Graph) DisconnectedGraphs() ([]*Graph, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// InDegree returns the count of vertices that point to me in one big lookup map.
|
// InDegree returns the count of vertices that point to me in one big lookup map.
|
||||||
func (g *Graph) InDegree() map[*Vertex]int {
|
func (g *Graph) InDegree() map[Vertex]int {
|
||||||
result := make(map[*Vertex]int)
|
result := make(map[Vertex]int)
|
||||||
for k := range g.adjacency {
|
for k := range g.adjacency {
|
||||||
result[k] = 0 // initialize
|
result[k] = 0 // initialize
|
||||||
}
|
}
|
||||||
@@ -444,8 +414,8 @@ func (g *Graph) InDegree() map[*Vertex]int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// OutDegree returns the count of vertices that point away in one big lookup map.
|
// OutDegree returns the count of vertices that point away in one big lookup map.
|
||||||
func (g *Graph) OutDegree() map[*Vertex]int {
|
func (g *Graph) OutDegree() map[Vertex]int {
|
||||||
result := make(map[*Vertex]int)
|
result := make(map[Vertex]int)
|
||||||
|
|
||||||
for k := range g.adjacency {
|
for k := range g.adjacency {
|
||||||
result[k] = 0 // initialize
|
result[k] = 0 // initialize
|
||||||
@@ -457,12 +427,12 @@ func (g *Graph) OutDegree() map[*Vertex]int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TopologicalSort returns the sort of graph vertices in that order.
|
// TopologicalSort returns the sort of graph vertices in that order.
|
||||||
// based on descriptions and code from wikipedia and rosetta code
|
// It is based on descriptions and code from wikipedia and rosetta code.
|
||||||
// TODO: add memoization, and cache invalidation to speed this up :)
|
// TODO: add memoization, and cache invalidation to speed this up :)
|
||||||
func (g *Graph) TopologicalSort() ([]*Vertex, error) { // kahn's algorithm
|
func (g *Graph) TopologicalSort() ([]Vertex, error) { // kahn's algorithm
|
||||||
var L []*Vertex // empty list that will contain the sorted elements
|
var L []Vertex // empty list that will contain the sorted elements
|
||||||
var S []*Vertex // set of all nodes with no incoming edges
|
var S []Vertex // set of all nodes with no incoming edges
|
||||||
remaining := make(map[*Vertex]int) // amount of edges remaining
|
remaining := make(map[Vertex]int) // amount of edges remaining
|
||||||
|
|
||||||
for v, d := range g.InDegree() {
|
for v, d := range g.InDegree() {
|
||||||
if d == 0 {
|
if d == 0 {
|
||||||
@@ -513,19 +483,19 @@ func (g *Graph) TopologicalSort() ([]*Vertex, error) { // kahn's algorithm
|
|||||||
// actually return a tree if we cared about correctness.
|
// actually return a tree if we cared about correctness.
|
||||||
// This operates by a recursive algorithm; a more efficient version is likely.
|
// This operates by a recursive algorithm; a more efficient version is likely.
|
||||||
// If you don't give this function a DAG, you might cause infinite recursion!
|
// If you don't give this function a DAG, you might cause infinite recursion!
|
||||||
func (g *Graph) Reachability(a, b *Vertex) []*Vertex {
|
func (g *Graph) Reachability(a, b Vertex) []Vertex {
|
||||||
if a == nil || b == nil {
|
if a == nil || b == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
vertices := g.OutgoingGraphVertices(a) // what points away from a ?
|
vertices := g.OutgoingGraphVertices(a) // what points away from a ?
|
||||||
if len(vertices) == 0 {
|
if len(vertices) == 0 {
|
||||||
return []*Vertex{} // nope
|
return []Vertex{} // nope
|
||||||
}
|
}
|
||||||
if VertexContains(b, vertices) {
|
if VertexContains(b, vertices) {
|
||||||
return []*Vertex{a, b} // found
|
return []Vertex{a, b} // found
|
||||||
}
|
}
|
||||||
// TODO: parallelize this with go routines?
|
// TODO: parallelize this with go routines?
|
||||||
var collected = make([][]*Vertex, len(vertices))
|
var collected = make([][]Vertex, len(vertices))
|
||||||
pick := -1
|
pick := -1
|
||||||
for i, v := range vertices {
|
for i, v := range vertices {
|
||||||
collected[i] = g.Reachability(v, b) // find b by recursion
|
collected[i] = g.Reachability(v, b) // find b by recursion
|
||||||
@@ -538,116 +508,15 @@ func (g *Graph) Reachability(a, b *Vertex) []*Vertex {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if pick < 0 {
|
if pick < 0 {
|
||||||
return []*Vertex{} // nope
|
return []Vertex{} // nope
|
||||||
}
|
}
|
||||||
result := []*Vertex{a} // tack on a
|
result := []Vertex{a} // tack on a
|
||||||
result = append(result, collected[pick]...)
|
result = append(result, collected[pick]...)
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
// GraphSync updates the oldGraph so that it matches the newGraph receiver. It
|
|
||||||
// leaves identical elements alone so that they don't need to be refreshed. It
|
|
||||||
// tries to mutate existing elements into new ones, if they support this.
|
|
||||||
// FIXME: add test cases
|
|
||||||
func (g *Graph) GraphSync(oldGraph *Graph) (*Graph, error) {
|
|
||||||
|
|
||||||
if oldGraph == nil {
|
|
||||||
var err error
|
|
||||||
oldGraph, err = NewGraph(g.GetName()) // copy over the name
|
|
||||||
if err != nil {
|
|
||||||
return nil, errwrap.Wrapf(err, "could not run GraphSync() properly")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
oldGraph.SetName(g.GetName()) // overwrite the name
|
|
||||||
|
|
||||||
var lookup = make(map[*Vertex]*Vertex)
|
|
||||||
var vertexKeep []*Vertex // list of vertices which are the same in new graph
|
|
||||||
var edgeKeep []*Edge // list of vertices which are the same in new graph
|
|
||||||
|
|
||||||
for v := range g.adjacency { // loop through the vertices (resources)
|
|
||||||
res := v.Res // resource
|
|
||||||
var vertex *Vertex
|
|
||||||
|
|
||||||
// step one, direct compare with res.Compare
|
|
||||||
if vertex == nil { // redundant guard for consistency
|
|
||||||
fn := func(v *Vertex) (bool, error) {
|
|
||||||
return v.Res.Compare(res), nil
|
|
||||||
}
|
|
||||||
var err error
|
|
||||||
vertex, err = oldGraph.VertexMatchFn(fn)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errwrap.Wrapf(err, "could not VertexMatchFn() resource")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: consider adding a mutate API.
|
|
||||||
// step two, try and mutate with res.Mutate
|
|
||||||
//if vertex == nil { // not found yet...
|
|
||||||
// vertex = oldGraph.MutateMatch(res)
|
|
||||||
//}
|
|
||||||
|
|
||||||
if vertex == nil { // no match found yet
|
|
||||||
if err := res.Validate(); err != nil {
|
|
||||||
return nil, errwrap.Wrapf(err, "could not Validate() resource")
|
|
||||||
}
|
|
||||||
vertex = v
|
|
||||||
oldGraph.AddVertex(vertex) // call standalone in case not part of an edge
|
|
||||||
}
|
|
||||||
lookup[v] = vertex // used for constructing edges
|
|
||||||
vertexKeep = append(vertexKeep, vertex) // append
|
|
||||||
}
|
|
||||||
|
|
||||||
// get rid of any vertices we shouldn't keep (that aren't in new graph)
|
|
||||||
for v := range oldGraph.adjacency {
|
|
||||||
if !VertexContains(v, vertexKeep) {
|
|
||||||
// wait for exit before starting new graph!
|
|
||||||
v.SendEvent(event.EventExit, nil) // sync
|
|
||||||
v.Res.WaitGroup().Wait()
|
|
||||||
oldGraph.DeleteVertex(v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// compare edges
|
|
||||||
for v1 := range g.adjacency { // loop through the vertices (resources)
|
|
||||||
for v2, e := range g.adjacency[v1] {
|
|
||||||
// we have an edge!
|
|
||||||
|
|
||||||
// lookup vertices (these should exist now)
|
|
||||||
//res1 := v1.Res // resource
|
|
||||||
//res2 := v2.Res
|
|
||||||
//vertex1 := oldGraph.CompareMatch(res1) // now: VertexMatchFn
|
|
||||||
//vertex2 := oldGraph.CompareMatch(res2) // now: VertexMatchFn
|
|
||||||
vertex1, exists1 := lookup[v1]
|
|
||||||
vertex2, exists2 := lookup[v2]
|
|
||||||
if !exists1 || !exists2 { // no match found, bug?
|
|
||||||
//if vertex1 == nil || vertex2 == nil { // no match found
|
|
||||||
return nil, fmt.Errorf("new vertices weren't found") // programming error
|
|
||||||
}
|
|
||||||
|
|
||||||
edge, exists := oldGraph.adjacency[vertex1][vertex2]
|
|
||||||
if !exists || edge.Name != e.Name { // TODO: edgeCmp
|
|
||||||
edge = e // use or overwrite edge
|
|
||||||
}
|
|
||||||
oldGraph.adjacency[vertex1][vertex2] = edge // store it (AddEdge)
|
|
||||||
edgeKeep = append(edgeKeep, edge) // mark as saved
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// delete unused edges
|
|
||||||
for v1 := range oldGraph.adjacency {
|
|
||||||
for _, e := range oldGraph.adjacency[v1] {
|
|
||||||
// we have an edge!
|
|
||||||
if !EdgeContains(e, edgeKeep) {
|
|
||||||
oldGraph.DeleteEdge(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return oldGraph, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// VertexContains is an "in array" function to test for a vertex in a slice of vertices.
|
// VertexContains is an "in array" function to test for a vertex in a slice of vertices.
|
||||||
func VertexContains(needle *Vertex, haystack []*Vertex) bool {
|
func VertexContains(needle Vertex, haystack []Vertex) bool {
|
||||||
for _, v := range haystack {
|
for _, v := range haystack {
|
||||||
if needle == v {
|
if needle == v {
|
||||||
return true
|
return true
|
||||||
@@ -667,9 +536,8 @@ func EdgeContains(needle *Edge, haystack []*Edge) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Reverse reverses a list of vertices.
|
// Reverse reverses a list of vertices.
|
||||||
func Reverse(vs []*Vertex) []*Vertex {
|
func Reverse(vs []Vertex) []Vertex {
|
||||||
//var out []*Vertex // XXX: golint suggests, but it fails testing
|
out := []Vertex{}
|
||||||
out := make([]*Vertex, 0) // empty list
|
|
||||||
l := len(vs)
|
l := len(vs)
|
||||||
for i := range vs {
|
for i := range vs {
|
||||||
out = append(out, vs[l-i-1])
|
out = append(out, vs[l-i-1])
|
||||||
|
|||||||
@@ -18,25 +18,23 @@
|
|||||||
package pgraph
|
package pgraph
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/purpleidea/mgmt/resources"
|
|
||||||
"github.com/purpleidea/mgmt/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// vertex is a test struct to test the library.
|
||||||
|
type vertex struct {
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
|
||||||
|
// String is a required method of the Vertex interface we must fulfill.
|
||||||
|
func (v *vertex) String() string {
|
||||||
|
return v.name
|
||||||
|
}
|
||||||
|
|
||||||
// NV is a helper function to make testing easier. It creates a new noop vertex.
|
// NV is a helper function to make testing easier. It creates a new noop vertex.
|
||||||
func NV(s string) *Vertex {
|
func NV(s string) Vertex {
|
||||||
obj := &resources.NoopRes{
|
obj := &vertex{s}
|
||||||
BaseRes: resources.BaseRes{
|
|
||||||
Name: s,
|
|
||||||
},
|
|
||||||
Comment: "Testing!",
|
|
||||||
}
|
|
||||||
return NewVertex(obj)
|
return NewVertex(obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,7 +118,7 @@ func TestPgraphT3(t *testing.T) {
|
|||||||
t.Errorf("should have 3 vertices instead of: %d", i)
|
t.Errorf("should have 3 vertices instead of: %d", i)
|
||||||
t.Errorf("found: %v", out1)
|
t.Errorf("found: %v", out1)
|
||||||
for _, v := range out1 {
|
for _, v := range out1 {
|
||||||
t.Errorf("value: %v", v.GetName())
|
t.Errorf("value: %s", v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,7 +127,7 @@ func TestPgraphT3(t *testing.T) {
|
|||||||
t.Errorf("should have 3 vertices instead of: %d", i)
|
t.Errorf("should have 3 vertices instead of: %d", i)
|
||||||
t.Errorf("found: %v", out1)
|
t.Errorf("found: %v", out1)
|
||||||
for _, v := range out1 {
|
for _, v := range out1 {
|
||||||
t.Errorf("value: %v", v.GetName())
|
t.Errorf("value: %s", v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -152,7 +150,7 @@ func TestPgraphT4(t *testing.T) {
|
|||||||
t.Errorf("should have 3 vertices instead of: %d", i)
|
t.Errorf("should have 3 vertices instead of: %d", i)
|
||||||
t.Errorf("found: %v", out)
|
t.Errorf("found: %v", out)
|
||||||
for _, v := range out {
|
for _, v := range out {
|
||||||
t.Errorf("value: %v", v.GetName())
|
t.Errorf("value: %s", v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -179,7 +177,7 @@ func TestPgraphT5(t *testing.T) {
|
|||||||
G.AddEdge(v5, v6, e5)
|
G.AddEdge(v5, v6, e5)
|
||||||
//G.AddEdge(v6, v4, e6)
|
//G.AddEdge(v6, v4, e6)
|
||||||
|
|
||||||
save := []*Vertex{v1, v2, v3}
|
save := []Vertex{v1, v2, v3}
|
||||||
out, err := G.FilterGraph("new g5", save)
|
out, err := G.FilterGraph("new g5", save)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("failed with: %v", err)
|
t.Errorf("failed with: %v", err)
|
||||||
@@ -269,26 +267,26 @@ func TestPgraphT8(t *testing.T) {
|
|||||||
v1 := NV("v1")
|
v1 := NV("v1")
|
||||||
v2 := NV("v2")
|
v2 := NV("v2")
|
||||||
v3 := NV("v3")
|
v3 := NV("v3")
|
||||||
if VertexContains(v1, []*Vertex{v1, v2, v3}) != true {
|
if VertexContains(v1, []Vertex{v1, v2, v3}) != true {
|
||||||
t.Errorf("should be true instead of false.")
|
t.Errorf("should be true instead of false.")
|
||||||
}
|
}
|
||||||
|
|
||||||
v4 := NV("v4")
|
v4 := NV("v4")
|
||||||
v5 := NV("v5")
|
v5 := NV("v5")
|
||||||
v6 := NV("v6")
|
v6 := NV("v6")
|
||||||
if VertexContains(v4, []*Vertex{v5, v6}) != false {
|
if VertexContains(v4, []Vertex{v5, v6}) != false {
|
||||||
t.Errorf("should be false instead of true.")
|
t.Errorf("should be false instead of true.")
|
||||||
}
|
}
|
||||||
|
|
||||||
v7 := NV("v7")
|
v7 := NV("v7")
|
||||||
v8 := NV("v8")
|
v8 := NV("v8")
|
||||||
v9 := NV("v9")
|
v9 := NV("v9")
|
||||||
if VertexContains(v8, []*Vertex{v7, v8, v9}) != true {
|
if VertexContains(v8, []Vertex{v7, v8, v9}) != true {
|
||||||
t.Errorf("should be true instead of false.")
|
t.Errorf("should be true instead of false.")
|
||||||
}
|
}
|
||||||
|
|
||||||
v1b := NV("v1") // same value, different objects
|
v1b := NV("v1") // same value, different objects
|
||||||
if VertexContains(v1b, []*Vertex{v1, v2, v3}) != false {
|
if VertexContains(v1b, []Vertex{v1, v2, v3}) != false {
|
||||||
t.Errorf("should be false instead of true.")
|
t.Errorf("should be false instead of true.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -316,7 +314,7 @@ func TestPgraphT9(t *testing.T) {
|
|||||||
G.AddEdge(v4, v5, e5)
|
G.AddEdge(v4, v5, e5)
|
||||||
G.AddEdge(v5, v6, e6)
|
G.AddEdge(v5, v6, e6)
|
||||||
|
|
||||||
indegree := G.InDegree() // map[*Vertex]int
|
indegree := G.InDegree() // map[Vertex]int
|
||||||
if i := indegree[v1]; i != 0 {
|
if i := indegree[v1]; i != 0 {
|
||||||
t.Errorf("indegree of v1 should be 0 instead of: %d", i)
|
t.Errorf("indegree of v1 should be 0 instead of: %d", i)
|
||||||
}
|
}
|
||||||
@@ -336,7 +334,7 @@ func TestPgraphT9(t *testing.T) {
|
|||||||
t.Errorf("indegree of v6 should be 1 instead of: %d", i)
|
t.Errorf("indegree of v6 should be 1 instead of: %d", i)
|
||||||
}
|
}
|
||||||
|
|
||||||
outdegree := G.OutDegree() // map[*Vertex]int
|
outdegree := G.OutDegree() // map[Vertex]int
|
||||||
if i := outdegree[v1]; i != 2 {
|
if i := outdegree[v1]; i != 2 {
|
||||||
t.Errorf("outdegree of v1 should be 2 instead of: %d", i)
|
t.Errorf("outdegree of v1 should be 2 instead of: %d", i)
|
||||||
}
|
}
|
||||||
@@ -358,12 +356,12 @@ func TestPgraphT9(t *testing.T) {
|
|||||||
|
|
||||||
s, err := G.TopologicalSort()
|
s, err := G.TopologicalSort()
|
||||||
// either possibility is a valid toposort
|
// either possibility is a valid toposort
|
||||||
match := reflect.DeepEqual(s, []*Vertex{v1, v2, v3, v4, v5, v6}) || reflect.DeepEqual(s, []*Vertex{v1, v3, v2, v4, v5, v6})
|
match := reflect.DeepEqual(s, []Vertex{v1, v2, v3, v4, v5, v6}) || reflect.DeepEqual(s, []Vertex{v1, v3, v2, v4, v5, v6})
|
||||||
if err != nil || !match {
|
if err != nil || !match {
|
||||||
t.Errorf("topological sort failed, error: %v", err)
|
t.Errorf("topological sort failed, error: %v", err)
|
||||||
str := "Found:"
|
str := "Found:"
|
||||||
for _, v := range s {
|
for _, v := range s {
|
||||||
str += " " + v.Res.GetName()
|
str += " " + v.String()
|
||||||
}
|
}
|
||||||
t.Errorf(str)
|
t.Errorf(str)
|
||||||
}
|
}
|
||||||
@@ -405,7 +403,7 @@ func TestPgraphReachability0(t *testing.T) {
|
|||||||
t.Logf("reachability failed")
|
t.Logf("reachability failed")
|
||||||
str := "Got:"
|
str := "Got:"
|
||||||
for _, v := range result {
|
for _, v := range result {
|
||||||
str += " " + v.Res.GetName()
|
str += " " + v.String()
|
||||||
}
|
}
|
||||||
t.Errorf(str)
|
t.Errorf(str)
|
||||||
}
|
}
|
||||||
@@ -416,13 +414,13 @@ func TestPgraphReachability0(t *testing.T) {
|
|||||||
v6 := NV("v6")
|
v6 := NV("v6")
|
||||||
|
|
||||||
result := G.Reachability(v1, v6)
|
result := G.Reachability(v1, v6)
|
||||||
expected := []*Vertex{}
|
expected := []Vertex{}
|
||||||
|
|
||||||
if !reflect.DeepEqual(result, expected) {
|
if !reflect.DeepEqual(result, expected) {
|
||||||
t.Logf("reachability failed")
|
t.Logf("reachability failed")
|
||||||
str := "Got:"
|
str := "Got:"
|
||||||
for _, v := range result {
|
for _, v := range result {
|
||||||
str += " " + v.Res.GetName()
|
str += " " + v.String()
|
||||||
}
|
}
|
||||||
t.Errorf(str)
|
t.Errorf(str)
|
||||||
}
|
}
|
||||||
@@ -447,13 +445,13 @@ func TestPgraphReachability0(t *testing.T) {
|
|||||||
G.AddEdge(v3, v5, e5)
|
G.AddEdge(v3, v5, e5)
|
||||||
|
|
||||||
result := G.Reachability(v1, v6)
|
result := G.Reachability(v1, v6)
|
||||||
expected := []*Vertex{}
|
expected := []Vertex{}
|
||||||
|
|
||||||
if !reflect.DeepEqual(result, expected) {
|
if !reflect.DeepEqual(result, expected) {
|
||||||
t.Logf("reachability failed")
|
t.Logf("reachability failed")
|
||||||
str := "Got:"
|
str := "Got:"
|
||||||
for _, v := range result {
|
for _, v := range result {
|
||||||
str += " " + v.Res.GetName()
|
str += " " + v.String()
|
||||||
}
|
}
|
||||||
t.Errorf(str)
|
t.Errorf(str)
|
||||||
}
|
}
|
||||||
@@ -482,13 +480,13 @@ func TestPgraphReachability1(t *testing.T) {
|
|||||||
G.AddEdge(v5, v6, e5)
|
G.AddEdge(v5, v6, e5)
|
||||||
|
|
||||||
result := G.Reachability(v1, v6)
|
result := G.Reachability(v1, v6)
|
||||||
expected := []*Vertex{v1, v2, v3, v4, v5, v6}
|
expected := []Vertex{v1, v2, v3, v4, v5, v6}
|
||||||
|
|
||||||
if !reflect.DeepEqual(result, expected) {
|
if !reflect.DeepEqual(result, expected) {
|
||||||
t.Logf("reachability failed")
|
t.Logf("reachability failed")
|
||||||
str := "Got:"
|
str := "Got:"
|
||||||
for _, v := range result {
|
for _, v := range result {
|
||||||
str += " " + v.Res.GetName()
|
str += " " + v.String()
|
||||||
}
|
}
|
||||||
t.Errorf(str)
|
t.Errorf(str)
|
||||||
}
|
}
|
||||||
@@ -517,15 +515,15 @@ func TestPgraphReachability2(t *testing.T) {
|
|||||||
G.AddEdge(v5, v6, e6)
|
G.AddEdge(v5, v6, e6)
|
||||||
|
|
||||||
result := G.Reachability(v1, v6)
|
result := G.Reachability(v1, v6)
|
||||||
expected1 := []*Vertex{v1, v2, v4, v5, v6}
|
expected1 := []Vertex{v1, v2, v4, v5, v6}
|
||||||
expected2 := []*Vertex{v1, v3, v4, v5, v6}
|
expected2 := []Vertex{v1, v3, v4, v5, v6}
|
||||||
|
|
||||||
// !xor test
|
// !xor test
|
||||||
if reflect.DeepEqual(result, expected1) == reflect.DeepEqual(result, expected2) {
|
if reflect.DeepEqual(result, expected1) == reflect.DeepEqual(result, expected2) {
|
||||||
t.Logf("reachability failed")
|
t.Logf("reachability failed")
|
||||||
str := "Got:"
|
str := "Got:"
|
||||||
for _, v := range result {
|
for _, v := range result {
|
||||||
str += " " + v.Res.GetName()
|
str += " " + v.String()
|
||||||
}
|
}
|
||||||
t.Errorf(str)
|
t.Errorf(str)
|
||||||
}
|
}
|
||||||
@@ -554,13 +552,13 @@ func TestPgraphReachability3(t *testing.T) {
|
|||||||
G.AddEdge(v5, v6, e6)
|
G.AddEdge(v5, v6, e6)
|
||||||
|
|
||||||
result := G.Reachability(v1, v6)
|
result := G.Reachability(v1, v6)
|
||||||
expected := []*Vertex{v1, v5, v6}
|
expected := []Vertex{v1, v5, v6}
|
||||||
|
|
||||||
if !reflect.DeepEqual(result, expected) {
|
if !reflect.DeepEqual(result, expected) {
|
||||||
t.Logf("reachability failed")
|
t.Logf("reachability failed")
|
||||||
str := "Got:"
|
str := "Got:"
|
||||||
for _, v := range result {
|
for _, v := range result {
|
||||||
str += " " + v.Res.GetName()
|
str += " " + v.String()
|
||||||
}
|
}
|
||||||
t.Errorf(str)
|
t.Errorf(str)
|
||||||
}
|
}
|
||||||
@@ -589,13 +587,13 @@ func TestPgraphReachability4(t *testing.T) {
|
|||||||
G.AddEdge(v1, v6, e6)
|
G.AddEdge(v1, v6, e6)
|
||||||
|
|
||||||
result := G.Reachability(v1, v6)
|
result := G.Reachability(v1, v6)
|
||||||
expected := []*Vertex{v1, v6}
|
expected := []Vertex{v1, v6}
|
||||||
|
|
||||||
if !reflect.DeepEqual(result, expected) {
|
if !reflect.DeepEqual(result, expected) {
|
||||||
t.Logf("reachability failed")
|
t.Logf("reachability failed")
|
||||||
str := "Got:"
|
str := "Got:"
|
||||||
for _, v := range result {
|
for _, v := range result {
|
||||||
str += " " + v.Res.GetName()
|
str += " " + v.String()
|
||||||
}
|
}
|
||||||
t.Errorf(str)
|
t.Errorf(str)
|
||||||
}
|
}
|
||||||
@@ -609,249 +607,19 @@ func TestPgraphT11(t *testing.T) {
|
|||||||
v5 := NV("v5")
|
v5 := NV("v5")
|
||||||
v6 := NV("v6")
|
v6 := NV("v6")
|
||||||
|
|
||||||
if rev := Reverse([]*Vertex{}); !reflect.DeepEqual(rev, []*Vertex{}) {
|
if rev := Reverse([]Vertex{}); !reflect.DeepEqual(rev, []Vertex{}) {
|
||||||
t.Errorf("reverse of vertex slice failed")
|
t.Errorf("reverse of vertex slice failed (empty)")
|
||||||
}
|
}
|
||||||
|
|
||||||
if rev := Reverse([]*Vertex{v1}); !reflect.DeepEqual(rev, []*Vertex{v1}) {
|
if rev := Reverse([]Vertex{v1}); !reflect.DeepEqual(rev, []Vertex{v1}) {
|
||||||
t.Errorf("reverse of vertex slice failed")
|
t.Errorf("reverse of vertex slice failed (single)")
|
||||||
}
|
}
|
||||||
|
|
||||||
if rev := Reverse([]*Vertex{v1, v2, v3, v4, v5, v6}); !reflect.DeepEqual(rev, []*Vertex{v6, v5, v4, v3, v2, v1}) {
|
if rev := Reverse([]Vertex{v1, v2, v3, v4, v5, v6}); !reflect.DeepEqual(rev, []Vertex{v6, v5, v4, v3, v2, v1}) {
|
||||||
t.Errorf("reverse of vertex slice failed")
|
t.Errorf("reverse of vertex slice failed (1..6)")
|
||||||
}
|
}
|
||||||
|
|
||||||
if rev := Reverse([]*Vertex{v6, v5, v4, v3, v2, v1}); !reflect.DeepEqual(rev, []*Vertex{v1, v2, v3, v4, v5, v6}) {
|
if rev := Reverse([]Vertex{v6, v5, v4, v3, v2, v1}); !reflect.DeepEqual(rev, []Vertex{v1, v2, v3, v4, v5, v6}) {
|
||||||
t.Errorf("reverse of vertex slice failed")
|
t.Errorf("reverse of vertex slice failed (6..1)")
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type NoopResTest struct {
|
|
||||||
resources.NoopRes
|
|
||||||
}
|
|
||||||
|
|
||||||
func (obj *NoopResTest) GroupCmp(r resources.Res) bool {
|
|
||||||
res, ok := r.(*NoopResTest)
|
|
||||||
if !ok {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: implement this in vertexCmp for *testGrouper instead?
|
|
||||||
if strings.Contains(res.Name, ",") { // HACK
|
|
||||||
return false // element to be grouped is already grouped!
|
|
||||||
}
|
|
||||||
|
|
||||||
// group if they start with the same letter! (helpful hack for testing)
|
|
||||||
return obj.Name[0] == res.Name[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewNoopResTest(name string) *NoopResTest {
|
|
||||||
obj := &NoopResTest{
|
|
||||||
NoopRes: resources.NoopRes{
|
|
||||||
BaseRes: resources.BaseRes{
|
|
||||||
Name: name,
|
|
||||||
MetaParams: resources.MetaParams{
|
|
||||||
AutoGroup: true, // always autogroup
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return obj
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListStrCmp compares two lists of strings
|
|
||||||
func ListStrCmp(a, b []string) bool {
|
|
||||||
//fmt.Printf("CMP: %v with %v\n", a, b) // debugging
|
|
||||||
if a == nil && b == nil {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if a == nil || b == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if len(a) != len(b) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
for i := range a {
|
|
||||||
if a[i] != b[i] {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// GraphCmp compares the topology of two graphs and returns nil if they're equal
|
|
||||||
// It also compares if grouped element groups are identical
|
|
||||||
func GraphCmp(g1, g2 *Graph) error {
|
|
||||||
if n1, n2 := g1.NumVertices(), g2.NumVertices(); n1 != n2 {
|
|
||||||
return fmt.Errorf("graph g1 has %d vertices, while g2 has %d", n1, n2)
|
|
||||||
}
|
|
||||||
if e1, e2 := g1.NumEdges(), g2.NumEdges(); e1 != e2 {
|
|
||||||
return fmt.Errorf("graph g1 has %d edges, while g2 has %d", e1, e2)
|
|
||||||
}
|
|
||||||
|
|
||||||
var m = make(map[*Vertex]*Vertex) // g1 to g2 vertex correspondence
|
|
||||||
Loop:
|
|
||||||
// check vertices
|
|
||||||
for v1 := range g1.adjacency { // for each vertex in g1
|
|
||||||
|
|
||||||
l1 := strings.Split(v1.GetName(), ",") // make list of everyone's names...
|
|
||||||
for _, x1 := range v1.GetGroup() {
|
|
||||||
l1 = append(l1, x1.GetName()) // add my contents
|
|
||||||
}
|
|
||||||
l1 = util.StrRemoveDuplicatesInList(l1) // remove duplicates
|
|
||||||
sort.Strings(l1)
|
|
||||||
|
|
||||||
// inner loop
|
|
||||||
for v2 := range g2.adjacency { // does it match in g2 ?
|
|
||||||
|
|
||||||
l2 := strings.Split(v2.GetName(), ",")
|
|
||||||
for _, x2 := range v2.GetGroup() {
|
|
||||||
l2 = append(l2, x2.GetName())
|
|
||||||
}
|
|
||||||
l2 = util.StrRemoveDuplicatesInList(l2) // remove duplicates
|
|
||||||
sort.Strings(l2)
|
|
||||||
|
|
||||||
// does l1 match l2 ?
|
|
||||||
if ListStrCmp(l1, l2) { // cmp!
|
|
||||||
m[v1] = v2
|
|
||||||
continue Loop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return fmt.Errorf("graph g1, has no match in g2 for: %v", v1.GetName())
|
|
||||||
}
|
|
||||||
// vertices (and groups) match :)
|
|
||||||
|
|
||||||
// check edges
|
|
||||||
for v1 := range g1.adjacency { // for each vertex in g1
|
|
||||||
v2 := m[v1] // lookup in map to get correspondance
|
|
||||||
// g1.adjacency[v1] corresponds to g2.adjacency[v2]
|
|
||||||
if e1, e2 := len(g1.adjacency[v1]), len(g2.adjacency[v2]); e1 != e2 {
|
|
||||||
return fmt.Errorf("graph g1, vertex(%v) has %d edges, while g2, vertex(%v) has %d", v1.GetName(), e1, v2.GetName(), e2)
|
|
||||||
}
|
|
||||||
|
|
||||||
for vv1, ee1 := range g1.adjacency[v1] {
|
|
||||||
vv2 := m[vv1]
|
|
||||||
ee2 := g2.adjacency[v2][vv2]
|
|
||||||
|
|
||||||
// these are edges from v1 -> vv1 via ee1 (graph 1)
|
|
||||||
// to cmp to edges from v2 -> vv2 via ee2 (graph 2)
|
|
||||||
|
|
||||||
// check: (1) vv1 == vv2 ? (we've already checked this!)
|
|
||||||
l1 := strings.Split(vv1.GetName(), ",") // make list of everyone's names...
|
|
||||||
for _, x1 := range vv1.GetGroup() {
|
|
||||||
l1 = append(l1, x1.GetName()) // add my contents
|
|
||||||
}
|
|
||||||
l1 = util.StrRemoveDuplicatesInList(l1) // remove duplicates
|
|
||||||
sort.Strings(l1)
|
|
||||||
|
|
||||||
l2 := strings.Split(vv2.GetName(), ",")
|
|
||||||
for _, x2 := range vv2.GetGroup() {
|
|
||||||
l2 = append(l2, x2.GetName())
|
|
||||||
}
|
|
||||||
l2 = util.StrRemoveDuplicatesInList(l2) // remove duplicates
|
|
||||||
sort.Strings(l2)
|
|
||||||
|
|
||||||
// does l1 match l2 ?
|
|
||||||
if !ListStrCmp(l1, l2) { // cmp!
|
|
||||||
return fmt.Errorf("graph g1 and g2 don't agree on: %v and %v", vv1.GetName(), vv2.GetName())
|
|
||||||
}
|
|
||||||
|
|
||||||
// check: (2) ee1 == ee2
|
|
||||||
if ee1.Name != ee2.Name {
|
|
||||||
return fmt.Errorf("graph g1 edge(%v) doesn't match g2 edge(%v)", ee1.Name, ee2.Name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// check meta parameters
|
|
||||||
for v1 := range g1.adjacency { // for each vertex in g1
|
|
||||||
for v2 := range g2.adjacency { // does it match in g2 ?
|
|
||||||
s1, s2 := v1.Meta().Sema, v2.Meta().Sema
|
|
||||||
sort.Strings(s1)
|
|
||||||
sort.Strings(s2)
|
|
||||||
if !reflect.DeepEqual(s1, s2) {
|
|
||||||
return fmt.Errorf("vertex %s and vertex %s have different semaphores", v1.GetName(), v2.GetName())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil // success!
|
|
||||||
}
|
|
||||||
|
|
||||||
type testGrouper struct {
|
|
||||||
// TODO: this algorithm may not be correct in all cases. replace if needed!
|
|
||||||
nonReachabilityGrouper // "inherit" what we want, and reimplement the rest
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ag *testGrouper) name() string {
|
|
||||||
return "testGrouper"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ag *testGrouper) vertexMerge(v1, v2 *Vertex) (v *Vertex, err error) {
|
|
||||||
if err := v1.Res.GroupRes(v2.Res); err != nil { // group them first
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// HACK: update the name so it matches full list of self+grouped
|
|
||||||
obj := v1.Res
|
|
||||||
names := strings.Split(obj.GetName(), ",") // load in stored names
|
|
||||||
for _, n := range obj.GetGroup() {
|
|
||||||
names = append(names, n.GetName()) // add my contents
|
|
||||||
}
|
|
||||||
names = util.StrRemoveDuplicatesInList(names) // remove duplicates
|
|
||||||
sort.Strings(names)
|
|
||||||
obj.SetName(strings.Join(names, ","))
|
|
||||||
return // success or fail, and no need to merge the actual vertices!
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ag *testGrouper) edgeMerge(e1, e2 *Edge) *Edge {
|
|
||||||
// HACK: update the name so it makes a union of both names
|
|
||||||
n1 := strings.Split(e1.Name, ",") // load
|
|
||||||
n2 := strings.Split(e2.Name, ",") // load
|
|
||||||
names := append(n1, n2...)
|
|
||||||
names = util.StrRemoveDuplicatesInList(names) // remove duplicates
|
|
||||||
sort.Strings(names)
|
|
||||||
return NewEdge(strings.Join(names, ","))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *Graph) fullPrint() (str string) {
|
|
||||||
str += "\n"
|
|
||||||
for v := range g.adjacency {
|
|
||||||
if semas := v.Meta().Sema; len(semas) > 0 {
|
|
||||||
str += fmt.Sprintf("* v: %v; sema: %v\n", v.GetName(), semas)
|
|
||||||
} else {
|
|
||||||
str += fmt.Sprintf("* v: %v\n", v.GetName())
|
|
||||||
}
|
|
||||||
// TODO: add explicit grouping data?
|
|
||||||
}
|
|
||||||
for v1 := range g.adjacency {
|
|
||||||
for v2, e := range g.adjacency[v1] {
|
|
||||||
str += fmt.Sprintf("* e: %v -> %v # %v\n", v1.GetName(), v2.GetName(), e.Name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// helper function
|
|
||||||
func runGraphCmp(t *testing.T, g1, g2 *Graph) {
|
|
||||||
ch := g1.autoGroup(&testGrouper{}) // edits the graph
|
|
||||||
for range ch { // bleed the channel or it won't run :(
|
|
||||||
// pass
|
|
||||||
}
|
|
||||||
err := GraphCmp(g1, g2)
|
|
||||||
if err != nil {
|
|
||||||
t.Logf(" actual (g1): %v%v", g1, g1.fullPrint())
|
|
||||||
t.Logf("expected (g2): %v%v", g2, g2.fullPrint())
|
|
||||||
t.Logf("Cmp error:")
|
|
||||||
t.Errorf("%v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDurationAssumptions(t *testing.T) {
|
|
||||||
var d time.Duration
|
|
||||||
if (d == 0) != true {
|
|
||||||
t.Errorf("empty time.Duration is no longer equal to zero")
|
|
||||||
}
|
|
||||||
if (d > 0) != false {
|
|
||||||
t.Errorf("empty time.Duration is now greater than zero")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
// You should have received a copy of the GNU Affero General Public License
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
package pgraph
|
package resources
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -26,8 +26,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/purpleidea/mgmt/event"
|
"github.com/purpleidea/mgmt/event"
|
||||||
|
"github.com/purpleidea/mgmt/pgraph"
|
||||||
"github.com/purpleidea/mgmt/prometheus"
|
"github.com/purpleidea/mgmt/prometheus"
|
||||||
"github.com/purpleidea/mgmt/resources"
|
|
||||||
"github.com/purpleidea/mgmt/util"
|
"github.com/purpleidea/mgmt/util"
|
||||||
|
|
||||||
multierr "github.com/hashicorp/go-multierror"
|
multierr "github.com/hashicorp/go-multierror"
|
||||||
@@ -36,16 +36,16 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// OKTimestamp returns true if this element can run right now?
|
// OKTimestamp returns true if this element can run right now?
|
||||||
func (g *Graph) OKTimestamp(v *Vertex) bool {
|
func OKTimestamp(g *pgraph.Graph, v pgraph.Vertex) bool {
|
||||||
// these are all the vertices pointing TO v, eg: ??? -> v
|
// these are all the vertices pointing TO v, eg: ??? -> v
|
||||||
for _, n := range g.IncomingGraphVertices(v) {
|
for _, n := range g.IncomingGraphVertices(v) {
|
||||||
// if the vertex has a greater timestamp than any pre-req (n)
|
// if the vertex has a greater timestamp than any pre-req (n)
|
||||||
// then we can't run right now...
|
// then we can't run right now...
|
||||||
// if they're equal (eg: on init of 0) then we also can't run
|
// if they're equal (eg: on init of 0) then we also can't run
|
||||||
// b/c we should let our pre-req's go first...
|
// b/c we should let our pre-req's go first...
|
||||||
x, y := v.Res.Timestamp(), n.Res.Timestamp()
|
x, y := VtoR(v).Timestamp(), VtoR(n).Timestamp()
|
||||||
if b, ok := g.Value("debug"); ok && util.Bool(b) {
|
if b, ok := g.Value("debug"); ok && util.Bool(b) {
|
||||||
log.Printf("%s[%s]: OKTimestamp: (%v) >= %s[%s](%v): !%v", v.GetKind(), v.GetName(), x, n.GetKind(), n.GetName(), y, x >= y)
|
log.Printf("%s: OKTimestamp: (%v) >= %s(%v): !%v", VtoR(v).String(), x, VtoR(n).String(), y, x >= y)
|
||||||
}
|
}
|
||||||
if x >= y {
|
if x >= y {
|
||||||
return false
|
return false
|
||||||
@@ -55,12 +55,12 @@ func (g *Graph) OKTimestamp(v *Vertex) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Poke tells nodes after me in the dependency graph that they need to refresh.
|
// Poke tells nodes after me in the dependency graph that they need to refresh.
|
||||||
func (g *Graph) Poke(v *Vertex) error {
|
func Poke(g *pgraph.Graph, v pgraph.Vertex) error {
|
||||||
|
|
||||||
// if we're pausing (or exiting) then we should suspend poke's so that
|
// if we're pausing (or exiting) then we should suspend poke's so that
|
||||||
// the graph doesn't go on running forever until it's completely done!
|
// the graph doesn't go on running forever until it's completely done!
|
||||||
// this is an optional feature which we can do by default on user exit
|
// this is an optional feature which we can do by default on user exit
|
||||||
if g.fastPause {
|
if b, ok := g.Value("fastpause"); ok && util.Bool(b) {
|
||||||
return nil // TODO: should this be an error instead?
|
return nil // TODO: should this be an error instead?
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,21 +70,21 @@ func (g *Graph) Poke(v *Vertex) error {
|
|||||||
// we can skip this poke if resource hasn't done work yet... it
|
// we can skip this poke if resource hasn't done work yet... it
|
||||||
// needs to be poked if already running, or not running though!
|
// needs to be poked if already running, or not running though!
|
||||||
// TODO: does this need an || activity flag?
|
// TODO: does this need an || activity flag?
|
||||||
if n.Res.GetState() != resources.ResStateProcess {
|
if VtoR(n).GetState() != ResStateProcess {
|
||||||
if b, ok := g.Value("debug"); ok && util.Bool(b) {
|
if b, ok := g.Value("debug"); ok && util.Bool(b) {
|
||||||
log.Printf("%s[%s]: Poke: %s[%s]", v.GetKind(), v.GetName(), n.GetKind(), n.GetName())
|
log.Printf("%s: Poke: %s", VtoR(v).String(), VtoR(n).String())
|
||||||
}
|
}
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func(nn *Vertex) error {
|
go func(nn pgraph.Vertex) error {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
//edge := g.adjacency[v][nn] // lookup
|
//edge := g.adjacency[v][nn] // lookup
|
||||||
//notify := edge.Notify && edge.Refresh()
|
//notify := edge.Notify && edge.Refresh()
|
||||||
return nn.SendEvent(event.EventPoke, nil)
|
return VtoR(nn).SendEvent(event.EventPoke, nil)
|
||||||
}(n)
|
}(n)
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
if b, ok := g.Value("debug"); ok && util.Bool(b) {
|
if b, ok := g.Value("debug"); ok && util.Bool(b) {
|
||||||
log.Printf("%s[%s]: Poke: %s[%s]: Skipped!", v.GetKind(), v.GetName(), n.GetKind(), n.GetName())
|
log.Printf("%s: Poke: %s: Skipped!", VtoR(v).String(), VtoR(n).String())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -94,30 +94,30 @@ func (g *Graph) Poke(v *Vertex) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// BackPoke pokes the pre-requisites that are stale and need to run before I can run.
|
// BackPoke pokes the pre-requisites that are stale and need to run before I can run.
|
||||||
func (g *Graph) BackPoke(v *Vertex) {
|
func BackPoke(g *pgraph.Graph, v pgraph.Vertex) {
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
// these are all the vertices pointing TO v, eg: ??? -> v
|
// these are all the vertices pointing TO v, eg: ??? -> v
|
||||||
for _, n := range g.IncomingGraphVertices(v) {
|
for _, n := range g.IncomingGraphVertices(v) {
|
||||||
x, y, s := v.Res.Timestamp(), n.Res.Timestamp(), n.Res.GetState()
|
x, y, s := VtoR(v).Timestamp(), VtoR(n).Timestamp(), VtoR(n).GetState()
|
||||||
// If the parent timestamp needs poking AND it's not running
|
// If the parent timestamp needs poking AND it's not running
|
||||||
// Process, then poke it. If the parent is in ResStateProcess it
|
// Process, then poke it. If the parent is in ResStateProcess it
|
||||||
// means that an event is pending, so we'll be expecting a poke
|
// means that an event is pending, so we'll be expecting a poke
|
||||||
// back soon, so we can safely discard the extra parent poke...
|
// back soon, so we can safely discard the extra parent poke...
|
||||||
// TODO: implement a stateLT (less than) to tell if something
|
// TODO: implement a stateLT (less than) to tell if something
|
||||||
// happens earlier in the state cycle and that doesn't wrap nil
|
// happens earlier in the state cycle and that doesn't wrap nil
|
||||||
if x >= y && (s != resources.ResStateProcess && s != resources.ResStateCheckApply) {
|
if x >= y && (s != ResStateProcess && s != ResStateCheckApply) {
|
||||||
if b, ok := g.Value("debug"); ok && util.Bool(b) {
|
if b, ok := g.Value("debug"); ok && util.Bool(b) {
|
||||||
log.Printf("%s[%s]: BackPoke: %s[%s]", v.GetKind(), v.GetName(), n.GetKind(), n.GetName())
|
log.Printf("%s: BackPoke: %s", VtoR(v).String(), VtoR(n).String())
|
||||||
}
|
}
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func(nn *Vertex) error {
|
go func(nn pgraph.Vertex) error {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
return nn.SendEvent(event.EventBackPoke, nil)
|
return VtoR(nn).SendEvent(event.EventBackPoke, nil)
|
||||||
}(n)
|
}(n)
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
if b, ok := g.Value("debug"); ok && util.Bool(b) {
|
if b, ok := g.Value("debug"); ok && util.Bool(b) {
|
||||||
log.Printf("%s[%s]: BackPoke: %s[%s]: Skipped!", v.GetKind(), v.GetName(), n.GetKind(), n.GetName())
|
log.Printf("%s: BackPoke: %s: Skipped!", VtoR(v).String(), VtoR(n).String())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -127,7 +127,7 @@ func (g *Graph) BackPoke(v *Vertex) {
|
|||||||
|
|
||||||
// RefreshPending determines if any previous nodes have a refresh pending here.
|
// RefreshPending determines if any previous nodes have a refresh pending here.
|
||||||
// If this is true, it means I am expected to apply a refresh when I next run.
|
// If this is true, it means I am expected to apply a refresh when I next run.
|
||||||
func (g *Graph) RefreshPending(v *Vertex) bool {
|
func RefreshPending(g *pgraph.Graph, v pgraph.Vertex) bool {
|
||||||
var refresh bool
|
var refresh bool
|
||||||
for _, edge := range g.IncomingGraphEdges(v) {
|
for _, edge := range g.IncomingGraphEdges(v) {
|
||||||
// if we asked for a notify *and* if one is pending!
|
// if we asked for a notify *and* if one is pending!
|
||||||
@@ -140,7 +140,7 @@ func (g *Graph) RefreshPending(v *Vertex) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SetUpstreamRefresh sets the refresh value to any upstream vertices.
|
// SetUpstreamRefresh sets the refresh value to any upstream vertices.
|
||||||
func (g *Graph) SetUpstreamRefresh(v *Vertex, b bool) {
|
func SetUpstreamRefresh(g *pgraph.Graph, v pgraph.Vertex, b bool) {
|
||||||
for _, edge := range g.IncomingGraphEdges(v) {
|
for _, edge := range g.IncomingGraphEdges(v) {
|
||||||
if edge.Notify {
|
if edge.Notify {
|
||||||
edge.SetRefresh(b)
|
edge.SetRefresh(b)
|
||||||
@@ -149,7 +149,7 @@ func (g *Graph) SetUpstreamRefresh(v *Vertex, b bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SetDownstreamRefresh sets the refresh value to any downstream vertices.
|
// SetDownstreamRefresh sets the refresh value to any downstream vertices.
|
||||||
func (g *Graph) SetDownstreamRefresh(v *Vertex, b bool) {
|
func SetDownstreamRefresh(g *pgraph.Graph, v pgraph.Vertex, b bool) {
|
||||||
for _, edge := range g.OutgoingGraphEdges(v) {
|
for _, edge := range g.OutgoingGraphEdges(v) {
|
||||||
// if we asked for a notify *and* if one is pending!
|
// if we asked for a notify *and* if one is pending!
|
||||||
if edge.Notify {
|
if edge.Notify {
|
||||||
@@ -159,25 +159,25 @@ func (g *Graph) SetDownstreamRefresh(v *Vertex, b bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Process is the primary function to execute for a particular vertex in the graph.
|
// Process is the primary function to execute for a particular vertex in the graph.
|
||||||
func (g *Graph) Process(v *Vertex) error {
|
func Process(g *pgraph.Graph, v pgraph.Vertex) error {
|
||||||
obj := v.Res
|
obj := VtoR(v)
|
||||||
if b, ok := g.Value("debug"); ok && util.Bool(b) {
|
if b, ok := g.Value("debug"); ok && util.Bool(b) {
|
||||||
log.Printf("%s[%s]: Process()", obj.GetKind(), obj.GetName())
|
log.Printf("%s[%s]: Process()", obj.GetKind(), obj.GetName())
|
||||||
}
|
}
|
||||||
// FIXME: should these SetState methods be here or after the sema code?
|
// FIXME: should these SetState methods be here or after the sema code?
|
||||||
defer obj.SetState(resources.ResStateNil) // reset state when finished
|
defer obj.SetState(ResStateNil) // reset state when finished
|
||||||
obj.SetState(resources.ResStateProcess)
|
obj.SetState(ResStateProcess)
|
||||||
|
|
||||||
// is it okay to run dependency wise right now?
|
// is it okay to run dependency wise right now?
|
||||||
// if not, that's okay because when the dependency runs, it will poke
|
// if not, that's okay because when the dependency runs, it will poke
|
||||||
// us back and we will run if needed then!
|
// us back and we will run if needed then!
|
||||||
if !g.OKTimestamp(v) {
|
if !OKTimestamp(g, v) {
|
||||||
go g.BackPoke(v)
|
go BackPoke(g, v)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
// timestamp must be okay...
|
// timestamp must be okay...
|
||||||
if b, ok := g.Value("debug"); ok && util.Bool(b) {
|
if b, ok := g.Value("debug"); ok && util.Bool(b) {
|
||||||
log.Printf("%s[%s]: OKTimestamp(%v)", obj.GetKind(), obj.GetName(), v.Res.Timestamp())
|
log.Printf("%s[%s]: OKTimestamp(%v)", obj.GetKind(), obj.GetName(), VtoR(v).Timestamp())
|
||||||
}
|
}
|
||||||
|
|
||||||
// semaphores!
|
// semaphores!
|
||||||
@@ -191,11 +191,11 @@ func (g *Graph) Process(v *Vertex) error {
|
|||||||
if b, ok := g.Value("debug"); ok && util.Bool(b) && len(semas) > 0 {
|
if b, ok := g.Value("debug"); ok && util.Bool(b) && len(semas) > 0 {
|
||||||
log.Printf("%s[%s]: Sema: P(%s)", obj.GetKind(), obj.GetName(), strings.Join(semas, ", "))
|
log.Printf("%s[%s]: Sema: P(%s)", obj.GetKind(), obj.GetName(), strings.Join(semas, ", "))
|
||||||
}
|
}
|
||||||
if err := g.SemaLock(semas); err != nil { // lock
|
if err := SemaLock(g, semas); err != nil { // lock
|
||||||
// NOTE: in practice, this might not ever be truly necessary...
|
// NOTE: in practice, this might not ever be truly necessary...
|
||||||
return fmt.Errorf("shutdown of semaphores")
|
return fmt.Errorf("shutdown of semaphores")
|
||||||
}
|
}
|
||||||
defer g.SemaUnlock(semas) // unlock
|
defer SemaUnlock(g, semas) // unlock
|
||||||
if b, ok := g.Value("debug"); ok && util.Bool(b) && len(semas) > 0 {
|
if b, ok := g.Value("debug"); ok && util.Bool(b) && len(semas) > 0 {
|
||||||
defer log.Printf("%s[%s]: Sema: V(%s)", obj.GetKind(), obj.GetName(), strings.Join(semas, ", "))
|
defer log.Printf("%s[%s]: Sema: V(%s)", obj.GetKind(), obj.GetName(), strings.Join(semas, ", "))
|
||||||
}
|
}
|
||||||
@@ -225,11 +225,11 @@ func (g *Graph) Process(v *Vertex) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// lookup the refresh (notification) variable
|
// lookup the refresh (notification) variable
|
||||||
refresh = g.RefreshPending(v) // do i need to perform a refresh?
|
refresh = RefreshPending(g, v) // do i need to perform a refresh?
|
||||||
obj.SetRefresh(refresh) // tell the resource
|
obj.SetRefresh(refresh) // tell the resource
|
||||||
|
|
||||||
// changes can occur after this...
|
// changes can occur after this...
|
||||||
obj.SetState(resources.ResStateCheckApply)
|
obj.SetState(ResStateCheckApply)
|
||||||
|
|
||||||
// check cached state, to skip CheckApply; can't skip if refreshing
|
// check cached state, to skip CheckApply; can't skip if refreshing
|
||||||
if !refresh && obj.IsStateOK() {
|
if !refresh && obj.IsStateOK() {
|
||||||
@@ -248,14 +248,14 @@ func (g *Graph) Process(v *Vertex) error {
|
|||||||
|
|
||||||
if promErr := obj.Prometheus().UpdateCheckApplyTotal(obj.GetKind(), !noop, !checkOK, err != nil); promErr != nil {
|
if promErr := obj.Prometheus().UpdateCheckApplyTotal(obj.GetKind(), !noop, !checkOK, err != nil); promErr != nil {
|
||||||
// TODO: how to error correctly
|
// TODO: how to error correctly
|
||||||
log.Printf("%s[%s]: Prometheus.UpdateCheckApplyTotal() errored: %v", v.GetKind(), v.GetName(), err)
|
log.Printf("%s: Prometheus.UpdateCheckApplyTotal() errored: %v", VtoR(v).String(), err)
|
||||||
}
|
}
|
||||||
// TODO: Can the `Poll` converged timeout tracking be a
|
// TODO: Can the `Poll` converged timeout tracking be a
|
||||||
// more general method for all converged timeouts? this
|
// more general method for all converged timeouts? this
|
||||||
// would simplify the resources by removing boilerplate
|
// would simplify the resources by removing boilerplate
|
||||||
if v.Meta().Poll > 0 {
|
if VtoR(v).Meta().Poll > 0 {
|
||||||
if !checkOK { // something changed, restart timer
|
if !checkOK { // something changed, restart timer
|
||||||
cuid, _, _ := v.Res.ConvergerUIDs() // get the converger uid used to report status
|
cuid, _, _ := VtoR(v).ConvergerUIDs() // get the converger uid used to report status
|
||||||
cuid.ResetTimer() // activity!
|
cuid.ResetTimer() // activity!
|
||||||
if b, ok := g.Value("debug"); ok && util.Bool(b) {
|
if b, ok := g.Value("debug"); ok && util.Bool(b) {
|
||||||
log.Printf("%s[%s]: Converger: ResetTimer", obj.GetKind(), obj.GetName())
|
log.Printf("%s[%s]: Converger: ResetTimer", obj.GetKind(), obj.GetName())
|
||||||
@@ -275,7 +275,7 @@ func (g *Graph) Process(v *Vertex) error {
|
|||||||
if !noop && err == nil { // aka !noop || checkOK
|
if !noop && err == nil { // aka !noop || checkOK
|
||||||
obj.StateOK(true) // reset
|
obj.StateOK(true) // reset
|
||||||
if refresh {
|
if refresh {
|
||||||
g.SetUpstreamRefresh(v, false) // refresh happened, clear the request
|
SetUpstreamRefresh(g, v, false) // refresh happened, clear the request
|
||||||
obj.SetRefresh(false)
|
obj.SetRefresh(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -301,14 +301,14 @@ func (g *Graph) Process(v *Vertex) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if activity { // add refresh flag to downstream edges...
|
if activity { // add refresh flag to downstream edges...
|
||||||
g.SetDownstreamRefresh(v, true)
|
SetDownstreamRefresh(g, v, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// update this timestamp *before* we poke or the poked
|
// update this timestamp *before* we poke or the poked
|
||||||
// nodes might fail due to having a too old timestamp!
|
// nodes might fail due to having a too old timestamp!
|
||||||
v.Res.UpdateTimestamp() // this was touched...
|
VtoR(v).UpdateTimestamp() // this was touched...
|
||||||
obj.SetState(resources.ResStatePoking) // can't cancel parent poke
|
obj.SetState(ResStatePoking) // can't cancel parent poke
|
||||||
if err := g.Poke(v); err != nil {
|
if err := Poke(g, v); err != nil {
|
||||||
return errwrap.Wrapf(err, "the Poke() failed")
|
return errwrap.Wrapf(err, "the Poke() failed")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -327,9 +327,9 @@ func (obj *SentinelErr) Error() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// innerWorker is the CheckApply runner that reads from processChan.
|
// innerWorker is the CheckApply runner that reads from processChan.
|
||||||
// TODO: would it be better if this was a method on BaseRes that took in *Graph?
|
// TODO: would it be better if this was a method on BaseRes that took in *pgraph.Graph?
|
||||||
func (g *Graph) innerWorker(v *Vertex) {
|
func innerWorker(g *pgraph.Graph, v pgraph.Vertex) {
|
||||||
obj := v.Res
|
obj := VtoR(v)
|
||||||
running := false
|
running := false
|
||||||
done := make(chan struct{})
|
done := make(chan struct{})
|
||||||
playback := false // do we need to run another one?
|
playback := false // do we need to run another one?
|
||||||
@@ -341,9 +341,9 @@ func (g *Graph) innerWorker(v *Vertex) {
|
|||||||
<-timer.C // unnecessary, shouldn't happen
|
<-timer.C // unnecessary, shouldn't happen
|
||||||
}
|
}
|
||||||
|
|
||||||
var delay = time.Duration(v.Meta().Delay) * time.Millisecond
|
var delay = time.Duration(VtoR(v).Meta().Delay) * time.Millisecond
|
||||||
var retry = v.Meta().Retry // number of tries left, -1 for infinite
|
var retry = VtoR(v).Meta().Retry // number of tries left, -1 for infinite
|
||||||
var limiter = rate.NewLimiter(v.Meta().Limit, v.Meta().Burst)
|
var limiter = rate.NewLimiter(VtoR(v).Meta().Limit, VtoR(v).Meta().Burst)
|
||||||
limited := false
|
limited := false
|
||||||
|
|
||||||
wg := &sync.WaitGroup{} // wait for Process routine to exit
|
wg := &sync.WaitGroup{} // wait for Process routine to exit
|
||||||
@@ -355,17 +355,17 @@ Loop:
|
|||||||
if !ok { // processChan closed, let's exit
|
if !ok { // processChan closed, let's exit
|
||||||
break Loop // no event, so no ack!
|
break Loop // no event, so no ack!
|
||||||
}
|
}
|
||||||
if v.Res.Meta().Poll == 0 { // skip for polling
|
if VtoR(v).Meta().Poll == 0 { // skip for polling
|
||||||
wcuid.SetConverged(false)
|
wcuid.SetConverged(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// if process started, but no action yet, skip!
|
// if process started, but no action yet, skip!
|
||||||
if v.Res.GetState() == resources.ResStateProcess {
|
if VtoR(v).GetState() == ResStateProcess {
|
||||||
if b, ok := g.Value("debug"); ok && util.Bool(b) {
|
if b, ok := g.Value("debug"); ok && util.Bool(b) {
|
||||||
log.Printf("%s[%s]: Skipped event!", v.GetKind(), v.GetName())
|
log.Printf("%s: Skipped event!", VtoR(v).String())
|
||||||
}
|
}
|
||||||
ev.ACK() // ready for next message
|
ev.ACK() // ready for next message
|
||||||
v.Res.QuiesceGroup().Done()
|
VtoR(v).QuiesceGroup().Done()
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -373,27 +373,27 @@ Loop:
|
|||||||
// if waiting, we skip running a new execution!
|
// if waiting, we skip running a new execution!
|
||||||
if running || waiting {
|
if running || waiting {
|
||||||
if b, ok := g.Value("debug"); ok && util.Bool(b) {
|
if b, ok := g.Value("debug"); ok && util.Bool(b) {
|
||||||
log.Printf("%s[%s]: Playback added!", v.GetKind(), v.GetName())
|
log.Printf("%s: Playback added!", VtoR(v).String())
|
||||||
}
|
}
|
||||||
playback = true
|
playback = true
|
||||||
ev.ACK() // ready for next message
|
ev.ACK() // ready for next message
|
||||||
v.Res.QuiesceGroup().Done()
|
VtoR(v).QuiesceGroup().Done()
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// catch invalid rates
|
// catch invalid rates
|
||||||
if v.Meta().Burst == 0 && !(v.Meta().Limit == rate.Inf) { // blocked
|
if VtoR(v).Meta().Burst == 0 && !(VtoR(v).Meta().Limit == rate.Inf) { // blocked
|
||||||
e := fmt.Errorf("%s[%s]: Permanently limited (rate != Inf, burst: 0)", v.GetKind(), v.GetName())
|
e := fmt.Errorf("%s: Permanently limited (rate != Inf, burst: 0)", VtoR(v).String())
|
||||||
ev.ACK() // ready for next message
|
ev.ACK() // ready for next message
|
||||||
v.Res.QuiesceGroup().Done()
|
VtoR(v).QuiesceGroup().Done()
|
||||||
v.SendEvent(event.EventExit, &SentinelErr{e})
|
VtoR(v).SendEvent(event.EventExit, &SentinelErr{e})
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// rate limit
|
// rate limit
|
||||||
// FIXME: consider skipping rate limit check if
|
// FIXME: consider skipping rate limit check if
|
||||||
// the event is a poke instead of a watch event
|
// the event is a poke instead of a watch event
|
||||||
if !limited && !(v.Meta().Limit == rate.Inf) { // skip over the playback event...
|
if !limited && !(VtoR(v).Meta().Limit == rate.Inf) { // skip over the playback event...
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
r := limiter.ReserveN(now, 1) // one event
|
r := limiter.ReserveN(now, 1) // one event
|
||||||
// r.OK() seems to always be true here!
|
// r.OK() seems to always be true here!
|
||||||
@@ -401,12 +401,12 @@ Loop:
|
|||||||
if d > 0 { // delay
|
if d > 0 { // delay
|
||||||
limited = true
|
limited = true
|
||||||
playback = true
|
playback = true
|
||||||
log.Printf("%s[%s]: Limited (rate: %v/sec, burst: %d, next: %v)", v.GetKind(), v.GetName(), v.Meta().Limit, v.Meta().Burst, d)
|
log.Printf("%s: Limited (rate: %v/sec, burst: %d, next: %v)", VtoR(v).String(), VtoR(v).Meta().Limit, VtoR(v).Meta().Burst, d)
|
||||||
// start the timer...
|
// start the timer...
|
||||||
timer.Reset(d)
|
timer.Reset(d)
|
||||||
waiting = true // waiting for retry timer
|
waiting = true // waiting for retry timer
|
||||||
ev.ACK()
|
ev.ACK()
|
||||||
v.Res.QuiesceGroup().Done()
|
VtoR(v).QuiesceGroup().Done()
|
||||||
continue
|
continue
|
||||||
} // otherwise, we run directly!
|
} // otherwise, we run directly!
|
||||||
}
|
}
|
||||||
@@ -417,58 +417,58 @@ Loop:
|
|||||||
go func(ev *event.Event) {
|
go func(ev *event.Event) {
|
||||||
pcuid.SetConverged(false) // "block" Process
|
pcuid.SetConverged(false) // "block" Process
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
if e := g.Process(v); e != nil {
|
if e := Process(g, v); e != nil {
|
||||||
playback = true
|
playback = true
|
||||||
log.Printf("%s[%s]: CheckApply errored: %v", v.GetKind(), v.GetName(), e)
|
log.Printf("%s: CheckApply errored: %v", VtoR(v).String(), e)
|
||||||
if retry == 0 {
|
if retry == 0 {
|
||||||
if err := obj.Prometheus().UpdateState(fmt.Sprintf("%s[%s]", v.GetKind(), v.GetName()), v.GetKind(), prometheus.ResStateHardFail); err != nil {
|
if err := obj.Prometheus().UpdateState(VtoR(v).String(), VtoR(v).GetKind(), prometheus.ResStateHardFail); err != nil {
|
||||||
// TODO: how to error this?
|
// TODO: how to error this?
|
||||||
log.Printf("%s[%s]: Prometheus.UpdateState() errored: %v", v.GetKind(), v.GetName(), err)
|
log.Printf("%s: Prometheus.UpdateState() errored: %v", VtoR(v).String(), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// wrap the error in the sentinel
|
// wrap the error in the sentinel
|
||||||
v.Res.QuiesceGroup().Done() // before the Wait that happens in SendEvent!
|
VtoR(v).QuiesceGroup().Done() // before the Wait that happens in SendEvent!
|
||||||
v.SendEvent(event.EventExit, &SentinelErr{e})
|
VtoR(v).SendEvent(event.EventExit, &SentinelErr{e})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if retry > 0 { // don't decrement the -1
|
if retry > 0 { // don't decrement the -1
|
||||||
retry--
|
retry--
|
||||||
}
|
}
|
||||||
if err := obj.Prometheus().UpdateState(fmt.Sprintf("%s[%s]", v.GetKind(), v.GetName()), v.GetKind(), prometheus.ResStateSoftFail); err != nil {
|
if err := obj.Prometheus().UpdateState(VtoR(v).String(), VtoR(v).GetKind(), prometheus.ResStateSoftFail); err != nil {
|
||||||
// TODO: how to error this?
|
// TODO: how to error this?
|
||||||
log.Printf("%s[%s]: Prometheus.UpdateState() errored: %v", v.GetKind(), v.GetName(), err)
|
log.Printf("%s: Prometheus.UpdateState() errored: %v", VtoR(v).String(), err)
|
||||||
}
|
}
|
||||||
log.Printf("%s[%s]: CheckApply: Retrying after %.4f seconds (%d left)", v.GetKind(), v.GetName(), delay.Seconds(), retry)
|
log.Printf("%s: CheckApply: Retrying after %.4f seconds (%d left)", VtoR(v).String(), delay.Seconds(), retry)
|
||||||
// start the timer...
|
// start the timer...
|
||||||
timer.Reset(delay)
|
timer.Reset(delay)
|
||||||
waiting = true // waiting for retry timer
|
waiting = true // waiting for retry timer
|
||||||
// don't v.Res.QuiesceGroup().Done() b/c
|
// don't VtoR(v).QuiesceGroup().Done() b/c
|
||||||
// the timer is running and it can exit!
|
// the timer is running and it can exit!
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
retry = v.Meta().Retry // reset on success
|
retry = VtoR(v).Meta().Retry // reset on success
|
||||||
close(done) // trigger
|
close(done) // trigger
|
||||||
}(ev)
|
}(ev)
|
||||||
ev.ACK() // sync (now mostly useless)
|
ev.ACK() // sync (now mostly useless)
|
||||||
|
|
||||||
case <-timer.C:
|
case <-timer.C:
|
||||||
if v.Res.Meta().Poll == 0 { // skip for polling
|
if VtoR(v).Meta().Poll == 0 { // skip for polling
|
||||||
wcuid.SetConverged(false)
|
wcuid.SetConverged(false)
|
||||||
}
|
}
|
||||||
waiting = false
|
waiting = false
|
||||||
if !timer.Stop() {
|
if !timer.Stop() {
|
||||||
//<-timer.C // blocks, docs are wrong!
|
//<-timer.C // blocks, docs are wrong!
|
||||||
}
|
}
|
||||||
log.Printf("%s[%s]: CheckApply delay expired!", v.GetKind(), v.GetName())
|
log.Printf("%s: CheckApply delay expired!", VtoR(v).String())
|
||||||
close(done)
|
close(done)
|
||||||
|
|
||||||
// a CheckApply run (with possibly retry pause) finished
|
// a CheckApply run (with possibly retry pause) finished
|
||||||
case <-done:
|
case <-done:
|
||||||
if v.Res.Meta().Poll == 0 { // skip for polling
|
if VtoR(v).Meta().Poll == 0 { // skip for polling
|
||||||
wcuid.SetConverged(false)
|
wcuid.SetConverged(false)
|
||||||
}
|
}
|
||||||
if b, ok := g.Value("debug"); ok && util.Bool(b) {
|
if b, ok := g.Value("debug"); ok && util.Bool(b) {
|
||||||
log.Printf("%s[%s]: CheckApply finished!", v.GetKind(), v.GetName())
|
log.Printf("%s: CheckApply finished!", VtoR(v).String())
|
||||||
}
|
}
|
||||||
done = make(chan struct{}) // reset
|
done = make(chan struct{}) // reset
|
||||||
// re-send this event, to trigger a CheckApply()
|
// re-send this event, to trigger a CheckApply()
|
||||||
@@ -478,18 +478,18 @@ Loop:
|
|||||||
// TODO: can this experience indefinite postponement ?
|
// TODO: can this experience indefinite postponement ?
|
||||||
// see: https://github.com/golang/go/issues/11506
|
// see: https://github.com/golang/go/issues/11506
|
||||||
// pause or exit is in process if not quiescing!
|
// pause or exit is in process if not quiescing!
|
||||||
if !v.Res.IsQuiescing() {
|
if !VtoR(v).IsQuiescing() {
|
||||||
playback = false
|
playback = false
|
||||||
v.Res.QuiesceGroup().Add(1) // lock around it, b/c still running...
|
VtoR(v).QuiesceGroup().Add(1) // lock around it, b/c still running...
|
||||||
go func() {
|
go func() {
|
||||||
obj.Event() // replay a new event
|
obj.Event() // replay a new event
|
||||||
v.Res.QuiesceGroup().Done()
|
VtoR(v).QuiesceGroup().Done()
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
running = false
|
running = false
|
||||||
pcuid.SetConverged(true) // "unblock" Process
|
pcuid.SetConverged(true) // "unblock" Process
|
||||||
v.Res.QuiesceGroup().Done()
|
VtoR(v).QuiesceGroup().Done()
|
||||||
|
|
||||||
case <-wcuid.ConvergedTimer():
|
case <-wcuid.ConvergedTimer():
|
||||||
wcuid.SetConverged(true) // converged!
|
wcuid.SetConverged(true) // converged!
|
||||||
@@ -503,16 +503,16 @@ Loop:
|
|||||||
// Worker is the common run frontend of the vertex. It handles all of the retry
|
// Worker is the common run frontend of the vertex. It handles all of the retry
|
||||||
// and retry delay common code, and ultimately returns the final status of this
|
// and retry delay common code, and ultimately returns the final status of this
|
||||||
// vertex execution.
|
// vertex execution.
|
||||||
func (g *Graph) Worker(v *Vertex) error {
|
func Worker(g *pgraph.Graph, v pgraph.Vertex) error {
|
||||||
// listen for chan events from Watch() and run
|
// listen for chan events from Watch() and run
|
||||||
// the Process() function when they're received
|
// the Process() function when they're received
|
||||||
// this avoids us having to pass the data into
|
// this avoids us having to pass the data into
|
||||||
// the Watch() function about which graph it is
|
// the Watch() function about which graph it is
|
||||||
// running on, which isolates things nicely...
|
// running on, which isolates things nicely...
|
||||||
obj := v.Res
|
obj := VtoR(v)
|
||||||
if b, ok := g.Value("debug"); ok && util.Bool(b) {
|
if b, ok := g.Value("debug"); ok && util.Bool(b) {
|
||||||
log.Printf("%s[%s]: Worker: Running", v.GetKind(), v.GetName())
|
log.Printf("%s: Worker: Running", VtoR(v).String())
|
||||||
defer log.Printf("%s[%s]: Worker: Stopped", v.GetKind(), v.GetName())
|
defer log.Printf("%s: Worker: Stopped", VtoR(v).String())
|
||||||
}
|
}
|
||||||
// run the init (should match 1-1 with Close function)
|
// run the init (should match 1-1 with Close function)
|
||||||
if err := obj.Init(); err != nil {
|
if err := obj.Init(); err != nil {
|
||||||
@@ -537,7 +537,7 @@ func (g *Graph) Worker(v *Vertex) error {
|
|||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
g.innerWorker(v)
|
innerWorker(g, v)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
var err error // propagate the error up (this is a permanent BAD error!)
|
var err error // propagate the error up (this is a permanent BAD error!)
|
||||||
@@ -547,7 +547,7 @@ func (g *Graph) Worker(v *Vertex) error {
|
|||||||
// NOTE: we're using the same retry and delay metaparams that CheckApply
|
// NOTE: we're using the same retry and delay metaparams that CheckApply
|
||||||
// uses. This is for practicality. We can separate them later if needed!
|
// uses. This is for practicality. We can separate them later if needed!
|
||||||
var watchDelay time.Duration
|
var watchDelay time.Duration
|
||||||
var watchRetry = v.Meta().Retry // number of tries left, -1 for infinite
|
var watchRetry = VtoR(v).Meta().Retry // number of tries left, -1 for infinite
|
||||||
// watch blocks until it ends, & errors to retry
|
// watch blocks until it ends, & errors to retry
|
||||||
for {
|
for {
|
||||||
// TODO: do we have to stop the converged-timeout when in this block (perhaps we're in the delay block!)
|
// TODO: do we have to stop the converged-timeout when in this block (perhaps we're in the delay block!)
|
||||||
@@ -600,7 +600,7 @@ func (g *Graph) Worker(v *Vertex) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
timer.Stop() // it's nice to cleanup
|
timer.Stop() // it's nice to cleanup
|
||||||
log.Printf("%s[%s]: Watch delay expired!", v.GetKind(), v.GetName())
|
log.Printf("%s: Watch delay expired!", VtoR(v).String())
|
||||||
// NOTE: we can avoid the send if running Watch guarantees
|
// NOTE: we can avoid the send if running Watch guarantees
|
||||||
// one CheckApply event on startup!
|
// one CheckApply event on startup!
|
||||||
//if pendingSendEvent { // TODO: should this become a list in the future?
|
//if pendingSendEvent { // TODO: should this become a list in the future?
|
||||||
@@ -612,13 +612,13 @@ func (g *Graph) Worker(v *Vertex) error {
|
|||||||
|
|
||||||
// TODO: reset the watch retry count after some amount of success
|
// TODO: reset the watch retry count after some amount of success
|
||||||
var e error
|
var e error
|
||||||
if v.Res.Meta().Poll > 0 { // poll instead of watching :(
|
if VtoR(v).Meta().Poll > 0 { // poll instead of watching :(
|
||||||
cuid, _, _ := v.Res.ConvergerUIDs() // get the converger uid used to report status
|
cuid, _, _ := VtoR(v).ConvergerUIDs() // get the converger uid used to report status
|
||||||
cuid.StartTimer()
|
cuid.StartTimer()
|
||||||
e = v.Res.Poll()
|
e = VtoR(v).Poll()
|
||||||
cuid.StopTimer() // clean up nicely
|
cuid.StopTimer() // clean up nicely
|
||||||
} else {
|
} else {
|
||||||
e = v.Res.Watch() // run the watch normally
|
e = VtoR(v).Watch() // run the watch normally
|
||||||
}
|
}
|
||||||
if e == nil { // exit signal
|
if e == nil { // exit signal
|
||||||
err = nil // clean exit
|
err = nil // clean exit
|
||||||
@@ -628,7 +628,7 @@ func (g *Graph) Worker(v *Vertex) error {
|
|||||||
err = sentinelErr.err
|
err = sentinelErr.err
|
||||||
break // sentinel means, perma-exit
|
break // sentinel means, perma-exit
|
||||||
}
|
}
|
||||||
log.Printf("%s[%s]: Watch errored: %v", v.GetKind(), v.GetName(), e)
|
log.Printf("%s: Watch errored: %v", VtoR(v).String(), e)
|
||||||
if watchRetry == 0 {
|
if watchRetry == 0 {
|
||||||
err = fmt.Errorf("Permanent watch error: %v", e)
|
err = fmt.Errorf("Permanent watch error: %v", e)
|
||||||
break
|
break
|
||||||
@@ -636,8 +636,8 @@ func (g *Graph) Worker(v *Vertex) error {
|
|||||||
if watchRetry > 0 { // don't decrement the -1
|
if watchRetry > 0 { // don't decrement the -1
|
||||||
watchRetry--
|
watchRetry--
|
||||||
}
|
}
|
||||||
watchDelay = time.Duration(v.Meta().Delay) * time.Millisecond
|
watchDelay = time.Duration(VtoR(v).Meta().Delay) * time.Millisecond
|
||||||
log.Printf("%s[%s]: Watch: Retrying after %.4f seconds (%d left)", v.GetKind(), v.GetName(), watchDelay.Seconds(), watchRetry)
|
log.Printf("%s: Watch: Retrying after %.4f seconds (%d left)", VtoR(v).String(), watchDelay.Seconds(), watchRetry)
|
||||||
// We need to trigger a CheckApply after Watch restarts, so that
|
// We need to trigger a CheckApply after Watch restarts, so that
|
||||||
// we catch any lost events that happened while down. We do this
|
// we catch any lost events that happened while down. We do this
|
||||||
// by getting the Watch resource to send one event once it's up!
|
// by getting the Watch resource to send one event once it's up!
|
||||||
@@ -656,25 +656,28 @@ func (g *Graph) Worker(v *Vertex) error {
|
|||||||
|
|
||||||
// Start is a main kick to start the graph. It goes through in reverse topological
|
// Start is a main kick to start the graph. It goes through in reverse topological
|
||||||
// sort order so that events can't hit un-started vertices.
|
// sort order so that events can't hit un-started vertices.
|
||||||
func (g *Graph) Start(first bool) { // start or continue
|
func Start(g *pgraph.Graph, first bool) { // start or continue
|
||||||
log.Printf("State: %v -> %v", g.setState(graphStateStarting), g.getState())
|
log.Printf("State: %v -> %v", setState(g, graphStateStarting), getState(g))
|
||||||
defer log.Printf("State: %v -> %v", g.setState(graphStateStarted), g.getState())
|
defer log.Printf("State: %v -> %v", setState(g, graphStateStarted), getState(g))
|
||||||
t, _ := g.TopologicalSort()
|
t, _ := g.TopologicalSort()
|
||||||
indegree := g.InDegree() // compute all of the indegree's
|
indegree := g.InDegree() // compute all of the indegree's
|
||||||
reversed := Reverse(t)
|
reversed := pgraph.Reverse(t)
|
||||||
wg := &sync.WaitGroup{}
|
wg := &sync.WaitGroup{}
|
||||||
for _, v := range reversed { // run the Setup() for everyone first
|
for _, v := range reversed { // run the Setup() for everyone first
|
||||||
// run these in parallel, as long as we wait before continuing
|
// run these in parallel, as long as we wait before continuing
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func(vv *Vertex) {
|
go func(vv pgraph.Vertex) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
if !vv.Res.IsWorking() { // if Worker() is not running...
|
if !VtoR(vv).IsWorking() { // if Worker() is not running...
|
||||||
vv.Res.Setup() // initialize some vars in the resource
|
VtoR(vv).Setup() // initialize some vars in the resource
|
||||||
}
|
}
|
||||||
}(v)
|
}(v)
|
||||||
}
|
}
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
|
|
||||||
|
// ptr b/c: Mutex/WaitGroup must not be copied after first use
|
||||||
|
gwg := WgFromGraph(g)
|
||||||
|
|
||||||
// run through the topological reverse, and start or unpause each vertex
|
// run through the topological reverse, and start or unpause each vertex
|
||||||
for _, v := range reversed {
|
for _, v := range reversed {
|
||||||
// selective poke: here we reduce the number of initial pokes
|
// selective poke: here we reduce the number of initial pokes
|
||||||
@@ -697,37 +700,37 @@ func (g *Graph) Start(first bool) { // start or continue
|
|||||||
// of the indegree == 0 vertices, and an important aspect of the
|
// of the indegree == 0 vertices, and an important aspect of the
|
||||||
// Process() function is that even if the state is correct, it
|
// Process() function is that even if the state is correct, it
|
||||||
// will pass through the Poke so that it flows through the DAG.
|
// will pass through the Poke so that it flows through the DAG.
|
||||||
v.Res.Starter(indegree[v] == 0)
|
VtoR(v).Starter(indegree[v] == 0)
|
||||||
|
|
||||||
var unpause = true
|
var unpause = true
|
||||||
if !v.Res.IsWorking() { // if Worker() is not running...
|
if !VtoR(v).IsWorking() { // if Worker() is not running...
|
||||||
unpause = false // doesn't need unpausing on first start
|
unpause = false // doesn't need unpausing on first start
|
||||||
g.wg.Add(1)
|
gwg.Add(1)
|
||||||
// must pass in value to avoid races...
|
// must pass in value to avoid races...
|
||||||
// see: https://ttboj.wordpress.com/2015/07/27/golang-parallelism-issues-causing-too-many-open-files-error/
|
// see: https://ttboj.wordpress.com/2015/07/27/golang-parallelism-issues-causing-too-many-open-files-error/
|
||||||
go func(vv *Vertex) {
|
go func(vv pgraph.Vertex) {
|
||||||
defer g.wg.Done()
|
defer gwg.Done()
|
||||||
defer vv.Res.Reset()
|
defer VtoR(vv).Reset()
|
||||||
// TODO: if a sufficient number of workers error,
|
// TODO: if a sufficient number of workers error,
|
||||||
// should something be done? Should these restart
|
// should something be done? Should these restart
|
||||||
// after perma-failure if we have a graph change?
|
// after perma-failure if we have a graph change?
|
||||||
log.Printf("%s[%s]: Started", vv.GetKind(), vv.GetName())
|
log.Printf("%s: Started", VtoR(vv).String())
|
||||||
if err := g.Worker(vv); err != nil { // contains the Watch and CheckApply loops
|
if err := Worker(g, vv); err != nil { // contains the Watch and CheckApply loops
|
||||||
log.Printf("%s[%s]: Exited with failure: %v", vv.GetKind(), vv.GetName(), err)
|
log.Printf("%s: Exited with failure: %v", VtoR(vv).String(), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.Printf("%s[%s]: Exited", vv.GetKind(), vv.GetName())
|
log.Printf("%s: Exited", VtoR(vv).String())
|
||||||
}(v)
|
}(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case <-v.Res.Started(): // block until started
|
case <-VtoR(v).Started(): // block until started
|
||||||
case <-v.Res.Stopped(): // we failed on init
|
case <-VtoR(v).Stopped(): // we failed on init
|
||||||
// if the resource Init() fails, we don't hang!
|
// if the resource Init() fails, we don't hang!
|
||||||
}
|
}
|
||||||
|
|
||||||
if unpause { // unpause (if needed)
|
if unpause { // unpause (if needed)
|
||||||
v.Res.SendEvent(event.EventStart, nil) // sync!
|
VtoR(v).SendEvent(event.EventStart, nil) // sync!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// we wait for everyone to start before exiting!
|
// we wait for everyone to start before exiting!
|
||||||
@@ -736,27 +739,27 @@ func (g *Graph) Start(first bool) { // start or continue
|
|||||||
// Pause sends pause events to the graph in a topological sort order. If you set
|
// Pause sends pause events to the graph in a topological sort order. If you set
|
||||||
// the fastPause argument to true, then it will ask future propagation waves to
|
// the fastPause argument to true, then it will ask future propagation waves to
|
||||||
// not run through the graph before exiting, and instead will exit much quicker.
|
// not run through the graph before exiting, and instead will exit much quicker.
|
||||||
func (g *Graph) Pause(fastPause bool) {
|
func Pause(g *pgraph.Graph, fastPause bool) {
|
||||||
log.Printf("State: %v -> %v", g.setState(graphStatePausing), g.getState())
|
log.Printf("State: %v -> %v", setState(g, graphStatePausing), getState(g))
|
||||||
defer log.Printf("State: %v -> %v", g.setState(graphStatePaused), g.getState())
|
defer log.Printf("State: %v -> %v", setState(g, graphStatePaused), getState(g))
|
||||||
if fastPause {
|
if fastPause {
|
||||||
g.fastPause = true // set flag
|
g.SetValue("fastpause", true) // set flag
|
||||||
}
|
}
|
||||||
t, _ := g.TopologicalSort()
|
t, _ := g.TopologicalSort()
|
||||||
for _, v := range t { // squeeze out the events...
|
for _, v := range t { // squeeze out the events...
|
||||||
v.SendEvent(event.EventPause, nil) // sync
|
VtoR(v).SendEvent(event.EventPause, nil) // sync
|
||||||
}
|
}
|
||||||
g.fastPause = false // reset flag
|
g.SetValue("fastpause", false) // reset flag
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exit sends exit events to the graph in a topological sort order.
|
// Exit sends exit events to the graph in a topological sort order.
|
||||||
func (g *Graph) Exit() {
|
func Exit(g *pgraph.Graph) {
|
||||||
if g == nil { // empty graph that wasn't populated yet
|
if g == nil { // empty graph that wasn't populated yet
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: a second ^C could put this into fast pause, but do it for now!
|
// FIXME: a second ^C could put this into fast pause, but do it for now!
|
||||||
g.Pause(true) // implement this with pause to avoid duplicating the code
|
Pause(g, true) // implement this with pause to avoid duplicating the code
|
||||||
|
|
||||||
t, _ := g.TopologicalSort()
|
t, _ := g.TopologicalSort()
|
||||||
for _, v := range t { // squeeze out the events...
|
for _, v := range t { // squeeze out the events...
|
||||||
@@ -766,8 +769,25 @@ func (g *Graph) Exit() {
|
|||||||
// when we hit the 'default' in the select statement!
|
// when we hit the 'default' in the select statement!
|
||||||
// XXX: we can do this to quiesce, but it's not necessary now
|
// XXX: we can do this to quiesce, but it's not necessary now
|
||||||
|
|
||||||
v.SendEvent(event.EventExit, nil)
|
VtoR(v).SendEvent(event.EventExit, nil)
|
||||||
v.Res.WaitGroup().Wait()
|
VtoR(v).WaitGroup().Wait()
|
||||||
}
|
}
|
||||||
g.wg.Wait() // for now, this doesn't need to be a separate Wait() method
|
gwg := WgFromGraph(g)
|
||||||
|
gwg.Wait() // for now, this doesn't need to be a separate Wait() method
|
||||||
|
}
|
||||||
|
|
||||||
|
// WgFromGraph returns a pointer to the waitgroup stored with the graph,
|
||||||
|
// otherwise it panics. If one does not exist, it will create it.
|
||||||
|
func WgFromGraph(g *pgraph.Graph) *sync.WaitGroup {
|
||||||
|
x, exists := g.Value("waitgroup")
|
||||||
|
if !exists {
|
||||||
|
g.SetValue("waitgroup", &sync.WaitGroup{})
|
||||||
|
x, _ = g.Value("waitgroup")
|
||||||
|
}
|
||||||
|
|
||||||
|
wg, ok := x.(*sync.WaitGroup)
|
||||||
|
if !ok {
|
||||||
|
panic("not a *sync.WaitGroup")
|
||||||
|
}
|
||||||
|
return wg
|
||||||
}
|
}
|
||||||
@@ -37,7 +37,7 @@ func UIDExistsInUIDs(uid ResUID, uids []ResUID) bool {
|
|||||||
|
|
||||||
// addEdgesByMatchingUIDS adds edges to the vertex in a graph based on if it
|
// addEdgesByMatchingUIDS adds edges to the vertex in a graph based on if it
|
||||||
// matches a uid list.
|
// matches a uid list.
|
||||||
func addEdgesByMatchingUIDS(g *pgraph.Graph, v *pgraph.Vertex, uids []ResUID) []bool {
|
func addEdgesByMatchingUIDS(g *pgraph.Graph, v pgraph.Vertex, uids []ResUID) []bool {
|
||||||
// search for edges and see what matches!
|
// search for edges and see what matches!
|
||||||
var result []bool
|
var result []bool
|
||||||
|
|
||||||
@@ -50,22 +50,22 @@ func addEdgesByMatchingUIDS(g *pgraph.Graph, v *pgraph.Vertex, uids []ResUID) []
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if b, ok := g.Value("debug"); ok && util.Bool(b) {
|
if b, ok := g.Value("debug"); ok && util.Bool(b) {
|
||||||
log.Printf("Compile: AutoEdge: Match: %s[%s] with UID: %s[%s]", vv.GetKind(), vv.GetName(), uid.GetKind(), uid.GetName())
|
log.Printf("Compile: AutoEdge: Match: %s with UID: %s[%s]", VtoR(vv).String(), uid.GetKind(), uid.GetName())
|
||||||
}
|
}
|
||||||
// we must match to an effective UID for the resource,
|
// we must match to an effective UID for the resource,
|
||||||
// that is to say, the name value of a res is a helpful
|
// that is to say, the name value of a res is a helpful
|
||||||
// handle, but it is not necessarily a unique identity!
|
// handle, but it is not necessarily a unique identity!
|
||||||
// remember, resources can return multiple UID's each!
|
// remember, resources can return multiple UID's each!
|
||||||
if UIDExistsInUIDs(uid, vv.UIDs()) {
|
if UIDExistsInUIDs(uid, VtoR(vv).UIDs()) {
|
||||||
// add edge from: vv -> v
|
// add edge from: vv -> v
|
||||||
if uid.IsReversed() {
|
if uid.IsReversed() {
|
||||||
txt := fmt.Sprintf("AutoEdge: %s[%s] -> %s[%s]", vv.GetKind(), vv.GetName(), v.GetKind(), v.GetName())
|
txt := fmt.Sprintf("AutoEdge: %s -> %s", VtoR(vv).String(), VtoR(v).String())
|
||||||
log.Printf("Compile: Adding %s", txt)
|
log.Printf("Compile: Adding %s", txt)
|
||||||
g.AddEdge(vv, v, NewEdge(txt))
|
g.AddEdge(vv, v, pgraph.NewEdge(txt))
|
||||||
} else { // edges go the "normal" way, eg: pkg resource
|
} else { // edges go the "normal" way, eg: pkg resource
|
||||||
txt := fmt.Sprintf("AutoEdge: %s[%s] -> %s[%s]", v.GetKind(), v.GetName(), vv.GetKind(), vv.GetName())
|
txt := fmt.Sprintf("AutoEdge: %s -> %s", VtoR(v).String(), VtoR(vv).String())
|
||||||
log.Printf("Compile: Adding %s", txt)
|
log.Printf("Compile: Adding %s", txt)
|
||||||
g.AddEdge(v, vv, NewEdge(txt))
|
g.AddEdge(v, vv, pgraph.NewEdge(txt))
|
||||||
}
|
}
|
||||||
found = true
|
found = true
|
||||||
break
|
break
|
||||||
@@ -80,19 +80,19 @@ func addEdgesByMatchingUIDS(g *pgraph.Graph, v *pgraph.Vertex, uids []ResUID) []
|
|||||||
func AutoEdges(g *pgraph.Graph) {
|
func AutoEdges(g *pgraph.Graph) {
|
||||||
log.Println("Compile: Adding AutoEdges...")
|
log.Println("Compile: Adding AutoEdges...")
|
||||||
for _, v := range g.Vertices() { // for each vertexes autoedges
|
for _, v := range g.Vertices() { // for each vertexes autoedges
|
||||||
if !v.Meta().AutoEdge { // is the metaparam true?
|
if !VtoR(v).Meta().AutoEdge { // is the metaparam true?
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
autoEdgeObj := v.AutoEdges()
|
autoEdgeObj := VtoR(v).AutoEdges()
|
||||||
if autoEdgeObj == nil {
|
if autoEdgeObj == nil {
|
||||||
log.Printf("%s[%s]: Config: No auto edges were found!", v.GetKind(), v.GetName())
|
log.Printf("%s: Config: No auto edges were found!", VtoR(v).String())
|
||||||
continue // next vertex
|
continue // next vertex
|
||||||
}
|
}
|
||||||
|
|
||||||
for { // while the autoEdgeObj has more uids to add...
|
for { // while the autoEdgeObj has more uids to add...
|
||||||
uids := autoEdgeObj.Next() // get some!
|
uids := autoEdgeObj.Next() // get some!
|
||||||
if uids == nil {
|
if uids == nil {
|
||||||
log.Printf("%s[%s]: Config: The auto edge list is empty!", v.GetKind(), v.GetName())
|
log.Printf("%s: Config: The auto edge list is empty!", VtoR(v).String())
|
||||||
break // inner loop
|
break // inner loop
|
||||||
}
|
}
|
||||||
if b, ok := g.Value("debug"); ok && util.Bool(b) {
|
if b, ok := g.Value("debug"); ok && util.Bool(b) {
|
||||||
|
|||||||
@@ -15,12 +15,13 @@
|
|||||||
// You should have received a copy of the GNU Affero General Public License
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
package pgraph
|
package resources
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
|
"github.com/purpleidea/mgmt/pgraph"
|
||||||
"github.com/purpleidea/mgmt/util"
|
"github.com/purpleidea/mgmt/util"
|
||||||
|
|
||||||
errwrap "github.com/pkg/errors"
|
errwrap "github.com/pkg/errors"
|
||||||
@@ -30,18 +31,18 @@ import (
|
|||||||
type AutoGrouper interface {
|
type AutoGrouper interface {
|
||||||
// listed in the order these are typically called in...
|
// listed in the order these are typically called in...
|
||||||
name() string // friendly identifier
|
name() string // friendly identifier
|
||||||
init(*Graph) error // only call once
|
init(*pgraph.Graph) error // only call once
|
||||||
vertexNext() (*Vertex, *Vertex, error) // mostly algorithmic
|
vertexNext() (pgraph.Vertex, pgraph.Vertex, error) // mostly algorithmic
|
||||||
vertexCmp(*Vertex, *Vertex) error // can we merge these ?
|
vertexCmp(pgraph.Vertex, pgraph.Vertex) error // can we merge these ?
|
||||||
vertexMerge(*Vertex, *Vertex) (*Vertex, error) // vertex merge fn to use
|
vertexMerge(pgraph.Vertex, pgraph.Vertex) (pgraph.Vertex, error) // vertex merge fn to use
|
||||||
edgeMerge(*Edge, *Edge) *Edge // edge merge fn to use
|
edgeMerge(*pgraph.Edge, *pgraph.Edge) *pgraph.Edge // edge merge fn to use
|
||||||
vertexTest(bool) (bool, error) // call until false
|
vertexTest(bool) (bool, error) // call until false
|
||||||
}
|
}
|
||||||
|
|
||||||
// baseGrouper is the base type for implementing the AutoGrouper interface
|
// baseGrouper is the base type for implementing the AutoGrouper interface
|
||||||
type baseGrouper struct {
|
type baseGrouper struct {
|
||||||
graph *Graph // store a pointer to the graph
|
graph *pgraph.Graph // store a pointer to the graph
|
||||||
vertices []*Vertex // cached list of vertices
|
vertices []pgraph.Vertex // cached list of vertices
|
||||||
i int
|
i int
|
||||||
j int
|
j int
|
||||||
done bool
|
done bool
|
||||||
@@ -54,7 +55,7 @@ func (ag *baseGrouper) name() string {
|
|||||||
|
|
||||||
// init is called only once and before using other AutoGrouper interface methods
|
// init is called only once and before using other AutoGrouper interface methods
|
||||||
// the name method is the only exception: call it any time without side effects!
|
// the name method is the only exception: call it any time without side effects!
|
||||||
func (ag *baseGrouper) init(g *Graph) error {
|
func (ag *baseGrouper) init(g *pgraph.Graph) error {
|
||||||
if ag.graph != nil {
|
if ag.graph != nil {
|
||||||
return fmt.Errorf("the init method has already been called")
|
return fmt.Errorf("the init method has already been called")
|
||||||
}
|
}
|
||||||
@@ -73,7 +74,7 @@ func (ag *baseGrouper) init(g *Graph) error {
|
|||||||
// an intelligent algorithm would selectively offer only valid pairs of vertices
|
// an intelligent algorithm would selectively offer only valid pairs of vertices
|
||||||
// these should satisfy logical grouping requirements for the autogroup designs!
|
// these should satisfy logical grouping requirements for the autogroup designs!
|
||||||
// the desired algorithms can override, but keep this method as a base iterator!
|
// the desired algorithms can override, but keep this method as a base iterator!
|
||||||
func (ag *baseGrouper) vertexNext() (v1, v2 *Vertex, err error) {
|
func (ag *baseGrouper) vertexNext() (v1, v2 pgraph.Vertex, err error) {
|
||||||
// this does a for v... { for w... { return v, w }} but stepwise!
|
// this does a for v... { for w... { return v, w }} but stepwise!
|
||||||
l := len(ag.vertices)
|
l := len(ag.vertices)
|
||||||
if ag.i < l {
|
if ag.i < l {
|
||||||
@@ -108,43 +109,43 @@ func (ag *baseGrouper) vertexNext() (v1, v2 *Vertex, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ag *baseGrouper) vertexCmp(v1, v2 *Vertex) error {
|
func (ag *baseGrouper) vertexCmp(v1, v2 pgraph.Vertex) error {
|
||||||
if v1 == nil || v2 == nil {
|
if v1 == nil || v2 == nil {
|
||||||
return fmt.Errorf("the vertex is nil")
|
return fmt.Errorf("the vertex is nil")
|
||||||
}
|
}
|
||||||
if v1 == v2 { // skip yourself
|
if v1 == v2 { // skip yourself
|
||||||
return fmt.Errorf("the vertices are the same")
|
return fmt.Errorf("the vertices are the same")
|
||||||
}
|
}
|
||||||
if v1.GetKind() != v2.GetKind() { // we must group similar kinds
|
if VtoR(v1).GetKind() != VtoR(v2).GetKind() { // we must group similar kinds
|
||||||
// TODO: maybe future resources won't need this limitation?
|
// TODO: maybe future resources won't need this limitation?
|
||||||
return fmt.Errorf("the two resources aren't the same kind")
|
return fmt.Errorf("the two resources aren't the same kind")
|
||||||
}
|
}
|
||||||
// someone doesn't want to group!
|
// someone doesn't want to group!
|
||||||
if !v1.Meta().AutoGroup || !v2.Meta().AutoGroup {
|
if !VtoR(v1).Meta().AutoGroup || !VtoR(v2).Meta().AutoGroup {
|
||||||
return fmt.Errorf("one of the autogroup flags is false")
|
return fmt.Errorf("one of the autogroup flags is false")
|
||||||
}
|
}
|
||||||
if v1.Res.IsGrouped() { // already grouped!
|
if VtoR(v1).IsGrouped() { // already grouped!
|
||||||
return fmt.Errorf("already grouped")
|
return fmt.Errorf("already grouped")
|
||||||
}
|
}
|
||||||
if len(v2.Res.GetGroup()) > 0 { // already has children grouped!
|
if len(VtoR(v2).GetGroup()) > 0 { // already has children grouped!
|
||||||
return fmt.Errorf("already has groups")
|
return fmt.Errorf("already has groups")
|
||||||
}
|
}
|
||||||
if !v1.Res.GroupCmp(v2.Res) { // resource groupcmp failed!
|
if !VtoR(v1).GroupCmp(VtoR(v2)) { // resource groupcmp failed!
|
||||||
return fmt.Errorf("the GroupCmp failed")
|
return fmt.Errorf("the GroupCmp failed")
|
||||||
}
|
}
|
||||||
return nil // success
|
return nil // success
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ag *baseGrouper) vertexMerge(v1, v2 *Vertex) (v *Vertex, err error) {
|
func (ag *baseGrouper) vertexMerge(v1, v2 pgraph.Vertex) (v pgraph.Vertex, err error) {
|
||||||
// NOTE: it's important to use w.Res instead of w, b/c
|
// NOTE: it's important to use w.Res instead of w, b/c
|
||||||
// the w by itself is the *Vertex obj, not the *Res obj
|
// the w by itself is the *Vertex obj, not the *Res obj
|
||||||
// which is contained within it! They both satisfy the
|
// which is contained within it! They both satisfy the
|
||||||
// Res interface, which is why both will compile! :(
|
// Res interface, which is why both will compile! :(
|
||||||
err = v1.Res.GroupRes(v2.Res) // GroupRes skips stupid groupings
|
err = VtoR(v1).GroupRes(VtoR(v2)) // GroupRes skips stupid groupings
|
||||||
return // success or fail, and no need to merge the actual vertices!
|
return // success or fail, and no need to merge the actual vertices!
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ag *baseGrouper) edgeMerge(e1, e2 *Edge) *Edge {
|
func (ag *baseGrouper) edgeMerge(e1, e2 *pgraph.Edge) *pgraph.Edge {
|
||||||
return e1 // noop
|
return e1 // noop
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,18 +161,18 @@ func (ag *baseGrouper) vertexTest(b bool) (bool, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: this algorithm may not be correct in all cases. replace if needed!
|
// TODO: this algorithm may not be correct in all cases. replace if needed!
|
||||||
type nonReachabilityGrouper struct {
|
type NonReachabilityGrouper struct {
|
||||||
baseGrouper // "inherit" what we want, and reimplement the rest
|
baseGrouper // "inherit" what we want, and reimplement the rest
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ag *nonReachabilityGrouper) name() string {
|
func (ag *NonReachabilityGrouper) name() string {
|
||||||
return "nonReachabilityGrouper"
|
return "NonReachabilityGrouper"
|
||||||
}
|
}
|
||||||
|
|
||||||
// this algorithm relies on the observation that if there's a path from a to b,
|
// this algorithm relies on the observation that if there's a path from a to b,
|
||||||
// then they *can't* be merged (b/c of the existing dependency) so therefore we
|
// then they *can't* be merged (b/c of the existing dependency) so therefore we
|
||||||
// merge anything that *doesn't* satisfy this condition or that of the reverse!
|
// merge anything that *doesn't* satisfy this condition or that of the reverse!
|
||||||
func (ag *nonReachabilityGrouper) vertexNext() (v1, v2 *Vertex, err error) {
|
func (ag *NonReachabilityGrouper) vertexNext() (v1, v2 pgraph.Vertex, err error) {
|
||||||
for {
|
for {
|
||||||
v1, v2, err = ag.baseGrouper.vertexNext() // get all iterable pairs
|
v1, v2, err = ag.baseGrouper.vertexNext() // get all iterable pairs
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -202,15 +203,15 @@ func (ag *nonReachabilityGrouper) vertexNext() (v1, v2 *Vertex, err error) {
|
|||||||
// and then by deleting v2 from the graph. Since more than one edge between two
|
// and then by deleting v2 from the graph. Since more than one edge between two
|
||||||
// vertices is not allowed, duplicate edges are merged as well. an edge merge
|
// vertices is not allowed, duplicate edges are merged as well. an edge merge
|
||||||
// function can be provided if you'd like to control how you merge the edges!
|
// function can be provided if you'd like to control how you merge the edges!
|
||||||
func (g *Graph) VertexMerge(v1, v2 *Vertex, vertexMergeFn func(*Vertex, *Vertex) (*Vertex, error), edgeMergeFn func(*Edge, *Edge) *Edge) error {
|
func VertexMerge(g *pgraph.Graph, v1, v2 pgraph.Vertex, vertexMergeFn func(pgraph.Vertex, pgraph.Vertex) (pgraph.Vertex, error), edgeMergeFn func(*pgraph.Edge, *pgraph.Edge) *pgraph.Edge) error {
|
||||||
// methodology
|
// methodology
|
||||||
// 1) edges between v1 and v2 are removed
|
// 1) edges between v1 and v2 are removed
|
||||||
//Loop:
|
//Loop:
|
||||||
for k1 := range g.adjacency {
|
for k1 := range g.Adjacency() {
|
||||||
for k2 := range g.adjacency[k1] {
|
for k2 := range g.Adjacency()[k1] {
|
||||||
// v1 -> v2 || v2 -> v1
|
// v1 -> v2 || v2 -> v1
|
||||||
if (k1 == v1 && k2 == v2) || (k1 == v2 && k2 == v1) {
|
if (k1 == v1 && k2 == v2) || (k1 == v2 && k2 == v1) {
|
||||||
delete(g.adjacency[k1], k2) // delete map & edge
|
delete(g.Adjacency()[k1], k2) // delete map & edge
|
||||||
// NOTE: if we assume this is a DAG, then we can
|
// NOTE: if we assume this is a DAG, then we can
|
||||||
// assume only v1 -> v2 OR v2 -> v1 exists, and
|
// assume only v1 -> v2 OR v2 -> v1 exists, and
|
||||||
// we can break out of these loops immediately!
|
// we can break out of these loops immediately!
|
||||||
@@ -222,10 +223,10 @@ func (g *Graph) VertexMerge(v1, v2 *Vertex, vertexMergeFn func(*Vertex, *Vertex)
|
|||||||
|
|
||||||
// 2) edges that point towards v2 from X now point to v1 from X (no dupes)
|
// 2) edges that point towards v2 from X now point to v1 from X (no dupes)
|
||||||
for _, x := range g.IncomingGraphVertices(v2) { // all to vertex v (??? -> v)
|
for _, x := range g.IncomingGraphVertices(v2) { // all to vertex v (??? -> v)
|
||||||
e := g.adjacency[x][v2] // previous edge
|
e := g.Adjacency()[x][v2] // previous edge
|
||||||
r := g.Reachability(x, v1)
|
r := g.Reachability(x, v1)
|
||||||
// merge e with ex := g.adjacency[x][v1] if it exists!
|
// merge e with ex := g.Adjacency()[x][v1] if it exists!
|
||||||
if ex, exists := g.adjacency[x][v1]; exists && edgeMergeFn != nil && len(r) == 0 {
|
if ex, exists := g.Adjacency()[x][v1]; exists && edgeMergeFn != nil && len(r) == 0 {
|
||||||
e = edgeMergeFn(e, ex)
|
e = edgeMergeFn(e, ex)
|
||||||
}
|
}
|
||||||
if len(r) == 0 { // if not reachable, add it
|
if len(r) == 0 { // if not reachable, add it
|
||||||
@@ -238,21 +239,21 @@ func (g *Graph) VertexMerge(v1, v2 *Vertex, vertexMergeFn func(*Vertex, *Vertex)
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// this edge is from: prev, to: next
|
// this edge is from: prev, to: next
|
||||||
ex, _ := g.adjacency[prev][next] // get
|
ex, _ := g.Adjacency()[prev][next] // get
|
||||||
ex = edgeMergeFn(ex, e)
|
ex = edgeMergeFn(ex, e)
|
||||||
g.adjacency[prev][next] = ex // set
|
g.Adjacency()[prev][next] = ex // set
|
||||||
prev = next
|
prev = next
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
delete(g.adjacency[x], v2) // delete old edge
|
delete(g.Adjacency()[x], v2) // delete old edge
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3) edges that point from v2 to X now point from v1 to X (no dupes)
|
// 3) edges that point from v2 to X now point from v1 to X (no dupes)
|
||||||
for _, x := range g.OutgoingGraphVertices(v2) { // all from vertex v (v -> ???)
|
for _, x := range g.OutgoingGraphVertices(v2) { // all from vertex v (v -> ???)
|
||||||
e := g.adjacency[v2][x] // previous edge
|
e := g.Adjacency()[v2][x] // previous edge
|
||||||
r := g.Reachability(v1, x)
|
r := g.Reachability(v1, x)
|
||||||
// merge e with ex := g.adjacency[v1][x] if it exists!
|
// merge e with ex := g.Adjacency()[v1][x] if it exists!
|
||||||
if ex, exists := g.adjacency[v1][x]; exists && edgeMergeFn != nil && len(r) == 0 {
|
if ex, exists := g.Adjacency()[v1][x]; exists && edgeMergeFn != nil && len(r) == 0 {
|
||||||
e = edgeMergeFn(e, ex)
|
e = edgeMergeFn(e, ex)
|
||||||
}
|
}
|
||||||
if len(r) == 0 {
|
if len(r) == 0 {
|
||||||
@@ -265,13 +266,13 @@ func (g *Graph) VertexMerge(v1, v2 *Vertex, vertexMergeFn func(*Vertex, *Vertex)
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// this edge is from: prev, to: next
|
// this edge is from: prev, to: next
|
||||||
ex, _ := g.adjacency[prev][next]
|
ex, _ := g.Adjacency()[prev][next]
|
||||||
ex = edgeMergeFn(ex, e)
|
ex = edgeMergeFn(ex, e)
|
||||||
g.adjacency[prev][next] = ex
|
g.Adjacency()[prev][next] = ex
|
||||||
prev = next
|
prev = next
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
delete(g.adjacency[v2], x)
|
delete(g.Adjacency()[v2], x)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4) merge and then remove the (now merged/grouped) vertex
|
// 4) merge and then remove the (now merged/grouped) vertex
|
||||||
@@ -279,7 +280,8 @@ func (g *Graph) VertexMerge(v1, v2 *Vertex, vertexMergeFn func(*Vertex, *Vertex)
|
|||||||
if v, err := vertexMergeFn(v1, v2); err != nil {
|
if v, err := vertexMergeFn(v1, v2); err != nil {
|
||||||
return err
|
return err
|
||||||
} else if v != nil { // replace v1 with the "merged" version...
|
} else if v != nil { // replace v1 with the "merged" version...
|
||||||
*v1 = *v // TODO: is this safe? (replacing mutexes is undefined!)
|
//*v1 = *v // TODO: is this safe? (replacing mutexes is undefined!)
|
||||||
|
v1 = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
g.DeleteVertex(v2) // remove grouped vertex
|
g.DeleteVertex(v2) // remove grouped vertex
|
||||||
@@ -292,7 +294,7 @@ func (g *Graph) VertexMerge(v1, v2 *Vertex, vertexMergeFn func(*Vertex, *Vertex)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// autoGroup is the mechanical auto group "runner" that runs the interface spec
|
// autoGroup is the mechanical auto group "runner" that runs the interface spec
|
||||||
func (g *Graph) autoGroup(ag AutoGrouper) chan string {
|
func autoGroup(g *pgraph.Graph, ag AutoGrouper) chan string {
|
||||||
strch := make(chan string) // output log messages here
|
strch := make(chan string) // output log messages here
|
||||||
go func(strch chan string) {
|
go func(strch chan string) {
|
||||||
strch <- fmt.Sprintf("Compile: Grouping: Algorithm: %v...", ag.name())
|
strch <- fmt.Sprintf("Compile: Grouping: Algorithm: %v...", ag.name())
|
||||||
@@ -301,7 +303,7 @@ func (g *Graph) autoGroup(ag AutoGrouper) chan string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
var v, w *Vertex
|
var v, w pgraph.Vertex
|
||||||
v, w, err := ag.vertexNext() // get pair to compare
|
v, w, err := ag.vertexNext() // get pair to compare
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("error running autoGroup(vertexNext): %v", err)
|
log.Fatalf("error running autoGroup(vertexNext): %v", err)
|
||||||
@@ -317,7 +319,7 @@ func (g *Graph) autoGroup(ag AutoGrouper) chan string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// remove grouped vertex and merge edges (res is safe)
|
// remove grouped vertex and merge edges (res is safe)
|
||||||
} else if err := g.VertexMerge(v, w, ag.vertexMerge, ag.edgeMerge); err != nil { // merge...
|
} else if err := VertexMerge(g, v, w, ag.vertexMerge, ag.edgeMerge); err != nil { // merge...
|
||||||
strch <- fmt.Sprintf("Compile: Grouping: !VertexMerge for: %s into %s", wStr, vStr)
|
strch <- fmt.Sprintf("Compile: Grouping: !VertexMerge for: %s into %s", wStr, vStr)
|
||||||
|
|
||||||
} else { // success!
|
} else { // success!
|
||||||
@@ -340,11 +342,11 @@ func (g *Graph) autoGroup(ag AutoGrouper) chan string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// AutoGroup runs the auto grouping on the graph and prints out log messages
|
// AutoGroup runs the auto grouping on the graph and prints out log messages
|
||||||
func (g *Graph) AutoGroup() {
|
func AutoGroup(g *pgraph.Graph, ag AutoGrouper) {
|
||||||
// receive log messages from channel...
|
// receive log messages from channel...
|
||||||
// this allows test cases to avoid printing them when they're unwanted!
|
// this allows test cases to avoid printing them when they're unwanted!
|
||||||
// TODO: this algorithm may not be correct in all cases. replace if needed!
|
// TODO: this algorithm may not be correct in all cases. replace if needed!
|
||||||
for str := range g.autoGroup(&nonReachabilityGrouper{}) {
|
for str := range autoGroup(g, ag) {
|
||||||
log.Println(str)
|
log.Println(str)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
721
resources/autogroup_test.go
Normal file
721
resources/autogroup_test.go
Normal file
@@ -0,0 +1,721 @@
|
|||||||
|
// Mgmt
|
||||||
|
// Copyright (C) 2013-2017+ James Shubin and the project contributors
|
||||||
|
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package resources
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/purpleidea/mgmt/pgraph"
|
||||||
|
"github.com/purpleidea/mgmt/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
type testGrouper struct {
|
||||||
|
// TODO: this algorithm may not be correct in all cases. replace if needed!
|
||||||
|
NonReachabilityGrouper // "inherit" what we want, and reimplement the rest
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ag *testGrouper) name() string {
|
||||||
|
return "testGrouper"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ag *testGrouper) vertexMerge(v1, v2 pgraph.Vertex) (v pgraph.Vertex, err error) {
|
||||||
|
if err := VtoR(v1).GroupRes(VtoR(v2)); err != nil { // group them first
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// HACK: update the name so it matches full list of self+grouped
|
||||||
|
obj := VtoR(v1)
|
||||||
|
names := strings.Split(obj.GetName(), ",") // load in stored names
|
||||||
|
for _, n := range obj.GetGroup() {
|
||||||
|
names = append(names, n.GetName()) // add my contents
|
||||||
|
}
|
||||||
|
names = util.StrRemoveDuplicatesInList(names) // remove duplicates
|
||||||
|
sort.Strings(names)
|
||||||
|
obj.SetName(strings.Join(names, ","))
|
||||||
|
return // success or fail, and no need to merge the actual vertices!
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ag *testGrouper) edgeMerge(e1, e2 *pgraph.Edge) *pgraph.Edge {
|
||||||
|
// HACK: update the name so it makes a union of both names
|
||||||
|
n1 := strings.Split(e1.Name, ",") // load
|
||||||
|
n2 := strings.Split(e2.Name, ",") // load
|
||||||
|
names := append(n1, n2...)
|
||||||
|
names = util.StrRemoveDuplicatesInList(names) // remove duplicates
|
||||||
|
sort.Strings(names)
|
||||||
|
return pgraph.NewEdge(strings.Join(names, ","))
|
||||||
|
}
|
||||||
|
|
||||||
|
// helper function
|
||||||
|
func runGraphCmp(t *testing.T, g1, g2 *pgraph.Graph) {
|
||||||
|
AutoGroup(g1, &testGrouper{}) // edits the graph
|
||||||
|
err := GraphCmp(g1, g2)
|
||||||
|
if err != nil {
|
||||||
|
t.Logf(" actual (g1): %v%v", g1, fullPrint(g1))
|
||||||
|
t.Logf("expected (g2): %v%v", g2, fullPrint(g2))
|
||||||
|
t.Logf("Cmp error:")
|
||||||
|
t.Errorf("%v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type NoopResTest struct {
|
||||||
|
NoopRes
|
||||||
|
}
|
||||||
|
|
||||||
|
func (obj *NoopResTest) GroupCmp(r Res) bool {
|
||||||
|
res, ok := r.(*NoopResTest)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: implement this in vertexCmp for *testGrouper instead?
|
||||||
|
if strings.Contains(res.Name, ",") { // HACK
|
||||||
|
return false // element to be grouped is already grouped!
|
||||||
|
}
|
||||||
|
|
||||||
|
// group if they start with the same letter! (helpful hack for testing)
|
||||||
|
return obj.Name[0] == res.Name[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewNoopResTest(name string) *NoopResTest {
|
||||||
|
obj := &NoopResTest{
|
||||||
|
NoopRes: NoopRes{
|
||||||
|
BaseRes: BaseRes{
|
||||||
|
Name: name,
|
||||||
|
MetaParams: MetaParams{
|
||||||
|
AutoGroup: true, // always autogroup
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
|
||||||
|
// GraphCmp compares the topology of two graphs and returns nil if they're equal
|
||||||
|
// It also compares if grouped element groups are identical
|
||||||
|
func GraphCmp(g1, g2 *pgraph.Graph) error {
|
||||||
|
if n1, n2 := g1.NumVertices(), g2.NumVertices(); n1 != n2 {
|
||||||
|
return fmt.Errorf("graph g1 has %d vertices, while g2 has %d", n1, n2)
|
||||||
|
}
|
||||||
|
if e1, e2 := g1.NumEdges(), g2.NumEdges(); e1 != e2 {
|
||||||
|
return fmt.Errorf("graph g1 has %d edges, while g2 has %d", e1, e2)
|
||||||
|
}
|
||||||
|
|
||||||
|
var m = make(map[pgraph.Vertex]pgraph.Vertex) // g1 to g2 vertex correspondence
|
||||||
|
Loop:
|
||||||
|
// check vertices
|
||||||
|
for v1 := range g1.Adjacency() { // for each vertex in g1
|
||||||
|
|
||||||
|
l1 := strings.Split(VtoR(v1).GetName(), ",") // make list of everyone's names...
|
||||||
|
for _, x1 := range VtoR(v1).GetGroup() {
|
||||||
|
l1 = append(l1, x1.GetName()) // add my contents
|
||||||
|
}
|
||||||
|
l1 = util.StrRemoveDuplicatesInList(l1) // remove duplicates
|
||||||
|
sort.Strings(l1)
|
||||||
|
|
||||||
|
// inner loop
|
||||||
|
for v2 := range g2.Adjacency() { // does it match in g2 ?
|
||||||
|
|
||||||
|
l2 := strings.Split(VtoR(v2).GetName(), ",")
|
||||||
|
for _, x2 := range VtoR(v2).GetGroup() {
|
||||||
|
l2 = append(l2, x2.GetName())
|
||||||
|
}
|
||||||
|
l2 = util.StrRemoveDuplicatesInList(l2) // remove duplicates
|
||||||
|
sort.Strings(l2)
|
||||||
|
|
||||||
|
// does l1 match l2 ?
|
||||||
|
if ListStrCmp(l1, l2) { // cmp!
|
||||||
|
m[v1] = v2
|
||||||
|
continue Loop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fmt.Errorf("graph g1, has no match in g2 for: %v", VtoR(v1).GetName())
|
||||||
|
}
|
||||||
|
// vertices (and groups) match :)
|
||||||
|
|
||||||
|
// check edges
|
||||||
|
for v1 := range g1.Adjacency() { // for each vertex in g1
|
||||||
|
v2 := m[v1] // lookup in map to get correspondance
|
||||||
|
// g1.Adjacency()[v1] corresponds to g2.Adjacency()[v2]
|
||||||
|
if e1, e2 := len(g1.Adjacency()[v1]), len(g2.Adjacency()[v2]); e1 != e2 {
|
||||||
|
return fmt.Errorf("graph g1, vertex(%v) has %d edges, while g2, vertex(%v) has %d", VtoR(v1).GetName(), e1, VtoR(v2).GetName(), e2)
|
||||||
|
}
|
||||||
|
|
||||||
|
for vv1, ee1 := range g1.Adjacency()[v1] {
|
||||||
|
vv2 := m[vv1]
|
||||||
|
ee2 := g2.Adjacency()[v2][vv2]
|
||||||
|
|
||||||
|
// these are edges from v1 -> vv1 via ee1 (graph 1)
|
||||||
|
// to cmp to edges from v2 -> vv2 via ee2 (graph 2)
|
||||||
|
|
||||||
|
// check: (1) vv1 == vv2 ? (we've already checked this!)
|
||||||
|
l1 := strings.Split(VtoR(vv1).GetName(), ",") // make list of everyone's names...
|
||||||
|
for _, x1 := range VtoR(vv1).GetGroup() {
|
||||||
|
l1 = append(l1, x1.GetName()) // add my contents
|
||||||
|
}
|
||||||
|
l1 = util.StrRemoveDuplicatesInList(l1) // remove duplicates
|
||||||
|
sort.Strings(l1)
|
||||||
|
|
||||||
|
l2 := strings.Split(VtoR(vv2).GetName(), ",")
|
||||||
|
for _, x2 := range VtoR(vv2).GetGroup() {
|
||||||
|
l2 = append(l2, x2.GetName())
|
||||||
|
}
|
||||||
|
l2 = util.StrRemoveDuplicatesInList(l2) // remove duplicates
|
||||||
|
sort.Strings(l2)
|
||||||
|
|
||||||
|
// does l1 match l2 ?
|
||||||
|
if !ListStrCmp(l1, l2) { // cmp!
|
||||||
|
return fmt.Errorf("graph g1 and g2 don't agree on: %v and %v", VtoR(vv1).GetName(), VtoR(vv2).GetName())
|
||||||
|
}
|
||||||
|
|
||||||
|
// check: (2) ee1 == ee2
|
||||||
|
if ee1.Name != ee2.Name {
|
||||||
|
return fmt.Errorf("graph g1 edge(%v) doesn't match g2 edge(%v)", ee1.Name, ee2.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check meta parameters
|
||||||
|
for v1 := range g1.Adjacency() { // for each vertex in g1
|
||||||
|
for v2 := range g2.Adjacency() { // does it match in g2 ?
|
||||||
|
s1, s2 := VtoR(v1).Meta().Sema, VtoR(v2).Meta().Sema
|
||||||
|
sort.Strings(s1)
|
||||||
|
sort.Strings(s2)
|
||||||
|
if !reflect.DeepEqual(s1, s2) {
|
||||||
|
return fmt.Errorf("vertex %s and vertex %s have different semaphores", VtoR(v1).GetName(), VtoR(v2).GetName())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil // success!
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListStrCmp compares two lists of strings
|
||||||
|
func ListStrCmp(a, b []string) bool {
|
||||||
|
//fmt.Printf("CMP: %v with %v\n", a, b) // debugging
|
||||||
|
if a == nil && b == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if a == nil || b == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if len(a) != len(b) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for i := range a {
|
||||||
|
if a[i] != b[i] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func fullPrint(g *pgraph.Graph) (str string) {
|
||||||
|
str += "\n"
|
||||||
|
for v := range g.Adjacency() {
|
||||||
|
if semas := VtoR(v).Meta().Sema; len(semas) > 0 {
|
||||||
|
str += fmt.Sprintf("* v: %v; sema: %v\n", VtoR(v).GetName(), semas)
|
||||||
|
} else {
|
||||||
|
str += fmt.Sprintf("* v: %v\n", VtoR(v).GetName())
|
||||||
|
}
|
||||||
|
// TODO: add explicit grouping data?
|
||||||
|
}
|
||||||
|
for v1 := range g.Adjacency() {
|
||||||
|
for v2, e := range g.Adjacency()[v1] {
|
||||||
|
str += fmt.Sprintf("* e: %v -> %v # %v\n", VtoR(v1).GetName(), VtoR(v2).GetName(), e.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDurationAssumptions(t *testing.T) {
|
||||||
|
var d time.Duration
|
||||||
|
if (d == 0) != true {
|
||||||
|
t.Errorf("empty time.Duration is no longer equal to zero")
|
||||||
|
}
|
||||||
|
if (d > 0) != false {
|
||||||
|
t.Errorf("empty time.Duration is now greater than zero")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// all of the following test cases are laid out with the following semantics:
|
||||||
|
// * vertices which start with the same single letter are considered "like"
|
||||||
|
// * "like" elements should be merged
|
||||||
|
// * vertices can have any integer after their single letter "family" type
|
||||||
|
// * grouped vertices should have a name with a comma separated list of names
|
||||||
|
// * edges follow the same conventions about grouping
|
||||||
|
|
||||||
|
// empty graph
|
||||||
|
func TestPgraphGrouping1(t *testing.T) {
|
||||||
|
g1, _ := pgraph.NewGraph("g1") // original graph
|
||||||
|
g2, _ := pgraph.NewGraph("g2") // expected result
|
||||||
|
runGraphCmp(t, g1, g2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// single vertex
|
||||||
|
func TestPgraphGrouping2(t *testing.T) {
|
||||||
|
g1, _ := pgraph.NewGraph("g1") // original graph
|
||||||
|
{ // grouping to limit variable scope
|
||||||
|
a1 := pgraph.NewVertex(NewNoopResTest("a1"))
|
||||||
|
g1.AddVertex(a1)
|
||||||
|
}
|
||||||
|
g2, _ := pgraph.NewGraph("g2") // expected result
|
||||||
|
{
|
||||||
|
a1 := pgraph.NewVertex(NewNoopResTest("a1"))
|
||||||
|
g2.AddVertex(a1)
|
||||||
|
}
|
||||||
|
runGraphCmp(t, g1, g2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// two vertices
|
||||||
|
func TestPgraphGrouping3(t *testing.T) {
|
||||||
|
g1, _ := pgraph.NewGraph("g1") // original graph
|
||||||
|
{
|
||||||
|
a1 := pgraph.NewVertex(NewNoopResTest("a1"))
|
||||||
|
b1 := pgraph.NewVertex(NewNoopResTest("b1"))
|
||||||
|
g1.AddVertex(a1, b1)
|
||||||
|
}
|
||||||
|
g2, _ := pgraph.NewGraph("g2") // expected result
|
||||||
|
{
|
||||||
|
a1 := pgraph.NewVertex(NewNoopResTest("a1"))
|
||||||
|
b1 := pgraph.NewVertex(NewNoopResTest("b1"))
|
||||||
|
g2.AddVertex(a1, b1)
|
||||||
|
}
|
||||||
|
runGraphCmp(t, g1, g2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// two vertices merge
|
||||||
|
func TestPgraphGrouping4(t *testing.T) {
|
||||||
|
g1, _ := pgraph.NewGraph("g1") // original graph
|
||||||
|
{
|
||||||
|
a1 := pgraph.NewVertex(NewNoopResTest("a1"))
|
||||||
|
a2 := pgraph.NewVertex(NewNoopResTest("a2"))
|
||||||
|
g1.AddVertex(a1, a2)
|
||||||
|
}
|
||||||
|
g2, _ := pgraph.NewGraph("g2") // expected result
|
||||||
|
{
|
||||||
|
a := pgraph.NewVertex(NewNoopResTest("a1,a2"))
|
||||||
|
g2.AddVertex(a)
|
||||||
|
}
|
||||||
|
runGraphCmp(t, g1, g2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// three vertices merge
|
||||||
|
func TestPgraphGrouping5(t *testing.T) {
|
||||||
|
g1, _ := pgraph.NewGraph("g1") // original graph
|
||||||
|
{
|
||||||
|
a1 := pgraph.NewVertex(NewNoopResTest("a1"))
|
||||||
|
a2 := pgraph.NewVertex(NewNoopResTest("a2"))
|
||||||
|
a3 := pgraph.NewVertex(NewNoopResTest("a3"))
|
||||||
|
g1.AddVertex(a1, a2, a3)
|
||||||
|
}
|
||||||
|
g2, _ := pgraph.NewGraph("g2") // expected result
|
||||||
|
{
|
||||||
|
a := pgraph.NewVertex(NewNoopResTest("a1,a2,a3"))
|
||||||
|
g2.AddVertex(a)
|
||||||
|
}
|
||||||
|
runGraphCmp(t, g1, g2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// three vertices, two merge
|
||||||
|
func TestPgraphGrouping6(t *testing.T) {
|
||||||
|
g1, _ := pgraph.NewGraph("g1") // original graph
|
||||||
|
{
|
||||||
|
a1 := pgraph.NewVertex(NewNoopResTest("a1"))
|
||||||
|
a2 := pgraph.NewVertex(NewNoopResTest("a2"))
|
||||||
|
b1 := pgraph.NewVertex(NewNoopResTest("b1"))
|
||||||
|
g1.AddVertex(a1, a2, b1)
|
||||||
|
}
|
||||||
|
g2, _ := pgraph.NewGraph("g2") // expected result
|
||||||
|
{
|
||||||
|
a := pgraph.NewVertex(NewNoopResTest("a1,a2"))
|
||||||
|
b1 := pgraph.NewVertex(NewNoopResTest("b1"))
|
||||||
|
g2.AddVertex(a, b1)
|
||||||
|
}
|
||||||
|
runGraphCmp(t, g1, g2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// four vertices, three merge
|
||||||
|
func TestPgraphGrouping7(t *testing.T) {
|
||||||
|
g1, _ := pgraph.NewGraph("g1") // original graph
|
||||||
|
{
|
||||||
|
a1 := pgraph.NewVertex(NewNoopResTest("a1"))
|
||||||
|
a2 := pgraph.NewVertex(NewNoopResTest("a2"))
|
||||||
|
a3 := pgraph.NewVertex(NewNoopResTest("a3"))
|
||||||
|
b1 := pgraph.NewVertex(NewNoopResTest("b1"))
|
||||||
|
g1.AddVertex(a1, a2, a3, b1)
|
||||||
|
}
|
||||||
|
g2, _ := pgraph.NewGraph("g2") // expected result
|
||||||
|
{
|
||||||
|
a := pgraph.NewVertex(NewNoopResTest("a1,a2,a3"))
|
||||||
|
b1 := pgraph.NewVertex(NewNoopResTest("b1"))
|
||||||
|
g2.AddVertex(a, b1)
|
||||||
|
}
|
||||||
|
runGraphCmp(t, g1, g2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// four vertices, two&two merge
|
||||||
|
func TestPgraphGrouping8(t *testing.T) {
|
||||||
|
g1, _ := pgraph.NewGraph("g1") // original graph
|
||||||
|
{
|
||||||
|
a1 := pgraph.NewVertex(NewNoopResTest("a1"))
|
||||||
|
a2 := pgraph.NewVertex(NewNoopResTest("a2"))
|
||||||
|
b1 := pgraph.NewVertex(NewNoopResTest("b1"))
|
||||||
|
b2 := pgraph.NewVertex(NewNoopResTest("b2"))
|
||||||
|
g1.AddVertex(a1, a2, b1, b2)
|
||||||
|
}
|
||||||
|
g2, _ := pgraph.NewGraph("g2") // expected result
|
||||||
|
{
|
||||||
|
a := pgraph.NewVertex(NewNoopResTest("a1,a2"))
|
||||||
|
b := pgraph.NewVertex(NewNoopResTest("b1,b2"))
|
||||||
|
g2.AddVertex(a, b)
|
||||||
|
}
|
||||||
|
runGraphCmp(t, g1, g2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// five vertices, two&three merge
|
||||||
|
func TestPgraphGrouping9(t *testing.T) {
|
||||||
|
g1, _ := pgraph.NewGraph("g1") // original graph
|
||||||
|
{
|
||||||
|
a1 := pgraph.NewVertex(NewNoopResTest("a1"))
|
||||||
|
a2 := pgraph.NewVertex(NewNoopResTest("a2"))
|
||||||
|
b1 := pgraph.NewVertex(NewNoopResTest("b1"))
|
||||||
|
b2 := pgraph.NewVertex(NewNoopResTest("b2"))
|
||||||
|
b3 := pgraph.NewVertex(NewNoopResTest("b3"))
|
||||||
|
g1.AddVertex(a1, a2, b1, b2, b3)
|
||||||
|
}
|
||||||
|
g2, _ := pgraph.NewGraph("g2") // expected result
|
||||||
|
{
|
||||||
|
a := pgraph.NewVertex(NewNoopResTest("a1,a2"))
|
||||||
|
b := pgraph.NewVertex(NewNoopResTest("b1,b2,b3"))
|
||||||
|
g2.AddVertex(a, b)
|
||||||
|
}
|
||||||
|
runGraphCmp(t, g1, g2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// three unique vertices
|
||||||
|
func TestPgraphGrouping10(t *testing.T) {
|
||||||
|
g1, _ := pgraph.NewGraph("g1") // original graph
|
||||||
|
{
|
||||||
|
a1 := pgraph.NewVertex(NewNoopResTest("a1"))
|
||||||
|
b1 := pgraph.NewVertex(NewNoopResTest("b1"))
|
||||||
|
c1 := pgraph.NewVertex(NewNoopResTest("c1"))
|
||||||
|
g1.AddVertex(a1, b1, c1)
|
||||||
|
}
|
||||||
|
g2, _ := pgraph.NewGraph("g2") // expected result
|
||||||
|
{
|
||||||
|
a1 := pgraph.NewVertex(NewNoopResTest("a1"))
|
||||||
|
b1 := pgraph.NewVertex(NewNoopResTest("b1"))
|
||||||
|
c1 := pgraph.NewVertex(NewNoopResTest("c1"))
|
||||||
|
g2.AddVertex(a1, b1, c1)
|
||||||
|
}
|
||||||
|
runGraphCmp(t, g1, g2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// three unique vertices, two merge
|
||||||
|
func TestPgraphGrouping11(t *testing.T) {
|
||||||
|
g1, _ := pgraph.NewGraph("g1") // original graph
|
||||||
|
{
|
||||||
|
a1 := pgraph.NewVertex(NewNoopResTest("a1"))
|
||||||
|
b1 := pgraph.NewVertex(NewNoopResTest("b1"))
|
||||||
|
b2 := pgraph.NewVertex(NewNoopResTest("b2"))
|
||||||
|
c1 := pgraph.NewVertex(NewNoopResTest("c1"))
|
||||||
|
g1.AddVertex(a1, b1, b2, c1)
|
||||||
|
}
|
||||||
|
g2, _ := pgraph.NewGraph("g2") // expected result
|
||||||
|
{
|
||||||
|
a1 := pgraph.NewVertex(NewNoopResTest("a1"))
|
||||||
|
b := pgraph.NewVertex(NewNoopResTest("b1,b2"))
|
||||||
|
c1 := pgraph.NewVertex(NewNoopResTest("c1"))
|
||||||
|
g2.AddVertex(a1, b, c1)
|
||||||
|
}
|
||||||
|
runGraphCmp(t, g1, g2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// simple merge 1
|
||||||
|
// a1 a2 a1,a2
|
||||||
|
// \ / >>> | (arrows point downwards)
|
||||||
|
// b b
|
||||||
|
func TestPgraphGrouping12(t *testing.T) {
|
||||||
|
g1, _ := pgraph.NewGraph("g1") // original graph
|
||||||
|
{
|
||||||
|
a1 := pgraph.NewVertex(NewNoopResTest("a1"))
|
||||||
|
a2 := pgraph.NewVertex(NewNoopResTest("a2"))
|
||||||
|
b1 := pgraph.NewVertex(NewNoopResTest("b1"))
|
||||||
|
e1 := pgraph.NewEdge("e1")
|
||||||
|
e2 := pgraph.NewEdge("e2")
|
||||||
|
g1.AddEdge(a1, b1, e1)
|
||||||
|
g1.AddEdge(a2, b1, e2)
|
||||||
|
}
|
||||||
|
g2, _ := pgraph.NewGraph("g2") // expected result
|
||||||
|
{
|
||||||
|
a := pgraph.NewVertex(NewNoopResTest("a1,a2"))
|
||||||
|
b1 := pgraph.NewVertex(NewNoopResTest("b1"))
|
||||||
|
e := pgraph.NewEdge("e1,e2")
|
||||||
|
g2.AddEdge(a, b1, e)
|
||||||
|
}
|
||||||
|
runGraphCmp(t, g1, g2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// simple merge 2
|
||||||
|
// b b
|
||||||
|
// / \ >>> | (arrows point downwards)
|
||||||
|
// a1 a2 a1,a2
|
||||||
|
func TestPgraphGrouping13(t *testing.T) {
|
||||||
|
g1, _ := pgraph.NewGraph("g1") // original graph
|
||||||
|
{
|
||||||
|
a1 := pgraph.NewVertex(NewNoopResTest("a1"))
|
||||||
|
a2 := pgraph.NewVertex(NewNoopResTest("a2"))
|
||||||
|
b1 := pgraph.NewVertex(NewNoopResTest("b1"))
|
||||||
|
e1 := pgraph.NewEdge("e1")
|
||||||
|
e2 := pgraph.NewEdge("e2")
|
||||||
|
g1.AddEdge(b1, a1, e1)
|
||||||
|
g1.AddEdge(b1, a2, e2)
|
||||||
|
}
|
||||||
|
g2, _ := pgraph.NewGraph("g2") // expected result
|
||||||
|
{
|
||||||
|
a := pgraph.NewVertex(NewNoopResTest("a1,a2"))
|
||||||
|
b1 := pgraph.NewVertex(NewNoopResTest("b1"))
|
||||||
|
e := pgraph.NewEdge("e1,e2")
|
||||||
|
g2.AddEdge(b1, a, e)
|
||||||
|
}
|
||||||
|
runGraphCmp(t, g1, g2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// triple merge
|
||||||
|
// a1 a2 a3 a1,a2,a3
|
||||||
|
// \ | / >>> | (arrows point downwards)
|
||||||
|
// b b
|
||||||
|
func TestPgraphGrouping14(t *testing.T) {
|
||||||
|
g1, _ := pgraph.NewGraph("g1") // original graph
|
||||||
|
{
|
||||||
|
a1 := pgraph.NewVertex(NewNoopResTest("a1"))
|
||||||
|
a2 := pgraph.NewVertex(NewNoopResTest("a2"))
|
||||||
|
a3 := pgraph.NewVertex(NewNoopResTest("a3"))
|
||||||
|
b1 := pgraph.NewVertex(NewNoopResTest("b1"))
|
||||||
|
e1 := pgraph.NewEdge("e1")
|
||||||
|
e2 := pgraph.NewEdge("e2")
|
||||||
|
e3 := pgraph.NewEdge("e3")
|
||||||
|
g1.AddEdge(a1, b1, e1)
|
||||||
|
g1.AddEdge(a2, b1, e2)
|
||||||
|
g1.AddEdge(a3, b1, e3)
|
||||||
|
}
|
||||||
|
g2, _ := pgraph.NewGraph("g2") // expected result
|
||||||
|
{
|
||||||
|
a := pgraph.NewVertex(NewNoopResTest("a1,a2,a3"))
|
||||||
|
b1 := pgraph.NewVertex(NewNoopResTest("b1"))
|
||||||
|
e := pgraph.NewEdge("e1,e2,e3")
|
||||||
|
g2.AddEdge(a, b1, e)
|
||||||
|
}
|
||||||
|
runGraphCmp(t, g1, g2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// chain merge
|
||||||
|
// a1 a1
|
||||||
|
// / \ |
|
||||||
|
// b1 b2 >>> b1,b2 (arrows point downwards)
|
||||||
|
// \ / |
|
||||||
|
// c1 c1
|
||||||
|
func TestPgraphGrouping15(t *testing.T) {
|
||||||
|
g1, _ := pgraph.NewGraph("g1") // original graph
|
||||||
|
{
|
||||||
|
a1 := pgraph.NewVertex(NewNoopResTest("a1"))
|
||||||
|
b1 := pgraph.NewVertex(NewNoopResTest("b1"))
|
||||||
|
b2 := pgraph.NewVertex(NewNoopResTest("b2"))
|
||||||
|
c1 := pgraph.NewVertex(NewNoopResTest("c1"))
|
||||||
|
e1 := pgraph.NewEdge("e1")
|
||||||
|
e2 := pgraph.NewEdge("e2")
|
||||||
|
e3 := pgraph.NewEdge("e3")
|
||||||
|
e4 := pgraph.NewEdge("e4")
|
||||||
|
g1.AddEdge(a1, b1, e1)
|
||||||
|
g1.AddEdge(a1, b2, e2)
|
||||||
|
g1.AddEdge(b1, c1, e3)
|
||||||
|
g1.AddEdge(b2, c1, e4)
|
||||||
|
}
|
||||||
|
g2, _ := pgraph.NewGraph("g2") // expected result
|
||||||
|
{
|
||||||
|
a1 := pgraph.NewVertex(NewNoopResTest("a1"))
|
||||||
|
b := pgraph.NewVertex(NewNoopResTest("b1,b2"))
|
||||||
|
c1 := pgraph.NewVertex(NewNoopResTest("c1"))
|
||||||
|
e1 := pgraph.NewEdge("e1,e2")
|
||||||
|
e2 := pgraph.NewEdge("e3,e4")
|
||||||
|
g2.AddEdge(a1, b, e1)
|
||||||
|
g2.AddEdge(b, c1, e2)
|
||||||
|
}
|
||||||
|
runGraphCmp(t, g1, g2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// re-attach 1 (outer)
|
||||||
|
// technically the second possibility is valid too, depending on which order we
|
||||||
|
// merge edges in, and if we don't filter out any unnecessary edges afterwards!
|
||||||
|
// a1 a2 a1,a2 a1,a2
|
||||||
|
// | / | | \
|
||||||
|
// b1 / >>> b1 OR b1 / (arrows point downwards)
|
||||||
|
// | / | | /
|
||||||
|
// c1 c1 c1
|
||||||
|
func TestPgraphGrouping16(t *testing.T) {
|
||||||
|
g1, _ := pgraph.NewGraph("g1") // original graph
|
||||||
|
{
|
||||||
|
a1 := pgraph.NewVertex(NewNoopResTest("a1"))
|
||||||
|
a2 := pgraph.NewVertex(NewNoopResTest("a2"))
|
||||||
|
b1 := pgraph.NewVertex(NewNoopResTest("b1"))
|
||||||
|
c1 := pgraph.NewVertex(NewNoopResTest("c1"))
|
||||||
|
e1 := pgraph.NewEdge("e1")
|
||||||
|
e2 := pgraph.NewEdge("e2")
|
||||||
|
e3 := pgraph.NewEdge("e3")
|
||||||
|
g1.AddEdge(a1, b1, e1)
|
||||||
|
g1.AddEdge(b1, c1, e2)
|
||||||
|
g1.AddEdge(a2, c1, e3)
|
||||||
|
}
|
||||||
|
g2, _ := pgraph.NewGraph("g2") // expected result
|
||||||
|
{
|
||||||
|
a := pgraph.NewVertex(NewNoopResTest("a1,a2"))
|
||||||
|
b1 := pgraph.NewVertex(NewNoopResTest("b1"))
|
||||||
|
c1 := pgraph.NewVertex(NewNoopResTest("c1"))
|
||||||
|
e1 := pgraph.NewEdge("e1,e3")
|
||||||
|
e2 := pgraph.NewEdge("e2,e3") // e3 gets "merged through" to BOTH edges!
|
||||||
|
g2.AddEdge(a, b1, e1)
|
||||||
|
g2.AddEdge(b1, c1, e2)
|
||||||
|
}
|
||||||
|
runGraphCmp(t, g1, g2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// re-attach 2 (inner)
|
||||||
|
// a1 b2 a1
|
||||||
|
// | / |
|
||||||
|
// b1 / >>> b1,b2 (arrows point downwards)
|
||||||
|
// | / |
|
||||||
|
// c1 c1
|
||||||
|
func TestPgraphGrouping17(t *testing.T) {
|
||||||
|
g1, _ := pgraph.NewGraph("g1") // original graph
|
||||||
|
{
|
||||||
|
a1 := pgraph.NewVertex(NewNoopResTest("a1"))
|
||||||
|
b1 := pgraph.NewVertex(NewNoopResTest("b1"))
|
||||||
|
b2 := pgraph.NewVertex(NewNoopResTest("b2"))
|
||||||
|
c1 := pgraph.NewVertex(NewNoopResTest("c1"))
|
||||||
|
e1 := pgraph.NewEdge("e1")
|
||||||
|
e2 := pgraph.NewEdge("e2")
|
||||||
|
e3 := pgraph.NewEdge("e3")
|
||||||
|
g1.AddEdge(a1, b1, e1)
|
||||||
|
g1.AddEdge(b1, c1, e2)
|
||||||
|
g1.AddEdge(b2, c1, e3)
|
||||||
|
}
|
||||||
|
g2, _ := pgraph.NewGraph("g2") // expected result
|
||||||
|
{
|
||||||
|
a1 := pgraph.NewVertex(NewNoopResTest("a1"))
|
||||||
|
b := pgraph.NewVertex(NewNoopResTest("b1,b2"))
|
||||||
|
c1 := pgraph.NewVertex(NewNoopResTest("c1"))
|
||||||
|
e1 := pgraph.NewEdge("e1")
|
||||||
|
e2 := pgraph.NewEdge("e2,e3")
|
||||||
|
g2.AddEdge(a1, b, e1)
|
||||||
|
g2.AddEdge(b, c1, e2)
|
||||||
|
}
|
||||||
|
runGraphCmp(t, g1, g2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// re-attach 3 (double)
|
||||||
|
// similar to "re-attach 1", technically there is a second possibility for this
|
||||||
|
// a2 a1 b2 a1,a2
|
||||||
|
// \ | / |
|
||||||
|
// \ b1 / >>> b1,b2 (arrows point downwards)
|
||||||
|
// \ | / |
|
||||||
|
// c1 c1
|
||||||
|
func TestPgraphGrouping18(t *testing.T) {
|
||||||
|
g1, _ := pgraph.NewGraph("g1") // original graph
|
||||||
|
{
|
||||||
|
a1 := pgraph.NewVertex(NewNoopResTest("a1"))
|
||||||
|
a2 := pgraph.NewVertex(NewNoopResTest("a2"))
|
||||||
|
b1 := pgraph.NewVertex(NewNoopResTest("b1"))
|
||||||
|
b2 := pgraph.NewVertex(NewNoopResTest("b2"))
|
||||||
|
c1 := pgraph.NewVertex(NewNoopResTest("c1"))
|
||||||
|
e1 := pgraph.NewEdge("e1")
|
||||||
|
e2 := pgraph.NewEdge("e2")
|
||||||
|
e3 := pgraph.NewEdge("e3")
|
||||||
|
e4 := pgraph.NewEdge("e4")
|
||||||
|
g1.AddEdge(a1, b1, e1)
|
||||||
|
g1.AddEdge(b1, c1, e2)
|
||||||
|
g1.AddEdge(a2, c1, e3)
|
||||||
|
g1.AddEdge(b2, c1, e4)
|
||||||
|
}
|
||||||
|
g2, _ := pgraph.NewGraph("g2") // expected result
|
||||||
|
{
|
||||||
|
a := pgraph.NewVertex(NewNoopResTest("a1,a2"))
|
||||||
|
b := pgraph.NewVertex(NewNoopResTest("b1,b2"))
|
||||||
|
c1 := pgraph.NewVertex(NewNoopResTest("c1"))
|
||||||
|
e1 := pgraph.NewEdge("e1,e3")
|
||||||
|
e2 := pgraph.NewEdge("e2,e3,e4") // e3 gets "merged through" to BOTH edges!
|
||||||
|
g2.AddEdge(a, b, e1)
|
||||||
|
g2.AddEdge(b, c1, e2)
|
||||||
|
}
|
||||||
|
runGraphCmp(t, g1, g2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// connected merge 0, (no change!)
|
||||||
|
// a1 a1
|
||||||
|
// \ >>> \ (arrows point downwards)
|
||||||
|
// a2 a2
|
||||||
|
func TestPgraphGroupingConnected0(t *testing.T) {
|
||||||
|
g1, _ := pgraph.NewGraph("g1") // original graph
|
||||||
|
{
|
||||||
|
a1 := pgraph.NewVertex(NewNoopResTest("a1"))
|
||||||
|
a2 := pgraph.NewVertex(NewNoopResTest("a2"))
|
||||||
|
e1 := pgraph.NewEdge("e1")
|
||||||
|
g1.AddEdge(a1, a2, e1)
|
||||||
|
}
|
||||||
|
g2, _ := pgraph.NewGraph("g2") // expected result ?
|
||||||
|
{
|
||||||
|
a1 := pgraph.NewVertex(NewNoopResTest("a1"))
|
||||||
|
a2 := pgraph.NewVertex(NewNoopResTest("a2"))
|
||||||
|
e1 := pgraph.NewEdge("e1")
|
||||||
|
g2.AddEdge(a1, a2, e1)
|
||||||
|
}
|
||||||
|
runGraphCmp(t, g1, g2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// connected merge 1, (no change!)
|
||||||
|
// a1 a1
|
||||||
|
// \ \
|
||||||
|
// b >>> b (arrows point downwards)
|
||||||
|
// \ \
|
||||||
|
// a2 a2
|
||||||
|
func TestPgraphGroupingConnected1(t *testing.T) {
|
||||||
|
g1, _ := pgraph.NewGraph("g1") // original graph
|
||||||
|
{
|
||||||
|
a1 := pgraph.NewVertex(NewNoopResTest("a1"))
|
||||||
|
b := pgraph.NewVertex(NewNoopResTest("b"))
|
||||||
|
a2 := pgraph.NewVertex(NewNoopResTest("a2"))
|
||||||
|
e1 := pgraph.NewEdge("e1")
|
||||||
|
e2 := pgraph.NewEdge("e2")
|
||||||
|
g1.AddEdge(a1, b, e1)
|
||||||
|
g1.AddEdge(b, a2, e2)
|
||||||
|
}
|
||||||
|
g2, _ := pgraph.NewGraph("g2") // expected result ?
|
||||||
|
{
|
||||||
|
a1 := pgraph.NewVertex(NewNoopResTest("a1"))
|
||||||
|
b := pgraph.NewVertex(NewNoopResTest("b"))
|
||||||
|
a2 := pgraph.NewVertex(NewNoopResTest("a2"))
|
||||||
|
e1 := pgraph.NewEdge("e1")
|
||||||
|
e2 := pgraph.NewEdge("e2")
|
||||||
|
g2.AddEdge(a1, b, e1)
|
||||||
|
g2.AddEdge(b, a2, e2)
|
||||||
|
}
|
||||||
|
runGraphCmp(t, g1, g2)
|
||||||
|
}
|
||||||
@@ -19,9 +19,13 @@ package resources
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/purpleidea/mgmt/event"
|
||||||
"github.com/purpleidea/mgmt/pgraph"
|
"github.com/purpleidea/mgmt/pgraph"
|
||||||
|
"github.com/purpleidea/mgmt/util"
|
||||||
|
|
||||||
|
errwrap "github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:generate stringer -type=graphState -output=graphstate_stringer.go
|
//go:generate stringer -type=graphState -output=graphstate_stringer.go
|
||||||
@@ -43,7 +47,7 @@ func getState(g *pgraph.Graph) graphState {
|
|||||||
//mutex.Lock()
|
//mutex.Lock()
|
||||||
//defer mutex.Unlock()
|
//defer mutex.Unlock()
|
||||||
if u, ok := g.Value("state"); ok {
|
if u, ok := g.Value("state"); ok {
|
||||||
return util.Uint(u)
|
return graphState(util.Uint(u))
|
||||||
}
|
}
|
||||||
return graphStateNil
|
return graphStateNil
|
||||||
}
|
}
|
||||||
@@ -54,7 +58,7 @@ func setState(g *pgraph.Graph, state graphState) graphState {
|
|||||||
mutex.Lock()
|
mutex.Lock()
|
||||||
defer mutex.Unlock()
|
defer mutex.Unlock()
|
||||||
prev := getState(g)
|
prev := getState(g)
|
||||||
g.SetValue("state", state)
|
g.SetValue("state", uint(state))
|
||||||
return prev
|
return prev
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,3 +77,113 @@ func StateLockFromGraph(g *pgraph.Graph) *sync.Mutex {
|
|||||||
}
|
}
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// VtoR casts the Vertex into a Res for use. It panics if it can't convert.
|
||||||
|
func VtoR(v pgraph.Vertex) Res {
|
||||||
|
res, ok := v.(Res)
|
||||||
|
if !ok {
|
||||||
|
panic("not a Res")
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
// GraphSync updates the oldGraph so that it matches the newGraph receiver. It
|
||||||
|
// leaves identical elements alone so that they don't need to be refreshed. It
|
||||||
|
// tries to mutate existing elements into new ones, if they support this.
|
||||||
|
// FIXME: add test cases
|
||||||
|
func GraphSync(g *pgraph.Graph, oldGraph *pgraph.Graph) (*pgraph.Graph, error) {
|
||||||
|
|
||||||
|
if oldGraph == nil {
|
||||||
|
var err error
|
||||||
|
oldGraph, err = pgraph.NewGraph(g.GetName()) // copy over the name
|
||||||
|
if err != nil {
|
||||||
|
return nil, errwrap.Wrapf(err, "could not run GraphSync() properly")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
oldGraph.SetName(g.GetName()) // overwrite the name
|
||||||
|
|
||||||
|
var lookup = make(map[pgraph.Vertex]pgraph.Vertex)
|
||||||
|
var vertexKeep []pgraph.Vertex // list of vertices which are the same in new graph
|
||||||
|
var edgeKeep []*pgraph.Edge // list of vertices which are the same in new graph
|
||||||
|
|
||||||
|
for v := range g.Adjacency() { // loop through the vertices (resources)
|
||||||
|
res := VtoR(v) // resource
|
||||||
|
var vertex pgraph.Vertex
|
||||||
|
|
||||||
|
// step one, direct compare with res.Compare
|
||||||
|
if vertex == nil { // redundant guard for consistency
|
||||||
|
fn := func(v pgraph.Vertex) (bool, error) {
|
||||||
|
return VtoR(v).Compare(res), nil
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
vertex, err = oldGraph.VertexMatchFn(fn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errwrap.Wrapf(err, "could not VertexMatchFn() resource")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: consider adding a mutate API.
|
||||||
|
// step two, try and mutate with res.Mutate
|
||||||
|
//if vertex == nil { // not found yet...
|
||||||
|
// vertex = oldGraph.MutateMatch(res)
|
||||||
|
//}
|
||||||
|
|
||||||
|
if vertex == nil { // no match found yet
|
||||||
|
if err := res.Validate(); err != nil {
|
||||||
|
return nil, errwrap.Wrapf(err, "could not Validate() resource")
|
||||||
|
}
|
||||||
|
vertex = v
|
||||||
|
oldGraph.AddVertex(vertex) // call standalone in case not part of an edge
|
||||||
|
}
|
||||||
|
lookup[v] = vertex // used for constructing edges
|
||||||
|
vertexKeep = append(vertexKeep, vertex) // append
|
||||||
|
}
|
||||||
|
|
||||||
|
// get rid of any vertices we shouldn't keep (that aren't in new graph)
|
||||||
|
for v := range oldGraph.Adjacency() {
|
||||||
|
if !pgraph.VertexContains(v, vertexKeep) {
|
||||||
|
// wait for exit before starting new graph!
|
||||||
|
VtoR(v).SendEvent(event.EventExit, nil) // sync
|
||||||
|
VtoR(v).WaitGroup().Wait()
|
||||||
|
oldGraph.DeleteVertex(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// compare edges
|
||||||
|
for v1 := range g.Adjacency() { // loop through the vertices (resources)
|
||||||
|
for v2, e := range g.Adjacency()[v1] {
|
||||||
|
// we have an edge!
|
||||||
|
|
||||||
|
// lookup vertices (these should exist now)
|
||||||
|
//res1 := v1.Res // resource
|
||||||
|
//res2 := v2.Res
|
||||||
|
//vertex1 := oldGraph.CompareMatch(res1) // now: VertexMatchFn
|
||||||
|
//vertex2 := oldGraph.CompareMatch(res2) // now: VertexMatchFn
|
||||||
|
vertex1, exists1 := lookup[v1]
|
||||||
|
vertex2, exists2 := lookup[v2]
|
||||||
|
if !exists1 || !exists2 { // no match found, bug?
|
||||||
|
//if vertex1 == nil || vertex2 == nil { // no match found
|
||||||
|
return nil, fmt.Errorf("new vertices weren't found") // programming error
|
||||||
|
}
|
||||||
|
|
||||||
|
edge, exists := oldGraph.Adjacency()[vertex1][vertex2]
|
||||||
|
if !exists || edge.Name != e.Name { // TODO: edgeCmp
|
||||||
|
edge = e // use or overwrite edge
|
||||||
|
}
|
||||||
|
oldGraph.Adjacency()[vertex1][vertex2] = edge // store it (AddEdge)
|
||||||
|
edgeKeep = append(edgeKeep, edge) // mark as saved
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete unused edges
|
||||||
|
for v1 := range oldGraph.Adjacency() {
|
||||||
|
for _, e := range oldGraph.Adjacency()[v1] {
|
||||||
|
// we have an edge!
|
||||||
|
if !pgraph.EdgeContains(e, edgeKeep) {
|
||||||
|
oldGraph.DeleteEdge(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return oldGraph, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -177,6 +177,7 @@ type Base interface {
|
|||||||
SetName(string)
|
SetName(string)
|
||||||
SetKind(string)
|
SetKind(string)
|
||||||
GetKind() string
|
GetKind() string
|
||||||
|
String() string
|
||||||
Meta() *MetaParams
|
Meta() *MetaParams
|
||||||
Events() chan *event.Event
|
Events() chan *event.Event
|
||||||
Data() *Data
|
Data() *Data
|
||||||
@@ -430,6 +431,11 @@ func (obj *BaseRes) GetKind() string {
|
|||||||
return obj.Kind
|
return obj.Kind
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// String returns the canonical string representation for a resource.
|
||||||
|
func (obj *BaseRes) String() string {
|
||||||
|
return fmt.Sprintf("%s[%s]", obj.GetKind(), obj.GetName())
|
||||||
|
}
|
||||||
|
|
||||||
// Meta returns the MetaParams as a reference, which we can then get/set on.
|
// Meta returns the MetaParams as a reference, which we can then get/set on.
|
||||||
func (obj *BaseRes) Meta() *MetaParams {
|
func (obj *BaseRes) Meta() *MetaParams {
|
||||||
return &obj.MetaParams
|
return &obj.MetaParams
|
||||||
@@ -721,3 +727,14 @@ func (obj *BaseRes) Poll() error {
|
|||||||
func (obj *BaseRes) Prometheus() *prometheus.Prometheus {
|
func (obj *BaseRes) Prometheus() *prometheus.Prometheus {
|
||||||
return obj.Data().Prometheus
|
return obj.Data().Prometheus
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: consider adding a mutate API.
|
||||||
|
//func (g *Graph) MutateMatch(obj resources.Res) Vertex {
|
||||||
|
// for v := range g.adjacency {
|
||||||
|
// if err := v.Res.Mutate(obj); err == nil {
|
||||||
|
// // transmogrified!
|
||||||
|
// return v
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// return nil
|
||||||
|
//}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
// You should have received a copy of the GNU Affero General Public License
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
package pgraph
|
package resources
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -24,6 +24,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/purpleidea/mgmt/pgraph"
|
||||||
"github.com/purpleidea/mgmt/util/semaphore"
|
"github.com/purpleidea/mgmt/util/semaphore"
|
||||||
|
|
||||||
multierr "github.com/hashicorp/go-multierror"
|
multierr "github.com/hashicorp/go-multierror"
|
||||||
@@ -33,11 +34,11 @@ import (
|
|||||||
const SemaSep = ":"
|
const SemaSep = ":"
|
||||||
|
|
||||||
// SemaLock acquires the list of semaphores in the graph.
|
// SemaLock acquires the list of semaphores in the graph.
|
||||||
func (g *Graph) SemaLock(semas []string) error {
|
func SemaLock(g *pgraph.Graph, semas []string) error {
|
||||||
var reterr error
|
var reterr error
|
||||||
sort.Strings(semas) // very important to avoid deadlock in the dag!
|
sort.Strings(semas) // very important to avoid deadlock in the dag!
|
||||||
slock := SemaLockFromGraph(g)
|
slock := SemaLockFromGraph(g)
|
||||||
smap := *SemaMapFromGraph(g) // returns a *map, but can't use directly
|
smap := SemaMapFromGraph(g) // returns a map, which can be modified by ref
|
||||||
|
|
||||||
for _, id := range semas {
|
for _, id := range semas {
|
||||||
slock.Lock() // semaphore creation lock
|
slock.Lock() // semaphore creation lock
|
||||||
@@ -57,10 +58,10 @@ func (g *Graph) SemaLock(semas []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SemaUnlock releases the list of semaphores in the graph.
|
// SemaUnlock releases the list of semaphores in the graph.
|
||||||
func (g *Graph) SemaUnlock(semas []string) error {
|
func SemaUnlock(g *pgraph.Graph, semas []string) error {
|
||||||
var reterr error
|
var reterr error
|
||||||
sort.Strings(semas) // unlock in the same order to remove partial locks
|
sort.Strings(semas) // unlock in the same order to remove partial locks
|
||||||
smap := *SemaMapFromGraph(g)
|
smap := SemaMapFromGraph(g)
|
||||||
|
|
||||||
for _, id := range semas {
|
for _, id := range semas {
|
||||||
sema, ok := smap[id] // lookup
|
sema, ok := smap[id] // lookup
|
||||||
@@ -92,7 +93,7 @@ func SemaSize(id string) int {
|
|||||||
|
|
||||||
// SemaLockFromGraph returns a pointer to the semaphore lock stored with the
|
// SemaLockFromGraph returns a pointer to the semaphore lock stored with the
|
||||||
// graph, otherwise it panics. If one does not exist, it will create it.
|
// graph, otherwise it panics. If one does not exist, it will create it.
|
||||||
func SemaLockFromGraph(g *Graph) *sync.Mutex {
|
func SemaLockFromGraph(g *pgraph.Graph) *sync.Mutex {
|
||||||
x, exists := g.Value("slock")
|
x, exists := g.Value("slock")
|
||||||
if !exists {
|
if !exists {
|
||||||
g.SetValue("slock", &sync.Mutex{})
|
g.SetValue("slock", &sync.Mutex{})
|
||||||
@@ -15,12 +15,12 @@
|
|||||||
// You should have received a copy of the GNU Affero General Public License
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
package pgraph
|
package resources
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/purpleidea/mgmt/resources"
|
"github.com/purpleidea/mgmt/pgraph"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestSemaSize(t *testing.T) {
|
func TestSemaSize(t *testing.T) {
|
||||||
@@ -38,10 +38,10 @@ func TestSemaSize(t *testing.T) {
|
|||||||
|
|
||||||
func NewNoopResTestSema(name string, semas []string) *NoopResTest {
|
func NewNoopResTestSema(name string, semas []string) *NoopResTest {
|
||||||
obj := &NoopResTest{
|
obj := &NoopResTest{
|
||||||
NoopRes: resources.NoopRes{
|
NoopRes: NoopRes{
|
||||||
BaseRes: resources.BaseRes{
|
BaseRes: BaseRes{
|
||||||
Name: name,
|
Name: name,
|
||||||
MetaParams: resources.MetaParams{
|
MetaParams: MetaParams{
|
||||||
AutoGroup: true, // always autogroup
|
AutoGroup: true, // always autogroup
|
||||||
Sema: semas,
|
Sema: semas,
|
||||||
},
|
},
|
||||||
@@ -52,54 +52,54 @@ func NewNoopResTestSema(name string, semas []string) *NoopResTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestPgraphSemaphoreGrouping1(t *testing.T) {
|
func TestPgraphSemaphoreGrouping1(t *testing.T) {
|
||||||
g1, _ := NewGraph("g1") // original graph
|
g1, _ := pgraph.NewGraph("g1") // original graph
|
||||||
{
|
{
|
||||||
a1 := NewVertex(NewNoopResTestSema("a1", []string{"s:1"}))
|
a1 := pgraph.NewVertex(NewNoopResTestSema("a1", []string{"s:1"}))
|
||||||
a2 := NewVertex(NewNoopResTestSema("a2", []string{"s:2"}))
|
a2 := pgraph.NewVertex(NewNoopResTestSema("a2", []string{"s:2"}))
|
||||||
a3 := NewVertex(NewNoopResTestSema("a3", []string{"s:3"}))
|
a3 := pgraph.NewVertex(NewNoopResTestSema("a3", []string{"s:3"}))
|
||||||
g1.AddVertex(a1)
|
g1.AddVertex(a1)
|
||||||
g1.AddVertex(a2)
|
g1.AddVertex(a2)
|
||||||
g1.AddVertex(a3)
|
g1.AddVertex(a3)
|
||||||
}
|
}
|
||||||
g2, _ := NewGraph("g2") // expected result
|
g2, _ := pgraph.NewGraph("g2") // expected result
|
||||||
{
|
{
|
||||||
a123 := NewVertex(NewNoopResTestSema("a1,a2,a3", []string{"s:1", "s:2", "s:3"}))
|
a123 := pgraph.NewVertex(NewNoopResTestSema("a1,a2,a3", []string{"s:1", "s:2", "s:3"}))
|
||||||
g2.AddVertex(a123)
|
g2.AddVertex(a123)
|
||||||
}
|
}
|
||||||
runGraphCmp(t, g1, g2)
|
runGraphCmp(t, g1, g2)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPgraphSemaphoreGrouping2(t *testing.T) {
|
func TestPgraphSemaphoreGrouping2(t *testing.T) {
|
||||||
g1, _ := NewGraph("g1") // original graph
|
g1, _ := pgraph.NewGraph("g1") // original graph
|
||||||
{
|
{
|
||||||
a1 := NewVertex(NewNoopResTestSema("a1", []string{"s:10", "s:11"}))
|
a1 := pgraph.NewVertex(NewNoopResTestSema("a1", []string{"s:10", "s:11"}))
|
||||||
a2 := NewVertex(NewNoopResTestSema("a2", []string{"s:2"}))
|
a2 := pgraph.NewVertex(NewNoopResTestSema("a2", []string{"s:2"}))
|
||||||
a3 := NewVertex(NewNoopResTestSema("a3", []string{"s:3"}))
|
a3 := pgraph.NewVertex(NewNoopResTestSema("a3", []string{"s:3"}))
|
||||||
g1.AddVertex(a1)
|
g1.AddVertex(a1)
|
||||||
g1.AddVertex(a2)
|
g1.AddVertex(a2)
|
||||||
g1.AddVertex(a3)
|
g1.AddVertex(a3)
|
||||||
}
|
}
|
||||||
g2, _ := NewGraph("g2") // expected result
|
g2, _ := pgraph.NewGraph("g2") // expected result
|
||||||
{
|
{
|
||||||
a123 := NewVertex(NewNoopResTestSema("a1,a2,a3", []string{"s:10", "s:11", "s:2", "s:3"}))
|
a123 := pgraph.NewVertex(NewNoopResTestSema("a1,a2,a3", []string{"s:10", "s:11", "s:2", "s:3"}))
|
||||||
g2.AddVertex(a123)
|
g2.AddVertex(a123)
|
||||||
}
|
}
|
||||||
runGraphCmp(t, g1, g2)
|
runGraphCmp(t, g1, g2)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPgraphSemaphoreGrouping3(t *testing.T) {
|
func TestPgraphSemaphoreGrouping3(t *testing.T) {
|
||||||
g1, _ := NewGraph("g1") // original graph
|
g1, _ := pgraph.NewGraph("g1") // original graph
|
||||||
{
|
{
|
||||||
a1 := NewVertex(NewNoopResTestSema("a1", []string{"s:1", "s:2"}))
|
a1 := pgraph.NewVertex(NewNoopResTestSema("a1", []string{"s:1", "s:2"}))
|
||||||
a2 := NewVertex(NewNoopResTestSema("a2", []string{"s:2"}))
|
a2 := pgraph.NewVertex(NewNoopResTestSema("a2", []string{"s:2"}))
|
||||||
a3 := NewVertex(NewNoopResTestSema("a3", []string{"s:3"}))
|
a3 := pgraph.NewVertex(NewNoopResTestSema("a3", []string{"s:3"}))
|
||||||
g1.AddVertex(a1)
|
g1.AddVertex(a1)
|
||||||
g1.AddVertex(a2)
|
g1.AddVertex(a2)
|
||||||
g1.AddVertex(a3)
|
g1.AddVertex(a3)
|
||||||
}
|
}
|
||||||
g2, _ := NewGraph("g2") // expected result
|
g2, _ := pgraph.NewGraph("g2") // expected result
|
||||||
{
|
{
|
||||||
a123 := NewVertex(NewNoopResTestSema("a1,a2,a3", []string{"s:1", "s:2", "s:3"}))
|
a123 := pgraph.NewVertex(NewNoopResTestSema("a1,a2,a3", []string{"s:1", "s:2", "s:3"}))
|
||||||
g2.AddVertex(a123)
|
g2.AddVertex(a123)
|
||||||
}
|
}
|
||||||
runGraphCmp(t, g1, g2)
|
runGraphCmp(t, g1, g2)
|
||||||
@@ -103,14 +103,14 @@ func (c *GraphConfig) NewGraphFromConfig(hostname string, world resources.World,
|
|||||||
return nil, errwrap.Wrapf(err, "could not run NewGraphFromConfig() properly")
|
return nil, errwrap.Wrapf(err, "could not run NewGraphFromConfig() properly")
|
||||||
}
|
}
|
||||||
|
|
||||||
var lookup = make(map[string]map[string]*pgraph.Vertex)
|
var lookup = make(map[string]map[string]pgraph.Vertex)
|
||||||
|
|
||||||
//log.Printf("%+v", config) // debug
|
//log.Printf("%+v", config) // debug
|
||||||
|
|
||||||
// TODO: if defined (somehow)...
|
// TODO: if defined (somehow)...
|
||||||
graph.SetName(c.Graph) // set graph name
|
graph.SetName(c.Graph) // set graph name
|
||||||
|
|
||||||
var keep []*pgraph.Vertex // list of vertex which are the same in new graph
|
var keep []pgraph.Vertex // list of vertex which are the same in new graph
|
||||||
var resourceList []resources.Res // list of resources to export
|
var resourceList []resources.Res // list of resources to export
|
||||||
// use reflection to avoid duplicating code... better options welcome!
|
// use reflection to avoid duplicating code... better options welcome!
|
||||||
value := reflect.Indirect(reflect.ValueOf(c.Resources))
|
value := reflect.Indirect(reflect.ValueOf(c.Resources))
|
||||||
@@ -131,13 +131,13 @@ func (c *GraphConfig) NewGraphFromConfig(hostname string, world resources.World,
|
|||||||
// res.Meta().Noop = noop
|
// res.Meta().Noop = noop
|
||||||
//}
|
//}
|
||||||
if _, exists := lookup[kind]; !exists {
|
if _, exists := lookup[kind]; !exists {
|
||||||
lookup[kind] = make(map[string]*pgraph.Vertex)
|
lookup[kind] = make(map[string]pgraph.Vertex)
|
||||||
}
|
}
|
||||||
// XXX: should we export based on a @@ prefix, or a metaparam
|
// XXX: should we export based on a @@ prefix, or a metaparam
|
||||||
// like exported => true || exported => (host pattern)||(other pattern?)
|
// like exported => true || exported => (host pattern)||(other pattern?)
|
||||||
if !strings.HasPrefix(res.GetName(), "@@") { // not exported resource
|
if !strings.HasPrefix(res.GetName(), "@@") { // not exported resource
|
||||||
fn := func(v *pgraph.Vertex) (bool, error) {
|
fn := func(v pgraph.Vertex) (bool, error) {
|
||||||
return v.Res.Compare(res), nil
|
return resources.VtoR(v).Compare(res), nil
|
||||||
}
|
}
|
||||||
v, err := graph.VertexMatchFn(fn)
|
v, err := graph.VertexMatchFn(fn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -211,11 +211,11 @@ func (c *GraphConfig) NewGraphFromConfig(hostname string, world resources.World,
|
|||||||
|
|
||||||
// XXX: similar to other resource add code:
|
// XXX: similar to other resource add code:
|
||||||
if _, exists := lookup[kind]; !exists {
|
if _, exists := lookup[kind]; !exists {
|
||||||
lookup[kind] = make(map[string]*pgraph.Vertex)
|
lookup[kind] = make(map[string]pgraph.Vertex)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn := func(v *pgraph.Vertex) (bool, error) {
|
fn := func(v pgraph.Vertex) (bool, error) {
|
||||||
return v.Res.Compare(res), nil
|
return resources.VtoR(v).Compare(res), nil
|
||||||
}
|
}
|
||||||
v, err := graph.VertexMatchFn(fn)
|
v, err := graph.VertexMatchFn(fn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -163,27 +163,27 @@ func (c *GraphConfig) NewGraphFromConfig(hostname string, world resources.World,
|
|||||||
return nil, errwrap.Wrapf(err, "could not run NewGraphFromConfig() properly")
|
return nil, errwrap.Wrapf(err, "could not run NewGraphFromConfig() properly")
|
||||||
}
|
}
|
||||||
|
|
||||||
var lookup = make(map[string]map[string]*pgraph.Vertex)
|
var lookup = make(map[string]map[string]pgraph.Vertex)
|
||||||
|
|
||||||
//log.Printf("%+v", config) // debug
|
//log.Printf("%+v", config) // debug
|
||||||
|
|
||||||
// TODO: if defined (somehow)...
|
// TODO: if defined (somehow)...
|
||||||
graph.SetName(c.Graph) // set graph name
|
graph.SetName(c.Graph) // set graph name
|
||||||
|
|
||||||
var keep []*pgraph.Vertex // list of vertex which are the same in new graph
|
var keep []pgraph.Vertex // list of vertex which are the same in new graph
|
||||||
var resourceList []resources.Res // list of resources to export
|
var resourceList []resources.Res // list of resources to export
|
||||||
|
|
||||||
// Resources
|
// Resources
|
||||||
for _, res := range c.ResList {
|
for _, res := range c.ResList {
|
||||||
kind := res.GetKind()
|
kind := res.GetKind()
|
||||||
if _, exists := lookup[kind]; !exists {
|
if _, exists := lookup[kind]; !exists {
|
||||||
lookup[kind] = make(map[string]*pgraph.Vertex)
|
lookup[kind] = make(map[string]pgraph.Vertex)
|
||||||
}
|
}
|
||||||
// XXX: should we export based on a @@ prefix, or a metaparam
|
// XXX: should we export based on a @@ prefix, or a metaparam
|
||||||
// like exported => true || exported => (host pattern)||(other pattern?)
|
// like exported => true || exported => (host pattern)||(other pattern?)
|
||||||
if !strings.HasPrefix(res.GetName(), "@@") { // not exported resource
|
if !strings.HasPrefix(res.GetName(), "@@") { // not exported resource
|
||||||
fn := func(v *pgraph.Vertex) (bool, error) {
|
fn := func(v pgraph.Vertex) (bool, error) {
|
||||||
return v.Res.Compare(res), nil
|
return resources.VtoR(v).Compare(res), nil
|
||||||
}
|
}
|
||||||
v, err := graph.VertexMatchFn(fn)
|
v, err := graph.VertexMatchFn(fn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -257,11 +257,11 @@ func (c *GraphConfig) NewGraphFromConfig(hostname string, world resources.World,
|
|||||||
|
|
||||||
// XXX: similar to other resource add code:
|
// XXX: similar to other resource add code:
|
||||||
if _, exists := lookup[kind]; !exists {
|
if _, exists := lookup[kind]; !exists {
|
||||||
lookup[kind] = make(map[string]*pgraph.Vertex)
|
lookup[kind] = make(map[string]pgraph.Vertex)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn := func(v *pgraph.Vertex) (bool, error) {
|
fn := func(v pgraph.Vertex) (bool, error) {
|
||||||
return v.Res.Compare(res), nil
|
return resources.VtoR(v).Compare(res), nil
|
||||||
}
|
}
|
||||||
v, err := graph.VertexMatchFn(fn)
|
v, err := graph.VertexMatchFn(fn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
Reference in New Issue
Block a user