From 77edb9103222e5e5c24a50e5f61b6e8b457b15b6 Mon Sep 17 00:00:00 2001 From: Daniel Smith Date: Fri, 5 Sep 2014 16:11:30 -0700 Subject: [PATCH] Add Object type to runtime, make runtime test pass. --- pkg/api/register.go | 32 ++++++------ pkg/api/types.go | 30 ++++++++++++ pkg/api/v1beta1/conversion.go | 2 +- pkg/api/v1beta1/register.go | 32 ++++++------ pkg/api/v1beta1/types.go | 32 ++++++++++++ pkg/runtime/embedded.go | 8 +-- pkg/runtime/embedded_test.go | 23 +++++---- pkg/runtime/helper.go | 92 ++++++++++++++++++++++------------- pkg/runtime/helper_test.go | 30 +++--------- pkg/runtime/jsonbase.go | 4 +- pkg/runtime/jsonbase_test.go | 43 ++++++---------- pkg/runtime/types.go | 25 +++++++--- 12 files changed, 212 insertions(+), 141 deletions(-) diff --git a/pkg/api/register.go b/pkg/api/register.go index 54bd719bddb..909bb906d2f 100644 --- a/pkg/api/register.go +++ b/pkg/api/register.go @@ -21,21 +21,21 @@ import ( ) func init() { - runtime.AddKnownTypes("", - PodList{}, - Pod{}, - ReplicationControllerList{}, - ReplicationController{}, - ServiceList{}, - Service{}, - MinionList{}, - Minion{}, - Status{}, - ServerOpList{}, - ServerOp{}, - ContainerManifestList{}, - Endpoints{}, - EndpointsList{}, - Binding{}, + runtime.DefaultScheme.AddKnownTypes("", + &PodList{}, + &Pod{}, + &ReplicationControllerList{}, + &ReplicationController{}, + &ServiceList{}, + &Service{}, + &MinionList{}, + &Minion{}, + &Status{}, + &ServerOpList{}, + &ServerOp{}, + &ContainerManifestList{}, + &Endpoints{}, + &EndpointsList{}, + &Binding{}, ) } diff --git a/pkg/api/types.go b/pkg/api/types.go index 76ebc5edae2..365c0c94a44 100644 --- a/pkg/api/types.go +++ b/pkg/api/types.go @@ -65,6 +65,8 @@ type ContainerManifestList struct { Items []ContainerManifest `json:"items,omitempty" yaml:"items,omitempty"` } +func (*ContainerManifestList) IsAnAPIObject() {} + // Volume represents a named volume in a pod that may be accessed by any containers in the pod. type Volume struct { // Required: This must be a DNS_LABEL. Each volume in a pod must have @@ -287,6 +289,8 @@ type PodList struct { Items []Pod `json:"items" yaml:"items,omitempty"` } +func (*PodList) IsAnAPIObject() {} + // Pod is a collection of containers, used as either input (create, update) or as output (list, get). type Pod struct { JSONBase `json:",inline" yaml:",inline"` @@ -295,6 +299,8 @@ type Pod struct { CurrentState PodState `json:"currentState,omitempty" yaml:"currentState,omitempty"` } +func (*Pod) IsAnAPIObject() {} + // ReplicationControllerState is the state of a replication controller, either input (create, update) or as output (list, get). type ReplicationControllerState struct { Replicas int `json:"replicas" yaml:"replicas"` @@ -308,6 +314,8 @@ type ReplicationControllerList struct { Items []ReplicationController `json:"items,omitempty" yaml:"items,omitempty"` } +func (*ReplicationControllerList) IsAnAPIObject() {} + // ReplicationController represents the configuration of a replication controller. type ReplicationController struct { JSONBase `json:",inline" yaml:",inline"` @@ -315,6 +323,8 @@ type ReplicationController struct { Labels map[string]string `json:"labels,omitempty" yaml:"labels,omitempty"` } +func (*ReplicationController) IsAnAPIObject() {} + // PodTemplate holds the information used for creating pods. type PodTemplate struct { DesiredState PodState `json:"desiredState,omitempty" yaml:"desiredState,omitempty"` @@ -327,6 +337,8 @@ type ServiceList struct { Items []Service `json:"items" yaml:"items"` } +func (*ServiceList) IsAnAPIObject() {} + // Service is a named abstraction of software service (for example, mysql) consisting of local port // (for example 3306) that the proxy listens on, and the selector that determines which pods // will answer requests sent through the proxy. @@ -346,6 +358,8 @@ type Service struct { ContainerPort util.IntOrString `json:"containerPort,omitempty" yaml:"containerPort,omitempty"` } +func (*Service) IsAnAPIObject() {} + // Endpoints is a collection of endpoints that implement the actual service, for example: // Name: "mysql", Endpoints: ["10.10.1.1:1909", "10.10.2.2:8834"] type Endpoints struct { @@ -353,12 +367,16 @@ type Endpoints struct { Endpoints []string `json:"endpoints,omitempty" yaml:"endpoints,omitempty"` } +func (*Endpoints) IsAnAPIObject() {} + // EndpointsList is a list of endpoints. type EndpointsList struct { JSONBase `json:",inline" yaml:",inline"` Items []Endpoints `json:"items,omitempty" yaml:"items,omitempty"` } +func (*EndpointsList) IsAnAPIObject() {} + // Minion is a worker node in Kubernetenes. // The name of the minion according to etcd is in JSONBase.ID. type Minion struct { @@ -367,12 +385,16 @@ type Minion struct { HostIP string `json:"hostIP,omitempty" yaml:"hostIP,omitempty"` } +func (*Minion) IsAnAPIObject() {} + // MinionList is a list of minions. type MinionList struct { JSONBase `json:",inline" yaml:",inline"` Items []Minion `json:"items,omitempty" yaml:"items,omitempty"` } +func (*MinionList) IsAnAPIObject() {} + // Binding is written by a scheduler to cause a pod to be bound to a host. type Binding struct { JSONBase `json:",inline" yaml:",inline"` @@ -380,6 +402,8 @@ type Binding struct { Host string `json:"host" yaml:"host"` } +func (*Binding) IsAnAPIObject() {} + // Status is a return value for calls that don't return other objects. // TODO: this could go in apiserver, but I'm including it here so clients needn't // import both. @@ -403,6 +427,8 @@ type Status struct { Code int `json:"code,omitempty" yaml:"code,omitempty"` } +func (*Status) IsAnAPIObject() {} + // StatusDetails is a set of additional properties that MAY be set by the // server to provide additional information about a response. The Reason // field of a Status object defines what attributes will be set. Clients @@ -539,12 +565,16 @@ type ServerOp struct { JSONBase `yaml:",inline" json:",inline"` } +func (*ServerOp) IsAnAPIObject() {} + // ServerOpList is a list of operations, as delivered to API clients. type ServerOpList struct { JSONBase `yaml:",inline" json:",inline"` Items []ServerOp `yaml:"items,omitempty" json:"items,omitempty"` } +func (*ServerOpList) IsAnAPIObject() {} + // WatchEvent objects are streamed from the api server in response to a watch request. type WatchEvent struct { // The type of the watch event; added, modified, or deleted. diff --git a/pkg/api/v1beta1/conversion.go b/pkg/api/v1beta1/conversion.go index a56fc2ddcab..d83c001c2d9 100644 --- a/pkg/api/v1beta1/conversion.go +++ b/pkg/api/v1beta1/conversion.go @@ -24,7 +24,7 @@ import ( ) func init() { - runtime.AddConversionFuncs( + runtime.DefaultScheme.AddConversionFuncs( // EnvVar's Key is deprecated in favor of Name. func(in *newer.EnvVar, out *EnvVar, s conversion.Scope) error { out.Value = in.Value diff --git a/pkg/api/v1beta1/register.go b/pkg/api/v1beta1/register.go index c5768bac8aa..7dd8f83c559 100644 --- a/pkg/api/v1beta1/register.go +++ b/pkg/api/v1beta1/register.go @@ -21,21 +21,21 @@ import ( ) func init() { - runtime.AddKnownTypes("v1beta1", - PodList{}, - Pod{}, - ReplicationControllerList{}, - ReplicationController{}, - ServiceList{}, - Service{}, - MinionList{}, - Minion{}, - Status{}, - ServerOpList{}, - ServerOp{}, - ContainerManifestList{}, - Endpoints{}, - EndpointsList{}, - Binding{}, + runtime.DefaultScheme.AddKnownTypes("v1beta1", + &PodList{}, + &Pod{}, + &ReplicationControllerList{}, + &ReplicationController{}, + &ServiceList{}, + &Service{}, + &MinionList{}, + &Minion{}, + &Status{}, + &ServerOpList{}, + &ServerOp{}, + &ContainerManifestList{}, + &Endpoints{}, + &EndpointsList{}, + &Binding{}, ) } diff --git a/pkg/api/v1beta1/types.go b/pkg/api/v1beta1/types.go index 1bd2cccd5fe..74d576d8049 100644 --- a/pkg/api/v1beta1/types.go +++ b/pkg/api/v1beta1/types.go @@ -65,6 +65,8 @@ type ContainerManifestList struct { Items []ContainerManifest `json:"items,omitempty" yaml:"items,omitempty"` } +func (*ContainerManifestList) IsAnAPIObject() {} + // Volume represents a named volume in a pod that may be accessed by any containers in the pod. type Volume struct { // Required: This must be a DNS_LABEL. Each volume in a pod must have @@ -243,6 +245,8 @@ type JSONBase struct { APIVersion string `json:"apiVersion,omitempty" yaml:"apiVersion,omitempty"` } +func (*JSONBase) IsAnAPIObject() {} + // PodStatus represents a status of a pod. type PodStatus string @@ -298,6 +302,8 @@ type PodList struct { Items []Pod `json:"items" yaml:"items,omitempty"` } +func (*PodList) IsAnAPIObject() {} + // Pod is a collection of containers, used as either input (create, update) or as output (list, get). type Pod struct { JSONBase `json:",inline" yaml:",inline"` @@ -306,6 +312,8 @@ type Pod struct { CurrentState PodState `json:"currentState,omitempty" yaml:"currentState,omitempty"` } +func (*Pod) IsAnAPIObject() {} + // ReplicationControllerState is the state of a replication controller, either input (create, update) or as output (list, get). type ReplicationControllerState struct { Replicas int `json:"replicas" yaml:"replicas"` @@ -319,6 +327,8 @@ type ReplicationControllerList struct { Items []ReplicationController `json:"items,omitempty" yaml:"items,omitempty"` } +func (*ReplicationControllerList) IsAnAPIObject() {} + // ReplicationController represents the configuration of a replication controller. type ReplicationController struct { JSONBase `json:",inline" yaml:",inline"` @@ -326,6 +336,8 @@ type ReplicationController struct { Labels map[string]string `json:"labels,omitempty" yaml:"labels,omitempty"` } +func (*ReplicationController) IsAnAPIObject() {} + // PodTemplate holds the information used for creating pods. type PodTemplate struct { DesiredState PodState `json:"desiredState,omitempty" yaml:"desiredState,omitempty"` @@ -338,6 +350,8 @@ type ServiceList struct { Items []Service `json:"items" yaml:"items"` } +func (*ServiceList) IsAnAPIObject() {} + // Service is a named abstraction of software service (for example, mysql) consisting of local port // (for example 3306) that the proxy listens on, and the selector that determines which pods // will answer requests sent through the proxy. @@ -357,6 +371,8 @@ type Service struct { ContainerPort util.IntOrString `json:"containerPort,omitempty" yaml:"containerPort,omitempty"` } +func (*Service) IsAnAPIObject() {} + // Endpoints is a collection of endpoints that implement the actual service, for example: // Name: "mysql", Endpoints: ["10.10.1.1:1909", "10.10.2.2:8834"] type Endpoints struct { @@ -364,12 +380,16 @@ type Endpoints struct { Endpoints []string `json:"endpoints,omitempty" yaml:"endpoints,omitempty"` } +func (*Endpoints) IsAnAPIObject() {} + // EndpointsList is a list of endpoints. type EndpointsList struct { JSONBase `json:",inline" yaml:",inline"` Items []Endpoints `json:"items,omitempty" yaml:"items,omitempty"` } +func (*EndpointsList) IsAnAPIObject() {} + // Minion is a worker node in Kubernetenes. // The name of the minion according to etcd is in JSONBase.ID. type Minion struct { @@ -378,6 +398,8 @@ type Minion struct { HostIP string `json:"hostIP,omitempty" yaml:"hostIP,omitempty"` } +func (*Minion) IsAnAPIObject() {} + // MinionList is a list of minions. type MinionList struct { JSONBase `json:",inline" yaml:",inline"` @@ -387,6 +409,8 @@ type MinionList struct { Items []Minion `json:"items,omitempty" yaml:"items,omitempty"` } +func (*MinionList) IsAnAPIObject() {} + // Binding is written by a scheduler to cause a pod to be bound to a host. type Binding struct { JSONBase `json:",inline" yaml:",inline"` @@ -394,6 +418,8 @@ type Binding struct { Host string `json:"host" yaml:"host"` } +func (*Binding) IsAnAPIObject() {} + // Status is a return value for calls that don't return other objects. // TODO: this could go in apiserver, but I'm including it here so clients needn't // import both. @@ -417,6 +443,8 @@ type Status struct { Code int `json:"code,omitempty" yaml:"code,omitempty"` } +func (*Status) IsAnAPIObject() {} + // StatusDetails is a set of additional properties that MAY be set by the // server to provide additional information about a response. The Reason // field of a Status object defines what attributes will be set. Clients @@ -540,12 +568,16 @@ type ServerOp struct { JSONBase `yaml:",inline" json:",inline"` } +func (*ServerOp) IsAnAPIObject() {} + // ServerOpList is a list of operations, as delivered to API clients. type ServerOpList struct { JSONBase `yaml:",inline" json:",inline"` Items []ServerOp `yaml:"items,omitempty" json:"items,omitempty"` } +func (*ServerOpList) IsAnAPIObject() {} + // WatchEvent objects are streamed from the api server in response to a watch request. type WatchEvent struct { // The type of the watch event; added, modified, or deleted. diff --git a/pkg/runtime/embedded.go b/pkg/runtime/embedded.go index 8189a120d1a..fac3e133856 100644 --- a/pkg/runtime/embedded.go +++ b/pkg/runtime/embedded.go @@ -33,7 +33,7 @@ func (a *EmbeddedObject) UnmarshalJSON(b []byte) error { return nil } - obj, err := Decode(b) + obj, err := Codec.Decode(b) if err != nil { return err } @@ -48,7 +48,7 @@ func (a EmbeddedObject) MarshalJSON() ([]byte, error) { return []byte("null"), nil } - return Encode(a.Object) + return Codec.Encode(a.Object) } // SetYAML implements the yaml.Setter interface. @@ -67,7 +67,7 @@ func (a *EmbeddedObject) SetYAML(tag string, value interface{}) bool { if err != nil { panic("yaml can't reverse its own object") } - obj, err := Decode(b) + obj, err := Codec.Decode(b) if err != nil { return false } @@ -82,7 +82,7 @@ func (a EmbeddedObject) GetYAML() (tag string, value interface{}) { return } // Encode returns JSON, which is conveniently a subset of YAML. - v, err := Encode(a.Object) + v, err := Codec.Encode(a.Object) if err != nil { panic("impossible to encode API object!") } diff --git a/pkg/runtime/embedded_test.go b/pkg/runtime/embedded_test.go index d9e23780f80..66d4f39c377 100644 --- a/pkg/runtime/embedded_test.go +++ b/pkg/runtime/embedded_test.go @@ -22,14 +22,19 @@ import ( "testing" ) +type EmbeddedTest struct { + JSONBase `yaml:",inline" json:",inline"` + Object EmbeddedObject `yaml:"object,omitempty" json:"object,omitempty"` + EmptyObject EmbeddedObject `yaml:"emptyObject,omitempty" json:"emptyObject,omitempty"` +} + +func (*EmbeddedTest) IsAnAPIObject() {} + func TestEmbeddedObject(t *testing.T) { - type EmbeddedTest struct { - JSONBase `yaml:",inline" json:",inline"` - Object EmbeddedObject `yaml:"object,omitempty" json:"object,omitempty"` - EmptyObject EmbeddedObject `yaml:"emptyObject,omitempty" json:"emptyObject,omitempty"` - } - AddKnownTypes("", EmbeddedTest{}) - AddKnownTypes("v1beta1", EmbeddedTest{}) + // TODO(dbsmith) fix EmbeddedObject to not use DefaultScheme. + s := DefaultScheme + s.AddKnownTypes("", &EmbeddedTest{}) + s.AddKnownTypes("v1beta1", &EmbeddedTest{}) outer := &EmbeddedTest{ JSONBase: JSONBase{ID: "outer"}, @@ -40,14 +45,14 @@ func TestEmbeddedObject(t *testing.T) { }, } - wire, err := Encode(outer) + wire, err := s.Encode(outer) if err != nil { t.Fatalf("Unexpected encode error '%v'", err) } t.Logf("Wire format is:\n%v\n", string(wire)) - decoded, err := Decode(wire) + decoded, err := s.Decode(wire) if err != nil { t.Fatalf("Unexpected decode error %v", err) } diff --git a/pkg/runtime/helper.go b/pkg/runtime/helper.go index ac195cd8c2c..d6788d59a24 100644 --- a/pkg/runtime/helper.go +++ b/pkg/runtime/helper.go @@ -27,38 +27,56 @@ import ( // codec defines methods for serializing and deserializing API // objects. type codec interface { - Encode(obj interface{}) (data []byte, err error) - Decode(data []byte) (interface{}, error) - DecodeInto(data []byte, obj interface{}) error + Encode(obj Object) (data []byte, err error) + Decode(data []byte) (Object, error) + DecodeInto(data []byte, obj Object) error } // resourceVersioner provides methods for setting and retrieving // the resource version from an API object. type resourceVersioner interface { - SetResourceVersion(obj interface{}, version uint64) error - ResourceVersion(obj interface{}) (uint64, error) + SetResourceVersion(obj Object, version uint64) error + ResourceVersion(obj Object) (uint64, error) } var ResourceVersioner resourceVersioner = NewJSONBaseResourceVersioner() -var conversionScheme = conversion.NewScheme() -var Codec codec = conversionScheme +var DefaultScheme = NewScheme("", "v1beta1") +var Codec codec = DefaultScheme -func init() { - conversionScheme.InternalVersion = "" - conversionScheme.ExternalVersion = "v1beta1" - conversionScheme.MetaInsertionFactory = metaInsertion{} +// Scheme defines methods for serializing and deserializing API objects. It +// is an adaptation of conversion's Scheme for our API objects. +type Scheme struct { + raw *conversion.Scheme +} + +// NewScheme creates a new Scheme. A default scheme is provided and accessible +// as the "DefaultScheme" variable. +func NewScheme(internalVersion, externalVersion string) *Scheme { + s := &Scheme{conversion.NewScheme()} + s.raw.InternalVersion = internalVersion + s.raw.ExternalVersion = externalVersion + s.raw.MetaInsertionFactory = metaInsertion{} + return s } // AddKnownTypes registers the types of the arguments to the marshaller of the package api. // Encode() refuses the object unless its type is registered with AddKnownTypes. -func AddKnownTypes(version string, types ...interface{}) { - conversionScheme.AddKnownTypes(version, types...) +func (s *Scheme) AddKnownTypes(version string, types ...Object) { + interfaces := make([]interface{}, len(types)) + for i := range types { + interfaces[i] = types[i] + } + s.raw.AddKnownTypes(version, interfaces...) } // New returns a new API object of the given version ("" for internal // representation) and name, or an error if it hasn't been registered. -func New(versionName, typeName string) (interface{}, error) { - return conversionScheme.NewObject(versionName, typeName) +func (s *Scheme) New(versionName, typeName string) (Object, error) { + obj, err := s.raw.NewObject(versionName, typeName) + if err != nil { + return nil, err + } + return obj.(Object), nil } // AddConversionFuncs adds a function to the list of conversion functions. The given @@ -73,20 +91,20 @@ func New(versionName, typeName string) (interface{}, error) { // 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. -func AddConversionFuncs(conversionFuncs ...interface{}) error { - return conversionScheme.AddConversionFuncs(conversionFuncs...) +func (s *Scheme) AddConversionFuncs(conversionFuncs ...interface{}) error { + return s.raw.AddConversionFuncs(conversionFuncs...) } -// Convert will attempt to convert in into out. Both must be pointers to API objects. +// 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. -func Convert(in, out interface{}) error { - return conversionScheme.Convert(in, out) +func (s *Scheme) Convert(in, out interface{}) error { + return s.raw.Convert(in, out) } // FindJSONBase takes an arbitary api type, returns pointer to its JSONBase field. // obj must be a pointer to an api type. -func FindJSONBase(obj interface{}) (JSONBaseInterface, error) { +func FindJSONBase(obj Object) (JSONBaseInterface, error) { v, err := enforcePtr(obj) if err != nil { return nil, err @@ -108,8 +126,8 @@ func FindJSONBase(obj interface{}) (JSONBaseInterface, error) { } // EncodeOrDie is a version of Encode which will panic instead of returning an error. For tests. -func EncodeOrDie(obj interface{}) string { - return conversionScheme.EncodeOrDie(obj) +func (s *Scheme) EncodeOrDie(obj Object) string { + return s.raw.EncodeOrDie(obj) } // Encode turns the given api object into an appropriate JSON string. @@ -146,14 +164,14 @@ func EncodeOrDie(obj interface{}) string { // default will be needed, to allow operating in clusters that haven't yet // upgraded. // -func Encode(obj interface{}) (data []byte, err error) { - return conversionScheme.Encode(obj) +func (s *Scheme) Encode(obj Object) (data []byte, err error) { + return s.raw.Encode(obj) } // enforcePtr ensures that obj is a pointer of some sort. Returns a reflect.Value of the // dereferenced pointer, ensuring that it is settable/addressable. // Returns an error if this is not possible. -func enforcePtr(obj interface{}) (reflect.Value, error) { +func enforcePtr(obj Object) (reflect.Value, error) { v := reflect.ValueOf(obj) if v.Kind() != reflect.Ptr { return reflect.Value{}, fmt.Errorf("expected pointer, but got %v", v.Type().Name()) @@ -181,8 +199,12 @@ func VersionAndKind(data []byte) (version, kind string, err error) { // Deduces the type based upon the APIVersion and Kind fields, which are set // by Encode. Only versioned objects (APIVersion != "") are accepted. The object // will be converted into the in-memory unversioned type before being returned. -func Decode(data []byte) (interface{}, error) { - return conversionScheme.Decode(data) +func (s *Scheme) Decode(data []byte) (Object, error) { + obj, err := s.raw.Decode(data) + if err != nil { + return nil, err + } + return obj.(Object), nil } // DecodeInto parses a YAML or JSON string and stores it in obj. Returns an error @@ -190,22 +212,22 @@ func Decode(data []byte) (interface{}, error) { // pointer to an api type. // If obj's APIVersion doesn't match that in data, an attempt will be made to convert // data into obj's version. -func DecodeInto(data []byte, obj interface{}) error { - return conversionScheme.DecodeInto(data, obj) +func (s *Scheme) DecodeInto(data []byte, obj Object) error { + return s.raw.DecodeInto(data, obj) } // Does a deep copy of an API object. Useful mostly for tests. // TODO(dbsmith): implement directly instead of via Encode/Decode -func Copy(obj interface{}) (interface{}, error) { - data, err := Encode(obj) +func (s *Scheme) Copy(obj Object) (Object, error) { + data, err := s.Encode(obj) if err != nil { return nil, err } - return Decode(data) + return s.Decode(data) } -func CopyOrDie(obj interface{}) interface{} { - newObj, err := Copy(obj) +func (s *Scheme) CopyOrDie(obj Object) Object { + newObj, err := s.Copy(obj) if err != nil { panic(err) } diff --git a/pkg/runtime/helper_test.go b/pkg/runtime/helper_test.go index b2bcc9f6bf4..86e368fc58d 100644 --- a/pkg/runtime/helper_test.go +++ b/pkg/runtime/helper_test.go @@ -25,31 +25,13 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" ) -func TestEncode_NonPtr(t *testing.T) { - pod := api.Pod{ - Labels: map[string]string{"name": "foo"}, - } - obj := interface{}(pod) - data, err := runtime.Encode(obj) - obj2, err2 := runtime.Decode(data) - if err != nil || err2 != nil { - t.Fatalf("Failure: '%v' '%v'", err, err2) - } - if _, ok := obj2.(*api.Pod); !ok { - t.Fatalf("Got wrong type") - } - if !reflect.DeepEqual(obj2, &pod) { - t.Errorf("Expected:\n %#v,\n Got:\n %#v", &pod, obj2) - } -} - -func TestEncode_Ptr(t *testing.T) { +func TestEncode(t *testing.T) { pod := &api.Pod{ Labels: map[string]string{"name": "foo"}, } - obj := interface{}(pod) - data, err := runtime.Encode(obj) - obj2, err2 := runtime.Decode(data) + obj := runtime.Object(pod) + data, err := runtime.DefaultScheme.Encode(obj) + obj2, err2 := runtime.DefaultScheme.Decode(data) if err != nil || err2 != nil { t.Fatalf("Failure: '%v' '%v'", err, err2) } @@ -63,11 +45,11 @@ func TestEncode_Ptr(t *testing.T) { func TestBadJSONRejection(t *testing.T) { badJSONMissingKind := []byte(`{ }`) - if _, err := runtime.Decode(badJSONMissingKind); err == nil { + if _, err := runtime.DefaultScheme.Decode(badJSONMissingKind); err == nil { t.Errorf("Did not reject despite lack of kind field: %s", badJSONMissingKind) } badJSONUnknownType := []byte(`{"kind": "bar"}`) - if _, err1 := runtime.Decode(badJSONUnknownType); err1 == nil { + if _, err1 := runtime.DefaultScheme.Decode(badJSONUnknownType); err1 == nil { t.Errorf("Did not reject despite use of unknown type: %s", badJSONUnknownType) } /*badJSONKindMismatch := []byte(`{"kind": "Pod"}`) diff --git a/pkg/runtime/jsonbase.go b/pkg/runtime/jsonbase.go index bcd6fe0a50d..90d385a37a2 100644 --- a/pkg/runtime/jsonbase.go +++ b/pkg/runtime/jsonbase.go @@ -29,7 +29,7 @@ func NewJSONBaseResourceVersioner() resourceVersioner { type jsonBaseResourceVersioner struct{} -func (v jsonBaseResourceVersioner) ResourceVersion(obj interface{}) (uint64, error) { +func (v jsonBaseResourceVersioner) ResourceVersion(obj Object) (uint64, error) { json, err := FindJSONBase(obj) if err != nil { return 0, err @@ -37,7 +37,7 @@ func (v jsonBaseResourceVersioner) ResourceVersion(obj interface{}) (uint64, err return json.ResourceVersion(), nil } -func (v jsonBaseResourceVersioner) SetResourceVersion(obj interface{}, version uint64) error { +func (v jsonBaseResourceVersioner) SetResourceVersion(obj Object, version uint64) error { json, err := FindJSONBase(obj) if err != nil { return err diff --git a/pkg/runtime/jsonbase_test.go b/pkg/runtime/jsonbase_test.go index 759cd7611b2..0e0165aacb1 100644 --- a/pkg/runtime/jsonbase_test.go +++ b/pkg/runtime/jsonbase_test.go @@ -77,20 +77,20 @@ func TestGenericJSONBase(t *testing.T) { } } +type MyAPIObject struct { + JSONBase `yaml:",inline" json:",inline"` +} + +func (*MyAPIObject) IsAnAPIObject() {} + +type MyIncorrectlyMarkedAsAPIObject struct { +} + +func (*MyIncorrectlyMarkedAsAPIObject) IsAnAPIObject() {} + func TestResourceVersionerOfAPI(t *testing.T) { - type JSONBase struct { - Kind string `json:"kind,omitempty" yaml:"kind,omitempty"` - ID string `json:"id,omitempty" yaml:"id,omitempty"` - CreationTimestamp util.Time `json:"creationTimestamp,omitempty" yaml:"creationTimestamp,omitempty"` - SelfLink string `json:"selfLink,omitempty" yaml:"selfLink,omitempty"` - ResourceVersion uint64 `json:"resourceVersion,omitempty" yaml:"resourceVersion,omitempty"` - APIVersion string `json:"apiVersion,omitempty" yaml:"apiVersion,omitempty"` - } - type MyAPIObject struct { - JSONBase `yaml:",inline" json:",inline"` - } type T struct { - Object interface{} + Object Expected uint64 } testCases := map[string]T{ @@ -110,10 +110,10 @@ func TestResourceVersionerOfAPI(t *testing.T) { } failingCases := map[string]struct { - Object interface{} + Object Expected uint64 }{ - "not a valid object to try": {JSONBase{ResourceVersion: 1}, 1}, + "not a valid object to try": {&MyIncorrectlyMarkedAsAPIObject{}, 1}, } for key, testCase := range failingCases { _, err := versioning.ResourceVersion(testCase.Object) @@ -123,7 +123,7 @@ func TestResourceVersionerOfAPI(t *testing.T) { } setCases := map[string]struct { - Object interface{} + Object Expected uint64 }{ "pointer to api object with version": {&MyAPIObject{JSONBase: JSONBase{ResourceVersion: 1}}, 1}, @@ -140,17 +140,4 @@ func TestResourceVersionerOfAPI(t *testing.T) { t.Errorf("%s: expected %d, got %d", key, 5, actual) } } - - failingSetCases := map[string]struct { - Object interface{} - Expected uint64 - }{ - "empty api object": {MyAPIObject{}, 0}, - "api object with version": {MyAPIObject{JSONBase: JSONBase{ResourceVersion: 1}}, 1}, - } - for key, testCase := range failingSetCases { - if err := versioning.SetResourceVersion(testCase.Object, 5); err == nil { - t.Errorf("%s: unexpected non-error", key) - } - } } diff --git a/pkg/runtime/types.go b/pkg/runtime/types.go index eb240d1fca5..a4b37faeb21 100644 --- a/pkg/runtime/types.go +++ b/pkg/runtime/types.go @@ -20,6 +20,15 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/util" ) +// All api types must support the Object interface. It's deliberately tiny so that this is not an onerous +// burden. Implement it with a pointer reciever; this will allow us to use the go compiler to check the +// one thing about our objects that it's capable of checking for us. +type Object interface { + // This function is used only to enforce membership. It's never called. + // TODO: Consider mass rename in the future to make it do something useful. + IsAnAPIObject() +} + // 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. @@ -29,8 +38,9 @@ import ( // runtime.JSONBase `yaml:",inline" json:",inline"` // ... // other fields // } +// func (*MyAwesomeAPIObject) IsAnAPIObject() {} // -// JSONBase is provided here for convenience. You may use it directlly from this package or define +// JSONBase is provided here for convenience. You may use it directly from this package or define // your own with the same fields. // type JSONBase struct { @@ -43,17 +53,16 @@ type JSONBase struct { } // EmbeddedObject has appropriate encoder and decoder functions, such that on the wire, it's -// stored as a []byte, but in memory, the contained object is accessable as an interface{} -// via the Get() function. Only objects having a JSONBase may be stored via Object. +// stored as a []byte, but in memory, the contained object is accessable as an Object +// via the Get() function. Only valid API objects may be stored via EmbeddedObject. // The purpose of this is to allow an API object of type known only at runtime to be // embedded within other API objects. // // Note that object assumes that you've registered all of your api types with the api package. // -// Note that objects will be serialized into the api package's default external versioned type; -// this should be fixed in the future to use the version of the current Codec instead. +// TODO(dbsmith): Stop using runtime.Codec, use the codec appropriate for the conversion (I have a plan). type EmbeddedObject struct { - Object interface{} + Object } // Extension allows api objects with unknown types to be passed-through. This can be used @@ -61,4 +70,8 @@ type EmbeddedObject struct { // JSONBase features-- kind, version, resourceVersion, etc. // TODO: Not implemented yet type Extension struct { + JSONBase `yaml:",inline" json:",inline"` + // RawJSON to go here. } + +func (*Extension) IsAnAPIObject() {}