func TestWatchFromZeroIndex(t *testing.T) { codec := testapi.Default.Codec() pod := &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}} key := etcdtest.AddPrefix("/somekey/foo") server := etcdtesting.NewEtcdTestClientServer(t) defer server.Terminate(t) h := newEtcdHelper(server.Client, codec, etcdtest.PathPrefix()) // set before the watch and verify events err := h.Create(context.TODO(), key, pod, pod, 0) if err != nil { t.Fatalf("Unexpected error: %v", err) } pod.ResourceVersion = "" // check for concatenation on watch event with CAS updateFn := func(input runtime.Object, res storage.ResponseMeta) (runtime.Object, *uint64, error) { pod := input.(*api.Pod) pod.Name = "bar" return pod, nil, nil } err = h.GuaranteedUpdate(context.TODO(), key, &api.Pod{}, false, nil, updateFn) if err != nil { t.Fatalf("Unexpected error: %v", err) } watching, err := h.Watch(context.TODO(), key, "0", storage.Everything) if err != nil { t.Fatalf("Unexpected error: %v", err) } defer watching.Stop() // marked as modified b/c of concatenation event := <-watching.ResultChan() if event.Type != watch.Modified { t.Errorf("Unexpected event %#v", event) } pod.Name = "baz" updateFn = func(input runtime.Object, res storage.ResponseMeta) (runtime.Object, *uint64, error) { pod := input.(*api.Pod) pod.Name = "baz" return pod, nil, nil } err = h.GuaranteedUpdate(context.TODO(), key, &api.Pod{}, false, nil, updateFn) if err != nil { t.Fatalf("Unexpected error: %v", err) } event = <-watching.ResultChan() if event.Type != watch.Modified { t.Errorf("Unexpected event %#v", event) } if e, a := pod, event.Object; !api.Semantic.DeepDerivative(e, a) { t.Errorf("Unexpected error: expected %#v, got %#v", e, a) } }
func TestWatchPurposefulShutdown(t *testing.T) { _, codec := testScheme(t) server := etcdtesting.NewEtcdTestClientServer(t) defer server.Terminate(t) key := "/some/key" h := newEtcdHelper(server.Client, codec, etcdtest.PathPrefix()) // Test purposeful shutdown watching, err := h.Watch(context.TODO(), key, "0", storage.Everything) if err != nil { t.Fatalf("Unexpected error: %v", err) } watching.Stop() rt.Gosched() // There is a race in etcdWatcher so that after calling Stop() one of // two things can happen: // - ResultChan() may be closed (triggered by closing userStop channel) // - an Error "context cancelled" may be emitted (triggered by cancelling request // to etcd and putting that error to etcdError channel) // We need to be prepared for both here. event, open := <-watching.ResultChan() if open && event.Type != watch.Error { t.Errorf("Unexpected event from stopped watcher: %#v", event) } }
func TestWatcherTimeout(t *testing.T) { server, etcdStorage := newEtcdTestStorage(t, testapi.Default.Codec(), etcdtest.PathPrefix()) defer server.Terminate(t) cacher := newTestCacher(etcdStorage) defer cacher.Stop() // 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)) // Create a watcher that will not be reading any result. watcher, err := cacher.WatchList(context.TODO(), "pods/ns", startVersion, storage.Everything) if err != nil { t.Fatalf("Unexpected error: %v", err) } defer watcher.Stop() // Create a second watcher that will be reading result. readingWatcher, err := cacher.WatchList(context.TODO(), "pods/ns", startVersion, storage.Everything) if err != nil { t.Fatalf("Unexpected error: %v", err) } defer readingWatcher.Stop() for i := 1; i <= 22; i++ { pod := makeTestPod(strconv.Itoa(i)) _ = updatePod(t, etcdStorage, pod, nil) verifyWatchEvent(t, readingWatcher, watch.Added, pod) } }
func TestWatchEtcdState(t *testing.T) { codec := testapi.Default.Codec() key := etcdtest.AddPrefix("/somekey/foo") server := etcdtesting.NewEtcdTestClientServer(t) defer server.Terminate(t) h := newEtcdHelper(server.Client, codec, etcdtest.PathPrefix()) watching, err := h.Watch(context.TODO(), key, "0", storage.Everything) if err != nil { t.Fatalf("Unexpected error: %v", err) } defer watching.Stop() endpoint := &api.Endpoints{ ObjectMeta: api.ObjectMeta{Name: "foo"}, Subsets: emptySubsets(), } err = h.Create(context.TODO(), key, endpoint, endpoint, 0) if err != nil { t.Fatalf("Unexpected error: %v", err) } event := <-watching.ResultChan() if event.Type != watch.Added { t.Errorf("Unexpected event %#v", event) } subset := makeSubsets("127.0.0.1", 9000) endpoint.Subsets = subset endpoint.ResourceVersion = "" // CAS the previous value updateFn := func(input runtime.Object, res storage.ResponseMeta) (runtime.Object, *uint64, error) { newObj, err := api.Scheme.DeepCopy(endpoint) if err != nil { t.Errorf("unexpected error: %v", err) return nil, nil, err } return newObj.(*api.Endpoints), nil, nil } err = h.GuaranteedUpdate(context.TODO(), key, &api.Endpoints{}, false, nil, updateFn) if err != nil { t.Fatalf("Unexpected error: %v", err) } event = <-watching.ResultChan() if event.Type != watch.Modified { t.Errorf("Unexpected event %#v", event) } if e, a := endpoint, event.Object; !api.Semantic.DeepDerivative(e, a) { t.Errorf("Unexpected error: expected %#v, got %#v", e, a) } }
// setUp is a convience function for setting up for (most) tests. func setUp(t *testing.T) (*Master, *etcdtesting.EtcdTestServer, Config, *assert.Assertions) { server := etcdtesting.NewEtcdTestClientServer(t) master := &Master{ GenericAPIServer: &genericapiserver.GenericAPIServer{}, } config := Config{ Config: &genericapiserver.Config{}, } storageConfig := storagebackend.Config{ Prefix: etcdtest.PathPrefix(), CAFile: server.CAFile, KeyFile: server.KeyFile, CertFile: server.CertFile, } for _, url := range server.ClientURLs { storageConfig.ServerList = append(storageConfig.ServerList, url.String()) } resourceEncoding := genericapiserver.NewDefaultResourceEncodingConfig() resourceEncoding.SetVersionEncoding(api.GroupName, *testapi.Default.GroupVersion(), unversioned.GroupVersion{Group: api.GroupName, Version: runtime.APIVersionInternal}) resourceEncoding.SetVersionEncoding(autoscaling.GroupName, *testapi.Autoscaling.GroupVersion(), unversioned.GroupVersion{Group: autoscaling.GroupName, Version: runtime.APIVersionInternal}) resourceEncoding.SetVersionEncoding(batch.GroupName, *testapi.Batch.GroupVersion(), unversioned.GroupVersion{Group: batch.GroupName, Version: runtime.APIVersionInternal}) resourceEncoding.SetVersionEncoding(apps.GroupName, *testapi.Apps.GroupVersion(), unversioned.GroupVersion{Group: apps.GroupName, Version: runtime.APIVersionInternal}) resourceEncoding.SetVersionEncoding(extensions.GroupName, *testapi.Extensions.GroupVersion(), unversioned.GroupVersion{Group: extensions.GroupName, Version: runtime.APIVersionInternal}) resourceEncoding.SetVersionEncoding(rbac.GroupName, *testapi.Rbac.GroupVersion(), unversioned.GroupVersion{Group: rbac.GroupName, Version: runtime.APIVersionInternal}) resourceEncoding.SetVersionEncoding(certificates.GroupName, *testapi.Certificates.GroupVersion(), unversioned.GroupVersion{Group: certificates.GroupName, Version: runtime.APIVersionInternal}) storageFactory := genericapiserver.NewDefaultStorageFactory(storageConfig, testapi.StorageMediaType(), api.Codecs, resourceEncoding, DefaultAPIResourceConfigSource()) config.StorageFactory = storageFactory config.APIResourceConfigSource = DefaultAPIResourceConfigSource() config.PublicAddress = net.ParseIP("192.168.10.4") config.Serializer = api.Codecs config.KubeletClient = client.FakeKubeletClient{} config.APIPrefix = "/api" config.APIGroupPrefix = "/apis" config.APIResourceConfigSource = DefaultAPIResourceConfigSource() config.ProxyDialer = func(network, addr string) (net.Conn, error) { return nil, nil } config.ProxyTLSClientConfig = &tls.Config{} // TODO: this is kind of hacky. The trouble is that the sync loop // runs in a go-routine and there is no way to validate in the test // that the sync routine has actually run. The right answer here // is probably to add some sort of callback that we can register // to validate that it's actually been run, but for now we don't // run the sync routine and register types manually. config.disableThirdPartyControllerForTesting = true master.nodeRegistry = registrytest.NewNodeRegistry([]string{"node1", "node2"}, api.NodeResources{}) return master, server, config, assert.New(t) }
func TestFiltering(t *testing.T) { server, etcdStorage := newEtcdTestStorage(t, testapi.Default.Codec(), etcdtest.PathPrefix()) defer server.Terminate(t) cacher := newTestCacher(etcdStorage) defer cacher.Stop() // Ensure that the cacher is initialized, before creating any pods, // so that we are sure that all events will be present in cacher. syncWatcher, err := cacher.Watch(context.TODO(), "pods/ns/foo", "0", storage.Everything) if err != nil { t.Fatalf("Unexpected error: %v", err) } syncWatcher.Stop() podFoo := makeTestPod("foo") podFoo.Labels = map[string]string{"filter": "foo"} podFooFiltered := makeTestPod("foo") podFooPrime := makeTestPod("foo") podFooPrime.Labels = map[string]string{"filter": "foo"} podFooPrime.Spec.NodeName = "fakeNode" fooCreated := updatePod(t, etcdStorage, podFoo, nil) fooFiltered := updatePod(t, etcdStorage, podFooFiltered, fooCreated) fooUnfiltered := updatePod(t, etcdStorage, podFoo, fooFiltered) _ = updatePod(t, etcdStorage, podFooPrime, fooUnfiltered) deleted := api.Pod{} if err := etcdStorage.Delete(context.TODO(), etcdtest.AddPrefix("pods/ns/foo"), &deleted, nil); err != nil { t.Errorf("Unexpected error: %v", err) } // Set up Watch for object "podFoo" with label filter set. selector := labels.SelectorFromSet(labels.Set{"filter": "foo"}) filter := func(obj runtime.Object) bool { metadata, err := meta.Accessor(obj) if err != nil { t.Errorf("Unexpected error: %v", err) return false } return selector.Matches(labels.Set(metadata.GetLabels())) } watcher, err := cacher.Watch(context.TODO(), "pods/ns/foo", fooCreated.ResourceVersion, filter) if err != nil { t.Fatalf("Unexpected error: %v", err) } defer watcher.Stop() verifyWatchEvent(t, watcher, watch.Deleted, podFooFiltered) verifyWatchEvent(t, watcher, watch.Added, podFoo) verifyWatchEvent(t, watcher, watch.Modified, podFooPrime) verifyWatchEvent(t, watcher, watch.Deleted, podFooPrime) }
func TestStartingResourceVersion(t *testing.T) { server, etcdStorage := newEtcdTestStorage(t, testapi.Default.Codec(), etcdtest.PathPrefix()) defer server.Terminate(t) cacher := newTestCacher(etcdStorage) defer cacher.Stop() // add 1 object podFoo := makeTestPod("foo") fooCreated := updatePod(t, etcdStorage, podFoo, nil) // Set up Watch starting at fooCreated.ResourceVersion + 10 rv, err := storage.ParseWatchResourceVersion(fooCreated.ResourceVersion) if err != nil { t.Fatalf("Unexpected error: %v", err) } rv += 10 startVersion := strconv.Itoa(int(rv)) watcher, err := cacher.Watch(context.TODO(), "pods/ns/foo", startVersion, storage.Everything) if err != nil { t.Fatalf("Unexpected error: %v", err) } defer watcher.Stop() lastFoo := fooCreated for i := 0; i < 11; i++ { podFooForUpdate := makeTestPod("foo") podFooForUpdate.Labels = map[string]string{"foo": strconv.Itoa(i)} lastFoo = updatePod(t, etcdStorage, podFooForUpdate, lastFoo) } select { case e := <-watcher.ResultChan(): pod := e.Object.(*api.Pod) podRV, err := storage.ParseWatchResourceVersion(pod.ResourceVersion) if err != nil { t.Fatalf("unexpected error: %v", err) } // event should have at least rv + 1, since we're starting the watch at rv if podRV <= rv { t.Errorf("expected event with resourceVersion of at least %d, got %d", rv+1, podRV) } case <-time.After(wait.ForeverTestTimeout): t.Errorf("timed out waiting for event") } }
func TestWatch(t *testing.T) { codec := testapi.Default.Codec() server := etcdtesting.NewEtcdTestClientServer(t) defer server.Terminate(t) key := "/some/key" h := newEtcdHelper(server.Client, codec, etcdtest.PathPrefix()) watching, err := h.Watch(context.TODO(), key, "0", storage.Everything) if err != nil { t.Fatalf("Unexpected error: %v", err) } // watching is explicitly closed below. // Test normal case pod := &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}} returnObj := &api.Pod{} err = h.Create(context.TODO(), key, pod, returnObj, 0) if err != nil { t.Fatalf("Unexpected error: %v", err) } event := <-watching.ResultChan() if e, a := watch.Added, event.Type; e != a { t.Errorf("Expected %v, got %v", e, a) } if e, a := pod, event.Object; !api.Semantic.DeepDerivative(e, a) { t.Errorf("Expected %v, got %v", e, a) } watching.Stop() // There is a race in etcdWatcher so that after calling Stop() one of // two things can happen: // - ResultChan() may be closed (triggered by closing userStop channel) // - an Error "context cancelled" may be emitted (triggered by cancelling request // to etcd and putting that error to etcdError channel) // We need to be prepared for both here. event, open := <-watching.ResultChan() if open && event.Type != watch.Error { t.Errorf("Unexpected event from stopped watcher: %#v", event) } }
func NewTestGenericStoreRegistry(t *testing.T) (*etcdtesting.EtcdTestServer, *Store) { podPrefix := "/pods" server := etcdtesting.NewEtcdTestClientServer(t) s := etcdstorage.NewEtcdStorage(server.Client, testapi.Default.StorageCodec(), etcdtest.PathPrefix(), false, etcdtest.DeserializationCacheSize) strategy := &testRESTStrategy{api.Scheme, api.SimpleNameGenerator, true, false, true} return server, &Store{ NewFunc: func() runtime.Object { return &api.Pod{} }, NewListFunc: func() runtime.Object { return &api.PodList{} }, QualifiedResource: api.Resource("pods"), CreateStrategy: strategy, UpdateStrategy: strategy, DeleteStrategy: strategy, KeyRootFunc: func(ctx api.Context) string { return podPrefix }, KeyFunc: func(ctx api.Context, id string) (string, error) { if _, ok := api.NamespaceFrom(ctx); !ok { return "", fmt.Errorf("namespace is required") } return path.Join(podPrefix, id), nil }, ObjectNameFunc: func(obj runtime.Object) (string, error) { return obj.(*api.Pod).Name, nil }, PredicateFunc: func(label labels.Selector, field fields.Selector) generic.Matcher { return &generic.SelectionPredicate{ Label: label, Field: field, GetAttrs: func(obj runtime.Object) (labels.Set, fields.Set, error) { pod, ok := obj.(*api.Pod) if !ok { return nil, nil, fmt.Errorf("not a pod") } return labels.Set(pod.ObjectMeta.Labels), generic.ObjectMetaFieldsSet(pod.ObjectMeta, true), nil }, } }, Storage: s, } }
func NewEtcdStorage(t *testing.T, group string) (storage.Interface, *etcdtesting.EtcdTestServer) { server := etcdtesting.NewEtcdTestClientServer(t) storage := etcdstorage.NewEtcdStorage(server.Client, testapi.Groups[group].StorageCodec(), etcdtest.PathPrefix(), false, etcdtest.DeserializationCacheSize) return storage, server }
func TestList(t *testing.T) { server, etcdStorage := newEtcdTestStorage(t, testapi.Default.Codec(), etcdtest.PathPrefix()) defer server.Terminate(t) cacher := newTestCacher(etcdStorage) defer cacher.Stop() podFoo := makeTestPod("foo") podBar := makeTestPod("bar") podBaz := makeTestPod("baz") podFooPrime := makeTestPod("foo") podFooPrime.Spec.NodeName = "fakeNode" fooCreated := updatePod(t, etcdStorage, podFoo, nil) _ = updatePod(t, etcdStorage, podBar, nil) _ = updatePod(t, etcdStorage, podBaz, nil) _ = updatePod(t, etcdStorage, podFooPrime, fooCreated) deleted := api.Pod{} if err := etcdStorage.Delete(context.TODO(), etcdtest.AddPrefix("pods/ns/bar"), &deleted, nil); err != nil { t.Errorf("Unexpected error: %v", err) } // We first List directly from etcd by passing empty resourceVersion, // to get the current etcd resourceVersion. rvResult := &api.PodList{} if err := cacher.List(context.TODO(), "pods/ns", "", storage.Everything, rvResult); err != nil { t.Errorf("Unexpected error: %v", err) } deletedPodRV := rvResult.ListMeta.ResourceVersion result := &api.PodList{} // We pass the current etcd ResourceVersion received from the above List() operation, // since there is not easy way to get ResourceVersion of barPod deletion operation. if err := cacher.List(context.TODO(), "pods/ns", deletedPodRV, storage.Everything, result); err != nil { t.Errorf("Unexpected error: %v", err) } if result.ListMeta.ResourceVersion != deletedPodRV { t.Errorf("Incorrect resource version: %v", result.ListMeta.ResourceVersion) } if len(result.Items) != 2 { t.Errorf("Unexpected list result: %d", len(result.Items)) } keys := sets.String{} for _, item := range result.Items { keys.Insert(item.Name) } if !keys.HasAll("foo", "baz") { t.Errorf("Unexpected list result: %#v", result) } for _, item := range result.Items { // unset fields that are set by the infrastructure item.ResourceVersion = "" item.CreationTimestamp = unversioned.Time{} var expected *api.Pod switch item.Name { case "foo": expected = podFooPrime case "baz": expected = podBaz default: t.Errorf("Unexpected item: %v", item) } if e, a := *expected, item; !reflect.DeepEqual(e, a) { t.Errorf("Expected: %#v, got: %#v", e, a) } } }
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) defer cacher.Stop() podFoo := makeTestPod("foo") podBar := makeTestPod("bar") podFooPrime := makeTestPod("foo") podFooPrime.Spec.NodeName = "fakeNode" podFooBis := makeTestPod("foo") podFooBis.Spec.NodeName = "anotherFakeNode" // 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() 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) }