Merge lp:~jimbaker/juju-jitsu/watch-failfast into lp:juju-jitsu

Proposed by Jim Baker on 2012-08-08
Status: Merged
Approved by: Mark Mims on 2012-08-09
Approved revision: 75
Merge reported by: Mark Mims
Merged at revision: not available
Proposed branch: lp:~jimbaker/juju-jitsu/watch-failfast
Merge into: lp:juju-jitsu
Prerequisite: lp:~jimbaker/juju-jitsu/provider-info
Diff against target: 358 lines (+167/-146)
2 files modified
sub-commands/aiki/cli.py (+15/-0)
sub-commands/watch (+152/-146)
To merge this branch: bzr merge lp:~jimbaker/juju-jitsu/watch-failfast
Reviewer Review Type Date Requested Status
Mark Mims (community) 2012-08-08 Approve on 2012-08-09
Review via email: mp+118835@code.launchpad.net

Description of the change

Adds support for --failfast option, which immediately terminates the watch with exit code 1 if any condition is watching for a service unit (or as implied with a service using --state/--x-state). Also this branch refactors the watch script to use an instance of WatchManager to manage various parameters instead of passing through a bunch of functions.

$ jitsu watch -e testbed --failfast mysql --state=started foobar --state=started
2012-08-08 15:35:38,824 juju.common:INFO Connecting to environment...
2012-08-08 15:35:42,436 juju.common:INFO Connected to environment.
2012-08-08 15:35:42,436 watch:INFO Waiting for mysql service...
2012-08-08 15:35:42,436 watch:INFO Waiting for foobar service...
2012-08-08 15:35:42,735 watch:INFO Completed waiting for mysql service
2012-08-08 15:35:43,054 watch:INFO Waiting for unit mysql/1...
2012-08-08 15:35:43,409 watch:INFO Waiting for unit mysql/1 to be in [error, install-error, start-error, started, stop-error] and not in []
2012-08-08 15:35:43,955 watch:INFO Completed waiting for unit mysql/1 state: start-error
2012-08-08 15:35:43,955 watch:ERROR Failed fast: Unit mysql/1 in error state: start-error
Unit mysql/1 in error state: start-error
2012-08-08 15:35:43,956 watch:INFO Completed waiting for unit mysql/1
mysql

To post a comment you must log in.
Mark Mims (mark-mims) wrote :

