func (w *watchCache) GetAllEventsSinceThreadUnsafe(resourceVersion uint64) ([]*watchCacheEvent, error) { size := w.endIndex - w.startIndex oldest := w.resourceVersion if size > 0 { oldest = w.cache[w.startIndex%w.capacity].resourceVersion } if resourceVersion == 0 { // resourceVersion = 0 means that we don't require any specific starting point // and we would like to start watching from ~now. // However, to keep backward compatibility, we additionally need to return the // current state and only then start watching from that point. // // TODO: In v2 api, we should stop returning the current state - #13969. allItems := w.store.List() result := make([]*watchCacheEvent, len(allItems)) for i, item := range allItems { elem, ok := item.(*storeElement) if !ok { return nil, fmt.Errorf("not a storeElement: %v", elem) } objLabels, objFields, err := w.getAttrsFunc(elem.Object) if err != nil { return nil, err } result[i] = &watchCacheEvent{ Type: watch.Added, Object: elem.Object, ObjLabels: objLabels, ObjFields: objFields, Key: elem.Key, ResourceVersion: w.resourceVersion, } } return result, nil } if resourceVersion < oldest-1 { return nil, errors.NewGone(fmt.Sprintf("too old resource version: %d (%d)", resourceVersion, oldest-1)) } // Binary search the smallest index at which resourceVersion is greater than the given one. f := func(i int) bool { return w.cache[(w.startIndex+i)%w.capacity].resourceVersion > resourceVersion } first := sort.Search(size, f) result := make([]*watchCacheEvent, size-first) for i := 0; i < size-first; i++ { result[i] = w.cache[(w.startIndex+first+i)%w.capacity].watchCacheEvent } return result, nil }
func TestWatch(t *testing.T) { server, etcdStorage := newEtcdTestStorage(t, testapi.Default.Codec(), etcdtest.PathPrefix()) // Inject one list error to make sure we test the relist case. etcdStorage = &injectListError{errors: 1, Interface: etcdStorage} defer server.Terminate(t) cacher := newTestCacher(etcdStorage, 3) // small capacity to trigger "too old version" error defer cacher.Stop() podFoo := makeTestPod("foo") podBar := makeTestPod("bar") podFooPrime := makeTestPod("foo") podFooPrime.Spec.NodeName = "fakeNode" podFooBis := makeTestPod("foo") podFooBis.Spec.NodeName = "anotherFakeNode" podFooNS2 := makeTestPod("foo") podFooNS2.Namespace += "2" // initialVersion is used to initate the watcher at the beginning of the world, // which is not defined precisely in etcd. initialVersion, err := cacher.LastSyncResourceVersion() if err != nil { t.Fatalf("Unexpected error: %v", err) } startVersion := strconv.Itoa(int(initialVersion)) // Set up Watch for object "podFoo". watcher, err := cacher.Watch(context.TODO(), "pods/ns/foo", startVersion, storage.Everything) if err != nil { t.Fatalf("Unexpected error: %v", err) } defer watcher.Stop() // Create in another namespace first to make sure events from other namespaces don't get delivered updatePod(t, etcdStorage, podFooNS2, nil) fooCreated := updatePod(t, etcdStorage, podFoo, nil) _ = updatePod(t, etcdStorage, podBar, nil) fooUpdated := updatePod(t, etcdStorage, podFooPrime, fooCreated) verifyWatchEvent(t, watcher, watch.Added, podFoo) verifyWatchEvent(t, watcher, watch.Modified, podFooPrime) // Check whether we get too-old error via the watch channel tooOldWatcher, err := cacher.Watch(context.TODO(), "pods/ns/foo", "1", storage.Everything) if err != nil { t.Fatalf("Expected no direct error, got %v", err) } defer tooOldWatcher.Stop() // Ensure we get a "Gone" error expectedGoneError := errors.NewGone("").ErrStatus verifyWatchEvent(t, tooOldWatcher, watch.Error, &expectedGoneError) initialWatcher, err := cacher.Watch(context.TODO(), "pods/ns/foo", fooCreated.ResourceVersion, storage.Everything) if err != nil { t.Fatalf("Unexpected error: %v", err) } defer initialWatcher.Stop() verifyWatchEvent(t, initialWatcher, watch.Modified, podFooPrime) // Now test watch from "now". nowWatcher, err := cacher.Watch(context.TODO(), "pods/ns/foo", "0", storage.Everything) if err != nil { t.Fatalf("Unexpected error: %v", err) } defer nowWatcher.Stop() verifyWatchEvent(t, nowWatcher, watch.Added, podFooPrime) _ = updatePod(t, etcdStorage, podFooBis, fooUpdated) verifyWatchEvent(t, nowWatcher, watch.Modified, podFooBis) }