Merge lp:~jameinel/juju-core/reflect-only-facades into lp:~go-bot/juju-core/trunk

Proposed by John A Meinel
Status: Work in progress
Proposed branch: lp:~jameinel/juju-core/reflect-only-facades
Merge into: lp:~go-bot/juju-core/trunk
Diff against target: 652 lines (+316/-90)
6 files modified
rpc/reflect_test.go (+36/-20)
rpc/rpc_test.go (+208/-19)
rpc/rpcreflect/type.go (+13/-6)
rpc/rpcreflect/value.go (+33/-23)
rpc/server.go (+6/-4)
state/apiserver/root_test.go (+20/-18)
To merge this branch: bzr merge lp:~jameinel/juju-core/reflect-only-facades
Reviewer Review Type Date Requested Status
Juju Engineering Pending
Review via email: mp+218764@code.launchpad.net

Description of the change

rpc: expose concrete types

This changes the RPC package a bit so that if you are declaring
RootMethod that returns 'interface{}' it can find the concrete type that
is being returned and expose the methods on that type (instead of
exposing 0 methods from interface{}).

This is a step along the way to supporting passing versions for Facades
in the "id" slot. We currently let you pass the value, but the concrete
type that is returned defines all of the Methods that could be
available. Which means that if you add a new Method in V2 of your API,
you would suddenly have that exposed in the V1 of your API. Similarly
you couldn't *actually* ever change the interface for a give method,
because then you couldn't call it one way for V1 and a different way for
V2.

There is a fair bit more to be done before we have actual API
versioning, but this is a step along that path and it seemed cohesive
enough to review on its own.

https://codereview.appspot.com/98090043/

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

Reviewers: mp+218764_code.launchpad.net,

Message:
Please take a look.

Description:
rpc: expose concrete types

This changes the RPC package a bit so that if you are declaring
RootMethod that returns 'interface{}' it can find the concrete type that
is being returned and expose the methods on that type (instead of
exposing 0 methods from interface{}).

This is a step along the way to supporting passing versions for Facades
in the "id" slot. We currently let you pass the value, but the concrete
type that is returned defines all of the Methods that could be
available. Which means that if you add a new Method in V2 of your API,
you would suddenly have that exposed in the V1 of your API. Similarly
you couldn't *actually* ever change the interface for a give method,
because then you couldn't call it one way for V1 and a different way for
V2.

There is a fair bit more to be done before we have actual API
versioning, but this is a step along that path and it seemed cohesive
enough to review on its own.

https://code.launchpad.net/~jameinel/juju-core/reflect-only-facades/+merge/218764

(do not edit description out of merge proposal)

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

Affected files (+290, -61 lines):
   A [revision details]
   M rpc/reflect_test.go
   M rpc/rpc_test.go
   M rpc/rpcreflect/type.go
   M rpc/rpcreflect/value.go
   M rpc/server.go

Revision history for this message
Roger Peppe (rogpeppe) wrote :

I realise this is the direction you have decided to go, but before you do,
I think it is worth considering the advantages that a strictly static set
of exported methods provides. Specifically it allows the possibility of
allowing clients to introspect rpc method types without invoking the
methods, and other code to see all the methods exported, for example to
automatically generate documentation for the entire API or to generate
client entry points similarly (both of which I have done in the past).

I personally think that it is a pity we are to lose these possibilities,
when there is a reasonable versioning alternative available (we could just
include the version number in the top level method names).
On 8 May 2014 12:36, "John A Meinel" <email address hidden> wrote:

> Reviewers: mp+218764_code.launchpad.net,
>
> Message:
> Please take a look.
>
> Description:
> rpc: expose concrete types
>
> This changes the RPC package a bit so that if you are declaring
> RootMethod that returns 'interface{}' it can find the concrete type that
> is being returned and expose the methods on that type (instead of
> exposing 0 methods from interface{}).
>
> This is a step along the way to supporting passing versions for Facades
> in the "id" slot. We currently let you pass the value, but the concrete
> type that is returned defines all of the Methods that could be
> available. Which means that if you add a new Method in V2 of your API,
> you would suddenly have that exposed in the V1 of your API. Similarly
> you couldn't *actually* ever change the interface for a give method,
> because then you couldn't call it one way for V1 and a different way for
> V2.
>
> There is a fair bit more to be done before we have actual API
> versioning, but this is a step along that path and it seemed cohesive
> enough to review on its own.
>
>
> https://code.launchpad.net/~jameinel/juju-core/reflect-only-facades/+merge/218764
>
> (do not edit description out of merge proposal)
>
>
> Please review this at https://codereview.appspot.com/98090043/
>
> Affected files (+290, -61 lines):
> A [revision details]
> M rpc/reflect_test.go
> M rpc/rpc_test.go
> M rpc/rpcreflect/type.go
> M rpc/rpcreflect/value.go
> M rpc/server.go
>
>
>
> --
>
> https://code.launchpad.net/~jameinel/juju-core/reflect-only-facades/+merge/218764
> Your team juju hackers is requested to review the proposed merge of
> lp:~jameinel/juju-core/reflect-only-facades into lp:juju-core.
>

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

LGTM, thanks

https://codereview.appspot.com/98090043/diff/1/rpc/reflect_test.go
File rpc/reflect_test.go (right):

https://codereview.appspot.com/98090043/diff/1/rpc/reflect_test.go#newcode62
rpc/reflect_test.go:62: // Is this worth specifying?
It's a bit of a mouthful, but I think so.

https://codereview.appspot.com/98090043/diff/1/rpc/rpc_test.go
File rpc/rpc_test.go (right):

https://codereview.appspot.com/98090043/diff/1/rpc/rpc_test.go#newcode378
rpc/rpc_test.go:378: // to "a99" since that is what most of the
SimpleMethods code expects
<gentle twitch>

I've made this choice often enough myself, but rarely felt wholly
comfortable with it in retrospect. It's probably OK, I guess :).

https://codereview.appspot.com/98090043/diff/1/rpc/rpcreflect/type.go
File rpc/rpcreflect/type.go (right):

https://codereview.appspot.com/98090043/diff/1/rpc/rpcreflect/type.go#newcode161
rpc/rpcreflect/type.go:161: // that only embeds the interface you want.)
That does indeed work for me, but you don't have to do it here if it
complicates this CL.

https://codereview.appspot.com/98090043/

Revision history for this message
John A Meinel (jameinel) wrote :
Download full text (3.4 KiB)

FWIW I plan on going further to introduce a runtime registry so that as
types are declared they are registered with what version is available, etc.
And while it doesn't let you use static introspection, I think it provides
the same dynamic introspection.
I think it also allows for less coupling and repeating yourself. (Types can
register themselves in an init() hook rather that having to have a concrete
root type that has lots of:
func (r *srvRoot) Machiner(id string) (*machine.MachinerAPI, error) {
if id != "" {
// Safeguard id for possible future use.
return nil, common.ErrBadId
}
return machine.NewMachinerAPI(r.srv.state, r.resources, r)
}

redundancy.

On Thu, May 8, 2014 at 1:54 PM, Roger Peppe <email address hidden>wrote:

> I realise this is the direction you have decided to go, but before you do,
> I think it is worth considering the advantages that a strictly static set
> of exported methods provides. Specifically it allows the possibility of
> allowing clients to introspect rpc method types without invoking the
> methods, and other code to see all the methods exported, for example to
> automatically generate documentation for the entire API or to generate
> client entry points similarly (both of which I have done in the past).
>
> I personally think that it is a pity we are to lose these possibilities,
> when there is a reasonable versioning alternative available (we could just
> include the version number in the top level method names).
> On 8 May 2014 12:36, "John A Meinel" <email address hidden> wrote:
>
> > Reviewers: mp+218764_code.launchpad.net,
> >
> > Message:
> > Please take a look.
> >
> > Description:
> > rpc: expose concrete types
> >
> > This changes the RPC package a bit so that if you are declaring
> > RootMethod that returns 'interface{}' it can find the concrete type that
> > is being returned and expose the methods on that type (instead of
> > exposing 0 methods from interface{}).
> >
> > This is a step along the way to supporting passing versions for Facades
> > in the "id" slot. We currently let you pass the value, but the concrete
> > type that is returned defines all of the Methods that could be
> > available. Which means that if you add a new Method in V2 of your API,
> > you would suddenly have that exposed in the V1 of your API. Similarly
> > you couldn't *actually* ever change the interface for a give method,
> > because then you couldn't call it one way for V1 and a different way for
> > V2.
> >
> > There is a fair bit more to be done before we have actual API
> > versioning, but this is a step along that path and it seemed cohesive
> > enough to review on its own.
> >
> >
> >
> https://code.launchpad.net/~jameinel/juju-core/reflect-only-facades/+merge/218764
> >
> > (do not edit description out of merge proposal)
> >
> >
> > Please review this at https://codereview.appspot.com/98090043/
> >
> > Affected files (+290, -61 lines):
> > A [revision details]
> > M rpc/reflect_test.go
> > M rpc/rpc_test.go
> > M rpc/rpcreflect/type.go
> > M rpc/rpcreflect/value.go
> > M rpc/server.go
> >
> >
> >
> > --
> >
> >
> https://code.launchpad.net/~jameinel/juju-core/reflect-only-facades/+merg...

Read more...

Revision history for this message
John A Meinel (jameinel) wrote :
Download full text (4.1 KiB)

I also think it is actually easier to do collapsing of versions if you
specify it somewhere rather than just in the name. Yes you could look for
V\d+ at the end of every Method in order to collapse them down into named
Facades at different versions.

On Thu, May 8, 2014 at 4:15 PM, John A Meinel <email address hidden>wrote:

> FWIW I plan on going further to introduce a runtime registry so that as
> types are declared they are registered with what version is available, etc.
> And while it doesn't let you use static introspection, I think it provides
> the same dynamic introspection.
> I think it also allows for less coupling and repeating yourself. (Types can
> register themselves in an init() hook rather that having to have a concrete
> root type that has lots of:
> func (r *srvRoot) Machiner(id string) (*machine.MachinerAPI, error) {
> if id != "" {
> // Safeguard id for possible future use.
> return nil, common.ErrBadId
> }
> return machine.NewMachinerAPI(r.srv.state, r.resources, r)
> }
>
> redundancy.
>
>
>
> On Thu, May 8, 2014 at 1:54 PM, Roger Peppe <<email address hidden>
> >wrote:
>
> > I realise this is the direction you have decided to go, but before you
> do,
> > I think it is worth considering the advantages that a strictly static set
> > of exported methods provides. Specifically it allows the possibility of
> > allowing clients to introspect rpc method types without invoking the
> > methods, and other code to see all the methods exported, for example to
> > automatically generate documentation for the entire API or to generate
> > client entry points similarly (both of which I have done in the past).
> >
> > I personally think that it is a pity we are to lose these possibilities,
> > when there is a reasonable versioning alternative available (we could
> just
> > include the version number in the top level method names).
> > On 8 May 2014 12:36, "John A Meinel" <email address hidden> wrote:
> >
> > > Reviewers: mp+218764_code.launchpad.net,
> > >
> > > Message:
> > > Please take a look.
> > >
> > > Description:
> > > rpc: expose concrete types
> > >
> > > This changes the RPC package a bit so that if you are declaring
> > > RootMethod that returns 'interface{}' it can find the concrete type
> that
> > > is being returned and expose the methods on that type (instead of
> > > exposing 0 methods from interface{}).
> > >
> > > This is a step along the way to supporting passing versions for Facades
> > > in the "id" slot. We currently let you pass the value, but the concrete
> > > type that is returned defines all of the Methods that could be
> > > available. Which means that if you add a new Method in V2 of your API,
> > > you would suddenly have that exposed in the V1 of your API. Similarly
> > > you couldn't *actually* ever change the interface for a give method,
> > > because then you couldn't call it one way for V1 and a different way
> for
> > > V2.
> > >
> > > There is a fair bit more to be done before we have actual API
> > > versioning, but this is a step along that path and it seemed cohesive
> > > enough to review on its own.
> > >
> > >
> > >
> >
> https://code.launchpad.net/~jameinel/juju-core/reflect-only-fac...

Read more...

Revision history for this message
Roger Peppe (rogpeppe) wrote :
Download full text (4.2 KiB)

I am not convinced a global name space for types is a good idea. It should
be possible for two separate modules within the same program to export
entirely separate APIs without risk of clashing. I am on holiday though. I
will follow this no more!
On 8 May 2014 15:15, "John A Meinel" <email address hidden> wrote:

> FWIW I plan on going further to introduce a runtime registry so that as
> types are declared they are registered with what version is available, etc.
> And while it doesn't let you use static introspection, I think it provides
> the same dynamic introspection.
> I think it also allows for less coupling and repeating yourself. (Types can
> register themselves in an init() hook rather that having to have a concrete
> root type that has lots of:
> func (r *srvRoot) Machiner(id string) (*machine.MachinerAPI, error) {
> if id != "" {
> // Safeguard id for possible future use.
> return nil, common.ErrBadId
> }
> return machine.NewMachinerAPI(r.srv.state, r.resources, r)
> }
>
> redundancy.
>
>
>
> On Thu, May 8, 2014 at 1:54 PM, Roger Peppe <<email address hidden>
> >wrote:
>
> > I realise this is the direction you have decided to go, but before you
> do,
> > I think it is worth considering the advantages that a strictly static set
> > of exported methods provides. Specifically it allows the possibility of
> > allowing clients to introspect rpc method types without invoking the
> > methods, and other code to see all the methods exported, for example to
> > automatically generate documentation for the entire API or to generate
> > client entry points similarly (both of which I have done in the past).
> >
> > I personally think that it is a pity we are to lose these possibilities,
> > when there is a reasonable versioning alternative available (we could
> just
> > include the version number in the top level method names).
> > On 8 May 2014 12:36, "John A Meinel" <email address hidden> wrote:
> >
> > > Reviewers: mp+218764_code.launchpad.net,
> > >
> > > Message:
> > > Please take a look.
> > >
> > > Description:
> > > rpc: expose concrete types
> > >
> > > This changes the RPC package a bit so that if you are declaring
> > > RootMethod that returns 'interface{}' it can find the concrete type
> that
> > > is being returned and expose the methods on that type (instead of
> > > exposing 0 methods from interface{}).
> > >
> > > This is a step along the way to supporting passing versions for Facades
> > > in the "id" slot. We currently let you pass the value, but the concrete
> > > type that is returned defines all of the Methods that could be
> > > available. Which means that if you add a new Method in V2 of your API,
> > > you would suddenly have that exposed in the V1 of your API. Similarly
> > > you couldn't *actually* ever change the interface for a give method,
> > > because then you couldn't call it one way for V1 and a different way
> for
> > > V2.
> > >
> > > There is a fair bit more to be done before we have actual API
> > > versioning, but this is a step along that path and it seemed cohesive
> > > enough to review on its own.
> > >
> > >
> > >
> >
> https://code.launchpad.net/~jameinel/juju-core/reflect-only-facades/+merge...

Read more...

Revision history for this message
John A Meinel (jameinel) wrote :
Download full text (4.8 KiB)

The plan is to still support multiple roots, so the registry would be root
specific. So multiple potential registries and the current one gets handed
to the RPC package from the Root object.

On Fri, May 9, 2014 at 12:18 AM, Roger Peppe <email address hidden>wrote:

> I am not convinced a global name space for types is a good idea. It should
> be possible for two separate modules within the same program to export
> entirely separate APIs without risk of clashing. I am on holiday though. I
> will follow this no more!
> On 8 May 2014 15:15, "John A Meinel" <email address hidden> wrote:
>
> > FWIW I plan on going further to introduce a runtime registry so that as
> > types are declared they are registered with what version is available,
> etc.
> > And while it doesn't let you use static introspection, I think it
> provides
> > the same dynamic introspection.
> > I think it also allows for less coupling and repeating yourself. (Types
> can
> > register themselves in an init() hook rather that having to have a
> concrete
> > root type that has lots of:
> > func (r *srvRoot) Machiner(id string) (*machine.MachinerAPI, error) {
> > if id != "" {
> > // Safeguard id for possible future use.
> > return nil, common.ErrBadId
> > }
> > return machine.NewMachinerAPI(r.srv.state, r.resources, r)
> > }
> >
> > redundancy.
> >
> >
> >
> > On Thu, May 8, 2014 at 1:54 PM, Roger Peppe <<email address hidden>
> > >wrote:
> >
> > > I realise this is the direction you have decided to go, but before you
> > do,
> > > I think it is worth considering the advantages that a strictly static
> set
> > > of exported methods provides. Specifically it allows the possibility
> of
> > > allowing clients to introspect rpc method types without invoking the
> > > methods, and other code to see all the methods exported, for example to
> > > automatically generate documentation for the entire API or to generate
> > > client entry points similarly (both of which I have done in the past).
> > >
> > > I personally think that it is a pity we are to lose these
> possibilities,
> > > when there is a reasonable versioning alternative available (we could
> > just
> > > include the version number in the top level method names).
> > > On 8 May 2014 12:36, "John A Meinel" <email address hidden> wrote:
> > >
> > > > Reviewers: mp+218764_code.launchpad.net,
> > > >
> > > > Message:
> > > > Please take a look.
> > > >
> > > > Description:
> > > > rpc: expose concrete types
> > > >
> > > > This changes the RPC package a bit so that if you are declaring
> > > > RootMethod that returns 'interface{}' it can find the concrete type
> > that
> > > > is being returned and expose the methods on that type (instead of
> > > > exposing 0 methods from interface{}).
> > > >
> > > > This is a step along the way to supporting passing versions for
> Facades
> > > > in the "id" slot. We currently let you pass the value, but the
> concrete
> > > > type that is returned defines all of the Methods that could be
> > > > available. Which means that if you add a new Method in V2 of your
> API,
> > > > you would suddenly have that exposed in the V1 of your API. Similarly
> > > > you couldn't *actually* ...

Read more...

2716. By John A Meinel

We don't need the rootMethod of MethodCaller anymore

2717. By John A Meinel

remove the unused attributes of MethodCaller

2718. By John A Meinel

Remove the test that would fail if we just merged reflect-only-facades

2719. By John A Meinel

don't refer to the now unused package

2720. By John A Meinel

Merge trunk to get bugfix for bug #1301353

This lets the test suite run on Trusty with juju-mongodb.

Unmerged revisions

2720. By John A Meinel

Merge trunk to get bugfix for bug #1301353

This lets the test suite run on Trusty with juju-mongodb.

2719. By John A Meinel

don't refer to the now unused package

2718. By John A Meinel

Remove the test that would fail if we just merged reflect-only-facades

2717. By John A Meinel

remove the unused attributes of MethodCaller

2716. By John A Meinel

We don't need the rootMethod of MethodCaller anymore

2715. By John A Meinel

We don't use the Method.ObjType anymore, so we can get rid of it.

2714. By John A Meinel

Start cleaning up the test suite.

2713. By John A Meinel

tweak the spelling for unknown versions

2712. By John A Meinel

MethodCaller.Call doesn't need the Id anymore.

We've already resolved the concrete object, cache it and use it
rather than looking it up a second time.

2711. By John A Meinel

Since now methods may exist on different versions of the Interface, we need to put more detail into the error message.

For backwards compatibility if id = "" we omit that portion.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'rpc/reflect_test.go'
2--- rpc/reflect_test.go 2014-03-13 07:54:56 +0000
3+++ rpc/reflect_test.go 2014-05-12 09:51:08 +0000
4@@ -11,6 +11,7 @@
5
6 "launchpad.net/juju-core/rpc/rpcreflect"
7 "launchpad.net/juju-core/testing/testbase"
8+ "launchpad.net/juju-core/utils/set"
9 )
10
11 // We test rpcreflect in this package, so that the
12@@ -23,28 +24,43 @@
13 var _ = gc.Suite(&reflectSuite{})
14
15 func (*reflectSuite) TestTypeOf(c *gc.C) {
16- rtype := rpcreflect.TypeOf(reflect.TypeOf(&Root{}))
17+ root := SimpleRoot()
18+ rtype := rpcreflect.TypeOf(reflect.TypeOf(root))
19 c.Assert(rtype.DiscardedMethods(), gc.DeepEquals, []string{
20 "Discard1",
21 "Discard2",
22 "Discard3",
23 })
24- expect := map[string]reflect.Type{
25- "CallbackMethods": reflect.TypeOf(&CallbackMethods{}),
26- "ChangeAPIMethods": reflect.TypeOf(&ChangeAPIMethods{}),
27- "DelayedMethods": reflect.TypeOf(&DelayedMethods{}),
28- "ErrorMethods": reflect.TypeOf(&ErrorMethods{}),
29- "InterfaceMethods": reflect.TypeOf((*InterfaceMethods)(nil)).Elem(),
30- "SimpleMethods": reflect.TypeOf(&SimpleMethods{}),
31- }
32- c.Assert(rtype.MethodNames(), gc.HasLen, len(expect))
33- for name, expectGoType := range expect {
34- m, err := rtype.Method(name)
35+ expect := []struct {
36+ Name string
37+ Id string
38+ GoType reflect.Type
39+ }{
40+ {"CallbackMethods", "", reflect.TypeOf(&CallbackMethods{})},
41+ {"ChangeAPIMethods", "", reflect.TypeOf(&ChangeAPIMethods{})},
42+ {"DelayedMethods", "", reflect.TypeOf(&DelayedMethods{})},
43+ {"ErrorMethods", "", reflect.TypeOf(&ErrorMethods{})},
44+ {"InterfaceMethods", "a99", reflect.TypeOf((*InterfaceMethods)(nil)).Elem()},
45+ {"SimpleMethods", "a99", reflect.TypeOf(&SimpleMethods{})},
46+ {"VariableMethods", "1", reflect.TypeOf(&VariableMethods1{})},
47+ {"VariableMethods", "2", reflect.TypeOf(&VariableMethods2{})},
48+ {"VariableMethods", "interface", reflect.TypeOf(&SimpleMethods{})},
49+ {"VariableMethods", "restricted", reflect.TypeOf(RestrictedMethods{})},
50+ }
51+ var uniqueNames set.Strings
52+ for _, exp := range expect {
53+ uniqueNames.Add(exp.Name)
54+ }
55+ c.Assert(rtype.MethodNames(), gc.DeepEquals, uniqueNames.SortedValues())
56+ for _, exp := range expect {
57+ m, err := rtype.Method(exp.Name)
58 c.Assert(err, gc.IsNil)
59 c.Assert(m, gc.NotNil)
60 c.Assert(m.Call, gc.NotNil)
61- c.Assert(m.ObjType, gc.Equals, rpcreflect.ObjTypeOf(expectGoType))
62- c.Assert(m.ObjType.GoType(), gc.Equals, expectGoType)
63+ obj, err := m.Call(reflect.ValueOf(root), exp.Id)
64+ c.Check(obj.Type(), gc.Equals, exp.GoType)
65+ // Is this worth specifying?
66+ c.Check(rpcreflect.ObjTypeOf(obj.Type()), gc.Equals, rpcreflect.ObjTypeOf(exp.GoType))
67 }
68 m, err := rtype.Method("not found")
69 c.Assert(err, gc.Equals, rpcreflect.ErrMethodNotFound)
70@@ -97,7 +113,7 @@
71 func (*reflectSuite) TestValueOf(c *gc.C) {
72 v := rpcreflect.ValueOf(reflect.ValueOf(nil))
73 c.Check(v.IsValid(), jc.IsFalse)
74- c.Check(func() { v.MethodCaller("foo", "bar") }, gc.PanicMatches, "MethodCaller called on invalid Value")
75+ c.Check(func() { v.MethodCaller("foo", "bar", "") }, gc.PanicMatches, "MethodCaller called on invalid Value")
76
77 root := &Root{}
78 v = rpcreflect.ValueOf(reflect.ValueOf(root))
79@@ -115,22 +131,22 @@
80 root.simple["a99"] = &SimpleMethods{root: root, id: "a99"}
81 v := rpcreflect.ValueOf(reflect.ValueOf(root))
82
83- m, err := v.MethodCaller("foo", "bar")
84+ m, err := v.MethodCaller("foo", "a99", "bar")
85 c.Assert(err, gc.ErrorMatches, `unknown object type "foo"`)
86 c.Assert(err, gc.FitsTypeOf, (*rpcreflect.CallNotImplementedError)(nil))
87 c.Assert(m, gc.DeepEquals, rpcreflect.MethodCaller{})
88
89- m, err = v.MethodCaller("SimpleMethods", "bar")
90- c.Assert(err, gc.ErrorMatches, "no such request - method SimpleMethods.bar is not implemented")
91+ m, err = v.MethodCaller("SimpleMethods", "a99", "bar")
92+ c.Assert(err, gc.ErrorMatches, `no such request - method SimpleMethods\(a99\)\.bar is not implemented`)
93 c.Assert(err, gc.FitsTypeOf, (*rpcreflect.CallNotImplementedError)(nil))
94 c.Assert(m, gc.DeepEquals, rpcreflect.MethodCaller{})
95
96- m, err = v.MethodCaller("SimpleMethods", "Call1r1e")
97+ m, err = v.MethodCaller("SimpleMethods", "a99", "Call1r1e")
98 c.Assert(err, gc.IsNil)
99 c.Assert(m.ParamsType, gc.Equals, reflect.TypeOf(stringVal{}))
100 c.Assert(m.ResultType, gc.Equals, reflect.TypeOf(stringVal{}))
101
102- ret, err := m.Call("a99", reflect.ValueOf(stringVal{"foo"}))
103+ ret, err := m.Call(reflect.ValueOf(stringVal{"foo"}))
104 c.Assert(err, gc.IsNil)
105 c.Assert(ret.Interface(), gc.Equals, stringVal{"Call1r1e ret"})
106 }
107
108=== modified file 'rpc/rpc_test.go'
109--- rpc/rpc_test.go 2014-04-17 01:26:34 +0000
110+++ rpc/rpc_test.go 2014-05-12 09:51:08 +0000
111@@ -18,6 +18,7 @@
112
113 "launchpad.net/juju-core/rpc"
114 "launchpad.net/juju-core/rpc/jsoncodec"
115+ "launchpad.net/juju-core/rpc/rpcreflect"
116 "launchpad.net/juju-core/testing/testbase"
117 )
118
119@@ -257,11 +258,59 @@
120 return stringVal{"new method result"}
121 }
122
123+type VariableMethods1 struct {
124+ sm *SimpleMethods
125+}
126+
127+func (vm *VariableMethods1) Call0r1() stringVal {
128+ return vm.sm.Call0r1()
129+}
130+
131+type VariableMethods2 struct {
132+ sm *SimpleMethods
133+}
134+
135+func (vm *VariableMethods2) Call1r1(s stringVal) stringVal {
136+ return vm.sm.Call1r1(s)
137+}
138+
139+type RestrictedMethods struct {
140+ InterfaceMethods
141+}
142+
143+func (r *Root) VariableMethods(id string) (interface{}, error) {
144+ sm, err := r.SimpleMethods("a99")
145+ switch id {
146+ case "1":
147+ return &VariableMethods1{sm}, nil
148+ case "2":
149+ return &VariableMethods2{sm}, nil
150+ case "interface":
151+ var interfaced InterfaceMethods
152+ interfaced = sm
153+ return interfaced, err
154+ case "restricted":
155+ var restricted RestrictedMethods
156+ restricted.InterfaceMethods = sm
157+ return restricted, err
158+ default:
159+ return nil, &rpcreflect.CallNotImplementedError{
160+ RootMethod: "VariableMethods",
161+ Id: id,
162+ }
163+ }
164+}
165+
166+func SimpleRoot() *Root {
167+ root := &Root{
168+ simple: make(map[string]*SimpleMethods),
169+ }
170+ root.simple["a99"] = &SimpleMethods{root: root, id: "a99"}
171+ return root
172+}
173+
174 func (*rpcSuite) TestRPC(c *gc.C) {
175- root := &Root{
176- simple: make(map[string]*SimpleMethods),
177- }
178- root.simple["a99"] = &SimpleMethods{root: root, id: "a99"}
179+ root := SimpleRoot()
180 client, srvDone, clientNotifier, serverNotifier := newRPCClientServer(c, root, nil, false)
181 defer closeClient(c, client, srvDone)
182 for narg := 0; narg < 2; narg++ {
183@@ -324,13 +373,21 @@
184
185 // testErr specifies whether the call should be made to return an error.
186 testErr bool
187+
188+ // id specifies what id string to pass to the RPC, if empty we default
189+ // to "a99" since that is what most of the SimpleMethods code expects
190+ id string
191 }
192
193 // request returns the RPC request for the test call.
194 func (p testCallParams) request() rpc.Request {
195+ id := p.id
196+ if id == "" {
197+ id = "a99"
198+ }
199 return rpc.Request{
200 Type: p.entry,
201- Id: "a99",
202+ Id: id,
203 Action: callName(p.narg, p.nret, p.retErr),
204 }
205 }
206@@ -466,10 +523,7 @@
207 }
208
209 func (*rpcSuite) TestInterfaceMethods(c *gc.C) {
210- root := &Root{
211- simple: make(map[string]*SimpleMethods),
212- }
213- root.simple["a99"] = &SimpleMethods{root: root, id: "a99"}
214+ root := SimpleRoot()
215 client, srvDone, clientNotifier, serverNotifier := newRPCClientServer(c, root, nil, false)
216 defer closeClient(c, client, srvDone)
217 p := testCallParams{
218@@ -486,6 +540,133 @@
219 root.testCall(c, p)
220 p.testErr = true
221 root.testCall(c, p)
222+ // Call0r0 is defined on the underlying SimpleMethods, but is not
223+ // exposed at the InterfaceMethods level, so this call should fail with
224+ // CodeNotImplemented
225+ var r stringVal
226+ err := client.Call(rpc.Request{"InterfaceMethods", "a99", "Call0r0"}, stringVal{"arg"}, &r)
227+ c.Assert(err, gc.DeepEquals, &rpc.RequestError{
228+ Message: "no such request - method InterfaceMethods(a99).Call0r0 is not implemented",
229+ Code: rpc.CodeNotImplemented,
230+ })
231+}
232+
233+func (*rpcSuite) TestVariableMethods1(c *gc.C) {
234+ root := SimpleRoot()
235+ client, srvDone, clientNotifier, serverNotifier := newRPCClientServer(c, root, nil, false)
236+ defer closeClient(c, client, srvDone)
237+ p := testCallParams{
238+ client: client,
239+ clientNotifier: clientNotifier,
240+ serverNotifier: serverNotifier,
241+ entry: "VariableMethods",
242+ id: "1",
243+ narg: 0,
244+ nret: 1,
245+ retErr: false,
246+ testErr: false,
247+ }
248+
249+ root.testCall(c, p)
250+ // Call1r1 is exposed on id "2", but not on id "1"
251+ var r stringVal
252+ err := client.Call(rpc.Request{"VariableMethods", "1", "Call1r1"}, stringVal{"arg"}, &r)
253+ c.Assert(err, gc.DeepEquals, &rpc.RequestError{
254+ Message: "no such request - method VariableMethods(1).Call1r1 is not implemented",
255+ Code: rpc.CodeNotImplemented,
256+ })
257+}
258+
259+func (*rpcSuite) TestVariableMethods2(c *gc.C) {
260+ root := SimpleRoot()
261+ client, srvDone, clientNotifier, serverNotifier := newRPCClientServer(c, root, nil, false)
262+ defer closeClient(c, client, srvDone)
263+ p := testCallParams{
264+ client: client,
265+ clientNotifier: clientNotifier,
266+ serverNotifier: serverNotifier,
267+ entry: "VariableMethods",
268+ id: "2",
269+ narg: 1,
270+ nret: 1,
271+ retErr: false,
272+ testErr: false,
273+ }
274+
275+ root.testCall(c, p)
276+ // Call0r1 is exposed on id "1", but not on id "2"
277+ var r stringVal
278+ err := client.Call(rpc.Request{"VariableMethods", "2", "Call0r1"}, nil, &r)
279+ c.Assert(err, gc.DeepEquals, &rpc.RequestError{
280+ Message: "no such request - method VariableMethods(2).Call0r1 is not implemented",
281+ Code: rpc.CodeNotImplemented,
282+ })
283+}
284+
285+func (*rpcSuite) TestVariableMethodsInterface(c *gc.C) {
286+ root := SimpleRoot()
287+ client, srvDone, clientNotifier, serverNotifier := newRPCClientServer(c, root, nil, false)
288+ defer closeClient(c, client, srvDone)
289+ p := testCallParams{
290+ client: client,
291+ clientNotifier: clientNotifier,
292+ serverNotifier: serverNotifier,
293+ entry: "VariableMethods",
294+ id: "interface",
295+ narg: 1,
296+ nret: 1,
297+ retErr: true,
298+ testErr: false,
299+ }
300+
301+ root.testCall(c, p)
302+ // Because of interface unwrapping, someone might have thought
303+ // "interface" would only expose the methods on InterfaceMethods, but
304+ // it actually exposes all of them
305+ p.narg = 0
306+ root.testCall(c, p)
307+ p.nret = 0
308+ root.testCall(c, p)
309+}
310+
311+func (*rpcSuite) TestVariableMethodsRestricted(c *gc.C) {
312+ root := SimpleRoot()
313+ client, srvDone, clientNotifier, serverNotifier := newRPCClientServer(c, root, nil, false)
314+ defer closeClient(c, client, srvDone)
315+ p := testCallParams{
316+ client: client,
317+ clientNotifier: clientNotifier,
318+ serverNotifier: serverNotifier,
319+ entry: "VariableMethods",
320+ id: "restricted",
321+ narg: 1,
322+ nret: 1,
323+ retErr: true,
324+ testErr: false,
325+ }
326+
327+ root.testCall(c, p)
328+ // By embedding the InterfaceMethods inside a concrete
329+ // RestrictedMethods type, we actually only expose the methods defined
330+ // in InterfaceMethods
331+ var r stringVal
332+ err := client.Call(rpc.Request{"VariableMethods", "restricted", "Call0r0"}, nil, &r)
333+ c.Assert(err, gc.DeepEquals, &rpc.RequestError{
334+ Message: "no such request - method VariableMethods(restricted).Call0r0 is not implemented",
335+ Code: rpc.CodeNotImplemented,
336+ })
337+}
338+
339+func (*rpcSuite) TestVariableMethodsUnknown(c *gc.C) {
340+ root := SimpleRoot()
341+ client, srvDone, _, _ := newRPCClientServer(c, root, nil, false)
342+ defer closeClient(c, client, srvDone)
343+ var r stringVal
344+ err := client.Call(rpc.Request{"VariableMethods", "unknown", "Call0r0"}, nil, &r)
345+ c.Assert(err, gc.DeepEquals, &rpc.RequestError{
346+ Message: `unknown version "unknown" of interface "VariableMethods"`,
347+ Code: rpc.CodeNotImplemented,
348+ })
349 }
350
351 func (*rpcSuite) TestConcurrentCalls(c *gc.C) {
352@@ -566,7 +747,22 @@
353 }
354 client, srvDone, _, _ := newRPCClientServer(c, root, tfErr, false)
355 defer closeClient(c, client, srvDone)
356- err := client.Call(rpc.Request{"ErrorMethods", "", "Call"}, nil, nil)
357+ // First, we don't transform methods we can't find
358+ err := client.Call(rpc.Request{"foo", "", "bar"}, nil, nil)
359+ c.Assert(err, gc.DeepEquals, &rpc.RequestError{
360+ Message: `unknown object type "foo"`,
361+ Code: rpc.CodeNotImplemented,
362+ })
363+
364+ err = client.Call(rpc.Request{"ErrorMethods", "", "NoMethod"}, nil, nil)
365+ c.Assert(err, gc.DeepEquals, &rpc.RequestError{
366+ Message: "no such request - method ErrorMethods.NoMethod is not implemented",
367+ Code: rpc.CodeNotImplemented,
368+ })
369+
370+ // We do transform any errors that happen from calling the RootMethod
371+ // and beyond
372+ err = client.Call(rpc.Request{"ErrorMethods", "", "Call"}, nil, nil)
373 c.Assert(err, gc.DeepEquals, &rpc.RequestError{
374 Message: "transformed: message",
375 Code: "transformed: code",
376@@ -681,19 +877,16 @@
377 rpc.Request{"BadSomething", "a0", "No"},
378 `unknown object type "BadSomething"`,
379 rpc.CodeNotImplemented,
380- false,
381 )
382 testBadCall(c, client, clientNotifier, serverNotifier,
383- rpc.Request{"SimpleMethods", "xx", "No"},
384- "no such request - method SimpleMethods.No is not implemented",
385+ rpc.Request{"SimpleMethods", "a0", "No"},
386+ "no such request - method SimpleMethods(a0).No is not implemented",
387 rpc.CodeNotImplemented,
388- false,
389 )
390 testBadCall(c, client, clientNotifier, serverNotifier,
391 rpc.Request{"SimpleMethods", "xx", "Call0r0"},
392 `unknown SimpleMethods id`,
393 "",
394- true,
395 )
396 }
397
398@@ -704,7 +897,6 @@
399 req rpc.Request,
400 expectedErr string,
401 expectedErrCode string,
402- requestKnown bool,
403 ) {
404 clientNotifier.reset()
405 serverNotifier.reset()
406@@ -746,9 +938,6 @@
407 // If the request was not recognized or there was
408 // an error reading the body, body will be nil.
409 var expectBody interface{}
410- if requestKnown {
411- expectBody = struct{}{}
412- }
413 c.Assert(serverReq, gc.DeepEquals, requestEvent{
414 hdr: rpc.Header{
415 RequestId: requestId,
416
417=== modified file 'rpc/rpcreflect/type.go'
418--- rpc/rpcreflect/type.go 2014-04-02 04:59:44 +0000
419+++ rpc/rpcreflect/type.go 2014-05-12 09:51:08 +0000
420@@ -73,10 +73,6 @@
421 // the same type as the root object. The given id is passed
422 // as an argument to the method.
423 Call func(rcvr reflect.Value, id string) (reflect.Value, error)
424-
425- // Methods holds RPC object-method information about
426- // objects returned from the above call.
427- ObjType *ObjType
428 }
429
430 // TypeOf returns information on all root-level RPC methods
431@@ -153,11 +149,22 @@
432 err, _ = out[1].Interface().(error)
433 }
434 r = out[0]
435+ if r.Kind() == reflect.Interface && r.NumMethod() == 0 {
436+ // The original function returned (interface{}, error).
437+ // We want to unwrap the interface{} into its more
438+ // concrete type, so that we can get the proper methods
439+ // exposed.
440+ // TODO: jam 2014-05-08 We could change this to always
441+ // defer down to the concrete type, at the cost that to
442+ // expose a subset of a type you must always expose a
443+ // new struct type. (Though you can expose a struct
444+ // that only embeds the interface you want.)
445+ r = reflect.ValueOf(r.Interface())
446+ }
447 return
448 }
449 return &RootMethod{
450- Call: f,
451- ObjType: ObjTypeOf(t.Out(0)),
452+ Call: f,
453 }
454 }
455
456
457=== modified file 'rpc/rpcreflect/value.go'
458--- rpc/rpcreflect/value.go 2013-12-17 20:09:27 +0000
459+++ rpc/rpcreflect/value.go 2014-05-12 09:51:08 +0000
460@@ -11,15 +11,23 @@
461 // CallNotImplementedError is an error, returned an attempt to call to
462 // an unknown API method is made.
463 type CallNotImplementedError struct {
464+ RootMethod string
465+ Id string
466 Method string
467- RootMethod string
468 }
469
470 func (e *CallNotImplementedError) Error() string {
471 if e.Method == "" {
472- return fmt.Sprintf("unknown object type %q", e.RootMethod)
473- }
474- return fmt.Sprintf("no such request - method %s.%s is not implemented", e.RootMethod, e.Method)
475+ if e.Id == "" {
476+ return fmt.Sprintf("unknown object type %q", e.RootMethod)
477+ }
478+ return fmt.Sprintf("unknown version %q of interface %q", e.Id, e.RootMethod)
479+ }
480+ methodVersion := e.RootMethod
481+ if e.Id != "" {
482+ methodVersion = fmt.Sprintf("%s(%s)", e.RootMethod, e.Id)
483+ }
484+ return fmt.Sprintf("no such request - method %s.%s is not implemented", methodVersion, e.Method)
485 }
486
487 // MethodCaller knows how to call a particular RPC method.
488@@ -29,9 +37,8 @@
489 // ResultType holds the result type of the result of caling the object method.
490 ResultType reflect.Type
491
492- rootValue reflect.Value
493- rootMethod RootMethod
494- objMethod ObjMethod
495+ objMethod ObjMethod
496+ obj reflect.Value
497 }
498
499 // MethodCaller holds the value of the root of an RPC server that
500@@ -69,36 +76,39 @@
501 // It returns an error if either the root method or the object
502 // method were not found.
503 // It panics if called on the zero Value.
504-func (v Value) MethodCaller(rootMethodName, objMethodName string) (MethodCaller, error) {
505+func (v Value) MethodCaller(rootMethodName, objId, objMethodName string) (MethodCaller, error) {
506 if !v.IsValid() {
507 panic("MethodCaller called on invalid Value")
508 }
509- caller := MethodCaller{
510- rootValue: v.rootValue,
511- }
512- var err error
513- caller.rootMethod, err = v.rootType.Method(rootMethodName)
514+ rootMethod, err := v.rootType.Method(rootMethodName)
515 if err != nil {
516 return MethodCaller{}, &CallNotImplementedError{
517 RootMethod: rootMethodName,
518 }
519 }
520- caller.objMethod, err = caller.rootMethod.ObjType.Method(objMethodName)
521+ obj, err := rootMethod.Call(v.rootValue, objId)
522+ if err != nil {
523+ // TODO: better handling of CallNotImplementedError for bad
524+ // objId
525+ return MethodCaller{}, err
526+ }
527+ objType := ObjTypeOf(obj.Type())
528+ objMethod, err := objType.Method(objMethodName)
529 if err != nil {
530 return MethodCaller{}, &CallNotImplementedError{
531 RootMethod: rootMethodName,
532+ Id: objId,
533 Method: objMethodName,
534 }
535 }
536- caller.ParamsType = caller.objMethod.Params
537- caller.ResultType = caller.objMethod.Result
538- return caller, nil
539+ return MethodCaller{
540+ ParamsType: objMethod.Params,
541+ ResultType: objMethod.Result,
542+ objMethod: objMethod,
543+ obj: obj,
544+ }, nil
545 }
546
547-func (caller MethodCaller) Call(objId string, arg reflect.Value) (reflect.Value, error) {
548- obj, err := caller.rootMethod.Call(caller.rootValue, objId)
549- if err != nil {
550- return reflect.Value{}, err
551- }
552- return caller.objMethod.Call(obj, arg)
553+func (caller MethodCaller) Call(arg reflect.Value) (reflect.Value, error) {
554+ return caller.objMethod.Call(caller.obj, arg)
555 }
556
557=== modified file 'rpc/server.go'
558--- rpc/server.go 2014-03-05 19:41:34 +0000
559+++ rpc/server.go 2014-05-12 09:51:08 +0000
560@@ -373,8 +373,8 @@
561 if err := conn.readBody(nil, true); err != nil {
562 return err
563 }
564- // We don't transform the error because there
565- // may be no transformErrors function available.
566+ // We don't transform the error here. bindRequest will have
567+ // already transformed it and returned a zero req.
568 return conn.writeErrorResponse(hdr, err, startTime)
569 }
570 var argp interface{}
571@@ -462,13 +462,15 @@
572 if !rootValue.IsValid() {
573 return boundRequest{}, fmt.Errorf("no service")
574 }
575- caller, err := rootValue.MethodCaller(hdr.Request.Type, hdr.Request.Action)
576+ caller, err := rootValue.MethodCaller(hdr.Request.Type, hdr.Request.Id, hdr.Request.Action)
577 if err != nil {
578 if _, ok := err.(*rpcreflect.CallNotImplementedError); ok {
579 err = &serverError{
580 Message: err.Error(),
581 Code: CodeNotImplemented,
582 }
583+ } else {
584+ err = transformErrors(err)
585 }
586 return boundRequest{}, err
587 }
588@@ -482,7 +484,7 @@
589 // runRequest runs the given request and sends the reply.
590 func (conn *Conn) runRequest(req boundRequest, arg reflect.Value, startTime time.Time) {
591 defer conn.srvPending.Done()
592- rv, err := req.Call(req.hdr.Request.Id, arg)
593+ rv, err := req.Call(arg)
594 if err != nil {
595 err = conn.writeErrorResponse(&req.hdr, req.transformErrors(err), startTime)
596 } else {
597
598=== modified file 'state/apiserver/root_test.go'
599--- state/apiserver/root_test.go 2014-01-22 19:28:08 +0000
600+++ state/apiserver/root_test.go 2014-05-12 09:51:08 +0000
601@@ -8,7 +8,6 @@
602
603 gc "launchpad.net/gocheck"
604
605- "launchpad.net/juju-core/rpc/rpcreflect"
606 "launchpad.net/juju-core/state/apiserver"
607 "launchpad.net/juju-core/testing"
608 )
609@@ -27,23 +26,26 @@
610 "GetAuthTag",
611 }
612
613-func (*rootSuite) TestDiscardedAPIMethods(c *gc.C) {
614- t := rpcreflect.TypeOf(apiserver.RootType)
615- // We must have some root-level methods.
616- c.Assert(t.MethodNames(), gc.Not(gc.HasLen), 0)
617- c.Assert(t.DiscardedMethods(), gc.DeepEquals, allowedDiscardedMethods)
618-
619- for _, name := range t.MethodNames() {
620- m, err := t.Method(name)
621- c.Assert(err, gc.IsNil)
622- // We must have some methods on every object returned
623- // by a root-level method.
624- c.Assert(m.ObjType.MethodNames(), gc.Not(gc.HasLen), 0)
625- // We don't allow any methods that don't implement
626- // an RPC entry point.
627- c.Assert(m.ObjType.DiscardedMethods(), gc.HasLen, 0)
628- }
629-}
630+// TODO: jam 2014-05-11 reintroduce this test once we properly have types
631+// registered that can be reflected again. This was only disabled because we
632+// got rid of ObjType as an exposed attribute of RootType
633+// func (*rootSuite) TestDiscardedAPIMethods(c *gc.C) {
634+// t := rpcreflect.TypeOf(apiserver.RootType)
635+// // We must have some root-level methods.
636+// c.Assert(t.MethodNames(), gc.Not(gc.HasLen), 0)
637+// c.Assert(t.DiscardedMethods(), gc.DeepEquals, allowedDiscardedMethods)
638+//
639+// for _, name := range t.MethodNames() {
640+// m, err := t.Method(name)
641+// c.Assert(err, gc.IsNil)
642+// // We must have some methods on every object returned
643+// // by a root-level method.
644+// c.Assert(m.ObjType.MethodNames(), gc.Not(gc.HasLen), 0)
645+// // We don't allow any methods that don't implement
646+// // an RPC entry point.
647+// c.Assert(m.ObjType.DiscardedMethods(), gc.HasLen, 0)
648+// }
649+// }
650
651 func (r *rootSuite) TestPingTimeout(c *gc.C) {
652 closedc := make(chan time.Time, 1)

Subscribers

People subscribed via source and target branches

to status/vote changes: