diff --git a/cmd/libs/go2idl/client-gen/generators/generator_for_type.go b/cmd/libs/go2idl/client-gen/generators/generator_for_type.go index 76e7fe50331..32b1701ffb2 100644 --- a/cmd/libs/go2idl/client-gen/generators/generator_for_type.go +++ b/cmd/libs/go2idl/client-gen/generators/generator_for_type.go @@ -321,8 +321,8 @@ func (c *$.type|privatePlural$) UpdateStatus($.type|private$ *$.type|raw$) (resu var watchTemplate = ` // Watch returns a $.watchInterface|raw$ that watches the requested $.type|privatePlural$. func (c *$.type|privatePlural$) Watch(opts $.ListOptions|raw$) ($.watchInterface|raw$, error) { + opts.Watch = true return c.client.Get(). - Prefix("watch"). $if .namespaced$Namespace(c.ns).$end$ Resource("$.type|allLowercasePlural$"). VersionedParams(&opts, $.apiParameterCodec|raw$). diff --git a/pkg/client/tests/listwatch_test.go b/pkg/client/tests/listwatch_test.go index 18ba412f866..03a4e05c49a 100644 --- a/pkg/client/tests/listwatch_test.go +++ b/pkg/client/tests/listwatch_test.go @@ -122,8 +122,8 @@ func TestListWatchesCanWatch(t *testing.T) { // Node { location: buildLocation( - testapi.Default.ResourcePathWithPrefix("watch", "nodes", metav1.NamespaceAll, ""), - buildQueryValues(url.Values{})), + testapi.Default.ResourcePath("nodes", metav1.NamespaceAll, ""), + buildQueryValues(url.Values{"watch": []string{"true"}})), rv: "", resource: "nodes", namespace: metav1.NamespaceAll, @@ -131,8 +131,8 @@ func TestListWatchesCanWatch(t *testing.T) { }, { location: buildLocation( - testapi.Default.ResourcePathWithPrefix("watch", "nodes", metav1.NamespaceAll, ""), - buildQueryValues(url.Values{"resourceVersion": []string{"42"}})), + testapi.Default.ResourcePath("nodes", metav1.NamespaceAll, ""), + buildQueryValues(url.Values{"resourceVersion": []string{"42"}, "watch": []string{"true"}})), rv: "42", resource: "nodes", namespace: metav1.NamespaceAll, @@ -141,8 +141,8 @@ func TestListWatchesCanWatch(t *testing.T) { // pod with "assigned" field selector. { location: buildLocation( - testapi.Default.ResourcePathWithPrefix("watch", "pods", metav1.NamespaceAll, ""), - buildQueryValues(url.Values{fieldSelectorQueryParamName: []string{"spec.host="}, "resourceVersion": []string{"0"}})), + testapi.Default.ResourcePath("pods", metav1.NamespaceAll, ""), + buildQueryValues(url.Values{fieldSelectorQueryParamName: []string{"spec.host="}, "resourceVersion": []string{"0"}, "watch": []string{"true"}})), rv: "0", resource: "pods", namespace: metav1.NamespaceAll, @@ -151,8 +151,8 @@ func TestListWatchesCanWatch(t *testing.T) { // pod with namespace foo and assigned field selector { location: buildLocation( - testapi.Default.ResourcePathWithPrefix("watch", "pods", "foo", ""), - buildQueryValues(url.Values{fieldSelectorQueryParamName: []string{"spec.host="}, "resourceVersion": []string{"0"}})), + testapi.Default.ResourcePath("pods", "foo", ""), + buildQueryValues(url.Values{fieldSelectorQueryParamName: []string{"spec.host="}, "resourceVersion": []string{"0"}, "watch": []string{"true"}})), rv: "0", resource: "pods", namespace: "foo", diff --git a/pkg/controller/garbagecollector/garbagecollector_test.go b/pkg/controller/garbagecollector/garbagecollector_test.go index 7f741dddc27..ff658feda4b 100644 --- a/pkg/controller/garbagecollector/garbagecollector_test.go +++ b/pkg/controller/garbagecollector/garbagecollector_test.go @@ -354,7 +354,7 @@ func TestGCListWatcher(t *testing.T) { if e, a := 2, len(testHandler.actions); e != a { t.Errorf("expect %d requests, got %d", e, a) } - if e, a := "resourceVersion=1", testHandler.actions[0].query; e != a { + if e, a := "resourceVersion=1&watch=true", testHandler.actions[0].query; e != a { t.Errorf("expect %s, got %s", e, a) } if e, a := "resourceVersion=1", testHandler.actions[1].query; e != a { diff --git a/pkg/kubectl/cmd/get_test.go b/pkg/kubectl/cmd/get_test.go index 39200512d1f..530baa8972f 100644 --- a/pkg/kubectl/cmd/get_test.go +++ b/pkg/kubectl/cmd/get_test.go @@ -720,9 +720,11 @@ func TestWatchSelector(t *testing.T) { } switch req.URL.Path { case "/namespaces/test/pods": - return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, podList)}, nil - case "/watch/namespaces/test/pods": - return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: watchBody(codec, events[2:])}, nil + if req.URL.Query().Get("watch") == "true" { + return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: watchBody(codec, events[2:])}, nil + } else { + return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, podList)}, nil + } default: t.Fatalf("unexpected request: %#v\n%#v", req.URL, req) return nil, nil @@ -760,8 +762,12 @@ func TestWatchResource(t *testing.T) { switch req.URL.Path { case "/namespaces/test/pods/foo": return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &pods[1])}, nil - case "/watch/namespaces/test/pods/foo": - return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: watchBody(codec, events[1:])}, nil + case "/namespaces/test/pods": + if req.URL.Query().Get("watch") == "true" && req.URL.Query().Get("fieldSelector") == "metadata.name=foo" { + return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: watchBody(codec, events[1:])}, nil + } + t.Fatalf("unexpected request: %#v\n%#v", req.URL, req) + return nil, nil default: t.Fatalf("unexpected request: %#v\n%#v", req.URL, req) return nil, nil @@ -798,8 +804,12 @@ func TestWatchResourceIdentifiedByFile(t *testing.T) { switch req.URL.Path { case "/namespaces/test/replicationcontrollers/cassandra": return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &pods[1])}, nil - case "/watch/namespaces/test/replicationcontrollers/cassandra": - return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: watchBody(codec, events[1:])}, nil + case "/namespaces/test/replicationcontrollers": + if req.URL.Query().Get("watch") == "true" && req.URL.Query().Get("fieldSelector") == "metadata.name=cassandra" { + return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: watchBody(codec, events[1:])}, nil + } + t.Fatalf("unexpected request: %#v\n%#v", req.URL, req) + return nil, nil default: t.Fatalf("unexpected request: %#v\n%#v", req.URL, req) return nil, nil @@ -837,8 +847,12 @@ func TestWatchOnlyResource(t *testing.T) { switch req.URL.Path { case "/namespaces/test/pods/foo": return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &pods[1])}, nil - case "/watch/namespaces/test/pods/foo": - return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: watchBody(codec, events[1:])}, nil + case "/namespaces/test/pods": + if req.URL.Query().Get("watch") == "true" && req.URL.Query().Get("fieldSelector") == "metadata.name=foo" { + return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: watchBody(codec, events[1:])}, nil + } + t.Fatalf("unexpected request: %#v\n%#v", req.URL, req) + return nil, nil default: t.Fatalf("unexpected request: %#v\n%#v", req.URL, req) return nil, nil @@ -880,9 +894,11 @@ func TestWatchOnlyList(t *testing.T) { Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch req.URL.Path { case "/namespaces/test/pods": - return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, podList)}, nil - case "/watch/namespaces/test/pods": - return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: watchBody(codec, events[2:])}, nil + if req.URL.Query().Get("watch") == "true" { + return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: watchBody(codec, events[2:])}, nil + } else { + return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, podList)}, nil + } default: t.Fatalf("unexpected request: %#v\n%#v", req.URL, req) return nil, nil diff --git a/pkg/kubectl/resource/BUILD b/pkg/kubectl/resource/BUILD index 375d3146efc..1bdf515f1bf 100644 --- a/pkg/kubectl/resource/BUILD +++ b/pkg/kubectl/resource/BUILD @@ -31,6 +31,7 @@ go_library( "//vendor:k8s.io/apimachinery/pkg/api/errors", "//vendor:k8s.io/apimachinery/pkg/api/meta", "//vendor:k8s.io/apimachinery/pkg/apis/meta/v1", + "//vendor:k8s.io/apimachinery/pkg/fields", "//vendor:k8s.io/apimachinery/pkg/labels", "//vendor:k8s.io/apimachinery/pkg/runtime", "//vendor:k8s.io/apimachinery/pkg/runtime/schema", diff --git a/pkg/kubectl/resource/builder_test.go b/pkg/kubectl/resource/builder_test.go index 33cfb68c221..99b832deb33 100644 --- a/pkg/kubectl/resource/builder_test.go +++ b/pkg/kubectl/resource/builder_test.go @@ -1069,7 +1069,7 @@ func TestListObjectWithDifferentVersions(t *testing.T) { func TestWatch(t *testing.T) { _, svc := testData() w, err := NewBuilder(testapi.Default.RESTMapper(), api.Scheme, fakeClientWith("", t, map[string]string{ - "/watch/namespaces/test/services/redis-master?resourceVersion=12": watchBody(watch.Event{ + "/namespaces/test/services?fieldSelector=metadata.name%3Dredis-master&resourceVersion=12&watch=true": watchBody(watch.Event{ Type: watch.Added, Object: &svc.Items[0], }), diff --git a/pkg/kubectl/resource/helper.go b/pkg/kubectl/resource/helper.go index a7aa39eb1bf..5bb68018387 100644 --- a/pkg/kubectl/resource/helper.go +++ b/pkg/kubectl/resource/helper.go @@ -20,6 +20,7 @@ import ( "strconv" "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" @@ -75,21 +76,21 @@ func (m *Helper) List(namespace, apiVersion string, selector labels.Selector, ex func (m *Helper) Watch(namespace, resourceVersion, apiVersion string, labelSelector labels.Selector) (watch.Interface, error) { return m.RESTClient.Get(). - Prefix("watch"). NamespaceIfScoped(namespace, m.NamespaceScoped). Resource(m.Resource). Param("resourceVersion", resourceVersion). + Param("watch", "true"). LabelsSelectorParam(labelSelector). Watch() } func (m *Helper) WatchSingle(namespace, name, resourceVersion string) (watch.Interface, error) { return m.RESTClient.Get(). - Prefix("watch"). NamespaceIfScoped(namespace, m.NamespaceScoped). Resource(m.Resource). - Name(name). Param("resourceVersion", resourceVersion). + Param("watch", "true"). + FieldsSelectorParam(fields.OneTermEqualSelector("metadata.name", name)). Watch() } diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/apiserver_test.go b/staging/src/k8s.io/apiserver/pkg/endpoints/apiserver_test.go index 55571975068..ea67e41df2a 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/apiserver_test.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/apiserver_test.go @@ -737,6 +737,7 @@ func TestNotFound(t *testing.T) { "groupless namespaced PUT with extra segment": {"PUT", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/ns/simples/bar/baz", http.StatusNotFound}, "groupless namespaced watch missing storage": {"GET", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/watch/", http.StatusNotFound}, "groupless namespaced watch with bad method": {"POST", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/watch/namespaces/ns/simples/bar", http.StatusMethodNotAllowed}, + "groupless namespaced watch param with bad method": {"POST", "/" + grouplessPrefix + "/" + grouplessGroupVersion.Version + "/namespaces/ns/simples/bar?watch=true", http.StatusMethodNotAllowed}, // Positive checks to make sure everything is wired correctly "GET root": {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/simpleroots", http.StatusOK}, @@ -768,6 +769,7 @@ func TestNotFound(t *testing.T) { "namespaced PUT with extra segment": {"PUT", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/ns/simples/bar/baz", http.StatusNotFound}, "namespaced watch missing storage": {"GET", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/watch/", http.StatusNotFound}, "namespaced watch with bad method": {"POST", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/watch/namespaces/ns/simples/bar", http.StatusMethodNotAllowed}, + "namespaced watch param with bad method": {"POST", "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/ns/simples/bar?watch=true", http.StatusMethodNotAllowed}, } handler := handle(map[string]rest.Storage{ "simples": &SimpleRESTStorage{}, diff --git a/staging/src/k8s.io/client-go/dynamic/client.go b/staging/src/k8s.io/client-go/dynamic/client.go index 09b3b174c97..34ffe5846de 100644 --- a/staging/src/k8s.io/client-go/dynamic/client.go +++ b/staging/src/k8s.io/client-go/dynamic/client.go @@ -197,8 +197,8 @@ func (rc *ResourceClient) Watch(opts metav1.ListOptions) (watch.Interface, error if parameterEncoder == nil { parameterEncoder = defaultParameterEncoder } + opts.Watch = true return rc.cl.Get(). - Prefix("watch"). NamespaceIfScoped(rc.ns, rc.resource.Namespaced). Resource(rc.resource.Name). VersionedParams(&opts, parameterEncoder). diff --git a/staging/src/k8s.io/client-go/dynamic/client_test.go b/staging/src/k8s.io/client-go/dynamic/client_test.go index 663866526b7..f0bf8b35d9e 100644 --- a/staging/src/k8s.io/client-go/dynamic/client_test.go +++ b/staging/src/k8s.io/client-go/dynamic/client_test.go @@ -425,10 +425,12 @@ func TestWatch(t *testing.T) { namespace string events []watch.Event path string + query string }{ { - name: "normal_watch", - path: "/api/gtest/vtest/watch/rtest", + name: "normal_watch", + path: "/api/gtest/vtest/rtest", + query: "watch=true", events: []watch.Event{ {Type: watch.Added, Object: getObject("vTest", "rTest", "normal_watch")}, {Type: watch.Modified, Object: getObject("vTest", "rTest", "normal_watch")}, @@ -438,7 +440,8 @@ func TestWatch(t *testing.T) { { name: "namespaced_watch", namespace: "nstest", - path: "/api/gtest/vtest/watch/namespaces/nstest/rtest", + path: "/api/gtest/vtest/namespaces/nstest/rtest", + query: "watch=true", events: []watch.Event{ {Type: watch.Added, Object: getObject("vTest", "rTest", "namespaced_watch")}, {Type: watch.Modified, Object: getObject("vTest", "rTest", "namespaced_watch")}, @@ -457,6 +460,9 @@ func TestWatch(t *testing.T) { if r.URL.Path != tc.path { t.Errorf("Watch(%q) got path %s. wanted %s", tc.name, r.URL.Path, tc.path) } + if r.URL.RawQuery != tc.query { + t.Errorf("Watch(%q) got query %s. wanted %s", tc.name, r.URL.RawQuery, tc.query) + } enc := restclientwatch.NewEncoder(streaming.NewEncoder(w, dynamicCodec{}), dynamicCodec{}) for _, e := range tc.events { diff --git a/staging/src/k8s.io/client-go/tools/cache/listwatch.go b/staging/src/k8s.io/client-go/tools/cache/listwatch.go index 1261e758169..af01d474579 100644 --- a/staging/src/k8s.io/client-go/tools/cache/listwatch.go +++ b/staging/src/k8s.io/client-go/tools/cache/listwatch.go @@ -67,8 +67,8 @@ func NewListWatchFromClient(c Getter, resource string, namespace string, fieldSe Get() } watchFunc := func(options metav1.ListOptions) (watch.Interface, error) { + options.Watch = true return c.Get(). - Prefix("watch"). Namespace(namespace). Resource(resource). VersionedParams(&options, metav1.ParameterCodec). diff --git a/test/integration/client/client_test.go b/test/integration/client/client_test.go index 89d1013d7fd..07956ac1a7a 100644 --- a/test/integration/client/client_test.go +++ b/test/integration/client/client_test.go @@ -29,6 +29,7 @@ import ( apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" @@ -491,11 +492,11 @@ func TestSingleWatch(t *testing.T) { } w, err := client.Core().RESTClient().Get(). - Prefix("watch"). Namespace(ns.Name). Resource("events"). - Name("event-9"). Param("resourceVersion", rv1). + Param("watch", "true"). + FieldsSelectorParam(fields.OneTermEqualSelector("metadata.name", "event-9")). Watch() if err != nil {