resources: Add poll metaparameter
This allows a resource to use polling instead of the event based mechanism. This isn't recommended, but it could be useful, and it was certainly fun to code!
This commit is contained in:
@@ -449,6 +449,23 @@ they could have separate values, I've decided to use the same ones for both
|
|||||||
until there's a proper reason to want to do something differently for the Watch
|
until there's a proper reason to want to do something differently for the Watch
|
||||||
errors.
|
errors.
|
||||||
|
|
||||||
|
####Poll
|
||||||
|
Integer. Number of seconds to wait between `CheckApply` checks. If this is
|
||||||
|
greater than zero, then the standard event based `Watch` mechanism for this
|
||||||
|
resource is replaced with a simple polling mechanism. In general, this is not
|
||||||
|
recommended, unless you have a very good reason for doing so.
|
||||||
|
|
||||||
|
Please keep in mind that if you have a resource which changes every `I` seconds,
|
||||||
|
and you poll it every `J` seconds, and you've asked for a converged timeout of
|
||||||
|
`K` seconds, and `I <= J <= K`, then your graph will likely never converge.
|
||||||
|
|
||||||
|
When polling, the system detects that a resource is not converged if its
|
||||||
|
`CheckApply` method returns false. This allows a resource which changes every
|
||||||
|
`I` seconds, and which is polled every `J` seconds, and with a converged timeout
|
||||||
|
of `K` seconds to still converge when `J <= K`, as long as `I > J || I > K`,
|
||||||
|
which is another way of saying that if the resource finally settles down to give
|
||||||
|
the graph enough time, it can probably converge.
|
||||||
|
|
||||||
###Graph definition file
|
###Graph definition file
|
||||||
graph.yaml is the compiled graph definition file. The format is currently
|
graph.yaml is the compiled graph definition file. The format is currently
|
||||||
undocumented, but by looking through the [examples/](https://github.com/purpleidea/mgmt/tree/master/examples)
|
undocumented, but by looking through the [examples/](https://github.com/purpleidea/mgmt/tree/master/examples)
|
||||||
|
|||||||
24
examples/poll1.yaml
Normal file
24
examples/poll1.yaml
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
---
|
||||||
|
graph: mygraph
|
||||||
|
resources:
|
||||||
|
file:
|
||||||
|
- name: file1
|
||||||
|
meta:
|
||||||
|
poll: 5
|
||||||
|
path: "/tmp/mgmt/f1"
|
||||||
|
content: |
|
||||||
|
i am f1
|
||||||
|
state: exists
|
||||||
|
- name: file2
|
||||||
|
path: "/tmp/mgmt/f2"
|
||||||
|
content: |
|
||||||
|
i am f2
|
||||||
|
state: exists
|
||||||
|
- name: file3
|
||||||
|
meta:
|
||||||
|
poll: 1
|
||||||
|
path: "/tmp/mgmt/f3"
|
||||||
|
content: |
|
||||||
|
i am f3
|
||||||
|
state: exists
|
||||||
|
edges: []
|
||||||
@@ -209,8 +209,30 @@ func (g *Graph) Process(v *Vertex) error {
|
|||||||
|
|
||||||
// run the CheckApply!
|
// run the CheckApply!
|
||||||
} else {
|
} else {
|
||||||
|
// if the CheckApply run takes longer than the converged
|
||||||
|
// timeout, we could inappropriately converge mid-apply!
|
||||||
|
// avoid this by blocking convergence with a fake report
|
||||||
|
block := obj.Converger().Register() // get an extra cuid
|
||||||
|
block.SetConverged(false) // block while CheckApply runs!
|
||||||
|
|
||||||
// if this fails, don't UpdateTimestamp()
|
// if this fails, don't UpdateTimestamp()
|
||||||
checkOK, err = obj.CheckApply(!noop)
|
checkOK, err = obj.CheckApply(!noop)
|
||||||
|
|
||||||
|
block.SetConverged(true) // unblock
|
||||||
|
block.Unregister()
|
||||||
|
|
||||||
|
// TODO: Can the `Poll` converged timeout tracking be a
|
||||||
|
// more general method for all converged timeouts? this
|
||||||
|
// would simplify the resources by removing boilerplate
|
||||||
|
if v.Meta().Poll > 0 {
|
||||||
|
if !checkOK { // something changed, restart timer
|
||||||
|
cuid := v.Res.ConvergerUID() // get the converger uid used to report status
|
||||||
|
cuid.ResetTimer() // activity!
|
||||||
|
if g.Flags.Debug {
|
||||||
|
log.Printf("%s[%s]: Converger: ResetTimer", obj.Kind(), obj.GetName())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if checkOK && err != nil { // should never return this way
|
if checkOK && err != nil { // should never return this way
|
||||||
@@ -419,7 +441,15 @@ func (g *Graph) Worker(v *Vertex) error {
|
|||||||
|
|
||||||
// TODO: reset the watch retry count after some amount of success
|
// TODO: reset the watch retry count after some amount of success
|
||||||
v.Res.RegisterConverger()
|
v.Res.RegisterConverger()
|
||||||
e := v.Res.Watch(processChan)
|
var e error
|
||||||
|
if v.Meta().Poll > 0 { // poll instead of watching :(
|
||||||
|
cuid := v.Res.ConvergerUID() // get the converger uid used to report status
|
||||||
|
cuid.StartTimer()
|
||||||
|
e = v.Res.Poll(processChan)
|
||||||
|
cuid.StopTimer() // clean up nicely
|
||||||
|
} else {
|
||||||
|
e = v.Res.Watch(processChan) // run the watch normally
|
||||||
|
}
|
||||||
v.Res.UnregisterConverger()
|
v.Res.UnregisterConverger()
|
||||||
if e == nil { // exit signal
|
if e == nil { // exit signal
|
||||||
err = nil // clean exit
|
err = nil // clean exit
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
"time"
|
||||||
|
|
||||||
// TODO: should each resource be a sub-package?
|
// TODO: should each resource be a sub-package?
|
||||||
"github.com/purpleidea/mgmt/converger"
|
"github.com/purpleidea/mgmt/converger"
|
||||||
@@ -93,6 +94,7 @@ type MetaParams struct {
|
|||||||
// reason to want to do something differently for the Watch errors.
|
// reason to want to do something differently for the Watch errors.
|
||||||
Retry int16 `yaml:"retry"` // metaparam, number of times to retry on error. -1 for infinite
|
Retry int16 `yaml:"retry"` // metaparam, number of times to retry on error. -1 for infinite
|
||||||
Delay uint64 `yaml:"delay"` // metaparam, number of milliseconds to wait between retries
|
Delay uint64 `yaml:"delay"` // metaparam, number of milliseconds to wait between retries
|
||||||
|
Poll uint32 `yaml:"poll"` // metaparam, number of seconds between poll interval, 0 to watch.
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalYAML is the custom unmarshal handler for the MetaParams struct. It
|
// UnmarshalYAML is the custom unmarshal handler for the MetaParams struct. It
|
||||||
@@ -116,6 +118,7 @@ var DefaultMetaParams = MetaParams{
|
|||||||
Noop: false,
|
Noop: false,
|
||||||
Retry: 0, // TODO: is this a good default?
|
Retry: 0, // TODO: is this a good default?
|
||||||
Delay: 0, // TODO: is this a good default?
|
Delay: 0, // TODO: is this a good default?
|
||||||
|
Poll: 0, // defaults to watching for events
|
||||||
}
|
}
|
||||||
|
|
||||||
// The Base interface is everything that is common to all resources.
|
// The Base interface is everything that is common to all resources.
|
||||||
@@ -130,6 +133,7 @@ type Base interface {
|
|||||||
AssociateData(*Data)
|
AssociateData(*Data)
|
||||||
IsWatching() bool
|
IsWatching() bool
|
||||||
SetWatching(bool)
|
SetWatching(bool)
|
||||||
|
Converger() converger.Converger
|
||||||
RegisterConverger()
|
RegisterConverger()
|
||||||
UnregisterConverger()
|
UnregisterConverger()
|
||||||
ConvergerUID() converger.ConvergerUID
|
ConvergerUID() converger.ConvergerUID
|
||||||
@@ -153,6 +157,7 @@ type Base interface {
|
|||||||
Running(chan event.Event) error // notify the engine that Watch started
|
Running(chan event.Event) error // notify the engine that Watch started
|
||||||
Started() <-chan struct{} // returns when the resource has started
|
Started() <-chan struct{} // returns when the resource has started
|
||||||
Starter(bool)
|
Starter(bool)
|
||||||
|
Poll(chan event.Event) error // poll alternative to watching :(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Res is the minimum interface you need to implement to define a new resource.
|
// Res is the minimum interface you need to implement to define a new resource.
|
||||||
@@ -295,6 +300,12 @@ func (obj *BaseRes) SetWatching(b bool) {
|
|||||||
obj.watching = b
|
obj.watching = b
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Converger returns the converger object used by the system. It can be used to
|
||||||
|
// register new convergers if needed.
|
||||||
|
func (obj *BaseRes) Converger() converger.Converger {
|
||||||
|
return obj.converger
|
||||||
|
}
|
||||||
|
|
||||||
// RegisterConverger sets up the cuid for the resource. This is a helper
|
// RegisterConverger sets up the cuid for the resource. This is a helper
|
||||||
// function for the engine, and shouldn't be called by the resources directly.
|
// function for the engine, and shouldn't be called by the resources directly.
|
||||||
func (obj *BaseRes) RegisterConverger() {
|
func (obj *BaseRes) RegisterConverger() {
|
||||||
@@ -399,6 +410,9 @@ func (obj *BaseRes) Compare(res Res) bool {
|
|||||||
if obj.Meta().Delay != res.Meta().Delay {
|
if obj.Meta().Delay != res.Meta().Delay {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if obj.Meta().Poll != res.Meta().Poll {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -438,6 +452,45 @@ func (obj *BaseRes) Started() <-chan struct{} { return obj.started }
|
|||||||
// If we have an indegree of 0, we'll need to be a poke initiator in the graph.
|
// If we have an indegree of 0, we'll need to be a poke initiator in the graph.
|
||||||
func (obj *BaseRes) Starter(b bool) { obj.starter = b }
|
func (obj *BaseRes) Starter(b bool) { obj.starter = b }
|
||||||
|
|
||||||
|
// Poll is the watch replacement for when we want to poll, which outputs events.
|
||||||
|
func (obj *BaseRes) Poll(processChan chan event.Event) error {
|
||||||
|
cuid := obj.ConvergerUID() // get the converger uid used to report status
|
||||||
|
|
||||||
|
// create a time.Ticker for the given interval
|
||||||
|
ticker := time.NewTicker(time.Duration(obj.Meta().Poll) * time.Second)
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
// notify engine that we're running
|
||||||
|
if err := obj.Running(processChan); err != nil {
|
||||||
|
return err // bubble up a NACK...
|
||||||
|
}
|
||||||
|
|
||||||
|
var send = false
|
||||||
|
var exit = false
|
||||||
|
for {
|
||||||
|
obj.SetState(ResStateWatching)
|
||||||
|
select {
|
||||||
|
case <-ticker.C: // received the timer event
|
||||||
|
log.Printf("%s[%s]: polling...", obj.Kind(), obj.GetName())
|
||||||
|
send = true
|
||||||
|
obj.StateOK(false) // dirty
|
||||||
|
|
||||||
|
case event := <-obj.Events():
|
||||||
|
cuid.ResetTimer() // important
|
||||||
|
if exit, send = obj.ReadEvent(&event); exit {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if send {
|
||||||
|
send = false
|
||||||
|
if exit, err := obj.DoSend(processChan, ""); exit || err != nil {
|
||||||
|
return err // we exit or bubble up a NACK...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ResToB64 encodes a resource to a base64 encoded string (after serialization)
|
// ResToB64 encodes a resource to a base64 encoded string (after serialization)
|
||||||
func ResToB64(res Res) (string, error) {
|
func ResToB64(res Res) (string, error) {
|
||||||
b := bytes.Buffer{}
|
b := bytes.Buffer{}
|
||||||
|
|||||||
@@ -62,8 +62,6 @@ func (obj *BaseRes) ReadEvent(ev *event.Event) (exit, send bool) {
|
|||||||
if ev.GetActivity() { // if previous node did work, and we were notified...
|
if ev.GetActivity() { // if previous node did work, and we were notified...
|
||||||
//obj.StateOK(false) // not necessarily
|
//obj.StateOK(false) // not necessarily
|
||||||
poke = true // poke!
|
poke = true // poke!
|
||||||
// XXX: this should be elsewhere in case Watch isn't used (eg: Polling instead...)
|
|
||||||
// XXX: unless this is used in our "fallback" polling implementation???
|
|
||||||
//obj.SetRefresh(true) // TODO: is this redundant?
|
//obj.SetRefresh(true) // TODO: is this redundant?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user