lgtm

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'sub-commands/aiki/cli.py'
2--- sub-commands/aiki/cli.py 2012-07-05 17:30:37 +0000
3+++ sub-commands/aiki/cli.py 2012-08-08 21:42:19 +0000
4@@ -193,3 +193,18 @@
5 subparser.add_parser(
6 cmd, description=cmd,
7 help=help, usage="".join(usage), formatter_class=argparse.RawDescriptionHelpFormatter)
8+
9+
10+def format_states(states):
11+ return "[" + ", ".join(sorted(states)) + "]"
12+
13+
14+def format_port_pairs(port_pairs):
15+ return "[" + ", ".join("%s/%s" % (port, proto) for port, proto in sorted(port_pairs)) + "]"
16+
17+
18+def format_descriptors(*descriptors):
19+ if len(descriptors) == 1:
20+ return "peer relation %s" % descriptors[0]
21+ else:
22+ return "relation between " + " and ".join(sorted(descriptors))
23
24=== modified file 'sub-commands/watch'
25--- sub-commands/watch 2012-06-22 05:24:51 +0000
26+++ sub-commands/watch 2012-08-08 21:42:19 +0000
27@@ -6,20 +6,23 @@
28
29 from twisted.internet.defer import inlineCallbacks, returnValue, succeed
30
31-from aiki.cli import make_arg_parser, setup_logging, run_command, parse_relation_settings
32+from aiki.cli import (
33+ make_arg_parser, setup_logging, run_command, parse_relation_settings,
34+ format_states, format_descriptors, format_port_pairs)
35 from aiki.twistutils import CountDownLatch, wait_for_results
36 from aiki.watcher import Watcher, ANY_SETTING
37 from juju.hooks.cli import parse_port_protocol
38
39
40 log = logging.getLogger("watch")
41+ERROR_STATES = set(["error", "install-error", "start-error", "stop-error"])
42
43
44 def main():
45 parser = make_parser()
46 options = parse_options(parser)
47 setup_logging(options)
48- run_command(watch, options)
49+ run_command(WatchManager(), options)
50
51
52 def make_parser(root_parser=None):
53@@ -76,6 +79,7 @@
54 $ jitsu --any watch ... # any of the conditions may apply
55 """))
56 main_parser.add_argument("--any", default=False, action="store_true", help="Any of the conditions may be true")
57+ main_parser.add_argument("--failfast", default=False, action="store_true", help="Immediately fail if any conditions are in error state")
58 main_parser.add_argument("--number", default=False, action="store_true", help="Number output by the corresponding condition")
59 return main_parser
60
61@@ -123,151 +127,153 @@
62 return options
63
64
65-@inlineCallbacks
66-def watch(result, client, options):
67- wait_for_conditions = []
68- for condition in options.conditions:
69- wait_for_conditions.append(parse_and_wait_on_condition(client, options, condition))
70- yield wait_for_results(wait_for_conditions, options.any)
71-
72-
73-def print_result(options, condition, result):
74- """Given conditions complete asynchronously, the user can specify numbering to disambiguate"""
75- if options.number:
76- print condition.count,
77- print result
78-
79-
80-@inlineCallbacks
81-def parse_and_wait_on_condition(client, options, condition):
82- """Given a condition description from argparse, creates a watcher for it"""
83- watcher = Watcher(client)
84-
85- # Disambiguate the following cases: relation, unit, or service:
86- # 1. Relation
87- if len(condition.instance.split()) >= 2:
88- # Only handle non-peer relations, since peer relations are automatically established anyway;
89- # also handle error case of a relation between more than 2 services
90- relation_ident = yield wait_for_relation(client, watcher, options, condition, condition.instance)
91- print_result(options, condition, relation_ident)
92- return
93-
94- # 2. Service unit
95- if "/" in condition.instance:
96- yield wait_for_unit(client, watcher, options, condition, condition.instance)
97- print_result(options, condition, condition.instance)
98- return
99-
100- # 3. Service
101- yield wait_for_service(client, watcher, options, condition)
102- print_result(options, condition, condition.instance)
103-
104-
105-def _format_states(states):
106- return "[" + ", ".join(sorted(states)) + "]"
107-
108-
109-def _format_port_pairs(port_pairs):
110- return "[" + ", ".join("%s/%s" % (port, proto) for port, proto in sorted(port_pairs)) + "]"
111-
112-
113-@inlineCallbacks
114-def wait_for_unit(client, watcher, options, condition, unit_name, relation_ident=None):
115- log.info("Waiting for unit %s...", unit_name)
116- yield watcher.wait_for_unit(unit_name)
117-
118- # Support --state/--x-state
119- if condition.states or condition.excluded_states:
120- log.info(
121- "Waiting for unit %s to be in %s and not in %s",
122- unit_name, _format_states(condition.states), _format_states(condition.excluded_states))
123- agent_state = yield watcher.wait_for_unit_state(unit_name, condition.states, condition.excluded_states)
124- log.info("Completed waiting for unit %s state: %s", unit_name, agent_state)
125-
126- # Support --open-port/--closed-port
127- if condition.open_ports or condition.closed_ports:
128- open_ports = set(parse_port_protocol(p) for p in condition.open_ports)
129- closed_ports = set(parse_port_protocol(p) for p in condition.closed_ports)
130- log.info(
131- "Waiting for unit %s to have open ports in %s and not in %s",
132- unit_name, _format_port_pairs(open_ports), _format_port_pairs(closed_ports))
133- open_ports = yield watcher.wait_for_unit_ports(unit_name, open_ports, closed_ports)
134- log.info("Completed waiting for unit %s open ports: %s", unit_name, _format_port_pairs(open_ports))
135-
136- # Support relation settings
137- if condition.relation:
138- if relation_ident is None:
139- relation_ident = yield wait_for_relation(client, watcher, options, condition)
140- if condition.settings:
141- log.info("Waiting for %s: settings %s", unit_name, condition.settings)
142- settings = yield watcher.watch_unit_settings(unit_name, relation_ident, condition.settings)
143- log.info("Completed waiting for %s: expected %s, actual %s",
144- unit_name, condition.settings, settings)
145- log.info("Completed waiting for unit %s", unit_name)
146- returnValue(unit_name)
147-
148-
149-@inlineCallbacks
150-def wait_for_relation(client, watcher, options, condition, relation=None):
151- """Return relation ident corresponding to the condition or relation (if specified), waiting as necessary"""
152-
153- if relation is None:
154- relation = condition.relation
155-
156- if ":" in relation:
157- parts = relation.split(":")
158- if len(parts) == 2 and parts[1].isdigit():
159- relation_ident = relation
160+class WatchManager(object):
161+ """Manages the watching of all conditions"""
162+
163+ def __init__(self):
164+ self.overall = None
165+ self.exception = None
166+
167+ def failfast(self, e):
168+ log.error("Failed fast: %s", e)
169+ self.exception = e
170+ self.overall.callback(None)
171+
172+ @inlineCallbacks
173+ def __call__(self, result, client, options):
174+ self.options = options
175+ self.client = client
176+ self.watcher = Watcher(self.client)
177+ wait_for_conditions = []
178+ for condition in self.options.conditions:
179+ wait_for_conditions.append(self.parse_and_wait_on_condition(condition))
180+ self.overall = wait_for_results(wait_for_conditions, self.options.any)
181+ yield self.overall
182+ if self.exception:
183+ raise self.exception
184+
185+ def print_result(self, condition, result):
186+ """Given conditions complete asynchronously, the user can specify numbering to disambiguate"""
187+ if self.options.number:
188+ print condition.count,
189+ print result
190+
191+ @inlineCallbacks
192+ def parse_and_wait_on_condition(self, condition):
193+ """Given a condition description from argparse, creates a watcher for it"""
194+
195+ # Disambiguate the following cases: relation, unit, or service:
196+ # 1. Relation
197+ if len(condition.instance.split()) >= 2:
198+ # Only handle non-peer relations, since peer relations are automatically established anyway;
199+ # also handle error case of a relation between more than 2 services
200+ relation_ident = yield self.wait_for_relation(condition, condition.instance)
201+ self.print_result(condition, relation_ident)
202+ return
203+
204+ # 2. Service unit
205+ if "/" in condition.instance:
206+ yield self.wait_for_unit(condition, condition.instance)
207+ self.print_result(condition, condition.instance)
208+ return
209+
210+ # 3. Service
211+ yield self.wait_for_service(condition)
212+ self.print_result(condition, condition.instance)
213+
214+ @inlineCallbacks
215+ def wait_for_unit(self, condition, unit_name, relation_ident=None):
216+ log.info("Waiting for unit %s...", unit_name)
217+ yield self.watcher.wait_for_unit(unit_name)
218+
219+ # Support --state/--x-state
220+ if self.options.failfast:
221+ condition.states.update(ERROR_STATES)
222+
223+ if condition.states or condition.excluded_states:
224+ log.info(
225+ "Waiting for unit %s to be in %s and not in %s",
226+ unit_name, format_states(condition.states), format_states(condition.excluded_states))
227+ agent_state = yield self.watcher.wait_for_unit_state(unit_name, condition.states, condition.excluded_states)
228+ log.info("Completed waiting for unit %s state: %s", unit_name, agent_state)
229+ if self.options.failfast and agent_state in ERROR_STATES:
230+ self.failfast(Exception("Unit %s in error state: %s" % (unit_name, agent_state)))
231+
232+ # Support --open-port/--closed-port
233+ if condition.open_ports or condition.closed_ports:
234+ open_ports = set(parse_port_protocol(p) for p in condition.open_ports)
235+ closed_ports = set(parse_port_protocol(p) for p in condition.closed_ports)
236+ log.info(
237+ "Waiting for unit %s to have open ports in %s and not in %s",
238+ unit_name, format_port_pairs(open_ports), format_port_pairs(closed_ports))
239+ open_ports = yield self.watcher.wait_for_unit_ports(unit_name, open_ports, closed_ports)
240+ log.info("Completed waiting for unit %s open ports: %s", unit_name, format_port_pairs(open_ports))
241+
242+ # Support relation settings
243+ if condition.relation:
244+ if relation_ident is None:
245+ relation_ident = yield self.wait_for_relation(condition)
246+ if condition.settings:
247+ log.info("Waiting for %s: settings %s", unit_name, condition.settings)
248+ settings = yield self.watcher.watch_unit_settings(unit_name, relation_ident, condition.settings)
249+ log.info("Completed waiting for %s: expected %s, actual %s",
250+ unit_name, condition.settings, settings)
251+ log.info("Completed waiting for unit %s", unit_name)
252+ returnValue(unit_name)
253+
254+ @inlineCallbacks
255+ def wait_for_relation(self, condition, relation=None):
256+ """Return relation ident corresponding to the condition or relation (if specified), waiting as necessary"""
257+
258+ if relation is None:
259+ relation = condition.relation
260+
261+ if ":" in relation:
262+ parts = relation.split(":")
263+ if len(parts) == 2 and parts[1].isdigit():
264+ relation_ident = relation
265+ returnValue(relation_ident)
266+
267+ # Otherwise wait for it:
268+ descriptors = relation.split()
269+ if len(descriptors) == 1 or len(descriptors) == 2:
270+ log.info("Waiting for %s...", format_descriptors(*descriptors))
271+ relation_ident = yield self.watcher.wait_for_relation(*descriptors)
272+ log.info("Completed waiting for %s", format_descriptors(*descriptors))
273 returnValue(relation_ident)
274-
275- # Otherwise wait for it:
276- descriptors = relation.split()
277- if len(descriptors) == 1 or len(descriptors) == 2:
278- log.info("Waiting for %s...", format_descriptors(*descriptors))
279- relation_ident = yield watcher.wait_for_relation(*descriptors)
280- log.info("Completed waiting for %s", format_descriptors(*descriptors))
281- returnValue(relation_ident)
282-
283- raise ValueError("Bad relation: %s" % relation)
284-
285-
286-@inlineCallbacks
287-def wait_for_service(client, watcher, options, condition):
288- """Return service name when sucessfully waited"""
289- log.info("Waiting for %s%s service...",
290- "%d unit(s) of " % condition.num_units if condition.num_units else "",
291- condition.instance)
292- yield watcher.wait_for_service(condition.instance)
293- log.info("Completed waiting for %s service", condition.instance)
294- if condition.relation:
295- relation_ident = yield wait_for_relation(client, watcher, options, condition)
296- else:
297- relation_ident = None
298-
299- # If options used in the condition imply service unit watching,
300- # then default num_units to 1
301- if condition.num_units is None and (
302- condition.states or condition.excluded_states or condition.settings or
303- condition.open_ports or condition.closed_ports):
304- condition.num_units = 1
305-
306- if condition.num_units:
307- latch = CountDownLatch(condition.num_units)
308-
309- def new_unit_cb(unit_name):
310- # NOTE Could also presumably cancel any removed units, consider that for a future refactoring
311- return succeed(latch.add(wait_for_unit(client, watcher, options, condition, unit_name, relation_ident)))
312-
313- yield watcher.watch_new_service_units(condition.instance, new_unit_cb)
314- yield latch.completed
315- returnValue(condition.instance)
316-
317-
318-def format_descriptors(*descriptors):
319- if len(descriptors) == 1:
320- return "peer relation %s" % descriptors[0]
321- else:
322- return "relation between " + " and ".join(sorted(descriptors))
323+
324+ raise ValueError("Bad relation: %s" % relation)
325+
326+ @inlineCallbacks
327+ def wait_for_service(self, condition):
328+ """Return service name when sucessfully waited"""
329+ log.info("Waiting for %s%s service...",
330+ "%d unit(s) of " % condition.num_units if condition.num_units else "",
331+ condition.instance)
332+ yield self.watcher.wait_for_service(condition.instance)
333+ log.info("Completed waiting for %s service", condition.instance)
334+ if condition.relation:
335+ relation_ident = yield self.wait_for_relation(condition)
336+ else:
337+ relation_ident = None
338+
339+ # If options used in the condition imply service unit watching,
340+ # then default num_units to 1
341+ if condition.num_units is None and (
342+ condition.states or condition.excluded_states or condition.settings or
343+ condition.open_ports or condition.closed_ports):
344+ condition.num_units = 1
345+
346+ if condition.num_units:
347+ latch = CountDownLatch(condition.num_units)
348+
349+ def new_unit_cb(unit_name):
350+ # NOTE Could also presumably cancel any removed units, consider that for a future refactoring
351+ return succeed(latch.add(self.wait_for_unit(condition, unit_name, relation_ident)))
352+
353+ yield self.watcher.watch_new_service_units(condition.instance, new_unit_cb)
354+ yield latch.completed
355+ returnValue(condition.instance)
356
357
358 if __name__ == '__main__':

Subscribers

People subscribed via source and target branches

to all changes: