The engine core had some unfortunate bugs that were the result of some early design errors when I wasn't as familiar with channels. I've finally rewritten most of the bad parts, and I think it's much more logical and stable now. This also simplifies the resource API, since more of the work is done completely in the engine, and hidden from view. Lastly, this adds a few new metaparameters and associated code. There are still some open problems left to solve, but hopefully this brings us one step closer.
176 lines
5.9 KiB
Go
176 lines
5.9 KiB
Go
// Mgmt
|
|
// Copyright (C) 2013-2018+ 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 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 General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
package pgraph
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
errwrap "github.com/pkg/errors"
|
|
)
|
|
|
|
func strVertexCmpFn(v1, v2 Vertex) (bool, error) {
|
|
if v1.String() == "" || v2.String() == "" {
|
|
return false, fmt.Errorf("empty vertex")
|
|
}
|
|
return v1.String() == v2.String(), nil
|
|
}
|
|
|
|
func strEdgeCmpFn(e1, e2 Edge) (bool, error) {
|
|
if e1.String() == "" || e2.String() == "" {
|
|
return false, fmt.Errorf("empty edge")
|
|
}
|
|
return e1.String() == e2.String(), nil
|
|
}
|
|
|
|
// GraphSync updates the Graph so that it matches the newGraph. 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.
|
|
// This updates the Graph on success only. If it fails, then the graph won't
|
|
// have been modified.
|
|
// FIXME: should we do this with copies of the vertex resources?
|
|
func (obj *Graph) GraphSync(newGraph *Graph, vertexCmpFn func(Vertex, Vertex) (bool, error), vertexAddFn func(Vertex) error, vertexRemoveFn func(Vertex) error, edgeCmpFn func(Edge, Edge) (bool, error)) error {
|
|
oldGraph := obj.Copy() // work on a copy of the old graph
|
|
if oldGraph == nil {
|
|
var err error
|
|
oldGraph, err = NewGraph(newGraph.GetName()) // copy over the name
|
|
if err != nil {
|
|
return errwrap.Wrapf(err, "GraphSync failed")
|
|
}
|
|
}
|
|
oldGraph.SetName(newGraph.GetName()) // overwrite the name
|
|
|
|
if vertexCmpFn == nil {
|
|
vertexCmpFn = strVertexCmpFn // use simple string cmp version
|
|
}
|
|
if vertexAddFn == nil {
|
|
vertexAddFn = func(Vertex) error { return nil } // noop
|
|
}
|
|
if vertexRemoveFn == nil {
|
|
vertexRemoveFn = func(Vertex) error { return nil } // noop
|
|
}
|
|
if edgeCmpFn == nil {
|
|
edgeCmpFn = strEdgeCmpFn // use simple string cmp version
|
|
}
|
|
|
|
var lookup = make(map[Vertex]Vertex)
|
|
var vertexKeep []Vertex // list of vertices which are the same in new graph
|
|
var vertexDels []Vertex // list of vertices which are to be removed
|
|
var vertexAdds []Vertex // list of vertices which are to be added
|
|
var edgeKeep []Edge // list of edges which are the same in new graph
|
|
|
|
// XXX: run this as a topological sort or reverse topological sort?
|
|
for v := range newGraph.Adjacency() { // loop through the vertices (resources)
|
|
var vertex Vertex
|
|
// step one, direct compare with res.Cmp
|
|
if vertex == nil { // redundant guard for consistency
|
|
fn := func(vv Vertex) (bool, error) {
|
|
b, err := vertexCmpFn(vv, v)
|
|
return b, errwrap.Wrapf(err, "vertexCmpFn failed")
|
|
}
|
|
var err error
|
|
vertex, err = oldGraph.VertexMatchFn(fn)
|
|
if err != nil {
|
|
return errwrap.Wrapf(err, "VertexMatchFn failed")
|
|
}
|
|
}
|
|
|
|
// TODO: consider adding a mutate API.
|
|
// step two, try and mutate with res.Mutate
|
|
//if vertex == nil { // not found yet...
|
|
// vertex = oldGraph.MutateMatch(res)
|
|
//}
|
|
|
|
// run the removes BEFORE the adds, so don't do the add here...
|
|
if vertex == nil { // no match found yet
|
|
vertexAdds = append(vertexAdds, v) // append
|
|
vertex = v
|
|
}
|
|
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) {
|
|
vertexDels = append(vertexDels, v) // append
|
|
}
|
|
}
|
|
|
|
// see if any of the add/remove functions actually fail first
|
|
// XXX: run this as a reverse topological sort or topological sort?
|
|
for _, vertex := range vertexDels {
|
|
if err := vertexRemoveFn(vertex); err != nil {
|
|
return errwrap.Wrapf(err, "vertexRemoveFn failed")
|
|
}
|
|
}
|
|
for _, vertex := range vertexAdds {
|
|
if err := vertexAddFn(vertex); err != nil {
|
|
return errwrap.Wrapf(err, "vertexAddFn failed")
|
|
}
|
|
}
|
|
|
|
// no add/remove functions failed, so we can actually modify the graph!
|
|
for _, vertex := range vertexDels {
|
|
oldGraph.DeleteVertex(vertex)
|
|
}
|
|
for _, vertex := range vertexAdds {
|
|
oldGraph.AddVertex(vertex) // call standalone in case not part of an edge
|
|
}
|
|
|
|
// XXX: fixup this part so the CmpFn stuff fails early, and THEN we edit
|
|
// the graph at the end, if no errors happened...
|
|
// compare edges
|
|
for v1 := range newGraph.Adjacency() { // loop through the vertices (resources)
|
|
for v2, e := range newGraph.Adjacency()[v1] {
|
|
// we have an edge!
|
|
// lookup vertices (these should exist now)
|
|
vertex1, exists1 := lookup[v1]
|
|
vertex2, exists2 := lookup[v2]
|
|
if !exists1 || !exists2 { // no match found, bug?
|
|
//if vertex1 == nil || vertex2 == nil { // no match found
|
|
return fmt.Errorf("new vertices weren't found") // programming error
|
|
}
|
|
|
|
edge, exists := oldGraph.Adjacency()[vertex1][vertex2]
|
|
if !exists {
|
|
edge = e // use edge
|
|
} else if b, err := edgeCmpFn(edge, e); err != nil {
|
|
return errwrap.Wrapf(err, "edgeCmpFn failed")
|
|
} else if !b {
|
|
edge = e // 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)
|
|
}
|
|
}
|
|
}
|
|
|
|
// success
|
|
*obj = *oldGraph // save old graph
|
|
return nil
|
|
}
|