// List implements storage.Interface.List. func (s *store) List(ctx context.Context, key, resourceVersion string, pred storage.SelectionPredicate, listObj runtime.Object) error { listPtr, err := meta.GetItemsPtr(listObj) if err != nil { return err } key = keyWithPrefix(s.pathPrefix, key) // We need to make sure the key ended with "/" so that we only get children "directories". // e.g. if we have key "/a", "/a/b", "/ab", getting keys with prefix "/a" will return all three, // while with prefix "/a/" will return only "/a/b" which is the correct answer. if !strings.HasSuffix(key, "/") { key += "/" } getResp, err := s.client.KV.Get(ctx, key, clientv3.WithPrefix()) if err != nil { return err } elems := make([]*elemForDecode, len(getResp.Kvs)) for i, kv := range getResp.Kvs { elems[i] = &elemForDecode{ data: kv.Value, rev: uint64(kv.ModRevision), } } if err := decodeList(elems, storage.SimpleFilter(pred), listPtr, s.codec, s.versioner); err != nil { return err } // update version with cluster level revision return s.versioner.UpdateList(listObj, uint64(getResp.Header.Revision)) }
// Implements storage.Interface. func (h *etcdHelper) List(ctx context.Context, key string, resourceVersion string, pred storage.SelectionPredicate, listObj runtime.Object) error { if ctx == nil { glog.Errorf("Context is nil") } trace := util.NewTrace("List " + getTypeName(listObj)) defer trace.LogIfLong(400 * time.Millisecond) listPtr, err := meta.GetItemsPtr(listObj) if err != nil { return err } key = h.prefixEtcdKey(key) startTime := time.Now() trace.Step("About to list etcd node") nodes, index, err := h.listEtcdNode(ctx, key) trace.Step("Etcd node listed") metrics.RecordEtcdRequestLatency("list", getTypeName(listPtr), startTime) if err != nil { return err } if err := h.decodeNodeList(nodes, storage.SimpleFilter(pred), listPtr); err != nil { return err } trace.Step("Node list decoded") if err := h.versioner.UpdateList(listObj, index); err != nil { return err } return nil }
func (w *etcdWatcher) decodeObject(node *etcd.Node) (runtime.Object, error) { if obj, found := w.cache.getFromCache(node.ModifiedIndex, storage.SimpleFilter(storage.Everything)); found { return obj, nil } obj, err := runtime.Decode(w.encoding, []byte(node.Value)) if err != nil { return nil, err } // ensure resource version is set on the object we load from etcd if err := w.versioner.UpdateObject(obj, node.ModifiedIndex); err != nil { utilruntime.HandleError(fmt.Errorf("failure to version api object (%d) %#v: %v", node.ModifiedIndex, obj, err)) } // perform any necessary transformation if w.transform != nil { obj, err = w.transform(obj) if err != nil { utilruntime.HandleError(fmt.Errorf("failure to transform api object %#v: %v", obj, err)) return nil, err } } if node.ModifiedIndex != 0 { w.cache.addToCache(node.ModifiedIndex, obj) } return obj, nil }
func (s *store) watch(ctx context.Context, key string, rv string, pred storage.SelectionPredicate, recursive bool) (watch.Interface, error) { rev, err := storage.ParseWatchResourceVersion(rv) if err != nil { return nil, err } key = keyWithPrefix(s.pathPrefix, key) return s.watcher.Watch(ctx, key, int64(rev), recursive, storage.SimpleFilter(pred)) }
func TestWatchInterpretation_ResponseNotSet(t *testing.T) { _, codec := testScheme(t) w := newEtcdWatcher(false, false, nil, storage.SimpleFilter(storage.Everything), codec, versioner, nil, &fakeEtcdCache{}) w.emit = func(e watch.Event) { t.Errorf("Unexpected emit: %v", e) } w.sendResult(&etcd.Response{ Action: "update", }) w.Stop() }
// Implements storage.Interface. func (h *etcdHelper) WatchList(ctx context.Context, key string, resourceVersion string, pred storage.SelectionPredicate) (watch.Interface, error) { if ctx == nil { glog.Errorf("Context is nil") } watchRV, err := storage.ParseWatchResourceVersion(resourceVersion) if err != nil { return nil, err } key = h.prefixEtcdKey(key) w := newEtcdWatcher(true, h.quorum, exceptKey(key), storage.SimpleFilter(pred), h.codec, h.versioner, nil, h) go w.etcdWatch(ctx, h.etcdKeysAPI, key, watchRV) return w, nil }
func TestWatchInterpretation_ResponseNoNode(t *testing.T) { _, codec := testScheme(t) actions := []string{"create", "set", "compareAndSwap", "delete"} for _, action := range actions { w := newEtcdWatcher(false, false, nil, storage.SimpleFilter(storage.Everything), codec, versioner, nil, &fakeEtcdCache{}) w.emit = func(e watch.Event) { t.Errorf("Unexpected emit: %v", e) } w.sendResult(&etcd.Response{ Action: action, }) w.Stop() } }
func (w *watcher) createWatchChan(ctx context.Context, key string, rev int64, recursive bool, pred storage.SelectionPredicate) *watchChan { wc := &watchChan{ watcher: w, key: key, initialRev: rev, recursive: recursive, internalFilter: storage.SimpleFilter(pred), incomingEventChan: make(chan *event, incomingBufSize), resultChan: make(chan watch.Event, outgoingBufSize), errChan: make(chan error, 1), } if pred.Label.Empty() && pred.Field.Empty() { // The filter doesn't filter out any object. wc.internalFilter = nil } wc.ctx, wc.cancel = context.WithCancel(ctx) return wc }
func TestWatchContextCancel(t *testing.T) { ctx, store, cluster := testSetup(t) defer cluster.Terminate(t) canceledCtx, cancel := context.WithCancel(ctx) cancel() // When we watch with a canceled context, we should detect that it's context canceled. // We won't take it as error and also close the watcher. w, err := store.watcher.Watch(canceledCtx, "/abc", 0, false, storage.SimpleFilter(storage.Everything)) if err != nil { t.Fatal(err) } select { case _, ok := <-w.ResultChan(): if ok { t.Error("ResultChan() should be closed") } case <-time.After(wait.ForeverTestTimeout): t.Errorf("timeout after %v", wait.ForeverTestTimeout) } }
func TestWatchErrResultNotBlockAfterCancel(t *testing.T) { origCtx, store, cluster := testSetup(t) defer cluster.Terminate(t) ctx, cancel := context.WithCancel(origCtx) w := store.watcher.createWatchChan(ctx, "/abc", 0, false, storage.SimpleFilter(storage.Everything)) // make resutlChan and errChan blocking to ensure ordering. w.resultChan = make(chan watch.Event) w.errChan = make(chan error) // The event flow goes like: // - first we send an error, it should block on resultChan. // - Then we cancel ctx. The blocking on resultChan should be freed up // and run() goroutine should return. var wg sync.WaitGroup wg.Add(1) go func() { w.run() wg.Done() }() w.errChan <- fmt.Errorf("some error") cancel() wg.Wait() }
// Implements storage.Interface. func (h *etcdHelper) GetToList(ctx context.Context, key string, pred storage.SelectionPredicate, listObj runtime.Object) error { if ctx == nil { glog.Errorf("Context is nil") } trace := util.NewTrace("GetToList " + getTypeName(listObj)) listPtr, err := meta.GetItemsPtr(listObj) if err != nil { return err } key = h.prefixEtcdKey(key) startTime := time.Now() trace.Step("About to read etcd node") opts := &etcd.GetOptions{ Quorum: h.quorum, } response, err := h.etcdKeysAPI.Get(ctx, key, opts) trace.Step("Etcd node read") metrics.RecordEtcdRequestLatency("get", getTypeName(listPtr), startTime) if err != nil { if etcdutil.IsEtcdNotFound(err) { return nil } return toStorageErr(err, key, 0) } nodes := make([]*etcd.Node, 0) nodes = append(nodes, response.Node) if err := h.decodeNodeList(nodes, storage.SimpleFilter(pred), listPtr); err != nil { return err } trace.Step("Object decoded") if err := h.versioner.UpdateList(listObj, response.Index); err != nil { return err } return nil }
// GetToList implements storage.Interface.GetToList. func (s *store) GetToList(ctx context.Context, key string, resourceVersion string, pred storage.SelectionPredicate, listObj runtime.Object) error { listPtr, err := meta.GetItemsPtr(listObj) if err != nil { return err } key = keyWithPrefix(s.pathPrefix, key) getResp, err := s.client.KV.Get(ctx, key, s.getOps...) if err != nil { return err } if len(getResp.Kvs) == 0 { return nil } elems := []*elemForDecode{{ data: getResp.Kvs[0].Value, rev: uint64(getResp.Kvs[0].ModRevision), }} if err := decodeList(elems, storage.SimpleFilter(pred), listPtr, s.codec, s.versioner); err != nil { return err } // update version with cluster level revision return s.versioner.UpdateList(listObj, uint64(getResp.Header.Revision)) }
// It tests that // - first occurrence of objects should notify Add event // - update should trigger Modified event // - update that gets filtered should trigger Deleted event func testWatch(t *testing.T, recursive bool) { ctx, store, cluster := testSetup(t) defer cluster.Terminate(t) podFoo := &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}} podBar := &api.Pod{ObjectMeta: api.ObjectMeta{Name: "bar"}} tests := []struct { key string pred storage.SelectionPredicate watchTests []*testWatchStruct }{{ // create a key key: "/somekey-1", watchTests: []*testWatchStruct{{podFoo, true, watch.Added}}, pred: storage.Everything, }, { // create a key but obj gets filtered. Then update it with unfiltered obj key: "/somekey-3", watchTests: []*testWatchStruct{{podFoo, false, ""}, {podBar, true, watch.Added}}, pred: storage.SelectionPredicate{ Label: labels.Everything(), Field: fields.ParseSelectorOrDie("metadata.name=bar"), GetAttrs: func(obj runtime.Object) (labels.Set, fields.Set, error) { pod := obj.(*api.Pod) return nil, fields.Set{"metadata.name": pod.Name}, nil }, }, }, { // update key: "/somekey-4", watchTests: []*testWatchStruct{{podFoo, true, watch.Added}, {podBar, true, watch.Modified}}, pred: storage.Everything, }, { // delete because of being filtered key: "/somekey-5", watchTests: []*testWatchStruct{{podFoo, true, watch.Added}, {podBar, true, watch.Deleted}}, pred: storage.SelectionPredicate{ Label: labels.Everything(), Field: fields.ParseSelectorOrDie("metadata.name!=bar"), GetAttrs: func(obj runtime.Object) (labels.Set, fields.Set, error) { pod := obj.(*api.Pod) return nil, fields.Set{"metadata.name": pod.Name}, nil }, }, }} for i, tt := range tests { w, err := store.watch(ctx, tt.key, "0", storage.SimpleFilter(tt.pred), recursive) if err != nil { t.Fatalf("Watch failed: %v", err) } var prevObj *api.Pod for _, watchTest := range tt.watchTests { out := &api.Pod{} key := tt.key if recursive { key = key + "/item" } err := store.GuaranteedUpdate(ctx, key, out, true, nil, storage.SimpleUpdate( func(runtime.Object) (runtime.Object, error) { return watchTest.obj, nil })) if err != nil { t.Fatalf("GuaranteedUpdate failed: %v", err) } if watchTest.expectEvent { expectObj := out if watchTest.watchType == watch.Deleted { expectObj = prevObj expectObj.ResourceVersion = out.ResourceVersion } testCheckResult(t, i, watchTest.watchType, w, expectObj) } prevObj = out } w.Stop() testCheckStop(t, i, w) } }
// WatchList implements storage.Interface.WatchList. func (s *store) WatchList(ctx context.Context, key string, resourceVersion string, pred storage.SelectionPredicate) (watch.Interface, error) { return s.watch(ctx, key, resourceVersion, storage.SimpleFilter(pred), true) }