Add continuation to conversion routines

This commit is contained in:
Daniel Smith 2014-09-05 16:02:39 -07:00
parent 2978c9923e
commit 2ba6503511
3 changed files with 52 additions and 31 deletions

View File

@ -19,22 +19,20 @@ package v1beta1
import ( import (
// Alias this so it can be easily changed when we cut the next version. // Alias this so it can be easily changed when we cut the next version.
newer "github.com/GoogleCloudPlatform/kubernetes/pkg/api" newer "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/conversion"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
) )
func init() { func init() {
// Shortcut for sub-conversions. TODO: This should possibly be refactored
// such that this convert function is passed to each conversion func.
Convert := runtime.Convert
runtime.AddConversionFuncs( runtime.AddConversionFuncs(
// EnvVar's Key is deprecated in favor of Name. // EnvVar's Key is deprecated in favor of Name.
func(in *newer.EnvVar, out *EnvVar) error { func(in *newer.EnvVar, out *EnvVar, s conversion.Scope) error {
out.Value = in.Value out.Value = in.Value
out.Key = in.Name out.Key = in.Name
out.Name = in.Name out.Name = in.Name
return nil return nil
}, },
func(in *EnvVar, out *newer.EnvVar) error { func(in *EnvVar, out *newer.EnvVar, s conversion.Scope) error {
out.Value = in.Value out.Value = in.Value
if in.Name != "" { if in.Name != "" {
out.Name = in.Name out.Name = in.Name
@ -45,7 +43,7 @@ func init() {
}, },
// Path & MountType are deprecated. // Path & MountType are deprecated.
func(in *newer.VolumeMount, out *VolumeMount) error { func(in *newer.VolumeMount, out *VolumeMount, s conversion.Scope) error {
out.Name = in.Name out.Name = in.Name
out.ReadOnly = in.ReadOnly out.ReadOnly = in.ReadOnly
out.MountPath = in.MountPath out.MountPath = in.MountPath
@ -53,7 +51,7 @@ func init() {
out.MountType = "" // MountType is ignored. out.MountType = "" // MountType is ignored.
return nil return nil
}, },
func(in *VolumeMount, out *newer.VolumeMount) error { func(in *VolumeMount, out *newer.VolumeMount, s conversion.Scope) error {
out.Name = in.Name out.Name = in.Name
out.ReadOnly = in.ReadOnly out.ReadOnly = in.ReadOnly
if in.MountPath == "" { if in.MountPath == "" {
@ -65,18 +63,18 @@ func init() {
}, },
// MinionList.Items had a wrong name in v1beta1 // MinionList.Items had a wrong name in v1beta1
func(in *newer.MinionList, out *MinionList) error { func(in *newer.MinionList, out *MinionList, s conversion.Scope) error {
Convert(&in.JSONBase, &out.JSONBase) s.Convert(&in.JSONBase, &out.JSONBase, 0)
Convert(&in.Items, &out.Items) s.Convert(&in.Items, &out.Items, 0)
out.Minions = out.Items out.Minions = out.Items
return nil return nil
}, },
func(in *MinionList, out *newer.MinionList) error { func(in *MinionList, out *newer.MinionList, s conversion.Scope) error {
Convert(&in.JSONBase, &out.JSONBase) s.Convert(&in.JSONBase, &out.JSONBase, 0)
if len(in.Items) == 0 { if len(in.Items) == 0 {
Convert(&in.Minions, &out.Items) s.Convert(&in.Minions, &out.Items, 0)
} else { } else {
Convert(&in.Items, &out.Items) s.Convert(&in.Items, &out.Items, 0)
} }
return nil return nil
}, },

View File

@ -37,7 +37,7 @@ type Converter struct {
// do the conversion. // do the conversion.
funcs map[typePair]reflect.Value funcs map[typePair]reflect.Value
// If true, print helpful debugging info. Quite verbose. // If non-nil, will be called to print helpful debugging info. Quite verbose.
Debug DebugLogger Debug DebugLogger
} }
@ -48,29 +48,43 @@ func NewConverter() *Converter {
} }
} }
// Scope is passed to conversion funcs to allow them to continue an ongoing conversion.
// If multiple converters exist in the system, Scope will allow you to use the correct one
// from a conversion function--that is, the one your conversion function was called by.
type Scope interface {
// Call Convert to convert sub-objects. Note that if you call it with your own exact
// parameters, you'll run out of stack space before anything useful happens.
Convert(src, dest interface{}, flags FieldMatchingFlags) error
}
// Register registers a conversion func with the Converter. conversionFunc must take // 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 // three parameters: a pointer to the input type, a pointer to the output type, and
// return an error. // a conversion.Scope (which should be used if recursive conversion calls are desired).
// It must return an error.
// //
// Example: // Example:
// c.Register(func(in *Pod, out *v1beta1.Pod) error { ... return nil }) // c.Register(func(in *Pod, out *v1beta1.Pod, s Scope) error { ... return nil })
func (c *Converter) Register(conversionFunc interface{}) error { func (c *Converter) Register(conversionFunc interface{}) error {
fv := reflect.ValueOf(conversionFunc) fv := reflect.ValueOf(conversionFunc)
ft := fv.Type() ft := fv.Type()
if ft.Kind() != reflect.Func { if ft.Kind() != reflect.Func {
return fmt.Errorf("expected func, got: %v", ft) return fmt.Errorf("expected func, got: %v", ft)
} }
if ft.NumIn() != 2 { if ft.NumIn() != 3 {
return fmt.Errorf("expected two in params, got: %v", ft) return fmt.Errorf("expected three 'in' params, got: %v", ft)
} }
if ft.NumOut() != 1 { if ft.NumOut() != 1 {
return fmt.Errorf("expected one out param, got: %v", ft) return fmt.Errorf("expected one 'out' param, got: %v", ft)
} }
if ft.In(0).Kind() != reflect.Ptr { if ft.In(0).Kind() != reflect.Ptr {
return fmt.Errorf("expected pointer arg for in param 0, got: %v", ft) return fmt.Errorf("expected pointer arg for 'in' param 0, got: %v", ft)
} }
if ft.In(1).Kind() != reflect.Ptr { if ft.In(1).Kind() != reflect.Ptr {
return fmt.Errorf("expected pointer arg for in param 1, got: %v", ft) return fmt.Errorf("expected pointer arg for 'in' param 1, got: %v", ft)
}
scopeType := Scope(c)
if e, a := reflect.TypeOf(&scopeType).Elem(), ft.In(2); e != a {
return fmt.Errorf("expected '%v' arg for 'in' param 2, got '%v' (%v)", e, a, ft)
} }
var forErrorType error var forErrorType error
// This convolution is necessary, otherwise TypeOf picks up on the fact // This convolution is necessary, otherwise TypeOf picks up on the fact
@ -138,7 +152,8 @@ func (c *Converter) convert(sv, dv reflect.Value, flags FieldMatchingFlags) erro
if c.Debug != nil { if c.Debug != nil {
c.Debug.Logf("Calling custom conversion of '%v' to '%v'", st, dt) c.Debug.Logf("Calling custom conversion of '%v' to '%v'", st, dt)
} }
ret := fv.Call([]reflect.Value{sv.Addr(), dv.Addr()})[0].Interface() args := []reflect.Value{sv.Addr(), dv.Addr(), reflect.ValueOf(Scope(c))}
ret := fv.Call(args)[0].Interface()
// This convolution is necssary because nil interfaces won't convert // This convolution is necssary because nil interfaces won't convert
// to errors. // to errors.
if ret == nil { if ret == nil {

View File

@ -27,28 +27,30 @@ import (
func TestConverter_CallsRegisteredFunctions(t *testing.T) { func TestConverter_CallsRegisteredFunctions(t *testing.T) {
type A struct { type A struct {
Foo string Foo string
Baz int
} }
type B struct { type B struct {
Bar string Bar string
Baz int
} }
type C struct{} type C struct{}
c := NewConverter() c := NewConverter()
err := c.Register(func(in *A, out *B) error { err := c.Register(func(in *A, out *B, s Scope) error {
out.Bar = in.Foo out.Bar = in.Foo
return nil return s.Convert(&in.Baz, &out.Baz, 0)
}) })
if err != nil { if err != nil {
t.Fatalf("unexpected error %v", err) t.Fatalf("unexpected error %v", err)
} }
err = c.Register(func(in *B, out *A) error { err = c.Register(func(in *B, out *A, s Scope) error {
out.Foo = in.Bar out.Foo = in.Bar
return nil return s.Convert(&in.Baz, &out.Baz, 0)
}) })
if err != nil { if err != nil {
t.Fatalf("unexpected error %v", err) t.Fatalf("unexpected error %v", err)
} }
x := A{"hello, intrepid test reader!"} x := A{"hello, intrepid test reader!", 3}
y := B{} y := B{}
err = c.Convert(&x, &y, 0) err = c.Convert(&x, &y, 0)
@ -58,8 +60,11 @@ func TestConverter_CallsRegisteredFunctions(t *testing.T) {
if e, a := x.Foo, y.Bar; e != a { if e, a := x.Foo, y.Bar; e != a {
t.Errorf("expected %v, got %v", e, a) t.Errorf("expected %v, got %v", e, a)
} }
if e, a := x.Baz, y.Baz; e != a {
t.Errorf("expected %v, got %v", e, a)
}
z := B{"all your test are belong to us"} z := B{"all your test are belong to us", 42}
w := A{} w := A{}
err = c.Convert(&z, &w, 0) err = c.Convert(&z, &w, 0)
@ -69,8 +74,11 @@ func TestConverter_CallsRegisteredFunctions(t *testing.T) {
if e, a := z.Bar, w.Foo; e != a { if e, a := z.Bar, w.Foo; e != a {
t.Errorf("expected %v, got %v", e, a) t.Errorf("expected %v, got %v", e, a)
} }
if e, a := z.Baz, w.Baz; e != a {
t.Errorf("expected %v, got %v", e, a)
}
err = c.Register(func(in *A, out *C) error { err = c.Register(func(in *A, out *C, s Scope) error {
return fmt.Errorf("C can't store an A, silly") return fmt.Errorf("C can't store an A, silly")
}) })
if err != nil { if err != nil {