Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support renegotiation in client based on response ContentType #25465

Merged
merged 2 commits into from
May 12, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 8 additions & 0 deletions pkg/client/restclient/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ type Serializers struct {
Decoder runtime.Decoder
StreamingSerializer runtime.Serializer
Framer runtime.Framer
RenegotiatedDecoder func(contentType string, params map[string]string) (runtime.Decoder, error)
}

// NewRESTClient creates a new RESTClient. This client performs generic REST functions
Expand Down Expand Up @@ -156,6 +157,13 @@ func createSerializers(config ContentConfig) (*Serializers, error) {
Decoder: negotiated.DecoderToVersion(info.Serializer, internalGV),
StreamingSerializer: streamInfo.Serializer,
Framer: streamInfo.Framer,
RenegotiatedDecoder: func(contentType string, params map[string]string) (runtime.Decoder, error) {
renegotiated, ok := negotiated.SerializerForMediaType(contentType, params)
if !ok {
return nil, fmt.Errorf("serializer for %s not registered", contentType)
}
return negotiated.DecoderToVersion(renegotiated.Serializer, internalGV), nil
},
}, nil
}

Expand Down
30 changes: 27 additions & 3 deletions pkg/client/restclient/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -910,12 +910,30 @@ func (r *Request) transformResponse(resp *http.Response, req *http.Request) Resu
return Result{err: errors.FromObject(status)}
}

// TODO: Check ContentType.
contentType := resp.Header.Get("Content-Type")
var decoder runtime.Decoder
if contentType == r.content.ContentType {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure this is safe in all cases - the string content-type may only be partially equivalent. I think we have to do a slightly more invasive check.

At a minimum, in your else block you need to call mime.Parse(contentType) and pass both parts to renegotiate.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mean that I should have:

else {
mediaType, params, err := mime.Parse(contentType)
if err != nil {
return Result{err: "error parsing content type"}
}
decoder, err := r.serializers.RenegotiatedDecoder(mediaType, params)
if err != nil {
// return what I have
}
}

or sth else?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Basically yes.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

decoder = r.serializers.Decoder
} else {
mediaType, params, err := mime.ParseMediaType(contentType)
if err != nil {
return Result{err: errors.NewInternalError(err)}
}
decoder, err = r.serializers.RenegotiatedDecoder(mediaType, params)
if err != nil {
return Result{
body: body,
contentType: contentType,
statusCode: resp.StatusCode,
}
}
}

return Result{
body: body,
contentType: resp.Header.Get("Content-Type"),
contentType: contentType,
statusCode: resp.StatusCode,
decoder: r.serializers.Decoder,
decoder: decoder,
}
}

Expand Down Expand Up @@ -1021,6 +1039,9 @@ func (r Result) Get() (runtime.Object, error) {
if r.err != nil {
return nil, r.err
}
if r.decoder == nil {
return nil, fmt.Errorf("serializer for %s doesn't exist", r.contentType)
}
return runtime.Decode(r.decoder, r.body)
}

Expand All @@ -1036,6 +1057,9 @@ func (r Result) Into(obj runtime.Object) error {
if r.err != nil {
return r.err
}
if r.decoder == nil {
return fmt.Errorf("serializer for %s doesn't exist", r.contentType)
}
return runtime.DecodeInto(r.decoder, r.body, obj)
}

Expand Down
6 changes: 6 additions & 0 deletions pkg/client/typed/dynamic/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ func TestList(t *testing.T) {
t.Errorf("List(%q) got path %s. wanted %s", tc.name, r.URL.Path, tc.path)
}

w.Header().Set("Content-Type", runtime.ContentTypeJSON)
w.Write(tc.resp)
})
if err != nil {
Expand Down Expand Up @@ -179,6 +180,7 @@ func TestGet(t *testing.T) {
t.Errorf("Get(%q) got path %s. wanted %s", tc.name, r.URL.Path, tc.path)
}

w.Header().Set("Content-Type", runtime.ContentTypeJSON)
w.Write(tc.resp)
})
if err != nil {
Expand Down Expand Up @@ -231,6 +233,7 @@ func TestDelete(t *testing.T) {
t.Errorf("Delete(%q) got path %s. wanted %s", tc.name, r.URL.Path, tc.path)
}

w.Header().Set("Content-Type", runtime.ContentTypeJSON)
runtime.UnstructuredJSONScheme.EncodeToStream(statusOK, w)
})
if err != nil {
Expand Down Expand Up @@ -279,6 +282,7 @@ func TestDeleteCollection(t *testing.T) {
t.Errorf("DeleteCollection(%q) got path %s. wanted %s", tc.name, r.URL.Path, tc.path)
}

w.Header().Set("Content-Type", runtime.ContentTypeJSON)
runtime.UnstructuredJSONScheme.EncodeToStream(statusOK, w)
})
if err != nil {
Expand Down Expand Up @@ -326,6 +330,7 @@ func TestCreate(t *testing.T) {
t.Errorf("Create(%q) got path %s. wanted %s", tc.name, r.URL.Path, tc.path)
}

w.Header().Set("Content-Type", runtime.ContentTypeJSON)
data, err := ioutil.ReadAll(r.Body)
if err != nil {
t.Errorf("Create(%q) unexpected error reading body: %v", tc.name, err)
Expand Down Expand Up @@ -384,6 +389,7 @@ func TestUpdate(t *testing.T) {
t.Errorf("Update(%q) got path %s. wanted %s", tc.name, r.URL.Path, tc.path)
}

w.Header().Set("Content-Type", runtime.ContentTypeJSON)
data, err := ioutil.ReadAll(r.Body)
if err != nil {
t.Errorf("Update(%q) unexpected error reading body: %v", tc.name, err)
Expand Down
1 change: 1 addition & 0 deletions pkg/client/unversioned/fake/fake.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ func (c *RESTClient) Delete() *restclient.Request {

func (c *RESTClient) request(verb string) *restclient.Request {
config := restclient.ContentConfig{
ContentType: runtime.ContentTypeJSON,
GroupVersion: testapi.Default.GroupVersion(),
Codec: c.Codec,
}
Expand Down
5 changes: 4 additions & 1 deletion pkg/client/unversioned/helper_blackbox_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import (
"k8s.io/kubernetes/pkg/client/restclient"
"k8s.io/kubernetes/pkg/client/unversioned"
"k8s.io/kubernetes/pkg/client/unversioned/fake"
"k8s.io/kubernetes/pkg/runtime"
)

func objBody(object interface{}) io.ReadCloser {
Expand Down Expand Up @@ -104,7 +105,9 @@ func TestNegotiateVersion(t *testing.T) {
if test.sendErr != nil {
return nil, test.sendErr
}
return &http.Response{StatusCode: 200, Body: objBody(&uapi.APIVersions{Versions: test.serverVersions})}, nil
header := http.Header{}
header.Set("Content-Type", runtime.ContentTypeJSON)
return &http.Response{StatusCode: 200, Header: header, Body: objBody(&uapi.APIVersions{Versions: test.serverVersions})}, nil
}),
}
c := unversioned.NewOrDie(test.config)
Expand Down
2 changes: 2 additions & 0 deletions pkg/controller/namespace/namespace_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import (
"k8s.io/kubernetes/pkg/client/restclient"
"k8s.io/kubernetes/pkg/client/testing/core"
"k8s.io/kubernetes/pkg/client/typed/dynamic"
"k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/util/sets"
)

Expand Down Expand Up @@ -268,6 +269,7 @@ func (f *fakeActionHandler) ServeHTTP(response http.ResponseWriter, request *htt
defer f.lock.Unlock()

f.actions = append(f.actions, fakeAction{method: request.Method, path: request.URL.Path})
response.Header().Set("Content-Type", runtime.ContentTypeJSON)
response.WriteHeader(f.statusCode)
response.Write([]byte("{\"kind\": \"List\"}"))
}
Expand Down
14 changes: 7 additions & 7 deletions pkg/kubectl/cmd/annotate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -428,15 +428,15 @@ func TestAnnotateObject(t *testing.T) {
case "GET":
switch req.URL.Path {
case "/namespaces/test/pods/foo":
return &http.Response{StatusCode: 200, Body: objBody(codec, &pods.Items[0])}, nil
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &pods.Items[0])}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
}
case "PATCH":
switch req.URL.Path {
case "/namespaces/test/pods/foo":
return &http.Response{StatusCode: 200, Body: objBody(codec, &pods.Items[0])}, nil
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &pods.Items[0])}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
Expand Down Expand Up @@ -478,15 +478,15 @@ func TestAnnotateObjectFromFile(t *testing.T) {
case "GET":
switch req.URL.Path {
case "/namespaces/test/replicationcontrollers/cassandra":
return &http.Response{StatusCode: 200, Body: objBody(codec, &pods.Items[0])}, nil
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &pods.Items[0])}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
}
case "PATCH":
switch req.URL.Path {
case "/namespaces/test/replicationcontrollers/cassandra":
return &http.Response{StatusCode: 200, Body: objBody(codec, &pods.Items[0])}, nil
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &pods.Items[0])}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
Expand Down Expand Up @@ -529,17 +529,17 @@ func TestAnnotateMultipleObjects(t *testing.T) {
case "GET":
switch req.URL.Path {
case "/namespaces/test/pods":
return &http.Response{StatusCode: 200, Body: objBody(codec, pods)}, nil
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, pods)}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
}
case "PATCH":
switch req.URL.Path {
case "/namespaces/test/pods/foo":
return &http.Response{StatusCode: 200, Body: objBody(codec, &pods.Items[0])}, nil
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &pods.Items[0])}, nil
case "/namespaces/test/pods/bar":
return &http.Response{StatusCode: 200, Body: objBody(codec, &pods.Items[1])}, nil
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &pods.Items[1])}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
Expand Down
16 changes: 8 additions & 8 deletions pkg/kubectl/cmd/apply_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,11 +187,11 @@ func TestApplyObject(t *testing.T) {
switch p, m := req.URL.Path, req.Method; {
case p == pathRC && m == "GET":
bodyRC := ioutil.NopCloser(bytes.NewReader(currentRC))
return &http.Response{StatusCode: 200, Body: bodyRC}, nil
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodyRC}, nil
case p == pathRC && m == "PATCH":
validatePatchApplication(t, req)
bodyRC := ioutil.NopCloser(bytes.NewReader(currentRC))
return &http.Response{StatusCode: 200, Body: bodyRC}, nil
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodyRC}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
Expand Down Expand Up @@ -225,10 +225,10 @@ func TestApplyNonExistObject(t *testing.T) {
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case p == pathNameRC && m == "GET":
return &http.Response{StatusCode: 404, Body: ioutil.NopCloser(bytes.NewReader(nil))}, nil
return &http.Response{StatusCode: 404, Header: defaultHeader(), Body: ioutil.NopCloser(bytes.NewReader(nil))}, nil
case p == pathRC && m == "POST":
bodyRC := ioutil.NopCloser(bytes.NewReader(currentRC))
return &http.Response{StatusCode: 201, Body: bodyRC}, nil
return &http.Response{StatusCode: 201, Header: defaultHeader(), Body: bodyRC}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
Expand Down Expand Up @@ -273,18 +273,18 @@ func testApplyMultipleObjects(t *testing.T, asList bool) {
switch p, m := req.URL.Path, req.Method; {
case p == pathRC && m == "GET":
bodyRC := ioutil.NopCloser(bytes.NewReader(currentRC))
return &http.Response{StatusCode: 200, Body: bodyRC}, nil
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodyRC}, nil
case p == pathRC && m == "PATCH":
validatePatchApplication(t, req)
bodyRC := ioutil.NopCloser(bytes.NewReader(currentRC))
return &http.Response{StatusCode: 200, Body: bodyRC}, nil
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodyRC}, nil
case p == pathSVC && m == "GET":
bodySVC := ioutil.NopCloser(bytes.NewReader(currentSVC))
return &http.Response{StatusCode: 200, Body: bodySVC}, nil
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodySVC}, nil
case p == pathSVC && m == "PATCH":
validatePatchApplication(t, req)
bodySVC := ioutil.NopCloser(bytes.NewReader(currentSVC))
return &http.Response{StatusCode: 200, Body: bodySVC}, nil
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodySVC}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
Expand Down
4 changes: 2 additions & 2 deletions pkg/kubectl/cmd/attach_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ func TestAttach(t *testing.T) {
switch p, m := req.URL.Path, req.Method; {
case p == test.podPath && m == "GET":
body := objBody(codec, test.pod)
return &http.Response{StatusCode: 200, Body: body}, nil
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: body}, nil
default:
// Ensures no GET is performed when deleting by name
t.Errorf("%s: unexpected request: %s %#v\n%#v", test.name, req.Method, req.URL, req)
Expand Down Expand Up @@ -219,7 +219,7 @@ func TestAttachWarnings(t *testing.T) {
switch p, m := req.URL.Path, req.Method; {
case p == test.podPath && m == "GET":
body := objBody(codec, test.pod)
return &http.Response{StatusCode: 200, Body: body}, nil
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: body}, nil
default:
t.Errorf("%s: unexpected request: %s %#v\n%#v", test.name, req.Method, req.URL, req)
return nil, fmt.Errorf("unexpected request")
Expand Down
16 changes: 16 additions & 0 deletions pkg/kubectl/cmd/cmd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"reflect"
"testing"
Expand Down Expand Up @@ -49,6 +50,21 @@ func initTestErrorHandler(t *testing.T) {
})
}

