lang: funcs: txn: Add a simple graph implementation
This fulfills the GraphAPI that we use.
This commit is contained in:
180
lang/funcs/txn/graph.go
Normal file
180
lang/funcs/txn/graph.go
Normal file
@@ -0,0 +1,180 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2023+ 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 txn
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/purpleidea/mgmt/lang/interfaces"
|
||||
"github.com/purpleidea/mgmt/pgraph"
|
||||
)
|
||||
|
||||
var _ interfaces.GraphAPI = &Graph{} // ensure it meets this expectation
|
||||
|
||||
// Graph is a simple pgraph wrapper that implements the GraphAPI interface. That
|
||||
// interface is also implemented by *dage.Engine and the code is very similar.
|
||||
type Graph struct {
|
||||
Debug bool
|
||||
Logf func(format string, v ...interface{})
|
||||
|
||||
graph *pgraph.Graph // guarded by graphMutex
|
||||
graphMutex *sync.Mutex // TODO: &sync.RWMutex{} ?
|
||||
}
|
||||
|
||||
// Init initializes the struct before first use and returns it for ergonomics.
|
||||
func (obj *Graph) Init() *Graph {
|
||||
var err error
|
||||
obj.graph, err = pgraph.NewGraph("graph")
|
||||
if err != nil {
|
||||
panic("graph was not built")
|
||||
}
|
||||
obj.graphMutex = &sync.Mutex{} // TODO: &sync.RWMutex{} ?
|
||||
|
||||
return obj
|
||||
}
|
||||
|
||||
// AddVertex adds a vertex to the graph. It takes a lock.
|
||||
func (obj *Graph) AddVertex(f interfaces.Func) error {
|
||||
obj.graphMutex.Lock()
|
||||
defer obj.graphMutex.Unlock()
|
||||
if obj.Debug {
|
||||
obj.Logf("AddVertex: %p %s", f, f)
|
||||
}
|
||||
|
||||
obj.graph.AddVertex(f)
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddEdge adds an edge to the graph. It takes a lock.
|
||||
func (obj *Graph) AddEdge(f1, f2 interfaces.Func, fe *interfaces.FuncEdge) error {
|
||||
obj.graphMutex.Lock()
|
||||
defer obj.graphMutex.Unlock()
|
||||
if obj.Debug {
|
||||
obj.Logf("AddEdge %p %s: %p %s -> %p %s", fe, fe, f1, f1, f2, f2)
|
||||
}
|
||||
|
||||
// safety check to avoid cycles
|
||||
g := obj.graph.Copy()
|
||||
g.AddEdge(f1, f2, fe)
|
||||
if _, err := g.TopologicalSort(); err != nil {
|
||||
return err // not a dag
|
||||
}
|
||||
// if we didn't cycle, we can modify the real graph safely...
|
||||
|
||||
obj.graph.AddEdge(f1, f2, fe) // replaces any existing edge here
|
||||
|
||||
// This shouldn't error, since the test graph didn't find a cycle.
|
||||
if _, err := obj.graph.TopologicalSort(); err != nil {
|
||||
// programming error
|
||||
panic(err) // not a dag
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteVertex deletes a vertex from the graph. It takes a lock.
|
||||
func (obj *Graph) DeleteVertex(f interfaces.Func) error {
|
||||
obj.graphMutex.Lock()
|
||||
defer obj.graphMutex.Unlock()
|
||||
if obj.Debug {
|
||||
obj.Logf("DeleteVertex: %p %s", f, f)
|
||||
}
|
||||
|
||||
obj.graph.DeleteVertex(f)
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteEdge deletes an edge from the graph. It takes a lock.
|
||||
func (obj *Graph) DeleteEdge(fe *interfaces.FuncEdge) error {
|
||||
obj.graphMutex.Lock()
|
||||
defer obj.graphMutex.Unlock()
|
||||
if obj.Debug {
|
||||
f1, f2, found := obj.graph.LookupEdge(fe)
|
||||
if found {
|
||||
obj.Logf("DeleteEdge: %p %s -> %p %s", f1, f1, f2, f2)
|
||||
} else {
|
||||
obj.Logf("DeleteEdge: not found %p %s", fe, fe)
|
||||
}
|
||||
}
|
||||
|
||||
// Don't bother checking if edge exists first and don't error if it
|
||||
// doesn't because it might have gotten deleted when a vertex did, and
|
||||
// so there's no need to complain for nothing.
|
||||
obj.graph.DeleteEdge(fe)
|
||||
return nil
|
||||
}
|
||||
|
||||
// HasVertex checks if a vertex exists in the graph. It takes a lock.
|
||||
func (obj *Graph) HasVertex(f interfaces.Func) bool {
|
||||
obj.graphMutex.Lock() // XXX: should this be a RLock?
|
||||
defer obj.graphMutex.Unlock() // XXX: should this be an RUnlock?
|
||||
|
||||
return obj.graph.HasVertex(f)
|
||||
}
|
||||
|
||||
// LookupEdge checks which vertices (if any) exist between an edge in the graph.
|
||||
// It takes a lock.
|
||||
func (obj *Graph) LookupEdge(fe *interfaces.FuncEdge) (interfaces.Func, interfaces.Func, bool) {
|
||||
obj.graphMutex.Lock() // XXX: should this be a RLock?
|
||||
defer obj.graphMutex.Unlock() // XXX: should this be an RUnlock?
|
||||
|
||||
v1, v2, found := obj.graph.LookupEdge(fe)
|
||||
if !found {
|
||||
return nil, nil, found
|
||||
}
|
||||
f1, ok := v1.(interfaces.Func)
|
||||
if !ok {
|
||||
panic("not a Func")
|
||||
}
|
||||
f2, ok := v2.(interfaces.Func)
|
||||
if !ok {
|
||||
panic("not a Func")
|
||||
}
|
||||
return f1, f2, found
|
||||
}
|
||||
|
||||
// FindEdge checks which edge (if any) exists between two vertices in the graph.
|
||||
// It takes a lock. This is an important method in edge removal, because it's
|
||||
// what you really need to know for DeleteEdge to work. Requesting a specific
|
||||
// deletion isn't very sensical in this library when specified as the edge
|
||||
// pointer, since we might replace it with a new edge that has new arg names.
|
||||
// Instead, use this to look up what relationship you want, and then DeleteEdge
|
||||
// to remove it.
|
||||
func (obj *Graph) FindEdge(f1, f2 interfaces.Func) *interfaces.FuncEdge {
|
||||
obj.graphMutex.Lock() // XXX: should this be a RLock?
|
||||
defer obj.graphMutex.Unlock() // XXX: should this be an RUnlock?
|
||||
|
||||
edge := obj.graph.FindEdge(f1, f2)
|
||||
if edge == nil {
|
||||
return nil
|
||||
}
|
||||
fe, ok := edge.(*interfaces.FuncEdge)
|
||||
if !ok {
|
||||
panic("edge is not a FuncEdge")
|
||||
}
|
||||
|
||||
return fe
|
||||
}
|
||||
|
||||
// Graph returns a copy of the contained graph. It takes a lock.
|
||||
func (obj *Graph) Graph() *pgraph.Graph {
|
||||
obj.graphMutex.Lock() // XXX: should this be a RLock?
|
||||
defer obj.graphMutex.Unlock() // XXX: should this be an RUnlock?
|
||||
|
||||
return obj.graph.Copy()
|
||||
}
|
||||
Reference in New Issue
Block a user