Merge pull request #24710 from smarterclayton/store_proto_in_etcd

Automatic merge from submit-queue

Allow etcd to store protobuf objects

Split storage serialization from client negotiation, and allow API server to take flag controlling serialization.

TODO:

* [x] API server still doesn't start - range allocation object doesn't seem to round trip correctly to etcd
* [ ] Verify that third party resources are ignoring protobuf (add a test)
* [ ] Add integration tests that verify storage is correctly protobuf
* [ ] Add a global default for which storage format to prefer?
This commit is contained in:
k8s-merge-robot 2016-05-05 19:00:20 -07:00
commit ab36e0e35e
52 changed files with 651 additions and 280 deletions

View File

@ -44,6 +44,7 @@ type APIServer struct {
AuthorizationMode string AuthorizationMode string
AuthorizationConfig apiserver.AuthorizationConfig AuthorizationConfig apiserver.AuthorizationConfig
BasicAuthFile string BasicAuthFile string
DefaultStorageMediaType string
DeleteCollectionWorkers int DeleteCollectionWorkers int
DeprecatedStorageVersion string DeprecatedStorageVersion string
EtcdServersOverrides []string EtcdServersOverrides []string
@ -76,6 +77,7 @@ func NewAPIServer() *APIServer {
ServerRunOptions: genericapiserver.NewServerRunOptions(), ServerRunOptions: genericapiserver.NewServerRunOptions(),
AdmissionControl: "AlwaysAdmit", AdmissionControl: "AlwaysAdmit",
AuthorizationMode: "AlwaysAllow", AuthorizationMode: "AlwaysAllow",
DefaultStorageMediaType: "application/json",
DeleteCollectionWorkers: 1, DeleteCollectionWorkers: 1,
EventTTL: 1 * time.Hour, EventTTL: 1 * time.Hour,
MasterServiceNamespace: api.NamespaceDefault, MasterServiceNamespace: api.NamespaceDefault,
@ -153,6 +155,7 @@ func (s *APIServer) AddFlags(fs *pflag.FlagSet) {
"In the case where objects are moved from one group to the other, you may specify the format \"group1=group2/v1beta1,group3/v1beta1,...\". "+ "In the case where objects are moved from one group to the other, you may specify the format \"group1=group2/v1beta1,group3/v1beta1,...\". "+
"You only need to pass the groups you wish to change from the defaults. "+ "You only need to pass the groups you wish to change from the defaults. "+
"It defaults to a list of preferred versions of all registered groups, which is derived from the KUBE_API_VERSIONS environment variable.") "It defaults to a list of preferred versions of all registered groups, which is derived from the KUBE_API_VERSIONS environment variable.")
fs.StringVar(&s.DefaultStorageMediaType, "storage-media-type", s.DefaultStorageMediaType, "The media type to use to store objects in storage. Defaults to application/json. Some resources may only support a specific media type and will ignore this setting.")
fs.DurationVar(&s.EventTTL, "event-ttl", s.EventTTL, "Amount of time to retain events. Default 1 hour.") fs.DurationVar(&s.EventTTL, "event-ttl", s.EventTTL, "Amount of time to retain events. Default 1 hour.")
fs.StringVar(&s.BasicAuthFile, "basic-auth-file", s.BasicAuthFile, "If set, the file that will be used to admit requests to the secure port of the API server via http basic authentication.") fs.StringVar(&s.BasicAuthFile, "basic-auth-file", s.BasicAuthFile, "If set, the file that will be used to admit requests to the secure port of the API server via http basic authentication.")
fs.StringVar(&s.TokenAuthFile, "token-auth-file", s.TokenAuthFile, "If set, the file that will be used to secure the secure port of the API server via token authentication.") fs.StringVar(&s.TokenAuthFile, "token-auth-file", s.TokenAuthFile, "If set, the file that will be used to secure the secure port of the API server via token authentication.")

View File

@ -167,7 +167,9 @@ func Run(s *options.APIServer) error {
resourceEncoding.SetVersionEncoding(group, storageEncodingVersion, unversioned.GroupVersion{Group: group, Version: runtime.APIVersionInternal}) resourceEncoding.SetVersionEncoding(group, storageEncodingVersion, unversioned.GroupVersion{Group: group, Version: runtime.APIVersionInternal})
} }
storageFactory := genericapiserver.NewDefaultStorageFactory(s.StorageConfig, api.Codecs, resourceEncoding, apiResourceConfigSource) storageFactory := genericapiserver.NewDefaultStorageFactory(s.StorageConfig, s.DefaultStorageMediaType, api.Codecs, resourceEncoding, apiResourceConfigSource)
// third party resources are always serialized to storage using JSON
storageFactory.SetSerializer(extensions.Resource("thirdpartyresources"), "application/json", nil)
storageFactory.AddCohabitatingResources(batch.Resource("jobs"), extensions.Resource("jobs")) storageFactory.AddCohabitatingResources(batch.Resource("jobs"), extensions.Resource("jobs"))
storageFactory.AddCohabitatingResources(autoscaling.Resource("horizontalpodautoscalers"), extensions.Resource("horizontalpodautoscalers")) storageFactory.AddCohabitatingResources(autoscaling.Resource("horizontalpodautoscalers"), extensions.Resource("horizontalpodautoscalers"))
for _, override := range s.EtcdServersOverrides { for _, override := range s.EtcdServersOverrides {

View File

@ -110,6 +110,7 @@ kube-apiserver
--ssh-keyfile="": If non-empty, use secure SSH proxy to the nodes, using this user keyfile --ssh-keyfile="": If non-empty, use secure SSH proxy to the nodes, using this user keyfile
--ssh-user="": If non-empty, use secure SSH proxy to the nodes, using this user name --ssh-user="": If non-empty, use secure SSH proxy to the nodes, using this user name
--storage-backend="": The storage backend for persistence. Options: 'etcd2' (default), 'etcd3'. --storage-backend="": The storage backend for persistence. Options: 'etcd2' (default), 'etcd3'.
--storage-media-type="application/json": The media type to use to store objects in storage. Defaults to application/json. Some resources may only support a specific media type and will ignore this setting.
--storage-versions="apps/v1alpha1,authorization.k8s.io/v1beta1,autoscaling/v1,batch/v1,componentconfig/v1alpha1,extensions/v1beta1,metrics/v1alpha1,v1": The per-group version to store resources in. Specified in the format "group1/version1,group2/version2,...". In the case where objects are moved from one group to the other, you may specify the format "group1=group2/v1beta1,group3/v1beta1,...". You only need to pass the groups you wish to change from the defaults. It defaults to a list of preferred versions of all registered groups, which is derived from the KUBE_API_VERSIONS environment variable. --storage-versions="apps/v1alpha1,authorization.k8s.io/v1beta1,autoscaling/v1,batch/v1,componentconfig/v1alpha1,extensions/v1beta1,metrics/v1alpha1,v1": The per-group version to store resources in. Specified in the format "group1/version1,group2/version2,...". In the case where objects are moved from one group to the other, you may specify the format "group1=group2/v1beta1,group3/v1beta1,...". You only need to pass the groups you wish to change from the defaults. It defaults to a list of preferred versions of all registered groups, which is derived from the KUBE_API_VERSIONS environment variable.
--tls-cert-file="": File containing x509 Certificate for HTTPS. (CA cert, if any, concatenated after server cert). If HTTPS serving is enabled, and --tls-cert-file and --tls-private-key-file are not provided, a self-signed certificate and key are generated for the public address and saved to /var/run/kubernetes. --tls-cert-file="": File containing x509 Certificate for HTTPS. (CA cert, if any, concatenated after server cert). If HTTPS serving is enabled, and --tls-cert-file and --tls-private-key-file are not provided, a self-signed certificate and key are generated for the public address and saved to /var/run/kubernetes.
--tls-private-key-file="": File containing x509 private key matching --tls-cert-file. --tls-private-key-file="": File containing x509 private key matching --tls-cert-file.
@ -118,7 +119,7 @@ kube-apiserver
--watch-cache-sizes=[]: List of watch cache sizes for every resource (pods, nodes, etc.), comma separated. The individual override format: resource#size, where size is a number. It takes effect when watch-cache is enabled. --watch-cache-sizes=[]: List of watch cache sizes for every resource (pods, nodes, etc.), comma separated. The individual override format: resource#size, where size is a number. It takes effect when watch-cache is enabled.
``` ```
###### Auto generated by spf13/cobra on 28-Apr-2016 ###### Auto generated by spf13/cobra on 30-Apr-2016
<!-- BEGIN MUNGE: GENERATED_ANALYTICS --> <!-- BEGIN MUNGE: GENERATED_ANALYTICS -->

View File

@ -45,7 +45,7 @@ func newStorageFactory() genericapiserver.StorageFactory {
Prefix: genericapiserver.DefaultEtcdPathPrefix, Prefix: genericapiserver.DefaultEtcdPathPrefix,
ServerList: []string{"http://127.0.0.1:4001"}, ServerList: []string{"http://127.0.0.1:4001"},
} }
storageFactory := genericapiserver.NewDefaultStorageFactory(config, api.Codecs, genericapiserver.NewDefaultResourceEncodingConfig(), genericapiserver.NewResourceConfig()) storageFactory := genericapiserver.NewDefaultStorageFactory(config, "application/json", api.Codecs, genericapiserver.NewDefaultResourceEncodingConfig(), genericapiserver.NewResourceConfig())
return storageFactory return storageFactory
} }

View File