func defaultHeader() http.Header {
header := http.Header{}
header.Set("Content-Type", runtime.ContentTypeJSON)
return header
}

func defaultClientConfig() *restclient.Config {
return &restclient.Config{
ContentConfig: restclient.ContentConfig{
ContentType: runtime.ContentTypeJSON,
GroupVersion: testapi.Default.GroupVersion(),
},
}
}

type internalType struct {
Kind string
APIVersion string
Expand Down
2 changes: 1 addition & 1 deletion pkg/kubectl/cmd/create_configmap_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ func TestCreateConfigMap(t *testing.T) {
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case p == "/namespaces/test/configmaps" && m == "POST":
return &http.Response{StatusCode: 201, Body: objBody(codec, configMap)}, nil
return &http.Response{StatusCode: 201, Header: defaultHeader(), Body: objBody(codec, configMap)}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
Expand Down
2 changes: 1 addition & 1 deletion pkg/kubectl/cmd/create_namespace_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ func TestCreateNamespace(t *testing.T) {
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case p == "/namespaces" && m == "POST":
return &http.Response{StatusCode: 201, Body: objBody(codec, namespaceObject)}, nil
return &http.Response{StatusCode: 201, Header: defaultHeader(), Body: objBody(codec, namespaceObject)}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
Expand Down
4 changes: 2 additions & 2 deletions pkg/kubectl/cmd/create_secret_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ func TestCreateSecretGeneric(t *testing.T) {
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case p == "/namespaces/test/secrets" && m == "POST":
return &http.Response{StatusCode: 201, Body: objBody(codec, secretObject)}, nil
return &http.Response{StatusCode: 201, Header: defaultHeader(), Body: objBody(codec, secretObject)}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
Expand Down Expand Up @@ -63,7 +63,7 @@ func TestCreateSecretDockerRegistry(t *testing.T) {
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case p == "/namespaces/test/secrets" && m == "POST":
return &http.Response{StatusCode: 201, Body: objBody(codec, secretObject)}, nil
return &http.Response{StatusCode: 201, Header: defaultHeader(), Body: objBody(codec, secretObject)}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
Expand Down
2 changes: 1 addition & 1 deletion pkg/kubectl/cmd/create_serviceaccount_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ func TestCreateServiceAccount(t *testing.T) {
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case p == "/namespaces/test/serviceaccounts" && m == "POST":
return &http.Response{StatusCode: 201, Body: objBody(codec, serviceAccountObject)}, nil
return &http.Response{StatusCode: 201, Header: defaultHeader(), Body: objBody(codec, serviceAccountObject)}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
Expand Down
8 changes: 4 additions & 4 deletions pkg/kubectl/cmd/create_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func TestCreateObject(t *testing.T) {
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case p == "/namespaces/test/replicationcontrollers" && m == "POST":
return &http.Response{StatusCode: 201, Body: objBody(codec, &rc.Items[0])}, nil
return &http.Response{StatusCode: 201, Header: defaultHeader(), Body: objBody(codec, &rc.Items[0])}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
Expand Down Expand Up @@ -81,9 +81,9 @@ func TestCreateMultipleObject(t *testing.T) {
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case p == "/namespaces/test/services" && m == "POST":
return &http.Response{StatusCode: 201, Body: objBody(codec, &svc.Items[0])}, nil
return &http.Response{StatusCode: 201, Header: defaultHeader(), Body: objBody(codec, &svc.Items[0])}, nil
case p == "/namespaces/test/replicationcontrollers" && m == "POST":
return &http.Response{StatusCode: 201, Body: objBody(codec, &rc.Items[0])}, nil
return &http.Response{StatusCode: 201, Header: defaultHeader(), Body: objBody(codec, &rc.Items[0])}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
Expand Down Expand Up @@ -117,7 +117,7 @@ func TestCreateDirectory(t *testing.T) {
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case p == "/namespaces/test/replicationcontrollers" && m == "POST":
return &http.Response{StatusCode: 201, Body: objBody(codec, &rc.Items[0])}, nil
return &http.Response{StatusCode: 201, Header: defaultHeader(), Body: objBody(codec, &rc.Items[0])}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
Expand Down