Merge lp:~bcsaller/pyjuju/subordinate-implicit-interfaces into lp:pyjuju

Proposed by Benjamin Saller
Status: Merged
Approved by: Jim Baker
Approved revision: 471
Merged at revision: 469
Proposed branch: lp:~bcsaller/pyjuju/subordinate-implicit-interfaces
Merge into: lp:pyjuju
Prerequisite: lp:~bcsaller/pyjuju/subordinate-charms
Diff against target: 288 lines (+109/-27)
7 files modified
juju/charm/metadata.py (+16/-4)
juju/charm/tests/repository/series/logging/metadata.yaml (+3/-0)
juju/charm/tests/test_metadata.py (+21/-0)
juju/control/add_relation.py (+4/-1)
juju/control/tests/test_add_relation.py (+15/-1)
juju/state/service.py (+28/-7)
juju/state/tests/test_service.py (+22/-14)
To merge this branch: bzr merge lp:~bcsaller/pyjuju/subordinate-implicit-interfaces
Reviewer Review Type Date Requested Status
Jim Baker (community) Approve
Review via email: mp+92333@code.launchpad.net

This proposal supersedes a proposal from 2012-02-09.

Description of the change

Implict relations

Adds support for implicit relations. These changes are largely independent of the other subordinate work
though that work depends on this. Following this merge implicit relations (juju-info) are provided by
all services.

https://codereview.appspot.com/5645061/

Manually setting prereq branch

To post a comment you must log in.
Revision history for this message
Benjamin Saller (bcsaller) wrote : Posted in a previous version of this proposal

Reviewers: mp+92195_code.launchpad.net,

Message:
Please take a look.

Description:
Adds support for implicit relations. These changes are largely
independent of the other subordinate work
though that work depends on this. Following this merge implicit
relations (juju-info) are provided by
all services.

https://code.launchpad.net/~bcsaller/juju/subordinate-implicit-interfaces/+merge/92195

(do not edit description out of merge proposal)

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

Affected files:
   M .bzrignore
   M docs/source/charm-upgrades.rst
   M docs/source/charm.rst
   A docs/source/drafts/implicit-relations.rst
   A docs/source/drafts/subordinate-internals.rst
   A docs/source/drafts/subordinate-services.rst
   M juju/charm/metadata.py
   A juju/charm/tests/repository/series/logging/.ignored
   A juju/charm/tests/repository/series/logging/hooks/install
   A juju/charm/tests/repository/series/logging/metadata.yaml
   A juju/charm/tests/repository/series/logging/revision
   M juju/charm/tests/test_metadata.py
   M juju/control/add_relation.py
   M juju/control/tests/test_add_relation.py
   M juju/state/service.py
   M juju/state/tests/test_service.py

Revision history for this message
Jim Baker (jimbaker) wrote :

+1 LGTM! I should not have waited on the CR in Reitveld (which still is not taking in account the prereq), this is a very small change.

review: Approve
Revision history for this message
Kapil Thangavelu (hazmat) wrote :

The error messages here are inscrutable to a user, the notion of 'namespaces' isn't documented.

Revision history for this message
Benjamin Saller (bcsaller) wrote :

I'll fix this in a later branch

