From 5c0f5e85e23c46a7d1c112b645c8d7e59a844a65 Mon Sep 17 00:00:00 2001 From: Daniel Smith Date: Thu, 31 Jul 2014 12:10:15 -0700 Subject: [PATCH] Make api use converter package. --- pkg/api/converter.go | 195 --------------------------- pkg/api/converter_test.go | 110 --------------- pkg/api/helper.go | 273 ++------------------------------------ 3 files changed, 13 insertions(+), 565 deletions(-) delete mode 100644 pkg/api/converter.go delete mode 100644 pkg/api/converter_test.go diff --git a/pkg/api/converter.go b/pkg/api/converter.go deleted file mode 100644 index f82dd772d2..0000000000 --- a/pkg/api/converter.go +++ /dev/null @@ -1,195 +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" -) - -type typePair struct { - source reflect.Type - dest reflect.Type -} - -type debugLogger interface { - Logf(format string, args ...interface{}) -} - -// Converter knows how to convert one type to another. -type Converter struct { - // Map from the conversion pair to a function which can - // do the conversion. - funcs map[typePair]reflect.Value - - // If true, print helpful debugging info. Quite verbose. - debug debugLogger -} - -// NewConverter makes a new Converter object. -func NewConverter() *Converter { - return &Converter{ - funcs: map[typePair]reflect.Value{}, - } -} - -// Register registers a conversion func with the Converter. conversionFunc must take -// two parameters, the input and output type. It must take a pointer to each. It must -// return an error. -// -// Example: -// c.Register(func(in *Pod, out *v1beta1.Pod) error { ... return nil }) -func (c *Converter) Register(conversionFunc interface{}) error { - fv := reflect.ValueOf(conversionFunc) - ft := fv.Type() - if ft.Kind() != reflect.Func { - return fmt.Errorf("expected func, got: %v", ft) - } - if ft.NumIn() != 2 { - return fmt.Errorf("expected two in params, got: %v", ft) - } - if ft.NumOut() != 1 { - return fmt.Errorf("expected one out param, got: %v", ft) - } - if ft.In(0).Kind() != reflect.Ptr { - return fmt.Errorf("expected pointer arg for in param 0, got: %v", ft) - } - if ft.In(1).Kind() != reflect.Ptr { - return fmt.Errorf("expected pointer arg for in param 1, got: %v", ft) - } - var forErrorType error - // This convolution is necessary, otherwise TypeOf picks up on the fact - // that forErrorType is nil. - errorType := reflect.TypeOf(&forErrorType).Elem() - if ft.Out(0) != errorType { - return fmt.Errorf("expected error return, got: %v", ft) - } - c.funcs[typePair{ft.In(0).Elem(), ft.In(1).Elem()}] = fv - return nil -} - -// Convert will translate src to dest if it knows how. Both must be pointers. -// If no conversion func is registered and the default copying mechanism -// doesn't work on this type pair, an error will be returned. -// Not safe for objects with cyclic references! -func (c *Converter) Convert(src, dest interface{}) error { - dv, sv := reflect.ValueOf(dest), reflect.ValueOf(src) - if dv.Kind() != reflect.Ptr { - return fmt.Errorf("Need pointer, but got %#v", dest) - } - if sv.Kind() != reflect.Ptr { - return fmt.Errorf("Need pointer, but got %#v", src) - } - dv = dv.Elem() - sv = sv.Elem() - if !dv.CanAddr() { - return fmt.Errorf("Can't write to dest") - } - return c.convert(sv, dv) -} - -// convert recursively copies sv into dv, calling an appropriate conversion function if -// one is registered. -func (c *Converter) convert(sv, dv reflect.Value) error { - dt, st := dv.Type(), sv.Type() - if fv, ok := c.funcs[typePair{st, dt}]; ok { - if c.debug != nil { - c.debug.Logf("Calling custom conversion of '%v' to '%v'", st, dt) - } - ret := fv.Call([]reflect.Value{sv.Addr(), dv.Addr()})[0].Interface() - // This convolution is necssary because nil interfaces won't convert - // to errors. - if ret == nil { - return nil - } - return ret.(error) - } - - if dt.Name() != st.Name() { - return fmt.Errorf("Type names don't match: %v, %v", dt.Name(), st.Name()) - } - - // This should handle all simple types. - if st.AssignableTo(dt) { - dv.Set(sv) - return nil - } - if st.ConvertibleTo(dt) { - dv.Set(sv.Convert(dt)) - return nil - } - - if c.debug != nil { - c.debug.Logf("Trying to convert '%v' to '%v'", st, dt) - } - - switch dv.Kind() { - case reflect.Struct: - for i := 0; i < dt.NumField(); i++ { - f := dv.Type().Field(i) - sf := sv.FieldByName(f.Name) - if !sf.IsValid() { - return fmt.Errorf("%v not present in source %v for dest %v", f.Name, sv.Type(), dv.Type()) - } - df := dv.Field(i) - if err := c.convert(sf, df); err != nil { - return err - } - } - case reflect.Slice: - if sv.IsNil() { - // Don't make a zero-length slice. - dv.Set(reflect.Zero(dt)) - return nil - } - dv.Set(reflect.MakeSlice(dt, sv.Len(), sv.Cap())) - for i := 0; i < sv.Len(); i++ { - if err := c.convert(sv.Index(i), dv.Index(i)); err != nil { - return err - } - } - case reflect.Ptr: - if sv.IsNil() { - // Don't copy a nil ptr! - dv.Set(reflect.Zero(dt)) - return nil - } - dv.Set(reflect.New(dt.Elem())) - return c.convert(sv.Elem(), dv.Elem()) - case reflect.Map: - if sv.IsNil() { - // Don't copy a nil ptr! - dv.Set(reflect.Zero(dt)) - return nil - } - dv.Set(reflect.MakeMap(dt)) - for _, sk := range sv.MapKeys() { - dk := reflect.New(dt.Key()).Elem() - if err := c.convert(sk, dk); err != nil { - return err - } - dkv := reflect.New(dt.Elem()).Elem() - if err := c.convert(sv.MapIndex(sk), dkv); err != nil { - return err - } - dv.SetMapIndex(dk, dkv) - } - default: - return fmt.Errorf("Couldn't copy '%v' into '%v'", st, dt) - } - return nil -} diff --git a/pkg/api/converter_test.go b/pkg/api/converter_test.go deleted file mode 100644 index 18a97e9298..0000000000 --- a/pkg/api/converter_test.go +++ /dev/null @@ -1,110 +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" - "testing" -) - -func TestConverter(t *testing.T) { - type A struct { - Foo string - } - type B struct { - Bar string - } - type C struct{} - c := NewConverter() - err := c.Register(func(in *A, out *B) error { - out.Bar = in.Foo - return nil - }) - if err != nil { - t.Fatalf("unexpected error %v", err) - } - err = c.Register(func(in *B, out *A) error { - out.Foo = in.Bar - return nil - }) - if err != nil { - t.Fatalf("unexpected error %v", err) - } - - x := A{"hello, intrepid test reader!"} - y := B{} - - err = c.Convert(&x, &y) - if err != nil { - t.Fatalf("unexpected error %v", err) - } - if e, a := x.Foo, y.Bar; e != a { - t.Errorf("expected %v, got %v", e, a) - } - - z := B{"all your test are belong to us"} - w := A{} - - err = c.Convert(&z, &w) - if err != nil { - t.Fatalf("unexpected error %v", err) - } - if e, a := z.Bar, w.Foo; e != a { - t.Errorf("expected %v, got %v", e, a) - } - - err = c.Register(func(in *A, out *C) error { - return fmt.Errorf("C can't store an A, silly") - }) - if err != nil { - t.Fatalf("unexpected error %v", err) - } - - err = c.Convert(&A{}, &C{}) - if err == nil { - t.Errorf("unexpected non-error") - } -} - -func anonymousEmptyTypeNamedA() interface{} { - type A struct{} - return &A{} -} - -func TestCopyConvertor(t *testing.T) { - type A struct { - Bar string - } - type B struct { - Bar string - } - c := NewConverter() - err := c.Convert(anonymousEmptyTypeNamedA(), &A{}) - if err == nil { - t.Errorf("unexpected non-error") - } - - err = c.Convert(&A{}, anonymousEmptyTypeNamedA()) - if err != nil { - t.Fatalf("unexpected error %v", err) - } - - err = c.Convert(&B{}, &A{}) - if err == nil { - t.Errorf("unexpected non-error") - } -} diff --git a/pkg/api/helper.go b/pkg/api/helper.go index 8b4631b9c1..91a631217b 100644 --- a/pkg/api/helper.go +++ b/pkg/api/helper.go @@ -17,28 +17,20 @@ limitations under the License. package api import ( - "encoding/json" "fmt" "reflect" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1" + "github.com/GoogleCloudPlatform/kubernetes/pkg/conversion" "gopkg.in/v1/yaml" ) -// versionMap allows one to figure out the go type of an object with -// the given version and name. -var versionMap = map[string]map[string]reflect.Type{} - -// typeToVersion allows one to figure out the version for a given go object. -// The reflect.Type we index by should *not* be a pointer. If the same type -// is registered for multiple versions, the last one wins. -var typeToVersion = map[reflect.Type]string{} - -// theConverter stores all registered conversion functions. It also has -// default coverting behavior. -var theConverter = NewConverter() +var conversionScheme *conversion.Scheme func init() { + conversionScheme = conversion.NewScheme() + conversionScheme.InternalVersion = "" + conversionScheme.ExternalVersion = "v1beta1" AddKnownTypes("", PodList{}, Pod{}, @@ -98,31 +90,13 @@ func init() { // 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{}) { - knownTypes, found := versionMap[version] - if !found { - knownTypes = map[string]reflect.Type{} - versionMap[version] = knownTypes - } - for _, obj := range types { - t := reflect.TypeOf(obj) - if t.Kind() != reflect.Struct { - panic("All types must be structs.") - } - knownTypes[t.Name()] = t - typeToVersion[t] = version - } + 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) { - if types, ok := versionMap[versionName]; ok { - if t, ok := types[typeName]; ok { - return reflect.New(t).Interface(), nil - } - return nil, fmt.Errorf("No type '%v' for version '%v'", typeName, versionName) - } - return nil, fmt.Errorf("No version '%v'", versionName) + return conversionScheme.NewObject(versionName, typeName) } // AddConversionFuncs adds a function to the list of conversion functions. The given @@ -138,20 +112,14 @@ func New(versionName, typeName string) (interface{}, error) { // 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 { - for _, f := range conversionFuncs { - err := theConverter.Register(f) - if err != nil { - return err - } - } - return nil + 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 theConverter.Convert(in, out) + return conversionScheme.Convert(in, out) } // FindJSONBase takes an arbitary api type, returns pointer to its JSONBase field. @@ -196,11 +164,7 @@ func FindJSONBaseRO(obj interface{}) (JSONBase, error) { // EncodeOrDie is a version of Encode which will panic instead of returning an error. For tests. func EncodeOrDie(obj interface{}) string { - bytes, err := Encode(obj) - if err != nil { - panic(err) - } - return string(bytes) + return conversionScheme.EncodeOrDie(obj) } // Encode turns the given api object into an appropriate JSON string. @@ -238,93 +202,7 @@ func EncodeOrDie(obj interface{}) string { // upgraded. // func Encode(obj interface{}) (data []byte, err error) { - obj = maybeCopy(obj) - obj, err = maybeExternalize(obj) - if err != nil { - return nil, err - } - - jsonBase, err := prepareEncode(obj) - if err != nil { - return nil, err - } - - data, err = json.MarshalIndent(obj, "", " ") - if err != nil { - return nil, err - } - // Leave these blank in memory. - jsonBase.SetKind("") - jsonBase.SetAPIVersion("") - return data, err -} - -// Returns the API version of the go object, or an error if it's not a -// pointer or is unregistered. -func objAPIVersionAndName(obj interface{}) (apiVersion, name string, err error) { - v, err := enforcePtr(obj) - if err != nil { - return "", "", err - } - t := v.Type() - if version, ok := typeToVersion[t]; !ok { - return "", "", fmt.Errorf("Unregistered type: %v", t) - } else { - return version, t.Name(), nil - } -} - -// maybeExternalize converts obj to an external object if it isn't one already. -// obj must be a pointer. -func maybeExternalize(obj interface{}) (interface{}, error) { - version, _, err := objAPIVersionAndName(obj) - if err != nil { - return nil, err - } - if version != "" { - // Object is already of an external versioned type. - return obj, nil - } - return externalize(obj) -} - -// maybeCopy copies obj if it is not a pointer, to get a settable/addressable -// object. Guaranteed to return a pointer. -func maybeCopy(obj interface{}) interface{} { - v := reflect.ValueOf(obj) - if v.Kind() == reflect.Ptr { - return obj - } - v2 := reflect.New(v.Type()) - v2.Elem().Set(v) - return v2.Interface() -} - -// prepareEncode sets the APIVersion and Kind fields to match the go type in obj. -// Returns an error if the (version, name) pair isn't registered for the type or -// if the type is an internal, non-versioned object. -func prepareEncode(obj interface{}) (JSONBaseInterface, error) { - version, name, err := objAPIVersionAndName(obj) - if err != nil { - return nil, err - } - if version == "" { - return nil, fmt.Errorf("No version for '%v' (%#v); extremely inadvisable to write it in wire format.", name, obj) - } - jsonBase, err := FindJSONBase(obj) - if err != nil { - return nil, err - } - knownTypes, found := versionMap[version] - if !found { - return nil, fmt.Errorf("struct %s, %s won't be unmarshalable because it's not in known versions", version, name) - } - if _, contains := knownTypes[name]; !contains { - return nil, fmt.Errorf("struct %s, %s won't be unmarshalable because it's not in knownTypes", version, name) - } - jsonBase.SetAPIVersion(version) - jsonBase.SetKind(name) - return jsonBase, nil + return conversionScheme.Encode(obj) } // Ensures that obj is a pointer of some sort. Returns a reflect.Value of the @@ -359,35 +237,7 @@ func VersionAndKind(data []byte) (version, kind string, err error) { // 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) { - version, kind, err := VersionAndKind(data) - if err != nil { - return nil, err - } - if version == "" { - return nil, fmt.Errorf("API Version not set in '%s'", string(data)) - } - obj, err := New(version, kind) - if err != nil { - return nil, fmt.Errorf("Unable to create new object of type ('%s', '%s')", version, kind) - } - // yaml is a superset of json, so we use it to decode here. That way, - // we understand both. - err = yaml.Unmarshal(data, obj) - if err != nil { - return nil, err - } - obj, err = internalize(obj) - if err != nil { - return nil, err - } - jsonBase, err := FindJSONBase(obj) - if err != nil { - return nil, err - } - // Don't leave these set. Type and version info is deducible from go's type. - jsonBase.SetKind("") - jsonBase.SetAPIVersion("") - return obj, nil + return conversionScheme.Decode(data) } // DecodeInto parses a YAML or JSON string and stores it in obj. Returns an error @@ -396,102 +246,5 @@ func Decode(data []byte) (interface{}, error) { // 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 { - dataVersion, dataKind, err := VersionAndKind(data) - if err != nil { - return err - } - objVersion, objKind, err := objAPIVersionAndName(obj) - if err != nil { - return err - } - if dataKind == "" { - // Assume objects with unset Kind fields are being unmarshalled into the - // correct type. - dataKind = objKind - } - if dataKind != objKind { - return fmt.Errorf("data of kind '%v', obj of type '%v'", dataKind, objKind) - } - if dataVersion == "" { - // Assume objects with unset Version fields are being unmarshalled into the - // correct type. - dataVersion = objVersion - } - - if objVersion == dataVersion { - // Easy case! - err = yaml.Unmarshal(data, obj) - if err != nil { - return err - } - } else { - // TODO: look up in our map to see if we can do this dataVersion -> objVersion - // conversion. - if objVersion != "" || dataVersion != "v1beta1" { - return fmt.Errorf("Can't convert from '%v' to '%v' for type '%v'", dataVersion, objVersion, dataKind) - } - - external, err := New(dataVersion, dataKind) - if err != nil { - return fmt.Errorf("Unable to create new object of type ('%s', '%s')", dataVersion, dataKind) - } - // yaml is a superset of json, so we use it to decode here. That way, - // we understand both. - err = yaml.Unmarshal(data, external) - if err != nil { - return err - } - internal, err := internalize(external) - if err != nil { - return err - } - // Copy to the provided object. - vObj := reflect.ValueOf(obj) - vInternal := reflect.ValueOf(internal) - if !vInternal.Type().AssignableTo(vObj.Type()) { - return fmt.Errorf("%s is not assignable to %s", vInternal.Type(), vObj.Type()) - } - vObj.Elem().Set(vInternal.Elem()) - } - - jsonBase, err := FindJSONBase(obj) - if err != nil { - return err - } - // Don't leave these set. Type and version info is deducible from go's type. - jsonBase.SetKind("") - jsonBase.SetAPIVersion("") - return nil -} - -func internalize(obj interface{}) (interface{}, error) { - _, objKind, err := objAPIVersionAndName(obj) - if err != nil { - return nil, err - } - objOut, err := New("", objKind) - if err != nil { - return nil, err - } - err = theConverter.Convert(obj, objOut) - if err != nil { - return nil, err - } - return objOut, nil -} - -func externalize(obj interface{}) (interface{}, error) { - _, objKind, err := objAPIVersionAndName(obj) - if err != nil { - return nil, err - } - objOut, err := New("v1beta1", objKind) - if err != nil { - return nil, err - } - err = theConverter.Convert(obj, objOut) - if err != nil { - return nil, err - } - return objOut, nil + return conversionScheme.DecodeInto(data, obj) }