Replace negotiation with a new method that can extract info

Alter how runtime.SerializeInfo is represented to simplify negotiation and reduce the need to allocate during negotiation. Simplify the dynamic client's logic around negotiating type. Add more tests for media type handling where necessary.
parent f9f680a9
......@@ -545,7 +545,7 @@ func StartControllers(s *options.CMServer, kubeconfig *restclient.Config, rootCl
config := restclient.AddUserAgent(kubeconfig, "generic-garbage-collector")
config.ContentConfig.NegotiatedSerializer = serializer.DirectCodecFactory{CodecFactory: metaonly.NewMetadataCodecFactory()}
metaOnlyClientPool := dynamic.NewClientPool(config, restMapper, dynamic.LegacyAPIPathResolverFunc)
config.ContentConfig.NegotiatedSerializer = nil
config.ContentConfig = dynamic.ContentConfig()
clientPool := dynamic.NewClientPool(config, restMapper, dynamic.LegacyAPIPathResolverFunc)
garbageCollector, err := garbagecollector.NewGarbageCollector(metaOnlyClientPool, clientPool, restMapper, groupVersionResources)
if err != nil {
......
......@@ -54,12 +54,12 @@ func TestUniversalDeserializer(t *testing.T) {
expected := &v1.Pod{ObjectMeta: v1.ObjectMeta{Name: "test"}}
d := api.Codecs.UniversalDeserializer()
for _, mediaType := range []string{"application/json", "application/yaml", "application/vnd.kubernetes.protobuf"} {
e, ok := api.Codecs.SerializerForMediaType(mediaType, nil)
info, ok := runtime.SerializerInfoForMediaType(api.Codecs.SupportedMediaTypes(), mediaType)
if !ok {
t.Fatal(mediaType)
}
buf := &bytes.Buffer{}
if err := e.Encode(expected, buf); err != nil {
if err := info.Serializer.Encode(expected, buf); err != nil {
t.Fatalf("%s: %v", mediaType, err)
}
obj, _, err := d.Decode(buf.Bytes(), &unversioned.GroupVersionKind{Kind: "Pod", Version: "v1"}, nil)
......
......@@ -380,12 +380,15 @@ func TestObjectWatchFraming(t *testing.T) {
secret.Data["long"] = bytes.Repeat([]byte{0x01, 0x02, 0x03, 0x00}, 1000)
converted, _ := api.Scheme.ConvertToVersion(secret, v1.SchemeGroupVersion)
v1secret := converted.(*v1.Secret)
for _, streamingMediaType := range api.Codecs.SupportedStreamingMediaTypes() {
s, _ := api.Codecs.StreamingSerializerForMediaType(streamingMediaType, nil)
for _, info := range api.Codecs.SupportedMediaTypes() {
if info.StreamSerializer == nil {
continue
}
s := info.StreamSerializer
framer := s.Framer
embedded := s.Embedded.Serializer
embedded := info.Serializer
if embedded == nil {
t.Errorf("no embedded serializer for %s", streamingMediaType)
t.Errorf("no embedded serializer for %s", info.MediaType)
continue
}
innerDecode := api.Codecs.DecoderToVersion(embedded, api.SchemeGroupVersion)
......@@ -442,7 +445,7 @@ func TestObjectWatchFraming(t *testing.T) {
}
if !api.Semantic.DeepEqual(secret, outEvent.Object.Object) {
t.Fatalf("%s: did not match after frame decoding: %s", streamingMediaType, diff.ObjectGoPrintDiff(secret, outEvent.Object.Object))
t.Fatalf("%s: did not match after frame decoding: %s", info.MediaType, diff.ObjectGoPrintDiff(secret, outEvent.Object.Object))
}
}
}
......
......@@ -93,11 +93,11 @@ type TestGroup struct {
func init() {
if apiMediaType := os.Getenv("KUBE_TEST_API_TYPE"); len(apiMediaType) > 0 {
var ok bool
mediaType, options, err := mime.ParseMediaType(apiMediaType)
mediaType, _, err := mime.ParseMediaType(apiMediaType)
if err != nil {
panic(err)
}
serializer, ok = api.Codecs.SerializerForMediaType(mediaType, options)
serializer, ok = runtime.SerializerInfoForMediaType(api.Codecs.SupportedMediaTypes(), mediaType)
if !ok {
panic(fmt.Sprintf("no serializer for %s", apiMediaType))
}
......@@ -105,11 +105,11 @@ func init() {
if storageMediaType := StorageMediaType(); len(storageMediaType) > 0 {
var ok bool
mediaType, options, err := mime.ParseMediaType(storageMediaType)
mediaType, _, err := mime.ParseMediaType(storageMediaType)
if err != nil {
panic(err)
}
storageSerializer, ok = api.Codecs.SerializerForMediaType(mediaType, options)
storageSerializer, ok = runtime.SerializerInfoForMediaType(api.Codecs.SupportedMediaTypes(), mediaType)
if !ok {
panic(fmt.Sprintf("no serializer for %s", storageMediaType))
}
......@@ -312,7 +312,7 @@ func (g TestGroup) Codec() runtime.Codec {
if serializer.Serializer == nil {
return api.Codecs.LegacyCodec(g.externalGroupVersion)
}
return api.Codecs.CodecForVersions(serializer, api.Codecs.UniversalDeserializer(), unversioned.GroupVersions{g.externalGroupVersion}, nil)
return api.Codecs.CodecForVersions(serializer.Serializer, api.Codecs.UniversalDeserializer(), unversioned.GroupVersions{g.externalGroupVersion}, nil)
}
// NegotiatedSerializer returns the negotiated serializer for the server.
......@@ -452,11 +452,11 @@ func GetCodecForObject(obj runtime.Object) (runtime.Codec, error) {
}
// Codec used for unversioned types
if api.Scheme.Recognizes(kind) {
serializer, ok := api.Codecs.SerializerForFileExtension("json")
serializer, ok := runtime.SerializerInfoForMediaType(api.Codecs.SupportedMediaTypes(), runtime.ContentTypeJSON)
if !ok {
return nil, fmt.Errorf("no serializer registered for json")
}
return serializer, nil
return serializer.Serializer, nil
}
return nil, fmt.Errorf("unexpected kind: %v", kind)
}
......
......@@ -102,7 +102,8 @@ func (a *APIInstaller) NewWebService() *restful.WebService {
// If we stop using go-restful, we can default empty content-type to application/json on an
// endpoint by endpoint basis
ws.Consumes("*/*")
ws.Produces(a.group.Serializer.SupportedMediaTypes()...)
mediaTypes, streamMediaTypes := mediaTypesForSerializer(a.group.Serializer)
ws.Produces(append(mediaTypes, streamMediaTypes...)...)
ws.ApiVersion(a.group.GroupVersion.String())
return ws
......@@ -472,6 +473,10 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
//
// test/integration/auth_test.go is currently the most comprehensive status code test
mediaTypes, streamMediaTypes := mediaTypesForSerializer(a.group.Serializer)
allMediaTypes := append(mediaTypes, streamMediaTypes...)
ws.Produces(allMediaTypes...)
reqScope := RequestScope{
ContextFunc: ctxFn,
Serializer: a.group.Serializer,
......@@ -517,7 +522,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
Doc(doc).
Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")).
Operation("read"+namespaced+kind+strings.Title(subresource)+operationSuffix).
Produces(append(storageMeta.ProducesMIMETypes(action.Verb), a.group.Serializer.SupportedMediaTypes()...)...).
Produces(append(storageMeta.ProducesMIMETypes(action.Verb), mediaTypes...)...).
Returns(http.StatusOK, "OK", versionedObject).
Writes(versionedObject)
if isGetterWithOptions {
......@@ -542,7 +547,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
Doc(doc).
Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")).
Operation("list"+namespaced+kind+strings.Title(subresource)+operationSuffix).
Produces(append(storageMeta.ProducesMIMETypes(action.Verb), a.group.Serializer.SupportedMediaTypes()...)...).
Produces(append(storageMeta.ProducesMIMETypes(action.Verb), allMediaTypes...)...).
Returns(http.StatusOK, "OK", versionedList).
Writes(versionedList)
if err := addObjectParams(ws, route, versionedListOptions); err != nil {
......@@ -574,7 +579,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
Doc(doc).
Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")).
Operation("replace"+namespaced+kind+strings.Title(subresource)+operationSuffix).
Produces(append(storageMeta.ProducesMIMETypes(action.Verb), a.group.Serializer.SupportedMediaTypes()...)...).
Produces(append(storageMeta.ProducesMIMETypes(action.Verb), mediaTypes...)...).
Returns(http.StatusOK, "OK", versionedObject).
Reads(versionedObject).
Writes(versionedObject)
......@@ -591,7 +596,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")).
Consumes(string(api.JSONPatchType), string(api.MergePatchType), string(api.StrategicMergePatchType)).
Operation("patch"+namespaced+kind+strings.Title(subresource)+operationSuffix).
Produces(append(storageMeta.ProducesMIMETypes(action.Verb), a.group.Serializer.SupportedMediaTypes()...)...).
Produces(append(storageMeta.ProducesMIMETypes(action.Verb), mediaTypes...)...).
Returns(http.StatusOK, "OK", versionedObject).
Reads(unversioned.Patch{}).
Writes(versionedObject)
......@@ -613,7 +618,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
Doc(doc).
Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")).
Operation("create"+namespaced+kind+strings.Title(subresource)+operationSuffix).
Produces(append(storageMeta.ProducesMIMETypes(action.Verb), a.group.Serializer.SupportedMediaTypes()...)...).
Produces(append(storageMeta.ProducesMIMETypes(action.Verb), mediaTypes...)...).
Returns(http.StatusOK, "OK", versionedObject).
Reads(versionedObject).
Writes(versionedObject)
......@@ -629,7 +634,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
Doc(doc).
Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")).
Operation("delete"+namespaced+kind+strings.Title(subresource)+operationSuffix).
Produces(append(storageMeta.ProducesMIMETypes(action.Verb), a.group.Serializer.SupportedMediaTypes()...)...).
Produces(append(storageMeta.ProducesMIMETypes(action.Verb), mediaTypes...)...).
Writes(versionedStatus).
Returns(http.StatusOK, "OK", versionedStatus)
if isGracefulDeleter {
......@@ -647,7 +652,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
Doc(doc).
Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")).
Operation("deletecollection"+namespaced+kind+strings.Title(subresource)+operationSuffix).
Produces(append(storageMeta.ProducesMIMETypes(action.Verb), a.group.Serializer.SupportedMediaTypes()...)...).
Produces(append(storageMeta.ProducesMIMETypes(action.Verb), mediaTypes...)...).
Writes(versionedStatus).
Returns(http.StatusOK, "OK", versionedStatus)
if err := addObjectParams(ws, route, versionedListOptions); err != nil {
......@@ -666,7 +671,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
Doc(doc).
Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")).
Operation("watch"+namespaced+kind+strings.Title(subresource)+operationSuffix).
Produces(a.group.Serializer.SupportedStreamingMediaTypes()...).
Produces(allMediaTypes...).
Returns(http.StatusOK, "OK", versionedWatchEvent).
Writes(versionedWatchEvent)
if err := addObjectParams(ws, route, versionedListOptions); err != nil {
......@@ -685,7 +690,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
Doc(doc).
Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")).
Operation("watch"+namespaced+kind+strings.Title(subresource)+"List"+operationSuffix).
Produces(a.group.Serializer.SupportedStreamingMediaTypes()...).
Produces(allMediaTypes...).
Returns(http.StatusOK, "OK", versionedWatchEvent).
Writes(versionedWatchEvent)
if err := addObjectParams(ws, route, versionedListOptions); err != nil {
......
......@@ -211,6 +211,7 @@ func AddApiWebService(s runtime.NegotiatedSerializer, container *restful.Contain
// Because in release 1.1, /api returns response with empty APIVersion, we
// use StripVersionNegotiatedSerializer to keep the response backwards
// compatible.
mediaTypes, _ := mediaTypesForSerializer(s)
ss := StripVersionNegotiatedSerializer{s}
versionHandler := APIVersionHandler(ss, getAPIVersionsFunc)
ws := new(restful.WebService)
......@@ -219,8 +220,8 @@ func AddApiWebService(s runtime.NegotiatedSerializer, container *restful.Contain
ws.Route(ws.GET("/").To(versionHandler).
Doc("get available API versions").
Operation("getAPIVersions").
Produces(s.SupportedMediaTypes()...).
Consumes(s.SupportedMediaTypes()...).
Produces(mediaTypes...).
Consumes(mediaTypes...).
Writes(unversioned.APIVersions{}))
container.Add(ws)
}
......@@ -277,6 +278,7 @@ func NewApisWebService(s runtime.NegotiatedSerializer, apiPrefix string, f func(
// use StripVersionNegotiatedSerializer to keep the response backwards
// compatible.
ss := StripVersionNegotiatedSerializer{s}
mediaTypes, _ := mediaTypesForSerializer(s)
rootAPIHandler := RootAPIHandler(ss, f)
ws := new(restful.WebService)
ws.Path(apiPrefix)
......@@ -284,8 +286,8 @@ func NewApisWebService(s runtime.NegotiatedSerializer, apiPrefix string, f func(
ws.Route(ws.GET("/").To(rootAPIHandler).
Doc("get available API versions").
Operation("getAPIVersions").
Produces(s.SupportedMediaTypes()...).
Consumes(s.SupportedMediaTypes()...).
Produces(mediaTypes...).
Consumes(mediaTypes...).
Writes(unversioned.APIGroupList{}))
return ws
}
......@@ -300,6 +302,7 @@ func NewGroupWebService(s runtime.NegotiatedSerializer, path string, group unver
// response backwards compatible.
ss = StripVersionNegotiatedSerializer{s}
}
mediaTypes, _ := mediaTypesForSerializer(s)
groupHandler := GroupHandler(ss, group)
ws := new(restful.WebService)
ws.Path(path)
......@@ -307,8 +310,8 @@ func NewGroupWebService(s runtime.NegotiatedSerializer, path string, group unver
ws.Route(ws.GET("/").To(groupHandler).
Doc("get information of a group").
Operation("getAPIGroup").
Produces(s.SupportedMediaTypes()...).
Consumes(s.SupportedMediaTypes()...).
Produces(mediaTypes...).
Consumes(mediaTypes...).
Writes(unversioned.APIGroup{}))
return ws
}
......@@ -323,12 +326,13 @@ func AddSupportedResourcesWebService(s runtime.NegotiatedSerializer, ws *restful
// keep the response backwards compatible.
ss = StripVersionNegotiatedSerializer{s}
}
mediaTypes, _ := mediaTypesForSerializer(s)
resourceHandler := SupportedResourcesHandler(ss, groupVersion, lister)
ws.Route(ws.GET("/").To(resourceHandler).
Doc("get available resources").
Operation("getAPIResources").
Produces(s.SupportedMediaTypes()...).
Consumes(s.SupportedMediaTypes()...).
Produces(mediaTypes...).
Consumes(mediaTypes...).
Writes(unversioned.APIResourceList{}))
}
......@@ -417,7 +421,7 @@ func writeNegotiated(s runtime.NegotiatedSerializer, gv unversioned.GroupVersion
w.Header().Set("Content-Type", serializer.MediaType)
w.WriteHeader(statusCode)
encoder := s.EncoderForVersion(serializer, gv)
encoder := s.EncoderForVersion(serializer.Serializer, gv)
if err := encoder.Encode(object, w); err != nil {
errorJSONFatal(err, encoder, w)
}
......
......@@ -1224,11 +1224,12 @@ func TestMetadata(t *testing.T) {
matches[s] = i + 1
}
}
if matches["text/plain,application/json,application/yaml,application/vnd.kubernetes.protobuf"] == 0 ||
matches["application/json,application/json;stream=watch,application/vnd.kubernetes.protobuf,application/vnd.kubernetes.protobuf;stream=watch"] == 0 ||
matches["application/json,application/yaml,application/vnd.kubernetes.protobuf,application/json;stream=watch,application/vnd.kubernetes.protobuf;stream=watch"] == 0 ||
matches["application/json,application/yaml,application/vnd.kubernetes.protobuf"] == 0 ||
matches["*/*"] == 0 ||
len(matches) != 4 {
len(matches) != 5 {
t.Errorf("unexpected mime types: %v", matches)
}
}
......@@ -1321,6 +1322,89 @@ func TestGet(t *testing.T) {
}
}
func TestGetPretty(t *testing.T) {
storage := map[string]rest.Storage{}
simpleStorage := SimpleRESTStorage{
item: apiservertesting.Simple{
Other: "foo",
},
}
selfLinker := &setTestSelfLinker{
t: t,
expectedSet: "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple/id",
name: "id",
namespace: "default",
}
storage["simple"] = &simpleStorage
handler := handleLinker(storage, selfLinker)
server := httptest.NewServer(handler)
defer server.Close()
tests := []struct {
accept string
userAgent string
params url.Values
pretty bool
}{
{accept: runtime.ContentTypeJSON},
{accept: runtime.ContentTypeJSON + ";pretty=0"},
{accept: runtime.ContentTypeJSON, userAgent: "kubectl"},
{accept: runtime.ContentTypeJSON, params: url.Values{"pretty": {"0"}}},
{pretty: true, accept: runtime.ContentTypeJSON, userAgent: "curl"},
{pretty: true, accept: runtime.ContentTypeJSON, userAgent: "Mozilla/5.0"},
{pretty: true, accept: runtime.ContentTypeJSON, userAgent: "Wget"},
{pretty: true, accept: runtime.ContentTypeJSON + ";pretty=1"},
{pretty: true, accept: runtime.ContentTypeJSON, params: url.Values{"pretty": {"1"}}},
{pretty: true, accept: runtime.ContentTypeJSON, params: url.Values{"pretty": {"true"}}},
}
for i, test := range tests {
u, err := url.Parse(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple/id")
if err != nil {
t.Fatal(err)
}
u.RawQuery = test.params.Encode()
req := &http.Request{Method: "GET", URL: u}
req.Header = http.Header{}
req.Header.Set("Accept", test.accept)
req.Header.Set("User-Agent", test.userAgent)
resp, err := http.DefaultClient.Do(req)
if err != nil {
t.Fatal(err)
}
if resp.StatusCode != http.StatusOK {
t.Fatal(err)
}
var itemOut apiservertesting.Simple
body, err := extractBody(resp, &itemOut)
if err != nil {
t.Fatal(err)
}
// to get stable ordering we need to use a go type
unstructured := apiservertesting.Simple{}
if err := json.Unmarshal([]byte(body), &unstructured); err != nil {
t.Fatal(err)
}
var expect string
if test.pretty {
out, err := json.MarshalIndent(unstructured, "", " ")
if err != nil {
t.Fatal(err)
}
expect = string(out)
} else {
out, err := json.Marshal(unstructured)
if err != nil {
t.Fatal(err)
}
expect = string(out) + "\n"
}
if expect != body {
t.Errorf("%d: body did not match expected:\n%s\n%s", i, body, expect)
}
}
}
func TestGetBinary(t *testing.T) {
simpleStorage := SimpleRESTStorage{
stream: &SimpleStream{
......@@ -2719,12 +2803,12 @@ func TestCreateYAML(t *testing.T) {
simple := &apiservertesting.Simple{
Other: "bar",
}
serializer, ok := api.Codecs.SerializerForMediaType("application/yaml", nil)
info, ok := runtime.SerializerInfoForMediaType(api.Codecs.SupportedMediaTypes(), "application/yaml")
if !ok {
t.Fatal("No yaml serializer")
}
encoder := api.Codecs.EncoderForVersion(serializer, testGroupVersion)
decoder := api.Codecs.DecoderToVersion(serializer, testInternalGroupVersion)
encoder := api.Codecs.EncoderForVersion(info.Serializer, testGroupVersion)
decoder := api.Codecs.DecoderToVersion(info.Serializer, testInternalGroupVersion)
data, err := runtime.Encode(encoder, simple)
if err != nil {
......@@ -3216,7 +3300,7 @@ func BenchmarkUpdateProtobuf(b *testing.B) {
dest.Path = "/" + prefix + "/" + newGroupVersion.Group + "/" + newGroupVersion.Version + "/namespaces/foo/simples/bar"
dest.RawQuery = ""
info, _ := api.Codecs.SerializerForMediaType("application/vnd.kubernetes.protobuf", nil)
info, _ := runtime.SerializerInfoForMediaType(api.Codecs.SupportedMediaTypes(), "application/vnd.kubernetes.protobuf")
e := api.Codecs.EncoderForVersion(info.Serializer, newGroupVersion)
data, err := runtime.Encode(e, &items[0])
if err != nil {
......
......@@ -19,7 +19,6 @@ package apiserver
import (
"net/http"
"net/url"
"reflect"
"testing"
"k8s.io/kubernetes/pkg/api/unversioned"
......@@ -30,38 +29,24 @@ type fakeNegotiater struct {
serializer, streamSerializer runtime.Serializer
framer runtime.Framer
types, streamTypes []string
mediaType, streamMediaType string
options, streamOptions map[string]string
}
func (n *fakeNegotiater) SupportedMediaTypes() []string {
return n.types
}
func (n *fakeNegotiater) SupportedStreamingMediaTypes() []string {
return n.streamTypes
}
func (n *fakeNegotiater) SerializerForMediaType(mediaType string, options map[string]string) (runtime.SerializerInfo, bool) {
n.mediaType = mediaType
if len(options) > 0 {
n.options = options
}
return runtime.SerializerInfo{Serializer: n.serializer, MediaType: n.mediaType, EncodesAsText: true}, n.serializer != nil
}
func (n *fakeNegotiater) StreamingSerializerForMediaType(mediaType string, options map[string]string) (runtime.StreamSerializerInfo, bool) {
n.streamMediaType = mediaType
if len(options) > 0 {
n.streamOptions = options
func (n *fakeNegotiater) SupportedMediaTypes() []runtime.SerializerInfo {
var out []runtime.SerializerInfo
for _, s := range n.types {
info := runtime.SerializerInfo{Serializer: n.serializer, MediaType: s, EncodesAsText: true}
for _, t := range n.streamTypes {
if t == s {
info.StreamSerializer = &runtime.StreamSerializerInfo{
EncodesAsText: true,
Framer: n.framer,
Serializer: n.streamSerializer,
}
}
}
out = append(out, info)
}
return runtime.StreamSerializerInfo{
SerializerInfo: runtime.SerializerInfo{
Serializer: n.serializer,
MediaType: mediaType,
EncodesAsText: true,
},
Framer: n.framer,
}, n.streamSerializer != nil
return out
}
func (n *fakeNegotiater) EncoderForVersion(serializer runtime.Encoder, gv runtime.GroupVersioner) runtime.Encoder {
......@@ -202,12 +187,6 @@ func TestNegotiate(t *testing.T) {
},
},
{
ns: &fakeNegotiater{types: []string{"a/b/c"}},
errFn: func(err error) bool {
return err.Error() == "only the following media types are accepted: a/b/c"
},
},
{
ns: &fakeNegotiater{},
errFn: func(err error) bool {
return err.Error() == "only the following media types are accepted: "
......@@ -220,13 +199,6 @@ func TestNegotiate(t *testing.T) {
return err.Error() == "only the following media types are accepted: "
},
},
{
accept: "application/json",
ns: &fakeNegotiater{types: []string{"application/json"}},
errFn: func(err error) bool {
return err.Error() == "only the following media types are accepted: application/json"
},
},
}
for i, test := range testCases {
......@@ -264,8 +236,5 @@ func TestNegotiate(t *testing.T) {
if s.Serializer != test.serializer {
t.Errorf("%d: unexpected %s %s", i, test.serializer, s.Serializer)
}
if !reflect.DeepEqual(test.params, test.ns.options) {
t.Errorf("%d: unexpected %#v %#v", i, test.params, test.ns.options)
}
}
}
......@@ -364,7 +364,7 @@ func createHandler(r rest.NamedCreater, scope RequestScope, typer runtime.Object
scope.err(err, res.ResponseWriter, req.Request)
return
}
decoder := scope.Serializer.DecoderToVersion(s, unversioned.GroupVersion{Group: gv.Group, Version: runtime.APIVersionInternal})
decoder := scope.Serializer.DecoderToVersion(s.Serializer, unversioned.GroupVersion{Group: gv.Group, Version: runtime.APIVersionInternal})
body, err := readBody(req.Request)
if err != nil {
......@@ -480,15 +480,15 @@ func PatchResource(r rest.Patcher, scope RequestScope, typer runtime.ObjectTyper
return
}
s, ok := scope.Serializer.SerializerForMediaType("application/json", nil)
s, ok := runtime.SerializerInfoForMediaType(scope.Serializer.SupportedMediaTypes(), runtime.ContentTypeJSON)
if !ok {
scope.err(fmt.Errorf("no serializer defined for JSON"), res.ResponseWriter, req.Request)
return
}
gv := scope.Kind.GroupVersion()
codec := runtime.NewCodec(
scope.Serializer.EncoderForVersion(s, gv),
scope.Serializer.DecoderToVersion(s, unversioned.GroupVersion{Group: gv.Group, Version: runtime.APIVersionInternal}),
scope.Serializer.EncoderForVersion(s.Serializer, gv),
scope.Serializer.DecoderToVersion(s.Serializer, unversioned.GroupVersion{Group: gv.Group, Version: runtime.APIVersionInternal}),
)
updateAdmit := func(updatedObject runtime.Object, currentObject runtime.Object) error {
......@@ -685,7 +685,7 @@ func UpdateResource(r rest.Updater, scope RequestScope, typer runtime.ObjectType
defaultGVK := scope.Kind
original := r.New()
trace.Step("About to convert to expected version")
obj, gvk, err := scope.Serializer.DecoderToVersion(s, defaultGVK.GroupVersion()).Decode(body, &defaultGVK, original)
obj, gvk, err := scope.Serializer.DecoderToVersion(s.Serializer, defaultGVK.GroupVersion()).Decode(body, &defaultGVK, original)
if err != nil {
err = transformDecodeError(typer, err, original, gvk, body)
scope.err(err, res.ResponseWriter, req.Request)
......@@ -772,7 +772,7 @@ func DeleteResource(r rest.GracefulDeleter, checkBody bool, scope RequestScope,
return
}
defaultGVK := scope.Kind.GroupVersion().WithKind("DeleteOptions")
obj, _, err := scope.Serializer.DecoderToVersion(s, defaultGVK.GroupVersion()).Decode(body, &defaultGVK, options)
obj, _, err := scope.Serializer.DecoderToVersion(s.Serializer, defaultGVK.GroupVersion()).Decode(body, &defaultGVK, options)
if err != nil {
scope.err(err, res.ResponseWriter, req.Request)
return
......@@ -889,7 +889,7 @@ func DeleteCollection(r rest.CollectionDeleter, checkBody bool, scope RequestSco
return
}
defaultGVK := scope.Kind.GroupVersion().WithKind("DeleteOptions")
obj, _, err := scope.Serializer.DecoderToVersion(s, defaultGVK.GroupVersion()).Decode(body, &defaultGVK, options)
obj, _, err := scope.Serializer.DecoderToVersion(s.Serializer, defaultGVK.GroupVersion()).Decode(body, &defaultGVK, options)
if err != nil {
scope.err(err, res.ResponseWriter, req.Request)
return
......
......@@ -68,24 +68,33 @@ func serveWatch(watcher watch.Interface, scope RequestScope, req *restful.Reques
scope.err(err, res.ResponseWriter, req.Request)
return
}
if serializer.Framer == nil {
framer := serializer.StreamSerializer.Framer
streamSerializer := serializer.StreamSerializer.Serializer
embedded := serializer.Serializer
if framer == nil {
scope.err(fmt.Errorf("no framer defined for %q available for embedded encoding", serializer.MediaType), res.ResponseWriter, req.Request)
return
}
encoder := scope.Serializer.EncoderForVersion(serializer.Serializer, scope.Kind.GroupVersion())
encoder := scope.Serializer.EncoderForVersion(streamSerializer, scope.Kind.GroupVersion())
useTextFraming := serializer.EncodesAsText
// find the embedded serializer matching the media type
embeddedEncoder := scope.Serializer.EncoderForVersion(serializer.Embedded.Serializer, scope.Kind.GroupVersion())
embeddedEncoder := scope.Serializer.EncoderForVersion(embedded, scope.Kind.GroupVersion())
// TODO: next step, get back mediaTypeOptions from negotiate and return the exact value here
mediaType := serializer.MediaType
if mediaType != runtime.ContentTypeJSON {
mediaType += ";stream=watch"
}
server := &WatchServer{
watching: watcher,
scope: scope,
useTextFraming: useTextFraming,
mediaType: serializer.MediaType,
framer: serializer.Framer,
mediaType: mediaType,
framer: framer,
encoder: encoder,
embeddedEncoder: embeddedEncoder,
fixup: func(obj runtime.Object) {
......
......@@ -234,7 +234,8 @@ func TestWatchRead(t *testing.T) {
}
if response.StatusCode != http.StatusOK {
t.Fatalf("Unexpected response %#v", response)
b, _ := ioutil.ReadAll(response.Body)
t.Fatalf("Unexpected response for accept: %q: %#v\n%s", accept, response, string(b))
}
return response.Body, response.Header.Get("Content-Type")
}
......@@ -264,6 +265,11 @@ func TestWatchRead(t *testing.T) {
ExpectedContentType: "application/json",
MediaType: "application/json",
},
{
Accept: "application/json;stream=watch",
ExpectedContentType: "application/json", // legacy behavior
MediaType: "application/json",
},
// TODO: yaml stream serialization requires that RawExtension.MarshalJSON
// be able to understand nested encoding (since yaml calls json.Marshal
// rather than yaml.Marshal, which results in the raw bytes being in yaml).
......@@ -295,10 +301,11 @@ func TestWatchRead(t *testing.T) {
for _, protocol := range protocols {
for _, test := range testCases {
serializer, ok := api.Codecs.StreamingSerializerForMediaType(test.MediaType, nil)
if !ok {
t.Fatal(serializer)
info, ok := runtime.SerializerInfoForMediaType(api.Codecs.SupportedMediaTypes(), test.MediaType)
if !ok || info.StreamSerializer == nil {
t.Fatal(info)
}
streamSerializer := info.StreamSerializer
r, contentType := protocol.fn(test.Accept)
defer r.Close()
......@@ -306,17 +313,13 @@ func TestWatchRead(t *testing.T) {
if contentType != "__default__" && contentType != test.ExpectedContentType {
t.Errorf("Unexpected content type: %#v", contentType)
}
objectSerializer, ok := api.Codecs.SerializerForMediaType(test.MediaType, nil)
if !ok {
t.Fatal(objectSerializer)
}
objectCodec := api.Codecs.DecoderToVersion(objectSerializer, testInternalGroupVersion)
objectCodec := api.Codecs.DecoderToVersion(info.Serializer, testInternalGroupVersion)
var fr io.ReadCloser = r
if !protocol.selfFraming {
fr = serializer.Framer.NewFrameReader(r)
fr = streamSerializer.Framer.NewFrameReader(r)
}
d := streaming.NewDecoder(fr, serializer)
d := streaming.NewDecoder(fr, streamSerializer.Serializer)
var w *watch.FakeWatcher
for w == nil {
......@@ -568,10 +571,11 @@ func TestWatchHTTPTimeout(t *testing.T) {
timeoutCh := make(chan time.Time)
done := make(chan struct{})
serializer, ok := api.Codecs.StreamingSerializerForMediaType("application/json", nil)
if !ok {
t.Fatal(serializer)
info, ok := runtime.SerializerInfoForMediaType(api.Codecs.SupportedMediaTypes(), runtime.ContentTypeJSON)
if !ok || info.StreamSerializer == nil {
t.Fatal(info)
}
serializer := info.StreamSerializer
// Setup a new watchserver
watchServer := &WatchServer{
......
......@@ -18,6 +18,7 @@ package restclient
import (
"fmt"
"mime"
"net/http"
"net/url"
"os"
......@@ -153,34 +154,48 @@ func readExpBackoffConfig() BackoffManager {
}
// createSerializers creates all necessary serializers for given contentType.
// TODO: the negotiated serializer passed to this method should probably return
// serializers that control decoding and versioning without this package
// being aware of the types. Depends on whether RESTClient must deal with
// generic infrastructure.
func createSerializers(config ContentConfig) (*Serializers, error) {
negotiated := config.NegotiatedSerializer
mediaTypes := config.NegotiatedSerializer.SupportedMediaTypes()
contentType := config.ContentType
info, ok := negotiated.SerializerForMediaType(contentType, nil)
if !ok {
return nil, fmt.Errorf("serializer for %s not registered", contentType)
mediaType, _, err := mime.ParseMediaType(contentType)
if err != nil {
return nil, fmt.Errorf("the content type specified in the client configuration is not recognized: %v", err)
}
streamInfo, ok := negotiated.StreamingSerializerForMediaType(contentType, nil)
info, ok := runtime.SerializerInfoForMediaType(mediaTypes, mediaType)
if !ok {
return nil, fmt.Errorf("streaming serializer for %s not registered", contentType)
if len(contentType) != 0 || len(mediaTypes) == 0 {
return nil, fmt.Errorf("no serializers registered for %s", contentType)
}
info = mediaTypes[0]
}
internalGV := unversioned.GroupVersion{
Group: config.GroupVersion.Group,
Version: runtime.APIVersionInternal,
}
return &Serializers{
Encoder: negotiated.EncoderForVersion(info.Serializer, *config.GroupVersion),
Decoder: negotiated.DecoderToVersion(info.Serializer, internalGV),
StreamingSerializer: streamInfo.Serializer,
Framer: streamInfo.Framer,
s := &Serializers{
Encoder: config.NegotiatedSerializer.EncoderForVersion(info.Serializer, *config.GroupVersion),
Decoder: config.NegotiatedSerializer.DecoderToVersion(info.Serializer, internalGV),
RenegotiatedDecoder: func(contentType string, params map[string]string) (runtime.Decoder, error) {
renegotiated, ok := negotiated.SerializerForMediaType(contentType, params)
info, ok := runtime.SerializerInfoForMediaType(mediaTypes, contentType)
if !ok {
return nil, fmt.Errorf("serializer for %s not registered", contentType)
}
return negotiated.DecoderToVersion(renegotiated.Serializer, internalGV), nil
return config.NegotiatedSerializer.DecoderToVersion(info.Serializer, internalGV), nil
},
}, nil
}
if info.StreamSerializer != nil {
s.StreamingSerializer = info.StreamSerializer.Serializer
s.Framer = info.StreamSerializer.Framer
}
return s, nil
}
// Verb begins a request with a verb (GET, POST, PUT, DELETE).
......
......@@ -153,20 +153,8 @@ var fakeWrapperFunc = func(http.RoundTripper) http.RoundTripper {
type fakeNegotiatedSerializer struct{}
func (n *fakeNegotiatedSerializer) SupportedMediaTypes() []string {
return []string{}
}
func (n *fakeNegotiatedSerializer) SerializerForMediaType(mediaType string, params map[string]string) (s runtime.SerializerInfo, ok bool) {
return runtime.SerializerInfo{}, true
}
func (n *fakeNegotiatedSerializer) SupportedStreamingMediaTypes() []string {
return []string{}
}
func (n *fakeNegotiatedSerializer) StreamingSerializerForMediaType(mediaType string, params map[string]string) (s runtime.StreamSerializerInfo, ok bool) {
return runtime.StreamSerializerInfo{}, true
func (n *fakeNegotiatedSerializer) SupportedMediaTypes() []runtime.SerializerInfo {
return nil
}
func (n *fakeNegotiatedSerializer) EncoderForVersion(serializer runtime.Encoder, gv runtime.GroupVersioner) runtime.Encoder {
......
......@@ -306,10 +306,7 @@ func setDiscoveryDefaults(config *restclient.Config) error {
config.APIPath = ""
config.GroupVersion = nil
codec := runtime.NoopEncoder{Decoder: api.Codecs.UniversalDecoder()}
config.NegotiatedSerializer = serializer.NegotiatedSerializerWrapper(
runtime.SerializerInfo{Serializer: codec},
runtime.StreamSerializerInfo{},
)
config.NegotiatedSerializer = serializer.NegotiatedSerializerWrapper(runtime.SerializerInfo{Serializer: codec})
if len(config.UserAgent) == 0 {
config.UserAgent = restclient.DefaultKubernetesUserAgent()
}
......
......@@ -241,13 +241,22 @@ func (dynamicCodec) Encode(obj runtime.Object, w io.Writer) error {
// ContentConfig returns a restclient.ContentConfig for dynamic types.
func ContentConfig() restclient.ContentConfig {
// TODO: it's questionable that this should be using anything other than unstructured schema and JSON
codec := dynamicCodec{}
streamingInfo, _ := api.Codecs.StreamingSerializerForMediaType("application/json;stream=watch", nil)
var jsonInfo runtime.SerializerInfo
// TODO: api.Codecs here should become "pkg/apis/server/scheme" which is the minimal core you need
// to talk to a kubernetes server
for _, info := range api.Codecs.SupportedMediaTypes() {
if info.MediaType == runtime.ContentTypeJSON {
jsonInfo = info
break
}
}
jsonInfo.Serializer = dynamicCodec{}
jsonInfo.PrettySerializer = nil
return restclient.ContentConfig{
AcceptContentTypes: runtime.ContentTypeJSON,
ContentType: runtime.ContentTypeJSON,
NegotiatedSerializer: serializer.NegotiatedSerializerWrapper(runtime.SerializerInfo{Serializer: codec}, streamingInfo),
NegotiatedSerializer: serializer.NegotiatedSerializerWrapper(jsonInfo),
}
}
......
......@@ -19,12 +19,9 @@ package dynamic
import (
"sync"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/meta"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/client/restclient"
"k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/runtime/serializer"
)
// ClientPool manages a pool of dynamic clients.
......@@ -64,6 +61,7 @@ type clientPoolImpl struct {
// resources or groups.
func NewClientPool(config *restclient.Config, mapper meta.RESTMapper, apiPathResolverFunc APIPathResolverFunc) ClientPool {
confCopy := *config
return &clientPoolImpl{
config: &confCopy,
clients: map[unversioned.GroupVersion]*Client{},
......@@ -108,11 +106,6 @@ func (c *clientPoolImpl) ClientForGroupVersionKind(kind unversioned.GroupVersion
// we need to make a client
conf.GroupVersion = &gv
if conf.NegotiatedSerializer == nil {
streamingInfo, _ := api.Codecs.StreamingSerializerForMediaType("application/json;stream=watch", nil)
conf.NegotiatedSerializer = serializer.NegotiatedSerializerWrapper(runtime.SerializerInfo{Serializer: dynamicCodec{}}, streamingInfo)
}
dynamicClient, err := NewClient(conf)
if err != nil {
return nil, err
......
......@@ -529,6 +529,7 @@ func TestPatch(t *testing.T) {
return
}
w.Header().Set("Content-Type", "application/json")
w.Write(data)
})
if err != nil {
......
......@@ -92,25 +92,25 @@ func (c *RESTClient) request(verb string) *restclient.Request {
GroupVersion: &registered.GroupOrDie(api.GroupName).GroupVersion,
NegotiatedSerializer: c.NegotiatedSerializer,
}
ns := c.NegotiatedSerializer
serializer, _ := ns.SerializerForMediaType(runtime.ContentTypeJSON, nil)
streamingSerializer, _ := ns.StreamingSerializerForMediaType(runtime.ContentTypeJSON, nil)
groupName := api.GroupName
if c.GroupName != "" {
groupName = c.GroupName
}
ns := c.NegotiatedSerializer
info, _ := runtime.SerializerInfoForMediaType(ns.SupportedMediaTypes(), runtime.ContentTypeJSON)
internalVersion := unversioned.GroupVersion{
Group: registered.GroupOrDie(groupName).GroupVersion.Group,
Version: runtime.APIVersionInternal,
}
internalVersion.Version = runtime.APIVersionInternal
serializers := restclient.Serializers{
Encoder: ns.EncoderForVersion(serializer, registered.GroupOrDie(api.GroupName).GroupVersion),
Decoder: ns.DecoderToVersion(serializer, internalVersion),
StreamingSerializer: streamingSerializer,
Framer: streamingSerializer.Framer,
Encoder: ns.EncoderForVersion(info.Serializer, registered.GroupOrDie(api.GroupName).GroupVersion),
Decoder: ns.DecoderToVersion(info.Serializer, internalVersion),
}
if info.StreamSerializer != nil {
serializers.StreamingSerializer = info.StreamSerializer.Serializer
serializers.Framer = info.StreamSerializer.Framer
}
return restclient.NewRequest(c, verb, &url.URL{Host: "localhost"}, "", config, serializers, nil, nil)
}
......
......@@ -94,6 +94,7 @@ func (f *fakeActionHandler) ServeHTTP(response http.ResponseWriter, request *htt
fakeResponse.statusCode = 200
fakeResponse.content = []byte("{\"kind\": \"List\"}")
}
response.Header().Set("Content-Type", "application/json")
response.WriteHeader(fakeResponse.statusCode)
response.Write(fakeResponse.content)
}
......
......@@ -87,11 +87,11 @@ func verfiyMetadata(description string, t *testing.T, in *MetadataOnlyObject) {
func TestDecodeToMetadataOnlyObject(t *testing.T) {
data := getPodJson(t)
cf := serializer.DirectCodecFactory{CodecFactory: NewMetadataCodecFactory()}
serializer, ok := cf.SerializerForMediaType(runtime.ContentTypeJSON, nil)
info, ok := runtime.SerializerInfoForMediaType(cf.SupportedMediaTypes(), runtime.ContentTypeJSON)
if !ok {
t.Fatalf("expected to get a JSON serializer")
}
codec := cf.DecoderToVersion(serializer, unversioned.GroupVersion{Group: "SOMEGROUP", Version: "SOMEVERSION"})
codec := cf.DecoderToVersion(info.Serializer, unversioned.GroupVersion{Group: "SOMEGROUP", Version: "SOMEVERSION"})
// decode with into
into := &MetadataOnlyObject{}
ret, _, err := codec.Decode(data, nil, into)
......@@ -133,11 +133,11 @@ func verifyListMetadata(t *testing.T, metaOnlyList *MetadataOnlyObjectList) {
func TestDecodeToMetadataOnlyObjectList(t *testing.T) {
data := getPodListJson(t)
cf := serializer.DirectCodecFactory{CodecFactory: NewMetadataCodecFactory()}
serializer, ok := cf.SerializerForMediaType(runtime.ContentTypeJSON, nil)
info, ok := runtime.SerializerInfoForMediaType(cf.SupportedMediaTypes(), runtime.ContentTypeJSON)
if !ok {
t.Fatalf("expected to get a JSON serializer")
}
codec := cf.DecoderToVersion(serializer, unversioned.GroupVersion{Group: "SOMEGROUP", Version: "SOMEVERSION"})
codec := cf.DecoderToVersion(info.Serializer, unversioned.GroupVersion{Group: "SOMEGROUP", Version: "SOMEVERSION"})
// decode with into
into := &MetadataOnlyObjectList{}
ret, _, err := codec.Decode(data, nil, into)
......
......@@ -243,11 +243,11 @@ func (s *DefaultStorageFactory) Backends() []string {
// NewStorageCodec assembles a storage codec for the provided storage media type, the provided serializer, and the requested
// storage and memory versions.
func NewStorageCodec(storageMediaType string, ns runtime.StorageSerializer, storageVersion, memoryVersion unversioned.GroupVersion, config storagebackend.Config) (runtime.Codec, error) {
mediaType, options, err := mime.ParseMediaType(storageMediaType)
mediaType, _, err := mime.ParseMediaType(storageMediaType)
if err != nil {
return nil, fmt.Errorf("%q is not a valid mime-type", storageMediaType)
}
serializer, ok := ns.SerializerForMediaType(mediaType, options)
serializer, ok := runtime.SerializerInfoForMediaType(ns.SupportedMediaTypes(), mediaType)
if !ok {
return nil, fmt.Errorf("unable to find serializer for %q", storageMediaType)
}
......
......@@ -193,9 +193,7 @@ func TestGetUnknownSchemaObjectListGeneric(t *testing.T) {
}
f, tf, codec := cmdtesting.NewMixedFactory(regularClient)
negotiatedSerializer := serializer.NegotiatedSerializerWrapper(
runtime.SerializerInfo{Serializer: codec},
runtime.StreamSerializerInfo{})
negotiatedSerializer := serializer.NegotiatedSerializerWrapper(runtime.SerializerInfo{Serializer: codec})
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
NegotiatedSerializer: negotiatedSerializer,
......
......@@ -157,9 +157,7 @@ func NewTestFactory() (cmdutil.Factory, *TestFactory, runtime.Codec, runtime.Neg
Mapper: mapper,
Typer: scheme,
}
negotiatedSerializer := serializer.NegotiatedSerializerWrapper(
runtime.SerializerInfo{Serializer: codec},
runtime.StreamSerializerInfo{})
negotiatedSerializer := serializer.NegotiatedSerializerWrapper(runtime.SerializerInfo{Serializer: codec})
return &FakeFactory{
tf: t,
Codec: codec,
......
......@@ -71,8 +71,8 @@ func TestDecodeSinglePod(t *testing.T) {
}
for _, gv := range registered.EnabledVersionsForGroup(api.GroupName) {
s, _ := api.Codecs.SerializerForFileExtension("yaml")
encoder := api.Codecs.EncoderForVersion(s, gv)
info, _ := runtime.SerializerInfoForMediaType(api.Codecs.SupportedMediaTypes(), "application/yaml")
encoder := api.Codecs.EncoderForVersion(info.Serializer, gv)
yaml, err := runtime.Encode(encoder, pod)
if err != nil {
t.Errorf("unexpected error: %v", err)
......@@ -134,8 +134,8 @@ func TestDecodePodList(t *testing.T) {
}
for _, gv := range registered.EnabledVersionsForGroup(api.GroupName) {
s, _ := api.Codecs.SerializerForFileExtension("yaml")
encoder := api.Codecs.EncoderForVersion(s, gv)
info, _ := runtime.SerializerInfoForMediaType(api.Codecs.SupportedMediaTypes(), "application/yaml")
encoder := api.Codecs.EncoderForVersion(info.Serializer, gv)
yaml, err := runtime.Encode(encoder, podList)
if err != nil {
t.Errorf("unexpected error: %v", err)
......
......@@ -33,7 +33,6 @@ import (
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/apis/extensions/v1beta1"
"k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/util/sets"
"k8s.io/kubernetes/pkg/util/yaml"
"k8s.io/kubernetes/pkg/watch/versioned"
)
......@@ -206,32 +205,13 @@ func NewNegotiatedSerializer(s runtime.NegotiatedSerializer, kind string, encode
}
}
func (t *thirdPartyResourceDataCodecFactory) SupportedMediaTypes() []string {
supported := sets.NewString(t.delegate.SupportedMediaTypes()...)
return supported.Intersection(sets.NewString("application/json", "application/yaml")).List()
}
func (t *thirdPartyResourceDataCodecFactory) SerializerForMediaType(mediaType string, params map[string]string) (runtime.SerializerInfo, bool) {
switch mediaType {
case "application/json", "application/yaml":
return t.delegate.SerializerForMediaType(mediaType, params)
default:
return runtime.SerializerInfo{}, false
}
}
func (t *thirdPartyResourceDataCodecFactory) SupportedStreamingMediaTypes() []string {
supported := sets.NewString(t.delegate.SupportedStreamingMediaTypes()...)
return supported.Intersection(sets.NewString("application/json", "application/json;stream=watch")).List()
}
func (t *thirdPartyResourceDataCodecFactory) StreamingSerializerForMediaType(mediaType string, params map[string]string) (runtime.StreamSerializerInfo, bool) {
switch mediaType {
case "application/json", "application/json;stream=watch":
return t.delegate.StreamingSerializerForMediaType(mediaType, params)
default:
return runtime.StreamSerializerInfo{}, false
func (t *thirdPartyResourceDataCodecFactory) SupportedMediaTypes() []runtime.SerializerInfo {
for _, info := range t.delegate.SupportedMediaTypes() {
if info.MediaType == runtime.ContentTypeJSON {
return []runtime.SerializerInfo{info}
}
}
return nil
}
func (t *thirdPartyResourceDataCodecFactory) EncoderForVersion(s runtime.Encoder, gv runtime.GroupVersioner) runtime.Encoder {
......
......@@ -217,6 +217,22 @@ func (s base64Serializer) Decode(data []byte, defaults *unversioned.GroupVersion
return s.Serializer.Decode(out[:n], defaults, into)
}
// SerializerInfoForMediaType returns the first info in types that has a matching media type (which cannot
// include media-type parameters), or the first info with an empty media type, or false if no type matches.
func SerializerInfoForMediaType(types []SerializerInfo, mediaType string) (SerializerInfo, bool) {
for _, info := range types {
if info.MediaType == mediaType {
return info, true
}
}
for _, info := range types {
if len(info.MediaType) == 0 {
return info, true
}
}
return SerializerInfo{}, false
}
var (
// InternalGroupVersioner will always prefer the internal version for a given group version kind.
InternalGroupVersioner GroupVersioner = internalGroupVersioner{}
......
......@@ -89,20 +89,28 @@ type Framer interface {
// SerializerInfo contains information about a specific serialization format
type SerializerInfo struct {
Serializer
// EncodesAsText indicates this serializer can be encoded to UTF-8 safely.
EncodesAsText bool
// MediaType is the value that represents this serializer over the wire.
MediaType string
// EncodesAsText indicates this serializer can be encoded to UTF-8 safely.
EncodesAsText bool
// Serializer is the individual object serializer for this media type.
Serializer Serializer
// PrettySerializer, if set, can serialize this object in a form biased towards
// readability.
PrettySerializer Serializer
// StreamSerializer, if set, describes the streaming serialization format
// for this media type.
StreamSerializer *StreamSerializerInfo
}
// StreamSerializerInfo contains information about a specific stream serialization format
type StreamSerializerInfo struct {
SerializerInfo
// EncodesAsText indicates this serializer can be encoded to UTF-8 safely.
EncodesAsText bool
// Serializer is the top level object serializer for this type when streaming
Serializer
// Framer is the factory for retrieving streams that separate objects on the wire
Framer
// Embedded is the type of the nested serialization that should be used.
Embedded SerializerInfo
}
// NegotiatedSerializer is an interface used for obtaining encoders, decoders, and serializers
......@@ -110,21 +118,7 @@ type StreamSerializerInfo struct {
// that performs HTTP content negotiation to accept multiple formats.
type NegotiatedSerializer interface {
// SupportedMediaTypes is the media types supported for reading and writing single objects.
SupportedMediaTypes() []string
// SerializerForMediaType returns a serializer for the provided media type. params is the set of
// parameters applied to the media type that may modify the resulting output. ok will be false
// if no serializer matched the media type.
SerializerForMediaType(mediaType string, params map[string]string) (s SerializerInfo, ok bool)
// SupportedStreamingMediaTypes returns the media types of the supported streaming serializers.
// Streaming serializers control how multiple objects are written to a stream output.
SupportedStreamingMediaTypes() []string
// StreamingSerializerForMediaType returns a serializer for the provided media type that supports
// reading and writing multiple objects to a stream. It returns a framer and serializer, or an
// error if no such serializer can be created. Params is the set of parameters applied to the
// media type that may modify the resulting output. ok will be false if no serializer matched
// the media type.
StreamingSerializerForMediaType(mediaType string, params map[string]string) (s StreamSerializerInfo, ok bool)
SupportedMediaTypes() []SerializerInfo
// EncoderForVersion returns an encoder that ensures objects being written to the provided
// serializer are in the provided group version.
......@@ -138,9 +132,8 @@ type NegotiatedSerializer interface {
// that can read and write data at rest. This would commonly be used by client tools that must
// read files, or server side storage interfaces that persist restful objects.
type StorageSerializer interface {
// SerializerForMediaType returns a serializer for the provided media type. Options is a set of
// parameters applied to the media type that may modify the resulting output.
SerializerForMediaType(mediaType string, options map[string]string) (SerializerInfo, bool)
// SupportedMediaTypes are the media types supported for reading and writing objects.
SupportedMediaTypes() []SerializerInfo
// UniversalDeserializer returns a Serializer that can read objects in multiple supported formats
// by introspecting the data at rest.
......
......@@ -84,7 +84,8 @@ func TestScheme(t *testing.T) {
codecs := serializer.NewCodecFactory(scheme)
codec := codecs.LegacyCodec(externalGV)
jsonserializer, _ := codecs.SerializerForFileExtension("json")
info, _ := runtime.SerializerInfoForMediaType(codecs.SupportedMediaTypes(), runtime.ContentTypeJSON)
jsonserializer := info.Serializer
simple := &InternalSimple{
TestString: "foo",
......@@ -150,7 +151,8 @@ func TestScheme(t *testing.T) {
func TestBadJSONRejection(t *testing.T) {
scheme := runtime.NewScheme()
codecs := serializer.NewCodecFactory(scheme)
jsonserializer, _ := codecs.SerializerForFileExtension("json")
info, _ := runtime.SerializerInfoForMediaType(codecs.SupportedMediaTypes(), runtime.ContentTypeJSON)
jsonserializer := info.Serializer
badJSONMissingKind := []byte(`{ }`)
if _, err := runtime.Decode(jsonserializer, badJSONMissingKind); err == nil {
......
......@@ -36,13 +36,6 @@ type serializerType struct {
Serializer runtime.Serializer
PrettySerializer runtime.Serializer
// RawSerializer serializes an object without adding a type wrapper. Some serializers, like JSON
// automatically include identifying type information with the JSON. Others, like Protobuf, need
// a wrapper object that includes type information. This serializer should be set if the serializer
// can serialize / deserialize objects without type info. Note that this serializer will always
// be expected to pass into or a gvk to Decode, since no type information will be available on
// the object itself.
RawSerializer runtime.Serializer
AcceptStreamContentTypes []string
StreamContentType string
......@@ -65,10 +58,8 @@ func newSerializersForScheme(scheme *runtime.Scheme, mf json.MetaFactory) []seri
Serializer: jsonSerializer,
PrettySerializer: jsonPrettySerializer,
AcceptStreamContentTypes: []string{"application/json", "application/json;stream=watch"},
StreamContentType: "application/json",
Framer: json.Framer,
StreamSerializer: jsonSerializer,
Framer: json.Framer,
StreamSerializer: jsonSerializer,
},
{
AcceptContentTypes: []string{"application/yaml"},
......@@ -76,13 +67,6 @@ func newSerializersForScheme(scheme *runtime.Scheme, mf json.MetaFactory) []seri
FileExtensions: []string{"yaml"},
EncodesAsText: true,
Serializer: yamlSerializer,
// TODO: requires runtime.RawExtension to properly distinguish when the nested content is
// yaml, because the yaml encoder invokes MarshalJSON first
//AcceptStreamContentTypes: []string{"application/yaml", "application/yaml;stream=watch"},
//StreamContentType: "application/yaml;stream=watch",
//Framer: json.YAMLFramer,
//StreamSerializer: yamlSerializer,
},
}
......@@ -97,11 +81,10 @@ func newSerializersForScheme(scheme *runtime.Scheme, mf json.MetaFactory) []seri
// CodecFactory provides methods for retrieving codecs and serializers for specific
// versions and content types.
type CodecFactory struct {
scheme *runtime.Scheme
serializers []serializerType
universal runtime.Decoder
accepts []string
streamingAccepts []string
scheme *runtime.Scheme
serializers []serializerType
universal runtime.Decoder
accepts []runtime.SerializerInfo
legacySerializer runtime.Serializer
}
......@@ -120,7 +103,7 @@ func NewCodecFactory(scheme *runtime.Scheme) CodecFactory {
// newCodecFactory is a helper for testing that allows a different metafactory to be specified.
func newCodecFactory(scheme *runtime.Scheme, serializers []serializerType) CodecFactory {
decoders := make([]runtime.Decoder, 0, len(serializers))
accepts := []string{}
var accepts []runtime.SerializerInfo
alreadyAccepted := make(map[string]struct{})
var legacySerializer runtime.Serializer
......@@ -131,8 +114,21 @@ func newCodecFactory(scheme *runtime.Scheme, serializers []serializerType) Codec
continue
}
alreadyAccepted[mediaType] = struct{}{}
accepts = append(accepts, mediaType)
if mediaType == "application/json" {
info := runtime.SerializerInfo{
MediaType: d.ContentType,
EncodesAsText: d.EncodesAsText,
Serializer: d.Serializer,
PrettySerializer: d.PrettySerializer,
}
if d.StreamSerializer != nil {
info.StreamSerializer = &runtime.StreamSerializerInfo{
Serializer: d.StreamSerializer,
EncodesAsText: d.EncodesAsText,
Framer: d.Framer,
}
}
accepts = append(accepts, info)
if mediaType == runtime.ContentTypeJSON {
legacySerializer = d.Serializer
}
}
......@@ -141,45 +137,22 @@ func newCodecFactory(scheme *runtime.Scheme, serializers []serializerType) Codec
legacySerializer = serializers[0].Serializer
}
streamAccepts := []string{}
alreadyAccepted = make(map[string]struct{})
for _, d := range serializers {
if len(d.StreamContentType) == 0 {
continue
}
for _, mediaType := range d.AcceptStreamContentTypes {
if _, ok := alreadyAccepted[mediaType]; ok {
continue
}
alreadyAccepted[mediaType] = struct{}{}
streamAccepts = append(streamAccepts, mediaType)
}
}
return CodecFactory{
scheme: scheme,
serializers: serializers,
universal: recognizer.NewDecoder(decoders...),
accepts: accepts,
streamingAccepts: streamAccepts,
accepts: accepts,
legacySerializer: legacySerializer,
}
}
var _ runtime.NegotiatedSerializer = &CodecFactory{}
// SupportedMediaTypes returns the RFC2046 media types that this factory has serializers for.
func (f CodecFactory) SupportedMediaTypes() []string {
func (f CodecFactory) SupportedMediaTypes() []runtime.SerializerInfo {
return f.accepts
}
// SupportedStreamingMediaTypes returns the RFC2046 media types that this factory has stream serializers for.
func (f CodecFactory) SupportedStreamingMediaTypes() []string {
return f.streamingAccepts
}
// LegacyCodec encodes output to a given API versions, and decodes output into the internal form from
// any recognized source. The returned codec will always encode output to JSON. If a type is not
// found in the list of versions an error will be returned.
......@@ -242,64 +215,6 @@ func (f CodecFactory) EncoderForVersion(encoder runtime.Encoder, gv runtime.Grou
return f.CodecForVersions(encoder, nil, gv, nil)
}
// SerializerForMediaType returns a serializer that matches the provided RFC2046 mediaType, or false if no such
// serializer exists
func (f CodecFactory) SerializerForMediaType(mediaType string, params map[string]string) (runtime.SerializerInfo, bool) {
for _, s := range f.serializers {
for _, accepted := range s.AcceptContentTypes {
if accepted == mediaType {
// legacy support for ?pretty=1 continues, but this is more formally defined
if v, ok := params["pretty"]; ok && v == "1" && s.PrettySerializer != nil {
return runtime.SerializerInfo{Serializer: s.PrettySerializer, MediaType: s.ContentType, EncodesAsText: s.EncodesAsText}, true
}
// return the base variant
return runtime.SerializerInfo{Serializer: s.Serializer, MediaType: s.ContentType, EncodesAsText: s.EncodesAsText}, true
}
}
}
return runtime.SerializerInfo{}, false
}
// StreamingSerializerForMediaType returns a serializer that matches the provided RFC2046 mediaType, or false if no such
// serializer exists
func (f CodecFactory) StreamingSerializerForMediaType(mediaType string, params map[string]string) (runtime.StreamSerializerInfo, bool) {
for _, s := range f.serializers {
for _, accepted := range s.AcceptStreamContentTypes {
if accepted == mediaType {
// TODO: accept params
nested, ok := f.SerializerForMediaType(s.ContentType, nil)
if !ok {
panic("no serializer defined for internal content type")
}
return runtime.StreamSerializerInfo{
SerializerInfo: runtime.SerializerInfo{
Serializer: s.StreamSerializer,
MediaType: s.StreamContentType,
EncodesAsText: s.EncodesAsText,
},
Framer: s.Framer,
Embedded: nested,
}, true
}
}
}
return runtime.StreamSerializerInfo{}, false
}
// SerializerForFileExtension returns a serializer for the provided extension, or false if no serializer matches.
func (f CodecFactory) SerializerForFileExtension(extension string) (runtime.Serializer, bool) {
for _, s := range f.serializers {
for _, ext := range s.FileExtensions {
if extension == ext {
return s.Serializer, true
}
}
}
return nil, false
}
// DirectCodecFactory provides methods for retrieving "DirectCodec"s, which do not do conversion.
type DirectCodecFactory struct {
CodecFactory
......
......@@ -252,7 +252,8 @@ func TestTypes(t *testing.T) {
func TestVersionedEncoding(t *testing.T) {
s, _ := GetTestScheme()
cf := newCodecFactory(s, newSerializersForScheme(s, testMetaFactory{}))
encoder, _ := cf.SerializerForFileExtension("json")
info, _ := runtime.SerializerInfoForMediaType(cf.SupportedMediaTypes(), runtime.ContentTypeJSON)
encoder := info.Serializer
codec := cf.CodecForVersions(encoder, nil, unversioned.GroupVersion{Version: "v2"}, nil)
out, err := runtime.Encode(codec, &TestType1{})
......@@ -415,7 +416,8 @@ func GetDirectCodecTestScheme() *runtime.Scheme {
func TestDirectCodec(t *testing.T) {
s := GetDirectCodecTestScheme()
cf := newCodecFactory(s, newSerializersForScheme(s, testMetaFactory{}))
serializer, _ := cf.SerializerForFileExtension("json")
info, _ := runtime.SerializerInfoForMediaType(cf.SupportedMediaTypes(), runtime.ContentTypeJSON)
serializer := info.Serializer
df := DirectCodecFactory{cf}
ignoredGV, err := unversioned.ParseGroupVersion("ignored group/ignored version")
if err != nil {
......
......@@ -20,31 +20,18 @@ import (
"k8s.io/kubernetes/pkg/runtime"
)
// TODO: We should figure out what happens when someone asks
// encoder for version and it conflicts with the raw serializer.
// TODO: We should split negotiated serializers that we can change versions on from those we can change
// serialization formats on
type negotiatedSerializerWrapper struct {
info runtime.SerializerInfo
streamInfo runtime.StreamSerializerInfo
info runtime.SerializerInfo
}
func NegotiatedSerializerWrapper(info runtime.SerializerInfo, streamInfo runtime.StreamSerializerInfo) runtime.NegotiatedSerializer {
return &negotiatedSerializerWrapper{info, streamInfo}
func NegotiatedSerializerWrapper(info runtime.SerializerInfo) runtime.NegotiatedSerializer {
return &negotiatedSerializerWrapper{info}
}
func (n *negotiatedSerializerWrapper) SupportedMediaTypes() []string {
return []string{}
}
func (n *negotiatedSerializerWrapper) SerializerForMediaType(mediaType string, options map[string]string) (runtime.SerializerInfo, bool) {
return n.info, true
}
func (n *negotiatedSerializerWrapper) SupportedStreamingMediaTypes() []string {
return []string{}
}
func (n *negotiatedSerializerWrapper) StreamingSerializerForMediaType(mediaType string, options map[string]string) (runtime.StreamSerializerInfo, bool) {
return n.streamInfo, true
func (n *negotiatedSerializerWrapper) SupportedMediaTypes() []runtime.SerializerInfo {
return []runtime.SerializerInfo{n.info}
}
func (n *negotiatedSerializerWrapper) EncoderForVersion(e runtime.Encoder, _ runtime.GroupVersioner) runtime.Encoder {
......
......@@ -26,8 +26,7 @@ const (
// depending on it unintentionally.
// TODO: potentially move to pkg/api (since it's part of the Kube public API) and pass it in to the
// CodecFactory on initialization.
contentTypeProtobuf = "application/vnd.kubernetes.protobuf"
contentTypeProtobufWatch = contentTypeProtobuf + ";stream=watch"
contentTypeProtobuf = "application/vnd.kubernetes.protobuf"
)
func protobufSerializer(scheme *runtime.Scheme) (serializerType, bool) {
......@@ -38,12 +37,9 @@ func protobufSerializer(scheme *runtime.Scheme) (serializerType, bool) {
ContentType: contentTypeProtobuf,
FileExtensions: []string{"pb"},
Serializer: serializer,
RawSerializer: raw,
AcceptStreamContentTypes: []string{contentTypeProtobuf, contentTypeProtobufWatch},
StreamContentType: contentTypeProtobufWatch,
Framer: protobuf.LengthDelimitedFramer,
StreamSerializer: raw,
Framer: protobuf.LengthDelimitedFramer,
StreamSerializer: raw,
}, true
}
......
......@@ -56,10 +56,7 @@ func NewGenericWebhook(kubeConfigFile string, groupVersions []unversioned.GroupV
return nil, err
}
codec := api.Codecs.LegacyCodec(groupVersions...)
clientConfig.ContentConfig.NegotiatedSerializer = runtimeserializer.NegotiatedSerializerWrapper(
runtime.SerializerInfo{Serializer: codec},
runtime.StreamSerializerInfo{},
)
clientConfig.ContentConfig.NegotiatedSerializer = runtimeserializer.NegotiatedSerializerWrapper(runtime.SerializerInfo{Serializer: codec})
restClient, err := restclient.UnversionedRESTClientFor(clientConfig)
if err != nil {
......
......@@ -303,45 +303,46 @@ func NewMasterConfig() *master.Config {
Prefix: uuid.New(),
}
negotiatedSerializer := NewSingleContentTypeSerializer(api.Scheme, testapi.Default.Codec(), runtime.ContentTypeJSON)
info, _ := runtime.SerializerInfoForMediaType(api.Codecs.SupportedMediaTypes(), runtime.ContentTypeJSON)
ns := NewSingleContentTypeSerializer(api.Scheme, info)
storageFactory := genericapiserver.NewDefaultStorageFactory(config, runtime.ContentTypeJSON, negotiatedSerializer, genericapiserver.NewDefaultResourceEncodingConfig(), master.DefaultAPIResourceConfigSource())
storageFactory := genericapiserver.NewDefaultStorageFactory(config, runtime.ContentTypeJSON, ns, genericapiserver.NewDefaultResourceEncodingConfig(), master.DefaultAPIResourceConfigSource())
storageFactory.SetSerializer(
unversioned.GroupResource{Group: api.GroupName, Resource: genericapiserver.AllResources},
"",
NewSingleContentTypeSerializer(api.Scheme, testapi.Default.Codec(), runtime.ContentTypeJSON))
ns)
storageFactory.SetSerializer(
unversioned.GroupResource{Group: autoscaling.GroupName, Resource: genericapiserver.AllResources},
"",
NewSingleContentTypeSerializer(api.Scheme, testapi.Autoscaling.Codec(), runtime.ContentTypeJSON))
ns)
storageFactory.SetSerializer(
unversioned.GroupResource{Group: batch.GroupName, Resource: genericapiserver.AllResources},
"",
NewSingleContentTypeSerializer(api.Scheme, testapi.Batch.Codec(), runtime.ContentTypeJSON))
ns)
storageFactory.SetSerializer(
unversioned.GroupResource{Group: apps.GroupName, Resource: genericapiserver.AllResources},
"",
NewSingleContentTypeSerializer(api.Scheme, testapi.Apps.Codec(), runtime.ContentTypeJSON))
ns)
storageFactory.SetSerializer(
unversioned.GroupResource{Group: extensions.GroupName, Resource: genericapiserver.AllResources},
"",
NewSingleContentTypeSerializer(api.Scheme, testapi.Extensions.Codec(), runtime.ContentTypeJSON))
ns)
storageFactory.SetSerializer(
unversioned.GroupResource{Group: policy.GroupName, Resource: genericapiserver.AllResources},
"",
NewSingleContentTypeSerializer(api.Scheme, testapi.Policy.Codec(), runtime.ContentTypeJSON))
ns)
storageFactory.SetSerializer(
unversioned.GroupResource{Group: rbac.GroupName, Resource: genericapiserver.AllResources},
"",
NewSingleContentTypeSerializer(api.Scheme, testapi.Rbac.Codec(), runtime.ContentTypeJSON))
ns)
storageFactory.SetSerializer(
unversioned.GroupResource{Group: certificates.GroupName, Resource: genericapiserver.AllResources},
"",
NewSingleContentTypeSerializer(api.Scheme, testapi.Certificates.Codec(), runtime.ContentTypeJSON))
ns)
storageFactory.SetSerializer(
unversioned.GroupResource{Group: storage.GroupName, Resource: genericapiserver.AllResources},
"",
NewSingleContentTypeSerializer(api.Scheme, testapi.Storage.Codec(), runtime.ContentTypeJSON))
ns)
genericConfig := genericapiserver.NewConfig()
kubeVersion := version.Get()
......
......@@ -22,39 +22,26 @@ import (
)
// NewSingleContentTypeSerializer wraps a serializer in a NegotiatedSerializer that handles one content type
func NewSingleContentTypeSerializer(scheme *runtime.Scheme, serializer runtime.Serializer, contentType string) runtime.StorageSerializer {
func NewSingleContentTypeSerializer(scheme *runtime.Scheme, info runtime.SerializerInfo) runtime.StorageSerializer {
return &wrappedSerializer{
scheme: scheme,
serializer: serializer,
contentType: contentType,
scheme: scheme,
info: info,
}
}
type wrappedSerializer struct {
scheme *runtime.Scheme
serializer runtime.Serializer
contentType string
scheme *runtime.Scheme
info runtime.SerializerInfo
}
var _ runtime.StorageSerializer = &wrappedSerializer{}
func (s *wrappedSerializer) SupportedMediaTypes() []string {
return []string{s.contentType}
}
func (s *wrappedSerializer) SerializerForMediaType(mediaType string, options map[string]string) (runtime.SerializerInfo, bool) {
if mediaType != s.contentType {
return runtime.SerializerInfo{}, false
}
return runtime.SerializerInfo{
Serializer: s.serializer,
MediaType: mediaType,
EncodesAsText: true, // TODO: this should be parameterized
}, true
func (s *wrappedSerializer) SupportedMediaTypes() []runtime.SerializerInfo {
return []runtime.SerializerInfo{s.info}
}
func (s *wrappedSerializer) UniversalDeserializer() runtime.Decoder {
return s.serializer
return s.info.Serializer
}
func (s *wrappedSerializer) EncoderForVersion(encoder runtime.Encoder, gv runtime.GroupVersioner) runtime.Encoder {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment