Merge lp:~jameinel/juju-core/api-register-standard-facade into lp:~go-bot/juju-core/trunk

Proposed by John A Meinel
Status: Work in progress
Proposed branch: lp:~jameinel/juju-core/api-register-standard-facade
Merge into: lp:~go-bot/juju-core/trunk
Prerequisite: lp:~jameinel/juju-core/api-facade-registry
Diff against target: 250 lines (+209/-0)
3 files modified
state/apiserver/common/export_test.go (+9/-0)
state/apiserver/common/registry.go (+85/-0)
state/apiserver/common/registry_test.go (+115/-0)
To merge this branch: bzr merge lp:~jameinel/juju-core/api-register-standard-facade
Reviewer Review Type Date Requested Status
Juju Engineering Pending
Review via email: mp+219660@code.launchpad.net

Description of the change

state/apiserver/common: RegisterStandardFacade

So my previous patch introduced a Registry that can be used for
registering and then looking up Facades. However, almost all of our
existing code has methods like:
  func NewKeyManagerAPI(
      st *state.State,
      resources *common.Resources,
      authorizer common.Authorizer,
  ) (*KeyManagerAPI, error) {

And that has the property that it is != o
  type FacadeFactory func(
      st *state.State, resources *Resources, authorizer Authorizer, id string,
  ) (interface{}, error)

Because the return type for the latter is interface{} and the former is
a concrete type (that implements that interface, of course).

I could think of 3 ways to work around this, and this seemed the most
tasteful.

1) Rewrite all of the New*API() functions to return interface{}, and
then rewrite all of their tests to cast it back to the concrete type. I
like that New* returns a concrete type, though, so I'd rather keep that.

2) Wrap all of the calls to RegisterFacade with a bespoke func() that
just does the type currying, then all of the init() functions looked
like:

  func init() {
    common.RegisterFacade("KeyManager", 0, func(
          st *state.State, resources *Resources, authorizer Authorizer, id string,
   ) (interface{}, error) {
     if id != "" {
       return nil, common.ErrBadId
     }
     return NewKeyManagerAPI(st, resources, authorizer), nil
   })
  }

That isn't terrible, but it is a lot of boilerplate to write.

3) Use reflect to create the boilerplate functions. Then all of the
actual init lines just become:

  func init() {
      common.RegisterStandardFacade("KeyManager", 0, NewKeyManagerAPI)
  }

So at the cost of having RegisterStandardFacade have to do reflect
introspection, it gives us really nice syntax when we want to add more.
(And note that when we have multiple versions of Facades, (2) would have
to have a bespoke func() for each one.)

I could go back to (2) if people don't like the reflect magic, but I
think it does make things nicer overall.

https://codereview.appspot.com/99290043/

To post a comment you must log in.
Revision history for this message
John A Meinel (jameinel) wrote :

Reviewers: mp+219660_code.launchpad.net,

Message:
Please take a look.

Description:
state/apiserver/common: RegisterStandardFacade

So my previous patch introduced a Registry that can be used for
registering and then looking up Facades. However, almost all of our
existing code has methods like:
   func NewKeyManagerAPI(
       st *state.State,
       resources *common.Resources,
       authorizer common.Authorizer,
   ) (*KeyManagerAPI, error) {

And that has the property that it is != o
   type FacadeFactory func(
       st *state.State, resources *Resources, authorizer Authorizer, id
string,
   ) (interface{}, error)

Because the return type for the latter is interface{} and the former is
a concrete type (that implements that interface, of course).

I could think of 3 ways to work around this, and this seemed the most
tasteful.

1) Rewrite all of the New*API() functions to return interface{}, and
then rewrite all of their tests to cast it back to the concrete type. I
like that New* returns a concrete type, though, so I'd rather keep that.

2) Wrap all of the calls to RegisterFacade with a bespoke func() that
just does the type currying, then all of the init() functions looked
like:

   func init() {
     common.RegisterFacade("KeyManager", 0, func(
           st *state.State, resources *Resources, authorizer Authorizer,
id string,
    ) (interface{}, error) {
     if id != "" {
       return nil, common.ErrBadId
     }
     return NewKeyManagerAPI(st, resources, authorizer), nil
    })
   }

That isn't terrible, but it is a lot of boilerplate to write.

3) Use reflect to create the boilerplate functions. Then all of the
actual init lines just become:

   func init() {
       common.RegisterStandardFacade("KeyManager", 0, NewKeyManagerAPI)
   }

So at the cost of having RegisterStandardFacade have to do reflect
introspection, it gives us really nice syntax when we want to add more.
(And note that when we have multiple versions of Facades, (2) would have
to have a bespoke func() for each one.)

