mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-04 18:00:08 +00:00
Watch delivers current state for resourceVersion=0
Allows clients to get the current state without having to execute a get followed by a watch. Makes integration with action loops much cleaner.
This commit is contained in:
parent
c71866164f
commit
c5630a9567
@ -106,12 +106,21 @@ func IsEtcdWatchStoppedByUser(err error) bool {
|
|||||||
return etcd.ErrWatchStoppedByUser == err
|
return etcd.ErrWatchStoppedByUser == err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns true iff err is an etcd error, whose errorCode matches errorCode
|
// isEtcdErrorNum returns true iff err is an etcd error, whose errorCode matches errorCode
|
||||||
func isEtcdErrorNum(err error, errorCode int) bool {
|
func isEtcdErrorNum(err error, errorCode int) bool {
|
||||||
etcdError, ok := err.(*etcd.EtcdError)
|
etcdError, ok := err.(*etcd.EtcdError)
|
||||||
return ok && etcdError != nil && etcdError.ErrorCode == errorCode
|
return ok && etcdError != nil && etcdError.ErrorCode == errorCode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// etcdErrorIndex returns the index associated with the error message and whether the
|
||||||
|
// index was available.
|
||||||
|
func etcdErrorIndex(err error) (uint64, bool) {
|
||||||
|
if etcdError, ok := err.(*etcd.EtcdError); ok {
|
||||||
|
return etcdError.Index, true
|
||||||
|
}
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
func (h *EtcdHelper) listEtcdNode(key string) ([]*etcd.Node, error) {
|
func (h *EtcdHelper) listEtcdNode(key string) ([]*etcd.Node, error) {
|
||||||
result, err := h.Client.Get(key, false, true)
|
result, err := h.Client.Get(key, false, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -297,9 +306,9 @@ func Everything(interface{}) bool {
|
|||||||
// WatchList begins watching the specified key's items. Items are decoded into
|
// WatchList begins watching the specified key's items. Items are decoded into
|
||||||
// API objects, and any items passing 'filter' are sent down the returned
|
// API objects, and any items passing 'filter' are sent down the returned
|
||||||
// watch.Interface. resourceVersion may be used to specify what version to begin
|
// watch.Interface. resourceVersion may be used to specify what version to begin
|
||||||
// watching (e.g., for reconnecting without missing any updateds).
|
// watching (e.g., for reconnecting without missing any updates).
|
||||||
func (h *EtcdHelper) WatchList(key string, resourceVersion uint64, filter FilterFunc) (watch.Interface, error) {
|
func (h *EtcdHelper) WatchList(key string, resourceVersion uint64, filter FilterFunc) (watch.Interface, error) {
|
||||||
w := newEtcdWatcher(true, filter, h.Codec)
|
w := newEtcdWatcher(true, filter, h.Codec, h.ResourceVersioner, nil)
|
||||||
go w.etcdWatch(h.Client, key, resourceVersion)
|
go w.etcdWatch(h.Client, key, resourceVersion)
|
||||||
return w, nil
|
return w, nil
|
||||||
}
|
}
|
||||||
@ -307,14 +316,38 @@ func (h *EtcdHelper) WatchList(key string, resourceVersion uint64, filter Filter
|
|||||||
// Watch begins watching the specified key. Events are decoded into
|
// Watch begins watching the specified key. Events are decoded into
|
||||||
// API objects and sent down the returned watch.Interface.
|
// API objects and sent down the returned watch.Interface.
|
||||||
func (h *EtcdHelper) Watch(key string, resourceVersion uint64) (watch.Interface, error) {
|
func (h *EtcdHelper) Watch(key string, resourceVersion uint64) (watch.Interface, error) {
|
||||||
w := newEtcdWatcher(false, nil, h.Codec)
|
return h.WatchAndTransform(key, resourceVersion, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WatchAndTransform begins watching the specified key. Events are decoded into
|
||||||
|
// API objects and sent down the returned watch.Interface. If the transform
|
||||||
|
// function is provided, the value decoded from etcd will be passed to the function
|
||||||
|
// prior to being returned.
|
||||||
|
//
|
||||||
|
// The transform function can be used to populate data not available to etcd, or to
|
||||||
|
// change or wrap the serialized etcd object.
|
||||||
|
//
|
||||||
|
// startTime := time.Now()
|
||||||
|
// helper.WatchAndTransform(key, version, func(input interface{}) (interface{}, error) {
|
||||||
|
// value := input.(TimeAwareValue)
|
||||||
|
// value.Since = startTime
|
||||||
|
// return value, nil
|
||||||
|
// })
|
||||||
|
//
|
||||||
|
func (h *EtcdHelper) WatchAndTransform(key string, resourceVersion uint64, transform TransformFunc) (watch.Interface, error) {
|
||||||
|
w := newEtcdWatcher(false, nil, h.Codec, h.ResourceVersioner, transform)
|
||||||
go w.etcdWatch(h.Client, key, resourceVersion)
|
go w.etcdWatch(h.Client, key, resourceVersion)
|
||||||
return w, nil
|
return w, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TransformFunc attempts to convert an object to another object for use with a watcher
|
||||||
|
type TransformFunc func(interface{}) (interface{}, error)
|
||||||
|
|
||||||
// etcdWatcher converts a native etcd watch to a watch.Interface.
|
// etcdWatcher converts a native etcd watch to a watch.Interface.
|
||||||
type etcdWatcher struct {
|
type etcdWatcher struct {
|
||||||
encoding Codec
|
encoding Codec
|
||||||
|
versioner ResourceVersioner
|
||||||
|
transform TransformFunc
|
||||||
|
|
||||||
list bool // If we're doing a recursive watch, should be true.
|
list bool // If we're doing a recursive watch, should be true.
|
||||||
filter FilterFunc
|
filter FilterFunc
|
||||||
@ -332,10 +365,13 @@ type etcdWatcher struct {
|
|||||||
emit func(watch.Event)
|
emit func(watch.Event)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns a new etcdWatcher; if list is true, watch sub-nodes.
|
// newEtcdWatcher returns a new etcdWatcher; if list is true, watch sub-nodes. If you provide a transform
|
||||||
func newEtcdWatcher(list bool, filter FilterFunc, encoding Codec) *etcdWatcher {
|
// and a versioner, the versioner must be able to handle the objects that transform creates.
|
||||||
|
func newEtcdWatcher(list bool, filter FilterFunc, encoding Codec, versioner ResourceVersioner, transform TransformFunc) *etcdWatcher {
|
||||||
w := &etcdWatcher{
|
w := &etcdWatcher{
|
||||||
encoding: encoding,
|
encoding: encoding,
|
||||||
|
versioner: versioner,
|
||||||
|
transform: transform,
|
||||||
list: list,
|
list: list,
|
||||||
filter: filter,
|
filter: filter,
|
||||||
etcdIncoming: make(chan *etcd.Response),
|
etcdIncoming: make(chan *etcd.Response),
|
||||||
@ -354,13 +390,55 @@ func newEtcdWatcher(list bool, filter FilterFunc, encoding Codec) *etcdWatcher {
|
|||||||
func (w *etcdWatcher) etcdWatch(client EtcdGetSet, key string, resourceVersion uint64) {
|
func (w *etcdWatcher) etcdWatch(client EtcdGetSet, key string, resourceVersion uint64) {
|
||||||
defer util.HandleCrash()
|
defer util.HandleCrash()
|
||||||
defer close(w.etcdCallEnded)
|
defer close(w.etcdCallEnded)
|
||||||
|
if resourceVersion == 0 {
|
||||||
|
latest, ok := etcdGetInitialWatchState(client, key, w.list, w.etcdIncoming)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resourceVersion = latest
|
||||||
|
}
|
||||||
_, err := client.Watch(key, resourceVersion, w.list, w.etcdIncoming, w.etcdStop)
|
_, err := client.Watch(key, resourceVersion, w.list, w.etcdIncoming, w.etcdStop)
|
||||||
if err != etcd.ErrWatchStoppedByUser {
|
if err != etcd.ErrWatchStoppedByUser {
|
||||||
glog.Errorf("etcd.Watch stopped unexpectedly: %v (%#v)", err, err)
|
glog.Errorf("etcd.Watch stopped unexpectedly: %v (%#v)", err, key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pull stuff from etcd, convert, and push out the outgoing channel. Meant to be
|
// etcdGetInitialWatchState turns an etcd Get request into a watch equivalent
|
||||||
|
func etcdGetInitialWatchState(client EtcdGetSet, key string, recursive bool, incoming chan<- *etcd.Response) (resourceVersion uint64, success bool) {
|
||||||
|
success = true
|
||||||
|
|
||||||
|
resp, err := client.Get(key, false, recursive)
|
||||||
|
if err != nil {
|
||||||
|
if !IsEtcdNotFound(err) {
|
||||||
|
glog.Errorf("watch was unable to retrieve the current index for the provided key: %v (%#v)", err, key)
|
||||||
|
success = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if index, ok := etcdErrorIndex(err); ok {
|
||||||
|
resourceVersion = index
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resourceVersion = resp.EtcdIndex
|
||||||
|
convertRecursiveResponse(resp.Node, resp, incoming)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// convertRecursiveResponse turns a recursive get response from etcd into individual response objects
|
||||||
|
// by copying the original response. This emulates the behavior of a recursive watch.
|
||||||
|
func convertRecursiveResponse(node *etcd.Node, response *etcd.Response, incoming chan<- *etcd.Response) {
|
||||||
|
if node.Dir {
|
||||||
|
for i := range node.Nodes {
|
||||||
|
convertRecursiveResponse(node.Nodes[i], response, incoming)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
copied := *response
|
||||||
|
copied.Node = node
|
||||||
|
incoming <- &copied
|
||||||
|
}
|
||||||
|
|
||||||
|
// translate pulls stuff from etcd, convert, and push out the outgoing channel. Meant to be
|
||||||
// called as a goroutine.
|
// called as a goroutine.
|
||||||
func (w *etcdWatcher) translate() {
|
func (w *etcdWatcher) translate() {
|
||||||
defer close(w.outgoing)
|
defer close(w.outgoing)
|
||||||
@ -385,6 +463,7 @@ func (w *etcdWatcher) translate() {
|
|||||||
func (w *etcdWatcher) sendResult(res *etcd.Response) {
|
func (w *etcdWatcher) sendResult(res *etcd.Response) {
|
||||||
var action watch.EventType
|
var action watch.EventType
|
||||||
var data []byte
|
var data []byte
|
||||||
|
var index uint64
|
||||||
switch res.Action {
|
switch res.Action {
|
||||||
case "create":
|
case "create":
|
||||||
if res.Node == nil {
|
if res.Node == nil {
|
||||||
@ -392,13 +471,15 @@ func (w *etcdWatcher) sendResult(res *etcd.Response) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
data = []byte(res.Node.Value)
|
data = []byte(res.Node.Value)
|
||||||
|
index = res.Node.ModifiedIndex
|
||||||
action = watch.Added
|
action = watch.Added
|
||||||
case "set":
|
case "set", "compareAndSwap", "get":
|
||||||
if res.Node == nil {
|
if res.Node == nil {
|
||||||
glog.Errorf("unexpected nil node: %#v", res)
|
glog.Errorf("unexpected nil node: %#v", res)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
data = []byte(res.Node.Value)
|
data = []byte(res.Node.Value)
|
||||||
|
index = res.Node.ModifiedIndex
|
||||||
action = watch.Modified
|
action = watch.Modified
|
||||||
case "delete":
|
case "delete":
|
||||||
if res.PrevNode == nil {
|
if res.PrevNode == nil {
|
||||||
@ -406,6 +487,7 @@ func (w *etcdWatcher) sendResult(res *etcd.Response) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
data = []byte(res.PrevNode.Value)
|
data = []byte(res.PrevNode.Value)
|
||||||
|
index = res.PrevNode.ModifiedIndex
|
||||||
action = watch.Deleted
|
action = watch.Deleted
|
||||||
default:
|
default:
|
||||||
glog.Errorf("unknown action: %v", res.Action)
|
glog.Errorf("unknown action: %v", res.Action)
|
||||||
@ -419,6 +501,25 @@ func (w *etcdWatcher) sendResult(res *etcd.Response) {
|
|||||||
w.Stop()
|
w.Stop()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ensure resource version is set on the object we load from etcd
|
||||||
|
if w.versioner != nil {
|
||||||
|
if err := w.versioner.SetResourceVersion(obj, index); err != nil {
|
||||||
|
glog.Errorf("failure to version api object (%d) %#v: %v", index, obj, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// perform any necessary transformation
|
||||||
|
if w.transform != nil {
|
||||||
|
obj, err = w.transform(obj)
|
||||||
|
if err != nil {
|
||||||
|
glog.Errorf("failure to transform api object %#v: %v", obj, err)
|
||||||
|
// TODO: expose an error through watch.Interface?
|
||||||
|
w.Stop()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
w.emit(watch.Event{
|
w.emit(watch.Event{
|
||||||
Type: action,
|
Type: action,
|
||||||
Object: obj,
|
Object: obj,
|
||||||
|
@ -21,6 +21,7 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/conversion"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/conversion"
|
||||||
@ -329,7 +330,7 @@ func TestWatchInterpretation_ListCreate(t *testing.T) {
|
|||||||
w := newEtcdWatcher(true, func(interface{}) bool {
|
w := newEtcdWatcher(true, func(interface{}) bool {
|
||||||
t.Errorf("unexpected filter call")
|
t.Errorf("unexpected filter call")
|
||||||
return true
|
return true
|
||||||
}, codec)
|
}, codec, versioner, nil)
|
||||||
pod := &api.Pod{JSONBase: api.JSONBase{ID: "foo"}}
|
pod := &api.Pod{JSONBase: api.JSONBase{ID: "foo"}}
|
||||||
podBytes, _ := codec.Encode(pod)
|
podBytes, _ := codec.Encode(pod)
|
||||||
|
|
||||||
@ -353,7 +354,7 @@ func TestWatchInterpretation_ListAdd(t *testing.T) {
|
|||||||
w := newEtcdWatcher(true, func(interface{}) bool {
|
w := newEtcdWatcher(true, func(interface{}) bool {
|
||||||
t.Errorf("unexpected filter call")
|
t.Errorf("unexpected filter call")
|
||||||
return true
|
return true
|
||||||
}, codec)
|
}, codec, versioner, nil)
|
||||||
pod := &api.Pod{JSONBase: api.JSONBase{ID: "foo"}}
|
pod := &api.Pod{JSONBase: api.JSONBase{ID: "foo"}}
|
||||||
podBytes, _ := codec.Encode(pod)
|
podBytes, _ := codec.Encode(pod)
|
||||||
|
|
||||||
@ -377,7 +378,7 @@ func TestWatchInterpretation_Delete(t *testing.T) {
|
|||||||
w := newEtcdWatcher(true, func(interface{}) bool {
|
w := newEtcdWatcher(true, func(interface{}) bool {
|
||||||
t.Errorf("unexpected filter call")
|
t.Errorf("unexpected filter call")
|
||||||
return true
|
return true
|
||||||
}, codec)
|
}, codec, versioner, nil)
|
||||||
pod := &api.Pod{JSONBase: api.JSONBase{ID: "foo"}}
|
pod := &api.Pod{JSONBase: api.JSONBase{ID: "foo"}}
|
||||||
podBytes, _ := codec.Encode(pod)
|
podBytes, _ := codec.Encode(pod)
|
||||||
|
|
||||||
@ -401,7 +402,7 @@ func TestWatchInterpretation_ResponseNotSet(t *testing.T) {
|
|||||||
w := newEtcdWatcher(false, func(interface{}) bool {
|
w := newEtcdWatcher(false, func(interface{}) bool {
|
||||||
t.Errorf("unexpected filter call")
|
t.Errorf("unexpected filter call")
|
||||||
return true
|
return true
|
||||||
}, codec)
|
}, codec, versioner, nil)
|
||||||
w.emit = func(e watch.Event) {
|
w.emit = func(e watch.Event) {
|
||||||
t.Errorf("Unexpected emit: %v", e)
|
t.Errorf("Unexpected emit: %v", e)
|
||||||
}
|
}
|
||||||
@ -415,7 +416,7 @@ func TestWatchInterpretation_ResponseNoNode(t *testing.T) {
|
|||||||
w := newEtcdWatcher(false, func(interface{}) bool {
|
w := newEtcdWatcher(false, func(interface{}) bool {
|
||||||
t.Errorf("unexpected filter call")
|
t.Errorf("unexpected filter call")
|
||||||
return true
|
return true
|
||||||
}, codec)
|
}, codec, versioner, nil)
|
||||||
w.emit = func(e watch.Event) {
|
w.emit = func(e watch.Event) {
|
||||||
t.Errorf("Unexpected emit: %v", e)
|
t.Errorf("Unexpected emit: %v", e)
|
||||||
}
|
}
|
||||||
@ -428,7 +429,7 @@ func TestWatchInterpretation_ResponseBadData(t *testing.T) {
|
|||||||
w := newEtcdWatcher(false, func(interface{}) bool {
|
w := newEtcdWatcher(false, func(interface{}) bool {
|
||||||
t.Errorf("unexpected filter call")
|
t.Errorf("unexpected filter call")
|
||||||
return true
|
return true
|
||||||
}, codec)
|
}, codec, versioner, nil)
|
||||||
w.emit = func(e watch.Event) {
|
w.emit = func(e watch.Event) {
|
||||||
t.Errorf("Unexpected emit: %v", e)
|
t.Errorf("Unexpected emit: %v", e)
|
||||||
}
|
}
|
||||||
@ -442,6 +443,7 @@ func TestWatchInterpretation_ResponseBadData(t *testing.T) {
|
|||||||
|
|
||||||
func TestWatch(t *testing.T) {
|
func TestWatch(t *testing.T) {
|
||||||
fakeClient := MakeFakeEtcdClient(t)
|
fakeClient := MakeFakeEtcdClient(t)
|
||||||
|
fakeClient.expectNotFoundGetSet["/some/key"] = struct{}{}
|
||||||
h := EtcdHelper{fakeClient, codec, versioner}
|
h := EtcdHelper{fakeClient, codec, versioner}
|
||||||
|
|
||||||
watching, err := h.Watch("/some/key", 0)
|
watching, err := h.Watch("/some/key", 0)
|
||||||
@ -450,6 +452,10 @@ func TestWatch(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fakeClient.WaitForWatchCompletion()
|
fakeClient.WaitForWatchCompletion()
|
||||||
|
// when no get can be done AND the server doesn't provide an index, the Watch is 0 (from now)
|
||||||
|
if fakeClient.WatchIndex != 0 {
|
||||||
|
t.Errorf("Expected client to be at index %d, got %#v", 0, fakeClient)
|
||||||
|
}
|
||||||
|
|
||||||
// Test normal case
|
// Test normal case
|
||||||
pod := &api.Pod{JSONBase: api.JSONBase{ID: "foo"}}
|
pod := &api.Pod{JSONBase: api.JSONBase{ID: "foo"}}
|
||||||
@ -481,9 +487,165 @@ func TestWatch(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestWatchFromZeroIndex(t *testing.T) {
|
||||||
|
pod := &api.Pod{JSONBase: api.JSONBase{ID: "foo"}}
|
||||||
|
|
||||||
|
fakeClient := MakeFakeEtcdClient(t)
|
||||||
|
fakeClient.Data["/some/key"] = EtcdResponseWithError{
|
||||||
|
R: &etcd.Response{
|
||||||
|
Node: &etcd.Node{
|
||||||
|
Value: api.EncodeOrDie(pod),
|
||||||
|
ModifiedIndex: 1,
|
||||||
|
},
|
||||||
|
Action: "compareAndSwap",
|
||||||
|
EtcdIndex: 2,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
h := EtcdHelper{fakeClient, codec, versioner}
|
||||||
|
|
||||||
|
watching, err := h.Watch("/some/key", 0)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fakeClient.WaitForWatchCompletion()
|
||||||
|
|
||||||
|
// the existing node is detected and the index set
|
||||||
|
event := <-watching.ResultChan()
|
||||||
|
if e, a := watch.Modified, event.Type; e != a {
|
||||||
|
t.Errorf("Expected %v, got %v", e, a)
|
||||||
|
}
|
||||||
|
actualPod, ok := event.Object.(*api.Pod)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("expected a pod, got %#v", event.Object)
|
||||||
|
}
|
||||||
|
if actualPod.ResourceVersion != 1 {
|
||||||
|
t.Errorf("Expected pod with resource version %d, Got %#v", 1, actualPod)
|
||||||
|
}
|
||||||
|
pod.ResourceVersion = 1
|
||||||
|
if e, a := pod, event.Object; !reflect.DeepEqual(e, a) {
|
||||||
|
t.Errorf("Expected %v, got %v", e, a)
|
||||||
|
}
|
||||||
|
watching.Stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWatchListFromZeroIndex(t *testing.T) {
|
||||||
|
pod := &api.Pod{JSONBase: api.JSONBase{ID: "foo"}}
|
||||||
|
|
||||||
|
fakeClient := MakeFakeEtcdClient(t)
|
||||||
|
fakeClient.Data["/some/key"] = EtcdResponseWithError{
|
||||||
|
R: &etcd.Response{
|
||||||
|
Node: &etcd.Node{
|
||||||
|
Dir: true,
|
||||||
|
Nodes: etcd.Nodes{
|
||||||
|
&etcd.Node{
|
||||||
|
Value: api.EncodeOrDie(pod),
|
||||||
|
ModifiedIndex: 1,
|
||||||
|
Nodes: etcd.Nodes{},
|
||||||
|
},
|
||||||
|
&etcd.Node{
|
||||||
|
Value: api.EncodeOrDie(pod),
|
||||||
|
ModifiedIndex: 2,
|
||||||
|
Nodes: etcd.Nodes{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Action: "get",
|
||||||
|
EtcdIndex: 3,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
h := EtcdHelper{fakeClient, codec, versioner}
|
||||||
|
|
||||||
|
watching, err := h.WatchList("/some/key", 0, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// the existing node is detected and the index set
|
||||||
|
event := <-watching.ResultChan()
|
||||||
|
for i := 0; i < 2; i++ {
|
||||||
|
if e, a := watch.Modified, event.Type; e != a {
|
||||||
|
t.Errorf("Expected %v, got %v", e, a)
|
||||||
|
}
|
||||||
|
actualPod, ok := event.Object.(*api.Pod)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("expected a pod, got %#v", event.Object)
|
||||||
|
}
|
||||||
|
if actualPod.ResourceVersion != 1 {
|
||||||
|
t.Errorf("Expected pod with resource version %d, Got %#v", 1, actualPod)
|
||||||
|
}
|
||||||
|
pod.ResourceVersion = 1
|
||||||
|
if e, a := pod, event.Object; !reflect.DeepEqual(e, a) {
|
||||||
|
t.Errorf("Expected %v, got %v", e, a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fakeClient.WaitForWatchCompletion()
|
||||||
|
watching.Stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWatchFromNotFound(t *testing.T) {
|
||||||
|
fakeClient := MakeFakeEtcdClient(t)
|
||||||
|
fakeClient.Data["/some/key"] = EtcdResponseWithError{
|
||||||
|
R: &etcd.Response{
|
||||||
|
Node: nil,
|
||||||
|
},
|
||||||
|
E: &etcd.EtcdError{
|
||||||
|
Index: 2,
|
||||||
|
ErrorCode: 100,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
h := EtcdHelper{fakeClient, codec, versioner}
|
||||||
|
|
||||||
|
watching, err := h.Watch("/some/key", 0)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fakeClient.WaitForWatchCompletion()
|
||||||
|
if fakeClient.WatchIndex != 2 {
|
||||||
|
t.Errorf("Expected client to wait for %d, got %#v", 2, fakeClient)
|
||||||
|
}
|
||||||
|
|
||||||
|
watching.Stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWatchFromOtherError(t *testing.T) {
|
||||||
|
fakeClient := MakeFakeEtcdClient(t)
|
||||||
|
fakeClient.Data["/some/key"] = EtcdResponseWithError{
|
||||||
|
R: &etcd.Response{
|
||||||
|
Node: nil,
|
||||||
|
},
|
||||||
|
E: &etcd.EtcdError{
|
||||||
|
Index: 2,
|
||||||
|
ErrorCode: 101,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
h := EtcdHelper{fakeClient, codec, versioner}
|
||||||
|
|
||||||
|
watching, err := h.Watch("/some/key", 0)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case _, ok := <-watching.ResultChan():
|
||||||
|
if ok {
|
||||||
|
t.Fatalf("expected result channel to be closed")
|
||||||
|
}
|
||||||
|
case <-time.After(1 * time.Millisecond):
|
||||||
|
t.Fatalf("watch should have closed channel: %#v", watching)
|
||||||
|
}
|
||||||
|
|
||||||
|
if fakeClient.WatchResponse != nil || fakeClient.WatchIndex != 0 {
|
||||||
|
t.Fatalf("Watch should not have been invoked: %#v", fakeClient)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestWatchPurposefulShutdown(t *testing.T) {
|
func TestWatchPurposefulShutdown(t *testing.T) {
|
||||||
fakeClient := MakeFakeEtcdClient(t)
|
fakeClient := MakeFakeEtcdClient(t)
|
||||||
h := EtcdHelper{fakeClient, codec, versioner}
|
h := EtcdHelper{fakeClient, codec, versioner}
|
||||||
|
fakeClient.expectNotFoundGetSet["/some/key"] = struct{}{}
|
||||||
|
|
||||||
// Test purposeful shutdown
|
// Test purposeful shutdown
|
||||||
watching, err := h.Watch("/some/key", 0)
|
watching, err := h.Watch("/some/key", 0)
|
||||||
|
@ -51,6 +51,7 @@ type FakeEtcdClient struct {
|
|||||||
// Will become valid after Watch is called; tester may write to it. Tester may
|
// Will become valid after Watch is called; tester may write to it. Tester may
|
||||||
// also read from it to verify that it's closed after injecting an error.
|
// also read from it to verify that it's closed after injecting an error.
|
||||||
WatchResponse chan *etcd.Response
|
WatchResponse chan *etcd.Response
|
||||||
|
WatchIndex uint64
|
||||||
// Write to this to prematurely stop a Watch that is running in a goroutine.
|
// Write to this to prematurely stop a Watch that is running in a goroutine.
|
||||||
WatchInjectError chan<- error
|
WatchInjectError chan<- error
|
||||||
WatchStop chan<- bool
|
WatchStop chan<- bool
|
||||||
@ -229,6 +230,7 @@ func (f *FakeEtcdClient) WaitForWatchCompletion() {
|
|||||||
func (f *FakeEtcdClient) Watch(prefix string, waitIndex uint64, recursive bool, receiver chan *etcd.Response, stop chan bool) (*etcd.Response, error) {
|
func (f *FakeEtcdClient) Watch(prefix string, waitIndex uint64, recursive bool, receiver chan *etcd.Response, stop chan bool) (*etcd.Response, error) {
|
||||||
f.WatchResponse = receiver
|
f.WatchResponse = receiver
|
||||||
f.WatchStop = stop
|
f.WatchStop = stop
|
||||||
|
f.WatchIndex = waitIndex
|
||||||
injectedError := make(chan error)
|
injectedError := make(chan error)
|
||||||
|
|
||||||
defer close(injectedError)
|
defer close(injectedError)
|
||||||
|
Loading…
Reference in New Issue
Block a user