@ -44,6 +44,7 @@ type APIServer struct {
AuthorizationMode string AuthorizationMode string
AuthorizationConfig apiserver.AuthorizationConfig AuthorizationConfig apiserver.AuthorizationConfig
BasicAuthFile string BasicAuthFile string
DefaultStorageMediaType string
DeleteCollectionWorkers int DeleteCollectionWorkers int
DeprecatedStorageVersion string DeprecatedStorageVersion string
EtcdServersOverrides []string EtcdServersOverrides []string
@ -76,6 +77,7 @@ func NewAPIServer() *APIServer {
ServerRunOptions: genericapiserver.NewServerRunOptions(), ServerRunOptions: genericapiserver.NewServerRunOptions(),
AdmissionControl: "AlwaysAdmit", AdmissionControl: "AlwaysAdmit",
AuthorizationMode: "AlwaysAllow", AuthorizationMode: "AlwaysAllow",
DefaultStorageMediaType: "application/json",
DeleteCollectionWorkers: 1, DeleteCollectionWorkers: 1,
EventTTL: 1 * time.Hour, EventTTL: 1 * time.Hour,
MasterServiceNamespace: api.NamespaceDefault, MasterServiceNamespace: api.NamespaceDefault,
@ -153,6 +155,7 @@ func (s *APIServer) AddFlags(fs *pflag.FlagSet) {
"In the case where objects are moved from one group to the other, you may specify the format \"group1=group2/v1beta1,group3/v1beta1,...\". "+ "In the case where objects are moved from one group to the other, you may specify the format \"group1=group2/v1beta1,group3/v1beta1,...\". "+
"You only need to pass the groups you wish to change from the defaults. "+ "You only need to pass the groups you wish to change from the defaults. "+
"It defaults to a list of preferred versions of all registered groups, which is derived from the KUBE_API_VERSIONS environment variable.") "It defaults to a list of preferred versions of all registered groups, which is derived from the KUBE_API_VERSIONS environment variable.")
fs.StringVar(&s.DefaultStorageMediaType, "storage-media-type", s.DefaultStorageMediaType, "The media type to use to store objects in storage. Defaults to application/json. Some resources may only support a specific media type and will ignore this setting.")
fs.DurationVar(&s.EventTTL, "event-ttl", s.EventTTL, "Amount of time to retain events. Default 1 hour.") fs.DurationVar(&s.EventTTL, "event-ttl", s.EventTTL, "Amount of time to retain events. Default 1 hour.")
fs.StringVar(&s.BasicAuthFile, "basic-auth-file", s.BasicAuthFile, "If set, the file that will be used to admit requests to the secure port of the API server via http basic authentication.") fs.StringVar(&s.BasicAuthFile, "basic-auth-file", s.BasicAuthFile, "If set, the file that will be used to admit requests to the secure port of the API server via http basic authentication.")
fs.StringVar(&s.TokenAuthFile, "token-auth-file", s.TokenAuthFile, "If set, the file that will be used to secure the secure port of the API server via token authentication.") fs.StringVar(&s.TokenAuthFile, "token-auth-file", s.TokenAuthFile, "If set, the file that will be used to secure the secure port of the API server via token authentication.")

View File

@ -76,7 +76,7 @@ func Run(s *options.APIServer) error {
resourceEncoding.SetVersionEncoding(group, storageEncodingVersion, unversioned.GroupVersion{Group: group, Version: runtime.APIVersionInternal}) resourceEncoding.SetVersionEncoding(group, storageEncodingVersion, unversioned.GroupVersion{Group: group, Version: runtime.APIVersionInternal})
} }
storageFactory := genericapiserver.NewDefaultStorageFactory(s.StorageConfig, api.Codecs, resourceEncoding, apiResourceConfigSource) storageFactory := genericapiserver.NewDefaultStorageFactory(s.StorageConfig, s.DefaultStorageMediaType, api.Codecs, resourceEncoding, apiResourceConfigSource)
for _, override := range s.EtcdServersOverrides { for _, override := range s.EtcdServersOverrides {
tokens := strings.Split(override, "#") tokens := strings.Split(override, "#")
if len(tokens) != 2 { if len(tokens) != 2 {

View File

@ -192,6 +192,7 @@ ADMISSION_CONTROL="NamespaceLifecycle,LimitRanger,ResourceQuota"
--public-address-override="127.0.0.1" \ --public-address-override="127.0.0.1" \
--kubelet-port=${KUBELET_PORT} \ --kubelet-port=${KUBELET_PORT} \
--runtime-config=api/v1 \ --runtime-config=api/v1 \
--storage-media-type="${KUBE_TEST_API_STORAGE_TYPE-}" \
--cert-dir="${TMPDIR:-/tmp/}" \ --cert-dir="${TMPDIR:-/tmp/}" \
--service-cluster-ip-range="10.0.0.0/24" 1>&2 & --service-cluster-ip-range="10.0.0.0/24" 1>&2 &
APISERVER_PID=$! APISERVER_PID=$!
@ -202,6 +203,7 @@ kube::util::wait_for_url "http://127.0.0.1:${API_PORT}/healthz" "apiserver"
kube::log::status "Starting controller-manager" kube::log::status "Starting controller-manager"
"${KUBE_OUTPUT_HOSTBIN}/kube-controller-manager" \ "${KUBE_OUTPUT_HOSTBIN}/kube-controller-manager" \
--port="${CTLRMGR_PORT}" \ --port="${CTLRMGR_PORT}" \
--kube-api-content-type="${KUBE_TEST_API_TYPE-}" \
--master="127.0.0.1:${API_PORT}" 1>&2 & --master="127.0.0.1:${API_PORT}" 1>&2 &
CTLRMGR_PID=$! CTLRMGR_PID=$!

View File

@ -394,6 +394,7 @@ static-pods-config
stats-port stats-port
stop-services stop-services
storage-backend storage-backend
storage-media-type
storage-version storage-version
storage-versions storage-versions
streaming-connection-idle-timeout streaming-connection-idle-timeout

View File

@ -17,6 +17,7 @@ limitations under the License.
package api_test package api_test
import ( import (
"bytes"
"encoding/hex" "encoding/hex"
"math/rand" "math/rand"
"testing" "testing"
@ -37,10 +38,32 @@ import (
func init() { func init() {
codecsToTest = append(codecsToTest, func(version unversioned.GroupVersion, item runtime.Object) (runtime.Codec, error) { codecsToTest = append(codecsToTest, func(version unversioned.GroupVersion, item runtime.Object) (runtime.Codec, error) {
s := protobuf.NewSerializer(api.Scheme, runtime.ObjectTyperToTyper(api.Scheme), "application/arbitrary.content.type") s := protobuf.NewSerializer(api.Scheme, runtime.ObjectTyperToTyper(api.Scheme), "application/arbitrary.content.type")
return api.Codecs.CodecForVersions(s, testapi.ExternalGroupVersions(), nil), nil return api.Codecs.CodecForVersions(s, s, testapi.ExternalGroupVersions(), nil), nil
}) })
} }
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)
if !ok {
t.Fatal(mediaType)
}
buf := &bytes.Buffer{}
if err := e.EncodeToStream(expected, buf); err != nil {
t.Fatalf("%s: %v", mediaType, err)
}
obj, _, err := d.Decode(buf.Bytes(), &unversioned.GroupVersionKind{Kind: "Pod", Version: "v1"}, nil)
if err != nil {
t.Fatalf("%s: %v", mediaType, err)
}
if !api.Semantic.DeepEqual(expected, obj) {
t.Fatalf("%s: %#v", mediaType, obj)
}
}
}
func TestProtobufRoundTrip(t *testing.T) { func TestProtobufRoundTrip(t *testing.T) {
obj := &v1.Pod{} obj := &v1.Pod{}
apitesting.FuzzerFor(t, v1.SchemeGroupVersion, rand.NewSource(benchmarkSeed)).Fuzz(obj) apitesting.FuzzerFor(t, v1.SchemeGroupVersion, rand.NewSource(benchmarkSeed)).Fuzz(obj)

View File

@ -284,18 +284,14 @@ func TestObjectWatchFraming(t *testing.T) {
converted, _ := api.Scheme.ConvertToVersion(secret, "v1") converted, _ := api.Scheme.ConvertToVersion(secret, "v1")
v1secret := converted.(*v1.Secret) v1secret := converted.(*v1.Secret)
for _, streamingMediaType := range api.Codecs.SupportedStreamingMediaTypes() { for _, streamingMediaType := range api.Codecs.SupportedStreamingMediaTypes() {
s, framer, mediaType, _ := api.Codecs.StreamingSerializerForMediaType(streamingMediaType, nil) s, _ := api.Codecs.StreamingSerializerForMediaType(streamingMediaType, nil)
// TODO: remove this when the runtime.SerializerInfo PR lands framer := s.Framer
if mediaType == "application/vnd.kubernetes.protobuf;stream=watch" { embedded := s.Embedded.Serializer
mediaType = "application/vnd.kubernetes.protobuf" if embedded == nil {
} t.Errorf("no embedded serializer for %s", streamingMediaType)
embedded, ok := api.Codecs.SerializerForMediaType(mediaType, nil) continue
if !ok {
t.Logf("no embedded serializer for %s", mediaType)
embedded = s
} }
innerDecode := api.Codecs.DecoderToVersion(embedded, api.SchemeGroupVersion) innerDecode := api.Codecs.DecoderToVersion(embedded, api.SchemeGroupVersion)
//innerEncode := api.Codecs.EncoderForVersion(embedded, api.SchemeGroupVersion)
// write a single object through the framer and back out // write a single object through the framer and back out
obj := &bytes.Buffer{} obj := &bytes.Buffer{}

View File

@ -19,6 +19,7 @@ package testapi
import ( import (
"fmt" "fmt"
"mime"
"os" "os"
"reflect" "reflect"
"strings" "strings"
@ -33,6 +34,7 @@ import (
"k8s.io/kubernetes/pkg/apis/batch" "k8s.io/kubernetes/pkg/apis/batch"
"k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/runtime" "k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/runtime/serializer/recognizer"
_ "k8s.io/kubernetes/federation/apis/federation/install" _ "k8s.io/kubernetes/federation/apis/federation/install"
_ "k8s.io/kubernetes/pkg/api/install" _ "k8s.io/kubernetes/pkg/api/install"
@ -45,14 +47,16 @@ import (
) )
var ( var (
Groups = make(map[string]TestGroup) Groups = make(map[string]TestGroup)
Default TestGroup Default TestGroup
Autoscaling TestGroup Autoscaling TestGroup
Batch TestGroup Batch TestGroup
Extensions TestGroup Extensions TestGroup
Apps TestGroup Apps TestGroup
Federation TestGroup Federation TestGroup
NegotiatedSerializer = api.Codecs
serializer runtime.SerializerInfo
storageSerializer runtime.SerializerInfo
) )
type TestGroup struct { type TestGroup struct {
@ -62,6 +66,30 @@ type TestGroup struct {
} }
func init() { func init() {
if apiMediaType := os.Getenv("KUBE_TEST_API_TYPE"); len(apiMediaType) > 0 {
var ok bool
mediaType, options, err := mime.ParseMediaType(apiMediaType)
if err != nil {
panic(err)
}
serializer, ok = api.Codecs.SerializerForMediaType(mediaType, options)
if !ok {
panic(fmt.Sprintf("no serializer for %s", apiMediaType))
}
}
if storageMediaType := StorageMediaType(); len(storageMediaType) > 0 {
var ok bool
mediaType, options, err := mime.ParseMediaType(storageMediaType)
if err != nil {
panic(err)
}
storageSerializer, ok = api.Codecs.SerializerForMediaType(mediaType, options)
if !ok {
panic(fmt.Sprintf("no serializer for %s", storageMediaType))
}
}
kubeTestAPI := os.Getenv("KUBE_TEST_API") kubeTestAPI := os.Getenv("KUBE_TEST_API")
if len(kubeTestAPI) != 0 { if len(kubeTestAPI) != 0 {
testGroupVersions := strings.Split(kubeTestAPI, ",") testGroupVersions := strings.Split(kubeTestAPI, ",")
@ -173,9 +201,40 @@ func (g TestGroup) InternalTypes() map[string]reflect.Type {
} }
// Codec returns the codec for the API version to test against, as set by the // Codec returns the codec for the API version to test against, as set by the
// KUBE_TEST_API env var. // KUBE_TEST_API_TYPE env var.
func (g TestGroup) Codec() runtime.Codec { func (g TestGroup) Codec() runtime.Codec {
return api.Codecs.LegacyCodec(g.externalGroupVersion) if serializer.Serializer == nil {
return api.Codecs.LegacyCodec(g.externalGroupVersion)
}
return api.Codecs.CodecForVersions(serializer, api.Codecs.UniversalDeserializer(), []unversioned.GroupVersion{g.externalGroupVersion}, nil)
}
// NegotiatedSerializer returns the negotiated serializer for the server.
func (g TestGroup) NegotiatedSerializer() runtime.NegotiatedSerializer {
return api.Codecs
}
func StorageMediaType() string {
return os.Getenv("KUBE_TEST_API_STORAGE_TYPE")
}
// StorageCodec returns the codec for the API version to store in etcd, as set by the
// KUBE_TEST_API_STORAGE_TYPE env var.
func (g TestGroup) StorageCodec() runtime.Codec {
s := storageSerializer.Serializer
if s == nil {
return api.Codecs.LegacyCodec(g.externalGroupVersion)
}
// etcd2 only supports string data - we must wrap any result before returning
// TODO: remove for etcd3 / make parameterizable
if !storageSerializer.EncodesAsText {
s = runtime.NewBase64Serializer(s)
}
ds := recognizer.NewDecoder(s, api.Codecs.UniversalDeserializer())
return api.Codecs.CodecForVersions(s, ds, []unversioned.GroupVersion{g.externalGroupVersion}, nil)
} }
// Converter returns the api.Scheme for the API version to test against, as set by the // Converter returns the api.Scheme for the API version to test against, as set by the

View File

@ -21,6 +21,7 @@ syntax = 'proto2';
package k8s.io.kubernetes.pkg.api.unversioned; package k8s.io.kubernetes.pkg.api.unversioned;
import "k8s.io/kubernetes/pkg/runtime/generated.proto";
import "k8s.io/kubernetes/pkg/util/intstr/generated.proto"; import "k8s.io/kubernetes/pkg/util/intstr/generated.proto";
// Package-wide variables from generator "generated". // Package-wide variables from generator "generated".

View File

@ -95,6 +95,7 @@ import math "math"
import k8s_io_kubernetes_pkg_api_unversioned "k8s.io/kubernetes/pkg/api/unversioned" import k8s_io_kubernetes_pkg_api_unversioned "k8s.io/kubernetes/pkg/api/unversioned"
import k8s_io_kubernetes_pkg_api_v1 "k8s.io/kubernetes/pkg/api/v1" import k8s_io_kubernetes_pkg_api_v1 "k8s.io/kubernetes/pkg/api/v1"
import k8s_io_kubernetes_pkg_util_intstr "k8s.io/kubernetes/pkg/util/intstr" import k8s_io_kubernetes_pkg_util_intstr "k8s.io/kubernetes/pkg/util/intstr"
import io "io" import io "io"

View File

@ -24,6 +24,7 @@ package k8s.io.kubernetes.pkg.apis.extensions.v1beta1;
import "k8s.io/kubernetes/pkg/api/resource/generated.proto"; import "k8s.io/kubernetes/pkg/api/resource/generated.proto";
import "k8s.io/kubernetes/pkg/api/unversioned/generated.proto"; import "k8s.io/kubernetes/pkg/api/unversioned/generated.proto";
import "k8s.io/kubernetes/pkg/api/v1/generated.proto"; import "k8s.io/kubernetes/pkg/api/v1/generated.proto";
import "k8s.io/kubernetes/pkg/runtime/generated.proto";
import "k8s.io/kubernetes/pkg/util/intstr/generated.proto"; import "k8s.io/kubernetes/pkg/util/intstr/generated.proto";
// Package-wide variables from generator "generated". // Package-wide variables from generator "generated".

View File

@ -274,9 +274,16 @@ type StripVersionNegotiatedSerializer struct {
runtime.NegotiatedSerializer runtime.NegotiatedSerializer
} }
func (n StripVersionNegotiatedSerializer) EncoderForVersion(serializer runtime.Serializer, gv unversioned.GroupVersion) runtime.Encoder { func (n StripVersionNegotiatedSerializer) EncoderForVersion(encoder runtime.Encoder, gv unversioned.GroupVersion) runtime.Encoder {
encoder := n.NegotiatedSerializer.EncoderForVersion(serializer, gv) serializer, ok := encoder.(runtime.Serializer)
return stripVersionEncoder{encoder, serializer} if !ok {
// The stripVersionEncoder needs both an encoder and decoder, but is called from a context that doesn't have access to the
// decoder. We do a best effort cast here (since this code path is only for backwards compatibility) to get access to the caller's
// decoder.
panic(fmt.Sprintf("Unable to extract serializer from %#v", encoder))
}
versioned := n.NegotiatedSerializer.EncoderForVersion(encoder, gv)
return stripVersionEncoder{versioned, serializer}
} }
func keepUnversioned(group string) bool { func keepUnversioned(group string) bool {
@ -422,14 +429,14 @@ func write(statusCode int, gv unversioned.GroupVersion, s runtime.NegotiatedSeri
// writeNegotiated renders an object in the content type negotiated by the client // writeNegotiated renders an object in the content type negotiated by the client
func writeNegotiated(s runtime.NegotiatedSerializer, gv unversioned.GroupVersion, w http.ResponseWriter, req *http.Request, statusCode int, object runtime.Object) { func writeNegotiated(s runtime.NegotiatedSerializer, gv unversioned.GroupVersion, w http.ResponseWriter, req *http.Request, statusCode int, object runtime.Object) {
serializer, contentType, err := negotiateOutputSerializer(req, s) serializer, err := negotiateOutputSerializer(req, s)
if err != nil { if err != nil {
status := errToAPIStatus(err) status := errToAPIStatus(err)
writeRawJSON(int(status.Code), status, w) writeRawJSON(int(status.Code), status, w)
return return
} }
w.Header().Set("Content-Type", contentType) w.Header().Set("Content-Type", serializer.MediaType)
w.WriteHeader(statusCode) w.WriteHeader(statusCode)
encoder := s.EncoderForVersion(serializer, gv) encoder := s.EncoderForVersion(serializer, gv)

View File

@ -50,31 +50,31 @@ func negotiateOutput(req *http.Request, supported []string) (string, map[string]
return mediaType, accept.Params, nil return mediaType, accept.Params, nil
} }
func negotiateOutputSerializer(req *http.Request, ns runtime.NegotiatedSerializer) (runtime.Serializer, string, error) { func negotiateOutputSerializer(req *http.Request, ns runtime.NegotiatedSerializer) (runtime.SerializerInfo, error) {
supported := ns.SupportedMediaTypes() supported := ns.SupportedMediaTypes()
mediaType, params, err := negotiateOutput(req, supported) mediaType, params, err := negotiateOutput(req, supported)
if err != nil { if err != nil {
return nil, "", err return runtime.SerializerInfo{}, err
} }
if s, ok := ns.SerializerForMediaType(mediaType, params); ok { if s, ok := ns.SerializerForMediaType(mediaType, params); ok {
return s, mediaType, nil return s, nil
} }
return nil, "", errNotAcceptable{supported} return runtime.SerializerInfo{}, errNotAcceptable{supported}
} }
func negotiateOutputStreamSerializer(req *http.Request, ns runtime.NegotiatedSerializer) (runtime.Serializer, runtime.Framer, string, string, error) { func negotiateOutputStreamSerializer(req *http.Request, ns runtime.NegotiatedSerializer) (runtime.StreamSerializerInfo, error) {
supported := ns.SupportedMediaTypes() supported := ns.SupportedMediaTypes()
mediaType, params, err := negotiateOutput(req, supported) mediaType, params, err := negotiateOutput(req, supported)
if err != nil { if err != nil {
return nil, nil, "", "", err return runtime.StreamSerializerInfo{}, err
} }
if s, f, exactMediaType, ok := ns.StreamingSerializerForMediaType(mediaType, params); ok { if s, ok := ns.StreamingSerializerForMediaType(mediaType, params); ok {
return s, f, mediaType, exactMediaType, nil return s, nil
} }
return nil, nil, "", "", errNotAcceptable{supported} return runtime.StreamSerializerInfo{}, errNotAcceptable{supported}
} }
func negotiateInputSerializer(req *http.Request, s runtime.NegotiatedSerializer) (runtime.Serializer, error) { func negotiateInputSerializer(req *http.Request, s runtime.NegotiatedSerializer) (runtime.SerializerInfo, error) {
supported := s.SupportedMediaTypes() supported := s.SupportedMediaTypes()
mediaType := req.Header.Get("Content-Type") mediaType := req.Header.Get("Content-Type")
if len(mediaType) == 0 { if len(mediaType) == 0 {
@ -82,11 +82,11 @@ func negotiateInputSerializer(req *http.Request, s runtime.NegotiatedSerializer)
} }
mediaType, options, err := mime.ParseMediaType(mediaType) mediaType, options, err := mime.ParseMediaType(mediaType)
if err != nil { if err != nil {
return nil, errUnsupportedMediaType{supported} return runtime.SerializerInfo{}, errUnsupportedMediaType{supported}
} }
out, ok := s.SerializerForMediaType(mediaType, options) out, ok := s.SerializerForMediaType(mediaType, options)
if !ok { if !ok {
return nil, errUnsupportedMediaType{supported} return runtime.SerializerInfo{}, errUnsupportedMediaType{supported}
} }
return out, nil return out, nil
} }

View File

@ -41,27 +41,34 @@ func (n *fakeNegotiater) SupportedStreamingMediaTypes() []string {
return n.streamTypes return n.streamTypes
} }
func (n *fakeNegotiater) SerializerForMediaType(mediaType string, options map[string]string) (runtime.Serializer, bool) { func (n *fakeNegotiater) SerializerForMediaType(mediaType string, options map[string]string) (runtime.SerializerInfo, bool) {
n.mediaType = mediaType n.mediaType = mediaType
if len(options) > 0 { if len(options) > 0 {
n.options = options n.options = options
} }
return n.serializer, n.serializer != nil 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.Serializer, runtime.Framer, string, bool) { func (n *fakeNegotiater) StreamingSerializerForMediaType(mediaType string, options map[string]string) (runtime.StreamSerializerInfo, bool) {
n.streamMediaType = mediaType n.streamMediaType = mediaType
if len(options) > 0 { if len(options) > 0 {
n.streamOptions = options n.streamOptions = options
} }
return n.streamSerializer, n.framer, mediaType, n.streamSerializer != nil return runtime.StreamSerializerInfo{
SerializerInfo: runtime.SerializerInfo{
Serializer: n.serializer,
MediaType: mediaType,
EncodesAsText: true,
},
Framer: n.framer,
}, n.streamSerializer != nil
} }
func (n *fakeNegotiater) EncoderForVersion(serializer runtime.Serializer, gv unversioned.GroupVersion) runtime.Encoder { func (n *fakeNegotiater) EncoderForVersion(serializer runtime.Encoder, gv unversioned.GroupVersion) runtime.Encoder {
return n.serializer return n.serializer
} }
func (n *fakeNegotiater) DecoderToVersion(serializer runtime.Serializer, gv unversioned.GroupVersion) runtime.Decoder { func (n *fakeNegotiater) DecoderToVersion(serializer runtime.Decoder, gv unversioned.GroupVersion) runtime.Decoder {
return n.serializer return n.serializer
} }
@ -228,7 +235,7 @@ func TestNegotiate(t *testing.T) {
req = &http.Request{Header: http.Header{}} req = &http.Request{Header: http.Header{}}
req.Header.Set("Accept", test.accept) req.Header.Set("Accept", test.accept)
} }
s, contentType, err := negotiateOutputSerializer(req, test.ns) s, err := negotiateOutputSerializer(req, test.ns)
switch { switch {
case err == nil && test.errFn != nil: case err == nil && test.errFn != nil:
t.Errorf("%d: failed: expected error", i) t.Errorf("%d: failed: expected error", i)
@ -251,11 +258,11 @@ func TestNegotiate(t *testing.T) {
} }
continue continue
} }
if test.contentType != contentType { if test.contentType != s.MediaType {
t.Errorf("%d: unexpected %s %s", i, test.contentType, contentType) t.Errorf("%d: unexpected %s %s", i, test.contentType, s.MediaType)
} }
if s != test.serializer { if s.Serializer != test.serializer {
t.Errorf("%d: unexpected %s %s", i, test.serializer, s) t.Errorf("%d: unexpected %s %s", i, test.serializer, s.Serializer)
} }
if !reflect.DeepEqual(test.params, test.ns.options) { if !reflect.DeepEqual(test.params, test.ns.options) {
t.Errorf("%d: unexpected %#v %#v", i, test.params, test.ns.options) t.Errorf("%d: unexpected %#v %#v", i, test.params, test.ns.options)

View File

@ -59,47 +59,33 @@ func (w *realTimeoutFactory) TimeoutCh() (<-chan time.Time, func() bool) {
return t.C, t.Stop return t.C, t.Stop
} }
type textEncodable interface {
// EncodesAsText should return true if objects should be transmitted as a WebSocket Text
// frame (otherwise, they will be sent as a Binary frame).
EncodesAsText() bool
}
// serveWatch handles serving requests to the server // serveWatch handles serving requests to the server
// TODO: the functionality in this method and in WatchServer.Serve is not cleanly decoupled. // TODO: the functionality in this method and in WatchServer.Serve is not cleanly decoupled.
func serveWatch(watcher watch.Interface, scope RequestScope, req *restful.Request, res *restful.Response, timeout time.Duration) { func serveWatch(watcher watch.Interface, scope RequestScope, req *restful.Request, res *restful.Response, timeout time.Duration) {
// negotiate for the stream serializer // negotiate for the stream serializer
serializer, framer, mediaType, exactMediaType, err := negotiateOutputStreamSerializer(req.Request, scope.Serializer) serializer, err := negotiateOutputStreamSerializer(req.Request, scope.Serializer)
if err != nil { if err != nil {
scope.err(err, res.ResponseWriter, req.Request) scope.err(err, res.ResponseWriter, req.Request)
return return
} }
if framer == nil { if serializer.Framer == nil {
scope.err(fmt.Errorf("no framer defined for %q available for embedded encoding", mediaType), res.ResponseWriter, req.Request) scope.err(fmt.Errorf("no framer defined for %q available for embedded encoding", serializer.MediaType), res.ResponseWriter, req.Request)
return return
} }
encoder := scope.Serializer.EncoderForVersion(serializer, scope.Kind.GroupVersion()) encoder := scope.Serializer.EncoderForVersion(serializer.Serializer, scope.Kind.GroupVersion())
useTextFraming := false useTextFraming := serializer.EncodesAsText
if encodable, ok := serializer.(textEncodable); ok && encodable.EncodesAsText() {
useTextFraming = true
}
// find the embedded serializer matching the media type // find the embedded serializer matching the media type
embeddedSerializer, ok := scope.Serializer.SerializerForMediaType(mediaType, nil) embeddedEncoder := scope.Serializer.EncoderForVersion(serializer.Embedded.Serializer, scope.Kind.GroupVersion())
if !ok {
scope.err(fmt.Errorf("no serializer defined for %q available for embedded encoding", mediaType), res.ResponseWriter, req.Request)
return
}
embeddedEncoder := scope.Serializer.EncoderForVersion(embeddedSerializer, scope.Kind.GroupVersion())
server := &WatchServer{ server := &WatchServer{
watching: watcher, watching: watcher,
scope: scope, scope: scope,
useTextFraming: useTextFraming, useTextFraming: useTextFraming,
mediaType: exactMediaType, mediaType: serializer.MediaType,
framer: framer, framer: serializer.Framer,
encoder: encoder, encoder: encoder,
embeddedEncoder: embeddedEncoder, embeddedEncoder: embeddedEncoder,
fixup: func(obj runtime.Object) { fixup: func(obj runtime.Object) {

View File

@ -222,9 +222,9 @@ func TestWatchRead(t *testing.T) {
for _, protocol := range protocols { for _, protocol := range protocols {
for _, test := range testCases { for _, test := range testCases {
serializer, framer, _, ok := api.Codecs.StreamingSerializerForMediaType(test.MediaType, nil) serializer, ok := api.Codecs.StreamingSerializerForMediaType(test.MediaType, nil)
if !ok { if !ok {
t.Fatal(framer) t.Fatal(serializer)
} }
r, contentType := protocol.fn(test.Accept) r, contentType := protocol.fn(test.Accept)
@ -235,13 +235,13 @@ func TestWatchRead(t *testing.T) {
} }
objectSerializer, ok := api.Codecs.SerializerForMediaType(test.MediaType, nil) objectSerializer, ok := api.Codecs.SerializerForMediaType(test.MediaType, nil)
if !ok { if !ok {
t.Fatal(framer) t.Fatal(objectSerializer)
} }
objectCodec := api.Codecs.DecoderToVersion(objectSerializer, testInternalGroupVersion) objectCodec := api.Codecs.DecoderToVersion(objectSerializer, testInternalGroupVersion)
var fr io.ReadCloser = r var fr io.ReadCloser = r
if !protocol.selfFraming { if !protocol.selfFraming {
fr = framer.NewFrameReader(r) fr = serializer.Framer.NewFrameReader(r)
} }
d := streaming.NewDecoder(fr, serializer) d := streaming.NewDecoder(fr, serializer)
@ -495,9 +495,9 @@ func TestWatchHTTPTimeout(t *testing.T) {
timeoutCh := make(chan time.Time) timeoutCh := make(chan time.Time)
done := make(chan struct{}) done := make(chan struct{})
_, framer, _, ok := api.Codecs.StreamingSerializerForMediaType("application/json", nil) serializer, ok := api.Codecs.StreamingSerializerForMediaType("application/json", nil)
if !ok { if !ok {
t.Fatal(framer) t.Fatal(serializer)
} }
// Setup a new watchserver // Setup a new watchserver
@ -505,7 +505,7 @@ func TestWatchHTTPTimeout(t *testing.T) {
watching: watcher, watching: watcher,
mediaType: "testcase/json", mediaType: "testcase/json",
framer: framer, framer: serializer.Framer,
encoder: newCodec, encoder: newCodec,
embeddedEncoder: newCodec, embeddedEncoder: newCodec,

View File

@ -139,26 +139,23 @@ func readExpBackoffConfig() BackoffManager {
func createSerializers(config ContentConfig) (*Serializers, error) { func createSerializers(config ContentConfig) (*Serializers, error) {
negotiated := config.NegotiatedSerializer negotiated := config.NegotiatedSerializer
contentType := config.ContentType contentType := config.ContentType
serializer, ok := negotiated.SerializerForMediaType(contentType, nil) info, ok := negotiated.SerializerForMediaType(contentType, nil)
if !ok { if !ok {
return nil, fmt.Errorf("serializer for %s not registered", contentType) return nil, fmt.Errorf("serializer for %s not registered", contentType)
} }
streamingSerializer, framer, _, ok := negotiated.StreamingSerializerForMediaType(contentType, nil) streamInfo, ok := negotiated.StreamingSerializerForMediaType(contentType, nil)
if !ok { if !ok {
return nil, fmt.Errorf("streaming serializer for %s not registered", contentType) return nil, fmt.Errorf("streaming serializer for %s not registered", contentType)
} }
if framer == nil {
return nil, fmt.Errorf("no framer for %s", contentType)
}
internalGV := unversioned.GroupVersion{ internalGV := unversioned.GroupVersion{
Group: config.GroupVersion.Group, Group: config.GroupVersion.Group,
Version: runtime.APIVersionInternal, Version: runtime.APIVersionInternal,
} }
return &Serializers{ return &Serializers{
Encoder: negotiated.EncoderForVersion(serializer, *config.GroupVersion), Encoder: negotiated.EncoderForVersion(info.Serializer, *config.GroupVersion),
Decoder: negotiated.DecoderToVersion(serializer, internalGV), Decoder: negotiated.DecoderToVersion(info.Serializer, internalGV),
StreamingSerializer: streamingSerializer, StreamingSerializer: streamInfo.Serializer,
Framer: framer, Framer: streamInfo.Framer,
}, nil }, nil
} }

View File

@ -47,7 +47,7 @@ func TestDoRequestSuccess(t *testing.T) {
Host: testServer.URL, Host: testServer.URL,
ContentConfig: ContentConfig{ ContentConfig: ContentConfig{
GroupVersion: testapi.Default.GroupVersion(), GroupVersion: testapi.Default.GroupVersion(),
NegotiatedSerializer: testapi.NegotiatedSerializer, NegotiatedSerializer: testapi.Default.NegotiatedSerializer(),
}, },
Username: "user", Username: "user",
Password: "pass", Password: "pass",
@ -92,7 +92,7 @@ func TestDoRequestFailed(t *testing.T) {
Host: testServer.URL, Host: testServer.URL,
ContentConfig: ContentConfig{ ContentConfig: ContentConfig{
GroupVersion: testapi.Default.GroupVersion(), GroupVersion: testapi.Default.GroupVersion(),
NegotiatedSerializer: testapi.NegotiatedSerializer, NegotiatedSerializer: testapi.Default.NegotiatedSerializer(),
}, },
}) })
if err != nil { if err != nil {
@ -130,7 +130,7 @@ func TestDoRequestCreated(t *testing.T) {
Host: testServer.URL, Host: testServer.URL,
ContentConfig: ContentConfig{ ContentConfig: ContentConfig{
GroupVersion: testapi.Default.GroupVersion(), GroupVersion: testapi.Default.GroupVersion(),
NegotiatedSerializer: testapi.NegotiatedSerializer, NegotiatedSerializer: testapi.Default.NegotiatedSerializer(),
}, },
Username: "user", Username: "user",
Password: "pass", Password: "pass",

View File

@ -87,13 +87,13 @@ func TestSetKubernetesDefaultsUserAgent(t *testing.T) {
} }
func TestRESTClientRequires(t *testing.T) { func TestRESTClientRequires(t *testing.T) {
if _, err := RESTClientFor(&Config{Host: "127.0.0.1", ContentConfig: ContentConfig{NegotiatedSerializer: testapi.NegotiatedSerializer}}); err == nil { if _, err := RESTClientFor(&Config{Host: "127.0.0.1", ContentConfig: ContentConfig{NegotiatedSerializer: testapi.Default.NegotiatedSerializer()}}); err == nil {
t.Errorf("unexpected non-error") t.Errorf("unexpected non-error")
} }
if _, err := RESTClientFor(&Config{Host: "127.0.0.1", ContentConfig: ContentConfig{GroupVersion: testapi.Default.GroupVersion()}}); err == nil { if _, err := RESTClientFor(&Config{Host: "127.0.0.1", ContentConfig: ContentConfig{GroupVersion: testapi.Default.GroupVersion()}}); err == nil {
t.Errorf("unexpected non-error") t.Errorf("unexpected non-error")
} }
if _, err := RESTClientFor(&Config{Host: "127.0.0.1", ContentConfig: ContentConfig{GroupVersion: testapi.Default.GroupVersion(), NegotiatedSerializer: testapi.NegotiatedSerializer}}); err != nil { if _, err := RESTClientFor(&Config{Host: "127.0.0.1", ContentConfig: ContentConfig{GroupVersion: testapi.Default.GroupVersion(), NegotiatedSerializer: testapi.Default.NegotiatedSerializer()}}); err != nil {
t.Errorf("unexpected error: %v", err) t.Errorf("unexpected error: %v", err)
} }
} }

View File

@ -639,6 +639,10 @@ func (r *Request) Watch() (watch.Interface, error) {
if r.err != nil { if r.err != nil {
return nil, r.err return nil, r.err
} }
if r.serializers.Framer == nil {
return nil, fmt.Errorf("watching resources is not possible with this client (content-type: %s)", r.content.ContentType)
}
url := r.URL().String() url := r.URL().String()
req, err := http.NewRequest(r.verb, url, r.body) req, err := http.NewRequest(r.verb, url, r.body)
if err != nil { if err != nil {

View File

@ -247,7 +247,7 @@ func defaultContentConfig() ContentConfig {
return ContentConfig{ return ContentConfig{
GroupVersion: testapi.Default.GroupVersion(), GroupVersion: testapi.Default.GroupVersion(),
Codec: testapi.Default.Codec(), Codec: testapi.Default.Codec(),
NegotiatedSerializer: testapi.NegotiatedSerializer, NegotiatedSerializer: testapi.Default.NegotiatedSerializer(),
} }
} }
@ -549,6 +549,7 @@ func TestRequestWatch(t *testing.T) {
}, },
{ {
Request: &Request{ Request: &Request{
serializers: defaultSerializers(),
client: clientFunc(func(req *http.Request) (*http.Response, error) { client: clientFunc(func(req *http.Request) (*http.Response, error) {
return nil, io.EOF return nil, io.EOF
}), }),
@ -558,6 +559,7 @@ func TestRequestWatch(t *testing.T) {
}, },
{ {
Request: &Request{ Request: &Request{
serializers: defaultSerializers(),
client: clientFunc(func(req *http.Request) (*http.Response, error) { client: clientFunc(func(req *http.Request) (*http.Response, error) {
return nil, &url.Error{Err: io.EOF} return nil, &url.Error{Err: io.EOF}
}), }),
@ -567,6 +569,7 @@ func TestRequestWatch(t *testing.T) {
}, },
{ {
Request: &Request{ Request: &Request{
serializers: defaultSerializers(),
client: clientFunc(func(req *http.Request) (*http.Response, error) { client: clientFunc(func(req *http.Request) (*http.Response, error) {
return nil, errors.New("http: can't write HTTP request on broken connection") return nil, errors.New("http: can't write HTTP request on broken connection")
}), }),
@ -576,6 +579,7 @@ func TestRequestWatch(t *testing.T) {
}, },
{ {
Request: &Request{ Request: &Request{
serializers: defaultSerializers(),
client: clientFunc(func(req *http.Request) (*http.Response, error) { client: clientFunc(func(req *http.Request) (*http.Response, error) {
return nil, errors.New("foo: connection reset by peer") return nil, errors.New("foo: connection reset by peer")
}), }),

View File

@ -215,7 +215,10 @@ func setDiscoveryDefaults(config *restclient.Config) error {
config.APIPath = "" config.APIPath = ""
config.GroupVersion = nil config.GroupVersion = nil
codec := runtime.NoopEncoder{Decoder: api.Codecs.UniversalDecoder()} codec := runtime.NoopEncoder{Decoder: api.Codecs.UniversalDecoder()}
config.NegotiatedSerializer = serializer.NegotiatedSerializerWrapper(codec, codec, runtime.DefaultFramer) config.NegotiatedSerializer = serializer.NegotiatedSerializerWrapper(
runtime.SerializerInfo{Serializer: codec},
runtime.StreamSerializerInfo{},
)
if len(config.UserAgent) == 0 { if len(config.UserAgent) == 0 {
config.UserAgent = restclient.DefaultKubernetesUserAgent() config.UserAgent = restclient.DefaultKubernetesUserAgent()
} }

View File

@ -33,7 +33,6 @@ import (
"k8s.io/kubernetes/pkg/conversion/queryparams" "k8s.io/kubernetes/pkg/conversion/queryparams"
"k8s.io/kubernetes/pkg/runtime" "k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/runtime/serializer" "k8s.io/kubernetes/pkg/runtime/serializer"
serializerjson "k8s.io/kubernetes/pkg/runtime/serializer/json"
"k8s.io/kubernetes/pkg/watch" "k8s.io/kubernetes/pkg/watch"
) )
@ -51,8 +50,10 @@ func NewClient(conf *restclient.Config) (*Client, error) {
conf = &confCopy conf = &confCopy
codec := dynamicCodec{} codec := dynamicCodec{}
legacyCodec := api.Codecs.LegacyCodec(v1.SchemeGroupVersion)
conf.NegotiatedSerializer = serializer.NegotiatedSerializerWrapper(codec, legacyCodec, serializerjson.Framer) // TODO: it's questionable that this should be using anything other than unstructured schema and JSON
streamingInfo, _ := api.Codecs.StreamingSerializerForMediaType("application/json;stream=watch", nil)
conf.NegotiatedSerializer = serializer.NegotiatedSerializerWrapper(runtime.SerializerInfo{Serializer: codec}, streamingInfo)
if conf.APIPath == "" { if conf.APIPath == "" {
conf.APIPath = "/api" conf.APIPath = "/api"

View File

@ -42,7 +42,7 @@ func TestSetKubernetesDefaults(t *testing.T) {
ContentConfig: restclient.ContentConfig{ ContentConfig: restclient.ContentConfig{
GroupVersion: testapi.Default.GroupVersion(), GroupVersion: testapi.Default.GroupVersion(),
Codec: testapi.Default.Codec(), Codec: testapi.Default.Codec(),
NegotiatedSerializer: testapi.NegotiatedSerializer, NegotiatedSerializer: testapi.Default.NegotiatedSerializer(),
}, },
QPS: 5, QPS: 5,
Burst: 10, Burst: 10,
@ -126,7 +126,7 @@ func TestHelperGetServerAPIVersions(t *testing.T) {
w.Write(output) w.Write(output)
})) }))
defer server.Close() defer server.Close()
got, err := restclient.ServerAPIVersions(&restclient.Config{Host: server.URL, ContentConfig: restclient.ContentConfig{GroupVersion: &unversioned.GroupVersion{Group: "invalid version", Version: "one"}, NegotiatedSerializer: testapi.NegotiatedSerializer}}) got, err := restclient.ServerAPIVersions(&restclient.Config{Host: server.URL, ContentConfig: restclient.ContentConfig{GroupVersion: &unversioned.GroupVersion{Group: "invalid version", Version: "one"}, NegotiatedSerializer: testapi.Default.NegotiatedSerializer()}})
if err != nil { if err != nil {
t.Fatalf("unexpected encoding error: %v", err) t.Fatalf("unexpected encoding error: %v", err)
} }

View File

@ -215,7 +215,7 @@ func TestStream(t *testing.T) {
url, _ := url.ParseRequestURI(server.URL) url, _ := url.ParseRequestURI(server.URL)
config := restclient.ContentConfig{ config := restclient.ContentConfig{
GroupVersion: &unversioned.GroupVersion{Group: "x"}, GroupVersion: &unversioned.GroupVersion{Group: "x"},
NegotiatedSerializer: testapi.NegotiatedSerializer, NegotiatedSerializer: testapi.Default.NegotiatedSerializer(),
} }
c, err := restclient.NewRESTClient(url, "", config, -1, -1, nil, nil) c, err := restclient.NewRESTClient(url, "", config, -1, -1, nil, nil)
if err != nil { if err != nil {

View File

@ -26,7 +26,7 @@ type ResourceEncodingConfig interface {
// StorageEncoding returns the serialization format for the resource. // StorageEncoding returns the serialization format for the resource.
// TODO this should actually return a GroupVersionKind since you can logically have multiple "matching" Kinds // TODO this should actually return a GroupVersionKind since you can logically have multiple "matching" Kinds
// For now, it returns just the GroupVersion for consistency with old behavior // For now, it returns just the GroupVersion for consistency with old behavior
StoragageEncodingFor(unversioned.GroupResource) (unversioned.GroupVersion, error) StorageEncodingFor(unversioned.GroupResource) (unversioned.GroupVersion, error)
// InMemoryEncodingFor returns the groupVersion for the in memory representation the storage should convert to. // InMemoryEncodingFor returns the groupVersion for the in memory representation the storage should convert to.
InMemoryEncodingFor(unversioned.GroupResource) (unversioned.GroupVersion, error) InMemoryEncodingFor(unversioned.GroupResource) (unversioned.GroupVersion, error)
@ -78,7 +78,7 @@ func (o *DefaultResourceEncodingConfig) SetResourceEncoding(resourceBeingStored
o.Groups[group].InternalResourceEncodings[resourceBeingStored.Resource] = internalVersion o.Groups[group].InternalResourceEncodings[resourceBeingStored.Resource] = internalVersion
} }
func (o *DefaultResourceEncodingConfig) StoragageEncodingFor(resource unversioned.GroupResource) (unversioned.GroupVersion, error) { func (o *DefaultResourceEncodingConfig) StorageEncodingFor(resource unversioned.GroupResource) (unversioned.GroupVersion, error) {
groupMeta, err := registered.Group(resource.Group) groupMeta, err := registered.Group(resource.Group)
if err != nil { if err != nil {
return unversioned.GroupVersion{}, err return unversioned.GroupVersion{}, err

View File

@ -18,9 +18,11 @@ package genericapiserver
import ( import (
"fmt" "fmt"
"mime"
"k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/runtime" "k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/runtime/serializer/recognizer"
"k8s.io/kubernetes/pkg/runtime/serializer/versioning" "k8s.io/kubernetes/pkg/runtime/serializer/versioning"
"k8s.io/kubernetes/pkg/storage" "k8s.io/kubernetes/pkg/storage"
"k8s.io/kubernetes/pkg/storage/storagebackend" "k8s.io/kubernetes/pkg/storage/storagebackend"
@ -50,19 +52,25 @@ type DefaultStorageFactory struct {
Overrides map[unversioned.GroupResource]groupResourceOverrides Overrides map[unversioned.GroupResource]groupResourceOverrides
// DefaultMediaType is the media type used to store resources. If it is not set, "application/json" is used.
DefaultMediaType string
// DefaultSerializer is used to create encoders and decoders for the storage.Interface. // DefaultSerializer is used to create encoders and decoders for the storage.Interface.
DefaultSerializer runtime.NegotiatedSerializer DefaultSerializer runtime.StorageSerializer
// ResourceEncodingConfig describes how to encode a particular GroupVersionResource // ResourceEncodingConfig describes how to encode a particular GroupVersionResource
ResourceEncodingConfig ResourceEncodingConfig ResourceEncodingConfig ResourceEncodingConfig
// APIResourceConfigSource indicates whether the *storage* is enabled, NOT the API // APIResourceConfigSource indicates whether the *storage* is enabled, NOT the API
// This is discrete from resource enablement because those are separate concerns. How it is surfaced to the user via flags // This is discrete from resource enablement because those are separate concerns. How this source is configured
// or config is up to whoever is building this. // is left to the caller.
APIResourceConfigSource APIResourceConfigSource APIResourceConfigSource APIResourceConfigSource
// newEtcdFn exists to be overwritten for unit testing. You should never set this in a normal world. // newStorageCodecFn exists to be overwritten for unit testing.
newEtcdFn func(ns runtime.NegotiatedSerializer, storageVersion, memoryVersion unversioned.GroupVersion, config storagebackend.Config) (etcdStorage storage.Interface, err error) newStorageCodecFn func(storageMediaType string, ns runtime.StorageSerializer, storageVersion, memoryVersion unversioned.GroupVersion, config storagebackend.Config) (codec runtime.Codec, err error)
// newStorageFn exists to be overwritten for unit testing.
newStorageFn func(config storagebackend.Config) (etcdStorage storage.Interface, err error)
} }
type groupResourceOverrides struct { type groupResourceOverrides struct {
@ -71,8 +79,10 @@ type groupResourceOverrides struct {
etcdLocation []string etcdLocation []string
// etcdPrefix contains the list of "special" prefixes for a GroupResource. Resource=* means for the entire group // etcdPrefix contains the list of "special" prefixes for a GroupResource. Resource=* means for the entire group
etcdPrefix string etcdPrefix string
// mediaType is the desired serializer to choose. If empty, the default is chosen.
mediaType string
// serializer contains the list of "special" serializers for a GroupResource. Resource=* means for the entire group // serializer contains the list of "special" serializers for a GroupResource. Resource=* means for the entire group
serializer runtime.NegotiatedSerializer serializer runtime.StorageSerializer
// cohabitatingResources keeps track of which resources must be stored together. This happens when we have multiple ways // cohabitatingResources keeps track of which resources must be stored together. This happens when we have multiple ways
// of exposing one set of concepts. autoscaling.HPA and extensions.HPA as a for instance // of exposing one set of concepts. autoscaling.HPA and extensions.HPA as a for instance
// The order of the slice matters! It is the priority order of lookup for finding a storage location // The order of the slice matters! It is the priority order of lookup for finding a storage location
@ -83,15 +93,20 @@ var _ StorageFactory = &DefaultStorageFactory{}
const AllResources = "*" const AllResources = "*"
func NewDefaultStorageFactory(config storagebackend.Config, defaultSerializer runtime.NegotiatedSerializer, resourceEncodingConfig ResourceEncodingConfig, resourceConfig APIResourceConfigSource) *DefaultStorageFactory { func NewDefaultStorageFactory(config storagebackend.Config, defaultMediaType string, defaultSerializer runtime.StorageSerializer, resourceEncodingConfig ResourceEncodingConfig, resourceConfig APIResourceConfigSource) *DefaultStorageFactory {
if len(defaultMediaType) == 0 {
defaultMediaType = runtime.ContentTypeJSON
}
return &DefaultStorageFactory{ return &DefaultStorageFactory{
StorageConfig: config, StorageConfig: config,
Overrides: map[unversioned.GroupResource]groupResourceOverrides{}, Overrides: map[unversioned.GroupResource]groupResourceOverrides{},
DefaultMediaType: defaultMediaType,
DefaultSerializer: defaultSerializer, DefaultSerializer: defaultSerializer,
ResourceEncodingConfig: resourceEncodingConfig, ResourceEncodingConfig: resourceEncodingConfig,
APIResourceConfigSource: resourceConfig, APIResourceConfigSource: resourceConfig,
newEtcdFn: newEtcd, newStorageCodecFn: NewStorageCodec,
newStorageFn: newStorage,
} }
} }
@ -107,8 +122,9 @@ func (s *DefaultStorageFactory) SetEtcdPrefix(groupResource unversioned.GroupRes
s.Overrides[groupResource] = overrides s.Overrides[groupResource] = overrides
} }
func (s *DefaultStorageFactory) SetSerializer(groupResource unversioned.GroupResource, serializer runtime.NegotiatedSerializer) { func (s *DefaultStorageFactory) SetSerializer(groupResource unversioned.GroupResource, mediaType string, serializer runtime.StorageSerializer) {
overrides := s.Overrides[groupResource] overrides := s.Overrides[groupResource]
overrides.mediaType = mediaType
overrides.serializer = serializer overrides.serializer = serializer
s.Overrides[groupResource] = overrides s.Overrides[groupResource] = overrides
} }
@ -160,6 +176,14 @@ func (s *DefaultStorageFactory) New(groupResource unversioned.GroupResource) (st
etcdPrefix = exactResourceOverride.etcdPrefix etcdPrefix = exactResourceOverride.etcdPrefix
} }
etcdMediaType := s.DefaultMediaType
if len(groupOverride.mediaType) != 0 {
etcdMediaType = groupOverride.mediaType
}
if len(exactResourceOverride.mediaType) != 0 {
etcdMediaType = exactResourceOverride.mediaType
}
etcdSerializer := s.DefaultSerializer etcdSerializer := s.DefaultSerializer
if groupOverride.serializer != nil { if groupOverride.serializer != nil {
etcdSerializer = groupOverride.serializer etcdSerializer = groupOverride.serializer
@ -174,7 +198,7 @@ func (s *DefaultStorageFactory) New(groupResource unversioned.GroupResource) (st
config.ServerList = overriddenEtcdLocations config.ServerList = overriddenEtcdLocations
} }
storageEncodingVersion, err := s.ResourceEncodingConfig.StoragageEncodingFor(chosenStorageResource) storageEncodingVersion, err := s.ResourceEncodingConfig.StorageEncodingFor(chosenStorageResource)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -183,27 +207,19 @@ func (s *DefaultStorageFactory) New(groupResource unversioned.GroupResource) (st
return nil, err return nil, err
} }
codec, err := s.newStorageCodecFn(etcdMediaType, etcdSerializer, storageEncodingVersion, internalVersion, config)
if err != nil {
return nil, err
}
config.Codec = codec
glog.V(3).Infof("storing %v in %v, reading as %v from %v", groupResource, storageEncodingVersion, internalVersion, config) glog.V(3).Infof("storing %v in %v, reading as %v from %v", groupResource, storageEncodingVersion, internalVersion, config)
return s.newEtcdFn(etcdSerializer, storageEncodingVersion, internalVersion, config) return s.newStorageFn(config)
} }
func newEtcd(ns runtime.NegotiatedSerializer, storageVersion, memoryVersion unversioned.GroupVersion, config storagebackend.Config) (etcdStorage storage.Interface, err error) { // newStorage is the default implementation for creating a storage backend.
s, ok := ns.SerializerForMediaType("application/json", nil) func newStorage(config storagebackend.Config) (etcdStorage storage.Interface, err error) {
if !ok {
return nil, fmt.Errorf("unable to find serializer for JSON")
}
encoder := ns.EncoderForVersion(s, storageVersion)
decoder := ns.DecoderToVersion(s, memoryVersion)
if memoryVersion.Group != storageVersion.Group {
// Allow this codec to translate between groups.
if err = versioning.EnableCrossGroupEncoding(encoder, memoryVersion.Group, storageVersion.Group); err != nil {
return nil, fmt.Errorf("error setting up encoder from %v to %v: %v", memoryVersion, storageVersion, err)
}
if err = versioning.EnableCrossGroupDecoding(decoder, storageVersion.Group, memoryVersion.Group); err != nil {
return nil, fmt.Errorf("error setting up decoder from %v to %v: %v", storageVersion, memoryVersion, err)
}
}
config.Codec = runtime.NewCodec(encoder, decoder)
return storagebackend.Create(config) return storagebackend.Create(config)
} }
@ -217,3 +233,39 @@ func (s *DefaultStorageFactory) Backends() []string {
} }
return backends.List() return backends.List()
} }
// 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)
if err != nil {
return nil, fmt.Errorf("%q is not a valid mime-type", storageMediaType)
}
serializer, ok := ns.SerializerForMediaType(mediaType, options)
if !ok {
return nil, fmt.Errorf("unable to find serializer for %q", storageMediaType)
}
s := serializer.Serializer
// etcd2 only supports string data - we must wrap any result before returning
// TODO: storagebackend should return a boolean indicating whether it supports binary data
if !serializer.EncodesAsText && (config.Type == storagebackend.StorageTypeUnset || config.Type == storagebackend.StorageTypeETCD2) {
glog.V(4).Infof("Wrapping the underlying binary storage serializer with a base64 encoding for etcd2")
s = runtime.NewBase64Serializer(s)
}
ds := recognizer.NewDecoder(s, ns.UniversalDeserializer())
encoder := ns.EncoderForVersion(s, storageVersion)
decoder := ns.DecoderToVersion(ds, memoryVersion)
if memoryVersion.Group != storageVersion.Group {
// Allow this codec to translate between groups.
if err := versioning.EnableCrossGroupEncoding(encoder, memoryVersion.Group, storageVersion.Group); err != nil {
return nil, fmt.Errorf("error setting up encoder from %v to %v: %v", memoryVersion, storageVersion, err)
}
if err := versioning.EnableCrossGroupDecoding(decoder, storageVersion.Group, memoryVersion.Group); err != nil {
return nil, fmt.Errorf("error setting up decoder from %v to %v: %v", storageVersion, memoryVersion, err)
}
}
return runtime.NewCodec(encoder, decoder), nil
}

View File

@ -23,7 +23,6 @@ import (
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/storage" "k8s.io/kubernetes/pkg/storage"
"k8s.io/kubernetes/pkg/storage/storagebackend" "k8s.io/kubernetes/pkg/storage/storagebackend"
) )
@ -50,7 +49,7 @@ func TestUpdateEtcdOverrides(t *testing.T) {
defaultEtcdLocation := []string{"http://127.0.0.1"} defaultEtcdLocation := []string{"http://127.0.0.1"}
for i, test := range testCases { for i, test := range testCases {
actualConfig := storagebackend.Config{} actualConfig := storagebackend.Config{}
newEtcdFn := func(ns runtime.NegotiatedSerializer, storageVersion, memoryVersion unversioned.GroupVersion, config storagebackend.Config) (etcdStorage storage.Interface, err error) { newStorageFn := func(config storagebackend.Config) (_ storage.Interface, err error) {
actualConfig = config actualConfig = config
return nil, nil return nil, nil
} }
@ -59,8 +58,8 @@ func TestUpdateEtcdOverrides(t *testing.T) {
Prefix: DefaultEtcdPathPrefix, Prefix: DefaultEtcdPathPrefix,
ServerList: defaultEtcdLocation, ServerList: defaultEtcdLocation,
} }
storageFactory := NewDefaultStorageFactory(defaultConfig, api.Codecs, NewDefaultResourceEncodingConfig(), NewResourceConfig()) storageFactory := NewDefaultStorageFactory(defaultConfig, "", api.Codecs, NewDefaultResourceEncodingConfig(), NewResourceConfig())
storageFactory.newEtcdFn = newEtcdFn storageFactory.newStorageFn = newStorageFn
storageFactory.SetEtcdLocation(test.resource, test.servers) storageFactory.SetEtcdLocation(test.resource, test.servers)
var err error var err error

View File

@ -55,7 +55,7 @@ func (m *Mapper) InfoForData(data []byte, source string) (*Info, error) {
var obj runtime.Object var obj runtime.Object
var versioned runtime.Object var versioned runtime.Object
if registered.IsThirdPartyAPIGroupVersion(gvk.GroupVersion()) { if registered.IsThirdPartyAPIGroupVersion(gvk.GroupVersion()) {
obj, err = runtime.Decode(thirdpartyresourcedata.NewCodec(nil, gvk.Kind), data) obj, err = runtime.Decode(thirdpartyresourcedata.NewDecoder(nil, gvk.Kind), data)
versioned = obj versioned = obj
} else { } else {
obj, versioned = versions.Last(), versions.First() obj, versioned = versions.Last(), versions.First()

View File

@ -677,7 +677,14 @@ func (m *Master) InstallThirdPartyResource(rsrc *extensions.ThirdPartyResource)
func (m *Master) thirdpartyapi(group, kind, version string) *apiserver.APIGroupVersion { func (m *Master) thirdpartyapi(group, kind, version string) *apiserver.APIGroupVersion {
resourceStorage := thirdpartyresourcedataetcd.NewREST( resourceStorage := thirdpartyresourcedataetcd.NewREST(
generic.RESTOptions{Storage: m.thirdPartyStorage, Decorator: generic.UndecoratedStorage, DeleteCollectionWorkers: m.deleteCollectionWorkers}, group, kind) generic.RESTOptions{
Storage: m.thirdPartyStorage,
Decorator: generic.UndecoratedStorage,
DeleteCollectionWorkers: m.deleteCollectionWorkers,
},
group,
kind,
)
apiRoot := makeThirdPartyPath("") apiRoot := makeThirdPartyPath("")

View File

@ -88,7 +88,7 @@ func setUp(t *testing.T) (*Master, *etcdtesting.EtcdTestServer, Config, *assert.
resourceEncoding.SetVersionEncoding(batch.GroupName, *testapi.Batch.GroupVersion(), unversioned.GroupVersion{Group: batch.GroupName, Version: runtime.APIVersionInternal}) resourceEncoding.SetVersionEncoding(batch.GroupName, *testapi.Batch.GroupVersion(), unversioned.GroupVersion{Group: batch.GroupName, Version: runtime.APIVersionInternal})
resourceEncoding.SetVersionEncoding(apps.GroupName, *testapi.Apps.GroupVersion(), unversioned.GroupVersion{Group: apps.GroupName, Version: runtime.APIVersionInternal}) resourceEncoding.SetVersionEncoding(apps.GroupName, *testapi.Apps.GroupVersion(), unversioned.GroupVersion{Group: apps.GroupName, Version: runtime.APIVersionInternal})
resourceEncoding.SetVersionEncoding(extensions.GroupName, *testapi.Extensions.GroupVersion(), unversioned.GroupVersion{Group: extensions.GroupName, Version: runtime.APIVersionInternal}) resourceEncoding.SetVersionEncoding(extensions.GroupName, *testapi.Extensions.GroupVersion(), unversioned.GroupVersion{Group: extensions.GroupName, Version: runtime.APIVersionInternal})
storageFactory := genericapiserver.NewDefaultStorageFactory(storageConfig, api.Codecs, resourceEncoding, DefaultAPIResourceConfigSource()) storageFactory := genericapiserver.NewDefaultStorageFactory(storageConfig, testapi.StorageMediaType(), api.Codecs, resourceEncoding, DefaultAPIResourceConfigSource())
config.StorageFactory = storageFactory config.StorageFactory = storageFactory
config.APIResourceConfigSource = DefaultAPIResourceConfigSource() config.APIResourceConfigSource = DefaultAPIResourceConfigSource()

View File

@ -90,7 +90,7 @@ func hasCreated(t *testing.T, pod *api.Pod) func(runtime.Object) bool {
func NewTestGenericStoreRegistry(t *testing.T) (*etcdtesting.EtcdTestServer, *Store) { func NewTestGenericStoreRegistry(t *testing.T) (*etcdtesting.EtcdTestServer, *Store) {
podPrefix := "/pods" podPrefix := "/pods"
server := etcdtesting.NewEtcdTestClientServer(t) server := etcdtesting.NewEtcdTestClientServer(t)
s := etcdstorage.NewEtcdStorage(server.Client, testapi.Default.Codec(), etcdtest.PathPrefix(), false, etcdtest.DeserializationCacheSize) s := etcdstorage.NewEtcdStorage(server.Client, testapi.Default.StorageCodec(), etcdtest.PathPrefix(), false, etcdtest.DeserializationCacheSize)
strategy := &testRESTStrategy{api.Scheme, api.SimpleNameGenerator, true, false, true} strategy := &testRESTStrategy{api.Scheme, api.SimpleNameGenerator, true, false, true}
return server, &Store{ return server, &Store{

View File

@ -38,7 +38,7 @@ import (
func NewEtcdStorage(t *testing.T, group string) (storage.Interface, *etcdtesting.EtcdTestServer) { func NewEtcdStorage(t *testing.T, group string) (storage.Interface, *etcdtesting.EtcdTestServer) {
server := etcdtesting.NewEtcdTestClientServer(t) server := etcdtesting.NewEtcdTestClientServer(t)
storage := etcdstorage.NewEtcdStorage(server.Client, testapi.Groups[group].Codec(), etcdtest.PathPrefix(), false, etcdtest.DeserializationCacheSize) storage := etcdstorage.NewEtcdStorage(server.Client, testapi.Groups[group].StorageCodec(), etcdtest.PathPrefix(), false, etcdtest.DeserializationCacheSize)
return storage, server return storage, server
} }

View File

@ -33,6 +33,7 @@ import (
"k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/apis/extensions/v1beta1" "k8s.io/kubernetes/pkg/apis/extensions/v1beta1"
"k8s.io/kubernetes/pkg/runtime" "k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/util/sets"
) )
type thirdPartyObjectConverter struct { type thirdPartyObjectConverter struct {
@ -183,31 +184,39 @@ func NewNegotiatedSerializer(s runtime.NegotiatedSerializer, kind string, encode
} }
} }
func (t *thirdPartyResourceDataCodecFactory) EncoderForVersion(s runtime.Serializer, gv unversioned.GroupVersion) runtime.Encoder { func (t *thirdPartyResourceDataCodecFactory) SupportedMediaTypes() []string {
return NewCodec(runtime.NewCodec( supported := sets.NewString(t.NegotiatedSerializer.SupportedMediaTypes()...)
t.NegotiatedSerializer.EncoderForVersion(s, gv), return supported.Intersection(sets.NewString("application/json", "application/yaml")).List()
t.NegotiatedSerializer.DecoderToVersion(s, t.decodeGV),
), t.kind)
} }
func (t *thirdPartyResourceDataCodecFactory) DecoderToVersion(s runtime.Serializer, gv unversioned.GroupVersion) runtime.Decoder { func (t *thirdPartyResourceDataCodecFactory) SupportedStreamingMediaTypes() []string {
return NewCodec(runtime.NewCodec( supported := sets.NewString(t.NegotiatedSerializer.SupportedStreamingMediaTypes()...)
t.NegotiatedSerializer.EncoderForVersion(s, t.encodeGV), return supported.Intersection(sets.NewString("application/json", "application/json;stream=watch")).List()
t.NegotiatedSerializer.DecoderToVersion(s, gv),
), t.kind)
} }
type thirdPartyResourceDataCodec struct { func (t *thirdPartyResourceDataCodecFactory) EncoderForVersion(s runtime.Encoder, gv unversioned.GroupVersion) runtime.Encoder {
delegate runtime.Codec return &thirdPartyResourceDataEncoder{delegate: t.NegotiatedSerializer.EncoderForVersion(s, gv), kind: t.kind}
}
func (t *thirdPartyResourceDataCodecFactory) DecoderToVersion(s runtime.Decoder, gv unversioned.GroupVersion) runtime.Decoder {
return NewDecoder(t.NegotiatedSerializer.DecoderToVersion(s, gv), t.kind)
}
func NewCodec(delegate runtime.Codec, kind string) runtime.Codec {
return runtime.NewCodec(NewEncoder(delegate, kind), NewDecoder(delegate, kind))
}
type thirdPartyResourceDataDecoder struct {
delegate runtime.Decoder
kind string kind string
} }
var _ runtime.Codec = &thirdPartyResourceDataCodec{} func NewDecoder(delegate runtime.Decoder, kind string) runtime.Decoder {
return &thirdPartyResourceDataDecoder{delegate: delegate, kind: kind}
func NewCodec(codec runtime.Codec, kind string) runtime.Codec {
return &thirdPartyResourceDataCodec{codec, kind}
} }
var _ runtime.Decoder = &thirdPartyResourceDataDecoder{}
func parseObject(data []byte) (map[string]interface{}, error) { func parseObject(data []byte) (map[string]interface{}, error) {
var obj interface{} var obj interface{}
if err := json.Unmarshal(data, &obj); err != nil { if err := json.Unmarshal(data, &obj); err != nil {
@ -221,7 +230,7 @@ func parseObject(data []byte) (map[string]interface{}, error) {
return mapObj, nil return mapObj, nil
} }
func (t *thirdPartyResourceDataCodec) populate(data []byte) (runtime.Object, error) { func (t *thirdPartyResourceDataDecoder) populate(data []byte) (runtime.Object, error) {
mapObj, err := parseObject(data) mapObj, err := parseObject(data)
if err != nil { if err != nil {
return nil, err return nil, err
@ -229,7 +238,7 @@ func (t *thirdPartyResourceDataCodec) populate(data []byte) (runtime.Object, err
return t.populateFromObject(mapObj, data) return t.populateFromObject(mapObj, data)
} }
func (t *thirdPartyResourceDataCodec) populateFromObject(mapObj map[string]interface{}, data []byte) (runtime.Object, error) { func (t *thirdPartyResourceDataDecoder) populateFromObject(mapObj map[string]interface{}, data []byte) (runtime.Object, error) {
typeMeta := unversioned.TypeMeta{} typeMeta := unversioned.TypeMeta{}
if err := json.Unmarshal(data, &typeMeta); err != nil { if err := json.Unmarshal(data, &typeMeta); err != nil {
return nil, err return nil, err
@ -252,7 +261,7 @@ func (t *thirdPartyResourceDataCodec) populateFromObject(mapObj map[string]inter
} }
} }
func (t *thirdPartyResourceDataCodec) populateResource(objIn *extensions.ThirdPartyResourceData, mapObj map[string]interface{}, data []byte) error { func (t *thirdPartyResourceDataDecoder) populateResource(objIn *extensions.ThirdPartyResourceData, mapObj map[string]interface{}, data []byte) error {
metadata, ok := mapObj["metadata"].(map[string]interface{}) metadata, ok := mapObj["metadata"].(map[string]interface{})
if !ok { if !ok {
return fmt.Errorf("unexpected object for metadata: %#v", mapObj["metadata"]) return fmt.Errorf("unexpected object for metadata: %#v", mapObj["metadata"])
@ -274,7 +283,7 @@ func (t *thirdPartyResourceDataCodec) populateResource(objIn *extensions.ThirdPa
return nil return nil
} }
func (t *thirdPartyResourceDataCodec) Decode(data []byte, gvk *unversioned.GroupVersionKind, into runtime.Object) (runtime.Object, *unversioned.GroupVersionKind, error) { func (t *thirdPartyResourceDataDecoder) Decode(data []byte, gvk *unversioned.GroupVersionKind, into runtime.Object) (runtime.Object, *unversioned.GroupVersionKind, error) {
if into == nil { if into == nil {
obj, err := t.populate(data) obj, err := t.populate(data)
if err != nil { if err != nil {
@ -347,7 +356,7 @@ func (t *thirdPartyResourceDataCodec) Decode(data []byte, gvk *unversioned.Group
return thirdParty, actual, nil return thirdParty, actual, nil
} }
func (t *thirdPartyResourceDataCodec) populateListResource(objIn *extensions.ThirdPartyResourceDataList, mapObj map[string]interface{}) error { func (t *thirdPartyResourceDataDecoder) populateListResource(objIn *extensions.ThirdPartyResourceDataList, mapObj map[string]interface{}) error {
items, ok := mapObj["items"].([]interface{}) items, ok := mapObj["items"].([]interface{})
if !ok { if !ok {
return fmt.Errorf("unexpected object for items: %#v", mapObj["items"]) return fmt.Errorf("unexpected object for items: %#v", mapObj["items"])
@ -374,6 +383,17 @@ const template = `{
"items": [ %s ] "items": [ %s ]
}` }`
type thirdPartyResourceDataEncoder struct {
delegate runtime.Encoder
kind string
}
func NewEncoder(delegate runtime.Encoder, kind string) runtime.Encoder {
return &thirdPartyResourceDataEncoder{delegate: delegate, kind: kind}
}
var _ runtime.Encoder = &thirdPartyResourceDataEncoder{}
func encodeToJSON(obj *extensions.ThirdPartyResourceData, stream io.Writer) error { func encodeToJSON(obj *extensions.ThirdPartyResourceData, stream io.Writer) error {
var objOut interface{} var objOut interface{}
if err := json.Unmarshal(obj.Data, &objOut); err != nil { if err := json.Unmarshal(obj.Data, &objOut); err != nil {
@ -388,7 +408,7 @@ func encodeToJSON(obj *extensions.ThirdPartyResourceData, stream io.Writer) erro
return encoder.Encode(objMap) return encoder.Encode(objMap)
} }
func (t *thirdPartyResourceDataCodec) EncodeToStream(obj runtime.Object, stream io.Writer, overrides ...unversioned.GroupVersion) (err error) { func (t *thirdPartyResourceDataEncoder) EncodeToStream(obj runtime.Object, stream io.Writer, overrides ...unversioned.GroupVersion) (err error) {
switch obj := obj.(type) { switch obj := obj.(type) {
case *extensions.ThirdPartyResourceData: case *extensions.ThirdPartyResourceData:
return encodeToJSON(obj, stream) return encodeToJSON(obj, stream)

View File

@ -87,13 +87,14 @@ func TestCodec(t *testing.T) {
}, },
} }
for _, test := range tests { for _, test := range tests {
codec := &thirdPartyResourceDataCodec{kind: "Foo", delegate: testapi.Extensions.Codec()} d := &thirdPartyResourceDataDecoder{kind: "Foo", delegate: testapi.Extensions.Codec()}
e := &thirdPartyResourceDataEncoder{kind: "Foo", delegate: testapi.Extensions.Codec()}
data, err := json.Marshal(test.obj) data, err := json.Marshal(test.obj)
if err != nil { if err != nil {
t.Errorf("[%s] unexpected error: %v", test.name, err) t.Errorf("[%s] unexpected error: %v", test.name, err)
continue continue
} }
obj, err := runtime.Decode(codec, data) obj, err := runtime.Decode(d, data)
if err != nil && !test.expectErr { if err != nil && !test.expectErr {
t.Errorf("[%s] unexpected error: %v", test.name, err) t.Errorf("[%s] unexpected error: %v", test.name, err)
continue continue
@ -121,7 +122,7 @@ func TestCodec(t *testing.T) {
t.Errorf("[%s]\nexpected\n%v\nsaw\n%v\n", test.name, test.obj, &output) t.Errorf("[%s]\nexpected\n%v\nsaw\n%v\n", test.name, test.obj, &output)
} }
data, err = runtime.Encode(codec, rsrcObj) data, err = runtime.Encode(e, rsrcObj)
if err != nil { if err != nil {
t.Errorf("[%s] unexpected error: %v", test.name, err) t.Errorf("[%s] unexpected error: %v", test.name, err)
} }

View File

@ -18,6 +18,7 @@ package runtime
import ( import (
"bytes" "bytes"
"encoding/base64"
"fmt" "fmt"
"io" "io"
"net/url" "net/url"
@ -169,3 +170,27 @@ func (c *parameterCodec) EncodeParameters(obj Object, to unversioned.GroupVersio
} }
return queryparams.Convert(obj) return queryparams.Convert(obj)
} }
type base64Serializer struct {
Serializer
}
func NewBase64Serializer(s Serializer) Serializer {
return &base64Serializer{s}
}
func (s base64Serializer) EncodeToStream(obj Object, stream io.Writer, overrides ...unversioned.GroupVersion) error {
e := base64.NewEncoder(base64.StdEncoding, stream)
err := s.Serializer.EncodeToStream(obj, e, overrides...)
e.Close()
return err
}
func (s base64Serializer) Decode(data []byte, defaults *unversioned.GroupVersionKind, into Object) (Object, *unversioned.GroupVersionKind, error) {
out := make([]byte, base64.StdEncoding.DecodedLen(len(data)))
n, err := base64.StdEncoding.Decode(out, data)
if err != nil {
return nil, nil, err
}
return s.Serializer.Decode(out[:n], defaults, into)
}

View File

@ -118,3 +118,33 @@ func DeepCopy_runtime_Scheme(in Scheme, out *Scheme, c *conversion.Cloner) error
} }
return nil return nil
} }
func DeepCopy_runtime_SerializerInfo(in SerializerInfo, out *SerializerInfo, c *conversion.Cloner) error {
if in.Serializer == nil {
out.Serializer = nil
} else if newVal, err := c.DeepCopy(in.Serializer); err != nil {
return err
} else {
out.Serializer = newVal.(Serializer)
}
out.EncodesAsText = in.EncodesAsText
out.MediaType = in.MediaType
return nil
}
func DeepCopy_runtime_StreamSerializerInfo(in StreamSerializerInfo, out *StreamSerializerInfo, c *conversion.Cloner) error {
if err := DeepCopy_runtime_SerializerInfo(in.SerializerInfo, &out.SerializerInfo, c); err != nil {
return err
}
if in.Framer == nil {
out.Framer = nil
} else if newVal, err := c.DeepCopy(in.Framer); err != nil {
return err
} else {
out.Framer = newVal.(Framer)
}
if err := DeepCopy_runtime_SerializerInfo(in.Embedded, &out.Embedded, c); err != nil {
return err
}
return nil
}

View File

@ -86,32 +86,75 @@ type Framer interface {
NewFrameWriter(w io.Writer) io.Writer NewFrameWriter(w io.Writer) io.Writer
} }
// 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
}
// StreamSerializerInfo contains information about a specific stream serialization format
type StreamSerializerInfo struct {
SerializerInfo
// 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 // NegotiatedSerializer is an interface used for obtaining encoders, decoders, and serializers
// for multiple supported media types. // for multiple supported media types. This would commonly be accepted by a server component
// that performs HTTP content negotiation to accept multiple formats.
type NegotiatedSerializer interface { type NegotiatedSerializer interface {
// SupportedMediaTypes is the media types supported for reading and writing single objects. // SupportedMediaTypes is the media types supported for reading and writing single objects.
SupportedMediaTypes() []string SupportedMediaTypes() []string
// SerializerForMediaType returns a serializer for the provided media type. Options is a set of // 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. // parameters applied to the media type that may modify the resulting output. ok will be false
SerializerForMediaType(mediaType string, options map[string]string) (Serializer, bool) // 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. // SupportedStreamingMediaTypes returns the media types of the supported streaming serializers.
// Streaming serializers control how multiple objects are written to a stream output. // Streaming serializers control how multiple objects are written to a stream output.
SupportedStreamingMediaTypes() []string SupportedStreamingMediaTypes() []string
// StreamingSerializerForMediaType returns a serializer for the provided media type that supports // 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 // reading and writing multiple objects to a stream. It returns a framer and serializer, or an
// error if no such serializer can be created. Options is a set of parameters applied to the // 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. // media type that may modify the resulting output. ok will be false if no serializer matched
StreamingSerializerForMediaType(mediaType string, options map[string]string) (Serializer, Framer, string, bool) // the media type.
StreamingSerializerForMediaType(mediaType string, params map[string]string) (s StreamSerializerInfo, ok bool)
// EncoderForVersion returns an encoder that ensures objects being written to the provided // EncoderForVersion returns an encoder that ensures objects being written to the provided
// serializer are in the provided group version. // serializer are in the provided group version.
// TODO: take multiple group versions // TODO: take multiple group versions
EncoderForVersion(serializer Serializer, gv unversioned.GroupVersion) Encoder EncoderForVersion(serializer Encoder, gv unversioned.GroupVersion) Encoder
// DecoderForVersion returns a decoder that ensures objects being read by the provided // DecoderForVersion returns a decoder that ensures objects being read by the provided
// serializer are in the provided group version by default. // serializer are in the provided group version by default.
// TODO: take multiple group versions // TODO: take multiple group versions
DecoderToVersion(serializer Serializer, gv unversioned.GroupVersion) Decoder DecoderToVersion(serializer Decoder, gv unversioned.GroupVersion) Decoder
}
// StorageSerializer is an interface used for obtaining encoders, decoders, and serializers
// 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)
// UniversalDeserializer returns a Serializer that can read objects in multiple supported formats
// by introspecting the data at rest.
UniversalDeserializer() Decoder
// EncoderForVersion returns an encoder that ensures objects being written to the provided
// serializer are in the provided group version.
// TODO: take multiple group versions
EncoderForVersion(serializer Encoder, gv unversioned.GroupVersion) Encoder
// DecoderForVersion returns a decoder that ensures objects being read by the provided
// serializer are in the provided group version by default.
// TODO: take multiple group versions
DecoderToVersion(serializer Decoder, gv unversioned.GroupVersion) Decoder
} }
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////

View File

@ -31,6 +31,8 @@ type serializerType struct {
AcceptContentTypes []string AcceptContentTypes []string
ContentType string ContentType string
FileExtensions []string FileExtensions []string
// EncodesAsText should be true if this content type can be represented safely in UTF-8
EncodesAsText bool
Serializer runtime.Serializer Serializer runtime.Serializer
PrettySerializer runtime.Serializer PrettySerializer runtime.Serializer
@ -65,6 +67,7 @@ func newSerializersForScheme(scheme *runtime.Scheme, mf json.MetaFactory) []seri
AcceptContentTypes: []string{"application/json"}, AcceptContentTypes: []string{"application/json"},
ContentType: "application/json", ContentType: "application/json",
FileExtensions: []string{"json"}, FileExtensions: []string{"json"},
EncodesAsText: true,
Serializer: jsonSerializer, Serializer: jsonSerializer,
PrettySerializer: jsonPrettySerializer, PrettySerializer: jsonPrettySerializer,
@ -77,6 +80,7 @@ func newSerializersForScheme(scheme *runtime.Scheme, mf json.MetaFactory) []seri
AcceptContentTypes: []string{"application/yaml"}, AcceptContentTypes: []string{"application/yaml"},
ContentType: "application/yaml", ContentType: "application/yaml",
FileExtensions: []string{"yaml"}, FileExtensions: []string{"yaml"},
EncodesAsText: true,
Serializer: yamlSerializer, Serializer: yamlSerializer,
// TODO: requires runtime.RawExtension to properly distinguish when the nested content is // TODO: requires runtime.RawExtension to properly distinguish when the nested content is
@ -206,62 +210,91 @@ func (f CodecFactory) UniversalDeserializer() runtime.Decoder {
// //
// TODO: the decoder will eventually be removed in favor of dealing with objects in their versioned form // TODO: the decoder will eventually be removed in favor of dealing with objects in their versioned form
func (f CodecFactory) UniversalDecoder(versions ...unversioned.GroupVersion) runtime.Decoder { func (f CodecFactory) UniversalDecoder(versions ...unversioned.GroupVersion) runtime.Decoder {
return f.CodecForVersions(runtime.NoopEncoder{Decoder: f.universal}, nil, versions) return f.CodecForVersions(nil, f.universal, nil, versions)
} }
// CodecFor creates a codec with the provided serializer. If an object is decoded and its group is not in the list, // CodecFor creates a codec with the provided serializer. If an object is decoded and its group is not in the list,
// it will default to runtime.APIVersionInternal. If encode is not specified for an object's group, the object is not // it will default to runtime.APIVersionInternal. If encode is not specified for an object's group, the object is not
// converted. If encode or decode are nil, no conversion is performed. // converted. If encode or decode are nil, no conversion is performed.
func (f CodecFactory) CodecForVersions(serializer runtime.Serializer, encode []unversioned.GroupVersion, decode []unversioned.GroupVersion) runtime.Codec { func (f CodecFactory) CodecForVersions(encoder runtime.Encoder, decoder runtime.Decoder, encode []unversioned.GroupVersion, decode []unversioned.GroupVersion) runtime.Codec {
return versioning.NewCodecForScheme(f.scheme, serializer, serializer, encode, decode) return versioning.NewCodecForScheme(f.scheme, encoder, decoder, encode, decode)
} }
// DecoderToVersion returns a decoder that targets the provided group version. // DecoderToVersion returns a decoder that targets the provided group version.
func (f CodecFactory) DecoderToVersion(serializer runtime.Serializer, gv unversioned.GroupVersion) runtime.Decoder { func (f CodecFactory) DecoderToVersion(decoder runtime.Decoder, gv unversioned.GroupVersion) runtime.Decoder {
return f.CodecForVersions(serializer, nil, []unversioned.GroupVersion{gv}) return f.CodecForVersions(nil, decoder, nil, []unversioned.GroupVersion{gv})
} }
// EncoderForVersion returns an encoder that targets the provided group version. // EncoderForVersion returns an encoder that targets the provided group version.
func (f CodecFactory) EncoderForVersion(serializer runtime.Serializer, gv unversioned.GroupVersion) runtime.Encoder { func (f CodecFactory) EncoderForVersion(encoder runtime.Encoder, gv unversioned.GroupVersion) runtime.Encoder {
return f.CodecForVersions(serializer, []unversioned.GroupVersion{gv}, nil) return f.CodecForVersions(encoder, nil, []unversioned.GroupVersion{gv}, nil)
} }
// SerializerForMediaType returns a serializer that matches the provided RFC2046 mediaType, or false if no such // SerializerForMediaType returns a serializer that matches the provided RFC2046 mediaType, or false if no such
// serializer exists // serializer exists
func (f CodecFactory) SerializerForMediaType(mediaType string, options map[string]string) (runtime.Serializer, bool) { func (f CodecFactory) SerializerForMediaType(mediaType string, params map[string]string) (runtime.SerializerInfo, bool) {
for _, s := range f.serializers { for _, s := range f.serializers {
for _, accepted := range s.AcceptContentTypes { for _, accepted := range s.AcceptContentTypes {
if accepted == mediaType { if accepted == mediaType {
if s.Specialize != nil && len(options) > 0 { // specialization abstracts variants to the content type
serializer, ok := s.Specialize(options) if s.Specialize != nil && len(params) > 0 {
return serializer, ok serializer, ok := s.Specialize(params)
// TODO: return formatted mediaType+params
return runtime.SerializerInfo{Serializer: serializer, MediaType: s.ContentType, EncodesAsText: s.EncodesAsText}, ok
} }
if v, ok := options["pretty"]; ok && v == "1" && s.PrettySerializer != nil {
return s.PrettySerializer, true // 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 s.Serializer, true
// return the base variant
return runtime.SerializerInfo{Serializer: s.Serializer, MediaType: s.ContentType, EncodesAsText: s.EncodesAsText}, true
} }
} }
} }
return nil, false return runtime.SerializerInfo{}, false
} }
// StreamingSerializerForMediaType returns a serializer that matches the provided RFC2046 mediaType, or false if no such // StreamingSerializerForMediaType returns a serializer that matches the provided RFC2046 mediaType, or false if no such
// serializer exists // serializer exists
func (f CodecFactory) StreamingSerializerForMediaType(mediaType string, options map[string]string) (runtime.Serializer, runtime.Framer, string, bool) { func (f CodecFactory) StreamingSerializerForMediaType(mediaType string, params map[string]string) (runtime.StreamSerializerInfo, bool) {
for _, s := range f.serializers { for _, s := range f.serializers {
for _, accepted := range s.AcceptStreamContentTypes { for _, accepted := range s.AcceptStreamContentTypes {
if accepted == mediaType { if accepted == mediaType {
if s.StreamSpecialize != nil && len(options) > 0 { // TODO: accept params
serializer, ok := s.StreamSpecialize(options) nested, ok := f.SerializerForMediaType(s.ContentType, nil)
// TODO: have StreamSpecialize return exact content type if !ok {
return serializer, s.Framer, s.StreamContentType, ok panic("no serializer defined for internal content type")
} }
return s.StreamSerializer, s.Framer, s.StreamContentType, true
if s.StreamSpecialize != nil && len(params) > 0 {
serializer, ok := s.StreamSpecialize(params)
// TODO: return formatted mediaType+params
return runtime.StreamSerializerInfo{
SerializerInfo: runtime.SerializerInfo{
Serializer: serializer,
MediaType: s.StreamContentType,
EncodesAsText: s.EncodesAsText,
},
Framer: s.Framer,
Embedded: nested,
}, ok
}
return runtime.StreamSerializerInfo{
SerializerInfo: runtime.SerializerInfo{
Serializer: s.StreamSerializer,
MediaType: s.StreamContentType,
EncodesAsText: s.EncodesAsText,
},
Framer: s.Framer,
Embedded: nested,
}, true
} }
} }
} }
return nil, nil, "", false return runtime.StreamSerializerInfo{}, false
} }
// SerializerForFileExtension returns a serializer for the provided extension, or false if no serializer matches. // SerializerForFileExtension returns a serializer for the provided extension, or false if no serializer matches.

View File

@ -267,7 +267,7 @@ func TestVersionedEncoding(t *testing.T) {
encoder, _ := cf.SerializerForFileExtension("json") encoder, _ := cf.SerializerForFileExtension("json")
// codec that is unversioned uses the target version // codec that is unversioned uses the target version
unversionedCodec := cf.CodecForVersions(encoder, nil, nil) unversionedCodec := cf.CodecForVersions(encoder, nil, nil, nil)
_, err = runtime.Encode(unversionedCodec, &TestType1{}, unversioned.GroupVersion{Version: "v3"}) _, err = runtime.Encode(unversionedCodec, &TestType1{}, unversioned.GroupVersion{Version: "v3"})
if err == nil || !runtime.IsNotRegisteredError(err) { if err == nil || !runtime.IsNotRegisteredError(err) {
t.Fatal(err) t.Fatal(err)

View File

@ -186,20 +186,13 @@ func (s *Serializer) EncodeToStream(obj runtime.Object, w io.Writer, overrides .
} }
// RecognizesData implements the RecognizingDecoder interface. // RecognizesData implements the RecognizingDecoder interface.
func (s *Serializer) RecognizesData(peek io.Reader) (bool, error) { func (s *Serializer) RecognizesData(peek io.Reader) (ok, unknown bool, err error) {
_, ok := utilyaml.GuessJSONStream(peek, 2048)
if s.yaml { if s.yaml {
return !ok, nil // we could potentially look for '---'
return false, true, nil
} }
return ok, nil _, ok = utilyaml.GuessJSONStream(peek, 2048)
} return ok, false, nil
// EncodesAsText returns true because both JSON and YAML are considered textual representations
// of data. This is used to determine whether the serialized object should be transmitted over
// a WebSocket Text or Binary frame. This must remain true for legacy compatibility with v1.1
// watch over websocket implementations.
func (s *Serializer) EncodesAsText() bool {
return true
} }
// Framer is the default JSON framing behavior, with newlines delimiting individual objects. // Framer is the default JSON framing behavior, with newlines delimiting individual objects.

View File

@ -24,35 +24,34 @@ import (
// TODO: We should figure out what happens when someone asks // TODO: We should figure out what happens when someone asks
// encoder for version and it conflicts with the raw serializer. // encoder for version and it conflicts with the raw serializer.
type negotiatedSerializerWrapper struct { type negotiatedSerializerWrapper struct {
serializer runtime.Serializer info runtime.SerializerInfo
streamingSerializer runtime.Serializer streamInfo runtime.StreamSerializerInfo
framer runtime.Framer
} }
func NegotiatedSerializerWrapper(serializer, streamingSerializer runtime.Serializer, framer runtime.Framer) runtime.NegotiatedSerializer { func NegotiatedSerializerWrapper(info runtime.SerializerInfo, streamInfo runtime.StreamSerializerInfo) runtime.NegotiatedSerializer {
return &negotiatedSerializerWrapper{serializer, streamingSerializer, framer} return &negotiatedSerializerWrapper{info, streamInfo}
} }
func (n *negotiatedSerializerWrapper) SupportedMediaTypes() []string { func (n *negotiatedSerializerWrapper) SupportedMediaTypes() []string {
return []string{} return []string{}
} }
func (n *negotiatedSerializerWrapper) SerializerForMediaType(mediaType string, options map[string]string) (runtime.Serializer, bool) { func (n *negotiatedSerializerWrapper) SerializerForMediaType(mediaType string, options map[string]string) (runtime.SerializerInfo, bool) {
return n.serializer, true return n.info, true
} }
func (n *negotiatedSerializerWrapper) SupportedStreamingMediaTypes() []string { func (n *negotiatedSerializerWrapper) SupportedStreamingMediaTypes() []string {
return []string{} return []string{}
} }
func (n *negotiatedSerializerWrapper) StreamingSerializerForMediaType(mediaType string, options map[string]string) (runtime.Serializer, runtime.Framer, string, bool) { func (n *negotiatedSerializerWrapper) StreamingSerializerForMediaType(mediaType string, options map[string]string) (runtime.StreamSerializerInfo, bool) {
return n.streamingSerializer, n.framer, "", true return n.streamInfo, true
} }
func (n *negotiatedSerializerWrapper) EncoderForVersion(serializer runtime.Serializer, gv unversioned.GroupVersion) runtime.Encoder { func (n *negotiatedSerializerWrapper) EncoderForVersion(e runtime.Encoder, _ unversioned.GroupVersion) runtime.Encoder {
return n.serializer return e
} }
func (n *negotiatedSerializerWrapper) DecoderToVersion(serializer runtime.Serializer, gv unversioned.GroupVersion) runtime.Decoder { func (n *negotiatedSerializerWrapper) DecoderToVersion(d runtime.Decoder, _gv unversioned.GroupVersion) runtime.Decoder {
return n.serializer return d
} }

View File

@ -419,12 +419,6 @@ func (s *RawSerializer) EncodeToStream(obj runtime.Object, w io.Writer, override
} }
} }
// RecognizesData implements the RecognizingDecoder interface - objects encoded with this serializer
// have no innate identifying information and so cannot be recognized.
func (s *RawSerializer) RecognizesData(peek io.Reader) (bool, error) {
return false, nil
}
var LengthDelimitedFramer = lengthDelimitedFramer{} var LengthDelimitedFramer = lengthDelimitedFramer{}
type lengthDelimitedFramer struct{} type lengthDelimitedFramer struct{}

View File

@ -297,6 +297,18 @@ func TestDecodeObjects(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
unk2 := &runtime.Unknown{
TypeMeta: runtime.TypeMeta{Kind: "Pod", APIVersion: "v1"},
}
wire2 := make([]byte, len(wire1)*2)
n, err := unk2.NestedMarshalTo(wire2, obj1, uint64(obj1.Size()))
if err != nil {
t.Fatal(err)
}
if n != len(wire1) || !bytes.Equal(wire1, wire2[:n]) {
t.Fatalf("unexpected wire:\n%s", hex.Dump(wire2[:n]))
}
wire1 = append([]byte{0x6b, 0x38, 0x73, 0x00}, wire1...) wire1 = append([]byte{0x6b, 0x38, 0x73, 0x00}, wire1...)
testCases := []struct { testCases := []struct {

View File

@ -17,6 +17,7 @@ limitations under the License.
package recognizer package recognizer
import ( import (
"bufio"
"bytes" "bytes"
"fmt" "fmt"
"io" "io"
@ -27,51 +28,98 @@ import (
type RecognizingDecoder interface { type RecognizingDecoder interface {
runtime.Decoder runtime.Decoder
RecognizesData(peek io.Reader) (bool, error) // RecognizesData should return true if the input provided in the provided reader
// belongs to this decoder, or an error if the data could not be read or is ambiguous.
// Unknown is true if the data could not be determined to match the decoder type.
// Decoders should assume that they can read as much of peek as they need (as the caller
// provides) and may return unknown if the data provided is not sufficient to make a
// a determination. When peek returns EOF that may mean the end of the input or the
// end of buffered input - recognizers should return the best guess at that time.
RecognizesData(peek io.Reader) (ok, unknown bool, err error)
} }
// NewDecoder creates a decoder that will attempt multiple decoders in an order defined
// by:
//
// 1. The decoder implements RecognizingDecoder and identifies the data
// 2. All other decoders, and any decoder that returned true for unknown.
//
// The order passed to the constructor is preserved within those priorities.
func NewDecoder(decoders ...runtime.Decoder) runtime.Decoder { func NewDecoder(decoders ...runtime.Decoder) runtime.Decoder {
recognizing, blind := []RecognizingDecoder{}, []runtime.Decoder{}
for _, d := range decoders {
if r, ok := d.(RecognizingDecoder); ok {
recognizing = append(recognizing, r)
} else {
blind = append(blind, d)
}
}
return &decoder{ return &decoder{
recognizing: recognizing, decoders: decoders,
blind: blind,
} }
} }
type decoder struct { type decoder struct {
recognizing []RecognizingDecoder decoders []runtime.Decoder
blind []runtime.Decoder }
var _ RecognizingDecoder = &decoder{}
func (d *decoder) RecognizesData(peek io.Reader) (bool, bool, error) {
var (
lastErr error
anyUnknown bool
)
data, _ := bufio.NewReaderSize(peek, 1024).Peek(1024)
for _, r := range d.decoders {
switch t := r.(type) {
case RecognizingDecoder:
ok, unknown, err := t.RecognizesData(bytes.NewBuffer(data))
if err != nil {
lastErr = err
continue
}
anyUnknown = anyUnknown || unknown
if !ok {
continue
}
return true, false, nil
}
}
return false, anyUnknown, lastErr
} }
func (d *decoder) Decode(data []byte, gvk *unversioned.GroupVersionKind, into runtime.Object) (runtime.Object, *unversioned.GroupVersionKind, error) { func (d *decoder) Decode(data []byte, gvk *unversioned.GroupVersionKind, into runtime.Object) (runtime.Object, *unversioned.GroupVersionKind, error) {
var lastErr error var (
for _, r := range d.recognizing { lastErr error
buf := bytes.NewBuffer(data) skipped []runtime.Decoder
ok, err := r.RecognizesData(buf) )
if err != nil {
lastErr = err // try recognizers, record any decoders we need to give a chance later
continue for _, r := range d.decoders {
switch t := r.(type) {
case RecognizingDecoder:
buf := bytes.NewBuffer(data)
ok, unknown, err := t.RecognizesData(buf)
if err != nil {
lastErr = err
continue
}
if unknown {
skipped = append(skipped, t)
continue
}
if !ok {
continue
}
return r.Decode(data, gvk, into)
default:
skipped = append(skipped, t)
} }
if !ok {
continue
}
return r.Decode(data, gvk, into)
} }
for _, d := range d.blind {
out, actual, err := d.Decode(data, gvk, into) // try recognizers that returned unknown or didn't recognize their data
for _, r := range skipped {
out, actual, err := r.Decode(data, gvk, into)
if err != nil { if err != nil {
lastErr = err lastErr = err
continue continue
} }
return out, actual, nil return out, actual, nil
} }
if lastErr == nil { if lastErr == nil {
lastErr = fmt.Errorf("no serialization format matched the provided data") lastErr = fmt.Errorf("no serialization format matched the provided data")
} }

View File

@ -27,6 +27,7 @@ import (
// EnableCrossGroupDecoding modifies the given decoder in place, if it is a codec // EnableCrossGroupDecoding modifies the given decoder in place, if it is a codec
// from this package. It allows objects from one group to be auto-decoded into // from this package. It allows objects from one group to be auto-decoded into
// another group. 'destGroup' must already exist in the codec. // another group. 'destGroup' must already exist in the codec.
// TODO: this is an encapsulation violation and should be refactored
func EnableCrossGroupDecoding(d runtime.Decoder, sourceGroup, destGroup string) error { func EnableCrossGroupDecoding(d runtime.Decoder, sourceGroup, destGroup string) error {
internal, ok := d.(*codec) internal, ok := d.(*codec)
if !ok { if !ok {
@ -45,6 +46,7 @@ func EnableCrossGroupDecoding(d runtime.Decoder, sourceGroup, destGroup string)
// EnableCrossGroupEncoding modifies the given encoder in place, if it is a codec // EnableCrossGroupEncoding modifies the given encoder in place, if it is a codec
// from this package. It allows objects from one group to be auto-decoded into // from this package. It allows objects from one group to be auto-decoded into
// another group. 'destGroup' must already exist in the codec. // another group. 'destGroup' must already exist in the codec.
// TODO: this is an encapsulation violation and should be refactored
func EnableCrossGroupEncoding(e runtime.Encoder, sourceGroup, destGroup string) error { func EnableCrossGroupEncoding(e runtime.Encoder, sourceGroup, destGroup string) error {
internal, ok := e.(*codec) internal, ok := e.(*codec)
if !ok { if !ok {

View File

@ -86,9 +86,13 @@ func New(kubeConfigFile string) (*WebhookAuthorizer, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
serializer := json.NewSerializer(json.DefaultMetaFactory, api.Scheme, runtime.ObjectTyperToTyper(api.Scheme), false) serializer := json.NewSerializer(json.DefaultMetaFactory, api.Scheme, runtime.ObjectTyperToTyper(api.Scheme), false)
codec := versioning.NewCodecForScheme(api.Scheme, serializer, serializer, encodeVersions, decodeVersions) codec := versioning.NewCodecForScheme(api.Scheme, serializer, serializer, encodeVersions, decodeVersions)
clientConfig.ContentConfig.NegotiatedSerializer = runtimeserializer.NegotiatedSerializerWrapper(codec, codec, json.Framer) clientConfig.ContentConfig.NegotiatedSerializer = runtimeserializer.NegotiatedSerializerWrapper(
runtime.SerializerInfo{Serializer: codec},
runtime.StreamSerializerInfo{},
)
restClient, err := restclient.UnversionedRESTClientFor(clientConfig) restClient, err := restclient.UnversionedRESTClientFor(clientConfig)
if err != nil { if err != nil {

View File

@ -155,24 +155,29 @@ func NewMasterConfig() *master.Config {
Prefix: etcdtest.PathPrefix(), Prefix: etcdtest.PathPrefix(),
} }
negotiatedSerializer := NewSingleContentTypeSerializer(api.Scheme, testapi.Default.Codec(), "application/json") negotiatedSerializer := NewSingleContentTypeSerializer(api.Scheme, testapi.Default.Codec(), runtime.ContentTypeJSON)
storageFactory := genericapiserver.NewDefaultStorageFactory(config, negotiatedSerializer, genericapiserver.NewDefaultResourceEncodingConfig(), master.DefaultAPIResourceConfigSource()) storageFactory := genericapiserver.NewDefaultStorageFactory(config, runtime.ContentTypeJSON, negotiatedSerializer, genericapiserver.NewDefaultResourceEncodingConfig(), master.DefaultAPIResourceConfigSource())
storageFactory.SetSerializer( storageFactory.SetSerializer(
unversioned.GroupResource{Group: api.GroupName, Resource: genericapiserver.AllResources}, unversioned.GroupResource{Group: api.GroupName, Resource: genericapiserver.AllResources},
NewSingleContentTypeSerializer(api.Scheme, testapi.Default.Codec(), "application/json")) "",
NewSingleContentTypeSerializer(api.Scheme, testapi.Default.Codec(), runtime.ContentTypeJSON))
storageFactory.SetSerializer( storageFactory.SetSerializer(
unversioned.GroupResource{Group: autoscaling.GroupName, Resource: genericapiserver.AllResources}, unversioned.GroupResource{Group: autoscaling.GroupName, Resource: genericapiserver.AllResources},
NewSingleContentTypeSerializer(api.Scheme, testapi.Autoscaling.Codec(), "application/json")) "",
NewSingleContentTypeSerializer(api.Scheme, testapi.Autoscaling.Codec(), runtime.ContentTypeJSON))
storageFactory.SetSerializer( storageFactory.SetSerializer(
unversioned.GroupResource{Group: batch.GroupName, Resource: genericapiserver.AllResources}, unversioned.GroupResource{Group: batch.GroupName, Resource: genericapiserver.AllResources},
NewSingleContentTypeSerializer(api.Scheme, testapi.Batch.Codec(), "application/json")) "",
NewSingleContentTypeSerializer(api.Scheme, testapi.Batch.Codec(), runtime.ContentTypeJSON))
storageFactory.SetSerializer( storageFactory.SetSerializer(
unversioned.GroupResource{Group: apps.GroupName, Resource: genericapiserver.AllResources}, unversioned.GroupResource{Group: apps.GroupName, Resource: genericapiserver.AllResources},
NewSingleContentTypeSerializer(api.Scheme, testapi.Apps.Codec(), "application/json")) "",
NewSingleContentTypeSerializer(api.Scheme, testapi.Apps.Codec(), runtime.ContentTypeJSON))
storageFactory.SetSerializer( storageFactory.SetSerializer(
unversioned.GroupResource{Group: extensions.GroupName, Resource: genericapiserver.AllResources}, unversioned.GroupResource{Group: extensions.GroupName, Resource: genericapiserver.AllResources},
NewSingleContentTypeSerializer(api.Scheme, testapi.Extensions.Codec(), "application/json")) "",
NewSingleContentTypeSerializer(api.Scheme, testapi.Extensions.Codec(), runtime.ContentTypeJSON))
return &master.Config{ return &master.Config{
Config: &genericapiserver.Config{ Config: &genericapiserver.Config{

View File

@ -23,7 +23,7 @@ import (
) )
// NewSingleContentTypeSerializer wraps a serializer in a NegotiatedSerializer that handles one content type // NewSingleContentTypeSerializer wraps a serializer in a NegotiatedSerializer that handles one content type
func NewSingleContentTypeSerializer(scheme *runtime.Scheme, serializer runtime.Serializer, contentType string) runtime.NegotiatedSerializer { func NewSingleContentTypeSerializer(scheme *runtime.Scheme, serializer runtime.Serializer, contentType string) runtime.StorageSerializer {
return &wrappedSerializer{ return &wrappedSerializer{
scheme: scheme, scheme: scheme,
serializer: serializer, serializer: serializer,
@ -37,29 +37,31 @@ type wrappedSerializer struct {
contentType string contentType string
} }
var _ runtime.NegotiatedSerializer = &wrappedSerializer{} var _ runtime.StorageSerializer = &wrappedSerializer{}
func (s *wrappedSerializer) SupportedMediaTypes() []string { func (s *wrappedSerializer) SupportedMediaTypes() []string {
return []string{s.contentType} return []string{s.contentType}
} }
func (s *wrappedSerializer) SerializerForMediaType(mediaType string, options map[string]string) (runtime.Serializer, bool) { func (s *wrappedSerializer) SerializerForMediaType(mediaType string, options map[string]string) (runtime.SerializerInfo, bool) {
if mediaType != s.contentType { if mediaType != s.contentType {
return nil, false return runtime.SerializerInfo{}, false
} }
return s.serializer, true return runtime.SerializerInfo{
} Serializer: s.serializer,
func (s *wrappedSerializer) SupportedStreamingMediaTypes() []string { MediaType: mediaType,
return nil EncodesAsText: true, // TODO: this should be parameterized
} }, true
func (s *wrappedSerializer) StreamingSerializerForMediaType(mediaType string, options map[string]string) (runtime.Serializer, runtime.Framer, string, bool) {
return nil, nil, "", false
} }
func (s *wrappedSerializer) EncoderForVersion(serializer runtime.Serializer, gv unversioned.GroupVersion) runtime.Encoder { func (s *wrappedSerializer) UniversalDeserializer() runtime.Decoder {
return versioning.NewCodec(s.serializer, s.serializer, s.scheme, s.scheme, s.scheme, runtime.ObjectTyperToTyper(s.scheme), []unversioned.GroupVersion{gv}, nil) return s.serializer
} }
func (s *wrappedSerializer) DecoderToVersion(serializer runtime.Serializer, gv unversioned.GroupVersion) runtime.Decoder { func (s *wrappedSerializer) EncoderForVersion(encoder runtime.Encoder, gv unversioned.GroupVersion) runtime.Encoder {
return versioning.NewCodec(s.serializer, s.serializer, s.scheme, s.scheme, s.scheme, runtime.ObjectTyperToTyper(s.scheme), nil, []unversioned.GroupVersion{gv}) return versioning.NewCodec(encoder, nil, s.scheme, s.scheme, s.scheme, runtime.ObjectTyperToTyper(s.scheme), []unversioned.GroupVersion{gv}, nil)
}
func (s *wrappedSerializer) DecoderToVersion(decoder runtime.Decoder, gv unversioned.GroupVersion) runtime.Decoder {
return versioning.NewCodec(nil, decoder, s.scheme, s.scheme, s.scheme, runtime.ObjectTyperToTyper(s.scheme), nil, []unversioned.GroupVersion{gv})
} }