Remove codec awareness from conversion

Allow convertors to be specialized, some cleanup to how conversion
functions are stored internally to allow better reuse.
This commit is contained in:
Clayton Coleman 2015-12-21 00:07:15 -05:00
parent 9d5df20efe
commit 6582b4c2ea
16 changed files with 506 additions and 1100 deletions

View File

@ -40,8 +40,11 @@ type DebugLogger interface {
type Converter struct { type Converter struct {
// Map from the conversion pair to a function which can // Map from the conversion pair to a function which can
// do the conversion. // do the conversion.
conversionFuncs map[typePair]reflect.Value conversionFuncs ConversionFuncs
generatedConversionFuncs map[typePair]reflect.Value generatedConversionFuncs ConversionFuncs
// Set of conversions that should be treated as a no-op
ignoredConversions map[typePair]struct{}
// This is a map from a source field type and name, to a list of destination // This is a map from a source field type and name, to a list of destination
// field type and name. // field type and name.
@ -76,21 +79,30 @@ type Converter struct {
// NewConverter creates a new Converter object. // NewConverter creates a new Converter object.
func NewConverter() *Converter { func NewConverter() *Converter {
c := &Converter{ c := &Converter{
conversionFuncs: map[typePair]reflect.Value{}, conversionFuncs: NewConversionFuncs(),
generatedConversionFuncs: map[typePair]reflect.Value{}, generatedConversionFuncs: NewConversionFuncs(),
defaultingFuncs: map[reflect.Type]reflect.Value{}, ignoredConversions: make(map[typePair]struct{}),
defaultingInterfaces: map[reflect.Type]interface{}{}, defaultingFuncs: make(map[reflect.Type]reflect.Value),
defaultingInterfaces: make(map[reflect.Type]interface{}),
nameFunc: func(t reflect.Type) string { return t.Name() }, nameFunc: func(t reflect.Type) string { return t.Name() },
structFieldDests: map[typeNamePair][]typeNamePair{}, structFieldDests: make(map[typeNamePair][]typeNamePair),
structFieldSources: map[typeNamePair][]typeNamePair{}, structFieldSources: make(map[typeNamePair][]typeNamePair),
inputFieldMappingFuncs: map[reflect.Type]FieldMappingFunc{}, inputFieldMappingFuncs: make(map[reflect.Type]FieldMappingFunc),
inputDefaultFlags: map[reflect.Type]FieldMatchingFlags{}, inputDefaultFlags: make(map[reflect.Type]FieldMatchingFlags),
} }
c.RegisterConversionFunc(ByteSliceCopy) c.RegisterConversionFunc(ByteSliceCopy)
return c return c
} }
// WithConversions returns a Converter that is a copy of c but with the additional
// fns merged on top.
func (c *Converter) WithConversions(fns ConversionFuncs) *Converter {
copied := *c
copied.conversionFuncs = c.conversionFuncs.Merge(fns)
return &copied
}
// ByteSliceCopy prevents recursing into every byte // ByteSliceCopy prevents recursing into every byte
func ByteSliceCopy(in *[]byte, out *[]byte, s Scope) error { func ByteSliceCopy(in *[]byte, out *[]byte, s Scope) error {
*out = make([]byte, len(*in)) *out = make([]byte, len(*in))
@ -130,6 +142,42 @@ type Scope interface {
// the value of the source or destination struct tags. // the value of the source or destination struct tags.
type FieldMappingFunc func(key string, sourceTag, destTag reflect.StructTag) (source string, dest string) type FieldMappingFunc func(key string, sourceTag, destTag reflect.StructTag) (source string, dest string)
func NewConversionFuncs() ConversionFuncs {
return ConversionFuncs{fns: make(map[typePair]reflect.Value)}
}
type ConversionFuncs struct {
fns map[typePair]reflect.Value
}
// Add adds the provided conversion functions to the lookup table - they must have the signature
// `func(type1, type2, Scope) error`. Functions are added in the order passed and will override
// previously registered pairs.
func (c ConversionFuncs) Add(fns ...interface{}) error {
for _, fn := range fns {
fv := reflect.ValueOf(fn)
ft := fv.Type()
if err := verifyConversionFunctionSignature(ft); err != nil {
return err
}
c.fns[typePair{ft.In(0).Elem(), ft.In(1).Elem()}] = fv
}
return nil
}
// Merge returns a new ConversionFuncs that contains all conversions from
// both other and c, with other conversions taking precedence.
func (c ConversionFuncs) Merge(other ConversionFuncs) ConversionFuncs {
merged := NewConversionFuncs()
for k, v := range c.fns {
merged.fns[k] = v
}
for k, v := range other.fns {
merged.fns[k] = v
}
return merged
}
// Meta is supplied by Scheme, when it calls Convert. // Meta is supplied by Scheme, when it calls Convert.
type Meta struct { type Meta struct {
SrcVersion string SrcVersion string
@ -296,34 +344,44 @@ func verifyConversionFunctionSignature(ft reflect.Type) error {
// return nil // return nil
// }) // })
func (c *Converter) RegisterConversionFunc(conversionFunc interface{}) error { func (c *Converter) RegisterConversionFunc(conversionFunc interface{}) error {
fv := reflect.ValueOf(conversionFunc) return c.conversionFuncs.Add(conversionFunc)
ft := fv.Type()
if err := verifyConversionFunctionSignature(ft); err != nil {
return err
}
c.conversionFuncs[typePair{ft.In(0).Elem(), ft.In(1).Elem()}] = fv
return nil
} }
// Similar to RegisterConversionFunc, but registers conversion function that were // Similar to RegisterConversionFunc, but registers conversion function that were
// automatically generated. // automatically generated.
func (c *Converter) RegisterGeneratedConversionFunc(conversionFunc interface{}) error { func (c *Converter) RegisterGeneratedConversionFunc(conversionFunc interface{}) error {
fv := reflect.ValueOf(conversionFunc) return c.generatedConversionFuncs.Add(conversionFunc)
ft := fv.Type() }
if err := verifyConversionFunctionSignature(ft); err != nil {
return err // RegisterIgnoredConversion registers a "no-op" for conversion, where any requested
// conversion between from and to is ignored.
func (c *Converter) RegisterIgnoredConversion(from, to interface{}) error {
typeFrom := reflect.TypeOf(from)
typeTo := reflect.TypeOf(to)
if reflect.TypeOf(from).Kind() != reflect.Ptr {
return fmt.Errorf("expected pointer arg for 'from' param 0, got: %v", typeFrom)
} }
c.generatedConversionFuncs[typePair{ft.In(0).Elem(), ft.In(1).Elem()}] = fv if typeTo.Kind() != reflect.Ptr {
return fmt.Errorf("expected pointer arg for 'to' param 1, got: %v", typeTo)
}
c.ignoredConversions[typePair{typeFrom.Elem(), typeTo.Elem()}] = struct{}{}
return nil return nil
} }
// IsConversionIgnored returns true if the specified objects should be dropped during
// conversion.
func (c *Converter) IsConversionIgnored(inType, outType reflect.Type) bool {
_, found := c.ignoredConversions[typePair{inType, outType}]
return found
}
func (c *Converter) HasConversionFunc(inType, outType reflect.Type) bool { func (c *Converter) HasConversionFunc(inType, outType reflect.Type) bool {
_, found := c.conversionFuncs[typePair{inType, outType}] _, found := c.conversionFuncs.fns[typePair{inType, outType}]
return found return found
} }
func (c *Converter) ConversionFuncValue(inType, outType reflect.Type) (reflect.Value, bool) { func (c *Converter) ConversionFuncValue(inType, outType reflect.Type) (reflect.Value, bool) {
value, found := c.conversionFuncs[typePair{inType, outType}] value, found := c.conversionFuncs.fns[typePair{inType, outType}]
return value, found return value, found
} }
@ -509,16 +567,26 @@ func (c *Converter) convert(sv, dv reflect.Value, scope *scope) error {
fv.Call(args) fv.Call(args)
} }
pair := typePair{st, dt}
// ignore conversions of this type
if _, ok := c.ignoredConversions[pair]; ok {
if c.Debug != nil {
c.Debug.Logf("Ignoring conversion of '%v' to '%v'", st, dt)
}
return nil
}
// Convert sv to dv. // Convert sv to dv.
if fv, ok := c.conversionFuncs[typePair{st, dt}]; ok { if fv, ok := c.conversionFuncs.fns[pair]; ok {
if c.Debug != nil { if c.Debug != nil {
c.Debug.Logf("Calling custom conversion of '%v' to '%v'", st, dt) c.Debug.Logf("Calling custom conversion of '%v' to '%v'", st, dt)
} }
return c.callCustom(sv, dv, fv, scope) return c.callCustom(sv, dv, fv, scope)
} }
if fv, ok := c.generatedConversionFuncs[typePair{st, dt}]; ok { if fv, ok := c.generatedConversionFuncs.fns[pair]; ok {
if c.Debug != nil { if c.Debug != nil {
c.Debug.Logf("Calling custom conversion of '%v' to '%v'", st, dt) c.Debug.Logf("Calling generated conversion of '%v' to '%v'", st, dt)
} }
return c.callCustom(sv, dv, fv, scope) return c.callCustom(sv, dv, fv, scope)
} }

View File

@ -24,6 +24,8 @@ import (
"testing" "testing"
"github.com/google/gofuzz" "github.com/google/gofuzz"
"k8s.io/kubernetes/pkg/api/unversioned"
) )
func testLogger(t *testing.T) DebugLogger { func testLogger(t *testing.T) DebugLogger {
@ -221,6 +223,55 @@ func TestConverter_CallsRegisteredFunctions(t *testing.T) {
} }
} }
func TestConverter_IgnoredConversion(t *testing.T) {
type A struct{}
type B struct{}
count := 0
c := NewConverter()
if err := c.RegisterConversionFunc(func(in *A, out *B, s Scope) error {
count++
return nil
}); err != nil {
t.Fatalf("unexpected error %v", err)
}
if err := c.RegisterIgnoredConversion(&A{}, &B{}); err != nil {
t.Fatal(err)
}
a := A{}
b := B{}
if err := c.Convert(&a, &b, 0, nil); err != nil {
t.Errorf("%v", err)
}
if count != 0 {
t.Errorf("unexpected number of conversion invocations")
}
}
func TestConverter_IgnoredConversionNested(t *testing.T) {
type C string
type A struct {
C C
}
type B struct {
C C
}
c := NewConverter()
typed := C("")
if err := c.RegisterIgnoredConversion(&typed, &typed); err != nil {
t.Fatal(err)
}
a := A{C: C("test")}
b := B{C: C("other")}
if err := c.Convert(&a, &b, AllowDifferentFieldTypeNames, nil); err != nil {
t.Errorf("%v", err)
}
if b.C != C("other") {
t.Errorf("expected no conversion of field C: %#v", b)
}
}
func TestConverter_GeneratedConversionOverriden(t *testing.T) { func TestConverter_GeneratedConversionOverriden(t *testing.T) {
type A struct{} type A struct{}
type B struct{} type B struct{}
@ -243,6 +294,37 @@ func TestConverter_GeneratedConversionOverriden(t *testing.T) {
} }
} }
func TestConverter_WithConversionOverriden(t *testing.T) {
type A struct{}
type B struct{}
c := NewConverter()
if err := c.RegisterConversionFunc(func(in *A, out *B, s Scope) error {
return fmt.Errorf("conversion function should be overriden")
}); err != nil {
t.Fatalf("unexpected error %v", err)
}
if err := c.RegisterGeneratedConversionFunc(func(in *A, out *B, s Scope) error {
return fmt.Errorf("generated function should be overriden")
}); err != nil {
t.Fatalf("unexpected error %v", err)
}
ext := NewConversionFuncs()
ext.Add(func(in *A, out *B, s Scope) error {
return nil
})
newc := c.WithConversions(ext)
a := A{}
b := B{}
if err := c.Convert(&a, &b, 0, nil); err == nil || err.Error() != "conversion function should be overriden" {
t.Errorf("unexpected error: %v", err)
}
if err := newc.Convert(&a, &b, 0, nil); err != nil {
t.Errorf("%v", err)
}
}
func TestConverter_MapsStringArrays(t *testing.T) { func TestConverter_MapsStringArrays(t *testing.T) {
type A struct { type A struct {
Foo string Foo string
@ -681,3 +763,132 @@ func TestConverter_FieldRename(t *testing.T) {
} }
} }
} }
func TestMetaValues(t *testing.T) {
type InternalSimple struct {
APIVersion string `json:"apiVersion,omitempty"`
Kind string `json:"kind,omitempty"`
TestString string `json:"testString"`
}
type ExternalSimple struct {
APIVersion string `json:"apiVersion,omitempty"`
Kind string `json:"kind,omitempty"`
TestString string `json:"testString"`
}
internalGV := unversioned.GroupVersion{Group: "test.group", Version: "__internal"}
externalGV := unversioned.GroupVersion{Group: "test.group", Version: "externalVersion"}
s := NewScheme()
s.AddKnownTypeWithName(internalGV.WithKind("Simple"), &InternalSimple{})
s.AddKnownTypeWithName(externalGV.WithKind("Simple"), &ExternalSimple{})
internalToExternalCalls := 0
externalToInternalCalls := 0
// Register functions to verify that scope.Meta() gets set correctly.
err := s.AddConversionFuncs(
func(in *InternalSimple, out *ExternalSimple, scope Scope) error {
t.Logf("internal -> external")
if e, a := internalGV.String(), scope.Meta().SrcVersion; e != a {
t.Fatalf("Expected '%v', got '%v'", e, a)
}
if e, a := externalGV.String(), scope.Meta().DestVersion; e != a {
t.Fatalf("Expected '%v', got '%v'", e, a)
}
scope.Convert(&in.TestString, &out.TestString, 0)
internalToExternalCalls++
return nil
},
func(in *ExternalSimple, out *InternalSimple, scope Scope) error {
t.Logf("external -> internal")
if e, a := externalGV.String(), scope.Meta().SrcVersion; e != a {
t.Errorf("Expected '%v', got '%v'", e, a)
}
if e, a := internalGV.String(), scope.Meta().DestVersion; e != a {
t.Fatalf("Expected '%v', got '%v'", e, a)
}
scope.Convert(&in.TestString, &out.TestString, 0)
externalToInternalCalls++
return nil
},
)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
simple := &InternalSimple{
TestString: "foo",
}
s.Log(t)
out, err := s.ConvertToVersion(simple, externalGV.String())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
internal, err := s.ConvertToVersion(out, internalGV.String())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if e, a := simple, internal; !reflect.DeepEqual(e, a) {
t.Errorf("Expected:\n %#v,\n Got:\n %#v", e, a)
}
if e, a := 1, internalToExternalCalls; e != a {
t.Errorf("Expected %v, got %v", e, a)
}
if e, a := 1, externalToInternalCalls; e != a {
t.Errorf("Expected %v, got %v", e, a)
}
}
func TestMetaValuesUnregisteredConvert(t *testing.T) {
type InternalSimple struct {
Version string `json:"apiVersion,omitempty"`
Kind string `json:"kind,omitempty"`
TestString string `json:"testString"`
}
type ExternalSimple struct {
Version string `json:"apiVersion,omitempty"`
Kind string `json:"kind,omitempty"`
TestString string `json:"testString"`
}
s := NewScheme()
// We deliberately don't register the types.
internalToExternalCalls := 0
// Register functions to verify that scope.Meta() gets set correctly.
err := s.AddConversionFuncs(
func(in *InternalSimple, out *ExternalSimple, scope Scope) error {
if e, a := "unknown/unknown", scope.Meta().SrcVersion; e != a {
t.Fatalf("Expected '%v', got '%v'", e, a)
}
if e, a := "unknown/unknown", scope.Meta().DestVersion; e != a {
t.Fatalf("Expected '%v', got '%v'", e, a)
}
scope.Convert(&in.TestString, &out.TestString, 0)
internalToExternalCalls++
return nil
},
)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
simple := &InternalSimple{TestString: "foo"}
external := &ExternalSimple{}
err = s.Convert(simple, external)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if e, a := simple.TestString, external.TestString; e != a {
t.Errorf("Expected %v, got %v", e, a)
}
// Verify that our conversion handler got called.
if e, a := 1, internalToExternalCalls; e != a {
t.Errorf("Expected %v, got %v", e, a)
}
}

View File

@ -1,194 +0,0 @@
/*
Copyright 2014 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package conversion
import (
"errors"
"fmt"
"net/url"
"github.com/ugorji/go/codec"
"k8s.io/kubernetes/pkg/api/unversioned"
)
func (s *Scheme) DecodeToVersionedObject(data []byte) (interface{}, unversioned.GroupVersionKind, error) {
kind, err := s.DataKind(data)
if err != nil {
return nil, unversioned.GroupVersionKind{}, err
}
internalGV, exists := s.InternalVersions[kind.Group]
if !exists {
return nil, unversioned.GroupVersionKind{}, fmt.Errorf("no internalVersion specified for %v", kind)
}
if len(kind.Group) == 0 && len(internalGV.Group) != 0 {
return nil, unversioned.GroupVersionKind{}, fmt.Errorf("group not set in '%s'", string(data))
}
if len(kind.Version) == 0 && len(internalGV.Version) != 0 {
return nil, unversioned.GroupVersionKind{}, fmt.Errorf("version not set in '%s'", string(data))
}
if kind.Kind == "" {
return nil, unversioned.GroupVersionKind{}, fmt.Errorf("kind not set in '%s'", string(data))
}
obj, err := s.NewObject(kind)
if err != nil {
return nil, unversioned.GroupVersionKind{}, err
}
if err := codec.NewDecoderBytes(data, new(codec.JsonHandle)).Decode(obj); err != nil {
return nil, unversioned.GroupVersionKind{}, err
}
return obj, kind, nil
}
// Decode converts a JSON string back into a pointer to an api object.
// Deduces the type based upon the fields added by the MetaInsertionFactory
// technique. The object will be converted, if necessary, into the
// s.InternalVersion type before being returned. Decode will not decode
// objects without version set unless InternalVersion is also "".
func (s *Scheme) Decode(data []byte) (interface{}, error) {
return s.DecodeToVersion(data, unversioned.GroupVersion{})
}
// DecodeToVersion converts a JSON string back into a pointer to an api object.
// Deduces the type based upon the fields added by the MetaInsertionFactory
// technique. The object will be converted, if necessary, into the versioned
// type before being returned. Decode will not decode objects without version
// set unless version is also "".
// a GroupVersion with .IsEmpty() == true is means "use the internal version for
// the object's group"
func (s *Scheme) DecodeToVersion(data []byte, targetVersion unversioned.GroupVersion) (interface{}, error) {
obj, sourceKind, err := s.DecodeToVersionedObject(data)
if err != nil {
return nil, err
}
// Version and Kind should be blank in memory.
if err := s.SetVersionAndKind("", "", obj); err != nil {
return nil, err
}
// if the targetVersion is empty, then we want the internal version, but the internal version varies by
// group. We can lookup the group now because we have knowledge of the group
if targetVersion.IsEmpty() {
exists := false
targetVersion, exists = s.InternalVersions[sourceKind.Group]
if !exists {
return nil, fmt.Errorf("no internalVersion specified for %v", targetVersion)
}
}
// Convert if needed.
if targetVersion != sourceKind.GroupVersion() {
objOut, err := s.NewObject(targetVersion.WithKind(sourceKind.Kind))
if err != nil {
return nil, err
}
flags, meta := s.generateConvertMeta(sourceKind.GroupVersion(), targetVersion, obj)
if err := s.converter.Convert(obj, objOut, flags, meta); err != nil {
return nil, err
}
obj = objOut
}
return obj, nil
}
// DecodeInto parses a JSON string and stores it in obj. Returns an error
// if data.Kind is set and doesn't match the type of obj. Obj should be a
// pointer to an api type.
// If obj's version doesn't match that in data, an attempt will be made to convert
// data into obj's version.
func (s *Scheme) DecodeInto(data []byte, obj interface{}) error {
return s.DecodeIntoWithSpecifiedVersionKind(data, obj, unversioned.GroupVersionKind{})
}
// DecodeIntoWithSpecifiedVersionKind compares the passed in requestGroupVersionKind
// with data.Version and data.Kind, defaulting data.Version and
// data.Kind to the specified value if they are empty, or generating an error if
// data.Version and data.Kind are not empty and differ from the specified value.
// The function then implements the functionality of DecodeInto.
// If specifiedVersion and specifiedKind are empty, the function degenerates to
// DecodeInto.
func (s *Scheme) DecodeIntoWithSpecifiedVersionKind(data []byte, obj interface{}, requestedGVK unversioned.GroupVersionKind) error {
if len(data) == 0 {
return errors.New("empty input")
}
dataKind, err := s.DataKind(data)
if err != nil {
return err
}
if len(dataKind.Group) == 0 {
dataKind.Group = requestedGVK.Group
}
if len(dataKind.Version) == 0 {
dataKind.Version = requestedGVK.Version
}
if len(dataKind.Kind) == 0 {
dataKind.Kind = requestedGVK.Kind
}
if len(requestedGVK.Group) > 0 && requestedGVK.Group != dataKind.Group {
return errors.New(fmt.Sprintf("The fully qualified kind in the data (%v) does not match the specified apiVersion(%v)", dataKind, requestedGVK))
}
if len(requestedGVK.Version) > 0 && requestedGVK.Version != dataKind.Version {
return errors.New(fmt.Sprintf("The fully qualified kind in the data (%v) does not match the specified apiVersion(%v)", dataKind, requestedGVK))
}
if len(requestedGVK.Kind) > 0 && requestedGVK.Kind != dataKind.Kind {
return errors.New(fmt.Sprintf("The fully qualified kind in the data (%v) does not match the specified apiVersion(%v)", dataKind, requestedGVK))
}
objGVK, err := s.ObjectKind(obj)
if err != nil {
return err
}
// Assume objects with unset fields are being unmarshalled into the
// correct type.
if len(dataKind.Group) == 0 {
dataKind.Group = objGVK.Group
}
if len(dataKind.Version) == 0 {
dataKind.Version = objGVK.Version
}
if len(dataKind.Kind) == 0 {
dataKind.Kind = objGVK.Kind
}
external, err := s.NewObject(dataKind)
if err != nil {
return err
}
if err := codec.NewDecoderBytes(data, new(codec.JsonHandle)).Decode(external); err != nil {
return err
}
flags, meta := s.generateConvertMeta(dataKind.GroupVersion(), objGVK.GroupVersion(), external)
if err := s.converter.Convert(external, obj, flags, meta); err != nil {
return err
}
// Version and Kind should be blank in memory.
return s.SetVersionAndKind("", "", obj)
}
func (s *Scheme) DecodeParametersInto(parameters url.Values, obj interface{}) error {
if err := s.Convert(&parameters, obj); err != nil {
return err
}
// TODO: Should we do any convertion here?
return nil
}

View File

@ -14,18 +14,11 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
// Package conversion provides go object versioning and encoding/decoding // Package conversion provides go object versioning.
// mechanisms.
// //
// Specifically, conversion provides a way for you to define multiple versions // Specifically, conversion provides a way for you to define multiple versions
// of the same object. You may write functions which implement conversion logic, // of the same object. You may write functions which implement conversion logic,
// but for the fields which did not change, copying is automated. This makes it // but for the fields which did not change, copying is automated. This makes it
// easy to modify the structures you use in memory without affecting the format // easy to modify the structures you use in memory without affecting the format
// you store on disk or respond to in your external API calls. // you store on disk or respond to in your external API calls.
//
// The second offering of this package is automated encoding/decoding. The version
// and type of the object is recorded in the output, so it can be recreated upon
// reading. Currently, conversion writes JSON output, and interprets both JSON
// and YAML input.
//
package conversion package conversion

View File

@ -1,155 +0,0 @@
/*
Copyright 2014 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package conversion
import (
"bytes"
"encoding/json"
"fmt"
"io"
"path"
"k8s.io/kubernetes/pkg/api/unversioned"
)
// EncodeToVersion turns the given api object into an appropriate JSON string.
// Obj may be a pointer to a struct, or a struct. If a struct, a copy
// will be made, therefore it's recommended to pass a pointer to a
// struct. The type must have been registered.
//
// Memory/wire format differences:
// * Having to keep track of the Kind and Version fields makes tests
// very annoying, so the rule is that they are set only in wire format
// (json), not when in native (memory) format. This is possible because
// both pieces of information are implicit in the go typed object.
// * An exception: note that, if there are embedded API objects of known
// type, for example, PodList{... Items []Pod ...}, these embedded
// objects must be of the same version of the object they are embedded
// within, and their Version and Kind must both be empty.
// * Note that the exception does not apply to a generic APIObject type
// which recursively does Encode()/Decode(), and is capable of
// expressing any API object.
// * Only versioned objects should be encoded. This means that, if you pass
// a native object, Encode will convert it to a versioned object. For
// example, an api.Pod will get converted to a v1.Pod. However, if
// you pass in an object that's already versioned (v1.Pod), Encode
// will not modify it.
//
// The purpose of the above complex conversion behavior is to allow us to
// change the memory format yet not break compatibility with any stored
// objects, whether they be in our storage layer (e.g., etcd), or in user's
// config files.
//
func (s *Scheme) EncodeToVersion(obj interface{}, destVersion string) (data []byte, err error) {
buff := &bytes.Buffer{}
if err := s.EncodeToVersionStream(obj, destVersion, buff); err != nil {
return nil, err
}
return buff.Bytes(), nil
}
func (s *Scheme) EncodeToVersionStream(obj interface{}, destGroupVersionString string, stream io.Writer) error {
obj = maybeCopy(obj)
v, _ := EnforcePtr(obj) // maybeCopy guarantees a pointer
// Don't encode an object defined in the unversioned package, unless if the
// destGroupVersionString is v1, encode it to v1 for backward compatibility.
pkg := path.Base(v.Type().PkgPath())
if pkg == "unversioned" && destGroupVersionString != "v1" {
// TODO: convert this to streaming too
data, err := s.encodeUnversionedObject(obj)
if err != nil {
return err
}
_, err = stream.Write(data)
return err
}
if _, registered := s.typeToGVK[v.Type()]; !registered {
return fmt.Errorf("type %v is not registered for %q and it will be impossible to Decode it, therefore Encode will refuse to encode it.", v.Type(), destGroupVersionString)
}
objKind, err := s.ObjectKind(obj)
if err != nil {
return err
}
destVersion, err := unversioned.ParseGroupVersion(destGroupVersionString)
if err != nil {
return err
}
// Perform a conversion if necessary.
if objKind.GroupVersion() != destVersion {
objOut, err := s.NewObject(destVersion.WithKind(objKind.Kind))
if err != nil {
return err
}
flags, meta := s.generateConvertMeta(objKind.GroupVersion(), destVersion, obj)
err = s.converter.Convert(obj, objOut, flags, meta)
if err != nil {
return err
}
obj = objOut
// ensure the output object name comes from the destination type
newGroupVersionKind, err := s.ObjectKind(obj)
if err != nil {
return err
}
objKind.Kind = newGroupVersionKind.Kind
}
// Version and Kind should be set on the wire.
err = s.SetVersionAndKind(destVersion.String(), objKind.Kind, obj)
if err != nil {
return err
}
// To add metadata, do some simple surgery on the JSON.
encoder := json.NewEncoder(stream)
if err := encoder.Encode(obj); err != nil {
return err
}
// Version and Kind should be blank in memory. Reset them, since it's
// possible that we modified a user object and not a copy above.
err = s.SetVersionAndKind("", "", obj)
if err != nil {
return err
}
return nil
}
func (s *Scheme) encodeUnversionedObject(obj interface{}) (data []byte, err error) {
objGVK, err := s.ObjectKind(obj)
if err != nil {
return nil, err
}
if err = s.SetVersionAndKind("", objGVK.Kind, obj); err != nil {
return nil, err
}
data, err = json.Marshal(obj)
if err != nil {
return nil, err
}
// Version and Kind should be blank in memory. Reset them, since it's
// possible that we modified a user object and not a copy above.
err = s.SetVersionAndKind("", "", obj)
return data, nil
}

View File

@ -28,6 +28,11 @@ type notRegisteredErr struct {
t reflect.Type t reflect.Type
} }
// NewNotRegisteredErr is exposed for testing.
func NewNotRegisteredErr(gvk unversioned.GroupVersionKind, t reflect.Type) error {
return &notRegisteredErr{gvk: gvk, t: t}
}
func (k *notRegisteredErr) Error() string { func (k *notRegisteredErr) Error() string {
if k.t != nil { if k.t != nil {
return fmt.Sprintf("no kind is registered for the type %v", k.t) return fmt.Sprintf("no kind is registered for the type %v", k.t)

39
pkg/conversion/helper.go Normal file
View File

@ -0,0 +1,39 @@
/*
Copyright 2014 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package conversion
import (
"fmt"
"reflect"
)
// EnforcePtr ensures that obj is a pointer of some sort. Returns a reflect.Value
// of the dereferenced pointer, ensuring that it is settable/addressable.
// Returns an error if this is not possible.
func EnforcePtr(obj interface{}) (reflect.Value, error) {
v := reflect.ValueOf(obj)
if v.Kind() != reflect.Ptr {
if v.Kind() == reflect.Invalid {
return reflect.Value{}, fmt.Errorf("expected pointer, but got invalid kind")
}
return reflect.Value{}, fmt.Errorf("expected pointer, but got %v type", v.Type())
}
if v.IsNil() {
return reflect.Value{}, fmt.Errorf("expected pointer, but got nil")
}
return v.Elem(), nil
}

View File

@ -0,0 +1,38 @@
/*
Copyright 2014 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package conversion
import "testing"
func TestInvalidPtrValueKind(t *testing.T) {
var simple interface{}
switch obj := simple.(type) {
default:
_, err := EnforcePtr(obj)
if err == nil {
t.Errorf("Expected error on invalid kind")
}
}
}
func TestEnforceNilPtr(t *testing.T) {
var nilPtr *struct{}
_, err := EnforcePtr(nilPtr)
if err == nil {
t.Errorf("Expected error on nil pointer")
}
}

View File

@ -1,153 +0,0 @@
/*
Copyright 2014 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package conversion
import (
"encoding/json"
"fmt"
"path"
"reflect"
"k8s.io/kubernetes/pkg/api/unversioned"
)
// MetaFactory is used to store and retrieve the version and kind
// information for all objects in a scheme.
type MetaFactory interface {
// Update sets the given version and kind onto the object.
Update(version, kind string, obj interface{}) error
// Interpret should return the group,version,kind of the wire-format of
// the object.
Interpret(data []byte) (gvk unversioned.GroupVersionKind, err error)
}
// DefaultMetaFactory is a default factory for versioning objects in JSON. The object
// in memory and in the default JSON serialization will use the "kind" and "apiVersion"
// fields.
var DefaultMetaFactory = SimpleMetaFactory{KindField: "Kind", VersionField: "APIVersion"}
// SimpleMetaFactory provides default methods for retrieving the type and version of objects
// that are identified with an "apiVersion" and "kind" fields in their JSON
// serialization. It may be parameterized with the names of the fields in memory, or an
// optional list of base structs to search for those fields in memory.
type SimpleMetaFactory struct {
// The name of the API version field in memory of the struct
VersionField string
// The name of the kind field in memory of the struct.
KindField string
// Optional, if set will look in the named inline structs to find the fields to set.
BaseFields []string
}
// Interpret will return the group,version,kind of the JSON wire-format
// encoding of an object, or an error.
func (SimpleMetaFactory) Interpret(data []byte) (unversioned.GroupVersionKind, error) {
findKind := struct {
APIVersion string `json:"apiVersion,omitempty"`
Kind string `json:"kind,omitempty"`
}{}
err := json.Unmarshal(data, &findKind)
if err != nil {
return unversioned.GroupVersionKind{}, fmt.Errorf("couldn't get version/kind; json parse error: %v", err)
}
gv, err := unversioned.ParseGroupVersion(findKind.APIVersion)
if err != nil {
return unversioned.GroupVersionKind{}, fmt.Errorf("couldn't parse apiVersion: %v", err)
}
return gv.WithKind(findKind.Kind), nil
}
func (f SimpleMetaFactory) Update(version, kind string, obj interface{}) error {
return UpdateVersionAndKind(f.BaseFields, f.VersionField, version, f.KindField, kind, obj)
}
// UpdateVersionAndKind uses reflection to find and set the versionField and kindField fields
// on a pointer to a struct to version and kind. Provided as a convenience for others
// implementing MetaFactory. Pass an array to baseFields to check one or more nested structs
// for the named fields. The version field is treated as optional if it is not present in the struct.
// TODO: this method is on its way out
func UpdateVersionAndKind(baseFields []string, versionField, version, kindField, kind string, obj interface{}) error {
if typed, ok := obj.(unversioned.ObjectKind); ok {
if len(version) == 0 && len(kind) == 0 {
typed.SetGroupVersionKind(nil)
} else {
gv, err := unversioned.ParseGroupVersion(version)
if err != nil {
return err
}
typed.SetGroupVersionKind(&unversioned.GroupVersionKind{Group: gv.Group, Version: gv.Version, Kind: kind})
}
return nil
}
v, err := EnforcePtr(obj)
if err != nil {
return err
}
pkg := path.Base(v.Type().PkgPath())
t := v.Type()
name := t.Name()
if v.Kind() != reflect.Struct {
return fmt.Errorf("expected struct, but got %v: %v (%#v)", v.Kind(), name, v.Interface())
}
for i := range baseFields {
base := v.FieldByName(baseFields[i])
if !base.IsValid() {
continue
}
v = base
}
field := v.FieldByName(kindField)
if !field.IsValid() {
// Types defined in the unversioned package are allowed to not have a
// kindField. Clients will have to know what they are based on the
// context.
// TODO: add some type trait here, or some way of indicating whether
// this feature is allowed on a per-type basis. Using package name is
// overly broad and a bit hacky.
if pkg == "unversioned" {
return nil
}
return fmt.Errorf("couldn't find %v field in %#v", kindField, v.Interface())
}
field.SetString(kind)
if field := v.FieldByName(versionField); field.IsValid() {
field.SetString(version)
}
return nil
}
// EnforcePtr ensures that obj is a pointer of some sort. Returns a reflect.Value
// of the dereferenced pointer, ensuring that it is settable/addressable.
// Returns an error if this is not possible.
func EnforcePtr(obj interface{}) (reflect.Value, error) {
v := reflect.ValueOf(obj)
if v.Kind() != reflect.Ptr {
if v.Kind() == reflect.Invalid {
return reflect.Value{}, fmt.Errorf("expected pointer, but got invalid kind")
}
return reflect.Value{}, fmt.Errorf("expected pointer, but got %v type", v.Type())
}
if v.IsNil() {
return reflect.Value{}, fmt.Errorf("expected pointer, but got nil")
}
return v.Elem(), nil
}

View File

@ -1,289 +0,0 @@
/*
Copyright 2014 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package conversion
import (
"fmt"
"reflect"
"testing"
"k8s.io/kubernetes/pkg/api/unversioned"
)
func TestSimpleMetaFactoryInterpret(t *testing.T) {
factory := SimpleMetaFactory{}
fqKind, err := factory.Interpret([]byte(`{"apiVersion":"g/1","kind":"object"}`))
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
expectedFQKind := unversioned.GroupVersionKind{Group: "g", Version: "1", Kind: "object"}
if expectedFQKind != fqKind {
t.Errorf("unexpected interpret: %s %s", expectedFQKind, fqKind)
}
// no kind or version
fqKind, err = factory.Interpret([]byte(`{}`))
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !fqKind.IsEmpty() {
t.Errorf("unexpected interpret: %s %s", fqKind)
}
// unparsable
fqKind, err = factory.Interpret([]byte(`{`))
if err == nil {
t.Errorf("unexpected non-error")
}
}
func TestSimpleMetaFactoryUpdate(t *testing.T) {
factory := SimpleMetaFactory{VersionField: "V", KindField: "K"}
obj := struct {
V string
K string
}{"1", "2"}
// must pass a pointer
if err := factory.Update("test", "other", obj); err == nil {
t.Errorf("unexpected non-error")
}
if obj.V != "1" || obj.K != "2" {
t.Errorf("unexpected update: %v", obj)
}
// updates
if err := factory.Update("test", "other", &obj); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if obj.V != "test" || obj.K != "other" {
t.Errorf("unexpected update: %v", obj)
}
}
// Test Updating objects that don't have a Kind field.
func TestSimpleMetaFactoryUpdateNoKindField(t *testing.T) {
factory := SimpleMetaFactory{VersionField: "APIVersion", KindField: "Kind"}
// obj does not have a Kind field and is not defined in the unversioned package.
obj := struct {
SomeField string
}{"1"}
expectedError := fmt.Errorf("couldn't find %v field in %#v", factory.KindField, obj)
if err := factory.Update("test", "other", &obj); err == nil || expectedError.Error() != err.Error() {
t.Fatalf("expected error: %v, got: %v", expectedError, err)
}
// ListMeta does not have a Kind field, but is defined in the unversioned package.
listMeta := unversioned.ListMeta{}
if err := factory.Update("test", "other", &listMeta); err != nil {
t.Fatalf("unexpected error: %v", err)
}
}
func TestSimpleMetaFactoryUpdateStruct(t *testing.T) {
factory := SimpleMetaFactory{BaseFields: []string{"Test"}, VersionField: "V", KindField: "K"}
type Inner struct {
V string
K string
}
obj := struct {
Test Inner
}{Test: Inner{"1", "2"}}
// updates
if err := factory.Update("test", "other", &obj); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if obj.Test.V != "test" || obj.Test.K != "other" {
t.Errorf("unexpected update: %v", obj)
}
}
func TestMetaValues(t *testing.T) {
type InternalSimple struct {
APIVersion string `json:"apiVersion,omitempty"`
Kind string `json:"kind,omitempty"`
TestString string `json:"testString"`
}
type ExternalSimple struct {
APIVersion string `json:"apiVersion,omitempty"`
Kind string `json:"kind,omitempty"`
TestString string `json:"testString"`
}
internalGV := unversioned.GroupVersion{Group: "test.group", Version: ""}
externalGV := unversioned.GroupVersion{Group: "test.group", Version: "externalVersion"}
s := NewScheme()
s.InternalVersions[internalGV.Group] = internalGV
s.AddKnownTypeWithName(internalGV.WithKind("Simple"), &InternalSimple{})
s.AddKnownTypeWithName(externalGV.WithKind("Simple"), &ExternalSimple{})
internalToExternalCalls := 0
externalToInternalCalls := 0
// Register functions to verify that scope.Meta() gets set correctly.
err := s.AddConversionFuncs(
func(in *InternalSimple, out *ExternalSimple, scope Scope) error {
t.Logf("internal -> external")
if e, a := internalGV.String(), scope.Meta().SrcVersion; e != a {
t.Fatalf("Expected '%v', got '%v'", e, a)
}
if e, a := externalGV.String(), scope.Meta().DestVersion; e != a {
t.Fatalf("Expected '%v', got '%v'", e, a)
}
scope.Convert(&in.TestString, &out.TestString, 0)
internalToExternalCalls++
return nil
},
func(in *ExternalSimple, out *InternalSimple, scope Scope) error {
t.Logf("external -> internal")
if e, a := externalGV.String(), scope.Meta().SrcVersion; e != a {
t.Errorf("Expected '%v', got '%v'", e, a)
}
if e, a := internalGV.String(), scope.Meta().DestVersion; e != a {
t.Fatalf("Expected '%v', got '%v'", e, a)
}
scope.Convert(&in.TestString, &out.TestString, 0)
externalToInternalCalls++
return nil
},
)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
simple := &InternalSimple{
TestString: "foo",
}
s.Log(t)
// Test Encode, Decode, and DecodeInto
data, err := s.EncodeToVersion(simple, externalGV.String())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
t.Logf(string(data))
obj2, err := s.Decode(data)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if _, ok := obj2.(*InternalSimple); !ok {
t.Fatalf("Got wrong type")
}
if e, a := simple, obj2; !reflect.DeepEqual(e, a) {
t.Errorf("Expected:\n %#v,\n Got:\n %#v", e, a)
}
obj3 := &InternalSimple{}
if err := s.DecodeInto(data, obj3); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if e, a := simple, obj3; !reflect.DeepEqual(e, a) {
t.Errorf("Expected:\n %#v,\n Got:\n %#v", e, a)
}
// Test Convert
external := &ExternalSimple{}
err = s.Convert(simple, external)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if e, a := simple.TestString, external.TestString; e != a {
t.Errorf("Expected %v, got %v", e, a)
}
// Encode and Convert should each have caused an increment.
if e, a := 2, internalToExternalCalls; e != a {
t.Errorf("Expected %v, got %v", e, a)
}
// Decode and DecodeInto should each have caused an increment.
if e, a := 2, externalToInternalCalls; e != a {
t.Errorf("Expected %v, got %v", e, a)
}
}
func TestMetaValuesUnregisteredConvert(t *testing.T) {
type InternalSimple struct {
Version string `json:"apiVersion,omitempty"`
Kind string `json:"kind,omitempty"`
TestString string `json:"testString"`
}
type ExternalSimple struct {
Version string `json:"apiVersion,omitempty"`
Kind string `json:"kind,omitempty"`
TestString string `json:"testString"`
}
s := NewScheme()
// We deliberately don't register the types.
internalToExternalCalls := 0
// Register functions to verify that scope.Meta() gets set correctly.
err := s.AddConversionFuncs(
func(in *InternalSimple, out *ExternalSimple, scope Scope) error {
if e, a := "unknown/unknown", scope.Meta().SrcVersion; e != a {
t.Fatalf("Expected '%v', got '%v'", e, a)
}
if e, a := "unknown/unknown", scope.Meta().DestVersion; e != a {
t.Fatalf("Expected '%v', got '%v'", e, a)
}
scope.Convert(&in.TestString, &out.TestString, 0)
internalToExternalCalls++
return nil
},
)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
simple := &InternalSimple{TestString: "foo"}
external := &ExternalSimple{}
err = s.Convert(simple, external)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if e, a := simple.TestString, external.TestString; e != a {
t.Errorf("Expected %v, got %v", e, a)
}
// Verify that our conversion handler got called.
if e, a := 1, internalToExternalCalls; e != a {
t.Errorf("Expected %v, got %v", e, a)
}
}
func TestInvalidPtrValueKind(t *testing.T) {
var simple interface{}
switch obj := simple.(type) {
default:
_, err := EnforcePtr(obj)
if err == nil {
t.Errorf("Expected error on invalid kind")
}
}
}
func TestEnforceNilPtr(t *testing.T) {
var nilPtr *struct{}
_, err := EnforcePtr(nilPtr)
if err == nil {
t.Errorf("Expected error on nil pointer")
}
}

View File

@ -21,8 +21,6 @@ import (
"net/url" "net/url"
"reflect" "reflect"
"strings" "strings"
"k8s.io/kubernetes/pkg/runtime"
) )
func jsonTag(field reflect.StructField) (string, bool) { func jsonTag(field reflect.StructField) (string, bool) {
@ -93,10 +91,10 @@ func addListOfParams(values url.Values, tag string, omitempty bool, list reflect
} }
} }
// Convert takes a versioned runtime.Object and serializes it to a url.Values object // Convert takes an object and converts it to a url.Values object using JSON tags as
// using JSON tags as parameter names. Only top-level simple values, arrays, and slices // parameter names. Only top-level simple values, arrays, and slices are serialized.
// are serialized. Embedded structs, maps, etc. will not be serialized. // Embedded structs, maps, etc. will not be serialized.
func Convert(obj runtime.Object) (url.Values, error) { func Convert(obj interface{}) (url.Values, error) {
result := url.Values{} result := url.Values{}
if obj == nil { if obj == nil {
return result, nil return result, nil

View File

@ -23,7 +23,6 @@ import (
"k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/conversion/queryparams" "k8s.io/kubernetes/pkg/conversion/queryparams"
"k8s.io/kubernetes/pkg/runtime"
) )
type namedString string type namedString string
@ -85,7 +84,7 @@ func validateResult(t *testing.T, input interface{}, actual, expected url.Values
func TestConvert(t *testing.T) { func TestConvert(t *testing.T) {
tests := []struct { tests := []struct {
input runtime.Object input interface{}
expected url.Values expected url.Values
}{ }{
{ {

View File

@ -33,6 +33,13 @@ type Scheme struct {
// The reflect.Type we index by should *not* be a pointer. // The reflect.Type we index by should *not* be a pointer.
typeToGVK map[reflect.Type][]unversioned.GroupVersionKind typeToGVK map[reflect.Type][]unversioned.GroupVersionKind
// unversionedTypes are transformed without conversion in ConvertToVersion.
unversionedTypes map[reflect.Type]unversioned.GroupVersionKind
// unversionedKinds are the names of kinds that can be created in the context of any group
// or version
// TODO: resolve the status of unversioned types.
unversionedKinds map[string]reflect.Type
// converter stores all registered conversion functions. It also has // converter stores all registered conversion functions. It also has
// default coverting behavior. // default coverting behavior.
converter *Converter converter *Converter
@ -44,34 +51,17 @@ type Scheme struct {
// Indent will cause the JSON output from Encode to be indented, // Indent will cause the JSON output from Encode to be indented,
// if and only if it is true. // if and only if it is true.
Indent bool Indent bool
// InternalVersion is the default internal version. It is recommended that
// you use "" for the internal version.
// TODO logically the InternalVersion is different for every Group, so this structure
// must be map
InternalVersions map[string]unversioned.GroupVersion
// MetaInsertionFactory is used to create an object to store and retrieve
// the version and kind information for all objects. The default uses the
// keys "apiVersion" and "kind" respectively.
MetaFactory MetaFactory
} }
// NewScheme manufactures a new scheme. // NewScheme manufactures a new scheme.
func NewScheme() *Scheme { func NewScheme() *Scheme {
s := &Scheme{ s := &Scheme{
gvkToType: map[unversioned.GroupVersionKind]reflect.Type{}, gvkToType: map[unversioned.GroupVersionKind]reflect.Type{},
typeToGVK: map[reflect.Type][]unversioned.GroupVersionKind{}, typeToGVK: map[reflect.Type][]unversioned.GroupVersionKind{},
converter: NewConverter(), unversionedTypes: map[reflect.Type]unversioned.GroupVersionKind{},
cloner: NewCloner(), unversionedKinds: map[string]reflect.Type{},
// TODO remove this hard coded list. As step one, hardcode it here so this pull doesn't become even bigger converter: NewConverter(),
InternalVersions: map[string]unversioned.GroupVersion{ cloner: NewCloner(),
"": {},
"componentconfig": {Group: "componentconfig"},
"extensions": {Group: "extensions"},
"metrics": {Group: "metrics"},
},
MetaFactory: DefaultMetaFactory,
} }
s.converter.nameFunc = s.nameFunc s.converter.nameFunc = s.nameFunc
return s return s
@ -92,11 +82,8 @@ func (s *Scheme) nameFunc(t reflect.Type) string {
} }
for _, gvk := range gvks { for _, gvk := range gvks {
internalGV, exists := s.InternalVersions[gvk.Group] internalGV := gvk.GroupVersion()
if !exists { internalGV.Version = "__internal" // this is hacky and maybe should be passed in
internalGV := gvk.GroupVersion()
internalGV.Version = ""
}
internalGVK := internalGV.WithKind(gvk.Kind) internalGVK := internalGV.WithKind(gvk.Kind)
if internalType, exists := s.gvkToType[internalGVK]; exists { if internalType, exists := s.gvkToType[internalGVK]; exists {
@ -107,11 +94,30 @@ func (s *Scheme) nameFunc(t reflect.Type) string {
return gvks[0].Kind return gvks[0].Kind
} }
// AddUnversionedTypes registers all types passed in 'types' as being members of version 'version',
// and marks them as being convertible to all API versions.
// All objects passed to types should be pointers to structs. The name that go reports for
// the struct becomes the "kind" field when encoding.
func (s *Scheme) AddUnversionedTypes(version unversioned.GroupVersion, types ...interface{}) {
s.AddKnownTypes(version, types...)
for _, obj := range types {
t := reflect.TypeOf(obj).Elem()
gvk := version.WithKind(t.Name())
s.unversionedTypes[t] = gvk
if _, ok := s.unversionedKinds[gvk.Kind]; ok {
panic(fmt.Sprintf("%v has already been registered as unversioned kind %q - kind name must be unique", reflect.TypeOf(t), gvk.Kind))
}
s.unversionedKinds[gvk.Kind] = t
}
}
// AddKnownTypes registers all types passed in 'types' as being members of version 'version'. // AddKnownTypes registers all types passed in 'types' as being members of version 'version'.
// Encode() will refuse objects unless their type has been registered with AddKnownTypes.
// All objects passed to types should be pointers to structs. The name that go reports for // All objects passed to types should be pointers to structs. The name that go reports for
// the struct becomes the "kind" field when encoding. // the struct becomes the "kind" field when encoding.
func (s *Scheme) AddKnownTypes(gv unversioned.GroupVersion, types ...interface{}) { func (s *Scheme) AddKnownTypes(gv unversioned.GroupVersion, types ...interface{}) {
if len(gv.Version) == 0 {
panic(fmt.Sprintf("version is required on all types: %s %v", gv, types[0]))
}
for _, obj := range types { for _, obj := range types {
t := reflect.TypeOf(obj) t := reflect.TypeOf(obj)
if t.Kind() != reflect.Ptr { if t.Kind() != reflect.Ptr {
@ -133,6 +139,9 @@ func (s *Scheme) AddKnownTypes(gv unversioned.GroupVersion, types ...interface{}
// your structs. // your structs.
func (s *Scheme) AddKnownTypeWithName(gvk unversioned.GroupVersionKind, obj interface{}) { func (s *Scheme) AddKnownTypeWithName(gvk unversioned.GroupVersionKind, obj interface{}) {
t := reflect.TypeOf(obj) t := reflect.TypeOf(obj)
if len(gvk.Version) == 0 {
panic(fmt.Sprintf("version is required on all types: %s %v", gvk, t))
}
if t.Kind() != reflect.Ptr { if t.Kind() != reflect.Ptr {
panic("All types must be pointers to structs.") panic("All types must be pointers to structs.")
} }
@ -168,6 +177,9 @@ func (s *Scheme) NewObject(kind unversioned.GroupVersionKind) (interface{}, erro
return reflect.New(t).Interface(), nil return reflect.New(t).Interface(), nil
} }
if t, exists := s.unversionedKinds[kind.Kind]; exists {
return reflect.New(t).Interface(), nil
}
return nil, &notRegisteredErr{gvk: kind} return nil, &notRegisteredErr{gvk: kind}
} }
@ -221,6 +233,13 @@ func (s *Scheme) AddGeneratedConversionFuncs(conversionFuncs ...interface{}) err
return nil return nil
} }
// AddIgnoredConversionType identifies a pair of types that should be skipped by
// dynamic conversion (because the data inside them is explicitly dropped during
// conversion).
func (s *Scheme) AddIgnoredConversionType(from, to interface{}) error {
return s.converter.RegisterIgnoredConversion(from, to)
}
// AddDeepCopyFuncs adds functions to the list of deep copy functions. // AddDeepCopyFuncs adds functions to the list of deep copy functions.
// Note that to copy sub-objects, you can use the conversion.Cloner object that // Note that to copy sub-objects, you can use the conversion.Cloner object that
// will be passed to your deep-copy function. // will be passed to your deep-copy function.
@ -282,6 +301,22 @@ func (s *Scheme) Recognizes(gvk unversioned.GroupVersionKind) bool {
return exists return exists
} }
// IsUnversioned returns true if the Go object is registered as an unversioned type, or sets
// ok to false if the provided object is not registered in the scheme.
func (s *Scheme) IsUnversioned(obj interface{}) (unversioned bool, registered bool) {
v, err := EnforcePtr(obj)
if err != nil {
return false, false
}
t := v.Type()
if _, ok := s.typeToGVK[t]; !ok {
return false, false
}
_, ok := s.unversionedTypes[t]
return ok, true
}
// RegisterInputDefaults sets the provided field mapping function and field matching // RegisterInputDefaults sets the provided field mapping function and field matching
// as the defaults for the provided input type. The fn may be nil, in which case no // as the defaults for the provided input type. The fn may be nil, in which case no
// mapping will happen by default. Use this method to register a mechanism for handling // mapping will happen by default. Use this method to register a mechanism for handling
@ -330,15 +365,23 @@ func (s *Scheme) ConvertToVersion(in interface{}, outGroupVersionString string)
return nil, fmt.Errorf("only pointers to struct types may be converted: %v", t) return nil, fmt.Errorf("only pointers to struct types may be converted: %v", t)
} }
gvks, ok := s.typeToGVK[t]
if !ok {
return nil, fmt.Errorf("%v cannot be converted into version %q", t, outGroupVersionString)
}
outVersion, err := unversioned.ParseGroupVersion(outGroupVersionString) outVersion, err := unversioned.ParseGroupVersion(outGroupVersionString)
if err != nil { if err != nil {
return nil, err return nil, err
} }
outKind := outVersion.WithKind(gvks[0].Kind)
var kind unversioned.GroupVersionKind
if unversionedKind, ok := s.unversionedTypes[t]; ok {
kind = unversionedKind
} else {
kinds, ok := s.typeToGVK[t]
if !ok || len(kinds) == 0 {
return nil, fmt.Errorf("%v is not a registered type and cannot be converted into version %q", t, outGroupVersionString)
}
kind = kinds[0]
}
outKind := outVersion.WithKind(kind.Kind)
inKind, err := s.ObjectKind(in) inKind, err := s.ObjectKind(in)
if err != nil { if err != nil {
@ -355,10 +398,6 @@ func (s *Scheme) ConvertToVersion(in interface{}, outGroupVersionString string)
return nil, err return nil, err
} }
if err := s.SetVersionAndKind(outVersion.String(), outKind.Kind, out); err != nil {
return nil, err
}
return out, nil return out, nil
} }
@ -367,6 +406,14 @@ func (s *Scheme) Converter() *Converter {
return s.converter return s.converter
} }
// WithConversions returns a scheme with additional conversion functions
func (s *Scheme) WithConversions(fns ConversionFuncs) *Scheme {
c := s.converter.WithConversions(fns)
copied := *s
copied.converter = c
return &copied
}
// generateConvertMeta constructs the meta value we pass to Convert. // generateConvertMeta constructs the meta value we pass to Convert.
func (s *Scheme) generateConvertMeta(srcGroupVersion, destGroupVersion unversioned.GroupVersion, in interface{}) (FieldMatchingFlags, *Meta) { func (s *Scheme) generateConvertMeta(srcGroupVersion, destGroupVersion unversioned.GroupVersion, in interface{}) (FieldMatchingFlags, *Meta) {
t := reflect.TypeOf(in) t := reflect.TypeOf(in)
@ -377,12 +424,6 @@ func (s *Scheme) generateConvertMeta(srcGroupVersion, destGroupVersion unversion
} }
} }
// DataKind will return the group,version,kind of the given wire-format
// encoding of an API Object, or an error.
func (s *Scheme) DataKind(data []byte) (unversioned.GroupVersionKind, error) {
return s.MetaFactory.Interpret(data)
}
// ObjectKind returns the group,version,kind of the go object, // ObjectKind returns the group,version,kind of the go object,
// or an error if it's not a pointer or is unregistered. // or an error if it's not a pointer or is unregistered.
func (s *Scheme) ObjectKind(obj interface{}) (unversioned.GroupVersionKind, error) { func (s *Scheme) ObjectKind(obj interface{}) (unversioned.GroupVersionKind, error) {
@ -390,7 +431,6 @@ func (s *Scheme) ObjectKind(obj interface{}) (unversioned.GroupVersionKind, erro
if err != nil { if err != nil {
return unversioned.GroupVersionKind{}, err return unversioned.GroupVersionKind{}, err
} }
return gvks[0], nil return gvks[0], nil
} }
@ -399,22 +439,16 @@ func (s *Scheme) ObjectKind(obj interface{}) (unversioned.GroupVersionKind, erro
func (s *Scheme) ObjectKinds(obj interface{}) ([]unversioned.GroupVersionKind, error) { func (s *Scheme) ObjectKinds(obj interface{}) ([]unversioned.GroupVersionKind, error) {
v, err := EnforcePtr(obj) v, err := EnforcePtr(obj)
if err != nil { if err != nil {
return []unversioned.GroupVersionKind{}, err return nil, err
} }
t := v.Type() t := v.Type()
gvks, ok := s.typeToGVK[t] gvks, ok := s.typeToGVK[t]
if !ok { if !ok {
return []unversioned.GroupVersionKind{}, &notRegisteredErr{t: t} return nil, &notRegisteredErr{t: t}
} }
return gvks, nil
}
// SetVersionAndKind sets the version and kind fields (with help from return gvks, nil
// MetaInsertionFactory). Returns an error if this isn't possible. obj
// must be a pointer.
func (s *Scheme) SetVersionAndKind(version, kind string, obj interface{}) error {
return s.MetaFactory.Update(version, kind, obj)
} }
// maybeCopy copies obj if it is not a pointer, to get a settable/addressable // maybeCopy copies obj if it is not a pointer, to get a settable/addressable

View File

@ -18,15 +18,11 @@ package conversion
import ( import (
"encoding/json" "encoding/json"
"fmt"
"reflect"
"strings"
"testing" "testing"
"k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/util" "k8s.io/kubernetes/pkg/util"
"github.com/ghodss/yaml"
"github.com/google/gofuzz" "github.com/google/gofuzz"
flag "github.com/spf13/pflag" flag "github.com/spf13/pflag"
) )
@ -109,7 +105,7 @@ var TestObjectFuzzer = fuzz.New().NilChance(.5).NumElements(1, 100).Funcs(
// Returns a new Scheme set up with the test objects. // Returns a new Scheme set up with the test objects.
func GetTestScheme() *Scheme { func GetTestScheme() *Scheme {
internalGV := unversioned.GroupVersion{} internalGV := unversioned.GroupVersion{Version: "__internal"}
externalGV := unversioned.GroupVersion{Version: "v1"} externalGV := unversioned.GroupVersion{Version: "v1"}
s := NewScheme() s := NewScheme()
@ -122,34 +118,9 @@ func GetTestScheme() *Scheme {
s.AddKnownTypeWithName(externalGV.WithKind("TestType2"), &ExternalTestType2{}) s.AddKnownTypeWithName(externalGV.WithKind("TestType2"), &ExternalTestType2{})
s.AddKnownTypeWithName(internalGV.WithKind("TestType3"), &TestType1{}) s.AddKnownTypeWithName(internalGV.WithKind("TestType3"), &TestType1{})
s.AddKnownTypeWithName(externalGV.WithKind("TestType3"), &ExternalTestType1{}) s.AddKnownTypeWithName(externalGV.WithKind("TestType3"), &ExternalTestType1{})
s.MetaFactory = testMetaFactory{}
return s return s
} }
type testMetaFactory struct{}
func (testMetaFactory) Interpret(data []byte) (unversioned.GroupVersionKind, error) {
findKind := struct {
APIVersion string `json:"myVersionKey,omitempty"`
ObjectKind string `json:"myKindKey,omitempty"`
}{}
// yaml is a superset of json, so we use it to decode here. That way,
// we understand both.
err := yaml.Unmarshal(data, &findKind)
if err != nil {
return unversioned.GroupVersionKind{}, fmt.Errorf("couldn't get version/kind: %v", err)
}
gv, err := unversioned.ParseGroupVersion(findKind.APIVersion)
if err != nil {
return unversioned.GroupVersionKind{}, err
}
return gv.WithKind(findKind.ObjectKind), nil
}
func (testMetaFactory) Update(version, kind string, obj interface{}) error {
return UpdateVersionAndKind(nil, "APIVersion", version, "ObjectKind", kind, obj)
}
func objDiff(a, b interface{}) string { func objDiff(a, b interface{}) string {
ab, err := json.Marshal(a) ab, err := json.Marshal(a)
if err != nil { if err != nil {
@ -170,111 +141,6 @@ func objDiff(a, b interface{}) string {
//) //)
} }
func runTest(t *testing.T, source interface{}) {
name := reflect.TypeOf(source).Elem().Name()
TestObjectFuzzer.Fuzz(source)
s := GetTestScheme()
data, err := s.EncodeToVersion(source, "v1")
if err != nil {
t.Errorf("%v: %v (%#v)", name, err, source)
return
}
obj2, err := s.Decode(data)
if err != nil {
t.Errorf("%v: %v (%v)", name, err, string(data))
return
}
if !reflect.DeepEqual(source, obj2) {
t.Errorf("1: %v: diff: %v", name, objDiff(source, obj2))
return
}
obj3 := reflect.New(reflect.TypeOf(source).Elem()).Interface()
err = s.DecodeInto(data, obj3)
if err != nil {
t.Errorf("2: %v: %v", name, err)
return
}
if !reflect.DeepEqual(source, obj3) {
t.Errorf("3: %v: diff: %v", name, objDiff(source, obj3))
return
}
}
func TestTypes(t *testing.T) {
table := []interface{}{
&TestType1{},
&ExternalInternalSame{},
}
for _, item := range table {
// Try a few times, since runTest uses random values.
for i := 0; i < *fuzzIters; i++ {
runTest(t, item)
}
}
}
func TestMultipleNames(t *testing.T) {
s := GetTestScheme()
obj, err := s.Decode([]byte(`{"myKindKey":"TestType3","myVersionKey":"v1","A":"value"}`))
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
internal := obj.(*TestType1)
if internal.A != "value" {
t.Fatalf("unexpected decoded object: %#v", internal)
}
out, err := s.EncodeToVersion(internal, "v1")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !strings.Contains(string(out), `"myKindKey":"TestType1"`) {
t.Errorf("unexpected encoded output: %s", string(out))
}
}
func TestConvertTypesWhenDefaultNamesMatch(t *testing.T) {
internalGV := unversioned.GroupVersion{}
externalGV := unversioned.GroupVersion{Version: "v1"}
s := NewScheme()
// create two names internally, with TestType1 being preferred
s.AddKnownTypeWithName(internalGV.WithKind("TestType1"), &TestType1{})
s.AddKnownTypeWithName(internalGV.WithKind("OtherType1"), &TestType1{})
// create two names externally, with TestType1 being preferred
s.AddKnownTypeWithName(externalGV.WithKind("TestType1"), &ExternalTestType1{})
s.AddKnownTypeWithName(externalGV.WithKind("OtherType1"), &ExternalTestType1{})
s.MetaFactory = testMetaFactory{}
ext := &ExternalTestType1{}
ext.APIVersion = "v1"
ext.ObjectKind = "OtherType1"
ext.A = "test"
data, err := json.Marshal(ext)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
expect := &TestType1{A: "test"}
obj, err := s.Decode(data)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !reflect.DeepEqual(expect, obj) {
t.Errorf("unexpected object: %#v", obj)
}
into := &TestType1{}
if err := s.DecodeInto(data, into); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !reflect.DeepEqual(expect, obj) {
t.Errorf("unexpected object: %#v", obj)
}
}
func TestKnownTypes(t *testing.T) { func TestKnownTypes(t *testing.T) {
s := GetTestScheme() s := GetTestScheme()
if len(s.KnownTypes(unversioned.GroupVersion{Group: "group", Version: "v2"})) != 0 { if len(s.KnownTypes(unversioned.GroupVersion{Group: "group", Version: "v2"})) != 0 {
@ -313,75 +179,3 @@ func TestConvertToVersionErr(t *testing.T) {
t.Fatalf("unexpected non-error") t.Fatalf("unexpected non-error")
} }
} }
func TestEncode_NonPtr(t *testing.T) {
s := GetTestScheme()
tt := TestType1{A: "I'm not a pointer object"}
obj := interface{}(tt)
data, err := s.EncodeToVersion(obj, "v1")
obj2, err2 := s.Decode(data)
if err != nil || err2 != nil {
t.Fatalf("Failure: '%v' '%v'", err, err2)
}
if _, ok := obj2.(*TestType1); !ok {
t.Fatalf("Got wrong type")
}
if !reflect.DeepEqual(obj2, &tt) {
t.Errorf("Expected:\n %#v,\n Got:\n %#v", &tt, obj2)
}
}
func TestEncode_Ptr(t *testing.T) {
s := GetTestScheme()
tt := &TestType1{A: "I am a pointer object"}
obj := interface{}(tt)
data, err := s.EncodeToVersion(obj, "v1")
obj2, err2 := s.Decode(data)
if err != nil || err2 != nil {
t.Fatalf("Failure: '%v' '%v'", err, err2)
}
if _, ok := obj2.(*TestType1); !ok {
t.Fatalf("Got wrong type")
}
if !reflect.DeepEqual(obj2, tt) {
t.Errorf("Expected:\n %#v,\n Got:\n %#v", &tt, obj2)
}
}
func TestBadJSONRejection(t *testing.T) {
s := GetTestScheme()
badJSONs := [][]byte{
[]byte(`{"myVersionKey":"v1"}`), // Missing kind
[]byte(`{"myVersionKey":"v1","myKindKey":"bar"}`), // Unknown kind
[]byte(`{"myVersionKey":"bar","myKindKey":"TestType1"}`), // Unknown version
}
for _, b := range badJSONs {
if _, err := s.Decode(b); err == nil {
t.Errorf("Did not reject bad json: %s", string(b))
}
}
badJSONKindMismatch := []byte(`{"myVersionKey":"v1","myKindKey":"ExternalInternalSame"}`)
if err := s.DecodeInto(badJSONKindMismatch, &TestType1{}); err == nil {
t.Errorf("Kind is set but doesn't match the object type: %s", badJSONKindMismatch)
}
if err := s.DecodeInto([]byte(``), &TestType1{}); err == nil {
t.Errorf("Did not give error for empty data")
}
}
func TestBadJSONRejectionForSetInternalVersion(t *testing.T) {
s := GetTestScheme()
s.InternalVersions[""] = unversioned.GroupVersion{Version: "v1"}
badJSONs := [][]byte{
[]byte(`{"myKindKey":"TestType1"}`), // Missing version
}
for _, b := range badJSONs {
if _, err := s.Decode(b); err == nil {
t.Errorf("Did not reject bad json: %s", string(b))
}
}
badJSONKindMismatch := []byte(`{"myVersionKey":"v1","myKindKey":"ExternalInternalSame"}`)
if err := s.DecodeInto(badJSONKindMismatch, &TestType1{}); err == nil {
t.Errorf("Kind is set but doesn't match the object type: %s", badJSONKindMismatch)
}
}

View File

@ -23,10 +23,10 @@ import (
// TODO: Ideally we should create the necessary package structure in e.g., // TODO: Ideally we should create the necessary package structure in e.g.,
// pkg/conversion/test/... instead of importing pkg/api here. // pkg/conversion/test/... instead of importing pkg/api here.
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/latest"
"k8s.io/kubernetes/pkg/api/testapi" "k8s.io/kubernetes/pkg/api/testapi"
"k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/apis/extensions/v1beta1" "k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/runtime" "k8s.io/kubernetes/pkg/runtime"
) )
@ -67,8 +67,8 @@ func TestV1EncodeDecodeStatus(t *testing.T) {
func TestExperimentalEncodeDecodeStatus(t *testing.T) { func TestExperimentalEncodeDecodeStatus(t *testing.T) {
// TODO: caesarxuchao: use the testapi.Extensions.Codec() once the PR that // TODO: caesarxuchao: use the testapi.Extensions.Codec() once the PR that
// moves experimental from v1 to v1beta1 got merged. // moves experimental from v1 to v1beta1 got merged.
expCodec := runtime.CodecFor(api.Scheme, v1beta1.SchemeGroupVersion) expCodec := latest.Codecs.LegacyCodec(extensions.SchemeGroupVersion)
encoded, err := expCodec.Encode(status) encoded, err := runtime.Encode(expCodec, status)
if err != nil { if err != nil {
t.Errorf("unexpected error: %v", err) t.Errorf("unexpected error: %v", err)
} }
@ -79,7 +79,7 @@ func TestExperimentalEncodeDecodeStatus(t *testing.T) {
if typeMeta.Kind != "Status" { if typeMeta.Kind != "Status" {
t.Errorf("Kind is not set to \"Status\". Got %s", encoded) t.Errorf("Kind is not set to \"Status\". Got %s", encoded)
} }
if typeMeta.APIVersion != "" { if typeMeta.APIVersion != "v1" {
t.Errorf("APIVersion is not set to \"\". Got %s", encoded) t.Errorf("APIVersion is not set to \"\". Got %s", encoded)
} }
decoded, err := runtime.Decode(expCodec, encoded) decoded, err := runtime.Decode(expCodec, encoded)

View File

@ -0,0 +1,18 @@
/*
Copyright 2015 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package protobuf handles serializing API objects to and from wire formats.
package protobuf