Merge lp:~veebers/juju-ci-tools/record_timing_actions into lp:juju-ci-tools
- record_timing_actions
- Merge into trunk
Proposed by
Christopher Lee
Status: | Superseded |
---|---|
Proposed branch: | lp:~veebers/juju-ci-tools/record_timing_actions |
Merge into: | lp:juju-ci-tools |
Diff against target: |
2028 lines (+625/-178) 14 files modified
assess_container_networking.py (+3/-2) chaos.py (+3/-2) deploy_stack.py (+8/-7) jujupy/client.py (+192/-31) jujupy/fake.py (+5/-1) jujupy/tests/test_client.py (+237/-66) jujupy/tests/test_version_client.py (+46/-44) jujupy/version_client.py (+21/-10) tests/__init__.py (+21/-1) tests/test_assess_block.py (+2/-1) tests/test_assess_bootstrap.py (+2/-1) tests/test_assess_container_networking.py (+28/-2) tests/test_assess_user_grant_revoke.py (+2/-1) tests/test_deploy_stack.py (+55/-9) |
To merge this branch: | bzr merge lp:~veebers/juju-ci-tools/record_timing_actions |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Juju Release Engineering | Pending | ||
Review via email: mp+321134@code.launchpad.net |
This proposal has been superseded by a proposal from 2017-03-29.
Commit message
Enable collection of timing data for bootstrap, kill/destroy controller and deploy.
Description of the change
Enable collection of timing data for bootstrap, kill/destroy controller and deploy.
Timing collection for bootstrap and kill/destroy controller are collected without any need for any intervention from a test author.
deploy on the other hand needs to make a change where instead of:
client.
client.
Instead:
_, deploy_complete = client.deploy(...)
client.
The resulting timing data looks like: http://
To post a comment you must log in.
- 1966. By Christopher Lee
-
Don't actually return non-needed CommandComplete objects.
Unmerged revisions
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'assess_container_networking.py' |
2 | --- assess_container_networking.py 2017-02-24 18:28:04 +0000 |
3 | +++ assess_container_networking.py 2017-03-28 03:22:14 +0000 |
4 | @@ -248,8 +248,9 @@ |
5 | |
6 | d = re.search(r'^default\s+via\s+([\d\.]+)\s+', routes, re.MULTILINE) |
7 | if d: |
8 | - rc = client.juju('ssh', ('--proxy', target, |
9 | - 'ping -c1 -q ' + d.group(1)), check=False) |
10 | + rc, _ = client.juju( |
11 | + 'ssh', |
12 | + ('--proxy', target, 'ping -c1 -q ' + d.group(1)), check=False) |
13 | if rc != 0: |
14 | raise ValueError('%s unable to ping default route' % target) |
15 | else: |
16 | |
17 | === modified file 'chaos.py' |
18 | --- chaos.py 2016-06-27 11:41:04 +0000 |
19 | +++ chaos.py 2017-03-28 03:22:14 +0000 |
20 | @@ -146,8 +146,9 @@ |
21 | check_cmd += '/chaos_monkey.' + self.monkey_ids[unit_name] |
22 | check_cmd += '/chaos_runner.lock' |
23 | check_cmd += ' ]' |
24 | - if self.client.juju('run', ('--unit', unit_name, check_cmd), |
25 | - check=False): |
26 | + retvar, _ = self.client.juju( |
27 | + 'run', ('--unit', unit_name, check_cmd), check=False) |
28 | + if retvar != 0: |
29 | return 'done' |
30 | return 'running' |
31 | |
32 | |
33 | === modified file 'deploy_stack.py' |
34 | --- deploy_stack.py 2017-03-01 20:34:01 +0000 |
35 | +++ deploy_stack.py 2017-03-28 03:22:14 +0000 |
36 | @@ -8,7 +8,6 @@ |
37 | from contextlib import nested |
38 | except ImportError: |
39 | from contextlib import ExitStack as nested |
40 | - |
41 | import glob |
42 | import logging |
43 | import os |
44 | @@ -18,7 +17,7 @@ |
45 | import subprocess |
46 | import sys |
47 | import time |
48 | -import json |
49 | +import yaml |
50 | import shutil |
51 | |
52 | from chaos import background_chaos |
53 | @@ -249,10 +248,11 @@ |
54 | |
55 | def dump_juju_timings(client, log_directory): |
56 | try: |
57 | - with open(os.path.join(log_directory, 'juju_command_times.json'), |
58 | - 'w') as timing_file: |
59 | - json.dump(client.get_juju_timings(), timing_file, indent=2, |
60 | - sort_keys=True) |
61 | + report_path = os.path.join(log_directory, 'juju_command_times.yaml') |
62 | + with open(report_path, 'w') as timing_file: |
63 | + yaml.safe_dump( |
64 | + client.get_juju_timings(), |
65 | + timing_file) |
66 | timing_file.write('\n') |
67 | except Exception as e: |
68 | print_now("Failed to save timings") |
69 | @@ -534,9 +534,10 @@ |
70 | |
71 | def create_initial_model(self, upload_tools, series, boot_kwargs): |
72 | """Create the initial model by bootstrapping.""" |
73 | - self.client.bootstrap( |
74 | + _, bootstrap_complete = self.client.bootstrap( |
75 | upload_tools=upload_tools, bootstrap_series=series, |
76 | **boot_kwargs) |
77 | + self.client.get_controller_client().wait_for(bootstrap_complete) |
78 | |
79 | def get_hosts(self): |
80 | """Provide the controller host.""" |
81 | |
82 | === modified file 'jujupy/client.py' |
83 | --- jujupy/client.py 2017-03-27 15:15:05 +0000 |
84 | +++ jujupy/client.py 2017-03-28 03:22:14 +0000 |
85 | @@ -1095,7 +1095,7 @@ |
86 | self.feature_flags = feature_flags |
87 | self.debug = debug |
88 | self._timeout_path = get_timeout_path() |
89 | - self.juju_timings = {} |
90 | + self.juju_timings = [] |
91 | self.soft_deadline = soft_deadline |
92 | self._ignore_soft_deadline = False |
93 | |
94 | @@ -1131,6 +1131,9 @@ |
95 | debug = self.debug |
96 | result = self.__class__(full_path, version, feature_flags, debug, |
97 | self.soft_deadline) |
98 | + # Each clone shares a reference to juju_timings allowing us to collect |
99 | + # all commands run during a test. |
100 | + result.juju_timings = self.juju_timings |
101 | return result |
102 | |
103 | @property |
104 | @@ -1197,7 +1200,11 @@ |
105 | def juju(self, command, args, used_feature_flags, |
106 | juju_home, model=None, check=True, timeout=None, extra_env=None, |
107 | suppress_err=False): |
108 | - """Run a command under juju for the current environment.""" |
109 | + """Run a command under juju for the current environment. |
110 | + |
111 | + :return: Tuple rval, CommandTime rval being the commands exit code and |
112 | + a CommandTime object used for storing command timing data. |
113 | + """ |
114 | args = self.full_args(command, args, model, timeout) |
115 | log.info(' '.join(args)) |
116 | env = self.shell_environ(used_feature_flags, juju_home) |
117 | @@ -1207,17 +1214,17 @@ |
118 | call_func = subprocess.check_call |
119 | else: |
120 | call_func = subprocess.call |
121 | - start_time = time.time() |
122 | # Mutate os.environ instead of supplying env parameter so Windows can |
123 | # search env['PATH'] |
124 | stderr = subprocess.PIPE if suppress_err else None |
125 | + # Keep track of commands and how long the take. |
126 | + command_time = CommandTime(command, args, env) |
127 | with scoped_environ(env): |
128 | log.debug('Running juju with env: {}'.format(env)) |
129 | with self._check_timeouts(): |
130 | rval = call_func(args, stderr=stderr) |
131 | - self.juju_timings.setdefault(args, []).append( |
132 | - (time.time() - start_time)) |
133 | - return rval |
134 | + self.juju_timings.append(command_time) |
135 | + return rval, command_time |
136 | |
137 | def expect(self, command, args, used_feature_flags, juju_home, model=None, |
138 | timeout=None, extra_env=None): |
139 | @@ -1296,6 +1303,22 @@ |
140 | self.timeout = timeout |
141 | self.already_satisfied = already_satisfied |
142 | |
143 | + def iter_blocking_state(self, status): |
144 | + """Identify when the condition required is met. |
145 | + |
146 | + When the operation is complete yield nothing. Otherwise yields a |
147 | + tuple ('<item detail>', '<state>') |
148 | + as to why the action cannot be considered complete yet. |
149 | + |
150 | + An example for a condition of an application being removed: |
151 | + yield <application name>, 'still-present' |
152 | + """ |
153 | + raise NotImplementedError() |
154 | + |
155 | + def do_raise(self, model_name, status): |
156 | + """Raise exception for when success condition fails to be achieved.""" |
157 | + raise NotImplementedError() |
158 | + |
159 | |
160 | class ConditionList(BaseCondition): |
161 | """A list of conditions that support client.wait_for. |
162 | @@ -1325,6 +1348,15 @@ |
163 | self._conditions[0].do_raise(model_name, status) |
164 | |
165 | |
166 | +class NoopCondition(BaseCondition): |
167 | + |
168 | + def iter_blocking_state(self, status): |
169 | + return iter(()) |
170 | + |
171 | + def do_raise(self, model_name, status): |
172 | + raise Exception('NoopCondition failed: {}'.format(model_name)) |
173 | + |
174 | + |
175 | class WaitMachineNotPresent(BaseCondition): |
176 | """Condition satisfied when a given machine is not present.""" |
177 | |
178 | @@ -1419,6 +1451,103 @@ |
179 | raise VersionsNotUpdated(model_name, status) |
180 | |
181 | |
182 | +class WaitAgentsStarted(BaseCondition): |
183 | + |
184 | + def __init__(self, timeout=1200): |
185 | + super(WaitAgentsStarted, self).__init__(timeout) |
186 | + |
187 | + def iter_blocking_state(self, status): |
188 | + states = Status.check_agents_started(status) |
189 | + |
190 | + if states is not None: |
191 | + for state, item in states.items(): |
192 | + yield item[0], state |
193 | + |
194 | + def do_raise(self, model_name, status): |
195 | + raise AgentsNotStarted(model_name, status) |
196 | + |
197 | + |
198 | +class CommandTime: |
199 | + """Store timing details for a juju command.""" |
200 | + |
201 | + def __init__(self, cmd, full_args, envvars=None, start=None): |
202 | + """Constructor. |
203 | + |
204 | + :param cmd: Command string for command run (e.g. bootstrap) |
205 | + :param args: List of all args the command was called with. |
206 | + :param envvars: Dict of any extra envvars set before command was |
207 | + called. |
208 | + :param start: datetime.datetime object representing when the command |
209 | + was run. If None defaults to datetime.utcnow() |
210 | + """ |
211 | + self.cmd = cmd |
212 | + self.full_args = full_args |
213 | + self.envvars = envvars |
214 | + self.start = start if start else datetime.utcnow() |
215 | + self.end = None |
216 | + |
217 | + def actual_completion(self, end=None): |
218 | + """Signify that actual completion time of the command. |
219 | + |
220 | + Note. ignores multiple calls after the initial call. |
221 | + |
222 | + :param end: datetime.datetime object. If None defaults to |
223 | + datetime.datetime.utcnow() |
224 | + """ |
225 | + if self.end is None: |
226 | + self.end = end if end else datetime.utcnow() |
227 | + |
228 | + @property |
229 | + def total_seconds(self): |
230 | + """Total amount of seconds a command took to complete. |
231 | + |
232 | + :return: Int representing number of seconds or None if the command |
233 | + timing has never been completed. |
234 | + """ |
235 | + if self.end is None: |
236 | + return None |
237 | + return (self.end - self.start).total_seconds() |
238 | + |
239 | + |
240 | +class CommandComplete(BaseCondition): |
241 | + """Wraps a CommandTime and gives the ability to wait_for completion.""" |
242 | + |
243 | + def __init__(self, real_condition, command_time): |
244 | + """Constructor. |
245 | + |
246 | + :param real_condition: BaseCondition object. |
247 | + :param command_time: CommandTime object representing the command to |
248 | + wait for completion. |
249 | + """ |
250 | + super(CommandComplete, self).__init__( |
251 | + real_condition.timeout, |
252 | + real_condition.already_satisfied) |
253 | + self._real_condition = real_condition |
254 | + self.command_time = command_time |
255 | + if real_condition.already_satisfied: |
256 | + self.command_time.actual_completion() |
257 | + |
258 | + def iter_blocking_state(self, status): |
259 | + """Wraps the iter_blocking_state of the stored BaseCondition. |
260 | + |
261 | + When the operation is complete iter_blocking_state yields nothing. |
262 | + Otherwise iter_blocking_state yields details as to why the action |
263 | + cannot be considered complete yet. |
264 | + """ |
265 | + completed = True |
266 | + for item, state in self._real_condition.iter_blocking_state(status): |
267 | + completed = False |
268 | + yield item, state |
269 | + if completed: |
270 | + self.command_time.actual_completion() |
271 | + |
272 | + def do_raise(self, status): |
273 | + raise RuntimeError( |
274 | + 'Timed out waiting for "{}" command to complete: "{}"'.format( |
275 | + self.command_time.cmd, |
276 | + ' '.join(self.command_time.full_args))) |
277 | + |
278 | + |
279 | class ModelClient: |
280 | """Wraps calls to a juju instance, associated with a single model. |
281 | |
282 | @@ -1833,7 +1962,8 @@ |
283 | upload_tools, config_filename, bootstrap_series, credential, |
284 | auto_upgrade, metadata_source, no_gui, agent_version) |
285 | self.update_user_name() |
286 | - self.juju('bootstrap', args, include_e=False) |
287 | + retvar, ct = self.juju('bootstrap', args, include_e=False) |
288 | + return retvar, CommandComplete(WaitAgentsStarted(), ct) |
289 | |
290 | @contextmanager |
291 | def bootstrap_async(self, upload_tools=False, bootstrap_series=None, |
292 | @@ -1863,7 +1993,7 @@ |
293 | '--config', config_file)) |
294 | |
295 | def destroy_model(self): |
296 | - exit_status = self.juju( |
297 | + exit_status, _ = self.juju( |
298 | 'destroy-model', (self.env.environment, '-y',), |
299 | include_e=False, timeout=get_teardown_timeout(self)) |
300 | return exit_status |
301 | @@ -1871,22 +2001,32 @@ |
302 | def kill_controller(self, check=False): |
303 | """Kill a controller and its models. Hard kill option. |
304 | |
305 | - :return: Subprocess's exit code.""" |
306 | - return self.juju( |
307 | + :return: Tuple: Subprocess's exit code, CommandComplete object. |
308 | + """ |
309 | + retvar, ct = self.juju( |
310 | 'kill-controller', (self.env.controller.name, '-y'), |
311 | include_e=False, check=check, timeout=get_teardown_timeout(self)) |
312 | + # Already satisfied as this is a sync, operation. |
313 | + return retvar, CommandComplete( |
314 | + NoopCondition(already_satisfied=True), ct) |
315 | |
316 | def destroy_controller(self, all_models=False): |
317 | """Destroy a controller and its models. Soft kill option. |
318 | |
319 | :param all_models: If true will attempt to destroy all the |
320 | controller's models as well. |
321 | - :raises: subprocess.CalledProcessError if the operation fails.""" |
322 | + :raises: subprocess.CalledProcessError if the operation fails. |
323 | + :return: Tuple: Subprocess's exit code, CommandComplete object. |
324 | + """ |
325 | args = (self.env.controller.name, '-y') |
326 | if all_models: |
327 | args += ('--destroy-all-models',) |
328 | - return self.juju('destroy-controller', args, include_e=False, |
329 | - timeout=get_teardown_timeout(self)) |
330 | + retvar, ct = self.juju( |
331 | + 'destroy-controller', args, include_e=False, |
332 | + timeout=get_teardown_timeout(self)) |
333 | + # Already satisfied as this is a sync, operation. |
334 | + return retvar, CommandComplete( |
335 | + NoopCondition(already_satisfied=True), ct) |
336 | |
337 | def tear_down(self): |
338 | """Tear down the client as cleanly as possible. |
339 | @@ -1897,7 +2037,7 @@ |
340 | self.destroy_controller(all_models=True) |
341 | except subprocess.CalledProcessError: |
342 | logging.warning('tear_down destroy-controller failed') |
343 | - retval = self.kill_controller() |
344 | + retval, _ = self.kill_controller() |
345 | message = 'tear_down kill-controller result={}'.format(retval) |
346 | if retval == 0: |
347 | logging.info(message) |
348 | @@ -1971,7 +2111,8 @@ |
349 | |
350 | def set_model_constraints(self, constraints): |
351 | constraint_strings = self._dict_as_option_strings(constraints) |
352 | - return self.juju('set-model-constraints', constraint_strings) |
353 | + retvar, ct = self.juju('set-model-constraints', constraint_strings) |
354 | + return retvar, CommandComplete(NoopCondition(), ct) |
355 | |
356 | def get_model_config(self): |
357 | """Return the value of the environment's configured options.""" |
358 | @@ -1986,11 +2127,13 @@ |
359 | def set_env_option(self, option, value): |
360 | """Set the value of the option in the environment.""" |
361 | option_value = "%s=%s" % (option, value) |
362 | - return self.juju('model-config', (option_value,)) |
363 | + retvar, ct = self.juju('model-config', (option_value,)) |
364 | + return CommandComplete(NoopCondition(), ct) |
365 | |
366 | def unset_env_option(self, option): |
367 | """Unset the value of the option in the environment.""" |
368 | - return self.juju('model-config', ('--reset', option,)) |
369 | + retvar, ct = self.juju('model-config', ('--reset', option,)) |
370 | + return CommandComplete(NoopCondition(), ct) |
371 | |
372 | @staticmethod |
373 | def _format_cloud_region(cloud=None, region=None): |
374 | @@ -2074,13 +2217,22 @@ |
375 | |
376 | def controller_juju(self, command, args): |
377 | args = ('-c', self.env.controller.name) + args |
378 | - return self.juju(command, args, include_e=False) |
379 | + retvar, ct = self.juju(command, args, include_e=False) |
380 | + return CommandComplete(NoopCondition(), ct) |
381 | |
382 | def get_juju_timings(self): |
383 | - stringified_timings = {} |
384 | - for command, timings in self._backend.juju_timings.items(): |
385 | - stringified_timings[' '.join(command)] = timings |
386 | - return stringified_timings |
387 | + timing_breakdown = [] |
388 | + for ct in self._backend.juju_timings: |
389 | + timing_breakdown.append( |
390 | + { |
391 | + 'command': ct.cmd, |
392 | + 'full_args': ct.full_args, |
393 | + 'start': ct.start, |
394 | + 'end': ct.end, |
395 | + 'total_seconds': ct.total_seconds, |
396 | + } |
397 | + ) |
398 | + return timing_breakdown |
399 | |
400 | def juju_async(self, command, args, include_e=True, timeout=None): |
401 | model = self._cmd_model(include_e, controller=False) |
402 | @@ -2111,11 +2263,13 @@ |
403 | args.extend(['--bind', bind]) |
404 | if alias is not None: |
405 | args.extend([alias]) |
406 | - return self.juju('deploy', tuple(args)) |
407 | + retvar, ct = self.juju('deploy', tuple(args)) |
408 | + return retvar, CommandComplete(WaitAgentsStarted(), ct) |
409 | |
410 | def attach(self, service, resource): |
411 | args = (service, resource) |
412 | - return self.juju('attach', args) |
413 | + retvar, ct = self.juju('attach', args) |
414 | + return retvar, CommandComplete(NoopCondition(), ct) |
415 | |
416 | def list_resources(self, service_or_unit, details=True): |
417 | args = ('--format', 'yaml', service_or_unit) |
418 | @@ -2856,11 +3010,14 @@ |
419 | args = ('generate-tools', '-d', source_dir) |
420 | if stream is not None: |
421 | args += ('--stream', stream) |
422 | - return self.juju('metadata', args, include_e=False) |
423 | + retvar, ct = self.juju('metadata', args, include_e=False) |
424 | + return retvar, CommandComplete(NoopCondition(), ct) |
425 | |
426 | def add_cloud(self, cloud_name, cloud_file): |
427 | - return self.juju('add-cloud', ("--replace", cloud_name, cloud_file), |
428 | - include_e=False) |
429 | + retvar, ct = self.juju( |
430 | + 'add-cloud', ("--replace", cloud_name, cloud_file), |
431 | + include_e=False) |
432 | + return retvar, CommandComplete(NoopCondition(), ct) |
433 | |
434 | def add_cloud_interactive(self, cloud_name, cloud): |
435 | child = self.expect('add-cloud', include_e=False) |
436 | @@ -2970,11 +3127,13 @@ |
437 | |
438 | def disable_command(self, command_set, message=''): |
439 | """Disable a command-set.""" |
440 | - return self.juju('disable-command', (command_set, message)) |
441 | + retvar, ct = self.juju('disable-command', (command_set, message)) |
442 | + return retvar, CommandComplete(NoopCondition(), ct) |
443 | |
444 | def enable_command(self, args): |
445 | """Enable a command-set.""" |
446 | - return self.juju('enable-command', args) |
447 | + retvar, ct = self.juju('enable-command', args) |
448 | + return CommandComplete(NoopCondition(), ct) |
449 | |
450 | def sync_tools(self, local_dir=None, stream=None, source=None): |
451 | """Copy tools into a local directory or model.""" |
452 | @@ -2984,10 +3143,12 @@ |
453 | if source is not None: |
454 | args += ('--source', source) |
455 | if local_dir is None: |
456 | - return self.juju('sync-tools', args) |
457 | + retvar, ct = self.juju('sync-tools', args) |
458 | + return retvar, CommandComplete(NoopCondition(), ct) |
459 | else: |
460 | args += ('--local-dir', local_dir) |
461 | - return self.juju('sync-tools', args, include_e=False) |
462 | + retvar, ct = self.juju('sync-tools', args, include_e=False) |
463 | + return retvar, CommandComplete(NoopCondition(), ct) |
464 | |
465 | def switch(self, model=None, controller=None): |
466 | """Switch between models.""" |
467 | |
468 | === modified file 'jujupy/fake.py' |
469 | --- jujupy/fake.py 2017-03-27 15:15:05 +0000 |
470 | +++ jujupy/fake.py 2017-03-28 03:22:14 +0000 |
471 | @@ -35,6 +35,7 @@ |
472 | JujuData, |
473 | SoftDeadlineExceeded, |
474 | ) |
475 | +from jujupy.client import CommandTime |
476 | |
477 | __metaclass__ = type |
478 | |
479 | @@ -927,6 +928,7 @@ |
480 | num = int(parsed.n or 1) |
481 | self.deploy(model_state, parsed.charm_name, num, |
482 | parsed.service_name, parsed.series) |
483 | + return (0, CommandTime(command, args)) |
484 | if command == 'remove-application': |
485 | model_state.destroy_service(*args) |
486 | if command == 'add-relation': |
487 | @@ -975,8 +977,9 @@ |
488 | self.controller_state.destroy() |
489 | if command == 'kill-controller': |
490 | if self.controller_state.state == 'not-bootstrapped': |
491 | - return |
492 | + return (0, CommandTime(command, args)) |
493 | self.controller_state.destroy(kill=True) |
494 | + return (0, CommandTime(command, args)) |
495 | if command == 'destroy-model': |
496 | if not self.is_feature_enabled('jes'): |
497 | raise JESNotSupported() |
498 | @@ -1027,6 +1030,7 @@ |
499 | self.controller_state.shares.remove(username) |
500 | if command == 'restore-backup': |
501 | model_state.restore_backup() |
502 | + return 0, CommandTime(command, args) |
503 | |
504 | @contextmanager |
505 | def juju_async(self, command, args, used_feature_flags, |
506 | |
507 | === modified file 'jujupy/tests/test_client.py' |
508 | --- jujupy/tests/test_client.py 2017-03-24 23:50:18 +0000 |
509 | +++ jujupy/tests/test_client.py 2017-03-28 03:22:14 +0000 |
510 | @@ -43,6 +43,8 @@ |
511 | AgentUnresolvedError, |
512 | AppError, |
513 | BaseCondition, |
514 | + CommandTime, |
515 | + CommandComplete, |
516 | CannotConnectEnv, |
517 | ConditionList, |
518 | Controller, |
519 | @@ -68,6 +70,7 @@ |
520 | make_safe_config, |
521 | ModelClient, |
522 | NameNotAccepted, |
523 | + NoopCondition, |
524 | NoProvider, |
525 | parse_new_state_server_from_error, |
526 | ProvisioningError, |
527 | @@ -99,9 +102,11 @@ |
528 | from tests import ( |
529 | assert_juju_call, |
530 | client_past_deadline, |
531 | + make_fake_juju_return, |
532 | FakeHomeTestCase, |
533 | FakePopen, |
534 | observable_temp_file, |
535 | + patch_juju_call, |
536 | TestCase, |
537 | ) |
538 | from jujupy.utility import ( |
539 | @@ -176,6 +181,12 @@ |
540 | self.assertIsNot(cloned, backend) |
541 | self.assertIs(soft_deadline, cloned.soft_deadline) |
542 | |
543 | + def test_cloned_backends_share_juju_timings(self): |
544 | + backend = Juju2Backend('/bin/path', '2.0', set(), False) |
545 | + cloned = backend.clone( |
546 | + full_path=None, version=None, debug=None, feature_flags=None) |
547 | + self.assertIs(cloned.juju_timings, backend.juju_timings) |
548 | + |
549 | def test__check_timeouts(self): |
550 | backend = Juju2Backend('/bin/path', '2.0', set(), debug=False, |
551 | soft_deadline=datetime(2015, 1, 2, 3, 4, 5)) |
552 | @@ -348,6 +359,21 @@ |
553 | list(conditions.iter_blocking_state(None))) |
554 | |
555 | |
556 | +class TestNoopCondition(ClientTest): |
557 | + |
558 | + def test_iter_blocking_state_is_noop(self): |
559 | + condition = NoopCondition() |
560 | + called = False |
561 | + for _ in condition.iter_blocking_state({}): |
562 | + called = True |
563 | + self.assertFalse(called) |
564 | + |
565 | + def test_do_raise_raises_Exception(self): |
566 | + condition = NoopCondition() |
567 | + with self.assertRaises(Exception): |
568 | + condition.do_raise('model_name', {}) |
569 | + |
570 | + |
571 | class TestWaitMachineNotPresent(ClientTest): |
572 | |
573 | def test_iter_blocking_state(self): |
574 | @@ -635,7 +661,7 @@ |
575 | |
576 | def test_bootstrap_maas(self): |
577 | env = JujuData('maas', {'type': 'foo', 'region': 'asdf'}) |
578 | - with patch.object(ModelClient, 'juju') as mock: |
579 | + with patch_juju_call(ModelClient) as mock: |
580 | client = ModelClient(env, '2.0-zeta1', None) |
581 | with patch.object(client.env, 'maas', lambda: True): |
582 | with observable_temp_file() as config_file: |
583 | @@ -653,7 +679,7 @@ |
584 | # Disable space constraint with environment variable |
585 | os.environ['JUJU_CI_SPACELESSNESS'] = "1" |
586 | env = JujuData('maas', {'type': 'foo', 'region': 'asdf'}) |
587 | - with patch.object(ModelClient, 'juju') as mock: |
588 | + with patch_juju_call(ModelClient) as mock: |
589 | client = ModelClient(env, '2.0-zeta1', None) |
590 | with patch.object(client.env, 'maas', lambda: True): |
591 | with observable_temp_file() as config_file: |
592 | @@ -669,13 +695,13 @@ |
593 | def test_bootstrap_joyent(self): |
594 | env = JujuData('joyent', { |
595 | 'type': 'joyent', 'sdc-url': 'https://foo.api.joyentcloud.com'}) |
596 | - with patch.object(ModelClient, 'juju', autospec=True) as mock: |
597 | - client = ModelClient(env, '2.0-zeta1', None) |
598 | + client = ModelClient(env, '2.0-zeta1', None) |
599 | + with patch_juju_call(client) as mock: |
600 | with patch.object(client.env, 'joyent', lambda: True): |
601 | with observable_temp_file() as config_file: |
602 | client.bootstrap() |
603 | mock.assert_called_once_with( |
604 | - client, 'bootstrap', ( |
605 | + 'bootstrap', ( |
606 | '--constraints', 'mem=2G cpu-cores=1', |
607 | 'joyent/foo', 'joyent', |
608 | '--config', config_file.name, |
609 | @@ -685,7 +711,7 @@ |
610 | def test_bootstrap(self): |
611 | env = JujuData('foo', {'type': 'bar', 'region': 'baz'}) |
612 | with observable_temp_file() as config_file: |
613 | - with patch.object(ModelClient, 'juju') as mock: |
614 | + with patch_juju_call(ModelClient) as mock: |
615 | client = ModelClient(env, '2.0-zeta1', None) |
616 | client.bootstrap() |
617 | mock.assert_called_with( |
618 | @@ -702,7 +728,7 @@ |
619 | env = JujuData('foo', {'type': 'foo', 'region': 'baz'}) |
620 | client = ModelClient(env, '2.0-zeta1', None) |
621 | with observable_temp_file() as config_file: |
622 | - with patch.object(client, 'juju') as mock: |
623 | + with patch_juju_call(client) as mock: |
624 | client.bootstrap(upload_tools=True) |
625 | mock.assert_called_with( |
626 | 'bootstrap', ( |
627 | @@ -715,7 +741,7 @@ |
628 | env = JujuData('foo', {'type': 'foo', 'region': 'baz'}) |
629 | client = ModelClient(env, '2.0-zeta1', None) |
630 | with observable_temp_file() as config_file: |
631 | - with patch.object(client, 'juju') as mock: |
632 | + with patch_juju_call(client) as mock: |
633 | client.bootstrap(credential='credential_name') |
634 | mock.assert_called_with( |
635 | 'bootstrap', ( |
636 | @@ -728,7 +754,7 @@ |
637 | def test_bootstrap_bootstrap_series(self): |
638 | env = JujuData('foo', {'type': 'bar', 'region': 'baz'}) |
639 | client = ModelClient(env, '2.0-zeta1', None) |
640 | - with patch.object(client, 'juju') as mock: |
641 | + with patch_juju_call(client) as mock: |
642 | with observable_temp_file() as config_file: |
643 | client.bootstrap(bootstrap_series='angsty') |
644 | mock.assert_called_with( |
645 | @@ -742,7 +768,7 @@ |
646 | def test_bootstrap_auto_upgrade(self): |
647 | env = JujuData('foo', {'type': 'bar', 'region': 'baz'}) |
648 | client = ModelClient(env, '2.0-zeta1', None) |
649 | - with patch.object(client, 'juju') as mock: |
650 | + with patch_juju_call(client) as mock: |
651 | with observable_temp_file() as config_file: |
652 | client.bootstrap(auto_upgrade=True) |
653 | mock.assert_called_with( |
654 | @@ -755,7 +781,7 @@ |
655 | def test_bootstrap_no_gui(self): |
656 | env = JujuData('foo', {'type': 'bar', 'region': 'baz'}) |
657 | client = ModelClient(env, '2.0-zeta1', None) |
658 | - with patch.object(client, 'juju') as mock: |
659 | + with patch_juju_call(client) as mock: |
660 | with observable_temp_file() as config_file: |
661 | client.bootstrap(no_gui=True) |
662 | mock.assert_called_with( |
663 | @@ -768,7 +794,7 @@ |
664 | def test_bootstrap_metadata(self): |
665 | env = JujuData('foo', {'type': 'bar', 'region': 'baz'}) |
666 | client = ModelClient(env, '2.0-zeta1', None) |
667 | - with patch.object(client, 'juju') as mock: |
668 | + with patch_juju_call(client) as mock: |
669 | with observable_temp_file() as config_file: |
670 | client.bootstrap(metadata_source='/var/test-source') |
671 | mock.assert_called_with( |
672 | @@ -862,7 +888,7 @@ |
673 | client = ModelClient(model_data, None, None) |
674 | with patch.object(client, 'get_jes_command', |
675 | return_value=jes_command): |
676 | - with patch.object(controller_client, 'juju') as ccj_mock: |
677 | + with patch_juju_call(controller_client) as ccj_mock: |
678 | with observable_temp_file() as config_file: |
679 | controller_client.add_model(model_data) |
680 | ccj_mock.assert_called_once_with( |
681 | @@ -874,7 +900,7 @@ |
682 | client.bootstrap() |
683 | client.env.controller.explicit_region = True |
684 | model = client.env.clone('new-model') |
685 | - with patch.object(client._backend, 'juju') as juju_mock: |
686 | + with patch_juju_call(client._backend) as juju_mock: |
687 | with observable_temp_file() as config_file: |
688 | client.add_model(model) |
689 | juju_mock.assert_called_once_with('add-model', ( |
690 | @@ -886,7 +912,7 @@ |
691 | def test_add_model_by_name(self): |
692 | client = fake_juju_client() |
693 | client.bootstrap() |
694 | - with patch.object(client._backend, 'juju') as juju_mock: |
695 | + with patch_juju_call(client._backend) as juju_mock: |
696 | with observable_temp_file() as config_file: |
697 | client.add_model('new-model') |
698 | juju_mock.assert_called_once_with('add-model', ( |
699 | @@ -902,7 +928,7 @@ |
700 | def test_destroy_model(self): |
701 | env = JujuData('foo', {'type': 'ec2'}) |
702 | client = ModelClient(env, None, None) |
703 | - with patch.object(client, 'juju') as mock: |
704 | + with patch_juju_call(client) as mock: |
705 | client.destroy_model() |
706 | mock.assert_called_with( |
707 | 'destroy-model', ('foo', '-y'), |
708 | @@ -911,7 +937,7 @@ |
709 | def test_destroy_model_azure(self): |
710 | env = JujuData('foo', {'type': 'azure'}) |
711 | client = ModelClient(env, None, None) |
712 | - with patch.object(client, 'juju') as mock: |
713 | + with patch_juju_call(client) as mock: |
714 | client.destroy_model() |
715 | mock.assert_called_with( |
716 | 'destroy-model', ('foo', '-y'), |
717 | @@ -920,7 +946,7 @@ |
718 | def test_destroy_model_gce(self): |
719 | env = JujuData('foo', {'type': 'gce'}) |
720 | client = ModelClient(env, None, None) |
721 | - with patch.object(client, 'juju') as mock: |
722 | + with patch_juju_call(client) as mock: |
723 | client.destroy_model() |
724 | mock.assert_called_with( |
725 | 'destroy-model', ('foo', '-y'), |
726 | @@ -928,7 +954,7 @@ |
727 | |
728 | def test_kill_controller(self): |
729 | client = ModelClient(JujuData('foo', {'type': 'ec2'}), None, None) |
730 | - with patch.object(client, 'juju') as juju_mock: |
731 | + with patch_juju_call(client) as juju_mock: |
732 | client.kill_controller() |
733 | juju_mock.assert_called_once_with( |
734 | 'kill-controller', ('foo', '-y'), check=False, include_e=False, |
735 | @@ -936,7 +962,7 @@ |
736 | |
737 | def test_kill_controller_check(self): |
738 | client = ModelClient(JujuData('foo', {'type': 'ec2'}), None, None) |
739 | - with patch.object(client, 'juju') as juju_mock: |
740 | + with patch_juju_call(client) as juju_mock: |
741 | client.kill_controller(check=True) |
742 | juju_mock.assert_called_once_with( |
743 | 'kill-controller', ('foo', '-y'), check=True, include_e=False, |
744 | @@ -946,7 +972,7 @@ |
745 | client = ModelClient(JujuData('foo', {'type': 'azure'}), None, None) |
746 | with patch.object(client, 'get_jes_command', |
747 | return_value=jes_command): |
748 | - with patch.object(client, 'juju') as juju_mock: |
749 | + with patch_juju_call(client) as juju_mock: |
750 | client.kill_controller() |
751 | juju_mock.assert_called_once_with( |
752 | kill_command, ('foo', '-y'), check=False, include_e=False, |
753 | @@ -954,7 +980,7 @@ |
754 | |
755 | def test_kill_controller_gce(self): |
756 | client = ModelClient(JujuData('foo', {'type': 'gce'}), None, None) |
757 | - with patch.object(client, 'juju') as juju_mock: |
758 | + with patch_juju_call(client) as juju_mock: |
759 | client.kill_controller() |
760 | juju_mock.assert_called_once_with( |
761 | 'kill-controller', ('foo', '-y'), check=False, include_e=False, |
762 | @@ -962,7 +988,7 @@ |
763 | |
764 | def test_destroy_controller(self): |
765 | client = ModelClient(JujuData('foo', {'type': 'ec2'}), None, None) |
766 | - with patch.object(client, 'juju') as juju_mock: |
767 | + with patch_juju_call(client) as juju_mock: |
768 | client.destroy_controller() |
769 | juju_mock.assert_called_once_with( |
770 | 'destroy-controller', ('foo', '-y'), include_e=False, |
771 | @@ -970,7 +996,7 @@ |
772 | |
773 | def test_destroy_controller_all_models(self): |
774 | client = ModelClient(JujuData('foo', {'type': 'ec2'}), None, None) |
775 | - with patch.object(client, 'juju') as juju_mock: |
776 | + with patch_juju_call(client) as juju_mock: |
777 | client.destroy_controller(all_models=True) |
778 | juju_mock.assert_called_once_with( |
779 | 'destroy-controller', ('foo', '-y', '--destroy-all-models'), |
780 | @@ -988,7 +1014,9 @@ |
781 | side_effect=raise_error) as mock: |
782 | yield mock |
783 | else: |
784 | - with patch.object(target, attribute, autospec=True) as mock: |
785 | + with patch.object( |
786 | + target, attribute, autospec=True, |
787 | + return_value=make_fake_juju_return()) as mock: |
788 | yield mock |
789 | |
790 | with patch_raise(client, 'destroy_controller', destroy_raises |
791 | @@ -1219,21 +1247,21 @@ |
792 | def test_deploy_non_joyent(self): |
793 | env = ModelClient( |
794 | JujuData('foo', {'type': 'local'}), '1.234-76', None) |
795 | - with patch.object(env, 'juju') as mock_juju: |
796 | + with patch_juju_call(env) as mock_juju: |
797 | env.deploy('mondogb') |
798 | mock_juju.assert_called_with('deploy', ('mondogb',)) |
799 | |
800 | def test_deploy_joyent(self): |
801 | env = ModelClient( |
802 | JujuData('foo', {'type': 'local'}), '1.234-76', None) |
803 | - with patch.object(env, 'juju') as mock_juju: |
804 | + with patch_juju_call(env) as mock_juju: |
805 | env.deploy('mondogb') |
806 | mock_juju.assert_called_with('deploy', ('mondogb',)) |
807 | |
808 | def test_deploy_repository(self): |
809 | env = ModelClient( |
810 | JujuData('foo', {'type': 'local'}), '1.234-76', None) |
811 | - with patch.object(env, 'juju') as mock_juju: |
812 | + with patch_juju_call(env) as mock_juju: |
813 | env.deploy('/home/jrandom/repo/mongodb') |
814 | mock_juju.assert_called_with( |
815 | 'deploy', ('/home/jrandom/repo/mongodb',)) |
816 | @@ -1241,7 +1269,7 @@ |
817 | def test_deploy_to(self): |
818 | env = ModelClient( |
819 | JujuData('foo', {'type': 'local'}), '1.234-76', None) |
820 | - with patch.object(env, 'juju') as mock_juju: |
821 | + with patch_juju_call(env) as mock_juju: |
822 | env.deploy('mondogb', to='0') |
823 | mock_juju.assert_called_with( |
824 | 'deploy', ('mondogb', '--to', '0')) |
825 | @@ -1249,7 +1277,7 @@ |
826 | def test_deploy_service(self): |
827 | env = ModelClient( |
828 | JujuData('foo', {'type': 'local'}), '1.234-76', None) |
829 | - with patch.object(env, 'juju') as mock_juju: |
830 | + with patch_juju_call(env) as mock_juju: |
831 | env.deploy('local:mondogb', service='my-mondogb') |
832 | mock_juju.assert_called_with( |
833 | 'deploy', ('local:mondogb', 'my-mondogb',)) |
834 | @@ -1257,14 +1285,14 @@ |
835 | def test_deploy_force(self): |
836 | env = ModelClient( |
837 | JujuData('foo', {'type': 'local'}), '1.234-76', None) |
838 | - with patch.object(env, 'juju') as mock_juju: |
839 | + with patch_juju_call(env) as mock_juju: |
840 | env.deploy('local:mondogb', force=True) |
841 | mock_juju.assert_called_with('deploy', ('local:mondogb', '--force',)) |
842 | |
843 | def test_deploy_series(self): |
844 | env = ModelClient( |
845 | JujuData('foo', {'type': 'local'}), '1.234-76', None) |
846 | - with patch.object(env, 'juju') as mock_juju: |
847 | + with patch_juju_call(env) as mock_juju: |
848 | env.deploy('local:blah', series='xenial') |
849 | mock_juju.assert_called_with( |
850 | 'deploy', ('local:blah', '--series', 'xenial')) |
851 | @@ -1272,14 +1300,14 @@ |
852 | def test_deploy_multiple(self): |
853 | env = ModelClient( |
854 | JujuData('foo', {'type': 'local'}), '1.234-76', None) |
855 | - with patch.object(env, 'juju') as mock_juju: |
856 | + with patch_juju_call(env) as mock_juju: |
857 | env.deploy('local:blah', num=2) |
858 | mock_juju.assert_called_with( |
859 | 'deploy', ('local:blah', '-n', '2')) |
860 | |
861 | def test_deploy_resource(self): |
862 | env = ModelClient(JujuData('foo', {'type': 'local'}), None, None) |
863 | - with patch.object(env, 'juju') as mock_juju: |
864 | + with patch_juju_call(env) as mock_juju: |
865 | env.deploy('local:blah', resource='foo=/path/dir') |
866 | mock_juju.assert_called_with( |
867 | 'deploy', ('local:blah', '--resource', 'foo=/path/dir')) |
868 | @@ -1287,7 +1315,7 @@ |
869 | def test_deploy_storage(self): |
870 | env = EnvJujuClient1X( |
871 | SimpleEnvironment('foo', {'type': 'local'}), '1.234-76', None) |
872 | - with patch.object(env, 'juju') as mock_juju: |
873 | + with patch_juju_call(env) as mock_juju: |
874 | env.deploy('mondogb', storage='rootfs,1G') |
875 | mock_juju.assert_called_with( |
876 | 'deploy', ('mondogb', '--storage', 'rootfs,1G')) |
877 | @@ -1295,27 +1323,27 @@ |
878 | def test_deploy_constraints(self): |
879 | env = EnvJujuClient1X( |
880 | SimpleEnvironment('foo', {'type': 'local'}), '1.234-76', None) |
881 | - with patch.object(env, 'juju') as mock_juju: |
882 | + with patch_juju_call(env) as mock_juju: |
883 | env.deploy('mondogb', constraints='virt-type=kvm') |
884 | mock_juju.assert_called_with( |
885 | 'deploy', ('mondogb', '--constraints', 'virt-type=kvm')) |
886 | |
887 | def test_deploy_bind(self): |
888 | env = ModelClient(JujuData('foo', {'type': 'local'}), None, None) |
889 | - with patch.object(env, 'juju') as mock_juju: |
890 | + with patch_juju_call(env) as mock_juju: |
891 | env.deploy('mydb', bind='backspace') |
892 | mock_juju.assert_called_with('deploy', ('mydb', '--bind', 'backspace')) |
893 | |
894 | def test_deploy_aliased(self): |
895 | env = ModelClient(JujuData('foo', {'type': 'local'}), None, None) |
896 | - with patch.object(env, 'juju') as mock_juju: |
897 | + with patch_juju_call(env) as mock_juju: |
898 | env.deploy('local:blah', alias='blah-blah') |
899 | mock_juju.assert_called_with( |
900 | 'deploy', ('local:blah', 'blah-blah')) |
901 | |
902 | def test_attach(self): |
903 | env = ModelClient(JujuData('foo', {'type': 'local'}), None, None) |
904 | - with patch.object(env, 'juju') as mock_juju: |
905 | + with patch_juju_call(env) as mock_juju: |
906 | env.attach('foo', resource='foo=/path/dir') |
907 | mock_juju.assert_called_with('attach', ('foo', 'foo=/path/dir')) |
908 | |
909 | @@ -1383,7 +1411,7 @@ |
910 | def test_deploy_bundle_2x(self): |
911 | client = ModelClient(JujuData('an_env', None), |
912 | '1.23-series-arch', None) |
913 | - with patch.object(client, 'juju') as mock_juju: |
914 | + with patch_juju_call(client) as mock_juju: |
915 | client.deploy_bundle('bundle:~juju-qa/some-bundle') |
916 | mock_juju.assert_called_with( |
917 | 'deploy', ('bundle:~juju-qa/some-bundle'), timeout=3600) |
918 | @@ -1391,7 +1419,7 @@ |
919 | def test_deploy_bundle_template(self): |
920 | client = ModelClient(JujuData('an_env', None), |
921 | '1.23-series-arch', None) |
922 | - with patch.object(client, 'juju') as mock_juju: |
923 | + with patch_juju_call(client) as mock_juju: |
924 | client.deploy_bundle('bundle:~juju-qa/some-{container}-bundle') |
925 | mock_juju.assert_called_with( |
926 | 'deploy', ('bundle:~juju-qa/some-lxd-bundle'), timeout=3600) |
927 | @@ -1399,7 +1427,7 @@ |
928 | def test_upgrade_charm(self): |
929 | env = ModelClient( |
930 | JujuData('foo', {'type': 'local'}), '2.34-74', None) |
931 | - with patch.object(env, 'juju') as mock_juju: |
932 | + with patch_juju_call(env) as mock_juju: |
933 | env.upgrade_charm('foo-service', |
934 | '/bar/repository/angsty/mongodb') |
935 | mock_juju.assert_called_once_with( |
936 | @@ -1409,7 +1437,7 @@ |
937 | def test_remove_service(self): |
938 | env = ModelClient( |
939 | JujuData('foo', {'type': 'local'}), '1.234-76', None) |
940 | - with patch.object(env, 'juju') as mock_juju: |
941 | + with patch_juju_call(env) as mock_juju: |
942 | env.remove_service('mondogb') |
943 | mock_juju.assert_called_with('remove-application', ('mondogb',)) |
944 | |
945 | @@ -1578,7 +1606,7 @@ |
946 | |
947 | def test_remove_machine(self): |
948 | client = fake_juju_client() |
949 | - with patch.object(client._backend, 'juju') as juju_mock: |
950 | + with patch_juju_call(client._backend) as juju_mock: |
951 | condition = client.remove_machine('0') |
952 | call = backend_call( |
953 | client, 'remove-machine', ('0',), 'name:name') |
954 | @@ -1587,7 +1615,7 @@ |
955 | |
956 | def test_remove_machine_force(self): |
957 | client = fake_juju_client() |
958 | - with patch.object(client._backend, 'juju') as juju_mock: |
959 | + with patch_juju_call(client._backend) as juju_mock: |
960 | client.remove_machine('0', force=True) |
961 | call = backend_call( |
962 | client, 'remove-machine', ('--force', '0'), 'name:name') |
963 | @@ -1982,7 +2010,7 @@ |
964 | |
965 | def test_list_models(self): |
966 | client = ModelClient(JujuData('foo'), None, None) |
967 | - with patch.object(client, 'juju') as j_mock: |
968 | + with patch_juju_call(client) as j_mock: |
969 | client.list_models() |
970 | j_mock.assert_called_once_with( |
971 | 'list-models', ('-c', 'foo'), include_e=False) |
972 | @@ -2194,7 +2222,7 @@ |
973 | |
974 | def test_list_controllers(self): |
975 | client = ModelClient(JujuData('foo'), None, None) |
976 | - with patch.object(client, 'juju') as j_mock: |
977 | + with patch_juju_call(client) as j_mock: |
978 | client.list_controllers() |
979 | j_mock.assert_called_once_with('list-controllers', (), include_e=False) |
980 | |
981 | @@ -2662,7 +2690,7 @@ |
982 | |
983 | def test_set_model_constraints(self): |
984 | client = ModelClient(JujuData('bar', {}), None, '/foo') |
985 | - with patch.object(client, 'juju') as juju_mock: |
986 | + with patch_juju_call(client) as juju_mock: |
987 | client.set_model_constraints({'bar': 'baz'}) |
988 | juju_mock.assert_called_once_with('set-model-constraints', |
989 | ('bar=baz',)) |
990 | @@ -2948,7 +2976,7 @@ |
991 | def test_restore_backup(self): |
992 | env = JujuData('qux') |
993 | client = ModelClient(env, None, '/foobar/baz') |
994 | - with patch.object(client, 'juju') as gjo_mock: |
995 | + with patch_juju_call(client) as gjo_mock: |
996 | client.restore_backup('quxx') |
997 | gjo_mock.assert_called_once_with( |
998 | 'restore-backup', |
999 | @@ -3038,12 +3066,33 @@ |
1000 | self.assertEqual(0, po_mock.call_count) |
1001 | |
1002 | def test_get_juju_timings(self): |
1003 | + first_start = datetime(2017, 3, 22, 23, 36, 52, 0) |
1004 | + first_end = first_start + timedelta(seconds=2) |
1005 | + second_start = datetime(2017, 5, 22, 23, 36, 52, 0) |
1006 | env = JujuData('foo') |
1007 | client = ModelClient(env, None, 'my/juju/bin') |
1008 | - client._backend.juju_timings = {("juju", "op1"): [1], |
1009 | - ("juju", "op2"): [2]} |
1010 | + client._backend.juju_timings.extend([ |
1011 | + CommandTime('command1', ['command1', 'arg1'], start=first_start), |
1012 | + CommandTime( |
1013 | + 'command2', ['command2', 'arg1', 'arg2'], start=second_start)]) |
1014 | + client._backend.juju_timings[0].actual_completion(end=first_end) |
1015 | flattened_timings = client.get_juju_timings() |
1016 | - expected = {"juju op1": [1], "juju op2": [2]} |
1017 | + expected = [ |
1018 | + { |
1019 | + 'command': 'command1', |
1020 | + 'full_args': ['command1', 'arg1'], |
1021 | + 'start': first_start, |
1022 | + 'end': first_end, |
1023 | + 'total_seconds': 2, |
1024 | + }, |
1025 | + { |
1026 | + 'command': 'command2', |
1027 | + 'full_args': ['command2', 'arg1', 'arg2'], |
1028 | + 'start': second_start, |
1029 | + 'end': None, |
1030 | + 'total_seconds': None, |
1031 | + } |
1032 | + ] |
1033 | self.assertEqual(flattened_timings, expected) |
1034 | |
1035 | def test_deployer(self): |
1036 | @@ -3230,7 +3279,7 @@ |
1037 | |
1038 | def test_set_config(self): |
1039 | client = ModelClient(JujuData('bar', {}), None, '/foo') |
1040 | - with patch.object(client, 'juju') as juju_mock: |
1041 | + with patch_juju_call(client) as juju_mock: |
1042 | client.set_config('foo', {'bar': 'baz'}) |
1043 | juju_mock.assert_called_once_with('config', ('foo', 'bar=baz')) |
1044 | |
1045 | @@ -3285,7 +3334,7 @@ |
1046 | |
1047 | def test_upgrade_mongo(self): |
1048 | client = ModelClient(JujuData('bar', {}), None, '/foo') |
1049 | - with patch.object(client, 'juju') as juju_mock: |
1050 | + with patch_juju_call(client) as juju_mock: |
1051 | client.upgrade_mongo() |
1052 | juju_mock.assert_called_once_with('upgrade-mongo', ()) |
1053 | |
1054 | @@ -3330,7 +3379,7 @@ |
1055 | default_model = fake_client.model_name |
1056 | default_controller = fake_client.env.controller.name |
1057 | |
1058 | - with patch.object(fake_client, 'juju', return_value=True): |
1059 | + with patch_juju_call(fake_client): |
1060 | fake_client.revoke(username) |
1061 | fake_client.juju.assert_called_with('revoke', |
1062 | ('-c', default_controller, |
1063 | @@ -3410,7 +3459,7 @@ |
1064 | env = JujuData('foo') |
1065 | username = 'fakeuser' |
1066 | client = ModelClient(env, None, None) |
1067 | - with patch.object(client, 'juju') as mock: |
1068 | + with patch_juju_call(client) as mock: |
1069 | client.disable_user(username) |
1070 | mock.assert_called_with( |
1071 | 'disable-user', ('-c', 'foo', 'fakeuser'), include_e=False) |
1072 | @@ -3419,7 +3468,7 @@ |
1073 | env = JujuData('foo') |
1074 | username = 'fakeuser' |
1075 | client = ModelClient(env, None, None) |
1076 | - with patch.object(client, 'juju') as mock: |
1077 | + with patch_juju_call(client) as mock: |
1078 | client.enable_user(username) |
1079 | mock.assert_called_with( |
1080 | 'enable-user', ('-c', 'foo', 'fakeuser'), include_e=False) |
1081 | @@ -3427,7 +3476,7 @@ |
1082 | def test_logout(self): |
1083 | env = JujuData('foo') |
1084 | client = ModelClient(env, None, None) |
1085 | - with patch.object(client, 'juju') as mock: |
1086 | + with patch_juju_call(client) as mock: |
1087 | client.logout() |
1088 | mock.assert_called_with( |
1089 | 'logout', ('-c', 'foo'), include_e=False) |
1090 | @@ -3678,32 +3727,32 @@ |
1091 | |
1092 | def test_disable_command(self): |
1093 | client = ModelClient(JujuData('foo'), None, None) |
1094 | - with patch.object(client, 'juju', autospec=True) as mock: |
1095 | + with patch_juju_call(client) as mock: |
1096 | client.disable_command('all', 'message') |
1097 | mock.assert_called_once_with('disable-command', ('all', 'message')) |
1098 | |
1099 | def test_enable_command(self): |
1100 | client = ModelClient(JujuData('foo'), None, None) |
1101 | - with patch.object(client, 'juju', autospec=True) as mock: |
1102 | + with patch_juju_call(client) as mock: |
1103 | client.enable_command('all') |
1104 | mock.assert_called_once_with('enable-command', 'all') |
1105 | |
1106 | def test_sync_tools(self): |
1107 | client = ModelClient(JujuData('foo'), None, None) |
1108 | - with patch.object(client, 'juju', autospec=True) as mock: |
1109 | + with patch_juju_call(client) as mock: |
1110 | client.sync_tools() |
1111 | mock.assert_called_once_with('sync-tools', ()) |
1112 | |
1113 | def test_sync_tools_local_dir(self): |
1114 | client = ModelClient(JujuData('foo'), None, None) |
1115 | - with patch.object(client, 'juju', autospec=True) as mock: |
1116 | + with patch_juju_call(client) as mock: |
1117 | client.sync_tools('/agents') |
1118 | mock.assert_called_once_with('sync-tools', ('--local-dir', '/agents'), |
1119 | include_e=False) |
1120 | |
1121 | def test_generate_tool(self): |
1122 | client = ModelClient(JujuData('foo'), None, None) |
1123 | - with patch.object(client, 'juju', autospec=True) as mock: |
1124 | + with patch_juju_call(client) as mock: |
1125 | client.generate_tool('/agents') |
1126 | mock.assert_called_once_with('metadata', |
1127 | ('generate-tools', '-d', '/agents'), |
1128 | @@ -3711,7 +3760,7 @@ |
1129 | |
1130 | def test_generate_tool_with_stream(self): |
1131 | client = ModelClient(JujuData('foo'), None, None) |
1132 | - with patch.object(client, 'juju', autospec=True) as mock: |
1133 | + with patch_juju_call(client) as mock: |
1134 | client.generate_tool('/agents', "testing") |
1135 | mock.assert_called_once_with( |
1136 | 'metadata', ('generate-tools', '-d', '/agents', |
1137 | @@ -3719,7 +3768,7 @@ |
1138 | |
1139 | def test_add_cloud(self): |
1140 | client = ModelClient(JujuData('foo'), None, None) |
1141 | - with patch.object(client, 'juju', autospec=True) as mock: |
1142 | + with patch_juju_call(client) as mock: |
1143 | client.add_cloud('localhost', 'cfile') |
1144 | mock.assert_called_once_with('add-cloud', |
1145 | ('--replace', 'localhost', 'cfile'), |
1146 | @@ -3728,7 +3777,7 @@ |
1147 | def test_switch(self): |
1148 | def run_switch_test(expect, model=None, controller=None): |
1149 | client = ModelClient(JujuData('foo'), None, None) |
1150 | - with patch.object(client, 'juju', autospec=True) as mock: |
1151 | + with patch_juju_call(client) as mock: |
1152 | client.switch(model=model, controller=controller) |
1153 | mock.assert_called_once_with('switch', (expect,), include_e=False) |
1154 | run_switch_test('default', 'default') |
1155 | @@ -6021,3 +6070,125 @@ |
1156 | self.assertEqual(host, "2001:db8::3") |
1157 | fake_client.status_until.assert_called_once_with(timeout=600) |
1158 | self.assertEqual(self.log_stream.getvalue(), "") |
1159 | + |
1160 | + |
1161 | +class TestCommandTime(TestCase): |
1162 | + |
1163 | + def test_default_values(self): |
1164 | + full_args = ['juju', '--showlog', 'bootstrap'] |
1165 | + utcnow = datetime(2017, 3, 22, 23, 36, 52, 530631) |
1166 | + with patch('jujupy.client.datetime', autospec=True) as m_dt: |
1167 | + m_dt.utcnow.return_value = utcnow |
1168 | + ct = CommandTime('bootstrap', full_args) |
1169 | + self.assertEqual(ct.cmd, 'bootstrap') |
1170 | + self.assertEqual(ct.full_args, full_args) |
1171 | + self.assertEqual(ct.envvars, None) |
1172 | + self.assertEqual(ct.start, utcnow) |
1173 | + self.assertEqual(ct.end, None) |
1174 | + |
1175 | + def test_set_start_time(self): |
1176 | + ct = CommandTime('cmd', [], start='abc') |
1177 | + self.assertEqual(ct.start, 'abc') |
1178 | + |
1179 | + def test_set_envvar(self): |
1180 | + details = {'abc': 123} |
1181 | + ct = CommandTime('cmd', [], envvars=details) |
1182 | + self.assertEqual(ct.envvars, details) |
1183 | + |
1184 | + def test_actual_completion_sets_default(self): |
1185 | + utcnow = datetime(2017, 3, 22, 23, 36, 52, 530631) |
1186 | + ct = CommandTime('cmd', []) |
1187 | + with patch('jujupy.client.datetime', autospec=True) as m_dt: |
1188 | + m_dt.utcnow.return_value = utcnow |
1189 | + ct.actual_completion() |
1190 | + self.assertEqual(ct.end, utcnow) |
1191 | + |
1192 | + def test_actual_completion_idempotent(self): |
1193 | + ct = CommandTime('cmd', []) |
1194 | + ct.actual_completion(end='a') |
1195 | + ct.actual_completion(end='b') |
1196 | + self.assertEqual(ct.end, 'a') |
1197 | + |
1198 | + def test_actual_completion_set_value(self): |
1199 | + utcnow = datetime(2017, 3, 22, 23, 36, 52, 530631) |
1200 | + ct = CommandTime('cmd', []) |
1201 | + ct.actual_completion(end=utcnow) |
1202 | + self.assertEqual(ct.end, utcnow) |
1203 | + |
1204 | + def test_total_seconds_returns_None_when_not_complete(self): |
1205 | + ct = CommandTime('cmd', []) |
1206 | + self.assertEqual(ct.total_seconds, None) |
1207 | + |
1208 | + def test_total_seconds_returns_seconds_taken_to_complete(self): |
1209 | + utcstart = datetime(2017, 3, 22, 23, 36, 52, 530631) |
1210 | + utcend = utcstart + timedelta(seconds=1) |
1211 | + with patch('jujupy.client.datetime', autospec=True) as m_dt: |
1212 | + m_dt.utcnow.side_effect = [utcstart, utcend] |
1213 | + ct = CommandTime('cmd', []) |
1214 | + ct.actual_completion() |
1215 | + self.assertEqual(ct.total_seconds, 1) |
1216 | + |
1217 | + |
1218 | +class TestCommandComplete(TestCase): |
1219 | + |
1220 | + def test_default_values(self): |
1221 | + ct = CommandTime('cmd', []) |
1222 | + base_condition = BaseCondition() |
1223 | + cc = CommandComplete(base_condition, ct) |
1224 | + |
1225 | + self.assertEqual(cc.timeout, 300) |
1226 | + self.assertEqual(cc.already_satisfied, False) |
1227 | + self.assertEqual(cc._real_condition, base_condition) |
1228 | + self.assertEqual(cc.command_time, ct) |
1229 | + # actual_completion shouldn't be set as the condition is not already |
1230 | + # satisfied. |
1231 | + self.assertEqual(cc.command_time.end, None) |
1232 | + |
1233 | + def test_sets_total_seconds_when_already_satisfied(self): |
1234 | + base_condition = BaseCondition(already_satisfied=True) |
1235 | + ct = CommandTime('cmd', []) |
1236 | + cc = CommandComplete(base_condition, ct) |
1237 | + |
1238 | + self.assertIsNotNone(cc.command_time.total_seconds) |
1239 | + |
1240 | + def test_calls_wrapper_condition_iter(self): |
1241 | + class TestCondition(BaseCondition): |
1242 | + def iter_blocking_state(self, status): |
1243 | + yield 'item', status |
1244 | + |
1245 | + ct = CommandTime('cmd', []) |
1246 | + cc = CommandComplete(TestCondition(), ct) |
1247 | + |
1248 | + k, v = next(cc.iter_blocking_state('status_obj')) |
1249 | + self.assertEqual(k, 'item') |
1250 | + self.assertEqual(v, 'status_obj') |
1251 | + |
1252 | + def test_sets_actual_completion_when_complete(self): |
1253 | + """When the condition hits success must set actual_completion.""" |
1254 | + class TestCondition(BaseCondition): |
1255 | + def __init__(self): |
1256 | + super(TestCondition, self).__init__() |
1257 | + self._already_called = False |
1258 | + |
1259 | + def iter_blocking_state(self, status): |
1260 | + if not self._already_called: |
1261 | + self._already_called = True |
1262 | + yield 'item', status |
1263 | + |
1264 | + ct = CommandTime('cmd', []) |
1265 | + cc = CommandComplete(TestCondition(), ct) |
1266 | + |
1267 | + next(cc.iter_blocking_state('status_obj')) |
1268 | + self.assertIsNone(cc.command_time.end) |
1269 | + next(cc.iter_blocking_state('status_obj'), None) |
1270 | + self.assertIsNotNone(cc.command_time.end) |
1271 | + |
1272 | + def test_raises_exception_with_command_details(self): |
1273 | + ct = CommandTime('cmd', ['cmd', 'arg1', 'arg2']) |
1274 | + cc = CommandComplete(BaseCondition(), ct) |
1275 | + |
1276 | + with self.assertRaises(RuntimeError) as ex: |
1277 | + cc.do_raise('status') |
1278 | + self.assertEqual( |
1279 | + str(ex.exception), |
1280 | + 'Timed out waiting for "cmd" command to complete: "cmd arg1 arg2"') |
1281 | |
1282 | === modified file 'jujupy/tests/test_version_client.py' |
1283 | --- jujupy/tests/test_version_client.py 2017-03-24 23:50:18 +0000 |
1284 | +++ jujupy/tests/test_version_client.py 2017-03-28 03:22:14 +0000 |
1285 | @@ -69,6 +69,7 @@ |
1286 | ) |
1287 | from tests import ( |
1288 | assert_juju_call, |
1289 | + patch_juju_call, |
1290 | FakeHomeTestCase, |
1291 | FakePopen, |
1292 | observable_temp_file, |
1293 | @@ -367,7 +368,7 @@ |
1294 | def test_bootstrap(self): |
1295 | env = JujuData('foo', {'type': 'bar', 'region': 'baz'}) |
1296 | with observable_temp_file() as config_file: |
1297 | - with patch.object(ModelClientRC, 'juju') as mock: |
1298 | + with patch_juju_call(ModelClientRC) as mock: |
1299 | client = ModelClientRC(env, '2.0-zeta1', None) |
1300 | client.bootstrap() |
1301 | mock.assert_called_with( |
1302 | @@ -432,7 +433,7 @@ |
1303 | def test_upgrade_juju_nonlocal(self): |
1304 | client = EnvJujuClient1X( |
1305 | SimpleEnvironment('foo', {'type': 'nonlocal'}), '1.234-76', None) |
1306 | - with patch.object(client, 'juju') as juju_mock: |
1307 | + with patch_juju_call(client) as juju_mock: |
1308 | client.upgrade_juju() |
1309 | juju_mock.assert_called_with( |
1310 | 'upgrade-juju', ('--version', '1.234')) |
1311 | @@ -440,7 +441,7 @@ |
1312 | def test_upgrade_juju_local(self): |
1313 | client = EnvJujuClient1X( |
1314 | SimpleEnvironment('foo', {'type': 'local'}), '1.234-76', None) |
1315 | - with patch.object(client, 'juju') as juju_mock: |
1316 | + with patch_juju_call(client) as juju_mock: |
1317 | client.upgrade_juju() |
1318 | juju_mock.assert_called_with( |
1319 | 'upgrade-juju', ('--version', '1.234', '--upload-tools',)) |
1320 | @@ -448,7 +449,7 @@ |
1321 | def test_upgrade_juju_no_force_version(self): |
1322 | client = EnvJujuClient1X( |
1323 | SimpleEnvironment('foo', {'type': 'local'}), '1.234-76', None) |
1324 | - with patch.object(client, 'juju') as juju_mock: |
1325 | + with patch_juju_call(client) as juju_mock: |
1326 | client.upgrade_juju(force_version=False) |
1327 | juju_mock.assert_called_with( |
1328 | 'upgrade-juju', ('--upload-tools',)) |
1329 | @@ -467,7 +468,7 @@ |
1330 | |
1331 | def test_bootstrap_maas(self): |
1332 | env = SimpleEnvironment('maas') |
1333 | - with patch.object(EnvJujuClient1X, 'juju') as mock: |
1334 | + with patch_juju_call(EnvJujuClient1X) as mock: |
1335 | client = EnvJujuClient1X(env, None, None) |
1336 | with patch.object(client.env, 'maas', lambda: True): |
1337 | client.bootstrap() |
1338 | @@ -475,24 +476,24 @@ |
1339 | |
1340 | def test_bootstrap_joyent(self): |
1341 | env = SimpleEnvironment('joyent') |
1342 | - with patch.object(EnvJujuClient1X, 'juju', autospec=True) as mock: |
1343 | + with patch_juju_call(EnvJujuClient1X) as mock: |
1344 | client = EnvJujuClient1X(env, None, None) |
1345 | with patch.object(client.env, 'joyent', lambda: True): |
1346 | client.bootstrap() |
1347 | mock.assert_called_once_with( |
1348 | - client, 'bootstrap', ('--constraints', 'mem=2G cpu-cores=1')) |
1349 | + 'bootstrap', ('--constraints', 'mem=2G cpu-cores=1')) |
1350 | |
1351 | def test_bootstrap(self): |
1352 | env = SimpleEnvironment('foo') |
1353 | client = EnvJujuClient1X(env, None, None) |
1354 | - with patch.object(client, 'juju') as mock: |
1355 | + with patch_juju_call(client) as mock: |
1356 | client.bootstrap() |
1357 | mock.assert_called_with('bootstrap', ('--constraints', 'mem=2G')) |
1358 | |
1359 | def test_bootstrap_upload_tools(self): |
1360 | env = SimpleEnvironment('foo') |
1361 | client = EnvJujuClient1X(env, None, None) |
1362 | - with patch.object(client, 'juju') as mock: |
1363 | + with patch_juju_call(client) as mock: |
1364 | client.bootstrap(upload_tools=True) |
1365 | mock.assert_called_with( |
1366 | 'bootstrap', ('--upload-tools', '--constraints', 'mem=2G')) |
1367 | @@ -508,7 +509,7 @@ |
1368 | env.update_config({ |
1369 | 'default-series': 'angsty', |
1370 | }) |
1371 | - with patch.object(client, 'juju') as mock: |
1372 | + with patch_juju_call(client) as mock: |
1373 | client.bootstrap(bootstrap_series='angsty') |
1374 | mock.assert_called_with('bootstrap', ('--constraints', 'mem=2G')) |
1375 | |
1376 | @@ -573,7 +574,8 @@ |
1377 | def test_destroy_environment(self): |
1378 | env = SimpleEnvironment('foo', {'type': 'ec2'}) |
1379 | client = EnvJujuClient1X(env, None, None) |
1380 | - with patch.object(client, 'juju') as mock: |
1381 | + with patch.object( |
1382 | + client, 'juju', autospec=True, return_value=(0, None)) as mock: |
1383 | client.destroy_environment() |
1384 | mock.assert_called_with( |
1385 | 'destroy-environment', ('foo', '--force', '-y'), |
1386 | @@ -582,7 +584,9 @@ |
1387 | def test_destroy_environment_no_force(self): |
1388 | env = SimpleEnvironment('foo', {'type': 'ec2'}) |
1389 | client = EnvJujuClient1X(env, None, None) |
1390 | - with patch.object(client, 'juju') as mock: |
1391 | + with patch.object( |
1392 | + client, 'juju', |
1393 | + autospec=True, return_value=(0, None)) as mock: |
1394 | client.destroy_environment(force=False) |
1395 | mock.assert_called_with( |
1396 | 'destroy-environment', ('foo', '-y'), |
1397 | @@ -591,7 +595,8 @@ |
1398 | def test_destroy_environment_azure(self): |
1399 | env = SimpleEnvironment('foo', {'type': 'azure'}) |
1400 | client = EnvJujuClient1X(env, None, None) |
1401 | - with patch.object(client, 'juju') as mock: |
1402 | + with patch.object( |
1403 | + client, 'juju', autospec=True, return_value=(0, None)) as mock: |
1404 | client.destroy_environment(force=False) |
1405 | mock.assert_called_with( |
1406 | 'destroy-environment', ('foo', '-y'), |
1407 | @@ -600,7 +605,9 @@ |
1408 | def test_destroy_environment_gce(self): |
1409 | env = SimpleEnvironment('foo', {'type': 'gce'}) |
1410 | client = EnvJujuClient1X(env, None, None) |
1411 | - with patch.object(client, 'juju') as mock: |
1412 | + with patch.object( |
1413 | + client, 'juju', |
1414 | + autospec=True, return_value=(0, None)) as mock: |
1415 | client.destroy_environment(force=False) |
1416 | mock.assert_called_with( |
1417 | 'destroy-environment', ('foo', '-y'), |
1418 | @@ -609,7 +616,8 @@ |
1419 | def test_destroy_environment_delete_jenv(self): |
1420 | env = SimpleEnvironment('foo', {'type': 'ec2'}) |
1421 | client = EnvJujuClient1X(env, None, None) |
1422 | - with patch.object(client, 'juju'): |
1423 | + with patch.object( |
1424 | + client, 'juju', autospec=True, return_value=(0, None)): |
1425 | with temp_env({}) as juju_home: |
1426 | client.env.juju_home = juju_home |
1427 | jenv_path = get_jenv_path(juju_home, 'foo') |
1428 | @@ -622,7 +630,8 @@ |
1429 | def test_destroy_model(self): |
1430 | env = SimpleEnvironment('foo', {'type': 'ec2'}) |
1431 | client = EnvJujuClient1X(env, None, None) |
1432 | - with patch.object(client, 'juju') as mock: |
1433 | + with patch.object( |
1434 | + client, 'juju', autospec=True, return_value=(0, None)) as mock: |
1435 | client.destroy_model() |
1436 | mock.assert_called_with( |
1437 | 'destroy-environment', ('foo', '-y'), |
1438 | @@ -631,7 +640,9 @@ |
1439 | def test_kill_controller(self): |
1440 | client = EnvJujuClient1X( |
1441 | SimpleEnvironment('foo', {'type': 'ec2'}), None, None) |
1442 | - with patch.object(client, 'juju') as juju_mock: |
1443 | + with patch.object( |
1444 | + client, 'juju', |
1445 | + autospec=True, return_value=(0, None)) as juju_mock: |
1446 | client.kill_controller() |
1447 | juju_mock.assert_called_once_with( |
1448 | 'destroy-environment', ('foo', '--force', '-y'), check=False, |
1449 | @@ -640,7 +651,7 @@ |
1450 | def test_kill_controller_check(self): |
1451 | client = EnvJujuClient1X( |
1452 | SimpleEnvironment('foo', {'type': 'ec2'}), None, None) |
1453 | - with patch.object(client, 'juju') as juju_mock: |
1454 | + with patch_juju_call(client) as juju_mock: |
1455 | client.kill_controller(check=True) |
1456 | juju_mock.assert_called_once_with( |
1457 | 'destroy-environment', ('foo', '--force', '-y'), check=True, |
1458 | @@ -649,7 +660,7 @@ |
1459 | def test_destroy_controller(self): |
1460 | client = EnvJujuClient1X( |
1461 | SimpleEnvironment('foo', {'type': 'ec2'}), None, None) |
1462 | - with patch.object(client, 'juju') as juju_mock: |
1463 | + with patch_juju_call(client) as juju_mock: |
1464 | client.destroy_controller() |
1465 | juju_mock.assert_called_once_with( |
1466 | 'destroy-environment', ('foo', '-y'), |
1467 | @@ -813,21 +824,21 @@ |
1468 | def test_deploy_non_joyent(self): |
1469 | env = EnvJujuClient1X( |
1470 | SimpleEnvironment('foo', {'type': 'local'}), '1.234-76', None) |
1471 | - with patch.object(env, 'juju') as mock_juju: |
1472 | + with patch_juju_call(env) as mock_juju: |
1473 | env.deploy('mondogb') |
1474 | mock_juju.assert_called_with('deploy', ('mondogb',)) |
1475 | |
1476 | def test_deploy_joyent(self): |
1477 | env = EnvJujuClient1X( |
1478 | SimpleEnvironment('foo', {'type': 'local'}), '1.234-76', None) |
1479 | - with patch.object(env, 'juju') as mock_juju: |
1480 | + with patch_juju_call(env) as mock_juju: |
1481 | env.deploy('mondogb') |
1482 | mock_juju.assert_called_with('deploy', ('mondogb',)) |
1483 | |
1484 | def test_deploy_repository(self): |
1485 | env = EnvJujuClient1X( |
1486 | SimpleEnvironment('foo', {'type': 'local'}), '1.234-76', None) |
1487 | - with patch.object(env, 'juju') as mock_juju: |
1488 | + with patch_juju_call(env) as mock_juju: |
1489 | env.deploy('mondogb', '/home/jrandom/repo') |
1490 | mock_juju.assert_called_with( |
1491 | 'deploy', ('mondogb', '--repository', '/home/jrandom/repo')) |
1492 | @@ -835,7 +846,7 @@ |
1493 | def test_deploy_to(self): |
1494 | env = EnvJujuClient1X( |
1495 | SimpleEnvironment('foo', {'type': 'local'}), '1.234-76', None) |
1496 | - with patch.object(env, 'juju') as mock_juju: |
1497 | + with patch_juju_call(env) as mock_juju: |
1498 | env.deploy('mondogb', to='0') |
1499 | mock_juju.assert_called_with( |
1500 | 'deploy', ('mondogb', '--to', '0')) |
1501 | @@ -843,7 +854,7 @@ |
1502 | def test_deploy_service(self): |
1503 | env = EnvJujuClient1X( |
1504 | SimpleEnvironment('foo', {'type': 'local'}), '1.234-76', None) |
1505 | - with patch.object(env, 'juju') as mock_juju: |
1506 | + with patch_juju_call(env) as mock_juju: |
1507 | env.deploy('local:mondogb', service='my-mondogb') |
1508 | mock_juju.assert_called_with( |
1509 | 'deploy', ('local:mondogb', 'my-mondogb',)) |
1510 | @@ -851,7 +862,7 @@ |
1511 | def test_upgrade_charm(self): |
1512 | client = EnvJujuClient1X( |
1513 | SimpleEnvironment('foo', {'type': 'local'}), '1.234-76', None) |
1514 | - with patch.object(client, 'juju') as mock_juju: |
1515 | + with patch_juju_call(client) as mock_juju: |
1516 | client.upgrade_charm('foo-service', |
1517 | '/bar/repository/angsty/mongodb') |
1518 | mock_juju.assert_called_once_with( |
1519 | @@ -861,7 +872,7 @@ |
1520 | def test_remove_service(self): |
1521 | client = EnvJujuClient1X( |
1522 | SimpleEnvironment('foo', {'type': 'local'}), '1.234-76', None) |
1523 | - with patch.object(client, 'juju') as mock_juju: |
1524 | + with patch_juju_call(client) as mock_juju: |
1525 | client.remove_service('mondogb') |
1526 | mock_juju.assert_called_with('destroy-service', ('mondogb',)) |
1527 | |
1528 | @@ -1348,7 +1359,7 @@ |
1529 | |
1530 | def test_set_model_constraints(self): |
1531 | client = EnvJujuClient1X(SimpleEnvironment('bar', {}), None, '/foo') |
1532 | - with patch.object(client, 'juju') as juju_mock: |
1533 | + with patch_juju_call(client) as juju_mock: |
1534 | client.set_model_constraints({'bar': 'baz'}) |
1535 | juju_mock.assert_called_once_with('set-constraints', ('bar=baz',)) |
1536 | |
1537 | @@ -1592,7 +1603,7 @@ |
1538 | def test_enable_ha(self): |
1539 | env = SimpleEnvironment('qux') |
1540 | client = EnvJujuClient1X(env, None, '/foobar/baz') |
1541 | - with patch.object(client, 'juju', autospec=True) as eha_mock: |
1542 | + with patch_juju_call(client) as eha_mock: |
1543 | client.enable_ha() |
1544 | eha_mock.assert_called_once_with('ensure-availability', ('-n', '3')) |
1545 | |
1546 | @@ -1657,19 +1668,10 @@ |
1547 | client.get_jes_command() |
1548 | self.assertEqual(0, po_mock.call_count) |
1549 | |
1550 | - def test_get_juju_timings(self): |
1551 | - env = SimpleEnvironment('foo') |
1552 | - client = EnvJujuClient1X(env, None, 'my/juju/bin') |
1553 | - client._backend.juju_timings = {("juju", "op1"): [1], |
1554 | - ("juju", "op2"): [2]} |
1555 | - flattened_timings = client.get_juju_timings() |
1556 | - expected = {"juju op1": [1], "juju op2": [2]} |
1557 | - self.assertEqual(flattened_timings, expected) |
1558 | - |
1559 | def test_deploy_bundle_1x(self): |
1560 | client = EnvJujuClient1X(SimpleEnvironment('an_env', None), |
1561 | '1.23-series-arch', None) |
1562 | - with patch.object(client, 'juju') as mock_juju: |
1563 | + with patch_juju_call(client) as mock_juju: |
1564 | client.deploy_bundle('bundle:~juju-qa/some-bundle') |
1565 | mock_juju.assert_called_with( |
1566 | 'deployer', ('--debug', '--deploy-delay', '10', '--timeout', |
1567 | @@ -1678,7 +1680,7 @@ |
1568 | def test_deploy_bundle_template(self): |
1569 | client = EnvJujuClient1X(SimpleEnvironment('an_env', None), |
1570 | '1.23-series-arch', None) |
1571 | - with patch.object(client, 'juju') as mock_juju: |
1572 | + with patch_juju_call(client) as mock_juju: |
1573 | client.deploy_bundle('bundle:~juju-qa/some-{container}-bundle') |
1574 | mock_juju.assert_called_with( |
1575 | 'deployer', ( |
1576 | @@ -1923,14 +1925,14 @@ |
1577 | def test_add_space(self): |
1578 | client = EnvJujuClient1X(SimpleEnvironment(None, {'type': 'local'}), |
1579 | '1.23-series-arch', None) |
1580 | - with patch.object(client, 'juju', autospec=True) as juju_mock: |
1581 | + with patch_juju_call(client) as juju_mock: |
1582 | client.add_space('foo-space') |
1583 | juju_mock.assert_called_once_with('space create', ('foo-space')) |
1584 | |
1585 | def test_add_subnet(self): |
1586 | client = EnvJujuClient1X(SimpleEnvironment(None, {'type': 'local'}), |
1587 | '1.23-series-arch', None) |
1588 | - with patch.object(client, 'juju', autospec=True) as juju_mock: |
1589 | + with patch_juju_call(client) as juju_mock: |
1590 | client.add_subnet('bar-subnet', 'foo-space') |
1591 | juju_mock.assert_called_once_with('subnet add', |
1592 | ('bar-subnet', 'foo-space')) |
1593 | @@ -1944,7 +1946,7 @@ |
1594 | |
1595 | def test_set_config(self): |
1596 | client = EnvJujuClient1X(SimpleEnvironment('bar', {}), None, '/foo') |
1597 | - with patch.object(client, 'juju') as juju_mock: |
1598 | + with patch_juju_call(client) as juju_mock: |
1599 | client.set_config('foo', {'bar': 'baz'}) |
1600 | juju_mock.assert_called_once_with('set', ('foo', 'bar=baz')) |
1601 | |
1602 | @@ -2065,13 +2067,13 @@ |
1603 | |
1604 | def test_disable_command(self): |
1605 | client = EnvJujuClient1X(SimpleEnvironment('foo'), None, None) |
1606 | - with patch.object(client, 'juju', autospec=True) as mock: |
1607 | + with patch_juju_call(client) as mock: |
1608 | client.disable_command('all', 'message') |
1609 | mock.assert_called_once_with('block all', ('message', )) |
1610 | |
1611 | def test_enable_command(self): |
1612 | client = EnvJujuClient1X(SimpleEnvironment('foo'), None, None) |
1613 | - with patch.object(client, 'juju', autospec=True) as mock: |
1614 | + with patch_juju_call(client) as mock: |
1615 | client.enable_command('all') |
1616 | mock.assert_called_once_with('unblock', 'all') |
1617 | |
1618 | |
1619 | === modified file 'jujupy/version_client.py' |
1620 | --- jujupy/version_client.py 2017-03-27 15:15:05 +0000 |
1621 | +++ jujupy/version_client.py 2017-03-28 03:22:14 +0000 |
1622 | @@ -25,6 +25,7 @@ |
1623 | import yaml |
1624 | |
1625 | from jujupy.client import ( |
1626 | + CommandComplete, |
1627 | Controller, |
1628 | _DEFAULT_BUNDLE_TIMEOUT, |
1629 | get_cache_path, |
1630 | @@ -38,6 +39,7 @@ |
1631 | LXC_MACHINE, |
1632 | make_safe_config, |
1633 | ModelClient, |
1634 | + NoopCondition, |
1635 | SimpleEnvironment, |
1636 | Status, |
1637 | StatusItem, |
1638 | @@ -373,7 +375,8 @@ |
1639 | |
1640 | def set_model_constraints(self, constraints): |
1641 | constraint_strings = self._dict_as_option_strings(constraints) |
1642 | - return self.juju('set-constraints', constraint_strings) |
1643 | + retvar, ct = self.juju('set-constraints', constraint_strings) |
1644 | + return retvar, CommandComplete(NoopCondition(), ct) |
1645 | |
1646 | def set_config(self, service, options): |
1647 | option_strings = ['{}={}'.format(*item) for item in options.items()] |
1648 | @@ -394,11 +397,13 @@ |
1649 | def set_env_option(self, option, value): |
1650 | """Set the value of the option in the environment.""" |
1651 | option_value = "%s=%s" % (option, value) |
1652 | - return self.juju('set-env', (option_value,)) |
1653 | + retvar, ct = self.juju('set-env', (option_value,)) |
1654 | + return retvar, CommandComplete(NoopCondition(), ct) |
1655 | |
1656 | def unset_env_option(self, option): |
1657 | """Unset the value of the option in the environment.""" |
1658 | - return self.juju('set-env', ('{}='.format(option),)) |
1659 | + retvar, ct = self.juju('set-env', ('{}='.format(option),)) |
1660 | + return retvar, CommandComplete(NoopCondition(), ct) |
1661 | |
1662 | def get_model_defaults(self, model_key, cloud=None, region=None): |
1663 | log.info('No model-defaults stored for client (attempted get).') |
1664 | @@ -449,7 +454,8 @@ |
1665 | """Bootstrap a controller.""" |
1666 | self._check_bootstrap() |
1667 | args = self.get_bootstrap_args(upload_tools, bootstrap_series) |
1668 | - self.juju('bootstrap', args) |
1669 | + retvar, ct = self.juju('bootstrap', args) |
1670 | + return (0, CommandComplete(NoopCondition(), ct)) |
1671 | |
1672 | @contextmanager |
1673 | def bootstrap_async(self, upload_tools=False): |
1674 | @@ -497,25 +503,27 @@ |
1675 | """Destroy the environment, with force. Hard kill option. |
1676 | |
1677 | :return: Subprocess's exit code.""" |
1678 | - return self.juju( |
1679 | + retvar, ct = self.juju( |
1680 | 'destroy-environment', (self.env.environment, '--force', '-y'), |
1681 | check=check, include_e=False, timeout=get_teardown_timeout(self)) |
1682 | + return retvar, CommandComplete(NoopCondition(), ct) |
1683 | |
1684 | def destroy_controller(self, all_models=False): |
1685 | """Destroy the environment, with force. Soft kill option. |
1686 | |
1687 | :param all_models: Ignored. |
1688 | :raises: subprocess.CalledProcessError if the operation fails.""" |
1689 | - return self.juju( |
1690 | + retvar, ct = self.juju( |
1691 | 'destroy-environment', (self.env.environment, '-y'), |
1692 | include_e=False, timeout=get_teardown_timeout(self)) |
1693 | + return retvar, CommandComplete(NoopCondition(), ct) |
1694 | |
1695 | def destroy_environment(self, force=True, delete_jenv=False): |
1696 | if force: |
1697 | force_arg = ('--force',) |
1698 | else: |
1699 | force_arg = () |
1700 | - exit_status = self.juju( |
1701 | + exit_status, _ = self.juju( |
1702 | 'destroy-environment', |
1703 | (self.env.environment,) + force_arg + ('-y',), |
1704 | check=False, include_e=False, |
1705 | @@ -572,7 +580,8 @@ |
1706 | args.extend(['--storage', storage]) |
1707 | if constraints is not None: |
1708 | args.extend(['--constraints', constraints]) |
1709 | - return self.juju('deploy', tuple(args)) |
1710 | + retvar, ct = self.juju('deploy', tuple(args)) |
1711 | + return retvar, CommandComplete(NoopCondition(), ct) |
1712 | |
1713 | def upgrade_charm(self, service, charm_path=None): |
1714 | args = (service,) |
1715 | @@ -661,11 +670,13 @@ |
1716 | |
1717 | def disable_command(self, command_set, message=''): |
1718 | """Disable a command-set.""" |
1719 | - return self.juju('block {}'.format(command_set), (message, )) |
1720 | + retvar, ct = self.juju('block {}'.format(command_set), (message, )) |
1721 | + return retvar, CommandComplete(NoopCondition(), ct) |
1722 | |
1723 | def enable_command(self, args): |
1724 | """Enable a command-set.""" |
1725 | - return self.juju('unblock', args) |
1726 | + retvar, ct = self.juju('unblock', args) |
1727 | + return retvar, CommandComplete(NoopCondition(), ct) |
1728 | |
1729 | |
1730 | class EnvJujuClient22(EnvJujuClient1X): |
1731 | |
1732 | === modified file 'tests/__init__.py' |
1733 | --- tests/__init__.py 2017-03-10 19:46:30 +0000 |
1734 | +++ tests/__init__.py 2017-03-28 03:22:14 +0000 |
1735 | @@ -20,6 +20,7 @@ |
1736 | from unittest.mock import patch |
1737 | import yaml |
1738 | |
1739 | +from jujupy.client import CommandTime |
1740 | import utility |
1741 | |
1742 | |
1743 | @@ -152,12 +153,25 @@ |
1744 | os.environ[key] = org_value |
1745 | |
1746 | |
1747 | +@contextmanager |
1748 | +def patch_juju_call(client, return_value=0): |
1749 | + """Simple patch for client.juju call. |
1750 | + |
1751 | + :param return_value: A tuple to return representing the retvar and |
1752 | + CommandTime object |
1753 | + """ |
1754 | + with patch.object( |
1755 | + client, 'juju', |
1756 | + return_value=make_fake_juju_return(retvar=return_value)) as mock: |
1757 | + yield mock |
1758 | + |
1759 | + |
1760 | def assert_juju_call(test_case, mock_method, client, expected_args, |
1761 | call_index=None): |
1762 | """Check a mock's positional arguments. |
1763 | |
1764 | :param test_case: The test case currently being run. |
1765 | - :param mock_mothod: The mock object to be checked. |
1766 | + :param mock_method: The mock object to be checked. |
1767 | :param client: Ignored. |
1768 | :param expected_args: The expected positional arguments for the call. |
1769 | :param call_index: Index of the call to check, if None checks first call |
1770 | @@ -248,3 +262,9 @@ |
1771 | }, |
1772 | } |
1773 | } |
1774 | + |
1775 | + |
1776 | +def make_fake_juju_return( |
1777 | + retvar=0, cmd='mock_cmd', full_args=[], envvars=None, start=None): |
1778 | + """Shadow fake that defaults construction arguments.""" |
1779 | + return (retvar, CommandTime(cmd, full_args, envvars, start)) |
1780 | |
1781 | === modified file 'tests/test_assess_block.py' |
1782 | --- tests/test_assess_block.py 2017-01-20 20:49:41 +0000 |
1783 | +++ tests/test_assess_block.py 2017-03-28 03:22:14 +0000 |
1784 | @@ -18,6 +18,7 @@ |
1785 | ) |
1786 | from tests import ( |
1787 | parse_error, |
1788 | + patch_juju_call, |
1789 | FakeHomeTestCase, |
1790 | TestCase, |
1791 | ) |
1792 | @@ -149,7 +150,7 @@ |
1793 | autospec=True, side_effect=side_effects): |
1794 | with patch('assess_block.deploy_dummy_stack', autospec=True): |
1795 | with patch.object(client, 'remove_service') as mock_rs: |
1796 | - with patch.object(client, 'juju') as mock_juju: |
1797 | + with patch_juju_call(client) as mock_juju: |
1798 | with patch.object( |
1799 | client, 'wait_for_started') as mock_ws: |
1800 | assess_block(client, 'trusty') |
1801 | |
1802 | === modified file 'tests/test_assess_bootstrap.py' |
1803 | --- tests/test_assess_bootstrap.py 2017-03-01 19:02:24 +0000 |
1804 | +++ tests/test_assess_bootstrap.py 2017-03-28 03:22:14 +0000 |
1805 | @@ -29,6 +29,7 @@ |
1806 | from tests import ( |
1807 | FakeHomeTestCase, |
1808 | TestCase, |
1809 | + make_fake_juju_return, |
1810 | ) |
1811 | from utility import ( |
1812 | JujuAssertionError, |
1813 | @@ -361,7 +362,7 @@ |
1814 | args.temp_env_name = 'qux' |
1815 | with extended_bootstrap_cxt('2.0.0'): |
1816 | with patch('jujupy.ModelClient.juju', autospec=True, |
1817 | - side_effect=['', '']) as j_mock: |
1818 | + return_value=make_fake_juju_return()) as j_mock: |
1819 | with patch('assess_bootstrap.get_controller_hostname', |
1820 | return_value='test-host', autospec=True): |
1821 | bs_manager = BootstrapManager.from_args(args) |
1822 | |
1823 | === modified file 'tests/test_assess_container_networking.py' |
1824 | --- tests/test_assess_container_networking.py 2017-01-20 20:58:41 +0000 |
1825 | +++ tests/test_assess_container_networking.py 2017-03-28 03:22:14 +0000 |
1826 | @@ -17,6 +17,7 @@ |
1827 | LXD_MACHINE, |
1828 | SimpleEnvironment, |
1829 | ) |
1830 | +from jujupy.client import CommandTime |
1831 | |
1832 | import assess_container_networking as jcnet |
1833 | from tests import ( |
1834 | @@ -78,6 +79,29 @@ |
1835 | if cmd != 'bootstrap': |
1836 | self.commands.append((cmd, args)) |
1837 | if cmd == 'ssh': |
1838 | + ct = CommandTime(cmd, args) |
1839 | + if len(self._ssh_output) == 0: |
1840 | + return "", ct |
1841 | + |
1842 | + try: |
1843 | + ct = CommandTime(cmd, args) |
1844 | + return self._ssh_output[self._call_number()], ct |
1845 | + except IndexError: |
1846 | + # If we ran out of values, just return the last one |
1847 | + return self._ssh_output[-1], ct |
1848 | + else: |
1849 | + return super(JujuMock, self).juju(cmd, *rargs, **kwargs) |
1850 | + |
1851 | + def get_juju_output(self, cmd, *rargs, **kwargs): |
1852 | + # Almost exactly like juju() except get_juju_output doesn't return |
1853 | + # a CommandTime |
1854 | + if len(rargs) == 1: |
1855 | + args = rargs[0] |
1856 | + else: |
1857 | + args = rargs |
1858 | + if cmd != 'bootstrap': |
1859 | + self.commands.append((cmd, args)) |
1860 | + if cmd == 'ssh': |
1861 | if len(self._ssh_output) == 0: |
1862 | return "" |
1863 | |
1864 | @@ -87,7 +111,7 @@ |
1865 | # If we ran out of values, just return the last one |
1866 | return self._ssh_output[-1] |
1867 | else: |
1868 | - return super(JujuMock, self).juju(cmd, *rargs, **kwargs) |
1869 | + return super(JujuMock, self).get_juju_output(cmd, *rargs, **kwargs) |
1870 | |
1871 | def _call_number(self): |
1872 | call_number = self._call_n |
1873 | @@ -117,7 +141,9 @@ |
1874 | patch.object(self.client, 'wait_for', lambda *args, **kw: None), |
1875 | patch.object(self.client, 'wait_for_started', |
1876 | self.juju_mock.get_status), |
1877 | - patch.object(self.client, 'get_juju_output', self.juju_mock.juju), |
1878 | + patch.object( |
1879 | + self.client, 'get_juju_output', |
1880 | + self.juju_mock.get_juju_output), |
1881 | ] |
1882 | |
1883 | for patcher in patches: |
1884 | |
1885 | === modified file 'tests/test_assess_user_grant_revoke.py' |
1886 | --- tests/test_assess_user_grant_revoke.py 2017-03-23 19:23:19 +0000 |
1887 | +++ tests/test_assess_user_grant_revoke.py 2017-03-28 03:22:14 +0000 |
1888 | @@ -27,6 +27,7 @@ |
1889 | from tests import ( |
1890 | parse_error, |
1891 | TestCase, |
1892 | + patch_juju_call, |
1893 | ) |
1894 | |
1895 | |
1896 | @@ -150,7 +151,7 @@ |
1897 | def test_assert_write_model(self): |
1898 | fake_client = fake_juju_client() |
1899 | with patch.object(fake_client, 'wait_for_started'): |
1900 | - with patch.object(fake_client, 'juju', return_value=True): |
1901 | + with patch_juju_call(fake_client): |
1902 | assert_write_model(fake_client, 'write', True) |
1903 | with self.assertRaises(JujuAssertionError): |
1904 | assert_write_model(fake_client, 'write', False) |
1905 | |
1906 | === modified file 'tests/test_deploy_stack.py' |
1907 | --- tests/test_deploy_stack.py 2017-03-10 00:50:40 +0000 |
1908 | +++ tests/test_deploy_stack.py 2017-03-28 03:22:14 +0000 |
1909 | @@ -68,6 +68,11 @@ |
1910 | Status, |
1911 | Machine, |
1912 | ) |
1913 | + |
1914 | +from jujupy.client import ( |
1915 | + NoopCondition, |
1916 | + CommandTime, |
1917 | +) |
1918 | from jujupy.configuration import ( |
1919 | get_environments_path, |
1920 | get_jenv_path, |
1921 | @@ -82,6 +87,7 @@ |
1922 | assert_juju_call, |
1923 | FakeHomeTestCase, |
1924 | FakePopen, |
1925 | + make_fake_juju_return, |
1926 | observable_temp_file, |
1927 | temp_os_env, |
1928 | use_context, |
1929 | @@ -221,16 +227,38 @@ |
1930 | self.assertEqual('region-foo', env.get_region()) |
1931 | |
1932 | def test_dump_juju_timings(self): |
1933 | + first_start = datetime(2017, 3, 22, 23, 36, 52, 0) |
1934 | + first_end = first_start + timedelta(seconds=2) |
1935 | + second_start = datetime(2017, 3, 22, 23, 40, 51, 0) |
1936 | env = JujuData('foo', {'type': 'bar'}) |
1937 | client = ModelClient(env, None, None) |
1938 | - client._backend.juju_timings = {("juju", "op1"): [1], |
1939 | - ("juju", "op2"): [2]} |
1940 | - expected = {"juju op1": [1], "juju op2": [2]} |
1941 | + client._backend.juju_timings.extend([ |
1942 | + CommandTime('command1', ['command1', 'arg1'], start=first_start), |
1943 | + CommandTime( |
1944 | + 'command2', ['command2', 'arg1', 'arg2'], start=second_start) |
1945 | + ]) |
1946 | + client._backend.juju_timings[0].actual_completion(end=first_end) |
1947 | + expected = [ |
1948 | + { |
1949 | + 'command': 'command1', |
1950 | + 'full_args': ['command1', 'arg1'], |
1951 | + 'start': first_start, |
1952 | + 'end': first_end, |
1953 | + 'total_seconds': 2, |
1954 | + }, |
1955 | + { |
1956 | + 'command': 'command2', |
1957 | + 'full_args': ['command2', 'arg1', 'arg2'], |
1958 | + 'start': second_start, |
1959 | + 'end': None, |
1960 | + 'total_seconds': None, |
1961 | + } |
1962 | + ] |
1963 | with temp_dir() as fake_dir: |
1964 | dump_juju_timings(client, fake_dir) |
1965 | with open(os.path.join(fake_dir, |
1966 | - 'juju_command_times.json')) as out_file: |
1967 | - file_data = json.load(out_file) |
1968 | + 'juju_command_times.yaml')) as out_file: |
1969 | + file_data = yaml.load(out_file) |
1970 | self.assertEqual(file_data, expected) |
1971 | |
1972 | def test_check_token(self): |
1973 | @@ -1757,7 +1785,7 @@ |
1974 | def do_check(*args, **kwargs): |
1975 | with client.check_timeouts(): |
1976 | with tear_down_client.check_timeouts(): |
1977 | - pass |
1978 | + return make_fake_juju_return() |
1979 | |
1980 | with patch.object(bs_manager.tear_down_client, 'juju', |
1981 | side_effect=do_check, autospec=True): |
1982 | @@ -1996,7 +2024,9 @@ |
1983 | bs_manager.has_controller = False |
1984 | with patch('deploy_stack.safe_print_status', |
1985 | autospec=True) as sp_mock: |
1986 | - with patch.object(client, 'juju', wrap=client.juju) as juju_mock: |
1987 | + with patch.object( |
1988 | + client, 'juju', wrap=client.juju, |
1989 | + return_value=make_fake_juju_return()) as juju_mock: |
1990 | with patch.object(client, 'get_juju_output', |
1991 | wraps=client.get_juju_output) as gjo_mock: |
1992 | with patch.object(bs_manager, '_should_dump', |
1993 | @@ -2083,10 +2113,17 @@ |
1994 | @contextmanager |
1995 | def booted_to_bootstrap(self, bs_manager): |
1996 | """Preform patches to focus on the call to bootstrap.""" |
1997 | + # Need basic model details for get_status() (called in wait_for.) |
1998 | + bs_manager.client._backend.controller_state.add_model('controller') |
1999 | + bootstrap_return = (0, NoopCondition(already_satisfied=True)) |
2000 | with patch.object(bs_manager, 'dump_all_logs'): |
2001 | with patch.object(bs_manager, 'runtime_context'): |
2002 | - with patch.object(bs_manager.client, 'juju'): |
2003 | - with patch.object(bs_manager.client, 'bootstrap') as mock: |
2004 | + with patch.object( |
2005 | + bs_manager.client, 'juju', |
2006 | + return_value=make_fake_juju_return()): |
2007 | + with patch.object( |
2008 | + bs_manager.client, 'bootstrap', |
2009 | + return_value=bootstrap_return) as mock: |
2010 | yield mock |
2011 | |
2012 | def test_booted_context_kwargs(self): |
2013 | @@ -2280,6 +2317,15 @@ |
2014 | models = [{'name': 'controller'}, {'name': 'bar'}] |
2015 | self.addContext(patch.object(client, '_get_models', |
2016 | return_value=models, autospec=True)) |
2017 | + # bootstrap now calls wait for. |
2018 | + m_controller_client = Mock() |
2019 | + m_controller_client.wait_for.return_value = {} |
2020 | + self.addContext( |
2021 | + patch.object( |
2022 | + client, |
2023 | + 'get_controller_client', |
2024 | + autospec=True, |
2025 | + return_value=m_controller_client)) |
2026 | if jes: |
2027 | output = jes |
2028 | else: |