Split api into api, api/common, api/validation & apitools

This commit is contained in:
Daniel Smith
2014-08-29 15:48:41 -07:00
parent 5bed06f614
commit 6121e61f99
18 changed files with 249 additions and 110 deletions

19
pkg/api/common/doc.go Normal file
View File

@@ -0,0 +1,19 @@
/*
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 common provides types useful for all versions of any object
// that conforms to the kubernetes API object expectations.
package common

View File

@@ -14,10 +14,12 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package api
package common
import (
"gopkg.in/v1/yaml"
"github.com/GoogleCloudPlatform/kubernetes/pkg/apitools"
)
// Encode()/Decode() are the canonical way of converting an API object to/from
@@ -26,14 +28,14 @@ import (
// embedded within other API types.
// UnmarshalJSON implements the json.Unmarshaler interface.
func (a *APIObject) UnmarshalJSON(b []byte) error {
func (a *Object) UnmarshalJSON(b []byte) error {
// Handle JSON's "null": Decode() doesn't expect it.
if len(b) == 4 && string(b) == "null" {
a.Object = nil
return nil
}
obj, err := Decode(b)
obj, err := apitools.Decode(b)
if err != nil {
return err
}
@@ -42,17 +44,17 @@ func (a *APIObject) UnmarshalJSON(b []byte) error {
}
// MarshalJSON implements the json.Marshaler interface.
func (a APIObject) MarshalJSON() ([]byte, error) {
func (a Object) MarshalJSON() ([]byte, error) {
if a.Object == nil {
// Encode unset/nil objects as JSON's "null".
return []byte("null"), nil
}
return Encode(a.Object)
return apitools.Encode(a.Object)
}
// SetYAML implements the yaml.Setter interface.
func (a *APIObject) SetYAML(tag string, value interface{}) bool {
func (a *Object) SetYAML(tag string, value interface{}) bool {
if value == nil {
a.Object = nil
return true
@@ -67,7 +69,7 @@ func (a *APIObject) SetYAML(tag string, value interface{}) bool {
if err != nil {
panic("yaml can't reverse its own object")
}
obj, err := Decode(b)
obj, err := apitools.Decode(b)
if err != nil {
return false
}
@@ -76,13 +78,13 @@ func (a *APIObject) SetYAML(tag string, value interface{}) bool {
}
// GetYAML implements the yaml.Getter interface.
func (a APIObject) GetYAML() (tag string, value interface{}) {
func (a Object) GetYAML() (tag string, value interface{}) {
if a.Object == nil {
value = "null"
return
}
// Encode returns JSON, which is conveniently a subset of YAML.
v, err := Encode(a.Object)
v, err := apitools.Encode(a.Object)
if err != nil {
panic("impossible to encode API object!")
}

View File

@@ -14,40 +14,42 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package api
package common
import (
"encoding/json"
"reflect"
"testing"
"github.com/GoogleCloudPlatform/kubernetes/pkg/apitools"
)
func TestAPIObject(t *testing.T) {
func TestObject(t *testing.T) {
type EmbeddedTest struct {
JSONBase `yaml:",inline" json:",inline"`
Object APIObject `yaml:"object,omitempty" json:"object,omitempty"`
EmptyObject APIObject `yaml:"emptyObject,omitempty" json:"emptyObject,omitempty"`
Object Object `yaml:"object,omitempty" json:"object,omitempty"`
EmptyObject Object `yaml:"emptyObject,omitempty" json:"emptyObject,omitempty"`
}
AddKnownTypes("", EmbeddedTest{})
AddKnownTypes("v1beta1", EmbeddedTest{})
apitools.AddKnownTypes("", EmbeddedTest{})
apitools.AddKnownTypes("v1beta1", EmbeddedTest{})
outer := &EmbeddedTest{
JSONBase: JSONBase{ID: "outer"},
Object: APIObject{
Object: Object{
&EmbeddedTest{
JSONBase: JSONBase{ID: "inner"},
},
},
}
wire, err := Encode(outer)
wire, err := apitools.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 := apitools.Decode(wire)
if err != nil {
t.Fatalf("Unexpected decode error %v", err)
}
@@ -56,7 +58,7 @@ func TestAPIObject(t *testing.T) {
t.Errorf("Expected: %#v but got %#v", e, a)
}
// test JSON decoding, too, since api.Decode uses yaml unmarshalling.
// test JSON decoding, too, since apitools.Decode uses yaml unmarshalling.
var decodedViaJSON EmbeddedTest
err = json.Unmarshal(wire, &decodedViaJSON)
if err != nil {

61
pkg/api/common/types.go Normal file
View File

@@ -0,0 +1,61 @@
/*
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 common
import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
)
// JSONBase is shared by all top level objects. The proper way to use it is to inline it in your type,
// like this:
// type MyAwesomeAPIObject struct {
// common.JSONBase `yaml:",inline" json:",inline"`
// ... // other fields
// }
//
// JSONBase is provided here for convenience. You may use it directlly from this package or define
// your own with the same fields.
//
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"`
}
// Object 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.
// 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.
type Object struct {
Object interface{}
}
// Extension allows api objects with unknown types to be passed-through. This can be used
// to deal with the API objects from a plug-in. Extension objects still have functioning
// JSONBase features-- kind, version, resourceVersion, etc.
// TODO: Not implemented yet
type Extension struct {
}

View File

@@ -14,6 +14,9 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
// Package api includes all types used to communicate between the various
// parts of the Kubernetes system.
// Package api contains the latest (or "internal") version of the
// Kubernetes API objects. This is the API objects as represented in memory.
// The contract presented to clients is located in the versioned packages,
// which are sub-directories. The first one is "v1beta1". Those packages
// describe how a particular version is serialized to storage/network.
package api

View File

@@ -1,257 +0,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 api
import (
"fmt"
"reflect"
"github.com/GoogleCloudPlatform/kubernetes/pkg/conversion"
"gopkg.in/v1/yaml"
)
// 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
}
// 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)
}
var Codec codec
var ResourceVersioner resourceVersioner
var conversionScheme = conversion.NewScheme()
func init() {
conversionScheme.InternalVersion = ""
conversionScheme.ExternalVersion = "v1beta1"
conversionScheme.MetaInsertionFactory = metaInsertion{}
AddKnownTypes("",
PodList{},
Pod{},
ReplicationControllerList{},
ReplicationController{},
ServiceList{},
Service{},
MinionList{},
Minion{},
Status{},
ServerOpList{},
ServerOp{},
ContainerManifestList{},
Endpoints{},
Binding{},
)
Codec = conversionScheme
ResourceVersioner = NewJSONBaseResourceVersioner()
}
// 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...)
}
// 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)
}
// 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.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.
//
// 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.
func AddConversionFuncs(conversionFuncs ...interface{}) error {
return conversionScheme.AddConversionFuncs(conversionFuncs...)
}
// Convert will attempt to convert in into out. Both must be pointers to API objects.
// 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)
}
// 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) {
v, err := enforcePtr(obj)
if err != nil {
return nil, err
}
t := v.Type()
name := t.Name()
if v.Kind() != reflect.Struct {
return nil, fmt.Errorf("expected struct, but got %v: %v (%#v)", v.Kind(), name, v.Interface())
}
jsonBase := v.FieldByName("JSONBase")
if !jsonBase.IsValid() {
return nil, fmt.Errorf("struct %v lacks embedded JSON type", name)
}
g, err := newGenericJSONBase(jsonBase)
if err != nil {
return nil, err
}
return g, nil
}
// FindJSONBaseRO takes an arbitary api type, return a copy of its JSONBase field.
// obj may be a pointer to an api type, or a non-pointer struct api type.
func FindJSONBaseRO(obj interface{}) (JSONBase, error) {
v := reflect.ValueOf(obj)
if v.Kind() == reflect.Ptr {
v = v.Elem()
}
if v.Kind() != reflect.Struct {
return JSONBase{}, fmt.Errorf("expected struct, but got %v (%#v)", v.Type().Name(), v.Interface())
}
jsonBase := v.FieldByName("JSONBase")
if !jsonBase.IsValid() {
return JSONBase{}, fmt.Errorf("struct %v lacks embedded JSON type", v.Type().Name())
}
return jsonBase.Interface().(JSONBase), nil
}
// 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)
}
// Encode turns the given api object into an appropriate JSON string.
// Will return an error if the object doesn't have an embedded JSONBase.
// Obj may be a pointer to a struct, or a struct. If a struct, a copy
// must be made. If a pointer, the object may be modified before encoding,
// but will be put back into its original state before returning.
//
// Memory/wire format differences:
// * Having to keep track of the Kind and APIVersion fields makes tests
// very annoying, so the rule is that they are set only in wire format
// (json), not when in native (memory) format. This is possible because
// both pieces of information are implicit in the go typed object.
// * An exception: note that, if there are embedded API objects of known
// type, for example, PodList{... Items []Pod ...}, these embedded
// objects must be of the same version of the object they are embedded
// within, and their APIVersion and Kind must both be empty.
// * Note that the exception does not apply to the APIObject type, which
// recursively does Encode()/Decode(), and is capable of expressing any
// API object.
// * Only versioned objects should be encoded. This means that, if you pass
// a native object, Encode will convert it to a versioned object. For
// example, an api.Pod will get converted to a v1beta1.Pod. However, if
// you pass in an object that's already versioned (v1beta1.Pod), Encode
// will not modify it.
//
// The purpose of the above complex conversion behavior is to allow us to
// change the memory format yet not break compatibility with any stored
// objects, whether they be in our storage layer (e.g., etcd), or in user's
// config files.
//
// TODO/next steps: When we add our second versioned type, this package will
// need a version of Encode that lets you choose the wire version. A configurable
// 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)
}
// 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) {
v := reflect.ValueOf(obj)
if v.Kind() != reflect.Ptr {
return reflect.Value{}, fmt.Errorf("expected pointer, but got %v", v.Type().Name())
}
return v.Elem(), nil
}
// VersionAndKind will return the APIVersion and Kind of the given wire-format
// enconding of an APIObject, or an error.
func VersionAndKind(data []byte) (version, kind string, err error) {
findKind := struct {
Kind string `json:"kind,omitempty" yaml:"kind,omitempty"`
APIVersion string `json:"apiVersion,omitempty" yaml:"apiVersion,omitempty"`
}{}
// yaml is a superset of json, so we use it to decode here. That way,
// we understand both.
err = yaml.Unmarshal(data, &findKind)
if err != nil {
return "", "", fmt.Errorf("couldn't get version/kind: %v", err)
}
return findKind.APIVersion, findKind.Kind, nil
}
// Decode converts a YAML or JSON string back into a pointer to an api object.
// 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)
}
// DecodeInto parses a YAML or JSON string and stores it in obj. Returns an error
// if data.Kind is set and doesn't match the type of obj. Obj should be a
// 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)
}
// metaInsertion implements conversion.MetaInsertionFactory, which lets the conversion
// package figure out how to encode our object's types and versions. These fields are
// located in our JSONBase.
type metaInsertion struct {
JSONBase struct {
APIVersion string `json:"apiVersion,omitempty" yaml:"apiVersion,omitempty"`
Kind string `json:"kind,omitempty" yaml:"kind,omitempty"`
} `json:",inline" yaml:",inline"`
}
// Create returns a new metaInsertion with the version and kind fields set.
func (metaInsertion) Create(version, kind string) interface{} {
m := metaInsertion{}
m.JSONBase.APIVersion = version
m.JSONBase.Kind = kind
return &m
}
// Interpret returns the version and kind information from in, which must be
// a metaInsertion pointer object.
func (metaInsertion) Interpret(in interface{}) (version, kind string) {
m := in.(*metaInsertion)
return m.JSONBase.APIVersion, m.JSONBase.Kind
}

View File

@@ -1,220 +0,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 api_test
import (
"encoding/json"
"flag"
"fmt"
"reflect"
"testing"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
_ "github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
"github.com/fsouza/go-dockerclient"
"github.com/google/gofuzz"
)
var fuzzIters = flag.Int("fuzz_iters", 50, "How many fuzzing iterations to do.")
// apiObjectFuzzer can randomly populate api objects.
var apiObjectFuzzer = fuzz.New().NilChance(.5).NumElements(1, 1).Funcs(
func(j *api.JSONBase, c fuzz.Continue) {
// We have to customize the randomization of JSONBases because their
// APIVersion and Kind must remain blank in memory.
j.APIVersion = ""
j.Kind = ""
j.ID = c.RandString()
// TODO: Fix JSON/YAML packages and/or write custom encoding
// for uint64's. Somehow the LS *byte* of this is lost, but
// only when all 8 bytes are set.
j.ResourceVersion = c.RandUint64() >> 8
j.SelfLink = c.RandString()
var sec, nsec int64
c.Fuzz(&sec)
c.Fuzz(&nsec)
j.CreationTimestamp = util.Unix(sec, nsec).Rfc3339Copy()
},
func(intstr *util.IntOrString, c fuzz.Continue) {
// util.IntOrString will panic if its kind is set wrong.
if c.RandBool() {
intstr.Kind = util.IntstrInt
intstr.IntVal = int(c.RandUint64())
intstr.StrVal = ""
} else {
intstr.Kind = util.IntstrString
intstr.IntVal = 0
intstr.StrVal = c.RandString()
}
},
func(u64 *uint64, c fuzz.Continue) {
// TODO: uint64's are NOT handled right.
*u64 = c.RandUint64() >> 8
},
func(pb map[docker.Port][]docker.PortBinding, c fuzz.Continue) {
// This is necessary because keys with nil values get omitted.
// TODO: Is this a bug?
pb[docker.Port(c.RandString())] = []docker.PortBinding{
{c.RandString(), c.RandString()},
{c.RandString(), c.RandString()},
}
},
func(pm map[string]docker.PortMapping, c fuzz.Continue) {
// This is necessary because keys with nil values get omitted.
// TODO: Is this a bug?
pm[c.RandString()] = docker.PortMapping{
c.RandString(): c.RandString(),
}
},
)
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 runTest(t *testing.T, source interface{}) {
name := reflect.TypeOf(source).Elem().Name()
apiObjectFuzzer.Fuzz(source)
j, err := api.FindJSONBase(source)
if err != nil {
t.Fatalf("Unexpected error %v for %#v", err, source)
}
j.SetKind("")
j.SetAPIVersion("")
data, err := api.Encode(source)
if err != nil {
t.Errorf("%v: %v (%#v)", name, err, source)
return
}
obj2, err := api.Decode(data)
if err != nil {
t.Errorf("%v: %v", name, err)
return
} else {
if !reflect.DeepEqual(source, obj2) {
t.Errorf("1: %v: diff: %v", name, objDiff(source, obj2))
return
}
}
obj3 := reflect.New(reflect.TypeOf(source).Elem()).Interface()
err = api.DecodeInto(data, obj3)
if err != nil {
t.Errorf("2: %v: %v", name, err)
return
} else {
if !reflect.DeepEqual(source, obj3) {
t.Errorf("3: %v: diff: %v", name, objDiff(source, obj3))
return
}
}
}
func TestTypes(t *testing.T) {
table := []interface{}{
&api.PodList{},
&api.Pod{},
&api.ServiceList{},
&api.Service{},
&api.ReplicationControllerList{},
&api.ReplicationController{},
&api.MinionList{},
&api.Minion{},
&api.Status{},
&api.ServerOpList{},
&api.ServerOp{},
&api.ContainerManifestList{},
&api.Endpoints{},
&api.Binding{},
}
for _, item := range table {
// Try a few times, since runTest uses random values.
for i := 0; i < *fuzzIters; i++ {
runTest(t, item)
}
}
}
func TestEncode_NonPtr(t *testing.T) {
pod := api.Pod{
Labels: map[string]string{"name": "foo"},
}
obj := interface{}(pod)
data, err := api.Encode(obj)
obj2, err2 := api.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) {
pod := &api.Pod{
Labels: map[string]string{"name": "foo"},
}
obj := interface{}(pod)
data, err := api.Encode(obj)
obj2, err2 := api.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 TestBadJSONRejection(t *testing.T) {
badJSONMissingKind := []byte(`{ }`)
if _, err := api.Decode(badJSONMissingKind); err == nil {
t.Errorf("Did not reject despite lack of kind field: %s", badJSONMissingKind)
}
badJSONUnknownType := []byte(`{"kind": "bar"}`)
if _, err1 := api.Decode(badJSONUnknownType); err1 == nil {
t.Errorf("Did not reject despite use of unknown type: %s", badJSONUnknownType)
}
/*badJSONKindMismatch := []byte(`{"kind": "Pod"}`)
if err2 := DecodeInto(badJSONKindMismatch, &Minion{}); err2 == nil {
t.Errorf("Kind is set but doesn't match the object type: %s", badJSONKindMismatch)
}*/
}

View File

@@ -1,144 +0,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 api
import (
"fmt"
"reflect"
)
// NewJSONBaseResourceVersioner returns a resourceVersioner that can set or
// retrieve ResourceVersion on objects derived from JSONBase.
func NewJSONBaseResourceVersioner() resourceVersioner {
return &jsonBaseResourceVersioner{}
}
type jsonBaseResourceVersioner struct{}
func (v jsonBaseResourceVersioner) ResourceVersion(obj interface{}) (uint64, error) {
json, err := FindJSONBaseRO(obj)
if err != nil {
return 0, err
}
return json.ResourceVersion, nil
}
func (v jsonBaseResourceVersioner) SetResourceVersion(obj interface{}, version uint64) error {
json, err := FindJSONBase(obj)
if err != nil {
return err
}
json.SetResourceVersion(version)
return nil
}
// JSONBaseInterface lets you work with a JSONBase from any of the versioned or
// internal APIObjects.
type JSONBaseInterface interface {
ID() string
SetID(ID string)
APIVersion() string
SetAPIVersion(version string)
Kind() string
SetKind(kind string)
ResourceVersion() uint64
SetResourceVersion(version uint64)
}
type genericJSONBase struct {
id *string
apiVersion *string
kind *string
resourceVersion *uint64
}
func (g genericJSONBase) ID() string {
return *g.id
}
func (g genericJSONBase) SetID(id string) {
*g.id = id
}
func (g genericJSONBase) APIVersion() string {
return *g.apiVersion
}
func (g genericJSONBase) SetAPIVersion(version string) {
*g.apiVersion = version
}
func (g genericJSONBase) Kind() string {
return *g.kind
}
func (g genericJSONBase) SetKind(kind string) {
*g.kind = kind
}
func (g genericJSONBase) ResourceVersion() uint64 {
return *g.resourceVersion
}
func (g genericJSONBase) SetResourceVersion(version uint64) {
*g.resourceVersion = version
}
// fieldPtr puts the address of fieldName, which must be a member of v,
// into dest, which must be an address of a variable to which this field's
// address can be assigned.
func fieldPtr(v reflect.Value, fieldName string, dest interface{}) error {
field := v.FieldByName(fieldName)
if !field.IsValid() {
return fmt.Errorf("Couldn't find %v field in %#v", fieldName, v.Interface())
}
v = reflect.ValueOf(dest)
if v.Kind() != reflect.Ptr {
return fmt.Errorf("dest should be ptr")
}
v = v.Elem()
field = field.Addr()
if field.Type().AssignableTo(v.Type()) {
v.Set(field)
return nil
}
if field.Type().ConvertibleTo(v.Type()) {
v.Set(field.Convert(v.Type()))
return nil
}
return fmt.Errorf("Couldn't assign/convert %v to %v", field.Type(), v.Type())
}
// newGenericJSONBase creates a new generic JSONBase from v, which must be an
// addressable/setable reflect.Value having the same fields as api.JSONBase.
// Returns an error if this isn't the case.
func newGenericJSONBase(v reflect.Value) (genericJSONBase, error) {
g := genericJSONBase{}
if err := fieldPtr(v, "ID", &g.id); err != nil {
return g, err
}
if err := fieldPtr(v, "APIVersion", &g.apiVersion); err != nil {
return g, err
}
if err := fieldPtr(v, "Kind", &g.kind); err != nil {
return g, err
}
if err := fieldPtr(v, "ResourceVersion", &g.resourceVersion); err != nil {
return g, err
}
return g, nil
}

View File

@@ -1,134 +0,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 api
import (
"reflect"
"testing"
)
func TestGenericJSONBase(t *testing.T) {
j := JSONBase{
ID: "foo",
APIVersion: "a",
Kind: "b",
ResourceVersion: 1,
}
g, err := newGenericJSONBase(reflect.ValueOf(&j).Elem())
if err != nil {
t.Fatalf("new err: %v", err)
}
// Prove g supports JSONBaseInterface.
jbi := JSONBaseInterface(g)
if e, a := "foo", jbi.ID(); e != a {
t.Errorf("expected %v, got %v", e, a)
}
if e, a := "a", jbi.APIVersion(); e != a {
t.Errorf("expected %v, got %v", e, a)
}
if e, a := "b", jbi.Kind(); e != a {
t.Errorf("expected %v, got %v", e, a)
}
if e, a := uint64(1), jbi.ResourceVersion(); e != a {
t.Errorf("expected %v, got %v", e, a)
}
jbi.SetID("bar")
jbi.SetAPIVersion("c")
jbi.SetKind("d")
jbi.SetResourceVersion(2)
// Prove that jbi changes the original object.
if e, a := "bar", j.ID; e != a {
t.Errorf("expected %v, got %v", e, a)
}
if e, a := "c", j.APIVersion; e != a {
t.Errorf("expected %v, got %v", e, a)
}
if e, a := "d", j.Kind; e != a {
t.Errorf("expected %v, got %v", e, a)
}
if e, a := uint64(2), j.ResourceVersion; e != a {
t.Errorf("expected %v, got %v", e, a)
}
}
func TestResourceVersionerOfAPI(t *testing.T) {
testCases := map[string]struct {
Object interface{}
Expected uint64
}{
"empty api object": {Service{}, 0},
"api object with version": {Service{JSONBase: JSONBase{ResourceVersion: 1}}, 1},
"pointer to api object with version": {&Service{JSONBase: JSONBase{ResourceVersion: 1}}, 1},
}
versioning := NewJSONBaseResourceVersioner()
for key, testCase := range testCases {
actual, err := versioning.ResourceVersion(testCase.Object)
if err != nil {
t.Errorf("%s: unexpected error %#v", key, err)
}
if actual != testCase.Expected {
t.Errorf("%s: expected %d, got %d", key, testCase.Expected, actual)
}
}
failingCases := map[string]struct {
Object interface{}
Expected uint64
}{
"not a valid object to try": {JSONBase{ResourceVersion: 1}, 1},
}
for key, testCase := range failingCases {
_, err := versioning.ResourceVersion(testCase.Object)
if err == nil {
t.Errorf("%s: expected error, got nil", key)
}
}
setCases := map[string]struct {
Object interface{}
Expected uint64
}{
"pointer to api object with version": {&Service{JSONBase: JSONBase{ResourceVersion: 1}}, 1},
}
for key, testCase := range setCases {
if err := versioning.SetResourceVersion(testCase.Object, 5); err != nil {
t.Errorf("%s: unexpected error %#v", key, err)
}
actual, err := versioning.ResourceVersion(testCase.Object)
if err != nil {
t.Errorf("%s: unexpected error %#v", key, err)
}
if actual != 5 {
t.Errorf("%s: expected %d, got %d", key, 5, actual)
}
}
failingSetCases := map[string]struct {
Object interface{}
Expected uint64
}{
"empty api object": {Service{}, 0},
"api object with version": {Service{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)
}
}
}

