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

Proposed by Jim Baker
Status: Merged
Approved by: Mark Mims
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) Approve
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.
Revision history for this message
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
=== modified file 'sub-commands/aiki/cli.py'
--- sub-commands/aiki/cli.py 2012-07-05 17:30:37 +0000
+++ sub-commands/aiki/cli.py 2012-08-08 21:42:19 +0000
@@ -193,3 +193,18 @@
193 subparser.add_parser(193 subparser.add_parser(
194 cmd, description=cmd,194 cmd, description=cmd,
195 help=help, usage="".join(usage), formatter_class=argparse.RawDescriptionHelpFormatter)195 help=help, usage="".join(usage), formatter_class=argparse.RawDescriptionHelpFormatter)
196
197
198def format_states(states):
199 return "[" + ", ".join(sorted(states)) + "]"
200
201
202def format_port_pairs(port_pairs):
203 return "[" + ", ".join("%s/%s" % (port, proto) for port, proto in sorted(port_pairs)) + "]"
204
205
206def format_descriptors(*descriptors):
207 if len(descriptors) == 1:
208 return "peer relation %s" % descriptors[0]
209 else:
210 return "relation between " + " and ".join(sorted(descriptors))
196211
=== modified file 'sub-commands/watch'
--- sub-commands/watch 2012-06-22 05:24:51 +0000
+++ sub-commands/watch 2012-08-08 21:42:19 +0000
@@ -6,20 +6,23 @@
66
7from twisted.internet.defer import inlineCallbacks, returnValue, succeed7from twisted.internet.defer import inlineCallbacks, returnValue, succeed
88
9from aiki.cli import make_arg_parser, setup_logging, run_command, parse_relation_settings9from aiki.cli import (
10 make_arg_parser, setup_logging, run_command, parse_relation_settings,
11 format_states, format_descriptors, format_port_pairs)
10from aiki.twistutils import CountDownLatch, wait_for_results12from aiki.twistutils import CountDownLatch, wait_for_results
11from aiki.watcher import Watcher, ANY_SETTING13from aiki.watcher import Watcher, ANY_SETTING
12from juju.hooks.cli import parse_port_protocol14from juju.hooks.cli import parse_port_protocol
1315
1416
15log = logging.getLogger("watch")17log = logging.getLogger("watch")
18ERROR_STATES = set(["error", "install-error", "start-error", "stop-error"])
1619
1720
18def main():21def main():
19 parser = make_parser()22 parser = make_parser()
20 options = parse_options(parser)23 options = parse_options(parser)
21 setup_logging(options)24 setup_logging(options)
22 run_command(watch, options)25 run_command(WatchManager(), options)
2326
2427
25def make_parser(root_parser=None):28def make_parser(root_parser=None):
@@ -76,6 +79,7 @@
76 $ jitsu --any watch ... # any of the conditions may apply79 $ jitsu --any watch ... # any of the conditions may apply
77 """))80 """))
78 main_parser.add_argument("--any", default=False, action="store_true", help="Any of the conditions may be true")81 main_parser.add_argument("--any", default=False, action="store_true", help="Any of the conditions may be true")
82 main_parser.add_argument("--failfast", default=False, action="store_true", help="Immediately fail if any conditions are in error state")
79 main_parser.add_argument("--number", default=False, action="store_true", help="Number output by the corresponding condition")83 main_parser.add_argument("--number", default=False, action="store_true", help="Number output by the corresponding condition")
80 return main_parser84 return main_parser
8185
@@ -123,151 +127,153 @@
123 return options127 return options
124128
125129
126@inlineCallbacks130class WatchManager(object):
127def watch(result, client, options):131 """Manages the watching of all conditions"""
128 wait_for_conditions = []132
129 for condition in options.conditions:133 def __init__(self):
130 wait_for_conditions.append(parse_and_wait_on_condition(client, options, condition))134 self.overall = None
131 yield wait_for_results(wait_for_conditions, options.any)135 self.exception = None
132136
133137 def failfast(self, e):
134def print_result(options, condition, result):138 log.error("Failed fast: %s", e)
135 """Given conditions complete asynchronously, the user can specify numbering to disambiguate"""139 self.exception = e
136 if options.number:140 self.overall.callback(None)
137 print condition.count,141
138 print result142 @inlineCallbacks
139143 def __call__(self, result, client, options):
140144 self.options = options
141@inlineCallbacks145 self.client = client
142def parse_and_wait_on_condition(client, options, condition):146 self.watcher = Watcher(self.client)
143 """Given a condition description from argparse, creates a watcher for it"""147 wait_for_conditions = []
144 watcher = Watcher(client)148 for condition in self.options.conditions:
145149 wait_for_conditions.append(self.parse_and_wait_on_condition(condition))
146 # Disambiguate the following cases: relation, unit, or service:150 self.overall = wait_for_results(wait_for_conditions, self.options.any)
147 # 1. Relation151 yield self.overall
148 if len(condition.instance.split()) >= 2:152 if self.exception:
149 # Only handle non-peer relations, since peer relations are automatically established anyway;153 raise self.exception
150 # also handle error case of a relation between more than 2 services154
151 relation_ident = yield wait_for_relation(client, watcher, options, condition, condition.instance)155 def print_result(self, condition, result):
152 print_result(options, condition, relation_ident)156 """Given conditions complete asynchronously, the user can specify numbering to disambiguate"""
153 return157 if self.options.number:
154158 print condition.count,
155 # 2. Service unit159 print result
156 if "/" in condition.instance:160
157 yield wait_for_unit(client, watcher, options, condition, condition.instance)161 @inlineCallbacks
158 print_result(options, condition, condition.instance)162 def parse_and_wait_on_condition(self, condition):
159 return163 """Given a condition description from argparse, creates a watcher for it"""
160164
161 # 3. Service165 # Disambiguate the following cases: relation, unit, or service:
162 yield wait_for_service(client, watcher, options, condition)166 # 1. Relation
163 print_result(options, condition, condition.instance)167 if len(condition.instance.split()) >= 2:
164168 # Only handle non-peer relations, since peer relations are automatically established anyway;
165169 # also handle error case of a relation between more than 2 services
166def _format_states(states):170 relation_ident = yield self.wait_for_relation(condition, condition.instance)
167 return "[" + ", ".join(sorted(states)) + "]"171 self.print_result(condition, relation_ident)
168172 return
169173
170def _format_port_pairs(port_pairs):174 # 2. Service unit
171 return "[" + ", ".join("%s/%s" % (port, proto) for port, proto in sorted(port_pairs)) + "]"175 if "/" in condition.instance:
172176 yield self.wait_for_unit(condition, condition.instance)
173177 self.print_result(condition, condition.instance)
174@inlineCallbacks178 return
175def wait_for_unit(client, watcher, options, condition, unit_name, relation_ident=None):179
176 log.info("Waiting for unit %s...", unit_name)180 # 3. Service
177 yield watcher.wait_for_unit(unit_name)181 yield self.wait_for_service(condition)
178182 self.print_result(condition, condition.instance)
179 # Support --state/--x-state183
180 if condition.states or condition.excluded_states:184 @inlineCallbacks
181 log.info(185 def wait_for_unit(self, condition, unit_name, relation_ident=None):
182 "Waiting for unit %s to be in %s and not in %s", 186 log.info("Waiting for unit %s...", unit_name)
183 unit_name, _format_states(condition.states), _format_states(condition.excluded_states))187 yield self.watcher.wait_for_unit(unit_name)
184 agent_state = yield watcher.wait_for_unit_state(unit_name, condition.states, condition.excluded_states)188
185 log.info("Completed waiting for unit %s state: %s", unit_name, agent_state)189 # Support --state/--x-state
186190 if self.options.failfast:
187 # Support --open-port/--closed-port191 condition.states.update(ERROR_STATES)
188 if condition.open_ports or condition.closed_ports:192
189 open_ports = set(parse_port_protocol(p) for p in condition.open_ports)193 if condition.states or condition.excluded_states:
190 closed_ports = set(parse_port_protocol(p) for p in condition.closed_ports)194 log.info(
191 log.info(195 "Waiting for unit %s to be in %s and not in %s",
192 "Waiting for unit %s to have open ports in %s and not in %s", 196 unit_name, format_states(condition.states), format_states(condition.excluded_states))
193 unit_name, _format_port_pairs(open_ports), _format_port_pairs(closed_ports))197 agent_state = yield self.watcher.wait_for_unit_state(unit_name, condition.states, condition.excluded_states)
194 open_ports = yield watcher.wait_for_unit_ports(unit_name, open_ports, closed_ports)198 log.info("Completed waiting for unit %s state: %s", unit_name, agent_state)
195 log.info("Completed waiting for unit %s open ports: %s", unit_name, _format_port_pairs(open_ports))199 if self.options.failfast and agent_state in ERROR_STATES:
196200 self.failfast(Exception("Unit %s in error state: %s" % (unit_name, agent_state)))
197 # Support relation settings201
198 if condition.relation:202 # Support --open-port/--closed-port
199 if relation_ident is None:203 if condition.open_ports or condition.closed_ports:
200 relation_ident = yield wait_for_relation(client, watcher, options, condition)204 open_ports = set(parse_port_protocol(p) for p in condition.open_ports)
201 if condition.settings:205 closed_ports = set(parse_port_protocol(p) for p in condition.closed_ports)
202 log.info("Waiting for %s: settings %s", unit_name, condition.settings)206 log.info(
203 settings = yield watcher.watch_unit_settings(unit_name, relation_ident, condition.settings)207 "Waiting for unit %s to have open ports in %s and not in %s",
204 log.info("Completed waiting for %s: expected %s, actual %s",208 unit_name, format_port_pairs(open_ports), format_port_pairs(closed_ports))
205 unit_name, condition.settings, settings)209 open_ports = yield self.watcher.wait_for_unit_ports(unit_name, open_ports, closed_ports)
206 log.info("Completed waiting for unit %s", unit_name) 210 log.info("Completed waiting for unit %s open ports: %s", unit_name, format_port_pairs(open_ports))
207 returnValue(unit_name)211
208212 # Support relation settings
209213 if condition.relation:
210@inlineCallbacks214 if relation_ident is None:
211def wait_for_relation(client, watcher, options, condition, relation=None):215 relation_ident = yield self.wait_for_relation(condition)
212 """Return relation ident corresponding to the condition or relation (if specified), waiting as necessary"""216 if condition.settings:
213217 log.info("Waiting for %s: settings %s", unit_name, condition.settings)
214 if relation is None:218 settings = yield self.watcher.watch_unit_settings(unit_name, relation_ident, condition.settings)
215 relation = condition.relation219 log.info("Completed waiting for %s: expected %s, actual %s",
216220 unit_name, condition.settings, settings)
217 if ":" in relation:221 log.info("Completed waiting for unit %s", unit_name)
218 parts = relation.split(":")222 returnValue(unit_name)
219 if len(parts) == 2 and parts[1].isdigit():223
220 relation_ident = relation224 @inlineCallbacks
225 def wait_for_relation(self, condition, relation=None):
226 """Return relation ident corresponding to the condition or relation (if specified), waiting as necessary"""
227
228 if relation is None:
229 relation = condition.relation
230
231 if ":" in relation:
232 parts = relation.split(":")
233 if len(parts) == 2 and parts[1].isdigit():
234 relation_ident = relation
235 returnValue(relation_ident)
236
237 # Otherwise wait for it:
238 descriptors = relation.split()
239 if len(descriptors) == 1 or len(descriptors) == 2:
240 log.info("Waiting for %s...", format_descriptors(*descriptors))
241 relation_ident = yield self.watcher.wait_for_relation(*descriptors)
242 log.info("Completed waiting for %s", format_descriptors(*descriptors))
221 returnValue(relation_ident)243 returnValue(relation_ident)
222 244
223 # Otherwise wait for it:245 raise ValueError("Bad relation: %s" % relation)
224 descriptors = relation.split()246
225 if len(descriptors) == 1 or len(descriptors) == 2:247 @inlineCallbacks
226 log.info("Waiting for %s...", format_descriptors(*descriptors))248 def wait_for_service(self, condition):
227 relation_ident = yield watcher.wait_for_relation(*descriptors)249 """Return service name when sucessfully waited"""
228 log.info("Completed waiting for %s", format_descriptors(*descriptors))250 log.info("Waiting for %s%s service...",
229 returnValue(relation_ident)251 "%d unit(s) of " % condition.num_units if condition.num_units else "",
230252 condition.instance)
231 raise ValueError("Bad relation: %s" % relation)253 yield self.watcher.wait_for_service(condition.instance)
232254 log.info("Completed waiting for %s service", condition.instance)
233255 if condition.relation:
234@inlineCallbacks256 relation_ident = yield self.wait_for_relation(condition)
235def wait_for_service(client, watcher, options, condition):257 else:
236 """Return service name when sucessfully waited"""258 relation_ident = None
237 log.info("Waiting for %s%s service...", 259
238 "%d unit(s) of " % condition.num_units if condition.num_units else "",260 # If options used in the condition imply service unit watching,
239 condition.instance)261 # then default num_units to 1
240 yield watcher.wait_for_service(condition.instance)262 if condition.num_units is None and (
241 log.info("Completed waiting for %s service", condition.instance)263 condition.states or condition.excluded_states or condition.settings or
242 if condition.relation:264 condition.open_ports or condition.closed_ports):
243 relation_ident = yield wait_for_relation(client, watcher, options, condition)265 condition.num_units = 1
244 else:266
245 relation_ident = None267 if condition.num_units:
246268 latch = CountDownLatch(condition.num_units)
247 # If options used in the condition imply service unit watching,269
248 # then default num_units to 1270 def new_unit_cb(unit_name):
249 if condition.num_units is None and (271 # NOTE Could also presumably cancel any removed units, consider that for a future refactoring
250 condition.states or condition.excluded_states or condition.settings or272 return succeed(latch.add(self.wait_for_unit(condition, unit_name, relation_ident)))
251 condition.open_ports or condition.closed_ports):273
252 condition.num_units = 1274 yield self.watcher.watch_new_service_units(condition.instance, new_unit_cb)
253275 yield latch.completed
254 if condition.num_units:276 returnValue(condition.instance)
255 latch = CountDownLatch(condition.num_units)
256
257 def new_unit_cb(unit_name):
258 # NOTE Could also presumably cancel any removed units, consider that for a future refactoring
259 return succeed(latch.add(wait_for_unit(client, watcher, options, condition, unit_name, relation_ident)))
260
261 yield watcher.watch_new_service_units(condition.instance, new_unit_cb)
262 yield latch.completed
263 returnValue(condition.instance)
264
265
266def format_descriptors(*descriptors):
267 if len(descriptors) == 1:
268 return "peer relation %s" % descriptors[0]
269 else:
270 return "relation between " + " and ".join(sorted(descriptors))
271277
272278
273if __name__ == '__main__':279if __name__ == '__main__':

Subscribers

People subscribed via source and target branches

to all changes: