Merge lp:~jameinel/juju-core/api-rpc-reflect-version into lp:~go-bot/juju-core/trunk

Proposed by John A Meinel
Status: Work in progress
Proposed branch: lp:~jameinel/juju-core/api-rpc-reflect-version
Merge into: lp:~go-bot/juju-core/trunk
Prerequisite: lp:~jameinel/juju-core/typed-registry
Diff against target: 1306 lines (+645/-135)
12 files modified
rpc/jsoncodec/codec.go (+7/-3)
rpc/reflect_test.go (+47/-19)
rpc/registry/package_test.go (+14/-0)
rpc/registry/registry.go (+71/-0)
rpc/registry/registry_test.go (+96/-0)
rpc/rpc_test.go (+266/-47)
rpc/rpcreflect/type.go (+13/-6)
rpc/rpcreflect/value.go (+44/-19)
rpc/server.go (+60/-14)
state/apiserver/root_test.go (+20/-18)
utils/registry/registry.go (+6/-8)
utils/registry/registry_test.go (+1/-1)
To merge this branch: bzr merge lp:~jameinel/juju-core/api-rpc-reflect-version
Reviewer Review Type Date Requested Status
Juju Engineering Pending
Review via email: mp+219653@code.launchpad.net

Description of the change

rpc: support a Version field

This adds support for a Version field in RPC requests, and changes the
MethodCaller interface for how and when it looks up objects.
Most of the MethodCaller changes were already reviewed in:
 https://code.launchpad.net/~jameinel/juju-core/reflect-only-facades/+merge/218764
There are only a couple of tweaks because the lookup is now done with a
real Version instead of overriding the "id" field.

This also uses the registry.TypedNameVersion code in a sample registry that
shows one possible way that we can do a Registry with versioned lookups.
Note that the next steps don't actually make use of rpc/registry/* so
I'm willing to pull it out, or put it in as a test-suite only thing.
Alternatively it might be something tha could fit into
documentation/example sort of code.

This does disable one test that assumed you could statically iterate
over all the types and exposed methods. I introduce a similar function
later on once we have the factories registered.

One thing that was brought up before about always unfolding Interface(),
I didn't do that yet, but I still could if we feel it would be more
straightforward.

https://codereview.appspot.com/92410043/

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

Reviewers: mp+219653_code.launchpad.net,

Message:
Please take a look.

Description:
rpc: support a Version field

This adds support for a Version field in RPC requests, and changes the
MethodCaller interface for how and when it looks up objects.
Most of the MethodCaller changes were already reviewed in:

https://code.launchpad.net/~jameinel/juju-core/reflect-only-facades/+merge/218764
There are only a couple of tweaks because the lookup is now done with a
real Version instead of overriding the "id" field.

This also uses the registry.TypedNameVersion code in a sample registry
that
shows one possible way that we can do a Registry with versioned lookups.
Note that the next steps don't actually make use of rpc/registry/* so
I'm willing to pull it out, or put it in as a test-suite only thing.
Alternatively it might be something tha could fit into
documentation/example sort of code.

This does disable one test that assumed you could statically iterate
over all the types and exposed methods. I introduce a similar function
later on once we have the factories registered.

One thing that was brought up before about always unfolding Interface(),
I didn't do that yet, but I still could if we feel it would be more
straightforward.

https://code.launchpad.net/~jameinel/juju-core/api-rpc-reflect-version/+merge/219653

Requires:
https://code.launchpad.net/~jameinel/juju-core/typed-registry/+merge/219161

(do not edit description out of merge proposal)

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

Affected files (+636, -124 lines):
   A [revision details]
   M rpc/jsoncodec/codec.go
   M rpc/reflect_test.go
   A rpc/registry/package_test.go
   A rpc/registry/registry.go
   A rpc/registry/registry_test.go
   M rpc/rpc_test.go
   M rpc/rpcreflect/type.go
   M rpc/rpcreflect/value.go
   M rpc/server.go
   M state/apiserver/root_test.go

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

LGTM

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

https://codereview.appspot.com/92410043/diff/1/rpc/reflect_test.go#newcode58
rpc/reflect_test.go:58: // Is this worth specifying?
I still think it is :).

https://codereview.appspot.com/92410043/diff/1/rpc/registry/registry.go
File rpc/registry/registry.go (right):

https://codereview.appspot.com/92410043/diff/1/rpc/registry/registry.go#newcode30
rpc/registry/registry.go:30: // could just pass Factory instead of this
pointer Elem trick
Tell me about it.

https://codereview.appspot.com/92410043/diff/1/rpc/registry/registry.go#newcode50
rpc/registry/registry.go:50: ) {
/me feels quietly smug

https://codereview.appspot.com/92410043/diff/1/rpc/registry/registry.go#newcode62
rpc/registry/registry.go:62: // There was a problem, do we translate it
into CallNotImplementedError ?
I *think* this is good as it is.

https://codereview.appspot.com/92410043/diff/1/rpc/registry/registry_test.go
File rpc/registry/registry_test.go (right):

https://codereview.appspot.com/92410043/diff/1/rpc/registry/registry_test.go#newcode47
rpc/registry/registry_test.go:47: // c.Check(err, gc.ErrorMatches,
`unknown object type "facade"`)
d

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

https://codereview.appspot.com/92410043/diff/1/rpc/rpc_test.go#newcode789
rpc/rpc_test.go:789: // and beyond
I'm really appreciating the comments, but I'd love to see trailing full
stops.

https://codereview.appspot.com/92410043/

2722. By John A Meinel

address review comments.

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

Please take a look.

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

https://codereview.appspot.com/92410043/diff/1/rpc/reflect_test.go#newcode58
rpc/reflect_test.go:58: // Is this worth specifying?
On 2014/05/16 09:05:27, fwereade wrote:
> I still think it is :).

I wasn't sure because the test used to assert that rtype.ObjType matched
rpcreflect.ObjTypeOf() (so that the object type being exposed to the
world matched the object type you get by looking it up directly.)
Since we just asserted that the actual reflect.Type is identical, it
seemed a little strange to assert that ObjTypeOf(T), gc.Equals,
ObjTypeOf(T).

But I'm willing to leave it in.

Comment removed.

https://codereview.appspot.com/92410043/diff/1/rpc/registry/registry.go
File rpc/registry/registry.go (right):

https://codereview.appspot.com/92410043/diff/1/rpc/registry/registry.go#newcode50
rpc/registry/registry.go:50: ) {
On 2014/05/16 09:05:28, fwereade wrote:
> /me feels quietly smug

Having seen quite a bit of code with long params jumping around we do
have quite a few places that also do it Nate's way (possibly written by
him):