On Sat, Mar 3, 2012 at 7:25 PM, Kapil Thangavelu
<email address hidden> wrote:
> The error messages here are inscrutable to a user, the notion of 'namespaces' isn't documented.
> --
> https://code.launchpad.net/~bcsaller/juju/subordinate-implicit-interfaces/+merge/92333
> You are the owner of lp:~bcsaller/juju/subordinate-implicit-interfaces.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'juju/charm/metadata.py'
2--- juju/charm/metadata.py 2012-02-09 21:13:21 +0000
3+++ juju/charm/metadata.py 2012-02-09 21:13:21 +0000
4@@ -72,7 +72,7 @@
5 return {
6 "interface": UTF8_SCHEMA.coerce(value, path),
7 "limit": self.limit,
8- "scope": "global",
9+ "scope": SCOPE_GLOBAL,
10 "optional": False}
11 else:
12 # Optional values are context-sensitive and/or have
13@@ -83,7 +83,7 @@
14 value["limit"] = self.limit
15 if "optional" not in value:
16 value["optional"] = False
17- value["scope"] = value.get("scope", "global")
18+ value["scope"] = value.get("scope", SCOPE_GLOBAL)
19 return INTERFACE_SCHEMA.coerce(value, path)
20
21
22@@ -169,7 +169,7 @@
23 return False
24
25 for relation_data in self.requires.values():
26- if relation_data.get("scope") == "container":
27+ if relation_data.get("scope") == SCOPE_CONTAINER:
28 return True
29 return False
30
31@@ -216,11 +216,23 @@
32 "%s: revision field is obsolete. Move it to the 'revision' "
33 "file." % path)
34
35+ if self.provides:
36+ for rel in self.provides:
37+ if rel.startswith("juju-"):
38+ raise MetaDataError(
39+ "Charm %s attempting to provide relation in implicit relation namespace: %s" %
40+ (self.name, rel))
41+ interface = self.provides[rel]["interface"]
42+ if interface.startswith("juju-"):
43+ raise MetaDataError(
44+ "Charm %s attempting to provide interface in implicit namespace: "
45+ "%s (relation: %s)" % (self.name, interface, rel))
46+
47 if self._data.get("subordinate", False) is True:
48 proper_subordinate = False
49 if self.requires:
50 for relation_data in self.requires.values():
51- if relation_data.get("scope") == "container":
52+ if relation_data.get("scope") == SCOPE_CONTAINER:
53 proper_subordinate = True
54 if not proper_subordinate:
55 log.warning(
56
57=== modified file 'juju/charm/tests/repository/series/logging/metadata.yaml'
58--- juju/charm/tests/repository/series/logging/metadata.yaml 2012-02-09 21:13:21 +0000
59+++ juju/charm/tests/repository/series/logging/metadata.yaml 2012-02-09 21:13:21 +0000
60@@ -11,3 +11,6 @@
61 logging-directory:
62 interface: logging
63 scope: container
64+ juju-info-fallback:
65+ interface: juju-info
66+ scope: container
67\ No newline at end of file
68
69=== modified file 'juju/charm/tests/test_metadata.py'
70--- juju/charm/tests/test_metadata.py 2012-02-09 21:13:21 +0000
71+++ juju/charm/tests/test_metadata.py 2012-02-09 21:13:21 +0000
72@@ -218,6 +218,27 @@
73 serialization_data = self.metadata.get_serialization_data()
74 self.assertEquals(serialization_data["name"], "dummy")
75
76+ def test_provide_implicit_relation(self):
77+ """Verify providing a juju-* reserved relation errors"""
78+ with self.change_sample() as data:
79+ data["provides"] = {"juju-foo": {"interface": "juju-magic", "scope": "container"}}
80+
81+ # verify relation level error
82+ error = self.assertRaises(MetaDataError,
83+ self.metadata.parse, self.sample)
84+ self.assertIn("Charm dummy attempting to provide relation in implicit relation namespace: juju-foo",
85+ str(error))
86+
87+ # verify interface level error
88+ with self.change_sample() as data:
89+ data["provides"] = {"foo-rel": {"interface": "juju-magic", "scope": "container"}}
90+
91+ error = self.assertRaises(MetaDataError,
92+ self.metadata.parse, self.sample)
93+ self.assertIn(
94+ "Charm dummy attempting to provide interface in implicit namespace: juju-magic (relation: foo-rel)",
95+ str(error))
96+
97
98 class ParseTest(TestCase):
99 """Test the parsing of some well-known sample files"""
100
101=== modified file 'juju/control/add_relation.py'
102--- juju/control/add_relation.py 2011-10-07 12:37:03 +0000
103+++ juju/control/add_relation.py 2012-02-09 21:13:21 +0000
104@@ -50,7 +50,10 @@
105 if len(endpoint_pairs) == 0:
106 raise NoMatchingEndpoints()
107 elif len(endpoint_pairs) > 1:
108- raise AmbiguousRelation(descriptors, endpoint_pairs)
109+ for pair in endpoint_pairs[1:]:
110+ if not (pair[0].relation_name.startswith("juju-") or
111+ pair[1].relation_name.startswith("juju-")):
112+ raise AmbiguousRelation(descriptors, endpoint_pairs)
113
114 # At this point we just have one endpoint pair. We need to pick
115 # just one of the endpoints if it's a peer endpoint, since that's
116
117=== modified file 'juju/control/tests/test_add_relation.py'
118--- juju/control/tests/test_add_relation.py 2012-01-23 23:17:03 +0000
119+++ juju/control/tests/test_add_relation.py 2012-02-09 21:13:21 +0000
120@@ -95,7 +95,7 @@
121
122 @inlineCallbacks
123 def test_add_relation_multiple(self):
124- """Test that the command can be create multiple relations."""
125+ """Test that the command can be used to create multiple relations."""
126 environment = self.config.get("firstenv")
127 yield self.add_service_from_charm("mysql")
128 yield self.add_service_from_charm("wordpress")
129@@ -217,3 +217,17 @@
130 self.assertIn(
131 "Invalid environment 'roman-candle'",
132 self.stderr.getvalue())
133+
134+ @inlineCallbacks
135+ def test_relate_to_implicit(self):
136+ """Validate we can implicitly relate to an implicitly provided relation"""
137+ wait_on_reactor_stopped = self.setup_cli_reactor()
138+ self.setup_exit(0)
139+ self.mocker.replay()
140+ yield self.add_service_from_charm("mysql")
141+ yield self.add_service_from_charm("logging")
142+ main(["add-relation", "mysql", "logging"])
143+ yield wait_on_reactor_stopped
144+ self.assertIn(
145+ "Added juju-info relation to all service units.",
146+ self.output.getvalue())
147
148=== modified file 'juju/state/service.py'
149--- juju/state/service.py 2012-02-04 00:01:06 +0000
150+++ juju/state/service.py 2012-02-09 21:13:21 +0000
151@@ -140,13 +140,15 @@
152 A `descriptor` is of the form ``<service name>[:<relation name>]``.
153 Returns the following:
154
155- - Returns a set of matching endpoints, drawn from the peers,
156- provides, and requires interfaces. The empty set is
157+ - Returns a list of matching endpoints, drawn from the
158+ peers, provides, and requires interfaces. An empty list is
159 returned if there are no endpoints matching the
160- `descriptor`.
161+ `descriptor`. This list is sorted such that implicit
162+ relations appear last.
163
164 - Raises a `BadDescriptor` exception if `descriptor` cannot
165 be parsed.
166+
167 """
168 tokens = descriptor.split(":")
169 if len(tokens) == 1 and bool(tokens[0]):
170@@ -175,16 +177,35 @@
171 relation_type=spec["interface"],
172 relation_name=relation_name,
173 relation_role=relation_role))
174+
175+ # Add in implicit relations
176+ endpoints.add(
177+ RelationEndpoint(
178+ query_service_name, "juju-info", "juju-info", "server"))
179+
180+ # when offering matches implicit relations should be considered last.
181+ # this cmpfunc pushes implicit methods to the end of the list of possible
182+ # endpoints
183+ def low_priority_implicit_cmp(rel1, rel2):
184+ if rel1.relation_name.startswith("juju-"):
185+ return 1
186+ if rel2.relation_name.startswith("juju-"):
187+ return -1
188+ return cmp(rel1.relation_name, rel2.relation_name)
189+
190+ endpoints = sorted(endpoints, low_priority_implicit_cmp)
191+
192 returnValue(endpoints)
193
194 @inlineCallbacks
195 def join_descriptors(self, descriptor1, descriptor2):
196 """Return a list of pairs of RelationEndpoints joining descriptors."""
197 result = []
198- relation_set1 = yield self.get_relation_endpoints(descriptor1)
199- relation_set2 = yield self.get_relation_endpoints(descriptor2)
200- for relation1 in relation_set1:
201- for relation2 in relation_set2:
202+ relations_1 = yield self.get_relation_endpoints(descriptor1)
203+ relations_2 = yield self.get_relation_endpoints(descriptor2)
204+
205+ for relation1 in relations_1:
206+ for relation2 in relations_2:
207 if relation1.may_relate_to(relation2):
208 result.append((relation1, relation2))
209 returnValue(result)
210
211=== modified file 'juju/state/tests/test_service.py'
212--- juju/state/tests/test_service.py 2012-02-04 00:00:08 +0000
213+++ juju/state/tests/test_service.py 2012-02-09 21:13:21 +0000
214@@ -1642,17 +1642,18 @@
215 self.assertEqual(
216 (yield self.service_state_manager.get_relation_endpoints(
217 "wordpress")),
218- set([RelationEndpoint("wordpress", "http", "url", "server"),
219- RelationEndpoint("wordpress", "mysql", "db", "client"),
220- RelationEndpoint(
221- "wordpress", "varnish", "cache", "client")]))
222+ [ RelationEndpoint("wordpress", "varnish", "cache", "client"),
223+ RelationEndpoint("wordpress", "mysql", "db", "client"),
224+ RelationEndpoint("wordpress", "http", "url", "server"),
225+ RelationEndpoint("wordpress", "juju-info", "juju-info", "server")])
226 yield self.add_service_from_charm("riak")
227 self.assertEqual(
228 (yield self.service_state_manager.get_relation_endpoints(
229 "riak")),
230- set([RelationEndpoint("riak", "http", "admin", "server"),
231- RelationEndpoint("riak", "http", "endpoint", "server"),
232- RelationEndpoint("riak", "riak", "ring", "peer")]))
233+ [RelationEndpoint("riak", "http", "admin", "server"),
234+ RelationEndpoint("riak", "http", "endpoint", "server"),
235+ RelationEndpoint("riak", "riak", "ring", "peer"),
236+ RelationEndpoint("riak", "juju-info", "juju-info", "server")])
237
238 @inlineCallbacks
239 def test_get_relation_endpoints_service_name_relation_name(self):
240@@ -1661,34 +1662,41 @@
241 self.assertEqual(
242 (yield self.service_state_manager.get_relation_endpoints(
243 "wordpress:url")),
244- set([RelationEndpoint("wordpress", "http", "url", "server")]))
245+ [RelationEndpoint("wordpress", "http", "url", "server"),
246+ RelationEndpoint("wordpress", "juju-info", "juju-info", "server")])
247 self.assertEqual(
248 (yield self.service_state_manager.get_relation_endpoints(
249 "wordpress:db")),
250- set([RelationEndpoint("wordpress", "mysql", "db", "client")]))
251+ [RelationEndpoint("wordpress", "mysql", "db", "client"),
252+ RelationEndpoint("wordpress", "juju-info", "juju-info", "server")])
253 self.assertEqual(
254 (yield self.service_state_manager.get_relation_endpoints(
255 "wordpress:cache")),
256- set([RelationEndpoint(
257- "wordpress", "varnish", "cache", "client")]))
258+ [RelationEndpoint("wordpress", "varnish", "cache", "client"),
259+ RelationEndpoint("wordpress", "juju-info", "juju-info", "server")])
260 yield self.add_service_from_charm("riak")
261 self.assertEqual(
262 (yield self.service_state_manager.get_relation_endpoints(
263 "riak:ring")),
264- set([RelationEndpoint("riak", "riak", "ring", "peer")]))
265+ [RelationEndpoint("riak", "riak", "ring", "peer"),
266+ RelationEndpoint("riak", "juju-info", "juju-info", "server")])
267
268 @inlineCallbacks
269 def test_descriptor_for_services_without_charms(self):
270 """Test with services that have no corresponding charms defined"""
271 yield self.add_service("nocharm")
272+ # verify we get the implicit interface
273 self.assertEqual(
274 (yield self.service_state_manager.get_relation_endpoints(
275 "nocharm")),
276- set())
277+ [RelationEndpoint("nocharm",
278+ "juju-info", "juju-info", "server")])
279+
280 self.assertEqual(
281 (yield self.service_state_manager.get_relation_endpoints(
282 "nocharm:nonsense")),
283- set())
284+ [RelationEndpoint("nocharm",
285+ "juju-info", "juju-info", "server")])
286
287 @inlineCallbacks
288 def test_descriptor_for_missing_service(self):

Subscribers

People subscribed via source and target branches

to status/vote changes: