Merge lp:~jimbaker/pyjuju/hook-command-error-cases into lp:pyjuju
- hook-command-error-cases
- Merge into trunk
Status: | Merged | ||||||||
---|---|---|---|---|---|---|---|---|---|
Approved by: | Clint Byrum | ||||||||
Approved revision: | 584 | ||||||||
Merged at revision: | 587 | ||||||||
Proposed branch: | lp:~jimbaker/pyjuju/hook-command-error-cases | ||||||||
Merge into: | lp:pyjuju | ||||||||
Diff against target: |
456 lines (+292/-33) 4 files modified
juju/hooks/commands.py (+36/-10) juju/hooks/invoker.py (+6/-2) juju/hooks/protocol.py (+11/-6) juju/hooks/tests/test_invoker.py (+239/-15) |
||||||||
To merge this branch: | bzr merge lp:~jimbaker/pyjuju/hook-command-error-cases | ||||||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Clint Byrum (community) | Approve | ||
Review via email: mp+127188@code.launchpad.net |
Commit message
Description of the change
Exceptions for hook commands using relation id
AMP requires that all exceptions that are passed between the hook command (client) and unit agent (server) are registered, otherwise it will raise "twisted ERROR: Unhandled Error". This branch fixes this issue for exceptions introduced by the use of -r to specify a relation id. In addition for completeness, other tests were added around potential error cases.
This branch also resolves bug #1050592, which saw this issue in the use of relation-list with -r.
Note that these fixes continue the current practice that an error report is written to stderr ("Relation not found"), but the exit code of the hook command is still 0.
I have tested the branch with the the debug-charm attached to the bug, lp:~mew/+junk/debug-charm
Jim Baker (jimbaker) wrote : | # |
Reviewers: mp+127188_
Message:
Please take a look.
Description:
Exceptions for hook commands using relation id
AMP requires that all exceptions that are passed between the hook
command (client) and unit agent (server) are registered, otherwise it
will raise "twisted ERROR: Unhandled Error". This branch fixes this
issue for exceptions introduced by the use of -r to specify a relation
id. In addition for completeness, other tests were added around
potential error cases.
This branch also resolves bug #1050592, which saw this issue in the use
of relation-list with -r.
Note that these fixes continue the current practice that an error report
is written to stderr ("Relation not found"), but the exit code of the
hook command is still 0.
I have tested the branch with the the debug-charm attached to the bug,
lp:~mew/+junk/debug-charm
https:/
(do not edit description out of merge proposal)
Please review this at https:/
Affected files:
A [revision details]
M juju/hooks/
M juju/hooks/
M juju/hooks/
M juju/hooks/
Jim Baker (jimbaker) wrote : | # |
I'm fine with reporting "Relation not found", however, according to the spec, it should report "Not a valid relation id".
Note that the format check was being done on the other get_relation_
Clint Byrum (clint-fewbar) wrote : | # |
Ok, well if the spec has it, then I see no reason to delay merging.
Preview Diff
1 | === modified file 'juju/hooks/commands.py' |
2 | --- juju/hooks/commands.py 2012-05-03 16:46:40 +0000 |
3 | +++ juju/hooks/commands.py 2012-10-01 03:59:18 +0000 |
4 | @@ -9,6 +9,7 @@ |
5 | from juju.hooks.cli import ( |
6 | CommandLineClient, parse_log_level, parse_port_protocol) |
7 | from juju.hooks.protocol import MustSpecifyRelationName |
8 | +from juju.state.errors import InvalidRelationIdentity |
9 | |
10 | |
11 | BAD_CHARS = re.compile("[\-\./:()<>|?*]|(\\\)") |
12 | @@ -27,13 +28,22 @@ |
13 | |
14 | @inlineCallbacks |
15 | def run(self): |
16 | - # handle settings_name being explictly skipped on the cli |
17 | if self.options.settings_name == "-": |
18 | self.options.settings_name = "" |
19 | - result = yield self.client.relation_get(self.options.client_id, |
20 | - self.options.relation_id, |
21 | - self.options.unit_name, |
22 | - self.options.settings_name) |
23 | + if self.options.unit_name is None: |
24 | + print >>sys.stderr, "Unit name is not defined" |
25 | + return |
26 | + result = None |
27 | + try: |
28 | + result = yield self.client.relation_get(self.options.client_id, |
29 | + self.options.relation_id, |
30 | + self.options.unit_name, |
31 | + self.options.settings_name) |
32 | + except InvalidRelationIdentity, e: |
33 | + # This prevents the exception from getting wrapped by AMP |
34 | + print >>sys.stderr, e.relation_ident |
35 | + except Exception, e: |
36 | + print >>sys.stderr, str(e) |
37 | returnValue(result) |
38 | |
39 | def format_shell(self, result, stream): |
40 | @@ -83,10 +93,17 @@ |
41 | self.parser.add_argument( |
42 | "-r", dest="relation_id", default="", metavar="RELATION ID") |
43 | |
44 | + @inlineCallbacks |
45 | def run(self): |
46 | - return self.client.relation_set(self.options.client_id, |
47 | - self.options.relation_id, |
48 | - self.options.keyvalue_pairs) |
49 | + try: |
50 | + yield self.client.relation_set(self.options.client_id, |
51 | + self.options.relation_id, |
52 | + self.options.keyvalue_pairs) |
53 | + except InvalidRelationIdentity, e: |
54 | + # This prevents the exception from getting wrapped by AMP |
55 | + print >>sys.stderr, e.relation_ident |
56 | + except Exception, e: |
57 | + print >>sys.stderr, str(e) |
58 | |
59 | |
60 | def relation_set(): |
61 | @@ -135,9 +152,18 @@ |
62 | self.parser.add_argument( |
63 | "-r", dest="relation_id", default="", metavar="RELATION ID") |
64 | |
65 | + @inlineCallbacks |
66 | def run(self): |
67 | - return self.client.list_relations(self.options.client_id, |
68 | - self.options.relation_id) |
69 | + result = None |
70 | + try: |
71 | + result = yield self.client.list_relations(self.options.client_id, |
72 | + self.options.relation_id) |
73 | + except InvalidRelationIdentity, e: |
74 | + # This prevents the exception from getting wrapped by AMP |
75 | + print >>sys.stderr, e.relation_ident |
76 | + except Exception, e: |
77 | + print >>sys.stderr, str(e) |
78 | + returnValue(result) |
79 | |
80 | def format_eval(self, result, stream): |
81 | """ eval `juju-list` """ |
82 | |
83 | === modified file 'juju/hooks/invoker.py' |
84 | --- juju/hooks/invoker.py 2012-07-01 18:38:54 +0000 |
85 | +++ juju/hooks/invoker.py 2012-10-01 03:59:18 +0000 |
86 | @@ -6,7 +6,7 @@ |
87 | from twisted.python.failure import Failure |
88 | |
89 | from juju import errors |
90 | -from juju.state.errors import RelationStateNotFound |
91 | +from juju.state.errors import RelationStateNotFound, InvalidRelationIdentity |
92 | from juju.state.hook import RelationHookContext |
93 | |
94 | |
95 | @@ -239,7 +239,11 @@ |
96 | try: |
97 | return self._relation_contexts[relation_ident] |
98 | except KeyError: |
99 | - raise RelationStateNotFound() |
100 | + parts = relation_ident.split(":") |
101 | + if len(parts) != 2 or not parts[1].isdigit(): |
102 | + raise InvalidRelationIdentity(relation_ident) |
103 | + else: |
104 | + raise RelationStateNotFound() |
105 | |
106 | def get_relation_idents(self, relation_name): |
107 | return self._context.get_relation_idents(relation_name) |
108 | |
109 | === modified file 'juju/hooks/protocol.py' |
110 | --- juju/hooks/protocol.py 2012-09-15 14:13:27 +0000 |
111 | +++ juju/hooks/protocol.py 2012-10-01 03:59:18 +0000 |
112 | @@ -53,7 +53,8 @@ |
113 | from juju.errors import JujuError |
114 | from juju.lib import serializer |
115 | from juju.lib.format import get_charm_formatter, get_charm_formatter_from_env |
116 | -from juju.state.errors import UnitRelationStateNotFound |
117 | +from juju.state.errors import ( |
118 | + InvalidRelationIdentity, RelationStateNotFound, UnitRelationStateNotFound) |
119 | from juju.state.hook import RelationHookContext |
120 | |
121 | |
122 | @@ -84,11 +85,15 @@ |
123 | |
124 | |
125 | class BaseCommand(amp.Command): |
126 | - errors = {NoSuchUnit: "NoSuchUnit", |
127 | - NoSuchKey: "NoSuchKey", |
128 | - NotRelationContext: "NotRelationContext", |
129 | - UnitRelationStateNotFound: "UnitRelationStateNotFound", |
130 | - MustSpecifyRelationName: "MustSpecifyRelationName"} |
131 | + errors = { |
132 | + NoSuchUnit: "NoSuchUnit", |
133 | + NoSuchKey: "NoSuchKey", |
134 | + NotRelationContext: "NotRelationContext", |
135 | + UnitRelationStateNotFound: "UnitRelationStateNotFound", |
136 | + MustSpecifyRelationName: "MustSpecifyRelationName", |
137 | + InvalidRelationIdentity: "InvalidRelationIdentity", |
138 | + RelationStateNotFound: "RelationStateNotFound" |
139 | + } |
140 | |
141 | |
142 | # All the commands below this point should be documented in the |
143 | |
144 | === modified file 'juju/hooks/tests/test_invoker.py' |
145 | --- juju/hooks/tests/test_invoker.py 2012-09-15 14:13:27 +0000 |
146 | +++ juju/hooks/tests/test_invoker.py 2012-10-01 03:59:18 +0000 |
147 | @@ -123,6 +123,21 @@ |
148 | self.socket_path, self.server_factory) |
149 | |
150 | |
151 | +def capture_separate_log(name, level): |
152 | + """Support the separate capture of logging at different log levels. |
153 | + |
154 | + TestCase.capture_logging only allows one level to be captured at |
155 | + any given time. Given that the hook log captures both data AND |
156 | + traditional logging, it's useful to separate. |
157 | + """ |
158 | + logger = logging.getLogger(name) |
159 | + output = StringIO() |
160 | + handler = logging.StreamHandler(output) |
161 | + handler.setLevel(level) |
162 | + logger.addHandler(handler) |
163 | + return output |
164 | + |
165 | + |
166 | def get_cli_environ_path(*search_path): |
167 | """Construct a path environment variable. |
168 | |
169 | @@ -198,6 +213,31 @@ |
170 | os.chmod(fn, stat.S_IEXEC | stat.S_IREAD) |
171 | return fn |
172 | |
173 | + def create_capturing_hook(self, hook, files=()): |
174 | + """Create a hook to enable capturing of results into files. |
175 | + |
176 | + This method helps test scenarios of bash scripts that depend |
177 | + on exact captures of stdout and stderr as well as set -eu |
178 | + (Juju hook commands do not return nonzero exit codes, except |
179 | + in the case of parse failures). |
180 | + |
181 | + bin-path (the path to Juju commands) is always defined, along |
182 | + with paths to temporary files, as keyed to the list of |
183 | + `files`. |
184 | + """ |
185 | + args = {"bin-path": os.path.normpath( |
186 | + os.path.join(get_module_directory(juju), "..", "bin"))} |
187 | + for f in files: |
188 | + args[f] = self.makeFile() |
189 | + hook_fn = self.makeFile(hook.format(**args)) |
190 | + os.chmod(hook_fn, stat.S_IEXEC | stat.S_IREAD) |
191 | + return hook_fn, args |
192 | + |
193 | + def assert_file(self, path, data): |
194 | + """Assert file reached by `path` contains exactly this `data`.""" |
195 | + with open(path) as f: |
196 | + self.assertEqual(f.read(), data) |
197 | + |
198 | |
199 | class TestCompleteInvoker(InvokerTestBase, StatusTestBase): |
200 | |
201 | @@ -1211,6 +1251,29 @@ |
202 | hook_log.getvalue()) |
203 | |
204 | @defer.inlineCallbacks |
205 | + def test_relation_ids_nonexistent_relation_name(self): |
206 | + """Verify an empty listing is returned if name does not exist""" |
207 | + hook_log = self.capture_logging("hook", level=logging.DEBUG) |
208 | + yield self.add_a_blog("wordpress2") |
209 | + exe = yield self.ua.get_invoker( |
210 | + "db:0", "add", "mysql/0", self.relation, client_id="client_id") |
211 | + hook, args = self.create_capturing_hook( |
212 | + "#!/bin/bash\n" |
213 | + "set -eu\n" |
214 | + "data=$({bin-path}/relation-ids does-not-exist 2> {stderr})\n" |
215 | + "echo -n $data > {stdout}\n", |
216 | + files=["stdout", "stderr"]) |
217 | + result = yield exe(hook) |
218 | + self.assertEqual(result, 0) |
219 | + yield exe.ended |
220 | + self.assert_file(args["stdout"], "") |
221 | + self.assert_file(args["stderr"], "") |
222 | + self.assertEqual( |
223 | + hook_log.getvalue(), |
224 | + "Cached relation hook contexts on 'db:0': ['db:1']\n" |
225 | + "hook {} exited, exit code 0.\n".format(os.path.basename(hook))) |
226 | + |
227 | + @defer.inlineCallbacks |
228 | def test_relation_set_with_relation_id_option(self): |
229 | """Verify `relation-set` works with -r option.""" |
230 | # Invoker will be in the context of the db:0 relation |
231 | @@ -1238,6 +1301,57 @@ |
232 | " Setting changed: u'b'=u'xyz' (was unset) on 'db:1'"]) |
233 | |
234 | @defer.inlineCallbacks |
235 | + def test_relation_set_with_nonexistent_relation_id(self): |
236 | + """Verify error put on stderr when using nonexistent relation id.""" |
237 | + hook_log = self.capture_logging("hook", level=logging.DEBUG) |
238 | + yield self.add_a_blog("wordpress2") |
239 | + exe = yield self.ua.get_invoker( |
240 | + "db:0", "add", "mysql/0", self.relation, |
241 | + client_id="client_id") |
242 | + hook, args = self.create_capturing_hook( |
243 | + "#!/bin/bash\n" |
244 | + "set -eu\n" |
245 | + "data=$({bin-path}/relation-set -r db:12345 " |
246 | + "k1=v1 k2=v2 2> {stderr})\n" |
247 | + "echo -n $data > {stdout}\n", |
248 | + files=["stdout", "stderr"]) |
249 | + result = yield exe(hook) |
250 | + self.assertEqual(result, 0) |
251 | + yield exe.ended |
252 | + self.assert_file(args["stdout"], "") |
253 | + self.assert_file(args["stderr"], "Relation not found\n") |
254 | + self.assertEqual( |
255 | + hook_log.getvalue(), |
256 | + "Cached relation hook contexts on 'db:0': ['db:1']\n" |
257 | + "hook {} exited, exit code 0.\n".format(os.path.basename(hook))) |
258 | + |
259 | + @defer.inlineCallbacks |
260 | + def test_relation_set_with_invalid_relation_id(self): |
261 | + """Verify message is written to stderr for invalid formatted id.""" |
262 | + hook_log = self.capture_logging("hook", level=logging.DEBUG) |
263 | + yield self.add_a_blog("wordpress2") |
264 | + exe = yield self.ua.get_invoker( |
265 | + "db:0", "add", "mysql/0", self.relation, client_id="client_id") |
266 | + exe.environment["JUJU_REMOTE_UNIT"] = "wordpress/0" |
267 | + hook, args = self.create_capturing_hook( |
268 | + "#!/bin/bash\n" |
269 | + "set -eu\n" |
270 | + "data=$({bin-path}/relation-set -r invalid-id:forty-two " |
271 | + "k1=v1 k2=v2 2> {stderr})\n" |
272 | + "echo -n $data > {stdout}\n", |
273 | + files=["stderr", "stdout"]) |
274 | + result = yield exe(hook) |
275 | + self.assertEqual(result, 0) |
276 | + yield exe.ended |
277 | + self.assert_file(args["stdout"], "") |
278 | + self.assert_file(args["stderr"], |
279 | + "Not a valid relation id: 'invalid-id:forty-two'\n") |
280 | + self.assertEqual( |
281 | + hook_log.getvalue(), |
282 | + "Cached relation hook contexts on 'db:0': ['db:1']\n" |
283 | + "hook {} exited, exit code 0.\n".format(os.path.basename(hook))) |
284 | + |
285 | + @defer.inlineCallbacks |
286 | def test_relation_get_with_relation_id_option(self): |
287 | """Verify `relation-get` works with -r option.""" |
288 | yield self.add_a_blog("wordpress2") |
289 | @@ -1264,6 +1378,81 @@ |
290 | "name": "whiterabbit"}) |
291 | |
292 | @defer.inlineCallbacks |
293 | + def test_relation_get_with_nonexistent_relation_id(self): |
294 | + """Verify error put on stderr when using nonexistent relation id.""" |
295 | + hook_log = self.capture_logging("hook", level=logging.DEBUG) |
296 | + yield self.add_a_blog("wordpress2") |
297 | + exe = yield self.ua.get_invoker( |
298 | + "db:0", "add", "mysql/0", self.relation, |
299 | + client_id="client_id") |
300 | + hook, args = self.create_capturing_hook( |
301 | + "#!/bin/bash\n" |
302 | + "set -eu\n" |
303 | + "settings=$({bin-path}/relation-get -r db:12345 - mysql/0 " |
304 | + "2> {stderr})\n" |
305 | + "echo -n $settings > {settings}\n", |
306 | + files=["settings", "stderr"]) |
307 | + result = yield exe(hook) |
308 | + self.assertEqual(result, 0) |
309 | + yield exe.ended |
310 | + self.assert_file(args["settings"], "") |
311 | + self.assert_file(args["stderr"], "Relation not found\n") |
312 | + self.assertEqual( |
313 | + hook_log.getvalue(), |
314 | + "Cached relation hook contexts on 'db:0': ['db:1']\n" |
315 | + "hook {} exited, exit code 0.\n".format(os.path.basename(hook))) |
316 | + |
317 | + @defer.inlineCallbacks |
318 | + def test_relation_get_with_invalid_relation_id(self): |
319 | + """Verify message is written to stderr for invalid formatted id.""" |
320 | + hook_log = self.capture_logging("hook", level=logging.DEBUG) |
321 | + yield self.add_a_blog("wordpress2") |
322 | + exe = yield self.ua.get_invoker( |
323 | + "db:0", "add", "mysql/0", self.relation, client_id="client_id") |
324 | + exe.environment["JUJU_REMOTE_UNIT"] = "wordpress/0" |
325 | + hook, args = self.create_capturing_hook( |
326 | + "#!/bin/bash\n" |
327 | + "set -eu\n" |
328 | + "data=$({bin-path}/relation-get -r invalid-id:forty-two - " |
329 | + "2> {stderr})\n" |
330 | + "echo -n $data > {stdout}\n", |
331 | + files=["stderr", "stdout"]) |
332 | + result = yield exe(hook) |
333 | + self.assertEqual(result, 0) |
334 | + yield exe.ended |
335 | + self.assert_file(args["stderr"], |
336 | + "Not a valid relation id: 'invalid-id:forty-two'\n") |
337 | + self.assert_file(args["stdout"], "") |
338 | + self.assertEqual( |
339 | + hook_log.getvalue(), |
340 | + "Cached relation hook contexts on 'db:0': ['db:1']\n" |
341 | + "hook {} exited, exit code 0.\n".format(os.path.basename(hook))) |
342 | + |
343 | + @defer.inlineCallbacks |
344 | + def test_relation_get_unset_remote_unit_in_env(self): |
345 | + """Verify error is reported if JUJU_REMOTE_UNIT is not defined.""" |
346 | + hook_log = self.capture_logging("hook", level=logging.DEBUG) |
347 | + yield self.add_a_blog("wordpress2") |
348 | + exe = yield self.ua.get_invoker( |
349 | + "db:0", "add", "mysql/0", self.relation, client_id="client_id") |
350 | + self.assertNotIn("JUJU_REMOTE_UNIT", exe.environment) |
351 | + hook, args = self.create_capturing_hook( |
352 | + "#!/bin/bash\n" |
353 | + "set -eu\n" |
354 | + "data=$({bin-path}/relation-get -r db:0 - 2> {stderr})\n" |
355 | + "echo -n $data > {stdout}\n", |
356 | + files=["stderr", "stdout"]) |
357 | + result = yield exe(hook) |
358 | + self.assertEqual(result, 0) |
359 | + yield exe.ended |
360 | + self.assert_file(args["stdout"], "") |
361 | + self.assert_file(args["stderr"], "Unit name is not defined\n") |
362 | + self.assertEqual( |
363 | + hook_log.getvalue(), |
364 | + "Cached relation hook contexts on 'db:0': ['db:1']\n" |
365 | + "hook {} exited, exit code 0.\n".format(os.path.basename(hook))) |
366 | + |
367 | + @defer.inlineCallbacks |
368 | def test_relation_list_with_relation_id_option(self): |
369 | """Verify `relation-list` works with -r option.""" |
370 | yield self.add_a_blog("wordpress2") |
371 | @@ -1283,6 +1472,55 @@ |
372 | json.loads(hook_log.getvalue()), |
373 | ["wordpress2/0"]) |
374 | |
375 | + @defer.inlineCallbacks |
376 | + def test_relation_list_with_nonexistent_relation_id(self): |
377 | + """Verify a nonexistent relation id writes message to stderr.""" |
378 | + hook_log = self.capture_logging("hook", level=logging.DEBUG) |
379 | + yield self.add_a_blog("wordpress2") |
380 | + exe = yield self.ua.get_invoker( |
381 | + "db:0", "add", "mysql/0", self.relation, client_id="client_id") |
382 | + hook, args = self.create_capturing_hook( |
383 | + "#!/bin/bash\n" |
384 | + "set -eu\n" |
385 | + "data=$({bin-path}/relation-list -r db:12345 2> {stderr})\n" |
386 | + "echo -n $data > {stdout}\n", |
387 | + files=["stdout", "stderr"]) |
388 | + result = yield exe(hook) |
389 | + self.assertEqual(result, 0) |
390 | + yield exe.ended |
391 | + self.assert_file(args["stdout"], "") |
392 | + self.assert_file(args["stderr"], "Relation not found\n") |
393 | + self.assertEqual( |
394 | + hook_log.getvalue(), |
395 | + "Cached relation hook contexts on 'db:0': ['db:1']\n" |
396 | + "hook {} exited, exit code 0.\n".format(os.path.basename(hook))) |
397 | + |
398 | + @defer.inlineCallbacks |
399 | + def test_relation_list_with_invalid_relation_id(self): |
400 | + """Verify message is written to stderr for invalid formatted id.""" |
401 | + hook_log = self.capture_logging("hook", level=logging.DEBUG) |
402 | + yield self.add_a_blog("wordpress2") |
403 | + exe = yield self.ua.get_invoker( |
404 | + "db:0", "add", "mysql/0", self.relation, client_id="client_id") |
405 | + exe.environment["JUJU_REMOTE_UNIT"] = "wordpress/0" |
406 | + hook, args = self.create_capturing_hook( |
407 | + "#!/bin/bash\n" |
408 | + "set -eu\n" |
409 | + "data=$({bin-path}/relation-list -r invalid-id:forty-two " |
410 | + "2> {stderr})\n" |
411 | + "echo -n $data > {stdout}\n", |
412 | + files=["stderr", "stdout"]) |
413 | + result = yield exe(hook) |
414 | + self.assertEqual(result, 0) |
415 | + yield exe.ended |
416 | + self.assert_file(args["stdout"], "") |
417 | + self.assert_file(args["stderr"], |
418 | + "Not a valid relation id: 'invalid-id:forty-two'\n") |
419 | + self.assertEqual( |
420 | + hook_log.getvalue(), |
421 | + "Cached relation hook contexts on 'db:0': ['db:1']\n" |
422 | + "hook {} exited, exit code 0.\n".format(os.path.basename(hook))) |
423 | + |
424 | |
425 | class PortCommandsTest(RelationInvokerTestBase): |
426 | |
427 | @@ -1564,21 +1802,6 @@ |
428 | "closed 80/tcp"]) |
429 | |
430 | |
431 | -def capture_separate_log(name, level): |
432 | - """Support the separate capture of logging at different log levels. |
433 | - |
434 | - TestCase.capture_logging only allows one level to be captured at |
435 | - any given time. Given that the hook log captures both data AND |
436 | - traditional logging, it's useful to separate. |
437 | - """ |
438 | - logger = logging.getLogger(name) |
439 | - output = StringIO() |
440 | - handler = logging.StreamHandler(output) |
441 | - handler.setLevel(level) |
442 | - logger.addHandler(handler) |
443 | - return output |
444 | - |
445 | - |
446 | class TestCharmFormatV1(RelationInvokerTestBase): |
447 | |
448 | @defer.inlineCallbacks |
449 | @@ -1641,6 +1864,7 @@ |
450 | yield exe(self.create_hook( |
451 | "relation-set", "d=@%s" % data_file)) |
452 | result = yield exe(self.create_hook("relation-get", "d mysql/0")) |
453 | + self.assertEqual(result, 1) |
454 | self.assertIn( |
455 | "Error: \'utf8\' codec can\'t decode byte 0xca in position 30: " |
456 | "invalid continuation byte", |
Hi Jim. This is a pretty big merge, so perhaps next time use lbox propose -cr so we can review w/ rietveld?
Anyway, I think it all LGTM .. one bit of feedback:
99 - raise RelationStateNo tFound( ) ident.split( ":") Identity( relation_ ident) tFound( )
100 + parts = relation_
101 + if len(parts) != 2 or not parts[1].isdigit():
102 + raise InvalidRelation
103 + else:
104 + raise RelationStateNo
I don't really understand checking the format at this point.. we already know its not found, and thats what a user trying to debug their charm would want to know. The format problem would likely be *more* confusing. So I think this check, and the extra test for it, can just be dropped.