Commit f81ec248 authored by Daniel Smith's avatar Daniel Smith

Merge pull request #2786 from smarterclayton/load_opaque_objects

Allow runtime.Object to be encoded as runtime.RawExtension
parents b7705d25 8a833ca7
...@@ -25,7 +25,9 @@ import ( ...@@ -25,7 +25,9 @@ import (
internal "github.com/GoogleCloudPlatform/kubernetes/pkg/api" internal "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
_ "github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1" _ "github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1"
_ "github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta2" _ "github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta2"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/GoogleCloudPlatform/kubernetes/pkg/util"
docker "github.com/fsouza/go-dockerclient" docker "github.com/fsouza/go-dockerclient"
fuzz "github.com/google/gofuzz" fuzz "github.com/google/gofuzz"
) )
...@@ -83,6 +85,26 @@ var apiObjectFuzzer = fuzz.New().NilChance(.5).NumElements(1, 1).Funcs( ...@@ -83,6 +85,26 @@ var apiObjectFuzzer = fuzz.New().NilChance(.5).NumElements(1, 1).Funcs(
// only replicas round trips // only replicas round trips
j.Replicas = int(c.RandUint64()) 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) { func(intstr *util.IntOrString, c fuzz.Continue) {
// util.IntOrString will panic if its kind is set wrong. // util.IntOrString will panic if its kind is set wrong.
if c.RandBool() { if c.RandBool() {
......
...@@ -45,6 +45,7 @@ func init() { ...@@ -45,6 +45,7 @@ func init() {
&ContainerManifestList{}, &ContainerManifestList{},
&BoundPod{}, &BoundPod{},
&BoundPods{}, &BoundPods{},
&List{},
) )
// Legacy names are supported // Legacy names are supported
Scheme.AddKnownTypeWithName("", "Minion", &Node{}) Scheme.AddKnownTypeWithName("", "Minion", &Node{})
...@@ -71,3 +72,4 @@ func (*ContainerManifest) IsAnAPIObject() {} ...@@ -71,3 +72,4 @@ func (*ContainerManifest) IsAnAPIObject() {}
func (*ContainerManifestList) IsAnAPIObject() {} func (*ContainerManifestList) IsAnAPIObject() {}
func (*BoundPod) IsAnAPIObject() {} func (*BoundPod) IsAnAPIObject() {}
func (*BoundPods) IsAnAPIObject() {} func (*BoundPods) IsAnAPIObject() {}
func (*List) IsAnAPIObject() {}
...@@ -48,15 +48,6 @@ var apiObjectFuzzer = fuzz.New().NilChance(.5).NumElements(1, 1).Funcs( ...@@ -48,15 +48,6 @@ var apiObjectFuzzer = fuzz.New().NilChance(.5).NumElements(1, 1).Funcs(
// APIVersion and Kind must remain blank in memory. // APIVersion and Kind must remain blank in memory.
j.APIVersion = "" j.APIVersion = ""
j.Kind = "" 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) { func(j *api.TypeMeta, c fuzz.Continue) {
// We have to customize the randomization of TypeMetas because their // We have to customize the randomization of TypeMetas because their
...@@ -99,6 +90,26 @@ var apiObjectFuzzer = fuzz.New().NilChance(.5).NumElements(1, 1).Funcs( ...@@ -99,6 +90,26 @@ var apiObjectFuzzer = fuzz.New().NilChance(.5).NumElements(1, 1).Funcs(
// only replicas round trips // only replicas round trips
j.Replicas = int(c.RandUint64()) 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) { func(intstr *util.IntOrString, c fuzz.Continue) {
// util.IntOrString will panic if its kind is set wrong. // util.IntOrString will panic if its kind is set wrong.
if c.RandBool() { if c.RandBool() {
...@@ -154,7 +165,7 @@ func runTest(t *testing.T, codec runtime.Codec, source runtime.Object) { ...@@ -154,7 +165,7 @@ func runTest(t *testing.T, codec runtime.Codec, source runtime.Object) {
obj2, err := codec.Decode(data) obj2, err := codec.Decode(data)
if err != nil { 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 return
} }
if !reflect.DeepEqual(source, obj2) { if !reflect.DeepEqual(source, obj2) {
...@@ -188,7 +199,21 @@ func TestSpecificKind(t *testing.T) { ...@@ -188,7 +199,21 @@ func TestSpecificKind(t *testing.T) {
api.Scheme.Log(nil) 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 nonRoundTrippableTypes = util.NewStringSet("ContainerManifest")
var nonInternalRoundTrippableTypes = util.NewStringSet("List")
func TestRoundTripTypes(t *testing.T) { func TestRoundTripTypes(t *testing.T) {
for kind := range api.Scheme.KnownTypes("") { for kind := range api.Scheme.KnownTypes("") {
...@@ -206,7 +231,9 @@ func TestRoundTripTypes(t *testing.T) { ...@@ -206,7 +231,9 @@ func TestRoundTripTypes(t *testing.T) {
} }
runTest(t, v1beta1.Codec, item) runTest(t, v1beta1.Codec, item)
runTest(t, v1beta2.Codec, item) runTest(t, v1beta2.Codec, item)
runTest(t, api.Codec, item) if !nonInternalRoundTrippableTypes.Has(kind) {
runTest(t, api.Codec, item)
}
} }
} }
} }
......
...@@ -19,6 +19,7 @@ package api ...@@ -19,6 +19,7 @@ package api
import ( import (
"time" "time"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/GoogleCloudPlatform/kubernetes/pkg/util"
) )
...@@ -1000,3 +1001,11 @@ type BoundPods struct { ...@@ -1000,3 +1001,11 @@ type BoundPods struct {
// Items is the list of all pods bound to a given host. // Items is the list of all pods bound to a given host.
Items []BoundPod `json:"items"` 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"`
}
...@@ -50,6 +50,7 @@ func init() { ...@@ -50,6 +50,7 @@ func init() {
&ContainerManifestList{}, &ContainerManifestList{},
&BoundPod{}, &BoundPod{},
&BoundPods{}, &BoundPods{},
&List{},
) )
// Future names are supported // Future names are supported
api.Scheme.AddKnownTypeWithName("v1beta1", "Node", &Minion{}) api.Scheme.AddKnownTypeWithName("v1beta1", "Node", &Minion{})
...@@ -76,3 +77,4 @@ func (*ContainerManifest) IsAnAPIObject() {} ...@@ -76,3 +77,4 @@ func (*ContainerManifest) IsAnAPIObject() {}
func (*ContainerManifestList) IsAnAPIObject() {} func (*ContainerManifestList) IsAnAPIObject() {}
func (*BoundPod) IsAnAPIObject() {} func (*BoundPod) IsAnAPIObject() {}
func (*BoundPods) IsAnAPIObject() {} func (*BoundPods) IsAnAPIObject() {}
func (*List) IsAnAPIObject() {}
...@@ -19,6 +19,7 @@ package v1beta1 ...@@ -19,6 +19,7 @@ package v1beta1
import ( import (
"time" "time"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/GoogleCloudPlatform/kubernetes/pkg/util"
) )
...@@ -782,3 +783,9 @@ type BoundPods struct { ...@@ -782,3 +783,9 @@ type BoundPods struct {
// Items is the list of all pods bound to a given host. // 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"` 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"`
}
...@@ -50,6 +50,7 @@ func init() { ...@@ -50,6 +50,7 @@ func init() {
&ContainerManifestList{}, &ContainerManifestList{},
&BoundPod{}, &BoundPod{},
&BoundPods{}, &BoundPods{},
&List{},
) )
// Future names are supported // Future names are supported
api.Scheme.AddKnownTypeWithName("v1beta2", "Node", &Minion{}) api.Scheme.AddKnownTypeWithName("v1beta2", "Node", &Minion{})
...@@ -76,3 +77,4 @@ func (*ContainerManifest) IsAnAPIObject() {} ...@@ -76,3 +77,4 @@ func (*ContainerManifest) IsAnAPIObject() {}
func (*ContainerManifestList) IsAnAPIObject() {} func (*ContainerManifestList) IsAnAPIObject() {}
func (*BoundPod) IsAnAPIObject() {} func (*BoundPod) IsAnAPIObject() {}
func (*BoundPods) IsAnAPIObject() {} func (*BoundPods) IsAnAPIObject() {}
func (*List) IsAnAPIObject() {}
...@@ -19,6 +19,7 @@ package v1beta2 ...@@ -19,6 +19,7 @@ package v1beta2
import ( import (
"time" "time"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/GoogleCloudPlatform/kubernetes/pkg/util"
) )
...@@ -783,3 +784,9 @@ type BoundPods struct { ...@@ -783,3 +784,9 @@ type BoundPods struct {
// Items is the list of all pods bound to a given host. // 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"` 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"`
}
...@@ -46,6 +46,7 @@ func init() { ...@@ -46,6 +46,7 @@ func init() {
&OperationList{}, &OperationList{},
&Event{}, &Event{},
&EventList{}, &EventList{},
&List{},
) )
// Legacy names are supported // Legacy names are supported
api.Scheme.AddKnownTypeWithName("v1beta3", "Minion", &Node{}) api.Scheme.AddKnownTypeWithName("v1beta3", "Minion", &Node{})
...@@ -72,3 +73,4 @@ func (*Operation) IsAnAPIObject() {} ...@@ -72,3 +73,4 @@ func (*Operation) IsAnAPIObject() {}
func (*OperationList) IsAnAPIObject() {} func (*OperationList) IsAnAPIObject() {}
func (*Event) IsAnAPIObject() {} func (*Event) IsAnAPIObject() {}
func (*EventList) IsAnAPIObject() {} func (*EventList) IsAnAPIObject() {}
func (*List) IsAnAPIObject() {}
...@@ -19,6 +19,7 @@ package v1beta3 ...@@ -19,6 +19,7 @@ package v1beta3
import ( import (
"time" "time"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/GoogleCloudPlatform/kubernetes/pkg/util"
) )
...@@ -556,9 +557,14 @@ type ReplicationControllerSpec struct { ...@@ -556,9 +557,14 @@ type ReplicationControllerSpec struct {
// Selector is a label query over pods that should match the Replicas count. // Selector is a label query over pods that should match the Replicas count.
Selector map[string]string `json:"selector,omitempty"` 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. // 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 // ReplicationControllerStatus represents the current status of a replication
...@@ -944,3 +950,11 @@ type EventList struct { ...@@ -944,3 +950,11 @@ type EventList struct {
Items []Event `json:"items"` 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"`
}
...@@ -49,15 +49,6 @@ var apiObjectFuzzer = fuzz.New().NilChance(.5).NumElements(1, 1).Funcs( ...@@ -49,15 +49,6 @@ var apiObjectFuzzer = fuzz.New().NilChance(.5).NumElements(1, 1).Funcs(
// APIVersion and Kind must remain blank in memory. // APIVersion and Kind must remain blank in memory.
j.APIVersion = "" j.APIVersion = ""
j.Kind = "" 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) { func(j *api.TypeMeta, c fuzz.Continue) {
// We have to customize the randomization of TypeMetas because their // We have to customize the randomization of TypeMetas because their
......
...@@ -112,7 +112,7 @@ func (s *Scheme) DecodeInto(data []byte, obj interface{}) error { ...@@ -112,7 +112,7 @@ func (s *Scheme) DecodeInto(data []byte, obj interface{}) error {
} else { } else {
external, err := s.NewObject(dataVersion, dataKind) external, err := s.NewObject(dataVersion, dataKind)
if err != nil { 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, // yaml is a superset of json, so we use it to decode here. That way,
// we understand both. // we understand both.
......
/*
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
}
...@@ -145,14 +145,14 @@ func (s *Scheme) KnownTypes(version string) map[string]reflect.Type { ...@@ -145,14 +145,14 @@ func (s *Scheme) KnownTypes(version string) map[string]reflect.Type {
// NewObject returns a new object of the given version and name, // NewObject returns a new object of the given version and name,
// or an error if it hasn't been registered. // 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 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 reflect.New(t).Interface(), nil
} }
return nil, fmt.Errorf("no type '%v' for version '%v'", typeName, versionName) return nil, &notRegisteredErr{kind: kind, version: versionName}
} }
return nil, fmt.Errorf("no version '%v'", versionName) return nil, &notRegisteredErr{kind: kind, version: versionName}
} }
// AddConversionFuncs adds functions to the list of conversion functions. The given // AddConversionFuncs adds functions to the list of conversion functions. The given
...@@ -187,8 +187,7 @@ func (s *Scheme) NewObject(versionName, typeName string) (interface{}, error) { ...@@ -187,8 +187,7 @@ func (s *Scheme) NewObject(versionName, typeName string) (interface{}, error) {
// add conversion functions for things with changed/removed fields. // add conversion functions for things with changed/removed fields.
func (s *Scheme) AddConversionFuncs(conversionFuncs ...interface{}) error { func (s *Scheme) AddConversionFuncs(conversionFuncs ...interface{}) error {
for _, f := range conversionFuncs { for _, f := range conversionFuncs {
err := s.converter.Register(f) if err := s.converter.Register(f); err != nil {
if err != nil {
return err return err
} }
} }
...@@ -286,7 +285,7 @@ func (s *Scheme) ObjectVersionAndKind(obj interface{}) (apiVersion, kind string, ...@@ -286,7 +285,7 @@ func (s *Scheme) ObjectVersionAndKind(obj interface{}) (apiVersion, kind string,
version, vOK := s.typeToVersion[t] version, vOK := s.typeToVersion[t]
kinds, kOK := s.typeToKind[t] kinds, kOK := s.typeToKind[t]
if !vOK || !kOK { if !vOK || !kOK {
return "", "", fmt.Errorf("unregistered type: %v", t) return "", "", &notRegisteredErr{t: t}
} }
apiVersion = version apiVersion = version
kind = kinds[0] kind = kinds[0]
......
...@@ -22,16 +22,14 @@ import ( ...@@ -22,16 +22,14 @@ import (
"testing" "testing"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" "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 { type EmbeddedTest struct {
runtime.TypeMeta `json:",inline"` runtime.TypeMeta
ID string `json:"id,omitempty"` ID string
Object runtime.EmbeddedObject `json:"object,omitempty"` Object runtime.EmbeddedObject
EmptyObject runtime.EmbeddedObject `json:"emptyObject,omitempty"` EmptyObject runtime.EmbeddedObject
} }
type EmbeddedTestExternal struct { type EmbeddedTestExternal struct {
...@@ -41,21 +39,91 @@ type EmbeddedTestExternal struct { ...@@ -41,21 +39,91 @@ type EmbeddedTestExternal struct {
EmptyObject runtime.RawExtension `json:"emptyObject,omitempty"` 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 (*EmbeddedTest) IsAnAPIObject() {}
func (*EmbeddedTestExternal) 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) { func TestEmbeddedObject(t *testing.T) {
s := scheme s := runtime.NewScheme()
s.AddKnownTypes("", &EmbeddedTest{}) s.AddKnownTypes("", &EmbeddedTest{})
s.AddKnownTypeWithName("v1test", "EmbeddedTest", &EmbeddedTestExternal{}) s.AddKnownTypeWithName("v1test", "EmbeddedTest", &EmbeddedTestExternal{})
outer := &EmbeddedTest{ outer := &EmbeddedTest{
TypeMeta: runtime.TypeMeta{Name: "outer"}, ID: "outer",
ID: "outer",
Object: runtime.EmbeddedObject{ Object: runtime.EmbeddedObject{
&EmbeddedTest{ &EmbeddedTest{
TypeMeta: runtime.TypeMeta{Name: "inner"}, ID: "inner",
ID: "inner",
}, },
}, },
} }
...@@ -83,7 +151,7 @@ func TestEmbeddedObject(t *testing.T) { ...@@ -83,7 +151,7 @@ func TestEmbeddedObject(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("Unexpected decode error %v", err) 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) 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 { if !reflect.DeepEqual(externalViaJSON.EmptyObject.RawJSON, []byte("null")) || len(externalViaJSON.Object.RawJSON) == 0 {
......
/*
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)
}
...@@ -26,6 +26,8 @@ func (re *RawExtension) UnmarshalJSON(in []byte) error { ...@@ -26,6 +26,8 @@ func (re *RawExtension) UnmarshalJSON(in []byte) error {
return nil 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 return re.RawJSON, nil
} }
/*
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))
}
}
...@@ -140,15 +140,77 @@ func (self *Scheme) rawExtensionToEmbeddedObject(in *RawExtension, out *Embedded ...@@ -140,15 +140,77 @@ func (self *Scheme) rawExtensionToEmbeddedObject(in *RawExtension, out *Embedded
return nil 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. // NewScheme creates a new Scheme. This scheme is pluggable by default.
func NewScheme() *Scheme { func NewScheme() *Scheme {
s := &Scheme{conversion.NewScheme()} s := &Scheme{conversion.NewScheme()}
s.raw.InternalVersion = "" s.raw.InternalVersion = ""
s.raw.MetaFactory = conversion.SimpleMetaFactory{BaseFields: []string{"TypeMeta"}, VersionField: "APIVersion", KindField: "Kind"} s.raw.MetaFactory = conversion.SimpleMetaFactory{BaseFields: []string{"TypeMeta"}, VersionField: "APIVersion", KindField: "Kind"}
s.raw.AddConversionFuncs( if err := s.raw.AddConversionFuncs(
s.embeddedObjectToRawExtension, s.embeddedObjectToRawExtension,
s.rawExtensionToEmbeddedObject, s.rawExtensionToEmbeddedObject,
) s.runtimeObjectToRawExtensionArray,
s.rawExtensionToRuntimeObjectArray,
); err != nil {
panic(err)
}
return s return s
} }
...@@ -304,6 +366,8 @@ func (s *Scheme) DecodeInto(data []byte, obj Object) error { ...@@ -304,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. // Copy does a deep copy of an API object. Useful mostly for tests.
// TODO(dbsmith): implement directly instead of via Encode/Decode // 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) { func (s *Scheme) Copy(obj Object) (Object, error) {
data, err := s.EncodeToVersion(obj, "") data, err := s.EncodeToVersion(obj, "")
if err != nil { if err != nil {
......
...@@ -16,9 +16,7 @@ limitations under the License. ...@@ -16,9 +16,7 @@ limitations under the License.
package runtime package runtime
import ( import ()
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
)
// Note that the types provided in this file are not versioned and are intended to be // 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. // safe to use from within all versions of every API object.
...@@ -35,15 +33,8 @@ import ( ...@@ -35,15 +33,8 @@ import (
// your own with the same fields. // your own with the same fields.
// //
type TypeMeta struct { type TypeMeta struct {
APIVersion string `json:"apiVersion,omitempty"` APIVersion string `json:"apiVersion,omitempty" yaml:"apiVersion,omitempty"`
Kind string `json:"kind,omitempty"` Kind string `json:"kind,omitempty" yaml:"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"`
} }
// PluginBase is like TypeMeta, but it's intended for plugin objects that won't ever be encoded // PluginBase is like TypeMeta, but it's intended for plugin objects that won't ever be encoded
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment