Resources: Add retry and retry delay meta parameters

All resources can now set a retry limit (-1 for infinite) and a delay
between retries. This applies to both the CheckApply methods, and the
Watch methods as well. They each have their own separate counts, but use
the same input meta param, since I decided it wouldn't be useful to have
a separate watchRetry and watchDelay set of meta parameters.

In the process, we got rid of about 15 error cases which would normally
panic.

This patch required a slight overhaul of the Event system.

The previous commit is an earlier version of this patch which I decided
to leave in to "show my work" as I used to have to do in math class.
It's slightly more correct with the current event system, and this
version is less correct and has a few bugs, but that is because the
event system needs a massive overhaul, and once that's done this should
all work properly for the corner cases.
This commit is contained in:
James Shubin
2016-09-19 05:47:11 -04:00
parent 53cabd5ee4
commit fc24c91dde
11 changed files with 237 additions and 356 deletions

View File

@@ -65,7 +65,7 @@ func (obj *TimerRes) Validate() bool {
}
// Watch is the primary listener for this resource and it outputs events.
func (obj *TimerRes) Watch(processChan chan Event, delay time.Duration) error {
func (obj *TimerRes) Watch(processChan chan Event) error {
if obj.IsWatching() {
return nil
}
@@ -74,59 +74,13 @@ func (obj *TimerRes) Watch(processChan chan Event, delay time.Duration) error {
cuuid := obj.converger.Register()
defer cuuid.Unregister()
var doSend func(string) (bool, error) // lol, golang doesn't support recursive lambdas
doSend = func(comment string) (bool, error) {
resp := NewResp()
processChan <- Event{eventNil, resp, comment, true} // trigger process
select {
case e := <-resp: // wait for the ACK()
if e != nil { // we got a NACK
return true, e // exit with error
}
case event := <-obj.events:
// NOTE: this code should match the similar code below!
cuuid.SetConverged(false)
if exit, send := obj.ReadEvent(&event); exit {
return true, nil // exit, without error
} else if send {
return doSend(comment) // recurse
}
}
return false, nil // return, no error or exit signal
}
// if a retry-delay was requested, wait, but don't block our events!
if delay > 0 {
var pendingSendEvent bool
timer := time.NewTimer(delay)
Loop:
for {
select {
case <-timer.C: // the wait is over
break Loop // critical
case event := <-obj.events:
// NOTE: this code should match the similar code below!
cuuid.SetConverged(false)
if exit, send := obj.ReadEvent(&event); exit {
return nil // exit
} else if send {
// NOTE: see long comment in the file resource
//if exit, err := doSend(); exit || err != nil {
// return err // we exit or bubble up a NACK...
//}
pendingSendEvent = true // all events are identical for now...
}
}
}
timer.Stop() // it's nice to cleanup
log.Printf("%s[%s]: Delay expired!", obj.Kind(), obj.GetName())
if pendingSendEvent { // TODO: should this become a list in the future?
if exit, err := doSend("pending delayed event"); exit || err != nil {
return err // we exit or bubble up a NACK...
}
var startup bool
Startup := func(block bool) <-chan time.Time {
if block {
return nil // blocks forever
//return make(chan time.Time) // blocks forever
}
return time.After(time.Duration(500) * time.Millisecond) // 1/2 the resolution of converged timeout
}
// Create a time.Ticker for the given interval
@@ -149,11 +103,16 @@ func (obj *TimerRes) Watch(processChan chan Event, delay time.Duration) error {
case <-cuuid.ConvergedTimer():
cuuid.SetConverged(true)
continue
case <-Startup(startup):
cuuid.SetConverged(false)
send = true
}
if send {
startup = true // startup finished
send = false
obj.isStateOK = false
if exit, err := doSend("timer ticked"); exit || err != nil {
if exit, err := obj.DoSend(processChan, "timer ticked"); exit || err != nil {
return err // we exit or bubble up a NACK...
}
}