func (r *RootFromRegistry) MethodCaller(
 rootMethodName string, version int, objId, objMethodName string,
) (rcreflect.MethodCaller, error) {

I think I still prefer the consistency between args in and args out.

The only case I might switch is when the arg list is still too long
because of package names, etc. I haven't quite decided between:

func (r *RootFromRegistry) MethodCaller(
 rootMethodName string, version int,
         objId string, objMethodName string,
) (
 rpcreflect.MethodCaller, error,
) {

and

func (r *RootFromRegistry) MethodCaller(
 rootMethodName string,
         version int,
         objId string,
         objMethodName string,
) (
 rpcreflect.MethodCaller, error,
) {

(If you *have* to wrap, should you go down to one-per-line).

Anyway, I did like your suggestion, and certainly I asked looking for a
nice way to go. :)

https://codereview.appspot.com/92410043/diff/1/rpc/registry/registry.go#newcode62
rpc/registry/registry.go:62: // There was a problem, do we translate it
into CallNotImplementedError ?
On 2014/05/16 09:05:28, fwereade wrote:
> I *think* this is good as it is.

comment removed. I could see a case for mapping obvious types, like
potentially a NotFound into a CallNotImplemented.

https://codereview.appspot.com/92410043/diff/1/rpc/registry/registry_test.go
File rpc/registry/registry_test.go (right):

https://codereview.appspot.com/92410043/diff/1/rpc/registry/registry_test.go#newcode47
rpc/registry/registry_test.go:47: // c.Check(err, gc.ErrorMatches,
`unknown object type "facade"`)
On 2014/05/16 09:05:28, fwereade wrote:
> d

Done.

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

https://codereview.appspot.com/92410043/diff/1/rpc/rpc_test.go#newcode789
rpc/rpc_test.go:789: // and beyond
On 2014/05/16 09:05:28, fwereade wrote:
> I'm really appreciating the comments, but I'd love to see trailing
full stops.

I went through and audited the change so far, adding full stops where
appropriate (hopefully).

https://coderev...

Read more...

2723. By John A Meinel

add some fullstops for comments in another branch

2724. By John A Meinel

merge typed-registry, bringing it trunk and switch to github.com/juju/errors

Revision history for this message
Dimiter Naydenov (dimitern) wrote :

Few comments for clarification, otherwise looks great!

https://codereview.appspot.com/92410043/diff/20001/rpc/registry/registry.go
File rpc/registry/registry.go (right):

https://codereview.appspot.com/92410043/diff/20001/rpc/registry/registry.go#newcode20
rpc/registry/registry.go:20: func (r *RootFromRegistry) Register(name
string, version int, f Factory) {
Doc comment? Also, is panicking here a good idea?

https://codereview.appspot.com/92410043/diff/20001/rpc/registry/registry.go#newcode45
rpc/registry/registry.go:45: // objId: facadeVersion (will be translated
from a string into an int)
This comment is a bit confusing - we have version int and objId string -
is only the latter used for versioning?

https://codereview.appspot.com/92410043/diff/20001/rpc/server.go
File rpc/server.go (right):

https://codereview.appspot.com/92410043/diff/20001/rpc/server.go#newcode344
rpc/server.go:344: // version, id, methodName) triplet.
It's a quadruplet now actually :)

https://codereview.appspot.com/92410043/

2726. By John A Meinel

apply feedback from Dimiter's review.

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

Please take a look.

https://codereview.appspot.com/92410043/diff/20001/rpc/registry/registry.go
File rpc/registry/registry.go (right):

https://codereview.appspot.com/92410043/diff/20001/rpc/registry/registry.go#newcode20
rpc/registry/registry.go:20: func (r *RootFromRegistry) Register(name
string, version int, f Factory) {
On 2014/05/19 09:33:06, dimitern wrote:
> Doc comment? Also, is panicking here a good idea?

So this is example code, but panic() in stuff that happens during init()
is fine with me. Because then it panics early and not just end up with
things that aren't actually registered.

https://codereview.appspot.com/92410043/diff/20001/rpc/registry/registry.go#newcode45
rpc/registry/registry.go:45: // objId: facadeVersion (will be translated
from a string into an int)
On 2014/05/19 09:33:06, dimitern wrote:
> This comment is a bit confusing - we have version int and objId string
- is only
> the latter used for versioning?

Just out of date docstrings. It was added when we were using objId for
versioning, but now we have the actual Version field. I updated the
comments.

https://codereview.appspot.com/92410043/diff/20001/rpc/server.go
File rpc/server.go (right):

https://codereview.appspot.com/92410043/diff/20001/rpc/server.go#newcode344
rpc/server.go:344: // version, id, methodName) triplet.
On 2014/05/19 09:33:06, dimitern wrote:
> It's a quadruplet now actually :)

Done.

https://codereview.appspot.com/92410043/

Unmerged revisions

2726. By John A Meinel

apply feedback from Dimiter's review.

2724. By John A Meinel

merge typed-registry, bringing it trunk and switch to github.com/juju/errors

2723. By John A Meinel

add some fullstops for comments in another branch

2722. By John A Meinel

address review comments.

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
1=== modified file 'rpc/jsoncodec/codec.go'
2--- rpc/jsoncodec/codec.go 2014-03-05 19:41:34 +0000
3+++ rpc/jsoncodec/codec.go 2014-05-22 11:41:28 +0000
4@@ -64,6 +64,7 @@
5 type inMsg struct {
6 RequestId uint64
7 Type string
8+ Version int `json:",omitempty"`
9 Id string
10 Request string
11 Params json.RawMessage
12@@ -76,6 +77,7 @@
13 type outMsg struct {
14 RequestId uint64
15 Type string `json:",omitempty"`
16+ Version int `json:",omitempty"`
17 Id string `json:",omitempty"`
18 Request string `json:",omitempty"`
19 Params interface{} `json:",omitempty"`
20@@ -122,9 +124,10 @@
21 }
22 hdr.RequestId = c.msg.RequestId
23 hdr.Request = rpc.Request{
24- Type: c.msg.Type,
25- Id: c.msg.Id,
26- Action: c.msg.Request,
27+ Type: c.msg.Type,
28+ Version: c.msg.Version,
29+ Id: c.msg.Id,
30+ Action: c.msg.Request,
31 }
32 hdr.Error = c.msg.Error
33 hdr.ErrorCode = c.msg.ErrorCode
34@@ -184,6 +187,7 @@
35 m.RequestId = hdr.RequestId
36 m.Type = hdr.Request.Type
37 m.Id = hdr.Request.Id
38+ m.Version = hdr.Request.Version
39 m.Request = hdr.Request.Action
40 m.Error = hdr.Error
41 m.ErrorCode = hdr.ErrorCode
42
43=== modified file 'rpc/reflect_test.go'
44--- rpc/reflect_test.go 2014-05-20 04:27:02 +0000
45+++ rpc/reflect_test.go 2014-05-22 11:41:28 +0000
46@@ -11,6 +11,7 @@
47
48 "launchpad.net/juju-core/rpc/rpcreflect"
49 "launchpad.net/juju-core/testing"
50+ "launchpad.net/juju-core/utils/set"
51 )
52
53 // We test rpcreflect in this package, so that the
54@@ -23,28 +24,38 @@
55 var _ = gc.Suite(&reflectSuite{})
56
57 func (*reflectSuite) TestTypeOf(c *gc.C) {
58- rtype := rpcreflect.TypeOf(reflect.TypeOf(&Root{}))
59+ root := SimpleRoot()
60+ rtype := rpcreflect.TypeOf(reflect.TypeOf(root))
61 c.Assert(rtype.DiscardedMethods(), gc.DeepEquals, []string{
62 "Discard1",
63 "Discard2",
64 "Discard3",
65 })
66- expect := map[string]reflect.Type{
67- "CallbackMethods": reflect.TypeOf(&CallbackMethods{}),
68- "ChangeAPIMethods": reflect.TypeOf(&ChangeAPIMethods{}),
69- "DelayedMethods": reflect.TypeOf(&DelayedMethods{}),
70- "ErrorMethods": reflect.TypeOf(&ErrorMethods{}),
71- "InterfaceMethods": reflect.TypeOf((*InterfaceMethods)(nil)).Elem(),
72- "SimpleMethods": reflect.TypeOf(&SimpleMethods{}),
73- }
74- c.Assert(rtype.MethodNames(), gc.HasLen, len(expect))
75- for name, expectGoType := range expect {
76- m, err := rtype.Method(name)
77+ expect := []struct {
78+ Name string
79+ Id string
80+ GoType reflect.Type
81+ }{
82+ {"CallbackMethods", "", reflect.TypeOf(&CallbackMethods{})},
83+ {"ChangeAPIMethods", "", reflect.TypeOf(&ChangeAPIMethods{})},
84+ {"DelayedMethods", "", reflect.TypeOf(&DelayedMethods{})},
85+ {"ErrorMethods", "", reflect.TypeOf(&ErrorMethods{})},
86+ {"InterfaceMethods", "a99", reflect.TypeOf((*InterfaceMethods)(nil)).Elem()},
87+ {"SimpleMethods", "a99", reflect.TypeOf(&SimpleMethods{})},
88+ }
89+ var uniqueNames set.Strings
90+ for _, exp := range expect {
91+ uniqueNames.Add(exp.Name)
92+ }
93+ c.Assert(rtype.MethodNames(), gc.DeepEquals, uniqueNames.SortedValues())
94+ for _, exp := range expect {
95+ m, err := rtype.Method(exp.Name)
96 c.Assert(err, gc.IsNil)
97 c.Assert(m, gc.NotNil)
98 c.Assert(m.Call, gc.NotNil)
99- c.Assert(m.ObjType, gc.Equals, rpcreflect.ObjTypeOf(expectGoType))
100- c.Assert(m.ObjType.GoType(), gc.Equals, expectGoType)
101+ obj, err := m.Call(reflect.ValueOf(root), exp.Id)
102+ c.Check(obj.Type(), gc.Equals, exp.GoType)
103+ c.Check(rpcreflect.ObjTypeOf(obj.Type()), gc.Equals, rpcreflect.ObjTypeOf(exp.GoType))
104 }
105 m, err := rtype.Method("not found")
106 c.Assert(err, gc.Equals, rpcreflect.ErrMethodNotFound)
107@@ -97,7 +108,7 @@
108 func (*reflectSuite) TestValueOf(c *gc.C) {
109 v := rpcreflect.ValueOf(reflect.ValueOf(nil))
110 c.Check(v.IsValid(), jc.IsFalse)
111- c.Check(func() { v.MethodCaller("foo", "bar") }, gc.PanicMatches, "MethodCaller called on invalid Value")
112+ c.Check(func() { v.MethodCaller("foo", 0, "bar", "") }, gc.PanicMatches, "MethodCaller called on invalid Value")
113
114 root := &Root{}
115 v = rpcreflect.ValueOf(reflect.ValueOf(root))
116@@ -115,22 +126,39 @@
117 root.simple["a99"] = &SimpleMethods{root: root, id: "a99"}
118 v := rpcreflect.ValueOf(reflect.ValueOf(root))
119
120- m, err := v.MethodCaller("foo", "bar")
121+ m, err := v.MethodCaller("foo", 0, "a99", "bar")
122 c.Assert(err, gc.ErrorMatches, `unknown object type "foo"`)
123 c.Assert(err, gc.FitsTypeOf, (*rpcreflect.CallNotImplementedError)(nil))
124 c.Assert(m, gc.DeepEquals, rpcreflect.MethodCaller{})
125
126- m, err = v.MethodCaller("SimpleMethods", "bar")
127+ m, err = v.MethodCaller("SimpleMethods", 0, "a99", "bar")
128 c.Assert(err, gc.ErrorMatches, "no such request - method SimpleMethods.bar is not implemented")
129 c.Assert(err, gc.FitsTypeOf, (*rpcreflect.CallNotImplementedError)(nil))
130 c.Assert(m, gc.DeepEquals, rpcreflect.MethodCaller{})
131
132- m, err = v.MethodCaller("SimpleMethods", "Call1r1e")
133+ m, err = v.MethodCaller("SimpleMethods", 0, "a99", "Call1r1e")
134 c.Assert(err, gc.IsNil)
135 c.Assert(m.ParamsType, gc.Equals, reflect.TypeOf(stringVal{}))
136 c.Assert(m.ResultType, gc.Equals, reflect.TypeOf(stringVal{}))
137
138- ret, err := m.Call("a99", reflect.ValueOf(stringVal{"foo"}))
139+ ret, err := m.Call(reflect.ValueOf(stringVal{"foo"}))
140 c.Assert(err, gc.IsNil)
141 c.Assert(ret.Interface(), gc.Equals, stringVal{"Call1r1e ret"})
142 }
143+
144+func (*reflectSuite) TestMethodCallerRefusesVersionsNot0(c *gc.C) {
145+ root := &Root{
146+ simple: make(map[string]*SimpleMethods),
147+ }
148+ root.simple["a99"] = &SimpleMethods{root: root, id: "a99"}
149+ v := rpcreflect.ValueOf(reflect.ValueOf(root))
150+
151+ m, err := v.MethodCaller("SimpleMethods", 0, "a99", "Call1r1e")
152+ c.Assert(err, gc.IsNil)
153+ c.Assert(m.ParamsType, gc.Equals, reflect.TypeOf(stringVal{}))
154+ c.Assert(m.ResultType, gc.Equals, reflect.TypeOf(stringVal{}))
155+
156+ m, err = v.MethodCaller("SimpleMethods", 1, "a99", "Call1r1e")
157+ c.Assert(err, gc.FitsTypeOf, (*rpcreflect.CallNotImplementedError)(nil))
158+ c.Assert(err, gc.ErrorMatches, `unknown version \(1\) of interface "SimpleMethods"`)
159+}
160
161=== added directory 'rpc/registry'
162=== added file 'rpc/registry/package_test.go'
163--- rpc/registry/package_test.go 1970-01-01 00:00:00 +0000
164+++ rpc/registry/package_test.go 2014-05-22 11:41:28 +0000
165@@ -0,0 +1,14 @@
166+// Copyright 2014 Canonical Ltd.
167+// Licensed under the AGPLv3, see LICENCE file for details.
168+
169+package registry_test
170+
171+import (
172+ stdtesting "testing"
173+
174+ gc "launchpad.net/gocheck"
175+)
176+
177+func TestAll(t *stdtesting.T) {
178+ gc.TestingT(t)
179+}
180
181=== added file 'rpc/registry/registry.go'
182--- rpc/registry/registry.go 1970-01-01 00:00:00 +0000
183+++ rpc/registry/registry.go 2014-05-22 11:41:28 +0000
184@@ -0,0 +1,71 @@
185+// Copyright 2014 Canonical Ltd.
186+// Licensed under the AGPLv3, see LICENCE file for details.
187+
188+package registry
189+
190+import (
191+ "reflect"
192+
193+ "github.com/juju/errors"
194+
195+ "launchpad.net/juju-core/rpc/rpcreflect"
196+ "launchpad.net/juju-core/utils/registry"
197+)
198+
199+// RootFromRegistry is a map from (facade name, version) to a factory function that
200+// creates an object manifesting that facade.
201+type RootFromRegistry struct {
202+ registry *registry.TypedNameVersion
203+}
204+
205+// Register records a factory that will be returned from Get() when passed the
206+// specific (name, version) pair.
207+func (r *RootFromRegistry) Register(name string, version int, f Factory) {
208+ err := r.registry.Register(name, version, f)
209+ if err != nil {
210+ panic(err)
211+ }
212+}
213+
214+// NewRegistry creates a place to register facades.
215+func NewRegistry() *RootFromRegistry {
216+ // Note: 'type' is not a first class object like functions are, so we
217+ // can't just pass Factory. Instead we create an pointer to a (nil)
218+ // instance of it, and then use Elem to get the correct reflect.Type.
219+ factoryType := reflect.TypeOf((*Factory)(nil)).Elem()
220+ return &RootFromRegistry{
221+ registry: registry.NewTypedNameVersion(factoryType),
222+ }
223+}
224+
225+type Factory func() (interface{}, error)
226+
227+// MethodCaller is used by rpc.Server to place a call to a given Method on a
228+// Facade at the specific version.
229+// The parameter names match that of the rpcreflect.Value implementation, but
230+// might be better named:
231+// rootMethodName: facadeName
232+// version: facadeVersion (will be translated from a string into an int)
233+// objId: Id for the a concrete version of the facade (used for watchers)
234+// objMethodName: methodName
235+func (r *RootFromRegistry) MethodCaller(
236+ rootMethodName string, version int, objId, objMethodName string,
237+) (
238+ rpcreflect.MethodCaller, error,
239+) {
240+ factory, err := r.registry.Get(rootMethodName, version)
241+ if errors.IsNotFound(err) {
242+ return rpcreflect.MethodCaller{}, &rpcreflect.CallNotImplementedError{
243+ RootMethod: rootMethodName,
244+ Version: version,
245+ }
246+ } else if err != nil {
247+ return rpcreflect.MethodCaller{}, err
248+ }
249+ facade, err := factory.(Factory)()
250+ if err != nil {
251+ return rpcreflect.MethodCaller{}, err
252+ }
253+ return rpcreflect.MethodCallerFromValue(
254+ reflect.ValueOf(facade), rootMethodName, version, objMethodName)
255+}
256
257=== added file 'rpc/registry/registry_test.go'
258--- rpc/registry/registry_test.go 1970-01-01 00:00:00 +0000
259+++ rpc/registry/registry_test.go 2014-05-22 11:41:28 +0000
260@@ -0,0 +1,96 @@
261+// Copyright 2014 Canonical Ltd.
262+// Licensed under the AGPLv3, see LICENCE file for details.
263+
264+package registry_test
265+
266+import (
267+ "fmt"
268+ "reflect"
269+
270+ jc "github.com/juju/testing/checkers"
271+ gc "launchpad.net/gocheck"
272+
273+ "launchpad.net/juju-core/rpc/registry"
274+ "launchpad.net/juju-core/rpc/rpcreflect"
275+ "launchpad.net/juju-core/testing/testbase"
276+)
277+
278+type registrySuite struct {
279+ testbase.LoggingSuite
280+}
281+
282+var _ = gc.Suite(&registrySuite{})
283+
284+func nilFactory() (interface{}, error) {
285+ return nil, nil
286+}
287+
288+type testFacade struct {
289+ version string
290+ called bool
291+}
292+
293+type stringVal struct {
294+ value string
295+}
296+
297+func (t *testFacade) TestMethod() stringVal {
298+ t.called = true
299+ return stringVal{"called " + t.version}
300+}
301+
302+func (s *registrySuite) TestMethodCallerNoEntries(c *gc.C) {
303+ r := registry.NewRegistry()
304+ _, err := r.MethodCaller("facade", 1, "", "method")
305+ c.Check(err, gc.FitsTypeOf, (*rpcreflect.CallNotImplementedError)(nil))
306+ c.Check(err, gc.ErrorMatches, `unknown version \(1\) of interface "facade"`)
307+}
308+
309+func (s *registrySuite) TestMethodCallerUnknownVersion(c *gc.C) {
310+ r := registry.NewRegistry()
311+ r.Register("name", 0, nilFactory)
312+ _, err := r.MethodCaller("name", 1, "", "method")
313+ c.Check(err, gc.FitsTypeOf, (*rpcreflect.CallNotImplementedError)(nil))
314+ c.Check(err, gc.ErrorMatches, `unknown version \(1\) of interface "name"`)
315+}
316+
317+func (s *registrySuite) TestMethodCaller(c *gc.C) {
318+ r := registry.NewRegistry()
319+ testF := &testFacade{version: "v0"}
320+ singletonFactory := func() (interface{}, error) { return testF, nil }
321+ r.Register("test", 0, singletonFactory)
322+ mc, err := r.MethodCaller("test", 0, "", "TestMethod")
323+ c.Assert(err, gc.IsNil)
324+ result, err := mc.Call(reflect.ValueOf(stringVal{""}))
325+ c.Assert(err, gc.IsNil)
326+ c.Check(result.Interface().(stringVal).value, gc.Equals, "called v0")
327+ c.Check(testF.called, jc.IsTrue)
328+}
329+
330+func (s *registrySuite) TestMethodCallerFactoryError(c *gc.C) {
331+ r := registry.NewRegistry()
332+ r.Register("test", 0, func() (interface{}, error) {
333+ return nil, fmt.Errorf("internal factory failure")
334+ })
335+ _, err := r.MethodCaller("test", 0, "", "TestMethod")
336+ c.Check(err, gc.ErrorMatches, `internal factory failure`)
337+}
338+
339+func (s *registrySuite) TestMethodCallerMultipleVersions(c *gc.C) {
340+ r := registry.NewRegistry()
341+ testV0 := &testFacade{version: "v0"}
342+ testV1 := &testFacade{version: "v1"}
343+ r.Register("test", 0, func() (interface{}, error) {
344+ return testV0, nil
345+ })
346+ r.Register("test", 1, func() (interface{}, error) {
347+ return testV1, nil
348+ })
349+ mc, err := r.MethodCaller("test", 1, "", "TestMethod")
350+ c.Assert(err, gc.IsNil)
351+ result, err := mc.Call(reflect.ValueOf(stringVal{""}))
352+ c.Assert(err, gc.IsNil)
353+ c.Check(result.Interface().(stringVal).value, gc.Equals, "called v1")
354+ c.Check(testV0.called, jc.IsFalse)
355+ c.Check(testV1.called, jc.IsTrue)
356+}
357
358=== modified file 'rpc/rpc_test.go'
359--- rpc/rpc_test.go 2014-05-20 04:27:02 +0000
360+++ rpc/rpc_test.go 2014-05-22 11:41:28 +0000
361@@ -18,6 +18,7 @@
362
363 "launchpad.net/juju-core/rpc"
364 "launchpad.net/juju-core/rpc/jsoncodec"
365+ "launchpad.net/juju-core/rpc/rpcreflect"
366 "launchpad.net/juju-core/testing"
367 )
368
369@@ -230,7 +231,7 @@
370 return int64val{1}, nil
371 }
372 var r int64val
373- err := a.root.conn.Call(rpc.Request{"CallbackMethods", "", "Factorial"}, int64val{x.I - 1}, &r)
374+ err := a.root.conn.Call(rpc.Request{"CallbackMethods", 0, "", "Factorial"}, int64val{x.I - 1}, &r)
375 if err != nil {
376 return int64val{}, err
377 }
378@@ -257,11 +258,80 @@
379 return stringVal{"new method result"}
380 }
381
382+type VariableMethods1 struct {
383+ sm *SimpleMethods
384+}
385+
386+func (vm *VariableMethods1) Call0r1() stringVal {
387+ return vm.sm.Call0r1()
388+}
389+
390+type VariableMethods2 struct {
391+ sm *SimpleMethods
392+}
393+
394+func (vm *VariableMethods2) Call1r1(s stringVal) stringVal {
395+ return vm.sm.Call1r1(s)
396+}
397+
398+type RestrictedMethods struct {
399+ InterfaceMethods
400+}
401+
402+type CustomCaller struct {
403+ root *Root
404+}
405+
406+func (cc *CustomCaller) MethodCaller(
407+ rootMethodName string, version int, objId, objMethodName string,
408+) (
409+ rpcreflect.MethodCaller, error,
410+) {
411+ logger.Infof("got to MethodCaller: %q %d %q %q\n",
412+ rootMethodName, version, objId, objMethodName)
413+ if rootMethodName != "MultiVersion" {
414+ return rpcreflect.MethodCaller{}, &rpcreflect.CallNotImplementedError{
415+ RootMethod: rootMethodName,
416+ }
417+ }
418+ sm, err := cc.root.SimpleMethods(objId)
419+ if err != nil {
420+ return rpcreflect.MethodCaller{}, err
421+ }
422+ var facade interface{}
423+ switch version {
424+ case 0:
425+ facade = &VariableMethods1{sm}
426+ case 1:
427+ facade = &VariableMethods2{sm}
428+ case 2:
429+ var interfaced InterfaceMethods
430+ interfaced = sm
431+ facade = interfaced
432+ case 3:
433+ var restricted RestrictedMethods
434+ restricted.InterfaceMethods = sm
435+ facade = restricted
436+ default:
437+ return rpcreflect.MethodCaller{}, &rpcreflect.CallNotImplementedError{
438+ RootMethod: rootMethodName,
439+ Version: version,
440+ }
441+ }
442+ return rpcreflect.MethodCallerFromValue(
443+ reflect.ValueOf(facade), rootMethodName, version, objMethodName)
444+}
445+
446+func SimpleRoot() *Root {
447+ root := &Root{
448+ simple: make(map[string]*SimpleMethods),
449+ }
450+ root.simple["a99"] = &SimpleMethods{root: root, id: "a99"}
451+ return root
452+}
453+
454 func (*rpcSuite) TestRPC(c *gc.C) {
455- root := &Root{
456- simple: make(map[string]*SimpleMethods),
457- }
458- root.simple["a99"] = &SimpleMethods{root: root, id: "a99"}
459+ root := SimpleRoot()
460 client, srvDone, clientNotifier, serverNotifier := newRPCClientServer(c, root, nil, false)
461 defer closeClient(c, client, srvDone)
462 for narg := 0; narg < 2; narg++ {
463@@ -308,11 +378,11 @@
464 serverNotifier *notifier
465
466 // entry holds the top-level type that will be invoked
467- // (e.g. "SimpleMethods")
468+ // (e.g. "SimpleMethods").
469 entry string
470
471 // narg holds the number of arguments accepted by the
472- // call (0 or 1)
473+ // call (0 or 1).
474 narg int
475
476 // nret holds the number of values returned by the
477@@ -324,14 +394,18 @@
478
479 // testErr specifies whether the call should be made to return an error.
480 testErr bool
481+
482+ // version specifies what version of the interface to call, defaults to 0.
483+ version int
484 }
485
486 // request returns the RPC request for the test call.
487 func (p testCallParams) request() rpc.Request {
488 return rpc.Request{
489- Type: p.entry,
490- Id: "a99",
491- Action: callName(p.narg, p.nret, p.retErr),
492+ Type: p.entry,
493+ Version: p.version,
494+ Id: "a99",
495+ Action: callName(p.narg, p.nret, p.retErr),
496 }
497 }
498
499@@ -356,7 +430,10 @@
500 })
501 c.Assert(r, gc.Equals, stringVal{})
502 case p.nret > 0:
503- c.Assert(r, gc.Equals, stringVal{p.request().Action + " ret"})
504+ c.Check(r, gc.Equals, stringVal{p.request().Action + " ret"})
505+ }
506+ if !p.testErr {
507+ c.Check(err, gc.IsNil)
508 }
509
510 // Check that the call was actually made, the right
511@@ -466,10 +543,7 @@
512 }
513
514 func (*rpcSuite) TestInterfaceMethods(c *gc.C) {
515- root := &Root{
516- simple: make(map[string]*SimpleMethods),
517- }
518- root.simple["a99"] = &SimpleMethods{root: root, id: "a99"}
519+ root := SimpleRoot()
520 client, srvDone, clientNotifier, serverNotifier := newRPCClientServer(c, root, nil, false)
521 defer closeClient(c, client, srvDone)
522 p := testCallParams{
523@@ -486,6 +560,138 @@
524 root.testCall(c, p)
525 p.testErr = true
526 root.testCall(c, p)
527+ // Call0r0 is defined on the underlying SimpleMethods, but is not
528+ // exposed at the InterfaceMethods level, so this call should fail with
529+ // CodeNotImplemented.
530+ var r stringVal
531+ err := client.Call(rpc.Request{"InterfaceMethods", 0, "a99", "Call0r0"}, stringVal{"arg"}, &r)
532+ c.Assert(err, gc.DeepEquals, &rpc.RequestError{
533+ Message: "no such request - method InterfaceMethods.Call0r0 is not implemented",
534+ Code: rpc.CodeNotImplemented,
535+ })
536+}
537+
538+func (*rpcSuite) TestCustomCallerV0(c *gc.C) {
539+ root := &CustomCaller{SimpleRoot()}
540+ client, srvDone, clientNotifier, serverNotifier := newRPCClientServer(c, root, nil, false)
541+ defer closeClient(c, client, srvDone)
542+ // V0 of MultiVersion implements only VariableMethods1.Call0r1.
543+ p := testCallParams{
544+ client: client,
545+ clientNotifier: clientNotifier,
546+ serverNotifier: serverNotifier,
547+ entry: "MultiVersion",
548+ version: 0,
549+ narg: 0,
550+ nret: 1,
551+ retErr: false,
552+ testErr: false,
553+ }
554+
555+ root.root.testCall(c, p)
556+ // Call1r1 is exposed in version 1, but not in version 0.
557+ var r stringVal
558+ err := client.Call(rpc.Request{"MultiVersion", 0, "a99", "Call1r1"}, stringVal{"arg"}, &r)
559+ c.Assert(err, gc.DeepEquals, &rpc.RequestError{
560+ Message: "no such request - method MultiVersion.Call1r1 is not implemented",
561+ Code: rpc.CodeNotImplemented,
562+ })
563+}
564+
565+func (*rpcSuite) TestCustomCallerV1(c *gc.C) {
566+ root := &CustomCaller{SimpleRoot()}
567+ client, srvDone, clientNotifier, serverNotifier := newRPCClientServer(c, root, nil, false)
568+ defer closeClient(c, client, srvDone)
569+ // V1 of MultiVersion implements only VariableMethods2.Call1r1.
570+ p := testCallParams{
571+ client: client,
572+ clientNotifier: clientNotifier,
573+ serverNotifier: serverNotifier,
574+ entry: "MultiVersion",
575+ version: 1,
576+ narg: 1,
577+ nret: 1,
578+ retErr: false,
579+ testErr: false,
580+ }
581+
582+ root.root.testCall(c, p)
583+ // Call0r1 is exposed in version 0, but not in version 1.
584+ var r stringVal
585+ err := client.Call(rpc.Request{"MultiVersion", 1, "a99", "Call0r1"}, nil, &r)
586+ c.Assert(err, gc.DeepEquals, &rpc.RequestError{
587+ Message: "no such request - method MultiVersion(1).Call0r1 is not implemented",
588+ Code: rpc.CodeNotImplemented,
589+ })
590+}
591+
592+func (*rpcSuite) TestCustomCallerV2(c *gc.C) {
593+ root := &CustomCaller{SimpleRoot()}
594+ client, srvDone, clientNotifier, serverNotifier := newRPCClientServer(c, root, nil, false)
595+ defer closeClient(c, client, srvDone)
596+ p := testCallParams{
597+ client: client,
598+ clientNotifier: clientNotifier,
599+ serverNotifier: serverNotifier,
600+ entry: "MultiVersion",
601+ version: 2,
602+ narg: 1,
603+ nret: 1,
604+ retErr: true,
605+ testErr: false,
606+ }
607+
608+ root.root.testCall(c, p)
609+ // Because of interface unwrapping, someone might have thought
610+ // "interface" would only expose the methods on InterfaceMethods
611+ // (Call1r1e), but it actually exposes all of them.
612+ p.narg = 0
613+ root.root.testCall(c, p)
614+ var r stringVal
615+ err := client.Call(rpc.Request{"MultiVersion", 2, "a99", "Call0r1e"}, nil, &r)
616+ c.Assert(err, gc.IsNil)
617+ c.Check(r.Val, gc.Equals, "Call0r1e ret")
618+}
619+
620+func (*rpcSuite) TestCustomCallerV3(c *gc.C) {
621+ root := &CustomCaller{SimpleRoot()}
622+ client, srvDone, clientNotifier, serverNotifier := newRPCClientServer(c, root, nil, false)
623+ defer closeClient(c, client, srvDone)
624+ p := testCallParams{
625+ client: client,
626+ clientNotifier: clientNotifier,
627+ serverNotifier: serverNotifier,
628+ entry: "MultiVersion",
629+ version: 3,
630+ narg: 1,
631+ nret: 1,
632+ retErr: true,
633+ testErr: false,
634+ }
635+
636+ root.root.testCall(c, p)
637+ // By embedding the InterfaceMethods inside a concrete
638+ // RestrictedMethods type, we actually only expose the methods defined
639+ // in InterfaceMethods.
640+ var r stringVal
641+ err := client.Call(rpc.Request{"MultiVersion", 3, "a99", "Call0r1e"}, nil, &r)
642+ c.Assert(err, gc.DeepEquals, &rpc.RequestError{
643+ Message: `no such request - method MultiVersion(3).Call0r1e is not implemented`,
644+ Code: rpc.CodeNotImplemented,
645+ })
646+}
647+
648+func (*rpcSuite) TestCustomCallerUnknownVersion(c *gc.C) {
649+ root := &CustomCaller{SimpleRoot()}
650+ client, srvDone, _, _ := newRPCClientServer(c, root, nil, false)
651+ defer closeClient(c, client, srvDone)
652+ var r stringVal
653+ // Unknown version 5
654+ err := client.Call(rpc.Request{"MultiVersion", 5, "a99", "Call0r1"}, nil, &r)
655+ c.Assert(err, gc.DeepEquals, &rpc.RequestError{
656+ Message: `unknown version (5) of interface "MultiVersion"`,
657+ Code: rpc.CodeNotImplemented,
658+ })
659 }
660
661 func (*rpcSuite) TestConcurrentCalls(c *gc.C) {
662@@ -505,7 +711,7 @@
663 defer closeClient(c, client, srvDone)
664 call := func(id string, done chan<- struct{}) {
665 var r stringVal
666- err := client.Call(rpc.Request{"DelayedMethods", id, "Delay"}, nil, &r)
667+ err := client.Call(rpc.Request{"DelayedMethods", 0, id, "Delay"}, nil, &r)
668 c.Check(err, gc.IsNil)
669 c.Check(r.Val, gc.Equals, "return "+id)
670 done <- struct{}{}
671@@ -545,7 +751,7 @@
672 }
673 client, srvDone, _, _ := newRPCClientServer(c, root, nil, false)
674 defer closeClient(c, client, srvDone)
675- err := client.Call(rpc.Request{"ErrorMethods", "", "Call"}, nil, nil)
676+ err := client.Call(rpc.Request{"ErrorMethods", 0, "", "Call"}, nil, nil)
677 c.Assert(err, gc.ErrorMatches, `request error: message \(code\)`)
678 c.Assert(err.(rpc.ErrorCoder).ErrorCode(), gc.Equals, "code")
679 }
680@@ -566,18 +772,33 @@
681 }
682 client, srvDone, _, _ := newRPCClientServer(c, root, tfErr, false)
683 defer closeClient(c, client, srvDone)
684- err := client.Call(rpc.Request{"ErrorMethods", "", "Call"}, nil, nil)
685+ // First, we don't transform methods we can't find.
686+ err := client.Call(rpc.Request{"foo", 0, "", "bar"}, nil, nil)
687+ c.Assert(err, gc.DeepEquals, &rpc.RequestError{
688+ Message: `unknown object type "foo"`,
689+ Code: rpc.CodeNotImplemented,
690+ })
691+
692+ err = client.Call(rpc.Request{"ErrorMethods", 0, "", "NoMethod"}, nil, nil)
693+ c.Assert(err, gc.DeepEquals, &rpc.RequestError{
694+ Message: "no such request - method ErrorMethods.NoMethod is not implemented",
695+ Code: rpc.CodeNotImplemented,
696+ })
697+
698+ // We do transform any errors that happen from calling the RootMethod
699+ // and beyond.
700+ err = client.Call(rpc.Request{"ErrorMethods", 0, "", "Call"}, nil, nil)
701 c.Assert(err, gc.DeepEquals, &rpc.RequestError{
702 Message: "transformed: message",
703 Code: "transformed: code",
704 })
705
706 root.errorInst.err = nil
707- err = client.Call(rpc.Request{"ErrorMethods", "", "Call"}, nil, nil)
708+ err = client.Call(rpc.Request{"ErrorMethods", 0, "", "Call"}, nil, nil)
709 c.Assert(err, gc.IsNil)
710
711 root.errorInst = nil
712- err = client.Call(rpc.Request{"ErrorMethods", "", "Call"}, nil, nil)
713+ err = client.Call(rpc.Request{"ErrorMethods", 0, "", "Call"}, nil, nil)
714 c.Assert(err, gc.DeepEquals, &rpc.RequestError{
715 Message: "transformed: no error methods",
716 })
717@@ -600,7 +821,7 @@
718 done := make(chan struct{})
719 go func() {
720 var r stringVal
721- err := client.Call(rpc.Request{"DelayedMethods", "1", "Delay"}, nil, &r)
722+ err := client.Call(rpc.Request{"DelayedMethods", 0, "1", "Delay"}, nil, &r)
723 c.Check(err, gc.Equals, rpc.ErrShutdown)
724 done <- struct{}{}
725 }()
726@@ -635,7 +856,7 @@
727 defer closeClient(c, client, srvDone)
728 call := func(method string, arg, ret interface{}) (passedArg interface{}) {
729 root.calls = nil
730- err := client.Call(rpc.Request{"SimpleMethods", "a0", method}, arg, ret)
731+ err := client.Call(rpc.Request{"SimpleMethods", 0, "a0", method}, arg, ret)
732 c.Assert(err, gc.IsNil)
733 c.Assert(root.calls, gc.HasLen, 1)
734 info := root.calls[0]
735@@ -678,22 +899,19 @@
736 defer closeClient(c, client, srvDone)
737
738 testBadCall(c, client, clientNotifier, serverNotifier,
739- rpc.Request{"BadSomething", "a0", "No"},
740+ rpc.Request{"BadSomething", 0, "a0", "No"},
741 `unknown object type "BadSomething"`,
742 rpc.CodeNotImplemented,
743- false,
744 )
745 testBadCall(c, client, clientNotifier, serverNotifier,
746- rpc.Request{"SimpleMethods", "xx", "No"},
747+ rpc.Request{"SimpleMethods", 0, "a0", "No"},
748 "no such request - method SimpleMethods.No is not implemented",
749 rpc.CodeNotImplemented,
750- false,
751 )
752 testBadCall(c, client, clientNotifier, serverNotifier,
753- rpc.Request{"SimpleMethods", "xx", "Call0r0"},
754+ rpc.Request{"SimpleMethods", 0, "xx", "Call0r0"},
755 `unknown SimpleMethods id`,
756 "",
757- true,
758 )
759 }
760
761@@ -704,7 +922,6 @@
762 req rpc.Request,
763 expectedErr string,
764 expectedErrCode string,
765- requestKnown bool,
766 ) {
767 clientNotifier.reset()
768 serverNotifier.reset()
769@@ -746,9 +963,6 @@
770 // If the request was not recognized or there was
771 // an error reading the body, body will be nil.
772 var expectBody interface{}
773- if requestKnown {
774- expectBody = struct{}{}
775- }
776 c.Assert(serverReq, gc.DeepEquals, requestEvent{
777 hdr: rpc.Header{
778 RequestId: requestId,
779@@ -786,10 +1000,10 @@
780 }{
781 X: map[string]int{"hello": 65},
782 }
783- err := client.Call(rpc.Request{"SimpleMethods", "a0", "SliceArg"}, arg0, &ret)
784+ err := client.Call(rpc.Request{"SimpleMethods", 0, "a0", "SliceArg"}, arg0, &ret)
785 c.Assert(err, gc.ErrorMatches, `request error: json: cannot unmarshal object into Go value of type \[\]string`)
786
787- err = client.Call(rpc.Request{"SimpleMethods", "a0", "SliceArg"}, arg0, &ret)
788+ err = client.Call(rpc.Request{"SimpleMethods", 0, "a0", "SliceArg"}, arg0, &ret)
789 c.Assert(err, gc.ErrorMatches, `request error: json: cannot unmarshal object into Go value of type \[\]string`)
790
791 arg1 := struct {
792@@ -797,7 +1011,7 @@
793 }{
794 X: []string{"one"},
795 }
796- err = client.Call(rpc.Request{"SimpleMethods", "a0", "SliceArg"}, arg1, &ret)
797+ err = client.Call(rpc.Request{"SimpleMethods", 0, "a0", "SliceArg"}, arg1, &ret)
798 c.Assert(err, gc.IsNil)
799 c.Assert(ret.Val, gc.Equals, "SliceArg ret")
800 }
801@@ -806,7 +1020,7 @@
802 client, srvDone, _, _ := newRPCClientServer(c, &Root{}, nil, false)
803 err := client.Close()
804 c.Assert(err, gc.IsNil)
805- err = client.Call(rpc.Request{"Foo", "", "Bar"}, nil, nil)
806+ err = client.Call(rpc.Request{"Foo", 0, "", "Bar"}, nil, nil)
807 c.Assert(err, gc.Equals, rpc.ErrShutdown)
808 err = chanReadError(c, srvDone, "server done")
809 c.Assert(err, gc.IsNil)
810@@ -848,7 +1062,7 @@
811 clientRoot := &Root{conn: client}
812 client.Serve(clientRoot, nil)
813 var r int64val
814- err := client.Call(rpc.Request{"CallbackMethods", "", "Factorial"}, int64val{12}, &r)
815+ err := client.Call(rpc.Request{"CallbackMethods", 0, "", "Factorial"}, int64val{12}, &r)
816 c.Assert(err, gc.IsNil)
817 c.Assert(r.I, gc.Equals, int64(479001600))
818 }
819@@ -858,7 +1072,7 @@
820 client, srvDone, _, _ := newRPCClientServer(c, srvRoot, nil, true)
821 defer closeClient(c, client, srvDone)
822 var r int64val
823- err := client.Call(rpc.Request{"CallbackMethods", "", "Factorial"}, int64val{12}, &r)
824+ err := client.Call(rpc.Request{"CallbackMethods", 0, "", "Factorial"}, int64val{12}, &r)
825 c.Assert(err, gc.ErrorMatches, "request error: request error: no service")
826 }
827
828@@ -867,13 +1081,13 @@
829 client, srvDone, _, _ := newRPCClientServer(c, srvRoot, nil, true)
830 defer closeClient(c, client, srvDone)
831 var s stringVal
832- err := client.Call(rpc.Request{"NewlyAvailable", "", "NewMethod"}, nil, &s)
833+ err := client.Call(rpc.Request{"NewlyAvailable", 0, "", "NewMethod"}, nil, &s)
834 c.Assert(err, gc.ErrorMatches, `request error: unknown object type "NewlyAvailable" \(not implemented\)`)
835- err = client.Call(rpc.Request{"ChangeAPIMethods", "", "ChangeAPI"}, nil, nil)
836+ err = client.Call(rpc.Request{"ChangeAPIMethods", 0, "", "ChangeAPI"}, nil, nil)
837 c.Assert(err, gc.IsNil)
838- err = client.Call(rpc.Request{"ChangeAPIMethods", "", "ChangeAPI"}, nil, nil)
839+ err = client.Call(rpc.Request{"ChangeAPIMethods", 0, "", "ChangeAPI"}, nil, nil)
840 c.Assert(err, gc.ErrorMatches, `request error: unknown object type "ChangeAPIMethods" \(not implemented\)`)
841- err = client.Call(rpc.Request{"NewlyAvailable", "", "NewMethod"}, nil, &s)
842+ err = client.Call(rpc.Request{"NewlyAvailable", 0, "", "NewMethod"}, nil, &s)
843 c.Assert(err, gc.IsNil)
844 c.Assert(s, gc.Equals, stringVal{"new method result"})
845 }
846@@ -883,10 +1097,10 @@
847 client, srvDone, _, _ := newRPCClientServer(c, srvRoot, nil, true)
848 defer closeClient(c, client, srvDone)
849
850- err := client.Call(rpc.Request{"ChangeAPIMethods", "", "RemoveAPI"}, nil, nil)
851+ err := client.Call(rpc.Request{"ChangeAPIMethods", 0, "", "RemoveAPI"}, nil, nil)
852 c.Assert(err, gc.IsNil)
853
854- err = client.Call(rpc.Request{"ChangeAPIMethods", "", "RemoveAPI"}, nil, nil)
855+ err = client.Call(rpc.Request{"ChangeAPIMethods", 0, "", "RemoveAPI"}, nil, nil)
856 c.Assert(err, gc.ErrorMatches, "request error: no service")
857 }
858
859@@ -906,11 +1120,11 @@
860
861 result := make(chan error)
862 go func() {
863- result <- client.Call(rpc.Request{"DelayedMethods", "1", "Delay"}, nil, nil)
864+ result <- client.Call(rpc.Request{"DelayedMethods", 0, "1", "Delay"}, nil, nil)
865 }()
866 chanRead(c, ready, "method ready")
867
868- err := client.Call(rpc.Request{"ChangeAPIMethods", "", "ChangeAPI"}, nil, nil)
869+ err := client.Call(rpc.Request{"ChangeAPIMethods", 0, "", "ChangeAPI"}, nil, nil)
870 c.Assert(err, gc.IsNil)
871
872 // Ensure that not only does the request in progress complete,
873@@ -957,7 +1171,12 @@
874 role = roleBoth
875 }
876 rpcConn := rpc.NewConn(NewJSONCodec(conn, role), serverNotifier)
877- rpcConn.Serve(root, tfErr)
878+ if custroot, ok := root.(*CustomCaller); ok {
879+ rpcConn.ServeCaller(custroot, tfErr)
880+ custroot.root.conn = rpcConn
881+ } else {
882+ rpcConn.Serve(root, tfErr)
883+ }
884 if root, ok := root.(*Root); ok {
885 root.conn = rpcConn
886 }
887
888=== modified file 'rpc/rpcreflect/type.go'
889--- rpc/rpcreflect/type.go 2014-04-02 04:59:44 +0000
890+++ rpc/rpcreflect/type.go 2014-05-22 11:41:28 +0000
891@@ -73,10 +73,6 @@
892 // the same type as the root object. The given id is passed
893 // as an argument to the method.
894 Call func(rcvr reflect.Value, id string) (reflect.Value, error)
895-
896- // Methods holds RPC object-method information about
897- // objects returned from the above call.
898- ObjType *ObjType
899 }
900
901 // TypeOf returns information on all root-level RPC methods
902@@ -153,11 +149,22 @@
903 err, _ = out[1].Interface().(error)
904 }
905 r = out[0]
906+ if r.Kind() == reflect.Interface && r.NumMethod() == 0 {
907+ // The original function returned (interface{}, error).
908+ // We want to unwrap the interface{} into its more
909+ // concrete type, so that we can get the proper methods
910+ // exposed.
911+ // TODO: jam 2014-05-08 We could change this to always
912+ // defer down to the concrete type, at the cost that to
913+ // expose a subset of a type you must always expose a
914+ // new struct type. (Though you can expose a struct
915+ // that only embeds the interface you want.)
916+ r = reflect.ValueOf(r.Interface())
917+ }
918 return
919 }
920 return &RootMethod{
921- Call: f,
922- ObjType: ObjTypeOf(t.Out(0)),
923+ Call: f,
924 }
925 }
926
927
928=== modified file 'rpc/rpcreflect/value.go'
929--- rpc/rpcreflect/value.go 2013-12-17 20:09:27 +0000
930+++ rpc/rpcreflect/value.go 2014-05-22 11:41:28 +0000
931@@ -11,15 +11,23 @@
932 // CallNotImplementedError is an error, returned an attempt to call to
933 // an unknown API method is made.
934 type CallNotImplementedError struct {
935+ RootMethod string
936+ Version int
937 Method string
938- RootMethod string
939 }
940
941 func (e *CallNotImplementedError) Error() string {
942 if e.Method == "" {
943+ if e.Version != 0 {
944+ return fmt.Sprintf("unknown version (%d) of interface %q", e.Version, e.RootMethod)
945+ }
946 return fmt.Sprintf("unknown object type %q", e.RootMethod)
947 }
948- return fmt.Sprintf("no such request - method %s.%s is not implemented", e.RootMethod, e.Method)
949+ methodVersion := e.RootMethod
950+ if e.Version != 0 {
951+ methodVersion = fmt.Sprintf("%s(%d)", e.RootMethod, e.Version)
952+ }
953+ return fmt.Sprintf("no such request - method %s.%s is not implemented", methodVersion, e.Method)
954 }
955
956 // MethodCaller knows how to call a particular RPC method.
957@@ -29,9 +37,8 @@
958 // ResultType holds the result type of the result of caling the object method.
959 ResultType reflect.Type
960
961- rootValue reflect.Value
962- rootMethod RootMethod
963- objMethod ObjMethod
964+ objMethod ObjMethod
965+ obj reflect.Value
966 }
967
968 // MethodCaller holds the value of the root of an RPC server that
969@@ -69,36 +76,54 @@
970 // It returns an error if either the root method or the object
971 // method were not found.
972 // It panics if called on the zero Value.
973-func (v Value) MethodCaller(rootMethodName, objMethodName string) (MethodCaller, error) {
974+func (v Value) MethodCaller(rootMethodName string, rootVersion int, objId, objMethodName string) (MethodCaller, error) {
975 if !v.IsValid() {
976 panic("MethodCaller called on invalid Value")
977 }
978- caller := MethodCaller{
979- rootValue: v.rootValue,
980+ if rootVersion != 0 {
981+ return MethodCaller{}, &CallNotImplementedError{
982+ RootMethod: rootMethodName,
983+ Version: rootVersion,
984+ }
985 }
986 var err error
987- caller.rootMethod, err = v.rootType.Method(rootMethodName)
988+ rootMethod, err := v.rootType.Method(rootMethodName)
989 if err != nil {
990 return MethodCaller{}, &CallNotImplementedError{
991 RootMethod: rootMethodName,
992 }
993 }
994- caller.objMethod, err = caller.rootMethod.ObjType.Method(objMethodName)
995+ obj, err := rootMethod.Call(v.rootValue, objId)
996+ if err != nil {
997+ return MethodCaller{}, err
998+ }
999+ return MethodCallerFromValue(obj, rootMethodName, rootVersion, objMethodName)
1000+}
1001+
1002+// MethodCallerFromValue will reflect for ObjType and probe for the actual
1003+// method, wrapping it into a MethodCaller.
1004+func MethodCallerFromValue(
1005+ obj reflect.Value, rootMethodName string, version int, objMethodName string,
1006+) (
1007+ MethodCaller, error,
1008+) {
1009+ objType := ObjTypeOf(obj.Type())
1010+ objMethod, err := objType.Method(objMethodName)
1011 if err != nil {
1012 return MethodCaller{}, &CallNotImplementedError{
1013 RootMethod: rootMethodName,
1014+ Version: version,
1015 Method: objMethodName,
1016 }
1017 }
1018- caller.ParamsType = caller.objMethod.Params
1019- caller.ResultType = caller.objMethod.Result
1020- return caller, nil
1021+ return MethodCaller{
1022+ ParamsType: objMethod.Params,
1023+ ResultType: objMethod.Result,
1024+ objMethod: objMethod,
1025+ obj: obj,
1026+ }, nil
1027 }
1028
1029-func (caller MethodCaller) Call(objId string, arg reflect.Value) (reflect.Value, error) {
1030- obj, err := caller.rootMethod.Call(caller.rootValue, objId)
1031- if err != nil {
1032- return reflect.Value{}, err
1033- }
1034- return caller.objMethod.Call(obj, arg)
1035+func (caller MethodCaller) Call(arg reflect.Value) (reflect.Value, error) {
1036+ return caller.objMethod.Call(caller.obj, arg)
1037 }
1038
1039=== modified file 'rpc/server.go'
1040--- rpc/server.go 2014-03-05 19:41:34 +0000
1041+++ rpc/server.go 2014-05-22 11:41:28 +0000
1042@@ -69,6 +69,9 @@
1043 // Type holds the type of object to act on.
1044 Type string
1045
1046+ // Version holds the version of the interface to the Object to act on
1047+ Version int
1048+
1049 // Id holds the id of the object to act on.
1050 Id string
1051
1052@@ -107,8 +110,12 @@
1053 // mutex guards the following values.
1054 mutex sync.Mutex
1055
1056- // rootValue holds the value to use to serve RPC requests, if any.
1057- rootValue rpcreflect.Value
1058+ // rootCaller holds the value to use to serve RPC requests, if any.
1059+ rootCaller Caller
1060+
1061+ // killer is the original interface{} that was passed to
1062+ // Serve/ServeCaller if it implemented the Killer interface
1063+ killer Killer
1064
1065 // transformErrors is used to transform returned errors.
1066 transformErrors func(error) error
1067@@ -238,14 +245,44 @@
1068 // set of methods being served by the connection. This will have
1069 // no effect on calls that are currently being services.
1070 // If root is nil, the connection will serve no methods.
1071+//
1072+// root can optionally implement the Killer method. If implemented, when
1073+// the connection is closed, root.Kill() will be called.
1074 func (conn *Conn) Serve(root interface{}, transformErrors func(error) error) {
1075+ var rootCaller Caller
1076 rootValue := rpcreflect.ValueOf(reflect.ValueOf(root))
1077- if rootValue.IsValid() && transformErrors == nil {
1078+ if rootValue.IsValid() {
1079+ rootCaller = rootValue
1080+ }
1081+ conn.serve(rootCaller, root, transformErrors)
1082+}
1083+
1084+// ServeCaller serves RPC requests on the connection by invoking methods retrieved
1085+// from rootCaller. Note that it does not start the connection running, though
1086+// it may be called once the connection is already started.
1087+//
1088+// The server executes each client request by calling MethodCaller to obtain a
1089+// method to invoke. It invokes that method with the request parameters,
1090+// possibly returning some result.
1091+//
1092+// rootCaller can optionally implement the Killer method. If implemented, when
1093+// the connection is closed, rootCaller.Kill() will be called.
1094+func (conn *Conn) ServeCaller(rootCaller Caller, transformErrors func(error) error) {
1095+ conn.serve(rootCaller, rootCaller, transformErrors)
1096+}
1097+
1098+func (conn *Conn) serve(rootCaller Caller, maybeKiller interface{}, transformErrors func(error) error) {
1099+ if rootCaller != nil && transformErrors == nil {
1100 transformErrors = func(err error) error { return err }
1101 }
1102 conn.mutex.Lock()
1103 defer conn.mutex.Unlock()
1104- conn.rootValue = rootValue
1105+ conn.rootCaller = rootCaller
1106+ if killer, ok := maybeKiller.(Killer); ok {
1107+ conn.killer = killer
1108+ } else {
1109+ conn.killer = nil
1110+ }
1111 conn.transformErrors = transformErrors
1112 }
1113
1114@@ -279,10 +316,8 @@
1115 conn.closing = true
1116 // Kill server requests if appropriate. Client requests will be
1117 // terminated when the input loop finishes.
1118- if conn.rootValue.IsValid() {
1119- if killer, ok := conn.rootValue.GoValue().Interface().(Killer); ok {
1120- killer.Kill()
1121- }
1122+ if conn.killer != nil {
1123+ conn.killer.Kill()
1124 }
1125 conn.mutex.Unlock()
1126
1127@@ -305,6 +340,14 @@
1128 ErrorCode() string
1129 }
1130
1131+// Caller is a way to dynamically lookup methods from a given (rootName,
1132+// version, id, methodName) quadruplet.
1133+type Caller interface {
1134+ // MethodCaller takes a description of a method to call, and returns an
1135+ // rpcreflect.MethodCaller that can be used to call the concrete method.
1136+ MethodCaller(rootMethodName string, version int, objId, objMethodName string) (rpcreflect.MethodCaller, error)
1137+}
1138+
1139 // Killer represents a type that can be asked to abort any outstanding
1140 // requests. The Kill method should return immediately.
1141 type Killer interface {
1142@@ -373,8 +416,8 @@
1143 if err := conn.readBody(nil, true); err != nil {
1144 return err
1145 }
1146- // We don't transform the error because there
1147- // may be no transformErrors function available.
1148+ // We don't transform the error here. bindRequest will have
1149+ // already transformed it and returned a zero req.
1150 return conn.writeErrorResponse(hdr, err, startTime)
1151 }
1152 var argp interface{}
1153@@ -455,20 +498,23 @@
1154 // a boundRequest that can call those methods.
1155 func (conn *Conn) bindRequest(hdr *Header) (boundRequest, error) {
1156 conn.mutex.Lock()
1157- rootValue := conn.rootValue
1158+ rootCaller := conn.rootCaller
1159 transformErrors := conn.transformErrors
1160 conn.mutex.Unlock()
1161
1162- if !rootValue.IsValid() {
1163+ if conn.rootCaller == nil {
1164 return boundRequest{}, fmt.Errorf("no service")
1165 }
1166- caller, err := rootValue.MethodCaller(hdr.Request.Type, hdr.Request.Action)
1167+ caller, err := rootCaller.MethodCaller(
1168+ hdr.Request.Type, hdr.Request.Version, hdr.Request.Id, hdr.Request.Action)
1169 if err != nil {
1170 if _, ok := err.(*rpcreflect.CallNotImplementedError); ok {
1171 err = &serverError{
1172 Message: err.Error(),
1173 Code: CodeNotImplemented,
1174 }
1175+ } else {
1176+ err = transformErrors(err)
1177 }
1178 return boundRequest{}, err
1179 }
1180@@ -482,7 +528,7 @@
1181 // runRequest runs the given request and sends the reply.
1182 func (conn *Conn) runRequest(req boundRequest, arg reflect.Value, startTime time.Time) {
1183 defer conn.srvPending.Done()
1184- rv, err := req.Call(req.hdr.Request.Id, arg)
1185+ rv, err := req.Call(arg)
1186 if err != nil {
1187 err = conn.writeErrorResponse(&req.hdr, req.transformErrors(err), startTime)
1188 } else {
1189
1190=== modified file 'state/apiserver/root_test.go'
1191--- state/apiserver/root_test.go 2014-05-20 08:02:01 +0000
1192+++ state/apiserver/root_test.go 2014-05-22 11:41:28 +0000
1193@@ -8,7 +8,6 @@
1194
1195 gc "launchpad.net/gocheck"
1196
1197- "launchpad.net/juju-core/rpc/rpcreflect"
1198 "launchpad.net/juju-core/state/apiserver"
1199 "launchpad.net/juju-core/testing"
1200 )
1201@@ -27,23 +26,26 @@
1202 "GetAuthTag",
1203 }
1204
1205-func (*rootSuite) TestDiscardedAPIMethods(c *gc.C) {
1206- t := rpcreflect.TypeOf(apiserver.RootType)
1207- // We must have some root-level methods.
1208- c.Assert(t.MethodNames(), gc.Not(gc.HasLen), 0)
1209- c.Assert(t.DiscardedMethods(), gc.DeepEquals, allowedDiscardedMethods)
1210-
1211- for _, name := range t.MethodNames() {
1212- m, err := t.Method(name)
1213- c.Assert(err, gc.IsNil)
1214- // We must have some methods on every object returned
1215- // by a root-level method.
1216- c.Assert(m.ObjType.MethodNames(), gc.Not(gc.HasLen), 0)
1217- // We don't allow any methods that don't implement
1218- // an RPC entry point.
1219- c.Assert(m.ObjType.DiscardedMethods(), gc.HasLen, 0)
1220- }
1221-}
1222+// TODO: jam 2014-05-11 reintroduce this test once we properly have types
1223+// registered that can be reflected again. This was only disabled because we
1224+// got rid of ObjType as an exposed attribute of RootType
1225+// func (*rootSuite) TestDiscardedAPIMethods(c *gc.C) {
1226+// t := rpcreflect.TypeOf(apiserver.RootType)
1227+// // We must have some root-level methods.
1228+// c.Assert(t.MethodNames(), gc.Not(gc.HasLen), 0)
1229+// c.Assert(t.DiscardedMethods(), gc.DeepEquals, allowedDiscardedMethods)
1230+//
1231+// for _, name := range t.MethodNames() {
1232+// m, err := t.Method(name)
1233+// c.Assert(err, gc.IsNil)
1234+// // We must have some methods on every object returned
1235+// // by a root-level method.
1236+// c.Assert(m.ObjType.MethodNames(), gc.Not(gc.HasLen), 0)
1237+// // We don't allow any methods that don't implement
1238+// // an RPC entry point.
1239+// c.Assert(m.ObjType.DiscardedMethods(), gc.HasLen, 0)
1240+// }
1241+// }
1242
1243 func (r *rootSuite) TestPingTimeout(c *gc.C) {
1244 closedc := make(chan time.Time, 1)
1245
1246=== modified file 'utils/registry/registry.go'
1247--- utils/registry/registry.go 2014-05-16 11:48:18 +0000
1248+++ utils/registry/registry.go 2014-05-22 11:41:28 +0000
1249@@ -21,7 +21,7 @@
1250 versions map[string]Versions
1251 }
1252
1253-// NewTypedNameVersion creates a place to register your objects
1254+// NewTypedNameVersion creates a place to register your objects.
1255 func NewTypedNameVersion(requiredType reflect.Type) *TypedNameVersion {
1256 return &TypedNameVersion{
1257 requiredType: requiredType,
1258@@ -39,11 +39,9 @@
1259 type Versions map[int]interface{}
1260
1261 // Register records the factory that can be used to produce an instance of the
1262-// facade at the supplied version.
1263-// If the object being registered doesn't Implement the required Type, then an
1264-// error is returned.
1265-// An error is also returned if an object is already registered with the given
1266-// name and version.
1267+// facade at the supplied version. If the object being registered doesn't
1268+// Implement the required Type, then an error is returned. An error is also
1269+// returned if an object is already registered with the given name and version.
1270 func (r *TypedNameVersion) Register(name string, version int, obj interface{}) error {
1271 if !reflect.TypeOf(obj).ConvertibleTo(r.requiredType) {
1272 return fmt.Errorf("object of type %T cannot be converted to type %s.%s", obj, r.requiredType.PkgPath(), r.requiredType.Name())
1273@@ -65,7 +63,7 @@
1274 }
1275
1276 // descriptionFromVersions aggregates the information in a Versions map into a
1277-// more friendly form for List()
1278+// more friendly form for List().
1279 func descriptionFromVersions(name string, versions Versions) Description {
1280 intVersions := make([]int, 0, len(versions))
1281 for version := range versions {
1282@@ -94,7 +92,7 @@
1283 }
1284
1285 // Get returns the object for a single name and version. If the requested
1286-// facade is not found, it returns error.NotFound
1287+// facade is not found, it returns error.NotFound.
1288 func (r *TypedNameVersion) Get(name string, version int) (interface{}, error) {
1289 if versions, ok := r.versions[name]; ok {
1290 if factory, ok := versions[version]; ok {
1291
1292=== modified file 'utils/registry/registry_test.go'
1293--- utils/registry/registry_test.go 2014-05-20 04:27:02 +0000
1294+++ utils/registry/registry_test.go 2014-05-22 11:41:28 +0000
1295@@ -6,10 +6,10 @@
1296 import (
1297 "reflect"
1298
1299+ "github.com/juju/errors"
1300 jc "github.com/juju/testing/checkers"
1301 gc "launchpad.net/gocheck"
1302
1303- "github.com/juju/errors"
1304 "launchpad.net/juju-core/testing"
1305 "launchpad.net/juju-core/utils/registry"
1306 )

Subscribers

People subscribed via source and target branches

to status/vote changes: