From 71de8014d53c468281b7af083f7bea99c1f12d46 Mon Sep 17 00:00:00 2001 From: James Shubin Date: Mon, 24 Oct 2016 04:10:54 -0400 Subject: [PATCH] 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? --- examples/lib/libmgmt1.go | 110 +++++++++++++++++++++++++++++++++++++++ gconfig/gconfig.go | 44 ++++++++-------- mgmtmain/main.go | 50 +++++++++++++----- test/test-headerfmt.sh | 2 +- 4 files changed, 171 insertions(+), 35 deletions(-) create mode 100644 examples/lib/libmgmt1.go diff --git a/examples/lib/libmgmt1.go b/examples/lib/libmgmt1.go new file mode 100644 index 00000000..b5ac3244 --- /dev/null +++ b/examples/lib/libmgmt1.go @@ -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!") +} diff --git a/gconfig/gconfig.go b/gconfig/gconfig.go index 56eb6253..876d0ee9 100644 --- a/gconfig/gconfig.go +++ b/gconfig/gconfig.go @@ -41,33 +41,38 @@ type collectorResConfig struct { 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"` Name string `yaml:"name"` } -type edgeConfig struct { - Name string `yaml:"name"` - From vertexConfig `yaml:"from"` - To vertexConfig `yaml:"to"` +// Edge is the data structure of an edge. +type Edge struct { + Name string `yaml:"name"` + 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. type GraphConfig struct { - Graph string `yaml:"graph"` - 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"` - } `yaml:"resources"` + Graph string `yaml:"graph"` + Resources Resources `yaml:"resources"` Collector []collectorResConfig `yaml:"collect"` - Edges []edgeConfig `yaml:"edges"` + Edges []Edge `yaml:"edges"` Comment string `yaml:"comment"` Hostname string `yaml:"hostname"` // uuid for the host 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 // like exported => true || exported => (host pattern)||(other pattern?) 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) if v == nil { // no match found res.Init() diff --git a/mgmtmain/main.go b/mgmtmain/main.go index ac1a83fa..538916dd 100644 --- a/mgmtmain/main.go +++ b/mgmtmain/main.go @@ -49,10 +49,11 @@ type Main struct { Hostname *string // hostname to use; nil if undefined - File *string // graph file to run; nil if undefined - Puppet *string // puppet mode to run; nil if undefined - PuppetConf string // the path to an alternate puppet.conf file - Remotes []string // list of remote graph definitions to run + File *string // graph file to run; nil if undefined + Puppet *string // puppet mode to run; nil if undefined + PuppetConf string // the path to an alternate puppet.conf file + 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 Noop bool // globally force all resources into no-op mode @@ -80,12 +81,18 @@ type Main struct { clientURLs etcdtypes.URLs // processed client urls value serverURLs etcdtypes.URLs // processed server urls 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. 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 { 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.switchChan = make(chan func() *gconfig.GraphConfig) return nil } @@ -153,6 +161,14 @@ func (obj *Main) Exit(err error) { 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. func (obj *Main) Run() error { @@ -273,13 +289,14 @@ func (obj *Main) Run() error { go func() { startchan := make(chan struct{}) // start signal go func() { startchan <- struct{}{} }() - var configchan chan error - var puppetchan <-chan time.Time + var configChan chan error + var puppetChan <-chan time.Time + var customFunc = obj.GAPI // default if !obj.NoWatch && obj.File != nil { - configchan = recwatch.ConfigWatch(*obj.File) + configChan = recwatch.ConfigWatch(*obj.File) } else if obj.Puppet != nil { 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...") etcdchan := etcd.EtcdWatch(EmbdEtcd) @@ -296,10 +313,14 @@ func (obj *Main) Run() error { } // 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 - case e := <-configchan: + case e := <-configChan: if obj.NoWatch { continue // not ready to read config } @@ -319,7 +340,10 @@ func (obj *Main) Run() error { config = gconfig.ParseConfigFromFile(*obj.File) } else if obj.Puppet != nil { config = puppet.ParseConfigFromPuppet(*obj.Puppet, obj.PuppetConf) + } else if obj.GAPI != nil { + config = obj.GAPI() } + if config == nil { log.Printf("Config: Parse failure") continue @@ -337,7 +361,7 @@ func (obj *Main) Run() error { 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 if newFullgraph, err := config.NewGraphFromConfig(fullGraph, EmbdEtcd, obj.Noop); err == nil { // keep references to all original elements 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! 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 } log.Println("Main: Running...") diff --git a/test/test-headerfmt.sh b/test/test-headerfmt.sh index ccd64b2d..7f5fcbcd 100755 --- a/test/test-headerfmt.sh +++ b/test/test-headerfmt.sh @@ -11,7 +11,7 @@ done < "$FILE" cd "${ROOT}" find_files() { - git ls-files | grep '\.go$' + git ls-files | grep '\.go$' | grep -v '^examples/' } bad_files=$(