mirror of https://github.com/hashicorp/consul
parent
2b07355b94
commit
66edf0075a
@ -0,0 +1,41 @@
|
||||
package watch
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/armon/consul-api"
|
||||
)
|
||||
|
||||
// watchFactory is a function that can create a new WatchFunc
|
||||
// from a parameter configuration
|
||||
type watchFactory func(params map[string][]string) (WatchFunc, error)
|
||||
|
||||
// watchFuncFactory maps each type to a factory function
|
||||
var watchFuncFactory map[string]watchFactory
|
||||
|
||||
func init() {
|
||||
watchFuncFactory = map[string]watchFactory{
|
||||
"key": keyWatch,
|
||||
}
|
||||
}
|
||||
|
||||
// keyWatch is used to return a key watching function
|
||||
func keyWatch(params map[string][]string) (WatchFunc, error) {
|
||||
keys := params["key"]
|
||||
delete(params, "key")
|
||||
if len(keys) != 1 {
|
||||
return nil, fmt.Errorf("Must specify a single key to watch")
|
||||
}
|
||||
key := keys[0]
|
||||
|
||||
fn := func(p *WatchPlan) (uint64, interface{}, error) {
|
||||
kv := p.client.KV()
|
||||
opts := consulapi.QueryOptions{WaitIndex: p.lastIndex}
|
||||
pair, meta, err := kv.Get(key, &opts)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
return meta.LastIndex, pair, err
|
||||
}
|
||||
return fn, nil
|
||||
}
|
@ -0,0 +1,104 @@
|
||||
package watch
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"github.com/armon/consul-api"
|
||||
)
|
||||
|
||||
const (
|
||||
// retryInterval is the base retry value
|
||||
retryInterval = 5 * time.Second
|
||||
|
||||
// maximum back off time, this is to prevent
|
||||
// exponential runaway
|
||||
maxBackoffTime = 180 * time.Second
|
||||
)
|
||||
|
||||
// Run is used to run a watch plan
|
||||
func (p *WatchPlan) Run(address string) error {
|
||||
// Setup the client
|
||||
p.address = address
|
||||
conf := consulapi.DefaultConfig()
|
||||
conf.Address = address
|
||||
conf.Datacenter = p.Datacenter
|
||||
// TODO: conf.Token = p.Token
|
||||
client, err := consulapi.NewClient(conf)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to connect to agent: %v", err)
|
||||
}
|
||||
p.client = client
|
||||
|
||||
// Loop until we are canceled
|
||||
failures := 0
|
||||
for !p.shouldStop() {
|
||||
// Invoke the handler
|
||||
index, result, err := p.Func(p)
|
||||
|
||||
// Check if we should terminate since the function
|
||||
// could have blocked for a while
|
||||
if p.shouldStop() {
|
||||
break
|
||||
}
|
||||
|
||||
// Handle an error in the watch function
|
||||
if err != nil {
|
||||
log.Printf("consul.watch: Watch '%s' errored: %v", p.Query, err)
|
||||
|
||||
// Perform an exponential backoff
|
||||
failures++
|
||||
retry := retryInterval * time.Duration(failures*failures)
|
||||
if retry > maxBackoffTime {
|
||||
retry = maxBackoffTime
|
||||
}
|
||||
select {
|
||||
case <-time.After(retry):
|
||||
continue
|
||||
case <-p.stopCh:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Clear the failures
|
||||
failures = 0
|
||||
|
||||
// If the index is unchanged do nothing
|
||||
if index == p.lastIndex {
|
||||
continue
|
||||
}
|
||||
|
||||
// Update the index, look for change
|
||||
p.lastIndex = index
|
||||
if reflect.DeepEqual(p.lastResult, result) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Handle the updated result
|
||||
p.lastResult = result
|
||||
p.Handler(index, result)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop is used to stop running the watch plan
|
||||
func (p *WatchPlan) Stop() {
|
||||
p.stopLock.Lock()
|
||||
defer p.stopLock.Unlock()
|
||||
if p.stop {
|
||||
return
|
||||
}
|
||||
p.stop = true
|
||||
close(p.stopCh)
|
||||
}
|
||||
|
||||
func (p *WatchPlan) shouldStop() bool {
|
||||
select {
|
||||
case <-p.stopCh:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
package watch
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func init() {
|
||||
watchFuncFactory["noop"] = noopWatch
|
||||
}
|
||||
|
||||
func noopWatch(params map[string][]string) (WatchFunc, error) {
|
||||
fn := func(p *WatchPlan) (uint64, interface{}, error) {
|
||||
idx := p.lastIndex + 1
|
||||
return idx, idx, nil
|
||||
}
|
||||
return fn, nil
|
||||
}
|
||||
|
||||
func TestRun_Stop(t *testing.T) {
|
||||
plan, err := Parse("type:noop")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
var expect uint64 = 1
|
||||
plan.Handler = func(idx uint64, val interface{}) {
|
||||
if idx != expect {
|
||||
t.Fatalf("Bad: %d %d", expect, idx)
|
||||
}
|
||||
if val != expect {
|
||||
t.Fatalf("Bad: %d %d", expect, val)
|
||||
}
|
||||
expect++
|
||||
}
|
||||
|
||||
time.AfterFunc(10*time.Millisecond, func() {
|
||||
plan.Stop()
|
||||
})
|
||||
|
||||
err = plan.Run("127.0.0.1:8500")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
if expect == 1 {
|
||||
t.Fatalf("Bad: %d", expect)
|
||||
}
|
||||
}
|
Loading…
Reference in new issue