40
pkg/api/register.go Normal file
View File

@@ -0,0 +1,40 @@
/*
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 api
import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/apitools"
)
func init() {
apitools.AddKnownTypes("",
PodList{},
Pod{},
ReplicationControllerList{},
ReplicationController{},
ServiceList{},
Service{},
MinionList{},
Minion{},
Status{},
ServerOpList{},
ServerOp{},
ContainerManifestList{},
Endpoints{},
Binding{},
)
}

View File

@@ -17,6 +17,7 @@ limitations under the License.
package api
import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/common"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
"github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
"github.com/fsouza/go-dockerclient"
@@ -521,14 +522,5 @@ type WatchEvent struct {
// For added or modified objects, this is the new object; for deleted objects,
// it's the state of the object immediately prior to its deletion.
Object APIObject
}
// APIObject 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 APIObject.
// The purpose of this is to allow an API object of type known only at runtime to be
// embedded within other API objects.
type APIObject struct {
Object interface{}
Object common.Object
}

View File

@@ -19,15 +19,14 @@ package v1beta1
import (
// Alias this so it can be easily changed when we cut the next version.
newer "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
// Also import under original name for Convert and AddConversionFuncs.
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/apitools"
)
func init() {
// Shortcut for sub-conversions. TODO: This should possibly be refactored
// such that this convert function is passed to each conversion func.
Convert := api.Convert
api.AddConversionFuncs(
Convert := apitools.Convert
apitools.AddConversionFuncs(
// EnvVar's Key is deprecated in favor of Name.
func(in *newer.EnvVar, out *EnvVar) error {
out.Value = in.Value

View File

@@ -22,9 +22,10 @@ import (
newer "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1"
"github.com/GoogleCloudPlatform/kubernetes/pkg/apitools"
)
var Convert = newer.Convert
var Convert = apitools.Convert
func TestEnvConversion(t *testing.T) {
nonCanonical := []v1beta1.EnvVar{

View File

@@ -17,11 +17,11 @@ limitations under the License.
package v1beta1
import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/apitools"
)
func init() {
api.AddKnownTypes("v1beta1",
apitools.AddKnownTypes("v1beta1",
PodList{},
Pod{},
ReplicationControllerList{},

View File

@@ -17,6 +17,7 @@ limitations under the License.
package v1beta1
import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/common"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
"github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
"github.com/GoogleCloudPlatform/kubernetes/third_party/docker-api-structs"
@@ -521,14 +522,5 @@ type WatchEvent struct {
// For added or modified objects, this is the new object; for deleted objects,
// it's the state of the object immediately prior to its deletion.
Object APIObject
}
// APIObject 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 APIObject.
// The purpose of this is to allow an API object of type known only at runtime to be
// embedded within other API objects.
type APIObject struct {
Object interface{}
Object common.Object
}

View File

@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package api
package validation
import (
"strings"

View File

@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package api
package validation
import (
"strings"