I could go back to (2) if people don't like the reflect magic, but I
think it does make things nicer overall.

https://code.launchpad.net/~jameinel/juju-core/api-register-standard-facade/+merge/219660

Requires:
https://code.launchpad.net/~jameinel/juju-core/api-facade-registry/+merge/219654

(do not edit description out of merge proposal)

Please review this at https://codereview.appspot.com/99290043/

Affected files (+211, -0 lines):
   A [revision details]
   A state/apiserver/common/export_test.go
   M state/apiserver/common/registry.go
   M state/apiserver/common/registry_test.go

Revision history for this message
William Reade (fwereade) wrote :

LGTM. The yuck of the reflection is more than balanced by the squee of
the external interface.

https://codereview.appspot.com/99290043/

2726. By John A Meinel

Merged api-facade-registry into api-register-standard-facade.

2727. By John A Meinel

Merged api-facade-registry into api-register-standard-facade.

Revision history for this message
John A Meinel (jameinel) wrote :
2728. By John A Meinel

merge api-facade-registry to get updated trunk and github.com/juju/errors

2729. By John A Meinel

Merged api-facade-registry into api-register-standard-facade.

2730. By John A Meinel

Merged api-facade-registry into api-register-standard-facade.

Unmerged revisions

2730. By John A Meinel

Merged api-facade-registry into api-register-standard-facade.

2729. By John A Meinel

Merged api-facade-registry into api-register-standard-facade.

2728. By John A Meinel

merge api-facade-registry to get updated trunk and github.com/juju/errors

2727. By John A Meinel

Merged api-facade-registry into api-register-standard-facade.

2726. By John A Meinel

Merged api-facade-registry into api-register-standard-facade.

2725. By John A Meinel

merge deletion of ServeCaller

2724. By John A Meinel

Merge up the changes and resolve the conflicts

2723. By John A Meinel

merge in just the RegisterStandardFacade helpers.

2722. By John A Meinel

merge describe-api-versions but strip things down to just having the registry.

2721. By John A Meinel

merge in rpc-version.

This gives us all of the functionality for passing Version in the RPC,
and changes the MethodCaller signature so that it can take the version
and the objId.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== added file 'state/apiserver/common/export_test.go'
--- state/apiserver/common/export_test.go 1970-01-01 00:00:00 +0000
+++ state/apiserver/common/export_test.go 2014-05-22 12:03:12 +0000
@@ -0,0 +1,9 @@
1// Copyright 2014 Canonical Ltd.
2// Licensed under the AGPLv3, see LICENCE file for details.
3
4package common
5
6var (
7 ValidateNewFacade = validateNewFacade
8 WrapNewFacade = wrapNewFacade
9)
010
=== modified file 'state/apiserver/common/registry.go'
--- state/apiserver/common/registry.go 2014-05-22 12:03:11 +0000
+++ state/apiserver/common/registry.go 2014-05-22 12:03:12 +0000
@@ -4,7 +4,9 @@
4package common4package common
55
6import (6import (
7 "fmt"
7 "reflect"8 "reflect"
9 "runtime"
810
9 "github.com/juju/errors"11 "github.com/juju/errors"
1012
@@ -31,6 +33,89 @@
31 }33 }
32}34}
3335
36// validateNewFacade ensures that the facade factory we have has the right
37// input and output parameters for being used as a NewFoo function.
38func validateNewFacade(funcValue reflect.Value) error {
39 if !funcValue.IsValid() {
40 return fmt.Errorf("cannot wrap nil")
41 }
42 if funcValue.Kind() != reflect.Func {
43 return fmt.Errorf("wrong type %q is not a function", funcValue.Kind())
44 }
45 funcType := funcValue.Type()
46 funcName := runtime.FuncForPC(funcValue.Pointer()).Name()
47 if funcType.NumIn() != 3 || funcType.NumOut() != 2 {
48 return fmt.Errorf("function %q does not take 3 parameters and return 2",
49 funcName)
50 }
51 facadeType := reflect.TypeOf((*FacadeFactory)(nil)).Elem()
52 isSame := true
53 for i := 0; i < 3; i++ {
54 if funcType.In(i) != facadeType.In(i) {
55 isSame = false
56 break
57 }
58 }
59 if funcType.Out(1) != facadeType.Out(1) {
60 isSame = false
61 }
62 if !isSame {
63 return fmt.Errorf("function %q does not have the signature func (*state.State, *common.Resources, common.Authorizer) (*Type, error)",
64 funcName)
65 }
66 return nil
67}
68
69// wrapNewFacade turns a given NewFoo(st, resources, authorizer) (*Instance, error)
70// function and wraps it into a proper FacadeFactory function.
71func wrapNewFacade(newFunc interface{}) (FacadeFactory, error) {
72 funcValue := reflect.ValueOf(newFunc)
73 err := validateNewFacade(funcValue)
74 if err != nil {
75 return nil, err
76 }
77 // So we know newFunc is a func with the right args in and out, so
78 // wrap it into a helper function that matches the FacadeFactory.
79 wrapped := func(
80 st *state.State, resources *Resources, auth Authorizer, id string,
81 ) (
82 interface{}, error,
83 ) {
84 if id != "" {
85 return nil, ErrBadId
86 }
87 // st, resources, or auth is nil, then reflect.Call dies
88 // because reflect.ValueOf(anynil) is the Zero Value.
89 // So we use &obj.Elem() which gives us a concrete Value object
90 // that can refer to nil.
91 in := []reflect.Value{
92 reflect.ValueOf(&st).Elem(),
93 reflect.ValueOf(&resources).Elem(),
94 reflect.ValueOf(&auth).Elem(),
95 }
96 out := funcValue.Call(in)
97 if out[1].Interface() != nil {
98 err := out[1].Interface().(error)
99 return nil, err
100 }
101 return out[0].Interface(), nil
102 }
103 return wrapped, nil
104}
105
106// RegisterStandardFacade registers a factory function for a normal New* style
107// function. This requires that the function has the form:
108// NewFoo(*state.State, *common.Resources, common.Authorizer) (*Type, error)
109// With that syntax, we will create a helper function that wraps calling NewFoo
110// with the right parameters, and returns the *Type correctly.
111func RegisterStandardFacade(name string, version int, newFunc interface{}) {
112 wrapped, err := wrapNewFacade(newFunc)
113 if err != nil {
114 panic(err)
115 }
116 RegisterFacade(name, version, wrapped)
117}
118
34func GetFacade(name string, version int) (FacadeFactory, error) {119func GetFacade(name string, version int) (FacadeFactory, error) {
35 f, err := Facades.Get(name, version)120 f, err := Facades.Get(name, version)
36 if err != nil {121 if err != nil {
37122
=== modified file 'state/apiserver/common/registry_test.go'
--- state/apiserver/common/registry_test.go 2014-05-22 12:03:11 +0000
+++ state/apiserver/common/registry_test.go 2014-05-22 12:03:12 +0000
@@ -11,6 +11,7 @@
11 "launchpad.net/juju-core/rpc/rpcreflect"11 "launchpad.net/juju-core/rpc/rpcreflect"
12 "launchpad.net/juju-core/state"12 "launchpad.net/juju-core/state"
13 "launchpad.net/juju-core/state/apiserver/common"13 "launchpad.net/juju-core/state/apiserver/common"
14 apiservertesting "launchpad.net/juju-core/state/apiserver/testing"
14 "launchpad.net/juju-core/testing/testbase"15 "launchpad.net/juju-core/testing/testbase"
15 "launchpad.net/juju-core/utils/registry"16 "launchpad.net/juju-core/utils/registry"
16)17)
@@ -75,6 +76,120 @@
75 gc.PanicMatches, `object "myfacade\(0\)" already registered`)76 gc.PanicMatches, `object "myfacade\(0\)" already registered`)
76}77}
7778
79func checkValidateNewFacadeFailsWith(c *gc.C, obj interface{}, errMsg string) {
80 err := common.ValidateNewFacade(reflect.ValueOf(obj))
81 c.Check(err, gc.NotNil)
82 c.Check(err, gc.ErrorMatches, errMsg)
83}
84
85func noArgs() {
86}
87
88func badCountIn(a string) (*int, error) {
89 return nil, nil
90}
91
92func badCountOut(a, b, c string) error {
93 return nil
94}
95
96func wrongIn(a, b, c string) (*int, error) {
97 return nil, nil
98}
99
100func wrongOut(*state.State, *common.Resources, common.Authorizer) (error, *int) {
101 return nil, nil
102}
103
104func validFactory(*state.State, *common.Resources, common.Authorizer) (*int, error) {
105 var i = 100
106 return &i, nil
107}
108
109func (*facadeRegistrySuite) TestValidateNewFacade(c *gc.C) {
110 checkValidateNewFacadeFailsWith(c, nil,
111 `cannot wrap nil`)
112 checkValidateNewFacadeFailsWith(c, "notafunc",
113 `wrong type "string" is not a function`)
114 checkValidateNewFacadeFailsWith(c, noArgs,
115 `function ".*noArgs" does not take 3 parameters and return 2`)
116 checkValidateNewFacadeFailsWith(c, badCountIn,
117 `function ".*badCountIn" does not take 3 parameters and return 2`)
118 checkValidateNewFacadeFailsWith(c, badCountOut,
119 `function ".*badCountOut" does not take 3 parameters and return 2`)
120 checkValidateNewFacadeFailsWith(c, wrongIn,
121 `function ".*wrongIn" does not have the signature func \(\*state.State, \*common.Resources, common.Authorizer\) \(\*Type, error\)`)
122 checkValidateNewFacadeFailsWith(c, wrongOut,
123 `function ".*wrongOut" does not have the signature func \(\*state.State, \*common.Resources, common.Authorizer\) \(\*Type, error\)`)
124 err := common.ValidateNewFacade(reflect.ValueOf(validFactory))
125 c.Assert(err, gc.IsNil)
126}
127
128func (*facadeRegistrySuite) TestWrapNewFacadeFailure(c *gc.C) {
129 _, err := common.WrapNewFacade("notafunc")
130 c.Check(err, gc.ErrorMatches, `wrong type "string" is not a function`)
131}
132
133func (*facadeRegistrySuite) TestWrapNewFacadeHandlesId(c *gc.C) {
134 wrapped, err := common.WrapNewFacade(validFactory)
135 c.Assert(err, gc.IsNil)
136 val, err := wrapped(nil, nil, nil, "badId")
137 c.Check(err, gc.ErrorMatches, "id not found")
138 c.Check(val, gc.Equals, nil)
139}
140
141func (*facadeRegistrySuite) TestWrapNewFacadeCallsFunc(c *gc.C) {
142 wrapped, err := common.WrapNewFacade(validFactory)
143 c.Assert(err, gc.IsNil)
144 val, err := wrapped(nil, nil, nil, "")
145 c.Assert(err, gc.IsNil)
146 c.Check(*(val.(*int)), gc.Equals, 100)
147}
148
149type myResult struct {
150 st *state.State
151 resources *common.Resources
152 auth common.Authorizer
153}
154
155func (*facadeRegistrySuite) TestWrapNewFacadeCallsWithRightParams(c *gc.C) {
156 authorizer := apiservertesting.FakeAuthorizer{}
157 resources := common.NewResources()
158 testFunc := func(
159 st *state.State, resources *common.Resources,
160 authorizer common.Authorizer,
161 ) (*myResult, error) {
162 return &myResult{st, resources, authorizer}, nil
163 }
164 wrapped, err := common.WrapNewFacade(testFunc)
165 val, err := wrapped(nil, resources, authorizer, "")
166 c.Assert(err, gc.IsNil)
167 asResult := val.(*myResult)
168 c.Check(asResult.st, gc.IsNil)
169 c.Check(asResult.resources, gc.Equals, resources)
170 c.Check(asResult.auth, gc.Equals, authorizer)
171}
172
173func (r *facadeRegistrySuite) TestRegisterStandardFacade(c *gc.C) {
174 r.sanitizeFacades()
175 common.RegisterStandardFacade("testing", 0, validFactory)
176 wrapped, err := common.GetFacade("testing", 0)
177 c.Assert(err, gc.IsNil)
178 val, err := wrapped(nil, nil, nil, "")
179 c.Assert(err, gc.IsNil)
180 c.Check(*(val.(*int)), gc.Equals, 100)
181}
182
183func (r *facadeRegistrySuite) TestRegisterStandardFacadePanic(c *gc.C) {
184 r.sanitizeFacades()
185 c.Assert(
186 func() { common.RegisterStandardFacade("badtest", 0, noArgs) },
187 gc.PanicMatches,
188 `function ".*noArgs" does not take 3 parameters and return 2`)
189 _, err := common.GetFacade("badtest", 0)
190 c.Assert(err, gc.FitsTypeOf, (*rpcreflect.CallNotImplementedError)(nil))
191 c.Assert(err, gc.ErrorMatches, `unknown object type "badtest"`)
192}
78func (*facadeRegistrySuite) TestDiscardedAPIMethods(c *gc.C) {193func (*facadeRegistrySuite) TestDiscardedAPIMethods(c *gc.C) {
79 allFacades := common.Facades.List()194 allFacades := common.Facades.List()
80 c.Assert(allFacades, gc.HasLen, 0)195 c.Assert(allFacades, gc.HasLen, 0)

Subscribers

People subscribed via source and target branches

to status/vote changes: