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:
James Shubin
2017-05-16 11:23:26 -04:00
parent 4490c3ed1a
commit 3cf9639e99
15 changed files with 1241 additions and 1216 deletions

View File

@@ -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
} }
} }

View File

@@ -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)
}

View File

@@ -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)
} }
} }
} }

View File

@@ -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])

View File

@@ -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")
} }
} }

View File

@@ -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
} }

View File

@@ -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) {

View File

@@ -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
View 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)
}

View File

@@ -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
}

View File

@@ -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
//}

View File

@@ -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{})

View File

@@ -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)

View File

@@ -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 {

View File

@@ -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 {