Merge lp:~themue/pyjuju/go-state-topology-relation-endpoints into lp:pyjuju/go
- go-state-topology-relation-endpoints
- Merge into go
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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Juju Engineering | Pending | ||
Review via email: mp+105156@code.launchpad.net |
Commit message
Description of the change
Added methods for relation endpoints to topology.
Relation endpoints needed two methods for testing and
key retrieving in topology.
William Reade (fwereade) wrote : | # |
Gustavo Niemeyer (niemeyer) wrote : | # |
https:/
File state/topology.go (right):
https:/
state/topology.
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:/
state/topology.
Ditto.
Gustavo Niemeyer (niemeyer) wrote : | # |
https:/
File state/topology.go (right):
https:/
state/topology.
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?
Gustavo Niemeyer (niemeyer) wrote : | # |
Gustavo Niemeyer (niemeyer) wrote : | # |
Also in a good direction, thanks Frank.
https:/
File state/relation.go (right):
https:/
state/relation.
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:/
File state/topology.go (right):
https:/
state/topology.
does not exist.
// NoRelationError represents a relation not found for one or more
endpoints.
https:/
state/topology.
I suggest using this instead:
Endpoints []RelationEndpoint
https:/
state/topology.
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:/
state/topology.
RelationEndpoint) (string, error) {
I think the design we had is better one here:
RelationKey(
https:/
state/topology.
ep2.Interface != relation.Interface {
If ep1 and ep2 have diverging interfaces, we can return an error before
even starting to search.
https:/
state/topology.
Relation names matter as well.
https:/
state/topology.
RelationEndpoint) (string, error) {
We can subsume that into RelationKey as well.
Gustavo Niemeyer (niemeyer) wrote : | # |
Moving it back onto WIP.
Gustavo Niemeyer (niemeyer) wrote : | # |
https:/
File state/topology.go (right):
https:/
state/topology.
interfaces %q and %q", endpoints[
What about just returning NoRelationError?
https:/
state/topology.
endpoints passed")
s/passed/provided/
https:/
state/topology.
Huh? That's quite wrong.
Gustavo Niemeyer (niemeyer) wrote : | # |
LGTM, with a few trivials:
https:/
File state/topology.go (right):
https:/
state/topology.
s/ error//
https:/
state/topology.
s/ServiceKey/
https:/
state/topology.
Please add a tag "relation-name" so we follow the naming convention too.
https:/
state/topology.
must not be the same")
s/consumer/
https:/
state/topology.
Shouldn't the logic below work fine if this is dropped?
https:/
state/topology.
endpoints provided")
s/endpoints/
https:/
state/topology.
{
Both of these if blocks can be made into one:
if !ok || ... {
Gustavo Niemeyer (niemeyer) wrote : | # |
- 163. By Frank Mueller
-
Merged trunk.
Preview Diff
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 { |
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 go:550: if service == nil || service.Name !=
state/topology.
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 go:563: // between "endpoints" or "found" will be false.
state/topology.
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 HasRelationBetw eenEndpoints is actually
completely redundant.
https:/ /codereview. appspot. com/6198055/ diff/1/ state/topology. go#newcode576 go:576: for key, relation := range t.topology. Relations { eenEndpoints. .. by gut says that this is evidence of a
state/topology.
Please double-check why the implementation here is different to the one
in HasRelationBetw
bug somewhere.
https:/ /codereview. appspot. com/6198055/