Merge pull request #20127 from smarterclayton/remove_conversion_scheme

Auto commit by PR queue bot
This commit is contained in:
k8s-merge-robot 2016-01-30 12:39:44 -08:00
commit 6f66dbb9ab
18 changed files with 834 additions and 1057 deletions

View File

@ -92,7 +92,7 @@ func main() {
}
versionPath := pkgPath(gv.Group, gv.Version)
generator := kruntime.NewConversionGenerator(api.Scheme.Raw(), versionPath)
generator := kruntime.NewConversionGenerator(api.Scheme, versionPath)
apiShort := generator.AddImport(path.Join(pkgBase, "api"))
generator.AddImport(path.Join(pkgBase, "api/resource"))
// TODO(wojtek-t): Change the overwrites to a flag.

View File

@ -114,7 +114,7 @@ func main() {
}
versionPath := pkgPath(gv.Group, gv.Version)
generator := kruntime.NewDeepCopyGenerator(api.Scheme.Raw(), versionPath, sets.NewString("k8s.io/kubernetes"))
generator := kruntime.NewDeepCopyGenerator(api.Scheme, versionPath, sets.NewString("k8s.io/kubernetes"))
generator.AddImport(path.Join(pkgBase, "api"))
if len(*overwrites) > 0 {

View File

@ -35,7 +35,7 @@ func BenchmarkPodConversion(b *testing.B) {
b.Fatalf("Unexpected error decoding pod: %v", err)
}
scheme := api.Scheme.Raw()
scheme := api.Scheme
var result *api.Pod
for i := 0; i < b.N; i++ {
versionedObj, err := scheme.ConvertToVersion(&pod, testapi.Default.GroupVersion().String())
@ -63,7 +63,7 @@ func BenchmarkNodeConversion(b *testing.B) {
b.Fatalf("Unexpected error decoding node: %v", err)
}
scheme := api.Scheme.Raw()
scheme := api.Scheme
var result *api.Node
for i := 0; i < b.N; i++ {
versionedObj, err := scheme.ConvertToVersion(&node, testapi.Default.GroupVersion().String())
@ -91,7 +91,7 @@ func BenchmarkReplicationControllerConversion(b *testing.B) {
b.Fatalf("Unexpected error decoding node: %v", err)
}
scheme := api.Scheme.Raw()
scheme := api.Scheme
var result *api.ReplicationController
for i := 0; i < b.N; i++ {
versionedObj, err := scheme.ConvertToVersion(&replicationController, testapi.Default.GroupVersion().String())

View File

@ -36,6 +36,10 @@ type DebugLogger interface {
Logf(format string, args ...interface{})
}
type NameFunc func(t reflect.Type) string
var DefaultNameFunc = func(t reflect.Type) string { return t.Name() }
// Converter knows how to convert one type to another.
type Converter struct {
// Map from the conversion pair to a function which can
@ -77,14 +81,14 @@ type Converter struct {
}
// NewConverter creates a new Converter object.
func NewConverter() *Converter {
func NewConverter(nameFn NameFunc) *Converter {
c := &Converter{
conversionFuncs: NewConversionFuncs(),
generatedConversionFuncs: NewConversionFuncs(),
ignoredConversions: make(map[typePair]struct{}),
defaultingFuncs: make(map[reflect.Type]reflect.Value),
defaultingInterfaces: make(map[reflect.Type]interface{}),
nameFunc: func(t reflect.Type) string { return t.Name() },
nameFunc: nameFn,
structFieldDests: make(map[typeNamePair][]typeNamePair),
structFieldSources: make(map[typeNamePair][]typeNamePair),
@ -103,6 +107,13 @@ func (c *Converter) WithConversions(fns ConversionFuncs) *Converter {
return &copied
}
// DefaultMeta returns the conversion FieldMappingFunc and meta for a given type.
func (c *Converter) DefaultMeta(t reflect.Type) (FieldMatchingFlags, *Meta) {
return c.inputDefaultFlags[t], &Meta{
KeyNameMapping: c.inputFieldMappingFuncs[t],
}
}
// ByteSliceCopy prevents recursing into every byte
func ByteSliceCopy(in *[]byte, out *[]byte, s Scope) error {
*out = make([]byte, len(*in))

View File

@ -17,6 +17,7 @@ limitations under the License.
package conversion
import (
"encoding/json"
"fmt"
"reflect"
"strconv"
@ -24,10 +25,71 @@ import (
"testing"
"github.com/google/gofuzz"
flag "github.com/spf13/pflag"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/util"
)
var fuzzIters = flag.Int("fuzz-iters", 50, "How many fuzzing iterations to do.")
// Test a weird version/kind embedding format.
type MyWeirdCustomEmbeddedVersionKindField struct {
ID string `json:"ID,omitempty"`
APIVersion string `json:"myVersionKey,omitempty"`
ObjectKind string `json:"myKindKey,omitempty"`
Z string `json:"Z,omitempty"`
Y uint64 `json:"Y,omitempty"`
}
type TestType1 struct {
MyWeirdCustomEmbeddedVersionKindField `json:",inline"`
A string `json:"A,omitempty"`
B int `json:"B,omitempty"`
C int8 `json:"C,omitempty"`
D int16 `json:"D,omitempty"`
E int32 `json:"E,omitempty"`
F int64 `json:"F,omitempty"`
G uint `json:"G,omitempty"`
H uint8 `json:"H,omitempty"`
I uint16 `json:"I,omitempty"`
J uint32 `json:"J,omitempty"`
K uint64 `json:"K,omitempty"`
L bool `json:"L,omitempty"`
M map[string]int `json:"M,omitempty"`
N map[string]TestType2 `json:"N,omitempty"`
O *TestType2 `json:"O,omitempty"`
P []TestType2 `json:"Q,omitempty"`
}
type TestType2 struct {
A string `json:"A,omitempty"`
B int `json:"B,omitempty"`
}
type ExternalTestType2 struct {
A string `json:"A,omitempty"`
B int `json:"B,omitempty"`
}
type ExternalTestType1 struct {
MyWeirdCustomEmbeddedVersionKindField `json:",inline"`
A string `json:"A,omitempty"`
B int `json:"B,omitempty"`
C int8 `json:"C,omitempty"`
D int16 `json:"D,omitempty"`
E int32 `json:"E,omitempty"`
F int64 `json:"F,omitempty"`
G uint `json:"G,omitempty"`
H uint8 `json:"H,omitempty"`
I uint16 `json:"I,omitempty"`
J uint32 `json:"J,omitempty"`
K uint64 `json:"K,omitempty"`
L bool `json:"L,omitempty"`
M map[string]int `json:"M,omitempty"`
N map[string]ExternalTestType2 `json:"N,omitempty"`
O *ExternalTestType2 `json:"O,omitempty"`
P []ExternalTestType2 `json:"Q,omitempty"`
}
func testLogger(t *testing.T) DebugLogger {
// We don't set logger to eliminate rubbish logs in tests.
// If you want to switch it, simply switch it to: "return t"
@ -35,7 +97,7 @@ func testLogger(t *testing.T) DebugLogger {
}
func TestConverter_byteSlice(t *testing.T) {
c := NewConverter()
c := NewConverter(DefaultNameFunc)
src := []byte{1, 2, 3}
dest := []byte{}
err := c.Convert(&src, &dest, 0, nil)
@ -48,7 +110,7 @@ func TestConverter_byteSlice(t *testing.T) {
}
func TestConverter_MismatchedTypes(t *testing.T) {
c := NewConverter()
c := NewConverter(DefaultNameFunc)
err := c.RegisterConversionFunc(
func(in *[]string, out *int, s Scope) error {
@ -84,7 +146,7 @@ func TestConverter_DefaultConvert(t *testing.T) {
Bar string
Baz int
}
c := NewConverter()
c := NewConverter(DefaultNameFunc)
c.Debug = testLogger(t)
c.nameFunc = func(t reflect.Type) string { return "MyType" }
@ -123,7 +185,7 @@ func TestConverter_DeepCopy(t *testing.T) {
Baz interface{}
Qux map[string]string
}
c := NewConverter()
c := NewConverter(DefaultNameFunc)
c.Debug = testLogger(t)
foo, baz := "foo", "baz"
@ -166,7 +228,7 @@ func TestConverter_CallsRegisteredFunctions(t *testing.T) {
Baz int
}
type C struct{}
c := NewConverter()
c := NewConverter(DefaultNameFunc)
c.Debug = testLogger(t)
err := c.RegisterConversionFunc(func(in *A, out *B, s Scope) error {
out.Bar = in.Foo
@ -228,7 +290,7 @@ func TestConverter_IgnoredConversion(t *testing.T) {
type B struct{}
count := 0
c := NewConverter()
c := NewConverter(DefaultNameFunc)
if err := c.RegisterConversionFunc(func(in *A, out *B, s Scope) error {
count++
return nil
@ -257,7 +319,7 @@ func TestConverter_IgnoredConversionNested(t *testing.T) {
C C
}
c := NewConverter()
c := NewConverter(DefaultNameFunc)
typed := C("")
if err := c.RegisterIgnoredConversion(&typed, &typed); err != nil {
t.Fatal(err)
@ -275,7 +337,7 @@ func TestConverter_IgnoredConversionNested(t *testing.T) {
func TestConverter_GeneratedConversionOverriden(t *testing.T) {
type A struct{}
type B struct{}
c := NewConverter()
c := NewConverter(DefaultNameFunc)
if err := c.RegisterConversionFunc(func(in *A, out *B, s Scope) error {
return nil
}); err != nil {
@ -297,7 +359,7 @@ func TestConverter_GeneratedConversionOverriden(t *testing.T) {
func TestConverter_WithConversionOverriden(t *testing.T) {
type A struct{}
type B struct{}
c := NewConverter()
c := NewConverter(DefaultNameFunc)
if err := c.RegisterConversionFunc(func(in *A, out *B, s Scope) error {
return fmt.Errorf("conversion function should be overriden")
}); err != nil {
@ -331,7 +393,7 @@ func TestConverter_MapsStringArrays(t *testing.T) {
Baz int
Other string
}
c := NewConverter()
c := NewConverter(DefaultNameFunc)
c.Debug = testLogger(t)
if err := c.RegisterConversionFunc(func(input *[]string, out *string, s Scope) error {
if len(*input) == 0 {
@ -384,7 +446,7 @@ func TestConverter_MapsStringArraysWithMappingKey(t *testing.T) {
Baz int
Other string
}
c := NewConverter()
c := NewConverter(DefaultNameFunc)
c.Debug = testLogger(t)
if err := c.RegisterConversionFunc(func(input *[]string, out *string, s Scope) error {
if len(*input) == 0 {
@ -434,7 +496,7 @@ func TestConverter_fuzz(t *testing.T) {
}
f := fuzz.New().NilChance(.5).NumElements(0, 100)
c := NewConverter()
c := NewConverter(DefaultNameFunc)
c.nameFunc = func(t reflect.Type) string {
// Hide the fact that we don't have separate packages for these things.
return map[reflect.Type]string{
@ -473,7 +535,7 @@ func TestConverter_MapElemAddr(t *testing.T) {
type Bar struct {
A map[string]string
}
c := NewConverter()
c := NewConverter(DefaultNameFunc)
c.Debug = testLogger(t)
err := c.RegisterConversionFunc(
func(in *int, out *string, s Scope) error {
@ -519,7 +581,7 @@ func TestConverter_tags(t *testing.T) {
type Bar struct {
A string `test:"bar"`
}
c := NewConverter()
c := NewConverter(DefaultNameFunc)
c.Debug = testLogger(t)
err := c.RegisterConversionFunc(
func(in *string, out *string, s Scope) error {
@ -544,7 +606,7 @@ func TestConverter_tags(t *testing.T) {
func TestConverter_meta(t *testing.T) {
type Foo struct{ A string }
type Bar struct{ A string }
c := NewConverter()
c := NewConverter(DefaultNameFunc)
c.Debug = testLogger(t)
checks := 0
err := c.RegisterConversionFunc(
@ -653,7 +715,7 @@ func TestConverter_flags(t *testing.T) {
},
}
f := fuzz.New().NilChance(.5).NumElements(0, 100)
c := NewConverter()
c := NewConverter(DefaultNameFunc)
c.Debug = testLogger(t)
for i, item := range table {
@ -691,7 +753,7 @@ func TestConverter_FieldRename(t *testing.T) {
NameMeta
}
c := NewConverter()
c := NewConverter(DefaultNameFunc)
err := c.SetStructFieldCopy(WeirdMeta{}, "WeirdMeta", TypeMeta{}, "TypeMeta")
if err != nil {
t.Fatalf("unexpected error %v", err)
@ -764,131 +826,22 @@ 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
},
)
func objDiff(a, b interface{}) string {
ab, err := json.Marshal(a)
if err != nil {
t.Fatalf("unexpected error: %v", err)
panic("a")
}
simple := &InternalSimple{
TestString: "foo",
}
s.Log(t)
out, err := s.ConvertToVersion(simple, externalGV.String())
bb, err := json.Marshal(b)
if err != nil {
t.Fatalf("unexpected error: %v", err)
panic("b")
}
return util.StringDiff(string(ab), string(bb))
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)
}
// An alternate diff attempt, in case json isn't showing you
// the difference. (reflect.DeepEqual makes a distinction between
// nil and empty slices, for example.)
//return util.StringDiff(
// fmt.Sprintf("%#v", a),
// fmt.Sprintf("%#v", b),
//)
}

View File

@ -1,98 +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"
"k8s.io/kubernetes/pkg/api/unversioned"
)
type notRegisteredErr struct {
gvk unversioned.GroupVersionKind
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 {
if k.t != nil {
return fmt.Sprintf("no kind is registered for the type %v", k.t)
}
if len(k.gvk.Kind) == 0 {
return fmt.Sprintf("no version %q has been registered", k.gvk.GroupVersion())
}
if len(k.gvk.Version) == 0 {
return fmt.Sprintf("no kind %q is registered for the default version of group %q", k.gvk.Kind, k.gvk.Group)
}
return fmt.Sprintf("no kind %q is registered for version %q", k.gvk.Kind, k.gvk.GroupVersion())
}
// IsNotRegisteredError returns true if the error indicates the provided
// object or input data is not registered.
func IsNotRegisteredError(err error) bool {
if err == nil {
return false
}
_, ok := err.(*notRegisteredErr)
return ok
}
type missingKindErr struct {
data string
}
func NewMissingKindErr(data string) error {
return &missingKindErr{data}
}
func (k *missingKindErr) Error() string {
return fmt.Sprintf("Object 'Kind' is missing in '%s'", k.data)
}
func IsMissingKind(err error) bool {
if err == nil {
return false
}
_, ok := err.(*missingKindErr)
return ok
}
type missingVersionErr struct {
data string
}
func NewMissingVersionErr(data string) error {
return &missingVersionErr{data}
}
func (k *missingVersionErr) Error() string {
return fmt.Sprintf("Object 'apiVersion' is missing in '%s'", k.data)
}
func IsMissingVersion(err error) bool {
if err == nil {
return false
}
_, ok := err.(*missingVersionErr)
return ok
}

View File

@ -1,464 +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"
"k8s.io/kubernetes/pkg/api/unversioned"
)
// Scheme defines an entire encoding and decoding scheme.
type Scheme struct {
// versionMap allows one to figure out the go type of an object with
// the given version and name.
gvkToType map[unversioned.GroupVersionKind]reflect.Type
// typeToGroupVersion allows one to find metadata for a given go object.
// The reflect.Type we index by should *not* be a pointer.
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
// default coverting behavior.
converter *Converter
// cloner stores all registered copy functions. It also has default
// deep copy behavior.
cloner *Cloner
// Indent will cause the JSON output from Encode to be indented,
// if and only if it is true.
Indent bool
}
// NewScheme manufactures a new scheme.
func NewScheme() *Scheme {
s := &Scheme{
gvkToType: map[unversioned.GroupVersionKind]reflect.Type{},
typeToGVK: map[reflect.Type][]unversioned.GroupVersionKind{},
unversionedTypes: map[reflect.Type]unversioned.GroupVersionKind{},
unversionedKinds: map[string]reflect.Type{},
converter: NewConverter(),
cloner: NewCloner(),
}
s.converter.nameFunc = s.nameFunc
return s
}
// Log sets a logger on the scheme. For test purposes only
func (s *Scheme) Log(l DebugLogger) {
s.converter.Debug = l
}
// nameFunc returns the name of the type that we wish to use to determine when two types attempt
// a conversion. Defaults to the go name of the type if the type is not registered.
func (s *Scheme) nameFunc(t reflect.Type) string {
// find the preferred names for this type
gvks, ok := s.typeToGVK[t]
if !ok {
return t.Name()
}
for _, gvk := range gvks {
internalGV := gvk.GroupVersion()
internalGV.Version = "__internal" // this is hacky and maybe should be passed in
internalGVK := internalGV.WithKind(gvk.Kind)
if internalType, exists := s.gvkToType[internalGVK]; exists {
return s.typeToGVK[internalType][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'.
// 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) 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 {
t := reflect.TypeOf(obj)
if t.Kind() != reflect.Ptr {
panic("All types must be pointers to structs.")
}
t = t.Elem()
if t.Kind() != reflect.Struct {
panic("All types must be pointers to structs.")
}
gvk := gv.WithKind(t.Name())
s.gvkToType[gvk] = t
s.typeToGVK[t] = append(s.typeToGVK[t], gvk)
}
}
// AddKnownTypeWithName is like AddKnownTypes, but it lets you specify what this type should
// be encoded as. Useful for testing when you don't want to make multiple packages to define
// your structs.
func (s *Scheme) AddKnownTypeWithName(gvk unversioned.GroupVersionKind, obj interface{}) {
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 {
panic("All types must be pointers to structs.")
}
t = t.Elem()
if t.Kind() != reflect.Struct {
panic("All types must be pointers to structs.")
}
s.gvkToType[gvk] = t
s.typeToGVK[t] = append(s.typeToGVK[t], gvk)
}
// KnownTypes returns an array of the types that are known for a particular version.
func (s *Scheme) KnownTypes(gv unversioned.GroupVersion) map[string]reflect.Type {
types := map[string]reflect.Type{}
for gvk, t := range s.gvkToType {
if gv != gvk.GroupVersion() {
continue
}
types[gvk.Kind] = t
}
return types
}
// NewObject returns a new object of the given version and name,
// or an error if it hasn't been registered.
func (s *Scheme) NewObject(kind unversioned.GroupVersionKind) (interface{}, error) {
if t, exists := s.gvkToType[kind]; exists {
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}
}
// AddConversionFuncs adds functions to the list of conversion functions. The given
// functions should know how to convert between two of your API objects, or their
// sub-objects. We deduce how to call these functions from the types of their two
// parameters; see the comment for Converter.Register.
//
// Note that, if you need to copy sub-objects that didn't change, you can use the
// conversion.Scope object that will be passed to your conversion function.
// Additionally, all conversions started by Scheme will set the SrcVersion and
// DestVersion fields on the Meta object. Example:
//
// s.AddConversionFuncs(
// func(in *InternalObject, out *ExternalObject, scope conversion.Scope) error {
// // You can depend on Meta() being non-nil, and this being set to
// // the source version, e.g., ""
// s.Meta().SrcVersion
// // You can depend on this being set to the destination version,
// // e.g., "v1".
// s.Meta().DestVersion
// // Call scope.Convert to copy sub-fields.
// s.Convert(&in.SubFieldThatMoved, &out.NewLocation.NewName, 0)
// return nil
// },
// )
//
// (For more detail about conversion functions, see Converter.Register's comment.)
//
// Also note that the default behavior, if you don't add a conversion function, is to
// sanely copy fields that have the same names and same type names. It's OK if the
// destination type has extra fields, but it must not remove any. So you only need to
// add conversion functions for things with changed/removed fields.
func (s *Scheme) AddConversionFuncs(conversionFuncs ...interface{}) error {
for _, f := range conversionFuncs {
if err := s.converter.RegisterConversionFunc(f); err != nil {
return err
}
}
return nil
}
// Similar to AddConversionFuncs, but registers conversion functions that were
// automatically generated.
func (s *Scheme) AddGeneratedConversionFuncs(conversionFuncs ...interface{}) error {
for _, f := range conversionFuncs {
if err := s.converter.RegisterGeneratedConversionFunc(f); err != nil {
return err
}
}
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.
// Note that to copy sub-objects, you can use the conversion.Cloner object that
// will be passed to your deep-copy function.
func (s *Scheme) AddDeepCopyFuncs(deepCopyFuncs ...interface{}) error {
for _, f := range deepCopyFuncs {
if err := s.cloner.RegisterDeepCopyFunc(f); err != nil {
return err
}
}
return nil
}
// Similar to AddDeepCopyFuncs, but registers deep copy functions that were
// automatically generated.
func (s *Scheme) AddGeneratedDeepCopyFuncs(deepCopyFuncs ...interface{}) error {
for _, f := range deepCopyFuncs {
if err := s.cloner.RegisterGeneratedDeepCopyFunc(f); err != nil {
return err
}
}
return nil
}
// AddStructFieldConversion allows you to specify a mechanical copy for a moved
// or renamed struct field without writing an entire conversion function. See
// the comment in Converter.SetStructFieldCopy for parameter details.
// Call as many times as needed, even on the same fields.
func (s *Scheme) AddStructFieldConversion(srcFieldType interface{}, srcFieldName string, destFieldType interface{}, destFieldName string) error {
return s.converter.SetStructFieldCopy(srcFieldType, srcFieldName, destFieldType, destFieldName)
}
// AddDefaultingFuncs adds functions to the list of default-value functions.
// Each of the given functions is responsible for applying default values
// when converting an instance of a versioned API object into an internal
// API object. These functions do not need to handle sub-objects. We deduce
// how to call these functions from the types of their two parameters.
//
// s.AddDefaultingFuncs(
// func(obj *v1.Pod) {
// if obj.OptionalField == "" {
// obj.OptionalField = "DefaultValue"
// }
// },
// )
func (s *Scheme) AddDefaultingFuncs(defaultingFuncs ...interface{}) error {
for _, f := range defaultingFuncs {
err := s.converter.RegisterDefaultingFunc(f)
if err != nil {
return err
}
}
return nil
}
// Recognizes returns true if the scheme is able to handle the provided group,version,kind
// of an object.
func (s *Scheme) Recognizes(gvk unversioned.GroupVersionKind) bool {
_, exists := s.gvkToType[gvk]
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
// 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
// a specific input type in conversion, such as a map[string]string to structs.
func (s *Scheme) RegisterInputDefaults(in interface{}, fn FieldMappingFunc, defaultFlags FieldMatchingFlags) error {
return s.converter.RegisterInputDefaults(in, fn, defaultFlags)
}
// Performs a deep copy of the given object.
func (s *Scheme) DeepCopy(in interface{}) (interface{}, error) {
return s.cloner.DeepCopy(in)
}
// Convert will attempt to convert in into out. Both must be pointers. For easy
// testing of conversion functions. Returns an error if the conversion isn't
// possible. You can call this with types that haven't been registered (for example,
// a to test conversion of types that are nested within registered types), but in
// that case, the conversion.Scope object passed to your conversion functions won't
// have SrcVersion or DestVersion fields set correctly in Meta().
func (s *Scheme) Convert(in, out interface{}) error {
inVersion := unversioned.GroupVersion{Group: "unknown", Version: "unknown"}
outVersion := unversioned.GroupVersion{Group: "unknown", Version: "unknown"}
if gvk, err := s.ObjectKind(in); err == nil {
inVersion = gvk.GroupVersion()
}
if gvk, err := s.ObjectKind(out); err == nil {
outVersion = gvk.GroupVersion()
}
flags, meta := s.generateConvertMeta(inVersion, outVersion, in)
if flags == 0 {
flags = AllowDifferentFieldTypeNames
}
return s.converter.Convert(in, out, flags, meta)
}
// ConvertToVersion attempts to convert an input object to its matching Kind in another
// version within this scheme. Will return an error if the provided version does not
// contain the inKind (or a mapping by name defined with AddKnownTypeWithName).
func (s *Scheme) ConvertToVersion(in interface{}, outGroupVersionString string) (interface{}, error) {
t := reflect.TypeOf(in)
if t.Kind() != reflect.Ptr {
return nil, fmt.Errorf("only pointer types may be converted: %v", t)
}
t = t.Elem()
if t.Kind() != reflect.Struct {
return nil, fmt.Errorf("only pointers to struct types may be converted: %v", t)
}
outVersion, err := unversioned.ParseGroupVersion(outGroupVersionString)
if err != nil {
return nil, err
}
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)
if err != nil {
return nil, err
}
out, err := s.NewObject(outKind)
if err != nil {
return nil, err
}
flags, meta := s.generateConvertMeta(inKind.GroupVersion(), outVersion, in)
if err := s.converter.Convert(in, out, flags, meta); err != nil {
return nil, err
}
return out, nil
}
// Converter allows access to the converter for the scheme
func (s *Scheme) Converter() *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.
func (s *Scheme) generateConvertMeta(srcGroupVersion, destGroupVersion unversioned.GroupVersion, in interface{}) (FieldMatchingFlags, *Meta) {
t := reflect.TypeOf(in)
return s.converter.inputDefaultFlags[t], &Meta{
SrcVersion: srcGroupVersion.String(),
DestVersion: destGroupVersion.String(),
KeyNameMapping: s.converter.inputFieldMappingFuncs[t],
}
}
// ObjectKind returns the group,version,kind of the go object,
// or an error if it's not a pointer or is unregistered.
func (s *Scheme) ObjectKind(obj interface{}) (unversioned.GroupVersionKind, error) {
gvks, err := s.ObjectKinds(obj)
if err != nil {
return unversioned.GroupVersionKind{}, err
}
return gvks[0], nil
}
// ObjectKinds returns all possible group,version,kind of the go object,
// or an error if it's not a pointer or is unregistered.
func (s *Scheme) ObjectKinds(obj interface{}) ([]unversioned.GroupVersionKind, error) {
v, err := EnforcePtr(obj)
if err != nil {
return nil, err
}
t := v.Type()
gvks, ok := s.typeToGVK[t]
if !ok {
return nil, &notRegisteredErr{t: t}
}
return gvks, nil
}
// maybeCopy copies obj if it is not a pointer, to get a settable/addressable
// object. Guaranteed to return a pointer.
func maybeCopy(obj interface{}) interface{} {
v := reflect.ValueOf(obj)
if v.Kind() == reflect.Ptr {
return obj
}
v2 := reflect.New(v.Type())
v2.Elem().Set(v)
return v2.Interface()
}

View File

@ -1,181 +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"
"testing"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/util"
"github.com/google/gofuzz"
flag "github.com/spf13/pflag"
)
var fuzzIters = flag.Int("fuzz-iters", 50, "How many fuzzing iterations to do.")
// Test a weird version/kind embedding format.
type MyWeirdCustomEmbeddedVersionKindField struct {
ID string `json:"ID,omitempty"`
APIVersion string `json:"myVersionKey,omitempty"`
ObjectKind string `json:"myKindKey,omitempty"`
Z string `json:"Z,omitempty"`
Y uint64 `json:"Y,omitempty"`
}
type TestType1 struct {
MyWeirdCustomEmbeddedVersionKindField `json:",inline"`
A string `json:"A,omitempty"`
B int `json:"B,omitempty"`
C int8 `json:"C,omitempty"`
D int16 `json:"D,omitempty"`
E int32 `json:"E,omitempty"`
F int64 `json:"F,omitempty"`
G uint `json:"G,omitempty"`
H uint8 `json:"H,omitempty"`
I uint16 `json:"I,omitempty"`
J uint32 `json:"J,omitempty"`
K uint64 `json:"K,omitempty"`
L bool `json:"L,omitempty"`
M map[string]int `json:"M,omitempty"`
N map[string]TestType2 `json:"N,omitempty"`
O *TestType2 `json:"O,omitempty"`
P []TestType2 `json:"Q,omitempty"`
}
type TestType2 struct {
A string `json:"A,omitempty"`
B int `json:"B,omitempty"`
}
type ExternalTestType2 struct {
A string `json:"A,omitempty"`
B int `json:"B,omitempty"`
}
type ExternalTestType1 struct {
MyWeirdCustomEmbeddedVersionKindField `json:",inline"`
A string `json:"A,omitempty"`
B int `json:"B,omitempty"`
C int8 `json:"C,omitempty"`
D int16 `json:"D,omitempty"`
E int32 `json:"E,omitempty"`
F int64 `json:"F,omitempty"`
G uint `json:"G,omitempty"`
H uint8 `json:"H,omitempty"`
I uint16 `json:"I,omitempty"`
J uint32 `json:"J,omitempty"`
K uint64 `json:"K,omitempty"`
L bool `json:"L,omitempty"`
M map[string]int `json:"M,omitempty"`
N map[string]ExternalTestType2 `json:"N,omitempty"`
O *ExternalTestType2 `json:"O,omitempty"`
P []ExternalTestType2 `json:"Q,omitempty"`
}
type ExternalInternalSame struct {
MyWeirdCustomEmbeddedVersionKindField `json:",inline"`
A TestType2 `json:"A,omitempty"`
}
// TestObjectFuzzer can randomly populate all the above objects.
var TestObjectFuzzer = fuzz.New().NilChance(.5).NumElements(1, 100).Funcs(
func(j *MyWeirdCustomEmbeddedVersionKindField, c fuzz.Continue) {
// We have to customize the randomization of MyWeirdCustomEmbeddedVersionKindFields because their
// APIVersion and Kind must remain blank in memory.
j.APIVersion = ""
j.ObjectKind = ""
j.ID = c.RandString()
},
)
// Returns a new Scheme set up with the test objects.
func GetTestScheme() *Scheme {
internalGV := unversioned.GroupVersion{Version: "__internal"}
externalGV := unversioned.GroupVersion{Version: "v1"}
s := NewScheme()
// Ordinarily, we wouldn't add TestType2, but because this is a test and
// both types are from the same package, we need to get it into the system
// so that converter will match it with ExternalType2.
s.AddKnownTypes(internalGV, &TestType1{}, &TestType2{}, &ExternalInternalSame{})
s.AddKnownTypes(externalGV, &ExternalInternalSame{})
s.AddKnownTypeWithName(externalGV.WithKind("TestType1"), &ExternalTestType1{})
s.AddKnownTypeWithName(externalGV.WithKind("TestType2"), &ExternalTestType2{})
s.AddKnownTypeWithName(internalGV.WithKind("TestType3"), &TestType1{})
s.AddKnownTypeWithName(externalGV.WithKind("TestType3"), &ExternalTestType1{})
return s
}
func objDiff(a, b interface{}) string {
ab, err := json.Marshal(a)
if err != nil {
panic("a")
}
bb, err := json.Marshal(b)
if err != nil {
panic("b")
}
return util.StringDiff(string(ab), string(bb))
// An alternate diff attempt, in case json isn't showing you
// the difference. (reflect.DeepEqual makes a distinction between
// nil and empty slices, for example.)
//return util.StringDiff(
// fmt.Sprintf("%#v", a),
// fmt.Sprintf("%#v", b),
//)
}
func TestKnownTypes(t *testing.T) {
s := GetTestScheme()
if len(s.KnownTypes(unversioned.GroupVersion{Group: "group", Version: "v2"})) != 0 {
t.Errorf("should have no known types for v2")
}
types := s.KnownTypes(unversioned.GroupVersion{Version: "v1"})
for _, s := range []string{"TestType1", "TestType2", "TestType3", "ExternalInternalSame"} {
if _, ok := types[s]; !ok {
t.Errorf("missing type %q", s)
}
}
}
func TestConvertToVersion(t *testing.T) {
s := GetTestScheme()
tt := &TestType1{A: "I'm not a pointer object"}
other, err := s.ConvertToVersion(tt, "v1")
if err != nil {
t.Fatalf("Failure: %v", err)
}
converted, ok := other.(*ExternalTestType1)
if !ok {
t.Fatalf("Got wrong type")
}
if tt.A != converted.A {
t.Fatalf("Failed to convert object correctly: %#v", converted)
}
}
func TestConvertToVersionErr(t *testing.T) {
s := GetTestScheme()
tt := TestType1{A: "I'm not a pointer object"}
_, err := s.ConvertToVersion(tt, "v1")
if err == nil {
t.Fatalf("unexpected non-error")
}
}

View File

@ -36,7 +36,6 @@ import (
"k8s.io/kubernetes/pkg/api/meta"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/conversion"
"k8s.io/kubernetes/pkg/labels"
"k8s.io/kubernetes/pkg/runtime"
utilerrors "k8s.io/kubernetes/pkg/util/errors"
@ -181,7 +180,7 @@ func (p *VersionedPrinter) PrintObj(obj runtime.Object, w io.Writer) error {
continue
}
converted, err := p.convertor.ConvertToVersion(obj, version.String())
if conversion.IsNotRegisteredError(err) {
if runtime.IsNotRegisteredError(err) {
continue
}
if err != nil {

View File

@ -27,7 +27,6 @@ import (
"strings"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/conversion"
"k8s.io/kubernetes/pkg/util/sets"
)
@ -42,7 +41,7 @@ type ConversionGenerator interface {
AssumePrivateConversions()
}
func NewConversionGenerator(scheme *conversion.Scheme, targetPkg string) ConversionGenerator {
func NewConversionGenerator(scheme *Scheme, targetPkg string) ConversionGenerator {
g := &conversionGenerator{
scheme: scheme,
@ -66,7 +65,7 @@ func NewConversionGenerator(scheme *conversion.Scheme, targetPkg string) Convers
var complexTypes []reflect.Kind = []reflect.Kind{reflect.Map, reflect.Ptr, reflect.Slice, reflect.Interface, reflect.Struct}
type conversionGenerator struct {
scheme *conversion.Scheme
scheme *Scheme
nameFormat string
generatedNamePrefix string
@ -105,7 +104,7 @@ func (g *conversionGenerator) GenerateConversionsForType(gv unversioned.GroupVer
internalVersion := gv
internalVersion.Version = APIVersionInternal
internalObj, err := g.scheme.NewObject(internalVersion.WithKind(kind))
internalObj, err := g.scheme.New(internalVersion.WithKind(kind))
if err != nil {
return fmt.Errorf("cannot create an object of type %v in internal version", kind)
}

View File

@ -24,7 +24,6 @@ import (
"sort"
"strings"
"k8s.io/kubernetes/pkg/conversion"
"k8s.io/kubernetes/pkg/util/sets"
)
@ -69,7 +68,7 @@ type DeepCopyGenerator interface {
OverwritePackage(pkg, overwrite string)
}
func NewDeepCopyGenerator(scheme *conversion.Scheme, targetPkg string, include sets.String) DeepCopyGenerator {
func NewDeepCopyGenerator(scheme *Scheme, targetPkg string, include sets.String) DeepCopyGenerator {
g := &deepCopyGenerator{
scheme: scheme,
targetPkg: targetPkg,
@ -91,7 +90,7 @@ type pkgPathNamePair struct {
}
type deepCopyGenerator struct {
scheme *conversion.Scheme
scheme *Scheme
targetPkg string
copyables map[reflect.Type]bool
// map of package names to shortname

View File

@ -17,31 +17,86 @@ limitations under the License.
package runtime
import (
"k8s.io/kubernetes/pkg/conversion"
"fmt"
"reflect"
"k8s.io/kubernetes/pkg/api/unversioned"
)
type notRegisteredErr struct {
gvk unversioned.GroupVersionKind
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 {
if k.t != nil {
return fmt.Sprintf("no kind is registered for the type %v", k.t)
}
if len(k.gvk.Kind) == 0 {
return fmt.Sprintf("no version %q has been registered", k.gvk.GroupVersion())
}
if len(k.gvk.Version) == 0 {
return fmt.Sprintf("no kind %q is registered for the default version of group %q", k.gvk.Kind, k.gvk.Group)
}
return fmt.Sprintf("no kind %q is registered for version %q", k.gvk.Kind, k.gvk.GroupVersion())
}
// IsNotRegisteredError returns true if the error indicates the provided
// object or input data is not registered.
func IsNotRegisteredError(err error) bool {
return conversion.IsNotRegisteredError(err)
if err == nil {
return false
}
_, ok := err.(*notRegisteredErr)
return ok
}
type missingKindErr struct {
data string
}
func NewMissingKindErr(data string) error {
return &missingKindErr{data}
}
func (k *missingKindErr) Error() string {
return fmt.Sprintf("Object 'Kind' is missing in '%s'", k.data)
}
// IsMissingKind returns true if the error indicates that the provided object
// is missing a 'Kind' field.
func IsMissingKind(err error) bool {
return conversion.IsMissingKind(err)
if err == nil {
return false
}
_, ok := err.(*missingKindErr)
return ok
}
type missingVersionErr struct {
data string
}
// IsMissingVersion returns true if the error indicates that the provided object
// is missing a 'Versioj' field.
func IsMissingVersion(err error) bool {
return conversion.IsMissingVersion(err)
}
func NewMissingKindErr(data string) error {
return conversion.NewMissingKindErr(data)
}
func NewMissingVersionErr(data string) error {
return conversion.NewMissingVersionErr(data)
return &missingVersionErr{data}
}
func (k *missingVersionErr) Error() string {
return fmt.Sprintf("Object 'apiVersion' is missing in '%s'", k.data)
}
func IsMissingVersion(err error) bool {
if err == nil {
return false
}
_, ok := err.(*missingVersionErr)
return ok
}

View File

@ -24,8 +24,10 @@ import (
)
const (
APIVersionInternal = "__internal"
APIVersionUnversioned = "__unversioned"
// APIVersionInternal may be used if you are registering a type that should not
// be considered stable or serialized - it is a convention only and has no
// special behavior in this package.
APIVersionInternal = "__internal"
)
// Typer retrieves information about an object's group, version, and kind.

View File

@ -25,47 +25,115 @@ import (
"k8s.io/kubernetes/pkg/conversion"
)
// Scheme defines methods for serializing and deserializing API objects. It
// is an adaptation of conversion's Scheme for our API objects.
// Scheme defines methods for serializing and deserializing API objects, a type
// registry for converting group, version, and kind information to and from Go
// schemas, and mappings between Go schemas of different versions. A scheme is the
// foundation for a versioned API and versioned configuration over time.
//
// In a Scheme, a Type is a particular Go struct, a Version is a point-in-time
// identifier for a particular representation of that Type (typically backwards
// compatible), a Kind is the unique name for that Type within the Version, and a
// Group identifies a set of Versions, Kinds, and Types that evolve over time. An
// Unversioned Type is one that is not yet formally bound to a type and is promised
// to be backwards compatible (effectively a "v1" of a Type that does not expect
// to break in the future).
//
// Schemes are not expected to change at runtime and are only threadsafe after
// registration is complete.
type Scheme struct {
raw *conversion.Scheme
// versionMap allows one to figure out the go type of an object with
// the given version and name.
gvkToType map[unversioned.GroupVersionKind]reflect.Type
// typeToGroupVersion allows one to find metadata for a given go object.
// The reflect.Type we index by should *not* be a pointer.
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
// Map from version and resource to the corresponding func to convert
// resource field labels in that version to internal version.
fieldLabelConversionFuncs map[string]map[string]FieldLabelConversionFunc
// converter stores all registered conversion functions. It also has
// default coverting behavior.
converter *conversion.Converter
// cloner stores all registered copy functions. It also has default
// deep copy behavior.
cloner *conversion.Cloner
}
// Function to convert a field selector to internal representation.
type FieldLabelConversionFunc func(label, value string) (internalLabel, internalValue string, err error)
func (self *Scheme) Raw() *conversion.Scheme {
return self.raw
// NewScheme creates a new Scheme. This scheme is pluggable by default.
func NewScheme() *Scheme {
s := &Scheme{
gvkToType: map[unversioned.GroupVersionKind]reflect.Type{},
typeToGVK: map[reflect.Type][]unversioned.GroupVersionKind{},
unversionedTypes: map[reflect.Type]unversioned.GroupVersionKind{},
unversionedKinds: map[string]reflect.Type{},
cloner: conversion.NewCloner(),
fieldLabelConversionFuncs: map[string]map[string]FieldLabelConversionFunc{},
}
s.converter = conversion.NewConverter(s.nameFunc)
s.AddConversionFuncs(DefaultEmbeddedConversions()...)
// Enable map[string][]string conversions by default
if err := s.AddConversionFuncs(DefaultStringConversions...); err != nil {
panic(err)
}
if err := s.RegisterInputDefaults(&map[string][]string{}, JSONKeyMapper, conversion.AllowDifferentFieldTypeNames|conversion.IgnoreMissingFields); err != nil {
panic(err)
}
if err := s.RegisterInputDefaults(&url.Values{}, JSONKeyMapper, conversion.AllowDifferentFieldTypeNames|conversion.IgnoreMissingFields); err != nil {
panic(err)
}
return s
}
// nameFunc returns the name of the type that we wish to use to determine when two types attempt
// a conversion. Defaults to the go name of the type if the type is not registered.
func (s *Scheme) nameFunc(t reflect.Type) string {
// find the preferred names for this type
gvks, ok := s.typeToGVK[t]
if !ok {
return t.Name()
}
for _, gvk := range gvks {
internalGV := gvk.GroupVersion()
internalGV.Version = "__internal" // this is hacky and maybe should be passed in
internalGVK := internalGV.WithKind(gvk.Kind)
if internalType, exists := s.gvkToType[internalGVK]; exists {
return s.typeToGVK[internalType][0].Kind
}
}
return gvks[0].Kind
}
// fromScope gets the input version, desired output version, and desired Scheme
// from a conversion.Scope.
func (self *Scheme) fromScope(s conversion.Scope) (inVersion, outVersion string, scheme *Scheme) {
scheme = self
inVersion = s.Meta().SrcVersion
outVersion = s.Meta().DestVersion
func (s *Scheme) fromScope(scope conversion.Scope) (inVersion, outVersion string, scheme *Scheme) {
scheme = s
inVersion = scope.Meta().SrcVersion
outVersion = scope.Meta().DestVersion
return inVersion, outVersion, scheme
}
// NewScheme creates a new Scheme. This scheme is pluggable by default.
func NewScheme() *Scheme {
s := &Scheme{conversion.NewScheme(), map[string]map[string]FieldLabelConversionFunc{}}
s.AddConversionFuncs(DefaultEmbeddedConversions()...)
// Enable map[string][]string conversions by default
if err := s.raw.AddConversionFuncs(DefaultStringConversions...); err != nil {
panic(err)
}
if err := s.raw.RegisterInputDefaults(&map[string][]string{}, JSONKeyMapper, conversion.AllowDifferentFieldTypeNames|conversion.IgnoreMissingFields); err != nil {
panic(err)
}
if err := s.raw.RegisterInputDefaults(&url.Values{}, JSONKeyMapper, conversion.AllowDifferentFieldTypeNames|conversion.IgnoreMissingFields); err != nil {
panic(err)
}
return s
// Converter allows access to the converter for the scheme
func (s *Scheme) Converter() *conversion.Converter {
return s.converter
}
// AddUnversionedTypes registers the provided types as "unversioned", which means that they follow special rules.
@ -75,111 +143,221 @@ func NewScheme() *Scheme {
//
// TODO: there is discussion about removing unversioned and replacing it with objects that are manifest into
// every version with particular schemas. Resolve tihs method at that point.
func (s *Scheme) AddUnversionedTypes(gv unversioned.GroupVersion, types ...Object) {
interfaces := make([]interface{}, len(types))
for i := range types {
interfaces[i] = types[i]
func (s *Scheme) AddUnversionedTypes(version unversioned.GroupVersion, types ...Object) {
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
}
s.raw.AddUnversionedTypes(gv, interfaces...)
}
// AddKnownTypes registers the types of the arguments to the marshaller of the package api.
// AddKnownTypes registers all types passed in 'types' as being members of version 'version'.
// All objects passed to types should be pointers to structs. The name that go reports for
// the struct becomes the "kind" field when encoding. Version may not be empty - use the
// APIVersionInternal constant if you have a type that does not have a formal version.
func (s *Scheme) AddKnownTypes(gv unversioned.GroupVersion, types ...Object) {
interfaces := make([]interface{}, len(types))
for i := range types {
interfaces[i] = types[i]
if len(gv.Version) == 0 {
panic(fmt.Sprintf("version is required on all types: %s %v", gv, types[0]))
}
s.raw.AddKnownTypes(gv, interfaces...)
}
for _, obj := range types {
t := reflect.TypeOf(obj)
if t.Kind() != reflect.Ptr {
panic("All types must be pointers to structs.")
}
t = t.Elem()
if t.Kind() != reflect.Struct {
panic("All types must be pointers to structs.")
}
// AddIgnoredConversionType declares a particular conversion that should be ignored - during conversion
// this method is not invoked.
func (s *Scheme) AddIgnoredConversionType(from, to interface{}) error {
return s.raw.AddIgnoredConversionType(from, to)
gvk := gv.WithKind(t.Name())
s.gvkToType[gvk] = t
s.typeToGVK[t] = append(s.typeToGVK[t], gvk)
}
}
// AddKnownTypeWithName is like AddKnownTypes, but it lets you specify what this type should
// be encoded as. Useful for testing when you don't want to make multiple packages to define
// your structs.
// your structs. Version may not be empty - use the APIVersionInternal constant if you have a
// type that does not have a formal version.
func (s *Scheme) AddKnownTypeWithName(gvk unversioned.GroupVersionKind, obj Object) {
s.raw.AddKnownTypeWithName(gvk, 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 {
panic("All types must be pointers to structs.")
}
t = t.Elem()
if t.Kind() != reflect.Struct {
panic("All types must be pointers to structs.")
}
s.gvkToType[gvk] = t
s.typeToGVK[t] = append(s.typeToGVK[t], gvk)
}
// KnownTypes returns the types known for the given version.
// Return value must be treated as read-only.
func (s *Scheme) KnownTypes(gv unversioned.GroupVersion) map[string]reflect.Type {
return s.raw.KnownTypes(gv)
types := make(map[string]reflect.Type)
for gvk, t := range s.gvkToType {
if gv != gvk.GroupVersion() {
continue
}
types[gvk.Kind] = t
}
return types
}
// ObjectKind returns the default group,version,kind of the given Object.
// ObjectKind returns the group,version,kind of the go object,
// or an error if it's not a pointer or is unregistered.
func (s *Scheme) ObjectKind(obj Object) (unversioned.GroupVersionKind, error) {
return s.raw.ObjectKind(obj)
gvks, err := s.ObjectKinds(obj)
if err != nil {
return unversioned.GroupVersionKind{}, err
}
return gvks[0], nil
}
// ObjectKinds returns the all possible group,version,kind of the given Object.
// ObjectKinds returns all possible group,version,kind of the go object,
// or an error if it's not a pointer or is unregistered.
func (s *Scheme) ObjectKinds(obj Object) ([]unversioned.GroupVersionKind, error) {
return s.raw.ObjectKinds(obj)
v, err := conversion.EnforcePtr(obj)
if err != nil {
return nil, err
}
t := v.Type()
gvks, ok := s.typeToGVK[t]
if !ok {
return nil, &notRegisteredErr{t: t}
}
return gvks, nil
}
// Recognizes returns true if the scheme is able to handle the provided group,version,kind
// of an object.
func (s *Scheme) Recognizes(gvk unversioned.GroupVersionKind) bool {
return s.raw.Recognizes(gvk)
_, exists := s.gvkToType[gvk]
return exists
}
func (s *Scheme) IsUnversioned(obj Object) (bool, bool) {
return s.raw.IsUnversioned(obj)
v, err := conversion.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
}
// New returns a new API object of the given version ("" for internal
// representation) and name, or an error if it hasn't been registered.
// New returns a new API object of the given version and name, or an error if it hasn't
// been registered. The version and kind fields must be specified.
func (s *Scheme) New(kind unversioned.GroupVersionKind) (Object, error) {
obj, err := s.raw.NewObject(kind)
if err != nil {
return nil, err
if t, exists := s.gvkToType[kind]; exists {
return reflect.New(t).Interface().(Object), nil
}
return obj.(Object), nil
if t, exists := s.unversionedKinds[kind.Kind]; exists {
return reflect.New(t).Interface().(Object), nil
}
return nil, &notRegisteredErr{gvk: kind}
}
// Log sets a logger on the scheme. For test purposes only
func (s *Scheme) Log(l conversion.DebugLogger) {
s.raw.Log(l)
s.converter.Debug = l
}
// AddConversionFuncs adds a function to the list of conversion functions. The given
// function should know how to convert between two API objects. We deduce how to call
// it from the types of its two parameters; see the comment for
// Converter.RegisterConversionFunction.
// AddIgnoredConversionType identifies a pair of types that should be skipped by
// 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)
}
// AddConversionFuncs adds functions to the list of conversion functions. The given
// functions should know how to convert between two of your API objects, or their
// sub-objects. We deduce how to call these functions from the types of their two
// parameters; see the comment for Converter.Register.
//
// Note that, if you need to copy sub-objects that didn't change, it's safe to call
// Convert() inside your conversionFuncs, as long as you don't start a conversion
// chain that's infinitely recursive.
// Note that, if you need to copy sub-objects that didn't change, you can use the
// conversion.Scope object that will be passed to your conversion function.
// Additionally, all conversions started by Scheme will set the SrcVersion and
// DestVersion fields on the Meta object. Example:
//
// s.AddConversionFuncs(
// func(in *InternalObject, out *ExternalObject, scope conversion.Scope) error {
// // You can depend on Meta() being non-nil, and this being set to
// // the source version, e.g., ""
// s.Meta().SrcVersion
// // You can depend on this being set to the destination version,
// // e.g., "v1".
// s.Meta().DestVersion
// // Call scope.Convert to copy sub-fields.
// s.Convert(&in.SubFieldThatMoved, &out.NewLocation.NewName, 0)
// return nil
// },
// )
//
// (For more detail about conversion functions, see Converter.Register's comment.)
//
// Also note that the default behavior, if you don't add a conversion function, is to
// sanely copy fields that have the same names. It's OK if the destination type has
// extra fields, but it must not remove any. So you only need to add a conversion
// function for things with changed/removed fields.
// sanely copy fields that have the same names and same type names. It's OK if the
// destination type has extra fields, but it must not remove any. So you only need to
// add conversion functions for things with changed/removed fields.
func (s *Scheme) AddConversionFuncs(conversionFuncs ...interface{}) error {
return s.raw.AddConversionFuncs(conversionFuncs...)
for _, f := range conversionFuncs {
if err := s.converter.RegisterConversionFunc(f); err != nil {
return err
}
}
return nil
}
// Similar to AddConversionFuncs, but registers conversion functions that were
// automatically generated.
func (s *Scheme) AddGeneratedConversionFuncs(conversionFuncs ...interface{}) error {
return s.raw.AddGeneratedConversionFuncs(conversionFuncs...)
for _, f := range conversionFuncs {
if err := s.converter.RegisterGeneratedConversionFunc(f); err != nil {
return err
}
}
return nil
}
// AddDeepCopyFuncs adds a function to the list of deep-copy functions.
// For the expected format of deep-copy function, see the comment for
// Copier.RegisterDeepCopyFunction.
func (s *Scheme) AddDeepCopyFuncs(deepCopyFuncs ...interface{}) error {
return s.raw.AddDeepCopyFuncs(deepCopyFuncs...)
for _, f := range deepCopyFuncs {
if err := s.cloner.RegisterDeepCopyFunc(f); err != nil {
return err
}
}
return nil
}
// Similar to AddDeepCopyFuncs, but registers deep-copy functions that were
// automatically generated.
func (s *Scheme) AddGeneratedDeepCopyFuncs(deepCopyFuncs ...interface{}) error {
return s.raw.AddGeneratedDeepCopyFuncs(deepCopyFuncs...)
for _, f := range deepCopyFuncs {
if err := s.cloner.RegisterGeneratedDeepCopyFunc(f); err != nil {
return err
}
}
return nil
}
// AddFieldLabelConversionFunc adds a conversion function to convert field selectors
@ -198,19 +376,43 @@ func (s *Scheme) AddFieldLabelConversionFunc(version, kind string, conversionFun
// the comment in conversion.Converter.SetStructFieldCopy for parameter details.
// Call as many times as needed, even on the same fields.
func (s *Scheme) AddStructFieldConversion(srcFieldType interface{}, srcFieldName string, destFieldType interface{}, destFieldName string) error {
return s.raw.AddStructFieldConversion(srcFieldType, srcFieldName, destFieldType, destFieldName)
return s.converter.SetStructFieldCopy(srcFieldType, srcFieldName, destFieldType, destFieldName)
}
// AddDefaultingFuncs adds a function to the list of value-defaulting functions.
// We deduce how to call it from the types of its two parameters; see the
// comment for Converter.RegisterDefaultingFunction.
// 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
// mapping will happen by default. Use this method to register a mechanism for handling
// a specific input type in conversion, such as a map[string]string to structs.
func (s *Scheme) RegisterInputDefaults(in interface{}, fn conversion.FieldMappingFunc, defaultFlags conversion.FieldMatchingFlags) error {
return s.converter.RegisterInputDefaults(in, fn, defaultFlags)
}
// AddDefaultingFuncs adds functions to the list of default-value functions.
// Each of the given functions is responsible for applying default values
// when converting an instance of a versioned API object into an internal
// API object. These functions do not need to handle sub-objects. We deduce
// how to call these functions from the types of their two parameters.
//
// s.AddDefaultingFuncs(
// func(obj *v1.Pod) {
// if obj.OptionalField == "" {
// obj.OptionalField = "DefaultValue"
// }
// },
// )
func (s *Scheme) AddDefaultingFuncs(defaultingFuncs ...interface{}) error {
return s.raw.AddDefaultingFuncs(defaultingFuncs...)
for _, f := range defaultingFuncs {
err := s.converter.RegisterDefaultingFunc(f)
if err != nil {
return err
}
}
return nil
}
// Copy does a deep copy of an API object.
func (s *Scheme) Copy(src Object) (Object, error) {
dst, err := s.raw.DeepCopy(src)
dst, err := s.DeepCopy(src)
if err != nil {
return nil, err
}
@ -219,25 +421,33 @@ func (s *Scheme) Copy(src Object) (Object, error) {
// Performs a deep copy of the given object.
func (s *Scheme) DeepCopy(src interface{}) (interface{}, error) {
return s.raw.DeepCopy(src)
return s.cloner.DeepCopy(src)
}
// WithConversions returns an ObjectConvertor that has the additional conversion functions
// defined in fns. The current scheme is not altered.
func (s *Scheme) WithConversions(fns *conversion.ConversionFuncs) ObjectConvertor {
if fns == nil {
return s
}
copied := *s
copied.raw = s.raw.WithConversions(*fns)
return &copied
}
// Convert will attempt to convert in into out. Both must be pointers.
// For easy testing of conversion functions. Returns an error if the conversion isn't
// possible.
// Convert will attempt to convert in into out. Both must be pointers. For easy
// testing of conversion functions. Returns an error if the conversion isn't
// possible. You can call this with types that haven't been registered (for example,
// a to test conversion of types that are nested within registered types), but in
// that case, the conversion.Scope object passed to your conversion functions won't
// have SrcVersion or DestVersion fields set correctly in Meta().
func (s *Scheme) Convert(in, out interface{}) error {
return s.raw.Convert(in, out)
inVersion := unversioned.GroupVersion{Group: "unknown", Version: "unknown"}
outVersion := unversioned.GroupVersion{Group: "unknown", Version: "unknown"}
if inObj, ok := in.(Object); ok {
if gvk, err := s.ObjectKind(inObj); err == nil {
inVersion = gvk.GroupVersion()
}
}
if outObj, ok := out.(Object); ok {
if gvk, err := s.ObjectKind(outObj); err == nil {
outVersion = gvk.GroupVersion()
}
}
flags, meta := s.generateConvertMeta(inVersion, outVersion, in)
if flags == 0 {
flags = conversion.AllowDifferentFieldTypeNames
}
return s.converter.Convert(in, out, flags, meta)
}
// Converts the given field label and value for an kind field selector from
@ -267,22 +477,60 @@ func (s *Scheme) ConvertToVersion(in Object, outVersion string) (Object, error)
case *Unknown, *Unstructured:
old := in.GetObjectKind().GroupVersionKind()
defer in.GetObjectKind().SetGroupVersionKind(old)
setTargetVersion(in, s.raw, gv)
setTargetVersion(in, s, gv)
return in, nil
}
unknown, err := s.raw.ConvertToVersion(in, outVersion)
t := reflect.TypeOf(in)
if t.Kind() != reflect.Ptr {
return nil, fmt.Errorf("only pointer types may be converted: %v", t)
}
t = t.Elem()
if t.Kind() != reflect.Struct {
return nil, fmt.Errorf("only pointers to struct types may be converted: %v", t)
}
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, outVersion)
}
kind = kinds[0]
}
outKind := gv.WithKind(kind.Kind)
inKind, err := s.ObjectKind(in)
if err != nil {
return nil, err
}
obj, ok := unknown.(Object)
if !ok {
return nil, fmt.Errorf("the provided object cannot be converted to a runtime.Object: %#v", unknown)
out, err := s.New(outKind)
if err != nil {
return nil, err
}
setTargetVersion(obj, s.raw, gv)
return obj, nil
flags, meta := s.generateConvertMeta(inKind.GroupVersion(), gv, in)
if err := s.converter.Convert(in, out, flags, meta); err != nil {
return nil, err
}
setTargetVersion(out, s, gv)
return out, nil
}
func setTargetVersion(obj Object, raw *conversion.Scheme, gv unversioned.GroupVersion) {
// generateConvertMeta constructs the meta value we pass to Convert.
func (s *Scheme) generateConvertMeta(srcGroupVersion, destGroupVersion unversioned.GroupVersion, in interface{}) (conversion.FieldMatchingFlags, *conversion.Meta) {
flags, meta := s.converter.DefaultMeta(reflect.TypeOf(in))
meta.SrcVersion = srcGroupVersion.String()
meta.DestVersion = destGroupVersion.String()
return flags, meta
}
func setTargetVersion(obj Object, raw *Scheme, gv unversioned.GroupVersion) {
if gv.Version == APIVersionInternal {
// internal is a special case
obj.GetObjectKind().SetGroupVersionKind(nil)

View File

@ -20,6 +20,9 @@ import (
"reflect"
"testing"
"github.com/google/gofuzz"
flag "github.com/spf13/pflag"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/conversion"
"k8s.io/kubernetes/pkg/runtime"
@ -27,29 +30,16 @@ import (
"k8s.io/kubernetes/pkg/util"
)
type TypeMeta struct {
Kind string `json:"kind,omitempty"`
APIVersion string `json:"apiVersion,omitempty"`
}
// SetGroupVersionKind satisfies the ObjectKind interface for all objects that embed TypeMeta
func (obj *TypeMeta) SetGroupVersionKind(gvk *unversioned.GroupVersionKind) {
obj.APIVersion, obj.Kind = gvk.ToAPIVersionAndKind()
}
// GroupVersionKind satisfies the ObjectKind interface for all objects that embed TypeMeta
func (obj *TypeMeta) GroupVersionKind() *unversioned.GroupVersionKind {
return unversioned.FromAPIVersionAndKind(obj.APIVersion, obj.Kind)
}
var fuzzIters = flag.Int("fuzz-iters", 50, "How many fuzzing iterations to do.")
type InternalSimple struct {
TypeMeta `json:",inline"`
TestString string `json:"testString"`
runtime.TypeMeta `json:",inline"`
TestString string `json:"testString"`
}
type ExternalSimple struct {
TypeMeta `json:",inline"`
TestString string `json:"testString"`
runtime.TypeMeta `json:",inline"`
TestString string `json:"testString"`
}
func (obj *InternalSimple) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta }
@ -135,7 +125,7 @@ func TestScheme(t *testing.T) {
}
// clearing TypeMeta is a function of the scheme, which we do not test here (ConvertToVersion
// does not automatically clear TypeMeta anymore).
simple.TypeMeta = TypeMeta{Kind: "Simple", APIVersion: externalGV.String()}
simple.TypeMeta = runtime.TypeMeta{Kind: "Simple", APIVersion: externalGV.String()}
if e, a := simple, obj3; !reflect.DeepEqual(e, a) {
t.Errorf("Expected:\n %#v,\n Got:\n %#v", e, a)
}
@ -188,33 +178,33 @@ func TestBadJSONRejection(t *testing.T) {
}
type ExtensionA struct {
TypeMeta `json:",inline"`
TestString string `json:"testString"`
runtime.TypeMeta `json:",inline"`
TestString string `json:"testString"`
}
type ExtensionB struct {
TypeMeta `json:",inline"`
TestString string `json:"testString"`
runtime.TypeMeta `json:",inline"`
TestString string `json:"testString"`
}
type ExternalExtensionType struct {
TypeMeta `json:",inline"`
Extension runtime.RawExtension `json:"extension"`
runtime.TypeMeta `json:",inline"`
Extension runtime.RawExtension `json:"extension"`
}
type InternalExtensionType struct {
TypeMeta `json:",inline"`
Extension runtime.Object `json:"extension"`
runtime.TypeMeta `json:",inline"`
Extension runtime.Object `json:"extension"`
}
type ExternalOptionalExtensionType struct {
TypeMeta `json:",inline"`
Extension runtime.RawExtension `json:"extension,omitempty"`
runtime.TypeMeta `json:",inline"`
Extension runtime.RawExtension `json:"extension,omitempty"`
}
type InternalOptionalExtensionType struct {
TypeMeta `json:",inline"`
Extension runtime.Object `json:"extension,omitempty"`
runtime.TypeMeta `json:",inline"`
Extension runtime.Object `json:"extension,omitempty"`
}
func (obj *ExtensionA) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta }
@ -284,28 +274,28 @@ func TestExtensionMapping(t *testing.T) {
},
&InternalExtensionType{
Extension: &runtime.Unknown{
RawJSON: []byte(`{"kind":"A","apiVersion":"test.group/testExternal","testString":"foo"}`),
RawJSON: []byte(`{"apiVersion":"test.group/testExternal","kind":"A","testString":"foo"}`),
},
},
// apiVersion is set in the serialized object for easier consumption by clients
`{"kind":"ExtensionType","apiVersion":"` + externalGV.String() + `","extension":{"kind":"A","apiVersion":"test.group/testExternal","testString":"foo"}}
`{"apiVersion":"` + externalGV.String() + `","kind":"ExtensionType","extension":{"apiVersion":"test.group/testExternal","kind":"A","testString":"foo"}}
`,
}, {
&InternalExtensionType{Extension: runtime.NewEncodable(codec, &ExtensionB{TestString: "bar"})},
&InternalExtensionType{
Extension: &runtime.Unknown{
RawJSON: []byte(`{"kind":"B","apiVersion":"test.group/testExternal","testString":"bar"}`),
RawJSON: []byte(`{"apiVersion":"test.group/testExternal","kind":"B","testString":"bar"}`),
},
},
// apiVersion is set in the serialized object for easier consumption by clients
`{"kind":"ExtensionType","apiVersion":"` + externalGV.String() + `","extension":{"kind":"B","apiVersion":"test.group/testExternal","testString":"bar"}}
`{"apiVersion":"` + externalGV.String() + `","kind":"ExtensionType","extension":{"apiVersion":"test.group/testExternal","kind":"B","testString":"bar"}}
`,
}, {
&InternalExtensionType{Extension: nil},
&InternalExtensionType{
Extension: nil,
},
`{"kind":"ExtensionType","apiVersion":"` + externalGV.String() + `","extension":null}
`{"apiVersion":"` + externalGV.String() + `","kind":"ExtensionType","extension":null}
`,
},
}
@ -411,7 +401,273 @@ func TestUnversionedTypes(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if string(data) != `{"kind":"InternalSimple","apiVersion":"test.group/testExternal","testString":"I'm the same"}`+"\n" {
if string(data) != `{"apiVersion":"test.group/testExternal","kind":"InternalSimple","testString":"I'm the same"}`+"\n" {
t.Errorf("unexpected data: %s", data)
}
}
// Test a weird version/kind embedding format.
type MyWeirdCustomEmbeddedVersionKindField struct {
ID string `json:"ID,omitempty"`
APIVersion string `json:"myVersionKey,omitempty"`
ObjectKind string `json:"myKindKey,omitempty"`
Z string `json:"Z,omitempty"`
Y uint64 `json:"Y,omitempty"`
}
type TestType1 struct {
MyWeirdCustomEmbeddedVersionKindField `json:",inline"`
A string `json:"A,omitempty"`
B int `json:"B,omitempty"`
C int8 `json:"C,omitempty"`
D int16 `json:"D,omitempty"`
E int32 `json:"E,omitempty"`
F int64 `json:"F,omitempty"`
G uint `json:"G,omitempty"`
H uint8 `json:"H,omitempty"`
I uint16 `json:"I,omitempty"`
J uint32 `json:"J,omitempty"`
K uint64 `json:"K,omitempty"`
L bool `json:"L,omitempty"`
M map[string]int `json:"M,omitempty"`
N map[string]TestType2 `json:"N,omitempty"`
O *TestType2 `json:"O,omitempty"`
P []TestType2 `json:"Q,omitempty"`
}
type TestType2 struct {
A string `json:"A,omitempty"`
B int `json:"B,omitempty"`
}
type ExternalTestType2 struct {
A string `json:"A,omitempty"`
B int `json:"B,omitempty"`
}
type ExternalTestType1 struct {
MyWeirdCustomEmbeddedVersionKindField `json:",inline"`
A string `json:"A,omitempty"`
B int `json:"B,omitempty"`
C int8 `json:"C,omitempty"`
D int16 `json:"D,omitempty"`
E int32 `json:"E,omitempty"`
F int64 `json:"F,omitempty"`
G uint `json:"G,omitempty"`
H uint8 `json:"H,omitempty"`
I uint16 `json:"I,omitempty"`
J uint32 `json:"J,omitempty"`
K uint64 `json:"K,omitempty"`
L bool `json:"L,omitempty"`
M map[string]int `json:"M,omitempty"`
N map[string]ExternalTestType2 `json:"N,omitempty"`
O *ExternalTestType2 `json:"O,omitempty"`
P []ExternalTestType2 `json:"Q,omitempty"`
}
type ExternalInternalSame struct {
MyWeirdCustomEmbeddedVersionKindField `json:",inline"`
A TestType2 `json:"A,omitempty"`
}
func (obj *MyWeirdCustomEmbeddedVersionKindField) GetObjectKind() unversioned.ObjectKind { return obj }
func (obj *MyWeirdCustomEmbeddedVersionKindField) SetGroupVersionKind(gvk *unversioned.GroupVersionKind) {
obj.APIVersion, obj.ObjectKind = gvk.ToAPIVersionAndKind()
}
func (obj *MyWeirdCustomEmbeddedVersionKindField) GroupVersionKind() *unversioned.GroupVersionKind {
return unversioned.FromAPIVersionAndKind(obj.APIVersion, obj.ObjectKind)
}
func (obj *ExternalInternalSame) GetObjectKind() unversioned.ObjectKind {
return &obj.MyWeirdCustomEmbeddedVersionKindField
}
func (obj *TestType1) GetObjectKind() unversioned.ObjectKind {
return &obj.MyWeirdCustomEmbeddedVersionKindField
}
func (obj *ExternalTestType1) GetObjectKind() unversioned.ObjectKind {
return &obj.MyWeirdCustomEmbeddedVersionKindField
}
func (obj *TestType2) GetObjectKind() unversioned.ObjectKind { return unversioned.EmptyObjectKind }
func (obj *ExternalTestType2) GetObjectKind() unversioned.ObjectKind {
return unversioned.EmptyObjectKind
}
// TestObjectFuzzer can randomly populate all the above objects.
var TestObjectFuzzer = fuzz.New().NilChance(.5).NumElements(1, 100).Funcs(
func(j *MyWeirdCustomEmbeddedVersionKindField, c fuzz.Continue) {
// We have to customize the randomization of MyWeirdCustomEmbeddedVersionKindFields because their
// APIVersion and Kind must remain blank in memory.
j.APIVersion = ""
j.ObjectKind = ""
j.ID = c.RandString()
},
)
// Returns a new Scheme set up with the test objects.
func GetTestScheme() *runtime.Scheme {
internalGV := unversioned.GroupVersion{Version: "__internal"}
externalGV := unversioned.GroupVersion{Version: "v1"}
s := runtime.NewScheme()
// Ordinarily, we wouldn't add TestType2, but because this is a test and
// both types are from the same package, we need to get it into the system
// so that converter will match it with ExternalType2.
s.AddKnownTypes(internalGV, &TestType1{}, &TestType2{}, &ExternalInternalSame{})
s.AddKnownTypes(externalGV, &ExternalInternalSame{})
s.AddKnownTypeWithName(externalGV.WithKind("TestType1"), &ExternalTestType1{})
s.AddKnownTypeWithName(externalGV.WithKind("TestType2"), &ExternalTestType2{})
s.AddKnownTypeWithName(internalGV.WithKind("TestType3"), &TestType1{})
s.AddKnownTypeWithName(externalGV.WithKind("TestType3"), &ExternalTestType1{})
return s
}
func TestKnownTypes(t *testing.T) {
s := GetTestScheme()
if len(s.KnownTypes(unversioned.GroupVersion{Group: "group", Version: "v2"})) != 0 {
t.Errorf("should have no known types for v2")
}
types := s.KnownTypes(unversioned.GroupVersion{Version: "v1"})
for _, s := range []string{"TestType1", "TestType2", "TestType3", "ExternalInternalSame"} {
if _, ok := types[s]; !ok {
t.Errorf("missing type %q", s)
}
}
}
func TestConvertToVersion(t *testing.T) {
s := GetTestScheme()
tt := &TestType1{A: "I'm not a pointer object"}
other, err := s.ConvertToVersion(tt, "v1")
if err != nil {
t.Fatalf("Failure: %v", err)
}
converted, ok := other.(*ExternalTestType1)
if !ok {
t.Fatalf("Got wrong type")
}
if tt.A != converted.A {
t.Fatalf("Failed to convert object correctly: %#v", converted)
}
}
func TestMetaValues(t *testing.T) {
internalGV := unversioned.GroupVersion{Group: "test.group", Version: "__internal"}
externalGV := unversioned.GroupVersion{Group: "test.group", Version: "externalVersion"}
s := runtime.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 conversion.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 conversion.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 := runtime.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 conversion.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

@ -23,7 +23,6 @@ import (
"testing"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/conversion"
"k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/runtime/serializer/json"
"k8s.io/kubernetes/pkg/util"
@ -141,7 +140,7 @@ func TestDecode(t *testing.T) {
{
data: []byte(`{"kind":"Test","apiVersion":"other/blah","value":1,"Other":"test"}`),
into: &testDecodable{},
typer: &mockTyper{err: conversion.NewNotRegisteredErr(unversioned.GroupVersionKind{}, nil)},
typer: &mockTyper{err: runtime.NewNotRegisteredErr(unversioned.GroupVersionKind{}, nil)},
expectedGVK: &unversioned.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"},
expectedObject: &testDecodable{
Other: "test",

View File

@ -21,7 +21,6 @@ import (
"io"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/conversion"
)
// UnstructuredJSONScheme is capable of converting JSON data into the Unstructured
@ -52,7 +51,7 @@ func (s unstructuredJSONScheme) Decode(data []byte, _ *unversioned.GroupVersionK
}
if len(unstruct.APIVersion) == 0 {
return nil, nil, conversion.NewMissingVersionErr(string(data))
return nil, nil, NewMissingVersionErr(string(data))
}
gv, err := unversioned.ParseGroupVersion(unstruct.APIVersion)
if err != nil {
@ -60,7 +59,7 @@ func (s unstructuredJSONScheme) Decode(data []byte, _ *unversioned.GroupVersionK
}
gvk := gv.WithKind(unstruct.Kind)
if len(unstruct.Kind) == 0 {
return nil, &gvk, conversion.NewMissingKindErr(string(data))
return nil, &gvk, NewMissingKindErr(string(data))
}
unstruct.Object = m
return unstruct, &gvk, nil

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package conversion_test
package runtime_test
import (
"encoding/json"