From 60b66665e137fc468a9fae83b86686ff93fa3f89 Mon Sep 17 00:00:00 2001 From: Clayton Coleman Date: Mon, 25 Jan 2016 18:47:26 -0500 Subject: [PATCH] Move conversion.Scheme to runtime There is only one type registry to rule them all --- cmd/genconversion/conversion.go | 2 +- cmd/gendeepcopy/deep_copy.go | 2 +- pkg/api/conversion_test.go | 6 +- pkg/conversion/converter.go | 15 +- pkg/conversion/converter_test.go | 233 ++++----- pkg/conversion/error.go | 98 ---- pkg/conversion/scheme.go | 464 ------------------ pkg/conversion/scheme_test.go | 181 ------- pkg/kubectl/resource_printer.go | 3 +- pkg/runtime/conversion_generator.go | 7 +- pkg/runtime/deep_copy_generator.go | 5 +- pkg/runtime/error.go | 79 ++- pkg/runtime/interfaces.go | 6 +- pkg/runtime/scheme.go | 450 +++++++++++++---- pkg/runtime/scheme_test.go | 330 +++++++++++-- pkg/runtime/serializer/json/json_test.go | 3 +- pkg/runtime/unstructured.go | 5 +- .../unversioned_test.go | 2 +- 18 files changed, 834 insertions(+), 1057 deletions(-) delete mode 100644 pkg/conversion/error.go delete mode 100644 pkg/conversion/scheme.go delete mode 100644 pkg/conversion/scheme_test.go rename pkg/{conversion => runtime}/unversioned_test.go (99%) diff --git a/cmd/genconversion/conversion.go b/cmd/genconversion/conversion.go index 91e0b217555..76321f57f3a 100644 --- a/cmd/genconversion/conversion.go +++ b/cmd/genconversion/conversion.go @@ -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. diff --git a/cmd/gendeepcopy/deep_copy.go b/cmd/gendeepcopy/deep_copy.go index 70672df3a36..42c887f82bc 100644 --- a/cmd/gendeepcopy/deep_copy.go +++ b/cmd/gendeepcopy/deep_copy.go @@ -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 { diff --git a/pkg/api/conversion_test.go b/pkg/api/conversion_test.go index 1af454ef755..827022270ed 100644 --- a/pkg/api/conversion_test.go +++ b/pkg/api/conversion_test.go @@ -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()) diff --git a/pkg/conversion/converter.go b/pkg/conversion/converter.go index 00c9bcb492c..19cd4bef9d2 100644 --- a/pkg/conversion/converter.go +++ b/pkg/conversion/converter.go @@ -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)) diff --git a/pkg/conversion/converter_test.go b/pkg/conversion/converter_test.go index 8787ec37454..c639492a752 100644 --- a/pkg/conversion/converter_test.go +++ b/pkg/conversion/converter_test.go @@ -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), + //) } diff --git a/pkg/conversion/error.go b/pkg/conversion/error.go deleted file mode 100644 index 94075e8adfb..00000000000 --- a/pkg/conversion/error.go +++ /dev/null @@ -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 ¬RegisteredErr{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 -} diff --git a/pkg/conversion/scheme.go b/pkg/conversion/scheme.go deleted file mode 100644 index 236ac39f971..00000000000 --- a/pkg/conversion/scheme.go +++ /dev/null @@ -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, ¬RegisteredErr{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, ¬RegisteredErr{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() -} diff --git a/pkg/conversion/scheme_test.go b/pkg/conversion/scheme_test.go deleted file mode 100644 index 766aa8b479b..00000000000 --- a/pkg/conversion/scheme_test.go +++ /dev/null @@ -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") - } -} diff --git a/pkg/kubectl/resource_printer.go b/pkg/kubectl/resource_printer.go index 974cff0dfc4..b89a5ba7416 100644 --- a/pkg/kubectl/resource_printer.go +++ b/pkg/kubectl/resource_printer.go @@ -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 { diff --git a/pkg/runtime/conversion_generator.go b/pkg/runtime/conversion_generator.go index 6d70224e9be..8b4aea0c93d 100644 --- a/pkg/runtime/conversion_generator.go +++ b/pkg/runtime/conversion_generator.go @@ -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) } diff --git a/pkg/runtime/deep_copy_generator.go b/pkg/runtime/deep_copy_generator.go index 38fb54f5c47..4790969f129 100644 --- a/pkg/runtime/deep_copy_generator.go +++ b/pkg/runtime/deep_copy_generator.go @@ -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 diff --git a/pkg/runtime/error.go b/pkg/runtime/error.go index cbdc9b66105..e999e66e96c 100644 --- a/pkg/runtime/error.go +++ b/pkg/runtime/error.go @@ -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 ¬RegisteredErr{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 } diff --git a/pkg/runtime/interfaces.go b/pkg/runtime/interfaces.go index 6a51ee05ec8..67b37b4401b 100644 --- a/pkg/runtime/interfaces.go +++ b/pkg/runtime/interfaces.go @@ -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. diff --git a/pkg/runtime/scheme.go b/pkg/runtime/scheme.go index 474a6eb307f..37bd985aad4 100644 --- a/pkg/runtime/scheme.go +++ b/pkg/runtime/scheme.go @@ -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, ¬RegisteredErr{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, ¬RegisteredErr{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) diff --git a/pkg/runtime/scheme_test.go b/pkg/runtime/scheme_test.go index d0f9f2d08bf..5b933d84b62 100644 --- a/pkg/runtime/scheme_test.go +++ b/pkg/runtime/scheme_test.go @@ -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) + } +} diff --git a/pkg/runtime/serializer/json/json_test.go b/pkg/runtime/serializer/json/json_test.go index f9a744f8718..b9e1319fabc 100644 --- a/pkg/runtime/serializer/json/json_test.go +++ b/pkg/runtime/serializer/json/json_test.go @@ -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", diff --git a/pkg/runtime/unstructured.go b/pkg/runtime/unstructured.go index a236e9adb6b..4fe5091c379 100644 --- a/pkg/runtime/unstructured.go +++ b/pkg/runtime/unstructured.go @@ -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 diff --git a/pkg/conversion/unversioned_test.go b/pkg/runtime/unversioned_test.go similarity index 99% rename from pkg/conversion/unversioned_test.go rename to pkg/runtime/unversioned_test.go index 5a798be95eb..7943581ea51 100644 --- a/pkg/conversion/unversioned_test.go +++ b/pkg/runtime/unversioned_test.go @@ -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"