gapi: Allow the GAPI implementer to specify fast and exit

This allows the implementer of the GAPI to specify three parameters for
every Next message sent on the channel. The Fast parameter tells the
agent if it should do the pause quickly or if it should finish the
sequence. A quick pause means that it will cause a pause immediately
after the currently running resources finish, where as a slow (default)
pause will allow the wave of execution to finish. This is usually
preferred in scenarios where complex graphs are used where we want each
step to complete. The Exit parameter tells the engine to exit, and the
Err parameter tells the engine that an error occurred.
This commit is contained in:
James Shubin
2017-06-02 03:39:42 -04:00
parent 9531465410
commit 9cbaa892d3
12 changed files with 129 additions and 61 deletions

View File

@@ -124,14 +124,18 @@ func (obj *MyGAPI) Graph() (*pgraph.Graph, error) {
} }
// Next returns nil errors every time there could be a new graph. // Next returns nil errors every time there could be a new graph.
func (obj *MyGAPI) Next() chan error { func (obj *MyGAPI) Next() chan gapi.Next {
ch := make(chan error) ch := make(chan gapi.Next)
obj.wg.Add(1) obj.wg.Add(1)
go func() { go func() {
defer obj.wg.Done() defer obj.wg.Done()
defer close(ch) // this will run before the obj.wg.Done() defer close(ch) // this will run before the obj.wg.Done()
if !obj.initialized { if !obj.initialized {
ch <- fmt.Errorf("libmgmt: MyGAPI is not initialized") next := gapi.Next{
Err: fmt.Errorf("libmgmt: MyGAPI is not initialized"),
Exit: true, // exit, b/c programming error?
}
ch <- next
return return
} }
startChan := make(chan struct{}) // start signal startChan := make(chan struct{}) // start signal
@@ -159,7 +163,7 @@ func (obj *MyGAPI) Next() chan error {
log.Printf("libmgmt: Generating new graph...") log.Printf("libmgmt: Generating new graph...")
select { select {
case ch <- nil: // trigger a run case ch <- gapi.Next{}: // trigger a run
case <-obj.closeChan: case <-obj.closeChan:
return return
} }

View File

@@ -133,14 +133,18 @@ func (obj *MyGAPI) Graph() (*pgraph.Graph, error) {
} }
// Next returns nil errors every time there could be a new graph. // Next returns nil errors every time there could be a new graph.
func (obj *MyGAPI) Next() chan error { func (obj *MyGAPI) Next() chan gapi.Next {
ch := make(chan error) ch := make(chan gapi.Next)
obj.wg.Add(1) obj.wg.Add(1)
go func() { go func() {
defer obj.wg.Done() defer obj.wg.Done()
defer close(ch) // this will run before the obj.wg.Done() defer close(ch) // this will run before the obj.wg.Done()
if !obj.initialized { if !obj.initialized {
ch <- fmt.Errorf("libmgmt: MyGAPI is not initialized") next := gapi.Next{
Err: fmt.Errorf("libmgmt: MyGAPI is not initialized"),
Exit: true, // exit, b/c programming error?
}
ch <- next
return return
} }
startChan := make(chan struct{}) // start signal startChan := make(chan struct{}) // start signal
@@ -168,7 +172,7 @@ func (obj *MyGAPI) Next() chan error {
log.Printf("libmgmt: Generating new graph...") log.Printf("libmgmt: Generating new graph...")
select { select {
case ch <- nil: // trigger a run case ch <- gapi.Next{}: // trigger a run
case <-obj.closeChan: case <-obj.closeChan:
return return
} }

View File

@@ -121,14 +121,18 @@ func (obj *MyGAPI) Graph() (*pgraph.Graph, error) {
} }
// Next returns nil errors every time there could be a new graph. // Next returns nil errors every time there could be a new graph.
func (obj *MyGAPI) Next() chan error { func (obj *MyGAPI) Next() chan gapi.Next {
ch := make(chan error) ch := make(chan gapi.Next)
obj.wg.Add(1) obj.wg.Add(1)
go func() { go func() {
defer obj.wg.Done() defer obj.wg.Done()
defer close(ch) // this will run before the obj.wg.Done() defer close(ch) // this will run before the obj.wg.Done()
if !obj.initialized { if !obj.initialized {
ch <- fmt.Errorf("libmgmt: MyGAPI is not initialized") next := gapi.Next{
Err: fmt.Errorf("libmgmt: MyGAPI is not initialized"),
Exit: true, // exit, b/c programming error?
}
ch <- next
return return
} }
startChan := make(chan struct{}) // start signal startChan := make(chan struct{}) // start signal
@@ -156,7 +160,7 @@ func (obj *MyGAPI) Next() chan error {
log.Printf("libmgmt: Generating new graph...") log.Printf("libmgmt: Generating new graph...")
select { select {
case ch <- nil: // trigger a run case ch <- gapi.Next{}: // trigger a run
case <-obj.closeChan: case <-obj.closeChan:
return return
} }

View File

@@ -88,15 +88,18 @@ func (obj *MyGAPI) Graph() (*pgraph.Graph, error) {
} }
// Next returns nil errors every time there could be a new graph. // Next returns nil errors every time there could be a new graph.
func (obj *MyGAPI) Next() chan error { func (obj *MyGAPI) Next() chan gapi.Next {
ch := make(chan error) ch := make(chan gapi.Next)
obj.wg.Add(1) obj.wg.Add(1)
go func() { go func() {
defer obj.wg.Done() defer obj.wg.Done()
defer close(ch) // this will run before the obj.wg.Done() defer close(ch) // this will run before the obj.wg.Done()
if !obj.initialized { if !obj.initialized {
ch <- fmt.Errorf("libmgmt: MyGAPI is not initialized") next := gapi.Next{
return Err: fmt.Errorf("libmgmt: MyGAPI is not initialized"),
Exit: true, // exit, b/c programming error?
}
ch <- next
} }
startChan := make(chan struct{}) // start signal startChan := make(chan struct{}) // start signal
close(startChan) // kick it off! close(startChan) // kick it off!
@@ -123,7 +126,7 @@ func (obj *MyGAPI) Next() chan error {
log.Printf("libmgmt: Generating new graph...") log.Printf("libmgmt: Generating new graph...")
select { select {
case ch <- nil: // trigger a run case ch <- gapi.Next{}: // trigger a run
case <-obj.closeChan: case <-obj.closeChan:
return return
} }

View File

@@ -83,15 +83,18 @@ func (obj *MyGAPI) Graph() (*pgraph.Graph, error) {
} }
// Next returns nil errors every time there could be a new graph. // Next returns nil errors every time there could be a new graph.
func (obj *MyGAPI) Next() chan error { func (obj *MyGAPI) Next() chan gapi.Next {
ch := make(chan error) ch := make(chan gapi.Next)
obj.wg.Add(1) obj.wg.Add(1)
go func() { go func() {
defer obj.wg.Done() defer obj.wg.Done()
defer close(ch) // this will run before the obj.wg.Done() defer close(ch) // this will run before the obj.wg.Done()
if !obj.initialized { if !obj.initialized {
ch <- fmt.Errorf("libmgmt: MyGAPI is not initialized") next := gapi.Next{
return Err: fmt.Errorf("libmgmt: MyGAPI is not initialized"),
Exit: true, // exit, b/c programming error?
}
ch <- next
} }
startChan := make(chan struct{}) // start signal startChan := make(chan struct{}) // start signal
close(startChan) // kick it off! close(startChan) // kick it off!
@@ -118,7 +121,7 @@ func (obj *MyGAPI) Next() chan error {
log.Printf("libmgmt: Generating new graph...") log.Printf("libmgmt: Generating new graph...")
select { select {
case ch <- nil: // trigger a run case ch <- gapi.Next{}: // trigger a run
case <-obj.closeChan: case <-obj.closeChan:
return return
} }

View File

@@ -127,15 +127,18 @@ func (obj *MyGAPI) Graph() (*pgraph.Graph, error) {
} }
// Next returns nil errors every time there could be a new graph. // Next returns nil errors every time there could be a new graph.
func (obj *MyGAPI) Next() chan error { func (obj *MyGAPI) Next() chan gapi.Next {
ch := make(chan error) ch := make(chan gapi.Next)
obj.wg.Add(1) obj.wg.Add(1)
go func() { go func() {
defer obj.wg.Done() defer obj.wg.Done()
defer close(ch) // this will run before the obj.wg.Done() defer close(ch) // this will run before the obj.wg.Done()
if !obj.initialized { if !obj.initialized {
ch <- fmt.Errorf("libmgmt: MyGAPI is not initialized") next := gapi.Next{
return Err: fmt.Errorf("libmgmt: MyGAPI is not initialized"),
Exit: true, // exit, b/c programming error?
}
ch <- next
} }
startChan := make(chan struct{}) // start signal startChan := make(chan struct{}) // start signal
close(startChan) // kick it off! close(startChan) // kick it off!
@@ -162,7 +165,7 @@ func (obj *MyGAPI) Next() chan error {
log.Printf("libmgmt: Generating new graph...") log.Printf("libmgmt: Generating new graph...")
select { select {
case ch <- nil: // trigger a run case ch <- gapi.Next{}: // trigger a run
case <-obj.closeChan: case <-obj.closeChan:
return return
} }

View File

@@ -33,10 +33,23 @@ type Data struct {
// NOTE: we can add more fields here if needed by GAPI endpoints // NOTE: we can add more fields here if needed by GAPI endpoints
} }
// Next describes the particular response the GAPI implementer wishes to emit.
type Next struct {
// FIXME: the Fast pause parameter should eventually get replaced with a
// "SwitchMethod" parameter or similar that instead lets the implementer
// choose between fast pause, slow pause, and interrupt. Interrupt could
// be a future extension to the Resource API that lets an Interrupt() be
// called if we want to exit immediately from the CheckApply part of the
// resource for some reason. For now we'll keep this simple with a bool.
Fast bool // run a fast pause to switch?
Exit bool // should we cause the program to exit? (specify err or not)
Err error // if something goes wrong (use with or without exit!)
}
// GAPI is a Graph API that represents incoming graphs and change streams. // GAPI is a Graph API that represents incoming graphs and change streams.
type GAPI interface { type GAPI interface {
Init(Data) error // initializes the GAPI and passes in useful data Init(Data) error // initializes the GAPI and passes in useful data
Graph() (*pgraph.Graph, error) // returns the most recent pgraph Graph() (*pgraph.Graph, error) // returns the most recent pgraph
Next() chan error // returns a stream of switch events Next() chan Next // returns a stream of switch events
Close() error // shutdown the GAPI Close() error // shutdown the GAPI
} }

View File

@@ -380,7 +380,7 @@ func (obj *Main) Run() error {
Debug: obj.Flags.Debug, Debug: obj.Flags.Debug,
} }
var gapiChan chan error // stream events are nil errors var gapiChan chan gapi.Next // stream events contain some instructions!
if obj.GAPI != nil { if obj.GAPI != nil {
data := gapi.Data{ data := gapi.Data{
Hostname: hostname, Hostname: hostname,
@@ -405,8 +405,9 @@ func (obj *Main) Run() error {
log.Println("Main: Waiting...") log.Println("Main: Waiting...")
// The GAPI should always kick off an event on Next() at // The GAPI should always kick off an event on Next() at
// startup when (and if) it indeed has a graph to share! // startup when (and if) it indeed has a graph to share!
fastPause := false
select { select {
case err, ok := <-gapiChan: case next, ok := <-gapiChan:
if !ok { // channel closed if !ok { // channel closed
if obj.Flags.Debug { if obj.Flags.Debug {
log.Printf("Main: GAPI exited") log.Printf("Main: GAPI exited")
@@ -415,17 +416,22 @@ func (obj *Main) Run() error {
continue continue
} }
// if we've been asked to exit...
if next.Exit {
obj.Exit(next.Err) // trigger exit
continue // wait for exitchan
}
// the gapi lets us send an error to the channel // the gapi lets us send an error to the channel
// this means there was a failure, but not fatal // this means there was a failure, but not fatal
if err != nil { if err := next.Err; err != nil {
log.Printf("Main: Error with graph stream: %v", err) log.Printf("Main: Error with graph stream: %v", err)
// TODO: consider adding an option to continue // wait for another event
// exit on stream errors...
//obj.Exit(err) // trigger exit
continue // wait for exitchan or another event
} }
// everything else passes through to cause a compile! // everything else passes through to cause a compile!
fastPause = next.Fast // should we pause fast?
case <-exitchan: case <-exitchan:
return return
} }
@@ -439,7 +445,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?
graph.Pause(false) // sync graph.Pause(fastPause) // sync
//graph.UnGroup() // FIXME: implement me if needed! //graph.UnGroup() // FIXME: implement me if needed!
} }

View File

@@ -75,17 +75,21 @@ func (obj *GAPI) Graph() (*pgraph.Graph, error) {
} }
// Next returns nil errors every time there could be a new graph. // Next returns nil errors every time there could be a new graph.
func (obj *GAPI) Next() chan error { func (obj *GAPI) Next() chan gapi.Next {
puppetChan := func() <-chan time.Time { // helper function puppetChan := func() <-chan time.Time { // helper function
return time.Tick(time.Duration(RefreshInterval(obj.PuppetConf)) * time.Second) return time.Tick(time.Duration(RefreshInterval(obj.PuppetConf)) * time.Second)
} }
ch := make(chan error) ch := make(chan gapi.Next)
obj.wg.Add(1) obj.wg.Add(1)
go func() { go func() {
defer obj.wg.Done() defer obj.wg.Done()
defer close(ch) // this will run before the obj.wg.Done() defer close(ch) // this will run before the obj.wg.Done()
if !obj.initialized { if !obj.initialized {
ch <- fmt.Errorf("the puppet GAPI is not initialized") next := gapi.Next{
Err: fmt.Errorf("the puppet GAPI is not initialized"),
Exit: true, // exit, b/c programming error?
}
ch <- next
return return
} }
startChan := make(chan struct{}) // start signal startChan := make(chan struct{}) // start signal
@@ -119,8 +123,12 @@ func (obj *GAPI) Next() chan error {
} else { } else {
pChan = puppetChan() // TODO: okay to update interval in case it changed? pChan = puppetChan() // TODO: okay to update interval in case it changed?
} }
next := gapi.Next{
//Exit: true, // TODO: for permanent shutdown!
Err: nil,
}
select { select {
case ch <- nil: // trigger a run (send a msg) case ch <- next: // trigger a run (send a msg)
// unblock if we exit while waiting to send! // unblock if we exit while waiting to send!
case <-obj.closeChan: case <-obj.closeChan:
return return

View File

@@ -75,35 +75,39 @@ func (obj *MyGAPI) Graph() (*pgraph.Graph, error) {
} }
// Next returns nil errors every time there could be a new graph. // Next returns nil errors every time there could be a new graph.
func (obj *MyGAPI) Next() chan error { func (obj *MyGAPI) Next() chan gapi.Next {
ch := make(chan error) ch := make(chan gapi.Next)
obj.wg.Add(1) obj.wg.Add(1)
go func() { go func() {
defer obj.wg.Done() defer obj.wg.Done()
defer close(ch) // this will run before the obj.wg.Done() defer close(ch) // this will run before the obj.wg.Done()
if !obj.initialized { if !obj.initialized {
ch <- fmt.Errorf("%s: MyGAPI is not initialized", obj.Name) next := gapi.Next{
Err: fmt.Errorf("%s: MyGAPI is not initialized", obj.Name),
Exit: true, // exit, b/c programming error?
}
ch <- next
return return
} }
log.Printf("%s: Generating a bunch of new graphs...", obj.Name) log.Printf("%s: Generating a bunch of new graphs...", obj.Name)
ch <- nil ch <- gapi.Next{}
log.Printf("%s: New graph...", obj.Name) log.Printf("%s: New graph...", obj.Name)
ch <- nil ch <- gapi.Next{}
log.Printf("%s: New graph...", obj.Name) log.Printf("%s: New graph...", obj.Name)
ch <- nil ch <- gapi.Next{}
log.Printf("%s: New graph...", obj.Name) log.Printf("%s: New graph...", obj.Name)
ch <- nil ch <- gapi.Next{}
log.Printf("%s: New graph...", obj.Name) log.Printf("%s: New graph...", obj.Name)
ch <- nil ch <- gapi.Next{}
log.Printf("%s: New graph...", obj.Name) log.Printf("%s: New graph...", obj.Name)
ch <- nil ch <- gapi.Next{}
log.Printf("%s: New graph...", obj.Name) log.Printf("%s: New graph...", obj.Name)
ch <- nil ch <- gapi.Next{}
log.Printf("%s: New graph...", obj.Name) log.Printf("%s: New graph...", obj.Name)
ch <- nil ch <- gapi.Next{}
log.Printf("%s: New graph...", obj.Name) log.Printf("%s: New graph...", obj.Name)
ch <- nil ch <- gapi.Next{}
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
log.Printf("%s: Done generating graphs!", obj.Name) log.Printf("%s: Done generating graphs!", obj.Name)
}() }()

View File

@@ -77,14 +77,18 @@ func (obj *GAPI) Graph() (*pgraph.Graph, error) {
} }
// Next returns nil errors every time there could be a new graph. // Next returns nil errors every time there could be a new graph.
func (obj *GAPI) Next() chan error { func (obj *GAPI) Next() chan gapi.Next {
ch := make(chan error) ch := make(chan gapi.Next)
obj.wg.Add(1) obj.wg.Add(1)
go func() { go func() {
defer obj.wg.Done() defer obj.wg.Done()
defer close(ch) // this will run before the obj.wg.Done() defer close(ch) // this will run before the obj.wg.Done()
if !obj.initialized { if !obj.initialized {
ch <- fmt.Errorf("yamlgraph: GAPI is not initialized") next := gapi.Next{
Err: fmt.Errorf("yamlgraph: GAPI is not initialized"),
Exit: true, // exit, b/c programming error?
}
ch <- next
return return
} }
startChan := make(chan struct{}) // start signal startChan := make(chan struct{}) // start signal
@@ -122,8 +126,12 @@ func (obj *GAPI) Next() chan error {
} }
log.Printf("yamlgraph: Generating new graph...") log.Printf("yamlgraph: Generating new graph...")
next := gapi.Next{
//Exit: true, // TODO: for permanent shutdown!
Err: err,
}
select { select {
case ch <- err: // trigger a run (send a msg) case ch <- next: // trigger a run (send a msg)
// TODO: if the error is really bad, we could: // TODO: if the error is really bad, we could:
//if err != nil { //if err != nil {
// return // return

View File

@@ -77,14 +77,18 @@ func (obj *GAPI) Graph() (*pgraph.Graph, error) {
} }
// Next returns nil errors every time there could be a new graph. // Next returns nil errors every time there could be a new graph.
func (obj *GAPI) Next() chan error { func (obj *GAPI) Next() chan gapi.Next {
ch := make(chan error) ch := make(chan gapi.Next)
obj.wg.Add(1) obj.wg.Add(1)
go func() { go func() {
defer obj.wg.Done() defer obj.wg.Done()
defer close(ch) // this will run before the obj.wg.Done() defer close(ch) // this will run before the obj.wg.Done()
if !obj.initialized { if !obj.initialized {
ch <- fmt.Errorf("yamlgraph: GAPI is not initialized") next := gapi.Next{
Err: fmt.Errorf("yamlgraph: GAPI is not initialized"),
Exit: true, // exit, b/c programming error?
}
ch <- next
return return
} }
startChan := make(chan struct{}) // start signal startChan := make(chan struct{}) // start signal
@@ -122,8 +126,12 @@ func (obj *GAPI) Next() chan error {
} }
log.Printf("yamlgraph: Generating new graph...") log.Printf("yamlgraph: Generating new graph...")
next := gapi.Next{
//Exit: true, // TODO: for permanent shutdown!
Err: err,
}
select { select {
case ch <- err: // trigger a run (send a msg) case ch <- next: // trigger a run (send a msg)
// TODO: if the error is really bad, we could: // TODO: if the error is really bad, we could:
//if err != nil { //if err != nil {
// return // return