Merge lp:~themue/pyjuju/go-state-topology-relation-endpoints into lp:pyjuju/go

Proposed by Frank Mueller
Status: Merged
Approved by: Gustavo Niemeyer
Approved revision: 162
Merged at revision: 190
Proposed branch: lp:~themue/pyjuju/go-state-topology-relation-endpoints
Merge into: lp:pyjuju/go
Prerequisite: lp:~themue/pyjuju/go-state-topology-relations-without-endpoints
Diff against target: 487 lines (+267/-37)
3 files modified
state/internal_test.go (+188/-24)
state/relation.go (+5/-0)
state/topology.go (+74/-13)
To merge this branch: bzr merge lp:~themue/pyjuju/go-state-topology-relation-endpoints
Reviewer Review Type Date Requested Status
Juju Engineering Pending
Review via email: mp+105156@code.launchpad.net

Description of the change

Added methods for relation endpoints to topology.

Relation endpoints needed two methods for testing and
key retrieving in topology.

https://codereview.appspot.com/6198055/

To post a comment you must log in.
Revision history for this message
William Reade (fwereade) wrote :

https://codereview.appspot.com/6198055/diff/1/state/topology.go
File state/topology.go (right):

https://codereview.appspot.com/6198055/diff/1/state/topology.go#newcode550
state/topology.go:550: if service == nil || service.Name !=
e.RelationName || scope != e.RelationScope {
I'm pretty sure that "service.Name != e.RelationName" is either insane,
or it betrays a serious misnaming issue somewhere.

That said, per prereq review, I think we should drop RelationName
anyway.

https://codereview.appspot.com/6198055/diff/1/state/topology.go#newcode563
state/topology.go:563: // between "endpoints" or "found" will be false.
I'm -1 on the ... here, I think it's a wart in the python version and
I'd prefer not to duplicate it in Go. Also, it feels like "found" is
redundant -- can't we just return a key of ""? How about:

func (t *topology) RelationKey(ep1, ep2 RelationEndpoint) (string,
error)

func (t *topology) PeerRelationKey(ep RelationEndpoint) (string, error)

...and, given that, I think that HasRelationBetweenEndpoints is actually
completely redundant.

https://codereview.appspot.com/6198055/diff/1/state/topology.go#newcode576
state/topology.go:576: for key, relation := range t.topology.Relations {
Please double-check why the implementation here is different to the one
in HasRelationBetweenEndpoints... by gut says that this is evidence of a
bug somewhere.

https://codereview.appspot.com/6198055/

Revision history for this message
Gustavo Niemeyer (niemeyer) wrote :

https://codereview.appspot.com/6198055/diff/5001/state/topology.go
File state/topology.go (right):

https://codereview.appspot.com/6198055/diff/5001/state/topology.go#newcode545
state/topology.go:545: return "", nil
If the developer asks for a RelationKey, and it doesn't exist, an error
should be returned. Silently returning nothing and hoping that the
developer remembers to check for the case where it doesn't exist every
time is extremely error prone.

Imagine if you had to do this:

     f, err := os.Open(filename)
     if err != nil {
          ...
     }
     if f != nil {
          ...
     }
     ...

Every time!

The documentation should reflect the change too.

https://codereview.appspot.com/6198055/diff/5001/state/topology.go#newcode571
state/topology.go:571: return "", nil
Ditto.

https://codereview.appspot.com/6198055/

Revision history for this message
Gustavo Niemeyer (niemeyer) wrote :

https://codereview.appspot.com/6198055/diff/8002/state/topology.go
File state/topology.go (right):

https://codereview.appspot.com/6198055/diff/8002/state/topology.go#newcode20
state/topology.go:20: errRelationDoesNotExist = errors.New("relation
does not exist")
If we define that error more nicely, we can allow it to pop out to of
the state package when using it. For example:

type noRelationError struct {
     endpoint1, endpoint2 RelationEndpoint
}

func (e *noRelationError) Error() string {
     if e.endpoint2 != nil {
         return fmt.Errorf("state: no peer relation for %s", (what
here?))
     } else {
         return fmt.Errorf("state: no relation between %s and %s", (what
here?))
     }
}

Not sure about the details of the error messages, but it should be
something nice that defines the endpoints.

What do you think?

https://codereview.appspot.com/6198055/

Revision history for this message
Gustavo Niemeyer (niemeyer) wrote :
Revision history for this message
Gustavo Niemeyer (niemeyer) wrote :

Also in a good direction, thanks Frank.

https://codereview.appspot.com/6198055/diff/9002/state/relation.go
File state/relation.go (right):

https://codereview.appspot.com/6198055/diff/9002/state/relation.go#newcode55
state/relation.go:55: func (e *RelationEndpoint) String() string {
That's somewhat unexpected. What's the goal here?

I think I'd prefer to leave this out. %#v and %v are simpler and produce
better results than this as it is.

https://codereview.appspot.com/6198055/diff/9002/state/topology.go
File state/topology.go (right):

https://codereview.appspot.com/6198055/diff/9002/state/topology.go#newcode17
state/topology.go:17: // consumer endpoints of for one peer endpoint
does not exist.
// NoRelationError represents a relation not found for one or more
endpoints.

https://codereview.appspot.com/6198055/diff/9002/state/topology.go#newcode19
state/topology.go:19: Endpoint1 *RelationEndpoint
I suggest using this instead:

     Endpoints []RelationEndpoint

https://codereview.appspot.com/6198055/diff/9002/state/topology.go#newcode474
state/topology.go:474: // interface between two endpoints. If it doesn't
exist an error is returned.
// RelationKey returns the key for the relation established between the
// provided endpoints. If no matching relation is found, error will be
// of type *NoRelationError.

https://codereview.appspot.com/6198055/diff/9002/state/topology.go#newcode475
state/topology.go:475: func (t *topology) RelationKey(ep1, ep2
RelationEndpoint) (string, error) {
I think the design we had is better one here:

RelationKey(endpoints ...RelationEndpoint)

https://codereview.appspot.com/6198055/diff/9002/state/topology.go#newcode494
state/topology.go:494: if ep1.Interface != relation.Interface ||
ep2.Interface != relation.Interface {
If ep1 and ep2 have diverging interfaces, we can return an error before
even starting to search.

https://codereview.appspot.com/6198055/diff/9002/state/topology.go#newcode499
state/topology.go:499: }
Relation names matter as well.

https://codereview.appspot.com/6198055/diff/9002/state/topology.go#newcode506
state/topology.go:506: func (t *topology) PeerRelationKey(ep
RelationEndpoint) (string, error) {
We can subsume that into RelationKey as well.

https://codereview.appspot.com/6198055/

Revision history for this message
Gustavo Niemeyer (niemeyer) wrote :
Revision history for this message
Gustavo Niemeyer (niemeyer) wrote :

https://codereview.appspot.com/6198055/diff/27001/state/topology.go
File state/topology.go (right):

https://codereview.appspot.com/6198055/diff/27001/state/topology.go#newcode471
state/topology.go:471: return "", fmt.Errorf("state: differing
interfaces %q and %q", endpoints[0].Interface, endpoints[1].Interface)
What about just returning NoRelationError?

https://codereview.appspot.com/6198055/diff/27001/state/topology.go#newcode474
state/topology.go:474: return "", fmt.Errorf("state: illegal number of
endpoints passed")
s/passed/provided/

https://codereview.appspot.com/6198055/diff/27001/state/topology.go#newcode491
state/topology.go:491: if serviceName != endpoint.RelationName {
Huh? That's quite wrong.

https://codereview.appspot.com/6198055/

Revision history for this message
Gustavo Niemeyer (niemeyer) wrote :

LGTM, with a few trivials:

https://codereview.appspot.com/6198055/diff/25003/state/topology.go
File state/topology.go (right):

https://codereview.appspot.com/6198055/diff/25003/state/topology.go#newcode29
state/topology.go:29: panic("state: illegal relation error")
s/ error//

https://codereview.appspot.com/6198055/diff/25003/state/topology.go#newcode73
state/topology.go:73: ServiceKey string
s/ServiceKey/Service/; see the convention in the other types.

https://codereview.appspot.com/6198055/diff/25003/state/topology.go#newcode74
state/topology.go:74: RelationName string
Please add a tag "relation-name" so we follow the naming convention too.

https://codereview.appspot.com/6198055/diff/25003/state/topology.go#newcode430
state/topology.go:430: return fmt.Errorf("provider and consumer keys
must not be the same")
s/consumer/requirer/

https://codereview.appspot.com/6198055/diff/25003/state/topology.go#newcode474
state/topology.go:474: if t.topology.Relations == nil {
Shouldn't the logic below work fine if this is dropped?

https://codereview.appspot.com/6198055/diff/25003/state/topology.go#newcode485
state/topology.go:485: return "", fmt.Errorf("state: illegal number of
endpoints provided")
s/endpoints/relation endpoints/

https://codereview.appspot.com/6198055/diff/25003/state/topology.go#newcode498
state/topology.go:498: if service.RelationName != endpoint.RelationName
{
Both of these if blocks can be made into one:

if !ok || ... {

https://codereview.appspot.com/6198055/

Revision history for this message
Gustavo Niemeyer (niemeyer) wrote :
163. By Frank Mueller

Merged trunk.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'state/internal_test.go'
2--- state/internal_test.go 2012-05-25 14:10:49 +0000
3+++ state/internal_test.go 2012-05-29 18:02:21 +0000
4@@ -1,6 +1,3 @@
5-// launchpad.net/juju/go/state
6-//
7-// Copyright (c) 2011-2012 Canonical Ltd.
8 package state
9
10 import (
11@@ -461,12 +458,12 @@
12 s.t.AddRelation("r-1", &zkRelation{
13 Interface: "ifce",
14 Scope: ScopeGlobal,
15- Services: map[RelationRole]string{RolePeer: "s-p"},
16+ Services: map[RelationRole]*zkRelationService{RolePeer: &zkRelationService{"s-p", "cache"}},
17 })
18 relation, err = s.t.Relation("r-1")
19 c.Assert(err, IsNil)
20 c.Assert(relation, NotNil)
21- c.Assert(relation.Services[RolePeer], Equals, "s-p")
22+ c.Assert(relation.Services[RolePeer].Service, Equals, "s-p")
23 }
24
25 func (s *TopologySuite) TestAddRelation(c *C) {
26@@ -480,61 +477,82 @@
27 err = s.t.AddRelation("r-1", &zkRelation{
28 Interface: "ifce",
29 Scope: ScopeGlobal,
30- Services: map[RelationRole]string{RoleProvider: "s-p", RoleRequirer: "s-r"},
31+ Services: map[RelationRole]*zkRelationService{
32+ RoleProvider: &zkRelationService{"s-p", "db"},
33+ RoleRequirer: &zkRelationService{"s-r", "db"},
34+ },
35 })
36 c.Assert(err, IsNil)
37 relation, err = s.t.Relation("r-1")
38 c.Assert(err, IsNil)
39 c.Assert(relation, NotNil)
40- c.Assert(relation.Services[RoleProvider], Equals, "s-p")
41- c.Assert(relation.Services[RoleRequirer], Equals, "s-r")
42+ c.Assert(relation.Services[RoleProvider].Service, Equals, "s-p")
43+ c.Assert(relation.Services[RoleRequirer].Service, Equals, "s-r")
44
45 err = s.t.AddRelation("r-2", &zkRelation{
46 Interface: "",
47 Scope: ScopeGlobal,
48- Services: map[RelationRole]string{RoleProvider: "s-p", RoleRequirer: "s-r"},
49+ Services: map[RelationRole]*zkRelationService{
50+ RoleProvider: &zkRelationService{"s-p", "db"},
51+ RoleRequirer: &zkRelationService{"s-r", "db"},
52+ },
53 })
54 c.Assert(err, ErrorMatches, `relation interface is empty`)
55
56 err = s.t.AddRelation("r-3", &zkRelation{
57 Interface: "ifce",
58 Scope: ScopeGlobal,
59- Services: map[RelationRole]string{},
60+ Services: map[RelationRole]*zkRelationService{},
61 })
62 c.Assert(err, ErrorMatches, `relation has no services`)
63
64 err = s.t.AddRelation("r-4", &zkRelation{
65 Interface: "ifce",
66 Scope: ScopeGlobal,
67- Services: map[RelationRole]string{RoleProvider: "s-p"},
68+ Services: map[RelationRole]*zkRelationService{
69+ RoleProvider: &zkRelationService{"s-p", "db"},
70+ },
71 })
72 c.Assert(err, ErrorMatches, `relation has provider but no requirer`)
73
74 err = s.t.AddRelation("r-5", &zkRelation{
75 Interface: "ifce",
76 Scope: ScopeGlobal,
77- Services: map[RelationRole]string{RoleProvider: "s-p", RolePeer: "s-r"},
78+ Services: map[RelationRole]*zkRelationService{
79+ RoleProvider: &zkRelationService{"s-p", "db"},
80+ RolePeer: &zkRelationService{"s-r", "db"},
81+ },
82 })
83 c.Assert(err, ErrorMatches, `relation has provider but no requirer`)
84
85 err = s.t.AddRelation("r-6", &zkRelation{
86 Interface: "ifce",
87 Scope: ScopeGlobal,
88- Services: map[RelationRole]string{RoleProvider: "s-p", RoleRequirer: "s-r", RolePeer: "s-r"},
89+ Services: map[RelationRole]*zkRelationService{
90+ RoleProvider: &zkRelationService{"s-p", "db"},
91+ RoleRequirer: &zkRelationService{"s-r", "db"},
92+ RolePeer: &zkRelationService{"s-r", "db"},
93+ },
94 })
95 c.Assert(err, ErrorMatches, `relation with mixed peer, provider, and requirer roles`)
96
97 err = s.t.AddRelation("r-7", &zkRelation{
98 Interface: "ifce",
99 Scope: ScopeGlobal,
100- Services: map[RelationRole]string{RoleProvider: "s-p", RoleRequirer: "illegal"},
101+ Services: map[RelationRole]*zkRelationService{
102+ RoleProvider: &zkRelationService{"s-p", "db"},
103+ RoleRequirer: &zkRelationService{"illegal", "db"},
104+ },
105 })
106 c.Assert(err, ErrorMatches, `service with key "illegal" not found`)
107
108 err = s.t.AddRelation("r-1", &zkRelation{
109 Interface: "ifce",
110 Scope: ScopeGlobal,
111- Services: map[RelationRole]string{RoleProvider: "s-p", RoleRequirer: "s-r"},
112+ Services: map[RelationRole]*zkRelationService{
113+ RoleProvider: &zkRelationService{"s-p", "db"},
114+ RoleRequirer: &zkRelationService{"s-r", "db"},
115+ },
116 })
117 c.Assert(err, ErrorMatches, `relation key "r-1" already in use`)
118 }
119@@ -548,7 +566,9 @@
120 s.t.AddRelation("r-1", &zkRelation{
121 Interface: "ifce",
122 Scope: ScopeGlobal,
123- Services: map[RelationRole]string{RolePeer: "s-p"},
124+ Services: map[RelationRole]*zkRelationService{
125+ RolePeer: &zkRelationService{"s-p", "cache"},
126+ },
127 })
128 keys = s.t.RelationKeys()
129 c.Assert(keys, DeepEquals, []string{"r-1"})
130@@ -556,7 +576,9 @@
131 s.t.AddRelation("r-2", &zkRelation{
132 Interface: "ifce",
133 Scope: ScopeGlobal,
134- Services: map[RelationRole]string{RolePeer: "s-p"},
135+ Services: map[RelationRole]*zkRelationService{
136+ RolePeer: &zkRelationService{"s-p", "cache"},
137+ },
138 })
139 keys = s.t.RelationKeys()
140 c.Assert(keys, DeepEquals, []string{"r-1", "r-2"})
141@@ -572,12 +594,16 @@
142 s.t.AddRelation("r-0", &zkRelation{
143 Interface: "ifce0",
144 Scope: ScopeGlobal,
145- Services: map[RelationRole]string{RolePeer: "s-p"},
146+ Services: map[RelationRole]*zkRelationService{
147+ RolePeer: &zkRelationService{"s-p", "cache"},
148+ },
149 })
150 s.t.AddRelation("r-1", &zkRelation{
151 Interface: "ifce1",
152 Scope: ScopeGlobal,
153- Services: map[RelationRole]string{RolePeer: "s-p"},
154+ Services: map[RelationRole]*zkRelationService{
155+ RolePeer: &zkRelationService{"s-p", "cache"},
156+ },
157 })
158 relations, err = s.t.RelationsForService("s-p")
159 c.Assert(err, IsNil)
160@@ -594,21 +620,24 @@
161
162 func (s *TopologySuite) TestRemoveRelation(c *C) {
163 // Check that removing of a relation works.
164- s.t.AddService("s-c", "wordpress")
165+ s.t.AddService("s-r", "wordpress")
166 s.t.AddService("s-p", "mysql")
167
168 err := s.t.AddRelation("r-1", &zkRelation{
169 Interface: "ifce",
170 Scope: ScopeGlobal,
171- Services: map[RelationRole]string{RoleProvider: "s-p", RoleRequirer: "s-c"},
172+ Services: map[RelationRole]*zkRelationService{
173+ RoleProvider: &zkRelationService{"s-p", "db"},
174+ RoleRequirer: &zkRelationService{"s-r", "db"},
175+ },
176 })
177 c.Assert(err, IsNil)
178
179 relation, err := s.t.Relation("r-1")
180 c.Assert(err, IsNil)
181 c.Assert(relation, NotNil)
182- c.Assert(relation.Services[RoleProvider], Equals, "s-p")
183- c.Assert(relation.Services[RoleRequirer], Equals, "s-c")
184+ c.Assert(relation.Services[RoleProvider].Service, Equals, "s-p")
185+ c.Assert(relation.Services[RoleRequirer].Service, Equals, "s-r")
186
187 s.t.RemoveRelation("r-1")
188
189@@ -624,13 +653,148 @@
190 s.t.AddRelation("r-1", &zkRelation{
191 Interface: "ifce",
192 Scope: ScopeGlobal,
193- Services: map[RelationRole]string{RolePeer: "s-p"},
194+ Services: map[RelationRole]*zkRelationService{
195+ RolePeer: &zkRelationService{"s-p", "cache"},
196+ },
197 })
198
199 err := s.t.RemoveService("s-p")
200 c.Assert(err, ErrorMatches, `cannot remove service "s-p" with active relations`)
201 }
202
203+func (s *TopologySuite) TestRelationKeyEndpoints(c *C) {
204+ mysqlep1 := RelationEndpoint{"mysql", "ifce1", "db", RoleProvider, ScopeGlobal}
205+ blogep1 := RelationEndpoint{"wordpress", "ifce1", "db", RoleRequirer, ScopeGlobal}
206+ mysqlep2 := RelationEndpoint{"mysql", "ifce2", "db", RoleProvider, ScopeGlobal}
207+ blogep2 := RelationEndpoint{"wordpress", "ifce2", "db", RoleRequirer, ScopeGlobal}
208+ mysqlep3 := RelationEndpoint{"mysql", "ifce3", "db", RoleProvider, ScopeGlobal}
209+ blogep3 := RelationEndpoint{"wordpress", "ifce3", "db", RoleRequirer, ScopeGlobal}
210+ s.t.AddService("s-r", "wordpress")
211+ s.t.AddService("s-p", "mysql")
212+ s.t.AddRelation("r-0", &zkRelation{
213+ Interface: "ifce1",
214+ Scope: ScopeGlobal,
215+ Services: map[RelationRole]*zkRelationService{
216+ RoleProvider: &zkRelationService{"s-p", "db"},
217+ RoleRequirer: &zkRelationService{"s-r", "db"},
218+ },
219+ })
220+ s.t.AddRelation("r-1", &zkRelation{
221+ Interface: "ifce2",
222+ Scope: ScopeGlobal,
223+ Services: map[RelationRole]*zkRelationService{
224+ RoleProvider: &zkRelationService{"s-p", "db"},
225+ RoleRequirer: &zkRelationService{"s-r", "db"},
226+ },
227+ })
228+
229+ // Valid relations.
230+ key, err := s.t.RelationKey(mysqlep1, blogep1)
231+ c.Assert(err, IsNil)
232+ c.Assert(key, Equals, "r-0")
233+ key, err = s.t.RelationKey(blogep1, mysqlep1)
234+ c.Assert(err, IsNil)
235+ c.Assert(key, Equals, "r-0")
236+ key, err = s.t.RelationKey(mysqlep2, blogep2)
237+ c.Assert(err, IsNil)
238+ c.Assert(key, Equals, "r-1")
239+ key, err = s.t.RelationKey(blogep2, mysqlep2)
240+ c.Assert(err, IsNil)
241+ c.Assert(key, Equals, "r-1")
242+
243+ // Endpoints without relation.
244+ _, err = s.t.RelationKey(mysqlep3, blogep3)
245+ c.Assert(err, ErrorMatches, `state: no relation between "mysql:db" and "wordpress:db"`)
246+
247+ // Mix of endpoints of two relations.
248+ _, err = s.t.RelationKey(mysqlep1, blogep2)
249+ c.Assert(err, ErrorMatches, `state: no relation between "mysql:db" and "wordpress:db"`)
250+
251+ // Illegal number of endpoints.
252+ _, err = s.t.RelationKey()
253+ c.Assert(err, ErrorMatches, `state: illegal number of relation endpoints provided`)
254+ _, err = s.t.RelationKey(mysqlep1, mysqlep2, blogep1)
255+ c.Assert(err, ErrorMatches, `state: illegal number of relation endpoints provided`)
256+}
257+
258+func (s *TopologySuite) TestRelationKeyIllegalEndpoints(c *C) {
259+ mysqlep1 := RelationEndpoint{"mysql", "ifce", "db", RoleProvider, ScopeGlobal}
260+ blogep1 := RelationEndpoint{"wordpress", "ifce", "db", RoleRequirer, ScopeGlobal}
261+ mysqlep2 := RelationEndpoint{"illegal-mysql", "ifce", "db", RoleProvider, ScopeGlobal}
262+ blogep2 := RelationEndpoint{"illegal-wordpress", "ifce", "db", RoleRequirer, ScopeGlobal}
263+ riakep3 := RelationEndpoint{"riak", "ifce", "ring", RolePeer, ScopeGlobal}
264+ s.t.AddService("s-r", "wordpress")
265+ s.t.AddService("s-p1", "mysql")
266+ s.t.AddService("s-p2", "riak")
267+ s.t.AddRelation("r-0", &zkRelation{
268+ Interface: "ifce1",
269+ Scope: ScopeGlobal,
270+ Services: map[RelationRole]*zkRelationService{
271+ RoleProvider: &zkRelationService{"s-p", "db"},
272+ RoleRequirer: &zkRelationService{"s-r", "db"},
273+ },
274+ })
275+
276+ key, err := s.t.RelationKey(mysqlep1, blogep2)
277+ c.Assert(key, Equals, "")
278+ c.Assert(err, ErrorMatches, `state: no relation between "mysql:db" and "illegal-wordpress:db"`)
279+ key, err = s.t.RelationKey(mysqlep2, blogep1)
280+ c.Assert(key, Equals, "")
281+ c.Assert(err, ErrorMatches, `state: no relation between "illegal-mysql:db" and "wordpress:db"`)
282+ key, err = s.t.RelationKey(mysqlep1, riakep3)
283+ c.Assert(key, Equals, "")
284+ c.Assert(err, ErrorMatches, `state: no relation between "mysql:db" and "riak:ring"`)
285+}
286+
287+func (s *TopologySuite) TestPeerRelationKeyEndpoints(c *C) {
288+ riakep1 := RelationEndpoint{"riak", "ifce1", "ring", RolePeer, ScopeGlobal}
289+ riakep2 := RelationEndpoint{"riak", "ifce2", "ring", RolePeer, ScopeGlobal}
290+ riakep3 := RelationEndpoint{"riak", "ifce3", "ring", RolePeer, ScopeGlobal}
291+ s.t.AddService("s-p", "ring")
292+ s.t.AddRelation("r-0", &zkRelation{
293+ Interface: "ifce1",
294+ Scope: ScopeGlobal,
295+ Services: map[RelationRole]*zkRelationService{
296+ RolePeer: &zkRelationService{"s-p", "ring"},
297+ },
298+ })
299+ s.t.AddRelation("r-1", &zkRelation{
300+ Interface: "ifce2",
301+ Scope: ScopeGlobal,
302+ Services: map[RelationRole]*zkRelationService{
303+ RolePeer: &zkRelationService{"s-p", "ring"},
304+ },
305+ })
306+
307+ // Valid relations.
308+ key, err := s.t.RelationKey(riakep1)
309+ c.Assert(err, IsNil)
310+ c.Assert(key, Equals, "r-0")
311+ key, err = s.t.RelationKey(riakep2)
312+ c.Assert(err, IsNil)
313+ c.Assert(key, Equals, "r-1")
314+
315+ // Endpoint without relation.
316+ key, err = s.t.RelationKey(riakep3)
317+ c.Assert(err, ErrorMatches, `state: no peer relation for "riak:ring"`)
318+}
319+
320+func (s *TopologySuite) TestPeerRelationKeyIllegalEndpoints(c *C) {
321+ riakep1 := RelationEndpoint{"riak", "ifce", "illegal-ring", RolePeer, ScopeGlobal}
322+ s.t.AddService("s-p", "riak")
323+ s.t.AddRelation("r-0", &zkRelation{
324+ Interface: "ifce",
325+ Scope: ScopeGlobal,
326+ Services: map[RelationRole]*zkRelationService{
327+ RolePeer: &zkRelationService{"s-p", "ring"},
328+ },
329+ })
330+
331+ key, err := s.t.RelationKey(riakep1)
332+ c.Assert(key, Equals, "")
333+ c.Assert(err, ErrorMatches, `state: no peer relation for "riak:illegal-ring"`)
334+}
335+
336 type ConfigNodeSuite struct {
337 zkServer *zookeeper.Server
338 zkTestRoot string
339
340=== modified file 'state/relation.go'
341--- state/relation.go 2012-05-24 18:57:02 +0000
342+++ state/relation.go 2012-05-29 18:02:21 +0000
343@@ -41,3 +41,8 @@
344 }
345 panic("endpoint role is undefined")
346 }
347+
348+// String returns the unique identifier of the relation endpoint.
349+func (e RelationEndpoint) String() string {
350+ return e.ServiceName + ":" + e.RelationName
351+}
352
353=== modified file 'state/topology.go'
354--- state/topology.go 2012-05-24 18:57:02 +0000
355+++ state/topology.go 2012-05-29 18:02:21 +0000
356@@ -13,6 +13,22 @@
357 // when we know that a version is in fact actually incompatible.
358 const topologyVersion = 1
359
360+// NoRelationError represents a relation not found for one or more endpoints.
361+type NoRelationError struct {
362+ Endpoints []RelationEndpoint
363+}
364+
365+// Error returns the string representation of the error.
366+func (e NoRelationError) Error() string {
367+ switch len(e.Endpoints) {
368+ case 1:
369+ return fmt.Sprintf("state: no peer relation for %q", e.Endpoints[0])
370+ case 2:
371+ return fmt.Sprintf("state: no relation between %q and %q", e.Endpoints[0], e.Endpoints[1])
372+ }
373+ panic("state: illegal relation")
374+}
375+
376 // zkTopology is used to marshal and unmarshal the content
377 // of the /topology node in ZooKeeper.
378 type zkTopology struct {
379@@ -47,7 +63,15 @@
380 type zkRelation struct {
381 Interface string
382 Scope RelationScope
383- Services map[RelationRole]string
384+ Services map[RelationRole]*zkRelationService
385+}
386+
387+// zkRelationService represents the data of one
388+// service of a relation within the /topology
389+// node in ZooKeeper.
390+type zkRelationService struct {
391+ Service string
392+ RelationName string "relation-name"
393 }
394
395 // check verifies that r is a proper relation.
396@@ -63,9 +87,12 @@
397 RoleProvider: RoleRequirer,
398 RolePeer: RolePeer,
399 }
400- for serviceRole, serviceKey := range r.Services {
401- if serviceKey == "" {
402- return fmt.Errorf("relation has %s service with empty key", serviceRole)
403+ for serviceRole, service := range r.Services {
404+ if service.Service == "" {
405+ return fmt.Errorf("relation has %s service with empty service key", serviceRole)
406+ }
407+ if service.RelationName == "" {
408+ return fmt.Errorf("relation has %s service with empty relation name", serviceRole)
409 }
410 counterRole, ok := counterpart[serviceRole]
411 if !ok {
412@@ -391,16 +418,16 @@
413 if err := relation.check(); err != nil {
414 return err
415 }
416- for _, serviceKey := range relation.Services {
417- if err := t.assertService(serviceKey); err != nil {
418+ for _, service := range relation.Services {
419+ if err := t.assertService(service.Service); err != nil {
420 return err
421 }
422 }
423- if relation.Services[RolePeer] == "" {
424- providerKey := relation.Services[RoleProvider]
425- consumerKey := relation.Services[RoleRequirer]
426- if providerKey == consumerKey {
427- return fmt.Errorf("provider and consumer keys must not be the same")
428+ if relation.Services[RolePeer] == nil {
429+ providerKey := relation.Services[RoleProvider].Service
430+ requirerKey := relation.Services[RoleRequirer].Service
431+ if providerKey == requirerKey {
432+ return fmt.Errorf("provider and requirer keys must not be the same")
433 }
434 }
435 t.topology.Relations[relationKey] = relation
436@@ -430,8 +457,8 @@
437 }
438 relations := make(map[string]*zkRelation)
439 for relationKey, relation := range t.topology.Relations {
440- for _, roleServiceKey := range relation.Services {
441- if roleServiceKey == serviceKey {
442+ for _, service := range relation.Services {
443+ if service.Service == serviceKey {
444 relations[relationKey] = relation
445 break
446 }
447@@ -440,6 +467,40 @@
448 return relations, nil
449 }
450
451+// RelationKey returns the key for the relation established between the
452+// provided endpoints. If no matching relation is found, error will be
453+// of type *NoRelationError.
454+func (t *topology) RelationKey(endpoints ...RelationEndpoint) (string, error) {
455+ switch len(endpoints) {
456+ case 1:
457+ // Just pass.
458+ case 2:
459+ if endpoints[0].Interface != endpoints[1].Interface {
460+ return "", &NoRelationError{endpoints}
461+ }
462+ default:
463+ return "", fmt.Errorf("state: illegal number of relation endpoints provided")
464+ }
465+ for relationKey, relation := range t.topology.Relations {
466+ if relation.Interface != endpoints[0].Interface {
467+ continue
468+ }
469+ found := true
470+ for _, endpoint := range endpoints {
471+ service, ok := relation.Services[endpoint.RelationRole]
472+ if !ok || service.RelationName != endpoint.RelationName {
473+ found = false
474+ break
475+ }
476+ }
477+ if found {
478+ // All endpoints tested positive.
479+ return relationKey, nil
480+ }
481+ }
482+ return "", &NoRelationError{endpoints}
483+}
484+
485 // assertMachine checks if a machine exists.
486 func (t *topology) assertMachine(machineKey string) error {
487 if _, ok := t.topology.Machines[machineKey]; !ok {

Subscribers

People subscribed via source and target branches