main: Libify mgmt with a golang API

This is an initial implementation of a possible golang API. In this
particular version, the *gconfig.GraphConfig data structures are
emitted, instead of possibly building a pgraph. As long as we can
represent any local graph as the data structure, then this is fine!

Is there a way to merge the gconfig Vertex and the pgraph Vertex?
This commit is contained in:
James Shubin
2016-10-24 04:10:54 -04:00
parent 80476d19f9
commit 71de8014d5
4 changed files with 171 additions and 35 deletions

110
examples/lib/libmgmt1.go Normal file
View File

@@ -0,0 +1,110 @@
// libmgmt example
package main
import (
"fmt"
"log"
"os"
"os/signal"
"syscall"
"time"
"github.com/purpleidea/mgmt/gconfig"
mgmt "github.com/purpleidea/mgmt/mgmtmain"
"github.com/purpleidea/mgmt/resources"
)
func generateGraphConfig() *gconfig.GraphConfig {
n1, err := resources.NewNoopRes("noop1")
if err != nil {
return nil // error
}
gc := &gconfig.GraphConfig{
Graph: "libmgmt",
Resources: gconfig.Resources{ // must redefine anonymous struct :(
// in alphabetical order
Exec: []*resources.ExecRes{},
File: []*resources.FileRes{},
Msg: []*resources.MsgRes{},
Noop: []*resources.NoopRes{n1},
Pkg: []*resources.PkgRes{},
Svc: []*resources.SvcRes{},
Timer: []*resources.TimerRes{},
Virt: []*resources.VirtRes{},
},
//Collector: []collectorResConfig{},
//Edges: []Edge{},
Comment: "comment!",
//Hostname: "???",
//Remote: "???",
}
return gc
}
// Run runs an embedded mgmt server.
func Run() error {
obj := &mgmt.Main{}
obj.Program = "mgmtlib" // TODO: set on compilation
obj.Version = "0.0.1" // TODO: set on compilation
obj.TmpPrefix = true
obj.IdealClusterSize = -1
obj.ConvergedTimeout = -1
obj.Noop = true
obj.GAPI = generateGraphConfig // graph API function
if err := obj.Init(); err != nil {
return err
}
go func() {
for {
log.Printf("Generating new graph...")
obj.Switch(generateGraphConfig) // pass in function to run...
time.Sleep(15 * time.Second) // XXX: arbitrarily change graph every 30 seconds
}
}()
// install the exit signal handler
exit := make(chan struct{})
defer close(exit)
go func() {
signals := make(chan os.Signal, 1)
signal.Notify(signals, os.Interrupt) // catch ^C
//signal.Notify(signals, os.Kill) // catch signals
signal.Notify(signals, syscall.SIGTERM)
select {
case sig := <-signals: // any signal will do
if sig == os.Interrupt {
log.Println("Interrupted by ^C")
obj.Exit(nil)
return
}
log.Println("Interrupted by signal")
obj.Exit(fmt.Errorf("Killed by %v", sig))
return
case <-exit:
return
}
}()
if err := obj.Run(); err != nil {
return err
}
return nil
}
func main() {
log.Printf("Hello!")
if err := Run(); err != nil {
fmt.Println(err)
os.Exit(1)
return
}
log.Printf("Goodbye!")
}

View File

@@ -41,33 +41,38 @@ type collectorResConfig struct {
Pattern string `yaml:"pattern"` // XXX: Not Implemented Pattern string `yaml:"pattern"` // XXX: Not Implemented
} }
type vertexConfig struct { // Vertex is the data structure of a vertex.
type Vertex struct {
Kind string `yaml:"kind"` Kind string `yaml:"kind"`
Name string `yaml:"name"` Name string `yaml:"name"`
} }
type edgeConfig struct { // Edge is the data structure of an edge.
Name string `yaml:"name"` type Edge struct {
From vertexConfig `yaml:"from"` Name string `yaml:"name"`
To vertexConfig `yaml:"to"` From Vertex `yaml:"from"`
To Vertex `yaml:"to"`
}
// Resources is the data structure of the set of resources.
type Resources struct {
// in alphabetical order
Exec []*resources.ExecRes `yaml:"exec"`
File []*resources.FileRes `yaml:"file"`
Msg []*resources.MsgRes `yaml:"msg"`
Noop []*resources.NoopRes `yaml:"noop"`
Pkg []*resources.PkgRes `yaml:"pkg"`
Svc []*resources.SvcRes `yaml:"svc"`
Timer []*resources.TimerRes `yaml:"timer"`
Virt []*resources.VirtRes `yaml:"virt"`
} }
// GraphConfig is the data structure that describes a single graph to run. // GraphConfig is the data structure that describes a single graph to run.
type GraphConfig struct { type GraphConfig struct {
Graph string `yaml:"graph"` Graph string `yaml:"graph"`
Resources struct { Resources Resources `yaml:"resources"`
// in alphabetical order
Exec []*resources.ExecRes `yaml:"exec"`
File []*resources.FileRes `yaml:"file"`
Msg []*resources.MsgRes `yaml:"msg"`
Noop []*resources.NoopRes `yaml:"noop"`
Pkg []*resources.PkgRes `yaml:"pkg"`
Svc []*resources.SvcRes `yaml:"svc"`
Timer []*resources.TimerRes `yaml:"timer"`
Virt []*resources.VirtRes `yaml:"virt"`
} `yaml:"resources"`
Collector []collectorResConfig `yaml:"collect"` Collector []collectorResConfig `yaml:"collect"`
Edges []edgeConfig `yaml:"edges"` Edges []Edge `yaml:"edges"`
Comment string `yaml:"comment"` Comment string `yaml:"comment"`
Hostname string `yaml:"hostname"` // uuid for the host Hostname string `yaml:"hostname"` // uuid for the host
Remote string `yaml:"remote"` Remote string `yaml:"remote"`
@@ -152,9 +157,6 @@ func (c *GraphConfig) NewGraphFromConfig(g *pgraph.Graph, embdEtcd *etcd.EmbdEtc
// 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
// XXX: we don't have a way of knowing if any of the
// metaparams are undefined, and as a result to set the
// defaults that we want! I hate the go yaml parser!!!
v := graph.GetVertexMatch(res) v := graph.GetVertexMatch(res)
if v == nil { // no match found if v == nil { // no match found
res.Init() res.Init()

View File

@@ -49,10 +49,11 @@ type Main struct {
Hostname *string // hostname to use; nil if undefined Hostname *string // hostname to use; nil if undefined
File *string // graph file to run; nil if undefined File *string // graph file to run; nil if undefined
Puppet *string // puppet mode to run; nil if undefined Puppet *string // puppet mode to run; nil if undefined
PuppetConf string // the path to an alternate puppet.conf file PuppetConf string // the path to an alternate puppet.conf file
Remotes []string // list of remote graph definitions to run GAPI func() *gconfig.GraphConfig // graph API; nil if undefined
Remotes []string // list of remote graph definitions to run
NoWatch bool // do not update graph on watched graph definition file changes NoWatch bool // do not update graph on watched graph definition file changes
Noop bool // globally force all resources into no-op mode Noop bool // globally force all resources into no-op mode
@@ -80,12 +81,18 @@ type Main struct {
clientURLs etcdtypes.URLs // processed client urls value clientURLs etcdtypes.URLs // processed client urls value
serverURLs etcdtypes.URLs // processed server urls value serverURLs etcdtypes.URLs // processed server urls value
idealClusterSize uint16 // processed ideal cluster size value idealClusterSize uint16 // processed ideal cluster size value
exit chan error // exit signal
exit chan error // exit signal
switchChan chan func() *gconfig.GraphConfig // graph switches
} }
// Init initializes the main struct after it performs some validation. // Init initializes the main struct after it performs some validation.
func (obj *Main) Init() error { func (obj *Main) Init() error {
if obj.Program == "" || obj.Version == "" {
return fmt.Errorf("You must set the Program and Version strings!")
}
if obj.Prefix != nil && obj.TmpPrefix { if obj.Prefix != nil && obj.TmpPrefix {
return fmt.Errorf("Choosing a prefix and the request for a tmp prefix is illogical!") return fmt.Errorf("Choosing a prefix and the request for a tmp prefix is illogical!")
} }
@@ -145,6 +152,7 @@ func (obj *Main) Init() error {
} }
obj.exit = make(chan error) obj.exit = make(chan error)
obj.switchChan = make(chan func() *gconfig.GraphConfig)
return nil return nil
} }
@@ -153,6 +161,14 @@ func (obj *Main) Exit(err error) {
obj.exit <- err // trigger an exit! obj.exit <- err // trigger an exit!
} }
// Switch causes mgmt try to switch the currently running graph to a new one.
// The function passed in will usually be called immediately, but it can also
// happen after a delay, and more often than this Switch function is called!
func (obj *Main) Switch(f func() *gconfig.GraphConfig) {
obj.switchChan <- f
// TODO: should we get an ACK() and pass back a return value ?
}
// Run is the main execution entrypoint to run mgmt. // Run is the main execution entrypoint to run mgmt.
func (obj *Main) Run() error { func (obj *Main) Run() error {
@@ -273,13 +289,14 @@ func (obj *Main) Run() error {
go func() { go func() {
startchan := make(chan struct{}) // start signal startchan := make(chan struct{}) // start signal
go func() { startchan <- struct{}{} }() go func() { startchan <- struct{}{} }()
var configchan chan error var configChan chan error
var puppetchan <-chan time.Time var puppetChan <-chan time.Time
var customFunc = obj.GAPI // default
if !obj.NoWatch && obj.File != nil { if !obj.NoWatch && obj.File != nil {
configchan = recwatch.ConfigWatch(*obj.File) configChan = recwatch.ConfigWatch(*obj.File)
} else if obj.Puppet != nil { } else if obj.Puppet != nil {
interval := puppet.PuppetInterval(obj.PuppetConf) interval := puppet.PuppetInterval(obj.PuppetConf)
puppetchan = time.Tick(time.Duration(interval) * time.Second) puppetChan = time.Tick(time.Duration(interval) * time.Second)
} }
log.Println("Etcd: Starting...") log.Println("Etcd: Starting...")
etcdchan := etcd.EtcdWatch(EmbdEtcd) etcdchan := etcd.EtcdWatch(EmbdEtcd)
@@ -296,10 +313,14 @@ func (obj *Main) Run() error {
} }
// everything else passes through to cause a compile! // everything else passes through to cause a compile!
case <-puppetchan: case customFunc = <-obj.switchChan:
// handle a graph switch with a new custom function
obj.GAPI = customFunc
case <-puppetChan:
// nothing, just go on // nothing, just go on
case e := <-configchan: case e := <-configChan:
if obj.NoWatch { if obj.NoWatch {
continue // not ready to read config continue // not ready to read config
} }
@@ -319,7 +340,10 @@ func (obj *Main) Run() error {
config = gconfig.ParseConfigFromFile(*obj.File) config = gconfig.ParseConfigFromFile(*obj.File)
} else if obj.Puppet != nil { } else if obj.Puppet != nil {
config = puppet.ParseConfigFromPuppet(*obj.Puppet, obj.PuppetConf) config = puppet.ParseConfigFromPuppet(*obj.Puppet, obj.PuppetConf)
} else if obj.GAPI != nil {
config = obj.GAPI()
} }
if config == nil { if config == nil {
log.Printf("Config: Parse failure") log.Printf("Config: Parse failure")
continue continue
@@ -337,7 +361,7 @@ func (obj *Main) Run() error {
G.Pause() // sync G.Pause() // sync
} }
// build graph from yaml file on events (eg: from etcd) // build graph from config struct on events, eg: etcd...
// we need the vertices to be paused to work on them // we need the vertices to be paused to work on them
if newFullgraph, err := config.NewGraphFromConfig(fullGraph, EmbdEtcd, obj.Noop); err == nil { // keep references to all original elements if newFullgraph, err := config.NewGraphFromConfig(fullGraph, EmbdEtcd, obj.Noop); err == nil { // keep references to all original elements
fullGraph = newFullgraph fullGraph = newFullgraph
@@ -420,7 +444,7 @@ func (obj *Main) Run() error {
// wait for etcd to be running before we remote in, which we do above! // wait for etcd to be running before we remote in, which we do above!
go remotes.Run() go remotes.Run()
if obj.File == nil && obj.Puppet == nil { if obj.File == nil && obj.Puppet == nil && obj.GAPI == nil {
converger.Start() // better start this for empty graphs converger.Start() // better start this for empty graphs
} }
log.Println("Main: Running...") log.Println("Main: Running...")

View File

@@ -11,7 +11,7 @@ done < "$FILE"
cd "${ROOT}" cd "${ROOT}"
find_files() { find_files() {
git ls-files | grep '\.go$' git ls-files | grep '\.go$' | grep -v '^examples/'
} }
bad_files=$( bad_files=$(