From 84d84c50c234dc96497d48766e7daae587f0f889 Mon Sep 17 00:00:00 2001 From: Clayton Coleman Date: Wed, 3 Dec 2014 08:40:30 -0500 Subject: [PATCH 1/6] Add a strongly typed error for unrecognized kind/type/version --- pkg/conversion/decode.go | 2 +- pkg/conversion/error.go | 51 ++++++++++++++++++++++++++++++++++++++++ pkg/conversion/scheme.go | 10 ++++---- pkg/runtime/error.go | 27 +++++++++++++++++++++ 4 files changed, 84 insertions(+), 6 deletions(-) create mode 100644 pkg/conversion/error.go create mode 100644 pkg/runtime/error.go diff --git a/pkg/conversion/decode.go b/pkg/conversion/decode.go index 81be6bb5bef..40987becdb5 100644 --- a/pkg/conversion/decode.go +++ b/pkg/conversion/decode.go @@ -115,7 +115,7 @@ func (s *Scheme) DecodeInto(data []byte, obj interface{}) error { } else { external, err := s.NewObject(dataVersion, dataKind) if err != nil { - return fmt.Errorf("unable to create new object of type ('%s', '%s')", dataVersion, dataKind) + return err } // yaml is a superset of json, so we use it to decode here. That way, // we understand both. diff --git a/pkg/conversion/error.go b/pkg/conversion/error.go new file mode 100644 index 00000000000..7ee24ee4380 --- /dev/null +++ b/pkg/conversion/error.go @@ -0,0 +1,51 @@ +/* +Copyright 2014 Google Inc. 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" +) + +type notRegisteredErr struct { + kind string + version string + t reflect.Type +} + +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.kind) == 0 { + return fmt.Sprintf("no version %q has been registered", k.version) + } + if len(k.version) == 0 { + return fmt.Sprintf("no kind %q is registered for the default version", k.kind) + } + return fmt.Sprintf("no kind %q is registered for version %q", k.kind, k.version) +} + +// 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 +} diff --git a/pkg/conversion/scheme.go b/pkg/conversion/scheme.go index a42c83ed32e..5bffb03b377 100644 --- a/pkg/conversion/scheme.go +++ b/pkg/conversion/scheme.go @@ -143,14 +143,14 @@ func (s *Scheme) KnownTypes(version string) map[string]reflect.Type { // NewObject returns a new object of the given version and name, // or an error if it hasn't been registered. -func (s *Scheme) NewObject(versionName, typeName string) (interface{}, error) { +func (s *Scheme) NewObject(versionName, kind string) (interface{}, error) { if types, ok := s.versionMap[versionName]; ok { - if t, ok := types[typeName]; ok { + if t, ok := types[kind]; ok { return reflect.New(t).Interface(), nil } - return nil, fmt.Errorf("no type '%v' for version '%v'", typeName, versionName) + return nil, ¬RegisteredErr{kind: kind, version: versionName} } - return nil, fmt.Errorf("no version '%v'", versionName) + return nil, ¬RegisteredErr{kind: kind, version: versionName} } // AddConversionFuncs adds functions to the list of conversion functions. The given @@ -284,7 +284,7 @@ func (s *Scheme) ObjectVersionAndKind(obj interface{}) (apiVersion, kind string, version, vOK := s.typeToVersion[t] kinds, kOK := s.typeToKind[t] if !vOK || !kOK { - return "", "", fmt.Errorf("unregistered type: %v", t) + return "", "", ¬RegisteredErr{t: t} } apiVersion = version kind = kinds[0] diff --git a/pkg/runtime/error.go b/pkg/runtime/error.go new file mode 100644 index 00000000000..a84fe6ae9f3 --- /dev/null +++ b/pkg/runtime/error.go @@ -0,0 +1,27 @@ +/* +Copyright 2014 Google Inc. 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 runtime + +import ( + "github.com/GoogleCloudPlatform/kubernetes/pkg/conversion" +) + +// 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) +} From dd24b013cb139448701ae87b5a2595d92c21b865 Mon Sep 17 00:00:00 2001 From: Clayton Coleman Date: Wed, 3 Dec 2014 08:41:12 -0500 Subject: [PATCH 2/6] Use if err := ; err != nil instead of two lines --- pkg/conversion/scheme.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pkg/conversion/scheme.go b/pkg/conversion/scheme.go index 5bffb03b377..d86e11edb48 100644 --- a/pkg/conversion/scheme.go +++ b/pkg/conversion/scheme.go @@ -185,8 +185,7 @@ func (s *Scheme) NewObject(versionName, kind string) (interface{}, error) { // add conversion functions for things with changed/removed fields. func (s *Scheme) AddConversionFuncs(conversionFuncs ...interface{}) error { for _, f := range conversionFuncs { - err := s.converter.Register(f) - if err != nil { + if err := s.converter.Register(f); err != nil { return err } } From 714dbf452278b85b472f069aa5cee86508b284e4 Mon Sep 17 00:00:00 2001 From: Clayton Coleman Date: Wed, 3 Dec 2014 08:43:09 -0500 Subject: [PATCH 3/6] Shrink runtime.TypeMeta to be equivalent to api TypeMeta Remove unused fuzzing --- pkg/api/serialization_test.go | 9 --------- pkg/api/validation/schema_test.go | 9 --------- pkg/runtime/embedded_test.go | 6 +++--- pkg/runtime/types.go | 15 +++------------ 4 files changed, 6 insertions(+), 33 deletions(-) diff --git a/pkg/api/serialization_test.go b/pkg/api/serialization_test.go index ddd32ac0097..932852ace02 100644 --- a/pkg/api/serialization_test.go +++ b/pkg/api/serialization_test.go @@ -48,15 +48,6 @@ var apiObjectFuzzer = fuzz.New().NilChance(.5).NumElements(1, 1).Funcs( // APIVersion and Kind must remain blank in memory. j.APIVersion = "" j.Kind = "" - - j.Name = c.RandString() - j.ResourceVersion = strconv.FormatUint(c.RandUint64(), 10) - j.SelfLink = c.RandString() - - var sec, nsec int64 - c.Fuzz(&sec) - c.Fuzz(&nsec) - j.CreationTimestamp = util.Unix(sec, nsec).Rfc3339Copy() }, func(j *api.TypeMeta, c fuzz.Continue) { // We have to customize the randomization of TypeMetas because their diff --git a/pkg/api/validation/schema_test.go b/pkg/api/validation/schema_test.go index 34a0cf3559c..7ed8670e19f 100644 --- a/pkg/api/validation/schema_test.go +++ b/pkg/api/validation/schema_test.go @@ -49,15 +49,6 @@ var apiObjectFuzzer = fuzz.New().NilChance(.5).NumElements(1, 1).Funcs( // APIVersion and Kind must remain blank in memory. j.APIVersion = "" j.Kind = "" - - j.Name = c.RandString() - j.ResourceVersion = strconv.FormatUint(c.RandUint64(), 10) - j.SelfLink = c.RandString() - - var sec, nsec int64 - c.Fuzz(&sec) - c.Fuzz(&nsec) - j.CreationTimestamp = util.Unix(sec, nsec).Rfc3339Copy() }, func(j *api.TypeMeta, c fuzz.Continue) { // We have to customize the randomization of TypeMetas because their diff --git a/pkg/runtime/embedded_test.go b/pkg/runtime/embedded_test.go index 9fb795c95bb..c9ad19725f5 100644 --- a/pkg/runtime/embedded_test.go +++ b/pkg/runtime/embedded_test.go @@ -50,11 +50,11 @@ func TestEmbeddedObject(t *testing.T) { s.AddKnownTypeWithName("v1test", "EmbeddedTest", &EmbeddedTestExternal{}) outer := &EmbeddedTest{ - TypeMeta: runtime.TypeMeta{Name: "outer"}, + TypeMeta: runtime.TypeMeta{}, ID: "outer", Object: runtime.EmbeddedObject{ &EmbeddedTest{ - TypeMeta: runtime.TypeMeta{Name: "inner"}, + TypeMeta: runtime.TypeMeta{}, ID: "inner", }, }, @@ -83,7 +83,7 @@ func TestEmbeddedObject(t *testing.T) { if err != nil { t.Fatalf("Unexpected decode error %v", err) } - if externalViaJSON.Kind == "" || externalViaJSON.APIVersion == "" || externalViaJSON.Name != "outer" { + if externalViaJSON.Kind == "" || externalViaJSON.APIVersion == "" || externalViaJSON.ID != "outer" { t.Errorf("Expected objects to have type info set, got %#v", externalViaJSON) } if !reflect.DeepEqual(externalViaJSON.EmptyObject.RawJSON, []byte("null")) || len(externalViaJSON.Object.RawJSON) == 0 { diff --git a/pkg/runtime/types.go b/pkg/runtime/types.go index aa7eec39201..da7e0e93c5d 100644 --- a/pkg/runtime/types.go +++ b/pkg/runtime/types.go @@ -16,9 +16,7 @@ limitations under the License. package runtime -import ( - "github.com/GoogleCloudPlatform/kubernetes/pkg/util" -) +import () // Note that the types provided in this file are not versioned and are intended to be // safe to use from within all versions of every API object. @@ -35,15 +33,8 @@ import ( // your own with the same fields. // type TypeMeta struct { - APIVersion string `json:"apiVersion,omitempty"` - Kind string `json:"kind,omitempty"` - - Namespace string `json:"namespace,omitempty"` - Name string `json:"name,omitempty"` - UID string `json:"uid,omitempty"` - CreationTimestamp util.Time `json:"creationTimestamp,omitempty"` - SelfLink string `json:"selfLink,omitempty"` - ResourceVersion string `json:"resourceVersion,omitempty"` + APIVersion string `json:"apiVersion,omitempty" yaml:"apiVersion,omitempty"` + Kind string `json:"kind,omitempty" yaml:"kind,omitempty"` } // PluginBase is like TypeMeta, but it's intended for plugin objects that won't ever be encoded From 1eaa5c41f932d28aaace3459ad8537672e5131c8 Mon Sep 17 00:00:00 2001 From: Clayton Coleman Date: Sun, 7 Dec 2014 21:20:08 -0500 Subject: [PATCH 4/6] RawExtension was not properly marshalled MarshalJSON can't use a pointer to a struct for RawExtension: http://stackoverflow.com/questions/21390979/custom-marshaljson-never-gets-called-in-go --- pkg/runtime/extension.go | 4 +++- pkg/runtime/extension_test.go | 39 +++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 pkg/runtime/extension_test.go diff --git a/pkg/runtime/extension.go b/pkg/runtime/extension.go index 8f94f2b578f..982a4c4f69e 100644 --- a/pkg/runtime/extension.go +++ b/pkg/runtime/extension.go @@ -26,6 +26,8 @@ func (re *RawExtension) UnmarshalJSON(in []byte) error { return nil } -func (re *RawExtension) MarshalJSON() ([]byte, error) { +// Marshal may get called on pointers or values, so implement MarshalJSON on value. +// http://stackoverflow.com/questions/21390979/custom-marshaljson-never-gets-called-in-go +func (re RawExtension) MarshalJSON() ([]byte, error) { return re.RawJSON, nil } diff --git a/pkg/runtime/extension_test.go b/pkg/runtime/extension_test.go new file mode 100644 index 00000000000..07c0f9401ed --- /dev/null +++ b/pkg/runtime/extension_test.go @@ -0,0 +1,39 @@ +/* +Copyright 2014 Google Inc. 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 runtime_test + +import ( + "encoding/json" + "testing" + + "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" +) + +func TestEmbeddedRawExtensionMarshal(t *testing.T) { + type test struct { + Ext runtime.RawExtension + } + + extension := test{Ext: runtime.RawExtension{RawJSON: []byte(`{"foo":"bar"}`)}} + data, err := json.Marshal(extension) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if string(data) != `{"Ext":{"foo":"bar"}}` { + t.Errorf("unexpected data: %s", string(data)) + } +} From db2c59ff6161832a857cbe3489c6870ce26a25b1 Mon Sep 17 00:00:00 2001 From: Clayton Coleman Date: Wed, 3 Dec 2014 08:41:57 -0500 Subject: [PATCH 5/6] Allow conversion between []runtime.Object and []runtime.RawExtension This allows generic lists with unrecognized objects to be roundtripped between internal and external objects. --- pkg/runtime/embedded_test.go | 92 +++++++++++++++++++++++++++++++----- pkg/runtime/scheme.go | 66 +++++++++++++++++++++++++- 2 files changed, 144 insertions(+), 14 deletions(-) diff --git a/pkg/runtime/embedded_test.go b/pkg/runtime/embedded_test.go index c9ad19725f5..90aae83f1fe 100644 --- a/pkg/runtime/embedded_test.go +++ b/pkg/runtime/embedded_test.go @@ -22,16 +22,14 @@ import ( "testing" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" + "github.com/GoogleCloudPlatform/kubernetes/pkg/util" ) -var scheme = runtime.NewScheme() -var Codec = runtime.CodecFor(scheme, "v1test") - type EmbeddedTest struct { - runtime.TypeMeta `json:",inline"` - ID string `json:"id,omitempty"` - Object runtime.EmbeddedObject `json:"object,omitempty"` - EmptyObject runtime.EmbeddedObject `json:"emptyObject,omitempty"` + runtime.TypeMeta + ID string + Object runtime.EmbeddedObject + EmptyObject runtime.EmbeddedObject } type EmbeddedTestExternal struct { @@ -41,21 +39,91 @@ type EmbeddedTestExternal struct { EmptyObject runtime.RawExtension `json:"emptyObject,omitempty"` } +type ObjectTest struct { + runtime.TypeMeta + + ID string + Items []runtime.Object +} + +type ObjectTestExternal struct { + runtime.TypeMeta `yaml:",inline" json:",inline"` + + ID string `json:"id,omitempty"` + Items []runtime.RawExtension `json:"items,omitempty"` +} + +func (*ObjectTest) IsAnAPIObject() {} +func (*ObjectTestExternal) IsAnAPIObject() {} func (*EmbeddedTest) IsAnAPIObject() {} func (*EmbeddedTestExternal) IsAnAPIObject() {} +func TestDecodeEmptyRawExtensionAsObject(t *testing.T) { + s := runtime.NewScheme() + s.AddKnownTypes("", &ObjectTest{}) + s.AddKnownTypeWithName("v1test", "ObjectTest", &ObjectTestExternal{}) + + _, err := s.Decode([]byte(`{"kind":"ObjectTest","apiVersion":"v1test","items":[{}]}`)) + if err == nil { + t.Fatalf("unexpected non-error") + } +} + +func TestArrayOfRuntimeObject(t *testing.T) { + s := runtime.NewScheme() + s.AddKnownTypes("", &EmbeddedTest{}) + s.AddKnownTypeWithName("v1test", "EmbeddedTest", &EmbeddedTestExternal{}) + s.AddKnownTypes("", &ObjectTest{}) + s.AddKnownTypeWithName("v1test", "ObjectTest", &ObjectTestExternal{}) + + internal := &ObjectTest{ + Items: []runtime.Object{ + &EmbeddedTest{ID: "foo"}, + &EmbeddedTest{ID: "bar"}, + // TODO: until YAML is removed, this JSON must be in ascending key order to ensure consistent roundtrip serialization + &runtime.Unknown{RawJSON: []byte(`{"apiVersion":"unknown","foo":"bar","kind":"OtherTest"}`)}, + &ObjectTest{ + Items: []runtime.Object{ + &EmbeddedTest{ID: "baz"}, + }, + }, + }, + } + wire, err := s.EncodeToVersion(internal, "v1test") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + t.Logf("Wire format is:\n%s\n", string(wire)) + + obj := &ObjectTestExternal{} + if err := json.Unmarshal(wire, obj); err != nil { + t.Fatalf("unexpected error: %v", err) + } + t.Logf("exact wire is: %#v", string(obj.Items[0].RawJSON)) + + decoded, err := s.Decode(wire) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + internal.Items[2].(*runtime.Unknown).Kind = "OtherTest" + internal.Items[2].(*runtime.Unknown).APIVersion = "unknown" + if e, a := internal, decoded; !reflect.DeepEqual(e, a) { + t.Log(string(decoded.(*ObjectTest).Items[2].(*runtime.Unknown).RawJSON)) + t.Errorf("mismatched decoded: %s", util.ObjectDiff(e, a)) + } +} + func TestEmbeddedObject(t *testing.T) { - s := scheme + s := runtime.NewScheme() s.AddKnownTypes("", &EmbeddedTest{}) s.AddKnownTypeWithName("v1test", "EmbeddedTest", &EmbeddedTestExternal{}) outer := &EmbeddedTest{ - TypeMeta: runtime.TypeMeta{}, - ID: "outer", + ID: "outer", Object: runtime.EmbeddedObject{ &EmbeddedTest{ - TypeMeta: runtime.TypeMeta{}, - ID: "inner", + ID: "inner", }, }, } diff --git a/pkg/runtime/scheme.go b/pkg/runtime/scheme.go index 3984eabd2d5..5227607555a 100644 --- a/pkg/runtime/scheme.go +++ b/pkg/runtime/scheme.go @@ -140,15 +140,77 @@ func (self *Scheme) rawExtensionToEmbeddedObject(in *RawExtension, out *Embedded return nil } +// runtimeObjectToRawExtensionArray takes a list of objects and encodes them as RawExtension in the output version +// defined by the conversion.Scope. If objects must be encoded to different schema versions you should set them as +// runtime.Unknown in the internal version instead. +func (self *Scheme) runtimeObjectToRawExtensionArray(in *[]Object, out *[]RawExtension, s conversion.Scope) error { + src := *in + dest := make([]RawExtension, len(src)) + + _, outVersion, scheme := self.fromScope(s) + + for i := range src { + switch t := src[i].(type) { + case *Unknown: + dest[i].RawJSON = t.RawJSON + default: + data, err := scheme.EncodeToVersion(src[i], outVersion) + if err != nil { + return err + } + dest[i].RawJSON = data + } + } + *out = dest + return nil +} + +// rawExtensionToRuntimeObjectArray attempts to decode objects from the array - if they are unrecognized objects, +// they are added as Unknown. +func (self *Scheme) rawExtensionToRuntimeObjectArray(in *[]RawExtension, out *[]Object, s conversion.Scope) error { + src := *in + dest := make([]Object, len(src)) + + _, _, scheme := self.fromScope(s) + + for i := range src { + data := src[i].RawJSON + obj, err := scheme.Decode(data) + if err != nil { + if !IsNotRegisteredError(err) { + return err + } + version, kind, err := scheme.raw.DataVersionAndKind(data) + if err != nil { + return err + } + obj = &Unknown{ + TypeMeta: TypeMeta{ + APIVersion: version, + Kind: kind, + }, + RawJSON: data, + } + } + dest[i] = obj + } + *out = dest + return nil +} + // NewScheme creates a new Scheme. This scheme is pluggable by default. func NewScheme() *Scheme { s := &Scheme{conversion.NewScheme()} s.raw.InternalVersion = "" s.raw.MetaFactory = conversion.SimpleMetaFactory{BaseFields: []string{"TypeMeta"}, VersionField: "APIVersion", KindField: "Kind"} - s.raw.AddConversionFuncs( + if err := s.raw.AddConversionFuncs( s.embeddedObjectToRawExtension, s.rawExtensionToEmbeddedObject, - ) + s.runtimeObjectToRawExtensionArray, + s.rawExtensionToRuntimeObjectArray, + ); err != nil { + panic(err) + } return s } From 8a833ca70160048773102892b0ce236b800def9a Mon Sep 17 00:00:00 2001 From: Clayton Coleman Date: Sun, 7 Dec 2014 21:19:10 -0500 Subject: [PATCH 6/6] Add a List type that can contain arbitrary objects Supports objects that the core schema may not recognize and preserves them unmodified as runtime.Unknown --- pkg/api/latest/latest_test.go | 22 +++++++++++++++++++ pkg/api/register.go | 2 ++ pkg/api/serialization_test.go | 40 +++++++++++++++++++++++++++++++++-- pkg/api/types.go | 9 ++++++++ pkg/api/v1beta1/register.go | 2 ++ pkg/api/v1beta1/types.go | 7 ++++++ pkg/api/v1beta2/register.go | 2 ++ pkg/api/v1beta2/types.go | 7 ++++++ pkg/api/v1beta3/register.go | 2 ++ pkg/api/v1beta3/types.go | 18 ++++++++++++++-- pkg/runtime/scheme.go | 2 ++ 11 files changed, 109 insertions(+), 4 deletions(-) diff --git a/pkg/api/latest/latest_test.go b/pkg/api/latest/latest_test.go index f484fadcff6..e446fdb3b2b 100644 --- a/pkg/api/latest/latest_test.go +++ b/pkg/api/latest/latest_test.go @@ -25,7 +25,9 @@ import ( internal "github.com/GoogleCloudPlatform/kubernetes/pkg/api" _ "github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1" _ "github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta2" + "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" "github.com/GoogleCloudPlatform/kubernetes/pkg/util" + docker "github.com/fsouza/go-dockerclient" fuzz "github.com/google/gofuzz" ) @@ -83,6 +85,26 @@ var apiObjectFuzzer = fuzz.New().NilChance(.5).NumElements(1, 1).Funcs( // only replicas round trips j.Replicas = int(c.RandUint64()) }, + func(j *internal.List, c fuzz.Continue) { + c.Fuzz(&j.ListMeta) + c.Fuzz(&j.Items) + if j.Items == nil { + j.Items = []runtime.Object{} + } + }, + func(j *runtime.Object, c fuzz.Continue) { + if c.RandBool() { + *j = &runtime.Unknown{ + TypeMeta: runtime.TypeMeta{Kind: "Something", APIVersion: "unknown"}, + RawJSON: []byte(`{"apiVersion":"unknown","kind":"Something","someKey":"someValue"}`), + } + } else { + types := []runtime.Object{&internal.Pod{}, &internal.ReplicationController{}} + t := types[c.Rand.Intn(len(types))] + c.Fuzz(t) + *j = t + } + }, func(intstr *util.IntOrString, c fuzz.Continue) { // util.IntOrString will panic if its kind is set wrong. if c.RandBool() { diff --git a/pkg/api/register.go b/pkg/api/register.go index 14f7283c5ca..12fb68d1d85 100644 --- a/pkg/api/register.go +++ b/pkg/api/register.go @@ -45,6 +45,7 @@ func init() { &ContainerManifestList{}, &BoundPod{}, &BoundPods{}, + &List{}, ) } @@ -68,3 +69,4 @@ func (*ContainerManifest) IsAnAPIObject() {} func (*ContainerManifestList) IsAnAPIObject() {} func (*BoundPod) IsAnAPIObject() {} func (*BoundPods) IsAnAPIObject() {} +func (*List) IsAnAPIObject() {} diff --git a/pkg/api/serialization_test.go b/pkg/api/serialization_test.go index 932852ace02..e65bffe1270 100644 --- a/pkg/api/serialization_test.go +++ b/pkg/api/serialization_test.go @@ -90,6 +90,26 @@ var apiObjectFuzzer = fuzz.New().NilChance(.5).NumElements(1, 1).Funcs( // only replicas round trips j.Replicas = int(c.RandUint64()) }, + func(j *api.List, c fuzz.Continue) { + c.Fuzz(&j.ListMeta) + c.Fuzz(&j.Items) + if j.Items == nil { + j.Items = []runtime.Object{} + } + }, + func(j *runtime.Object, c fuzz.Continue) { + if c.RandBool() { + *j = &runtime.Unknown{ + TypeMeta: runtime.TypeMeta{Kind: "Something", APIVersion: "unknown"}, + RawJSON: []byte(`{"apiVersion":"unknown","kind":"Something","someKey":"someValue"}`), + } + } else { + types := []runtime.Object{&api.Pod{}, &api.ReplicationController{}} + t := types[c.Rand.Intn(len(types))] + c.Fuzz(t) + *j = t + } + }, func(intstr *util.IntOrString, c fuzz.Continue) { // util.IntOrString will panic if its kind is set wrong. if c.RandBool() { @@ -145,7 +165,7 @@ func runTest(t *testing.T, codec runtime.Codec, source runtime.Object) { obj2, err := codec.Decode(data) if err != nil { - t.Errorf("%v: %v", name, err) + t.Errorf("0: %v: %v\nCodec: %v\nData: %s\nSource: %#v", name, err, codec, string(data), source) return } if !reflect.DeepEqual(source, obj2) { @@ -179,7 +199,21 @@ func TestSpecificKind(t *testing.T) { api.Scheme.Log(nil) } +func TestList(t *testing.T) { + api.Scheme.Log(t) + kind := "List" + item, err := api.Scheme.New("", kind) + if err != nil { + t.Errorf("Couldn't make a %v? %v", kind, err) + return + } + runTest(t, v1beta1.Codec, item) + runTest(t, v1beta2.Codec, item) + api.Scheme.Log(nil) +} + var nonRoundTrippableTypes = util.NewStringSet("ContainerManifest") +var nonInternalRoundTrippableTypes = util.NewStringSet("List") func TestRoundTripTypes(t *testing.T) { for kind := range api.Scheme.KnownTypes("") { @@ -197,7 +231,9 @@ func TestRoundTripTypes(t *testing.T) { } runTest(t, v1beta1.Codec, item) runTest(t, v1beta2.Codec, item) - runTest(t, api.Codec, item) + if !nonInternalRoundTrippableTypes.Has(kind) { + runTest(t, api.Codec, item) + } } } } diff --git a/pkg/api/types.go b/pkg/api/types.go index 8b87f8b7da0..521483fb8e5 100644 --- a/pkg/api/types.go +++ b/pkg/api/types.go @@ -19,6 +19,7 @@ package api import ( "time" + "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" "github.com/GoogleCloudPlatform/kubernetes/pkg/util" ) @@ -999,3 +1000,11 @@ type BoundPods struct { // Items is the list of all pods bound to a given host. Items []BoundPod `json:"items"` } + +// List holds a list of objects, which may not be known by the server. +type List struct { + TypeMeta `json:",inline"` + ListMeta `json:"metadata,omitempty"` + + Items []runtime.Object `json:"items"` +} diff --git a/pkg/api/v1beta1/register.go b/pkg/api/v1beta1/register.go index b278d7769ec..202ebee51dc 100644 --- a/pkg/api/v1beta1/register.go +++ b/pkg/api/v1beta1/register.go @@ -46,6 +46,7 @@ func init() { &ContainerManifestList{}, &BoundPod{}, &BoundPods{}, + &List{}, ) } @@ -69,3 +70,4 @@ func (*ContainerManifest) IsAnAPIObject() {} func (*ContainerManifestList) IsAnAPIObject() {} func (*BoundPod) IsAnAPIObject() {} func (*BoundPods) IsAnAPIObject() {} +func (*List) IsAnAPIObject() {} diff --git a/pkg/api/v1beta1/types.go b/pkg/api/v1beta1/types.go index fad4907f123..9b0499a8e25 100644 --- a/pkg/api/v1beta1/types.go +++ b/pkg/api/v1beta1/types.go @@ -19,6 +19,7 @@ package v1beta1 import ( "time" + "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" "github.com/GoogleCloudPlatform/kubernetes/pkg/util" ) @@ -780,3 +781,9 @@ type BoundPods struct { // Items is the list of all pods bound to a given host. Items []BoundPod `json:"items" description:"list of all pods bound to a given host"` } + +// List holds a list of objects, which may not be known by the server. +type List struct { + TypeMeta `json:",inline"` + Items []runtime.RawExtension `json:"items" description:"list of objects"` +} diff --git a/pkg/api/v1beta2/register.go b/pkg/api/v1beta2/register.go index e0319b5ee19..fcbb3cdc07e 100644 --- a/pkg/api/v1beta2/register.go +++ b/pkg/api/v1beta2/register.go @@ -46,6 +46,7 @@ func init() { &ContainerManifestList{}, &BoundPod{}, &BoundPods{}, + &List{}, ) } @@ -69,3 +70,4 @@ func (*ContainerManifest) IsAnAPIObject() {} func (*ContainerManifestList) IsAnAPIObject() {} func (*BoundPod) IsAnAPIObject() {} func (*BoundPods) IsAnAPIObject() {} +func (*List) IsAnAPIObject() {} diff --git a/pkg/api/v1beta2/types.go b/pkg/api/v1beta2/types.go index b5b1f1adca4..79ce7d307ef 100644 --- a/pkg/api/v1beta2/types.go +++ b/pkg/api/v1beta2/types.go @@ -19,6 +19,7 @@ package v1beta2 import ( "time" + "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" "github.com/GoogleCloudPlatform/kubernetes/pkg/util" ) @@ -781,3 +782,9 @@ type BoundPods struct { // Items is the list of all pods bound to a given host. Items []BoundPod `json:"items" description:"list of all pods bound to a given host"` } + +// List holds a list of objects, which may not be known by the server. +type List struct { + TypeMeta `json:",inline"` + Items []runtime.RawExtension `json:"items" description:"list of objects"` +} diff --git a/pkg/api/v1beta3/register.go b/pkg/api/v1beta3/register.go index db6c3957f04..5d73780b719 100644 --- a/pkg/api/v1beta3/register.go +++ b/pkg/api/v1beta3/register.go @@ -46,6 +46,7 @@ func init() { &OperationList{}, &Event{}, &EventList{}, + &List{}, ) } @@ -69,3 +70,4 @@ func (*Operation) IsAnAPIObject() {} func (*OperationList) IsAnAPIObject() {} func (*Event) IsAnAPIObject() {} func (*EventList) IsAnAPIObject() {} +func (*List) IsAnAPIObject() {} diff --git a/pkg/api/v1beta3/types.go b/pkg/api/v1beta3/types.go index 65160c5dc6c..346695bf626 100644 --- a/pkg/api/v1beta3/types.go +++ b/pkg/api/v1beta3/types.go @@ -19,6 +19,7 @@ package v1beta3 import ( "time" + "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" "github.com/GoogleCloudPlatform/kubernetes/pkg/util" ) @@ -556,9 +557,14 @@ type ReplicationControllerSpec struct { // Selector is a label query over pods that should match the Replicas count. Selector map[string]string `json:"selector,omitempty"` - // Template is a reference to an object that describes the pod that will be created if + // TemplateRef is a reference to an object that describes the pod that will be created if // insufficient replicas are detected. - Template ObjectReference `json:"template,omitempty"` + TemplateRef *ObjectReference `json:"templateRef,omitempty"` + + // Template is the object that describes the pod that will be created if + // insufficient replicas are detected. This takes precedence over a + // TemplateRef. + Template *PodTemplateSpec `json:"template,omitempty"` } // ReplicationControllerStatus represents the current status of a replication @@ -944,3 +950,11 @@ type EventList struct { Items []Event `json:"items"` } + +// List holds a list of objects, which may not be known by the server. +type List struct { + TypeMeta `json:",inline"` + ListMeta `json:"metadata,omitempty"` + + Items []runtime.RawExtension `json:"items" description:"list of objects"` +} diff --git a/pkg/runtime/scheme.go b/pkg/runtime/scheme.go index 5227607555a..0a3f8d040af 100644 --- a/pkg/runtime/scheme.go +++ b/pkg/runtime/scheme.go @@ -366,6 +366,8 @@ func (s *Scheme) DecodeInto(data []byte, obj Object) error { // Copy does a deep copy of an API object. Useful mostly for tests. // TODO(dbsmith): implement directly instead of via Encode/Decode +// TODO(claytonc): Copy cannot be used for objects which do not encode type information, such +// as lists of runtime.Objects func (s *Scheme) Copy(obj Object) (Object, error) { data, err := s.EncodeToVersion(obj, "") if err != nil {