Merge lp:~veebers/juju-ci-tools/introduce_commandcomplete into lp:juju-ci-tools

Proposed by Christopher Lee
Status: Superseded
Proposed branch: lp:~veebers/juju-ci-tools/introduce_commandcomplete
Merge into: lp:juju-ci-tools
Diff against target: 1629 lines (+450/-138)
14 files modified
assess_container_networking.py (+3/-2)
chaos.py (+3/-2)
deploy_stack.py (+0/-1)
jujupy/client.py (+130/-26)
jujupy/fake.py (+5/-1)
jujupy/tests/test_client.py (+185/-57)
jujupy/tests/test_version_client.py (+42/-31)
jujupy/version_client.py (+19/-9)
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 (+8/-3)
To merge this branch: bzr merge lp:~veebers/juju-ci-tools/introduce_commandcomplete
Reviewer Review Type Date Requested Status
Juju Release Engineering Pending
Review via email: mp+320753@code.launchpad.net

This proposal has been superseded by a proposal from 2017-03-23.

Description of the change

Introduce CommandComplete to command wrappers (deploy etc.)

Introduces the CompleteComplete return to command wrappers and updates test to match the new return behaviour.

Sorry for the massive diff, most changes are touching lines for patching of ModelClient.juju() return.

To post a comment you must log in.

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-23 04:46:32 +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-23 04:46:32 +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-23 04:46:32 +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
45=== modified file 'jujupy/client.py'
46--- jujupy/client.py 2017-03-17 20:26:17 +0000
47+++ jujupy/client.py 2017-03-23 04:46:32 +0000
48@@ -1079,7 +1079,7 @@
49 self.feature_flags = feature_flags
50 self.debug = debug
51 self._timeout_path = get_timeout_path()
52- self.juju_timings = {}
53+ self.juju_timings = []
54 self.soft_deadline = soft_deadline
55 self._ignore_soft_deadline = False
56
57@@ -1181,7 +1181,11 @@
58 def juju(self, command, args, used_feature_flags,
59 juju_home, model=None, check=True, timeout=None, extra_env=None,
60 suppress_err=False):
61- """Run a command under juju for the current environment."""
62+ """Run a command under juju for the current environment.
63+
64+ :return: Tuple rval, CommandTime rval being the commands exit code and
65+ a CommandTime object used for storing command timing data.
66+ """
67 args = self.full_args(command, args, model, timeout)
68 log.info(' '.join(args))
69 env = self.shell_environ(used_feature_flags, juju_home)
70@@ -1191,17 +1195,17 @@
71 call_func = subprocess.check_call
72 else:
73 call_func = subprocess.call
74- start_time = time.time()
75 # Mutate os.environ instead of supplying env parameter so Windows can
76 # search env['PATH']
77 stderr = subprocess.PIPE if suppress_err else None
78+ # Keep track of commands and how long the take.
79+ command_time = CommandTime(command, args, env)
80 with scoped_environ(env):
81 log.debug('Running juju with env: {}'.format(env))
82 with self._check_timeouts():
83 rval = call_func(args, stderr=stderr)
84- self.juju_timings.setdefault(args, []).append(
85- (time.time() - start_time))
86- return rval
87+ self.juju_timings.append(command_time)
88+ return rval, command_time
89
90 def expect(self, command, args, used_feature_flags, juju_home, model=None,
91 timeout=None, extra_env=None):
92@@ -1403,6 +1407,87 @@
93 raise VersionsNotUpdated(model_name, status)
94
95
96+class CommandTime:
97+ """Store timing details for a juju command."""
98+
99+ def __init__(self, cmd, full_args, envvars=None, start=None):
100+ """Constructor.
101+
102+ :param cmd: Command string for command run (e.g. bootstrap)
103+ :param args: List of all args the command was called with.
104+ :param envvars: Dict of any extra envvars set before command was
105+ called.
106+ :param start: datetime.datetime object representing when the command
107+ was run. If None defaults to datetime.utcnow()
108+ """
109+ self.cmd = cmd
110+ self.full_args = full_args
111+ self.envvars = envvars
112+ self.start = start if start else datetime.utcnow()
113+ self.end = None
114+
115+ def actual_completion(self, end=None):
116+ """Signify that actual completion time of the command.
117+
118+ Note. ignores multiple calls after the initial call.
119+
120+ :param end: datetime.datetime object. If None defaults to
121+ datetime.datetime.utcnow()
122+ """
123+ if self.end is None:
124+ self.end = end if end else datetime.utcnow()
125+
126+ @property
127+ def actual_time(self):
128+ """The actual time a command took.
129+
130+ :return: A datetime.timedelta object or None if the command timing has
131+ never been completed.
132+ """
133+ if self.end is None:
134+ return None
135+ return self.end - self.start
136+
137+
138+class CommandComplete(BaseCondition):
139+ """Wraps a CommandTime and gives the ability to wait_for completion."""
140+
141+ def __init__(self, real_condition, command_time):
142+ """Constructor.
143+
144+ :param real_condition: BaseCondition object.
145+ :param command_time: CommandTime object representing the command to
146+ wait for completion.
147+ """
148+ super(CommandComplete, self).__init__(
149+ real_condition.timeout,
150+ real_condition.already_satisfied)
151+ self._real_condition = real_condition
152+ self.command_time = command_time
153+ if real_condition.already_satisfied:
154+ self.command_time.actual_completion()
155+
156+ def iter_blocking_state(self, status):
157+ """Wraps the iter_blocking_state of the stored BaseCondition.
158+
159+ When the operation is complete iter_blocking_state yields nothing.
160+ Otherwise iter_blocking_state yields details as to why the action
161+ cannot be considered complete yet.
162+ """
163+ completed = True
164+ for item, state in self._real_condition.iter_blocking_state(status):
165+ completed = False
166+ yield item, state
167+ if completed:
168+ self.command_time.actual_completion()
169+
170+ def do_raise(self, status):
171+ raise RuntimeError(
172+ 'Timed out waiting for "{}" command to complete: "{}"'.format(
173+ self.command_time.cmd,
174+ ' '.join(self.command_time.full_args)))
175+
176+
177 class ModelClient:
178 """Wraps calls to a juju instance, associated with a single model.
179
180@@ -1845,7 +1930,7 @@
181 '--config', config_file))
182
183 def destroy_model(self):
184- exit_status = self.juju(
185+ exit_status, _ = self.juju(
186 'destroy-model', (self.env.environment, '-y',),
187 include_e=False, timeout=get_teardown_timeout(self))
188 return exit_status
189@@ -1853,22 +1938,28 @@
190 def kill_controller(self, check=False):
191 """Kill a controller and its models. Hard kill option.
192
193- :return: Subprocess's exit code."""
194- return self.juju(
195+ :return: Tuple: Subprocess's exit code, CommandComplete object.
196+ """
197+ retvar, ct = self.juju(
198 'kill-controller', (self.env.controller.name, '-y'),
199 include_e=False, check=check, timeout=get_teardown_timeout(self))
200+ return retvar, CommandComplete(BaseCondition(), ct)
201
202 def destroy_controller(self, all_models=False):
203 """Destroy a controller and its models. Soft kill option.
204
205 :param all_models: If true will attempt to destroy all the
206 controller's models as well.
207- :raises: subprocess.CalledProcessError if the operation fails."""
208+ :raises: subprocess.CalledProcessError if the operation fails.
209+ :return: Tuple: Subprocess's exit code, CommandComplete object.
210+ """
211 args = (self.env.controller.name, '-y')
212 if all_models:
213 args += ('--destroy-all-models',)
214- return self.juju('destroy-controller', args, include_e=False,
215- timeout=get_teardown_timeout(self))
216+ retvar, ct = self.juju(
217+ 'destroy-controller', args, include_e=False,
218+ timeout=get_teardown_timeout(self))
219+ return retvar, CommandComplete(BaseCondition(), ct)
220
221 def tear_down(self):
222 """Tear down the client as cleanly as possible.
223@@ -1879,7 +1970,7 @@
224 self.destroy_controller(all_models=True)
225 except subprocess.CalledProcessError:
226 logging.warning('tear_down destroy-controller failed')
227- retval = self.kill_controller()
228+ retval, _ = self.kill_controller()
229 message = 'tear_down kill-controller result={}'.format(retval)
230 if retval == 0:
231 logging.info(message)
232@@ -1953,7 +2044,8 @@
233
234 def set_model_constraints(self, constraints):
235 constraint_strings = self._dict_as_option_strings(constraints)
236- return self.juju('set-model-constraints', constraint_strings)
237+ retvar, ct = self.juju('set-model-constraints', constraint_strings)
238+ return retvar, CommandComplete(BaseCondition(), ct)
239
240 def get_model_config(self):
241 """Return the value of the environment's configured options."""
242@@ -1968,11 +2060,13 @@
243 def set_env_option(self, option, value):
244 """Set the value of the option in the environment."""
245 option_value = "%s=%s" % (option, value)
246- return self.juju('model-config', (option_value,))
247+ retvar, ct = self.juju('model-config', (option_value,))
248+ return CommandComplete(BaseCondition(), ct)
249
250 def unset_env_option(self, option):
251 """Unset the value of the option in the environment."""
252- return self.juju('model-config', ('--reset', option,))
253+ retvar, ct = self.juju('model-config', ('--reset', option,))
254+ return CommandComplete(BaseCondition(), ct)
255
256 @staticmethod
257 def _format_cloud_region(cloud=None, region=None):
258@@ -2056,7 +2150,8 @@
259
260 def controller_juju(self, command, args):
261 args = ('-c', self.env.controller.name) + args
262- return self.juju(command, args, include_e=False)
263+ retvar, ct = self.juju(command, args, include_e=False)
264+ return CommandComplete(BaseCondition(), ct)
265
266 def get_juju_timings(self):
267 stringified_timings = {}
268@@ -2093,11 +2188,13 @@
269 args.extend(['--bind', bind])
270 if alias is not None:
271 args.extend([alias])
272- return self.juju('deploy', tuple(args))
273+ retvar, ct = self.juju('deploy', tuple(args))
274+ return CommandComplete(BaseCondition(), ct)
275
276 def attach(self, service, resource):
277 args = (service, resource)
278- return self.juju('attach', args)
279+ retvar, ct = self.juju('attach', args)
280+ return retvar, CommandComplete(BaseCondition(), ct)
281
282 def list_resources(self, service_or_unit, details=True):
283 args = ('--format', 'yaml', service_or_unit)
284@@ -2798,11 +2895,14 @@
285 args = ('generate-tools', '-d', source_dir)
286 if stream is not None:
287 args += ('--stream', stream)
288- return self.juju('metadata', args, include_e=False)
289+ retvar, ct = self.juju('metadata', args, include_e=False)
290+ return retvar, CommandComplete(BaseCondition(), ct)
291
292 def add_cloud(self, cloud_name, cloud_file):
293- return self.juju('add-cloud', ("--replace", cloud_name, cloud_file),
294- include_e=False)
295+ retvar, ct = self.juju(
296+ 'add-cloud', ("--replace", cloud_name, cloud_file),
297+ include_e=False)
298+ return retvar, CommandComplete(BaseCondition(), ct)
299
300 def add_cloud_interactive(self, cloud_name, cloud):
301 child = self.expect('add-cloud', include_e=False)
302@@ -2912,11 +3012,13 @@
303
304 def disable_command(self, command_set, message=''):
305 """Disable a command-set."""
306- return self.juju('disable-command', (command_set, message))
307+ retvar, ct = self.juju('disable-command', (command_set, message))
308+ return retvar, CommandComplete(BaseCondition(), ct)
309
310 def enable_command(self, args):
311 """Enable a command-set."""
312- return self.juju('enable-command', args)
313+ retvar, ct = self.juju('enable-command', args)
314+ return CommandComplete(BaseCondition(), ct)
315
316 def sync_tools(self, local_dir=None, stream=None, source=None):
317 """Copy tools into a local directory or model."""
318@@ -2926,10 +3028,12 @@
319 if source is not None:
320 args += ('--source', source)
321 if local_dir is None:
322- return self.juju('sync-tools', args)
323+ retvar, ct = self.juju('sync-tools', args)
324+ return retvar, CommandComplete(BaseCondition(), ct)
325 else:
326 args += ('--local-dir', local_dir)
327- return self.juju('sync-tools', args, include_e=False)
328+ retvar, ct = self.juju('sync-tools', args, include_e=False)
329+ return retvar, CommandComplete(BaseCondition(), ct)
330
331 def switch(self, model=None, controller=None):
332 """Switch between models."""
333
334=== modified file 'jujupy/fake.py'
335--- jujupy/fake.py 2017-03-17 20:43:14 +0000
336+++ jujupy/fake.py 2017-03-23 04:46:32 +0000
337@@ -19,6 +19,7 @@
338 JujuData,
339 SoftDeadlineExceeded,
340 )
341+from jujupy.client import CommandTime
342
343 __metaclass__ = type
344
345@@ -877,6 +878,7 @@
346 num = int(parsed.n or 1)
347 self.deploy(model_state, parsed.charm_name, num,
348 parsed.service_name, parsed.series)
349+ return (0, CommandTime(command, args))
350 if command == 'remove-application':
351 model_state.destroy_service(*args)
352 if command == 'add-relation':
353@@ -925,8 +927,9 @@
354 self.controller_state.destroy()
355 if command == 'kill-controller':
356 if self.controller_state.state == 'not-bootstrapped':
357- return
358+ return (0, CommandTime(command, args))
359 self.controller_state.destroy(kill=True)
360+ return (0, CommandTime(command, args))
361 if command == 'destroy-model':
362 if not self.is_feature_enabled('jes'):
363 raise JESNotSupported()
364@@ -977,6 +980,7 @@
365 self.controller_state.shares.remove(username)
366 if command == 'restore-backup':
367 model_state.restore_backup()
368+ return 0, CommandTime(command, args)
369
370 @contextmanager
371 def juju_async(self, command, args, used_feature_flags,
372
373=== modified file 'jujupy/tests/test_client.py'
374--- jujupy/tests/test_client.py 2017-03-20 18:43:52 +0000
375+++ jujupy/tests/test_client.py 2017-03-23 04:46:32 +0000
376@@ -43,6 +43,8 @@
377 AgentUnresolvedError,
378 AppError,
379 BaseCondition,
380+ CommandTime,
381+ CommandComplete,
382 CannotConnectEnv,
383 ConditionList,
384 Controller,
385@@ -99,9 +101,11 @@
386 from tests import (
387 assert_juju_call,
388 client_past_deadline,
389+ make_fake_juju_return,
390 FakeHomeTestCase,
391 FakePopen,
392 observable_temp_file,
393+ patch_juju_call,
394 TestCase,
395 )
396 from jujupy.utility import (
397@@ -702,7 +706,7 @@
398 env = JujuData('foo', {'type': 'foo', 'region': 'baz'})
399 client = ModelClient(env, '2.0-zeta1', None)
400 with observable_temp_file() as config_file:
401- with patch.object(client, 'juju') as mock:
402+ with patch_juju_call(client) as mock:
403 client.bootstrap(upload_tools=True)
404 mock.assert_called_with(
405 'bootstrap', (
406@@ -715,7 +719,7 @@
407 env = JujuData('foo', {'type': 'foo', 'region': 'baz'})
408 client = ModelClient(env, '2.0-zeta1', None)
409 with observable_temp_file() as config_file:
410- with patch.object(client, 'juju') as mock:
411+ with patch_juju_call(client) as mock:
412 client.bootstrap(credential='credential_name')
413 mock.assert_called_with(
414 'bootstrap', (
415@@ -728,7 +732,7 @@
416 def test_bootstrap_bootstrap_series(self):
417 env = JujuData('foo', {'type': 'bar', 'region': 'baz'})
418 client = ModelClient(env, '2.0-zeta1', None)
419- with patch.object(client, 'juju') as mock:
420+ with patch_juju_call(client) as mock:
421 with observable_temp_file() as config_file:
422 client.bootstrap(bootstrap_series='angsty')
423 mock.assert_called_with(
424@@ -742,7 +746,7 @@
425 def test_bootstrap_auto_upgrade(self):
426 env = JujuData('foo', {'type': 'bar', 'region': 'baz'})
427 client = ModelClient(env, '2.0-zeta1', None)
428- with patch.object(client, 'juju') as mock:
429+ with patch_juju_call(client) as mock:
430 with observable_temp_file() as config_file:
431 client.bootstrap(auto_upgrade=True)
432 mock.assert_called_with(
433@@ -755,7 +759,7 @@
434 def test_bootstrap_no_gui(self):
435 env = JujuData('foo', {'type': 'bar', 'region': 'baz'})
436 client = ModelClient(env, '2.0-zeta1', None)
437- with patch.object(client, 'juju') as mock:
438+ with patch_juju_call(client) as mock:
439 with observable_temp_file() as config_file:
440 client.bootstrap(no_gui=True)
441 mock.assert_called_with(
442@@ -768,7 +772,7 @@
443 def test_bootstrap_metadata(self):
444 env = JujuData('foo', {'type': 'bar', 'region': 'baz'})
445 client = ModelClient(env, '2.0-zeta1', None)
446- with patch.object(client, 'juju') as mock:
447+ with patch_juju_call(client) as mock:
448 with observable_temp_file() as config_file:
449 client.bootstrap(metadata_source='/var/test-source')
450 mock.assert_called_with(
451@@ -862,7 +866,7 @@
452 client = ModelClient(model_data, None, None)
453 with patch.object(client, 'get_jes_command',
454 return_value=jes_command):
455- with patch.object(controller_client, 'juju') as ccj_mock:
456+ with patch_juju_call(controller_client) as ccj_mock:
457 with observable_temp_file() as config_file:
458 controller_client.add_model(model_data)
459 ccj_mock.assert_called_once_with(
460@@ -874,7 +878,7 @@
461 client.bootstrap()
462 client.env.controller.explicit_region = True
463 model = client.env.clone('new-model')
464- with patch.object(client._backend, 'juju') as juju_mock:
465+ with patch_juju_call(client._backend) as juju_mock:
466 with observable_temp_file() as config_file:
467 client.add_model(model)
468 juju_mock.assert_called_once_with('add-model', (
469@@ -886,7 +890,7 @@
470 def test_add_model_by_name(self):
471 client = fake_juju_client()
472 client.bootstrap()
473- with patch.object(client._backend, 'juju') as juju_mock:
474+ with patch_juju_call(client._backend) as juju_mock:
475 with observable_temp_file() as config_file:
476 client.add_model('new-model')
477 juju_mock.assert_called_once_with('add-model', (
478@@ -902,7 +906,7 @@
479 def test_destroy_model(self):
480 env = JujuData('foo', {'type': 'ec2'})
481 client = ModelClient(env, None, None)
482- with patch.object(client, 'juju') as mock:
483+ with patch_juju_call(client) as mock:
484 client.destroy_model()
485 mock.assert_called_with(
486 'destroy-model', ('foo', '-y'),
487@@ -911,7 +915,7 @@
488 def test_destroy_model_azure(self):
489 env = JujuData('foo', {'type': 'azure'})
490 client = ModelClient(env, None, None)
491- with patch.object(client, 'juju') as mock:
492+ with patch_juju_call(client) as mock:
493 client.destroy_model()
494 mock.assert_called_with(
495 'destroy-model', ('foo', '-y'),
496@@ -920,7 +924,7 @@
497 def test_destroy_model_gce(self):
498 env = JujuData('foo', {'type': 'gce'})
499 client = ModelClient(env, None, None)
500- with patch.object(client, 'juju') as mock:
501+ with patch_juju_call(client) as mock:
502 client.destroy_model()
503 mock.assert_called_with(
504 'destroy-model', ('foo', '-y'),
505@@ -928,7 +932,7 @@
506
507 def test_kill_controller(self):
508 client = ModelClient(JujuData('foo', {'type': 'ec2'}), None, None)
509- with patch.object(client, 'juju') as juju_mock:
510+ with patch_juju_call(client) as juju_mock:
511 client.kill_controller()
512 juju_mock.assert_called_once_with(
513 'kill-controller', ('foo', '-y'), check=False, include_e=False,
514@@ -936,7 +940,7 @@
515
516 def test_kill_controller_check(self):
517 client = ModelClient(JujuData('foo', {'type': 'ec2'}), None, None)
518- with patch.object(client, 'juju') as juju_mock:
519+ with patch_juju_call(client) as juju_mock:
520 client.kill_controller(check=True)
521 juju_mock.assert_called_once_with(
522 'kill-controller', ('foo', '-y'), check=True, include_e=False,
523@@ -946,7 +950,7 @@
524 client = ModelClient(JujuData('foo', {'type': 'azure'}), None, None)
525 with patch.object(client, 'get_jes_command',
526 return_value=jes_command):
527- with patch.object(client, 'juju') as juju_mock:
528+ with patch_juju_call(client) as juju_mock:
529 client.kill_controller()
530 juju_mock.assert_called_once_with(
531 kill_command, ('foo', '-y'), check=False, include_e=False,
532@@ -954,7 +958,7 @@
533
534 def test_kill_controller_gce(self):
535 client = ModelClient(JujuData('foo', {'type': 'gce'}), None, None)
536- with patch.object(client, 'juju') as juju_mock:
537+ with patch_juju_call(client) as juju_mock:
538 client.kill_controller()
539 juju_mock.assert_called_once_with(
540 'kill-controller', ('foo', '-y'), check=False, include_e=False,
541@@ -962,7 +966,7 @@
542
543 def test_destroy_controller(self):
544 client = ModelClient(JujuData('foo', {'type': 'ec2'}), None, None)
545- with patch.object(client, 'juju') as juju_mock:
546+ with patch_juju_call(client) as juju_mock:
547 client.destroy_controller()
548 juju_mock.assert_called_once_with(
549 'destroy-controller', ('foo', '-y'), include_e=False,
550@@ -970,7 +974,7 @@
551
552 def test_destroy_controller_all_models(self):
553 client = ModelClient(JujuData('foo', {'type': 'ec2'}), None, None)
554- with patch.object(client, 'juju') as juju_mock:
555+ with patch_juju_call(client) as juju_mock:
556 client.destroy_controller(all_models=True)
557 juju_mock.assert_called_once_with(
558 'destroy-controller', ('foo', '-y', '--destroy-all-models'),
559@@ -988,7 +992,9 @@
560 side_effect=raise_error) as mock:
561 yield mock
562 else:
563- with patch.object(target, attribute, autospec=True) as mock:
564+ with patch.object(
565+ target, attribute, autospec=True,
566+ return_value=make_fake_juju_return()) as mock:
567 yield mock
568
569 with patch_raise(client, 'destroy_controller', destroy_raises
570@@ -1219,21 +1225,21 @@
571 def test_deploy_non_joyent(self):
572 env = ModelClient(
573 JujuData('foo', {'type': 'local'}), '1.234-76', None)
574- with patch.object(env, 'juju') as mock_juju:
575+ with patch_juju_call(env) as mock_juju:
576 env.deploy('mondogb')
577 mock_juju.assert_called_with('deploy', ('mondogb',))
578
579 def test_deploy_joyent(self):
580 env = ModelClient(
581 JujuData('foo', {'type': 'local'}), '1.234-76', None)
582- with patch.object(env, 'juju') as mock_juju:
583+ with patch_juju_call(env) as mock_juju:
584 env.deploy('mondogb')
585 mock_juju.assert_called_with('deploy', ('mondogb',))
586
587 def test_deploy_repository(self):
588 env = ModelClient(
589 JujuData('foo', {'type': 'local'}), '1.234-76', None)
590- with patch.object(env, 'juju') as mock_juju:
591+ with patch_juju_call(env) as mock_juju:
592 env.deploy('/home/jrandom/repo/mongodb')
593 mock_juju.assert_called_with(
594 'deploy', ('/home/jrandom/repo/mongodb',))
595@@ -1241,7 +1247,7 @@
596 def test_deploy_to(self):
597 env = ModelClient(
598 JujuData('foo', {'type': 'local'}), '1.234-76', None)
599- with patch.object(env, 'juju') as mock_juju:
600+ with patch_juju_call(env) as mock_juju:
601 env.deploy('mondogb', to='0')
602 mock_juju.assert_called_with(
603 'deploy', ('mondogb', '--to', '0'))
604@@ -1249,7 +1255,7 @@
605 def test_deploy_service(self):
606 env = ModelClient(
607 JujuData('foo', {'type': 'local'}), '1.234-76', None)
608- with patch.object(env, 'juju') as mock_juju:
609+ with patch_juju_call(env) as mock_juju:
610 env.deploy('local:mondogb', service='my-mondogb')
611 mock_juju.assert_called_with(
612 'deploy', ('local:mondogb', 'my-mondogb',))
613@@ -1257,14 +1263,14 @@
614 def test_deploy_force(self):
615 env = ModelClient(
616 JujuData('foo', {'type': 'local'}), '1.234-76', None)
617- with patch.object(env, 'juju') as mock_juju:
618+ with patch_juju_call(env) as mock_juju:
619 env.deploy('local:mondogb', force=True)
620 mock_juju.assert_called_with('deploy', ('local:mondogb', '--force',))
621
622 def test_deploy_series(self):
623 env = ModelClient(
624 JujuData('foo', {'type': 'local'}), '1.234-76', None)
625- with patch.object(env, 'juju') as mock_juju:
626+ with patch_juju_call(env) as mock_juju:
627 env.deploy('local:blah', series='xenial')
628 mock_juju.assert_called_with(
629 'deploy', ('local:blah', '--series', 'xenial'))
630@@ -1272,14 +1278,14 @@
631 def test_deploy_multiple(self):
632 env = ModelClient(
633 JujuData('foo', {'type': 'local'}), '1.234-76', None)
634- with patch.object(env, 'juju') as mock_juju:
635+ with patch_juju_call(env) as mock_juju:
636 env.deploy('local:blah', num=2)
637 mock_juju.assert_called_with(
638 'deploy', ('local:blah', '-n', '2'))
639
640 def test_deploy_resource(self):
641 env = ModelClient(JujuData('foo', {'type': 'local'}), None, None)
642- with patch.object(env, 'juju') as mock_juju:
643+ with patch_juju_call(env) as mock_juju:
644 env.deploy('local:blah', resource='foo=/path/dir')
645 mock_juju.assert_called_with(
646 'deploy', ('local:blah', '--resource', 'foo=/path/dir'))
647@@ -1287,7 +1293,7 @@
648 def test_deploy_storage(self):
649 env = EnvJujuClient1X(
650 SimpleEnvironment('foo', {'type': 'local'}), '1.234-76', None)
651- with patch.object(env, 'juju') as mock_juju:
652+ with patch_juju_call(env) as mock_juju:
653 env.deploy('mondogb', storage='rootfs,1G')
654 mock_juju.assert_called_with(
655 'deploy', ('mondogb', '--storage', 'rootfs,1G'))
656@@ -1295,27 +1301,27 @@
657 def test_deploy_constraints(self):
658 env = EnvJujuClient1X(
659 SimpleEnvironment('foo', {'type': 'local'}), '1.234-76', None)
660- with patch.object(env, 'juju') as mock_juju:
661+ with patch_juju_call(env) as mock_juju:
662 env.deploy('mondogb', constraints='virt-type=kvm')
663 mock_juju.assert_called_with(
664 'deploy', ('mondogb', '--constraints', 'virt-type=kvm'))
665
666 def test_deploy_bind(self):
667 env = ModelClient(JujuData('foo', {'type': 'local'}), None, None)
668- with patch.object(env, 'juju') as mock_juju:
669+ with patch_juju_call(env) as mock_juju:
670 env.deploy('mydb', bind='backspace')
671 mock_juju.assert_called_with('deploy', ('mydb', '--bind', 'backspace'))
672
673 def test_deploy_aliased(self):
674 env = ModelClient(JujuData('foo', {'type': 'local'}), None, None)
675- with patch.object(env, 'juju') as mock_juju:
676+ with patch_juju_call(env) as mock_juju:
677 env.deploy('local:blah', alias='blah-blah')
678 mock_juju.assert_called_with(
679 'deploy', ('local:blah', 'blah-blah'))
680
681 def test_attach(self):
682 env = ModelClient(JujuData('foo', {'type': 'local'}), None, None)
683- with patch.object(env, 'juju') as mock_juju:
684+ with patch_juju_call(env) as mock_juju:
685 env.attach('foo', resource='foo=/path/dir')
686 mock_juju.assert_called_with('attach', ('foo', 'foo=/path/dir'))
687
688@@ -1383,7 +1389,7 @@
689 def test_deploy_bundle_2x(self):
690 client = ModelClient(JujuData('an_env', None),
691 '1.23-series-arch', None)
692- with patch.object(client, 'juju') as mock_juju:
693+ with patch_juju_call(client) as mock_juju:
694 client.deploy_bundle('bundle:~juju-qa/some-bundle')
695 mock_juju.assert_called_with(
696 'deploy', ('bundle:~juju-qa/some-bundle'), timeout=3600)
697@@ -1391,7 +1397,7 @@
698 def test_deploy_bundle_template(self):
699 client = ModelClient(JujuData('an_env', None),
700 '1.23-series-arch', None)
701- with patch.object(client, 'juju') as mock_juju:
702+ with patch_juju_call(client) as mock_juju:
703 client.deploy_bundle('bundle:~juju-qa/some-{container}-bundle')
704 mock_juju.assert_called_with(
705 'deploy', ('bundle:~juju-qa/some-lxd-bundle'), timeout=3600)
706@@ -1399,7 +1405,7 @@
707 def test_upgrade_charm(self):
708 env = ModelClient(
709 JujuData('foo', {'type': 'local'}), '2.34-74', None)
710- with patch.object(env, 'juju') as mock_juju:
711+ with patch_juju_call(env) as mock_juju:
712 env.upgrade_charm('foo-service',
713 '/bar/repository/angsty/mongodb')
714 mock_juju.assert_called_once_with(
715@@ -1409,7 +1415,7 @@
716 def test_remove_service(self):
717 env = ModelClient(
718 JujuData('foo', {'type': 'local'}), '1.234-76', None)
719- with patch.object(env, 'juju') as mock_juju:
720+ with patch_juju_call(env) as mock_juju:
721 env.remove_service('mondogb')
722 mock_juju.assert_called_with('remove-application', ('mondogb',))
723
724@@ -1578,7 +1584,7 @@
725
726 def test_remove_machine(self):
727 client = fake_juju_client()
728- with patch.object(client._backend, 'juju') as juju_mock:
729+ with patch_juju_call(client._backend) as juju_mock:
730 condition = client.remove_machine('0')
731 call = backend_call(
732 client, 'remove-machine', ('0',), 'name:name')
733@@ -1587,7 +1593,7 @@
734
735 def test_remove_machine_force(self):
736 client = fake_juju_client()
737- with patch.object(client._backend, 'juju') as juju_mock:
738+ with patch_juju_call(client._backend) as juju_mock:
739 client.remove_machine('0', force=True)
740 call = backend_call(
741 client, 'remove-machine', ('--force', '0'), 'name:name')
742@@ -1982,7 +1988,7 @@
743
744 def test_list_models(self):
745 client = ModelClient(JujuData('foo'), None, None)
746- with patch.object(client, 'juju') as j_mock:
747+ with patch_juju_call(client) as j_mock:
748 client.list_models()
749 j_mock.assert_called_once_with(
750 'list-models', ('-c', 'foo'), include_e=False)
751@@ -2194,7 +2200,7 @@
752
753 def test_list_controllers(self):
754 client = ModelClient(JujuData('foo'), None, None)
755- with patch.object(client, 'juju') as j_mock:
756+ with patch_juju_call(client) as j_mock:
757 client.list_controllers()
758 j_mock.assert_called_once_with('list-controllers', (), include_e=False)
759
760@@ -2662,7 +2668,7 @@
761
762 def test_set_model_constraints(self):
763 client = ModelClient(JujuData('bar', {}), None, '/foo')
764- with patch.object(client, 'juju') as juju_mock:
765+ with patch_juju_call(client) as juju_mock:
766 client.set_model_constraints({'bar': 'baz'})
767 juju_mock.assert_called_once_with('set-model-constraints',
768 ('bar=baz',))
769@@ -2948,7 +2954,7 @@
770 def test_restore_backup(self):
771 env = JujuData('qux')
772 client = ModelClient(env, None, '/foobar/baz')
773- with patch.object(client, 'juju') as gjo_mock:
774+ with patch_juju_call(client) as gjo_mock:
775 client.restore_backup('quxx')
776 gjo_mock.assert_called_once_with(
777 'restore-backup',
778@@ -3230,7 +3236,7 @@
779
780 def test_set_config(self):
781 client = ModelClient(JujuData('bar', {}), None, '/foo')
782- with patch.object(client, 'juju') as juju_mock:
783+ with patch_juju_call(client) as juju_mock:
784 client.set_config('foo', {'bar': 'baz'})
785 juju_mock.assert_called_once_with('config', ('foo', 'bar=baz'))
786
787@@ -3285,7 +3291,7 @@
788
789 def test_upgrade_mongo(self):
790 client = ModelClient(JujuData('bar', {}), None, '/foo')
791- with patch.object(client, 'juju') as juju_mock:
792+ with patch_juju_call(client) as juju_mock:
793 client.upgrade_mongo()
794 juju_mock.assert_called_once_with('upgrade-mongo', ())
795
796@@ -3330,7 +3336,7 @@
797 default_model = fake_client.model_name
798 default_controller = fake_client.env.controller.name
799
800- with patch.object(fake_client, 'juju', return_value=True):
801+ with patch_juju_call(fake_client):
802 fake_client.revoke(username)
803 fake_client.juju.assert_called_with('revoke',
804 ('-c', default_controller,
805@@ -3410,7 +3416,7 @@
806 env = JujuData('foo')
807 username = 'fakeuser'
808 client = ModelClient(env, None, None)
809- with patch.object(client, 'juju') as mock:
810+ with patch_juju_call(client) as mock:
811 client.disable_user(username)
812 mock.assert_called_with(
813 'disable-user', ('-c', 'foo', 'fakeuser'), include_e=False)
814@@ -3419,7 +3425,7 @@
815 env = JujuData('foo')
816 username = 'fakeuser'
817 client = ModelClient(env, None, None)
818- with patch.object(client, 'juju') as mock:
819+ with patch_juju_call(client) as mock:
820 client.enable_user(username)
821 mock.assert_called_with(
822 'enable-user', ('-c', 'foo', 'fakeuser'), include_e=False)
823@@ -3427,7 +3433,7 @@
824 def test_logout(self):
825 env = JujuData('foo')
826 client = ModelClient(env, None, None)
827- with patch.object(client, 'juju') as mock:
828+ with patch_juju_call(client) as mock:
829 client.logout()
830 mock.assert_called_with(
831 'logout', ('-c', 'foo'), include_e=False)
832@@ -3667,32 +3673,32 @@
833
834 def test_disable_command(self):
835 client = ModelClient(JujuData('foo'), None, None)
836- with patch.object(client, 'juju', autospec=True) as mock:
837+ with patch_juju_call(client) as mock:
838 client.disable_command('all', 'message')
839 mock.assert_called_once_with('disable-command', ('all', 'message'))
840
841 def test_enable_command(self):
842 client = ModelClient(JujuData('foo'), None, None)
843- with patch.object(client, 'juju', autospec=True) as mock:
844+ with patch_juju_call(client) as mock:
845 client.enable_command('all')
846 mock.assert_called_once_with('enable-command', 'all')
847
848 def test_sync_tools(self):
849 client = ModelClient(JujuData('foo'), None, None)
850- with patch.object(client, 'juju', autospec=True) as mock:
851+ with patch_juju_call(client) as mock:
852 client.sync_tools()
853 mock.assert_called_once_with('sync-tools', ())
854
855 def test_sync_tools_local_dir(self):
856 client = ModelClient(JujuData('foo'), None, None)
857- with patch.object(client, 'juju', autospec=True) as mock:
858+ with patch_juju_call(client) as mock:
859 client.sync_tools('/agents')
860 mock.assert_called_once_with('sync-tools', ('--local-dir', '/agents'),
861 include_e=False)
862
863 def test_generate_tool(self):
864 client = ModelClient(JujuData('foo'), None, None)
865- with patch.object(client, 'juju', autospec=True) as mock:
866+ with patch_juju_call(client) as mock:
867 client.generate_tool('/agents')
868 mock.assert_called_once_with('metadata',
869 ('generate-tools', '-d', '/agents'),
870@@ -3700,7 +3706,7 @@
871
872 def test_generate_tool_with_stream(self):
873 client = ModelClient(JujuData('foo'), None, None)
874- with patch.object(client, 'juju', autospec=True) as mock:
875+ with patch_juju_call(client) as mock:
876 client.generate_tool('/agents', "testing")
877 mock.assert_called_once_with(
878 'metadata', ('generate-tools', '-d', '/agents',
879@@ -3708,7 +3714,7 @@
880
881 def test_add_cloud(self):
882 client = ModelClient(JujuData('foo'), None, None)
883- with patch.object(client, 'juju', autospec=True) as mock:
884+ with patch_juju_call(client) as mock:
885 client.add_cloud('localhost', 'cfile')
886 mock.assert_called_once_with('add-cloud',
887 ('--replace', 'localhost', 'cfile'),
888@@ -3717,7 +3723,7 @@
889 def test_switch(self):
890 def run_switch_test(expect, model=None, controller=None):
891 client = ModelClient(JujuData('foo'), None, None)
892- with patch.object(client, 'juju', autospec=True) as mock:
893+ with patch_juju_call(client) as mock:
894 client.switch(model=model, controller=controller)
895 mock.assert_called_once_with('switch', (expect,), include_e=False)
896 run_switch_test('default', 'default')
897@@ -6010,3 +6016,125 @@
898 self.assertEqual(host, "2001:db8::3")
899 fake_client.status_until.assert_called_once_with(timeout=600)
900 self.assertEqual(self.log_stream.getvalue(), "")
901+
902+
903+class TestCommandTime(TestCase):
904+
905+ def test_default_values(self):
906+ full_args = ['juju', '--showlog', 'bootstrap']
907+ utcnow = datetime(2017, 3, 22, 23, 36, 52, 530631)
908+ with patch('jujupy.client.datetime', autospec=True) as m_dt:
909+ m_dt.utcnow.return_value = utcnow
910+ ct = CommandTime('bootstrap', full_args)
911+ self.assertEqual(ct.cmd, 'bootstrap')
912+ self.assertEqual(ct.full_args, full_args)
913+ self.assertEqual(ct.envvars, None)
914+ self.assertEqual(ct.start, utcnow)
915+ self.assertEqual(ct.end, None)
916+
917+ def test_set_start_time(self):
918+ ct = CommandTime('cmd', [], start='abc')
919+ self.assertEqual(ct.start, 'abc')
920+
921+ def test_set_envvar(self):
922+ details = {'abc': 123}
923+ ct = CommandTime('cmd', [], envvars=details)
924+ self.assertEqual(ct.envvars, details)
925+
926+ def test_actual_completion_sets_default(self):
927+ utcnow = datetime(2017, 3, 22, 23, 36, 52, 530631)
928+ ct = CommandTime('cmd', [])
929+ with patch('jujupy.client.datetime', autospec=True) as m_dt:
930+ m_dt.utcnow.return_value = utcnow
931+ ct.actual_completion()
932+ self.assertEqual(ct.end, utcnow)
933+
934+ def test_actual_completion_idempotent(self):
935+ ct = CommandTime('cmd', [])
936+ ct.actual_completion(end='a')
937+ ct.actual_completion(end='b')
938+ self.assertEqual(ct.end, 'a')
939+
940+ def test_actual_completion_set_value(self):
941+ utcnow = datetime(2017, 3, 22, 23, 36, 52, 530631)
942+ ct = CommandTime('cmd', [])
943+ ct.actual_completion(end=utcnow)
944+ self.assertEqual(ct.end, utcnow)
945+
946+ def test_actual_time_returns_None_when_not_complete(self):
947+ ct = CommandTime('cmd', [])
948+ self.assertEqual(ct.actual_time, None)
949+
950+ def test_actual_time_returns_time_difference_when_complete(self):
951+ utcstart = datetime(2017, 3, 22, 23, 36, 52, 530631)
952+ utcend = utcstart + timedelta(seconds=1)
953+ with patch('jujupy.client.datetime', autospec=True) as m_dt:
954+ m_dt.utcnow.side_effect = [utcstart, utcend]
955+ ct = CommandTime('cmd', [])
956+ ct.actual_completion()
957+ self.assertEqual(ct.actual_time, utcend - utcstart)
958+
959+
960+class TestCommandComplete(TestCase):
961+
962+ def test_default_values(self):
963+ ct = CommandTime('cmd', [])
964+ base_condition = BaseCondition()
965+ cc = CommandComplete(base_condition, ct)
966+
967+ self.assertEqual(cc.timeout, 300)
968+ self.assertEqual(cc.already_satisfied, False)
969+ self.assertEqual(cc._real_condition, base_condition)
970+ self.assertEqual(cc.command_time, ct)
971+ # actual_completion shouldn't be set as the condition is not already
972+ # satisfied.
973+ self.assertEqual(cc.command_time.end, None)
974+
975+ def test_sets_actual_comletion_when_already_satisfied(self):
976+ base_condition = BaseCondition(already_satisfied=True)
977+ ct = CommandTime('cmd', [])
978+ cc = CommandComplete(base_condition, ct)
979+
980+ self.assertIsNotNone(cc.command_time.actual_time)
981+
982+ def test_calls_wrapper_condition_iter(self):
983+ class TestCondition(BaseCondition):
984+ def iter_blocking_state(self, status):
985+ yield 'item', status
986+
987+ ct = CommandTime('cmd', [])
988+ cc = CommandComplete(TestCondition(), ct)
989+
990+ k, v = next(cc.iter_blocking_state('status_obj'))
991+ self.assertEqual(k, 'item')
992+ self.assertEqual(v, 'status_obj')
993+
994+ def test_sets_actual_completion_when_complete(self):
995+ """When the condition hits success must set actual_completion."""
996+ class TestCondition(BaseCondition):
997+ def __init__(self):
998+ super(TestCondition, self).__init__()
999+ self._already_called = False
1000+
1001+ def iter_blocking_state(self, status):
1002+ if not self._already_called:
1003+ self._already_called = True
1004+ yield 'item', status
1005+
1006+ ct = CommandTime('cmd', [])
1007+ cc = CommandComplete(TestCondition(), ct)
1008+
1009+ next(cc.iter_blocking_state('status_obj'))
1010+ self.assertIsNone(cc.command_time.end)
1011+ next(cc.iter_blocking_state('status_obj'), None)
1012+ self.assertIsNotNone(cc.command_time.end)
1013+
1014+ def test_raises_exception_with_command_details(self):
1015+ ct = CommandTime('cmd', ['cmd', 'arg1', 'arg2'])
1016+ cc = CommandComplete(BaseCondition(), ct)
1017+
1018+ with self.assertRaises(RuntimeError) as ex:
1019+ cc.do_raise('status')
1020+ self.assertEqual(
1021+ str(ex.exception),
1022+ 'Timed out waiting for "cmd" command to complete: "cmd arg1 arg2"')
1023
1024=== modified file 'jujupy/tests/test_version_client.py'
1025--- jujupy/tests/test_version_client.py 2017-03-10 21:15:12 +0000
1026+++ jujupy/tests/test_version_client.py 2017-03-23 04:46:32 +0000
1027@@ -69,6 +69,7 @@
1028 )
1029 from tests import (
1030 assert_juju_call,
1031+ patch_juju_call,
1032 FakeHomeTestCase,
1033 FakePopen,
1034 observable_temp_file,
1035@@ -432,7 +433,7 @@
1036 def test_upgrade_juju_nonlocal(self):
1037 client = EnvJujuClient1X(
1038 SimpleEnvironment('foo', {'type': 'nonlocal'}), '1.234-76', None)
1039- with patch.object(client, 'juju') as juju_mock:
1040+ with patch_juju_call(client) as juju_mock:
1041 client.upgrade_juju()
1042 juju_mock.assert_called_with(
1043 'upgrade-juju', ('--version', '1.234'))
1044@@ -440,7 +441,7 @@
1045 def test_upgrade_juju_local(self):
1046 client = EnvJujuClient1X(
1047 SimpleEnvironment('foo', {'type': 'local'}), '1.234-76', None)
1048- with patch.object(client, 'juju') as juju_mock:
1049+ with patch_juju_call(client) as juju_mock:
1050 client.upgrade_juju()
1051 juju_mock.assert_called_with(
1052 'upgrade-juju', ('--version', '1.234', '--upload-tools',))
1053@@ -448,7 +449,7 @@
1054 def test_upgrade_juju_no_force_version(self):
1055 client = EnvJujuClient1X(
1056 SimpleEnvironment('foo', {'type': 'local'}), '1.234-76', None)
1057- with patch.object(client, 'juju') as juju_mock:
1058+ with patch_juju_call(client) as juju_mock:
1059 client.upgrade_juju(force_version=False)
1060 juju_mock.assert_called_with(
1061 'upgrade-juju', ('--upload-tools',))
1062@@ -485,14 +486,14 @@
1063 def test_bootstrap(self):
1064 env = SimpleEnvironment('foo')
1065 client = EnvJujuClient1X(env, None, None)
1066- with patch.object(client, 'juju') as mock:
1067+ with patch_juju_call(client) as mock:
1068 client.bootstrap()
1069 mock.assert_called_with('bootstrap', ('--constraints', 'mem=2G'))
1070
1071 def test_bootstrap_upload_tools(self):
1072 env = SimpleEnvironment('foo')
1073 client = EnvJujuClient1X(env, None, None)
1074- with patch.object(client, 'juju') as mock:
1075+ with patch_juju_call(client) as mock:
1076 client.bootstrap(upload_tools=True)
1077 mock.assert_called_with(
1078 'bootstrap', ('--upload-tools', '--constraints', 'mem=2G'))
1079@@ -508,7 +509,7 @@
1080 env.update_config({
1081 'default-series': 'angsty',
1082 })
1083- with patch.object(client, 'juju') as mock:
1084+ with patch_juju_call(client) as mock:
1085 client.bootstrap(bootstrap_series='angsty')
1086 mock.assert_called_with('bootstrap', ('--constraints', 'mem=2G'))
1087
1088@@ -573,7 +574,8 @@
1089 def test_destroy_environment(self):
1090 env = SimpleEnvironment('foo', {'type': 'ec2'})
1091 client = EnvJujuClient1X(env, None, None)
1092- with patch.object(client, 'juju') as mock:
1093+ with patch.object(
1094+ client, 'juju', autospec=True, return_value=(0, None)) as mock:
1095 client.destroy_environment()
1096 mock.assert_called_with(
1097 'destroy-environment', ('foo', '--force', '-y'),
1098@@ -582,7 +584,9 @@
1099 def test_destroy_environment_no_force(self):
1100 env = SimpleEnvironment('foo', {'type': 'ec2'})
1101 client = EnvJujuClient1X(env, None, None)
1102- with patch.object(client, 'juju') as mock:
1103+ with patch.object(
1104+ client, 'juju',
1105+ autospec=True, return_value=(0, None)) as mock:
1106 client.destroy_environment(force=False)
1107 mock.assert_called_with(
1108 'destroy-environment', ('foo', '-y'),
1109@@ -591,7 +595,8 @@
1110 def test_destroy_environment_azure(self):
1111 env = SimpleEnvironment('foo', {'type': 'azure'})
1112 client = EnvJujuClient1X(env, None, None)
1113- with patch.object(client, 'juju') as mock:
1114+ with patch.object(
1115+ client, 'juju', autospec=True, return_value=(0, None)) as mock:
1116 client.destroy_environment(force=False)
1117 mock.assert_called_with(
1118 'destroy-environment', ('foo', '-y'),
1119@@ -600,7 +605,9 @@
1120 def test_destroy_environment_gce(self):
1121 env = SimpleEnvironment('foo', {'type': 'gce'})
1122 client = EnvJujuClient1X(env, None, None)
1123- with patch.object(client, 'juju') as mock:
1124+ with patch.object(
1125+ client, 'juju',
1126+ autospec=True, return_value=(0, None)) as mock:
1127 client.destroy_environment(force=False)
1128 mock.assert_called_with(
1129 'destroy-environment', ('foo', '-y'),
1130@@ -609,7 +616,8 @@
1131 def test_destroy_environment_delete_jenv(self):
1132 env = SimpleEnvironment('foo', {'type': 'ec2'})
1133 client = EnvJujuClient1X(env, None, None)
1134- with patch.object(client, 'juju'):
1135+ with patch.object(
1136+ client, 'juju', autospec=True, return_value=(0, None)):
1137 with temp_env({}) as juju_home:
1138 client.env.juju_home = juju_home
1139 jenv_path = get_jenv_path(juju_home, 'foo')
1140@@ -622,7 +630,8 @@
1141 def test_destroy_model(self):
1142 env = SimpleEnvironment('foo', {'type': 'ec2'})
1143 client = EnvJujuClient1X(env, None, None)
1144- with patch.object(client, 'juju') as mock:
1145+ with patch.object(
1146+ client, 'juju', autospec=True, return_value=(0, None)) as mock:
1147 client.destroy_model()
1148 mock.assert_called_with(
1149 'destroy-environment', ('foo', '-y'),
1150@@ -631,7 +640,9 @@
1151 def test_kill_controller(self):
1152 client = EnvJujuClient1X(
1153 SimpleEnvironment('foo', {'type': 'ec2'}), None, None)
1154- with patch.object(client, 'juju') as juju_mock:
1155+ with patch.object(
1156+ client, 'juju',
1157+ autospec=True, return_value=(0, None)) as juju_mock:
1158 client.kill_controller()
1159 juju_mock.assert_called_once_with(
1160 'destroy-environment', ('foo', '--force', '-y'), check=False,
1161@@ -640,7 +651,7 @@
1162 def test_kill_controller_check(self):
1163 client = EnvJujuClient1X(
1164 SimpleEnvironment('foo', {'type': 'ec2'}), None, None)
1165- with patch.object(client, 'juju') as juju_mock:
1166+ with patch_juju_call(client) as juju_mock:
1167 client.kill_controller(check=True)
1168 juju_mock.assert_called_once_with(
1169 'destroy-environment', ('foo', '--force', '-y'), check=True,
1170@@ -649,7 +660,7 @@
1171 def test_destroy_controller(self):
1172 client = EnvJujuClient1X(
1173 SimpleEnvironment('foo', {'type': 'ec2'}), None, None)
1174- with patch.object(client, 'juju') as juju_mock:
1175+ with patch_juju_call(client) as juju_mock:
1176 client.destroy_controller()
1177 juju_mock.assert_called_once_with(
1178 'destroy-environment', ('foo', '-y'),
1179@@ -813,21 +824,21 @@
1180 def test_deploy_non_joyent(self):
1181 env = EnvJujuClient1X(
1182 SimpleEnvironment('foo', {'type': 'local'}), '1.234-76', None)
1183- with patch.object(env, 'juju') as mock_juju:
1184+ with patch_juju_call(env) as mock_juju:
1185 env.deploy('mondogb')
1186 mock_juju.assert_called_with('deploy', ('mondogb',))
1187
1188 def test_deploy_joyent(self):
1189 env = EnvJujuClient1X(
1190 SimpleEnvironment('foo', {'type': 'local'}), '1.234-76', None)
1191- with patch.object(env, 'juju') as mock_juju:
1192+ with patch_juju_call(env) as mock_juju:
1193 env.deploy('mondogb')
1194 mock_juju.assert_called_with('deploy', ('mondogb',))
1195
1196 def test_deploy_repository(self):
1197 env = EnvJujuClient1X(
1198 SimpleEnvironment('foo', {'type': 'local'}), '1.234-76', None)
1199- with patch.object(env, 'juju') as mock_juju:
1200+ with patch_juju_call(env) as mock_juju:
1201 env.deploy('mondogb', '/home/jrandom/repo')
1202 mock_juju.assert_called_with(
1203 'deploy', ('mondogb', '--repository', '/home/jrandom/repo'))
1204@@ -835,7 +846,7 @@
1205 def test_deploy_to(self):
1206 env = EnvJujuClient1X(
1207 SimpleEnvironment('foo', {'type': 'local'}), '1.234-76', None)
1208- with patch.object(env, 'juju') as mock_juju:
1209+ with patch_juju_call(env) as mock_juju:
1210 env.deploy('mondogb', to='0')
1211 mock_juju.assert_called_with(
1212 'deploy', ('mondogb', '--to', '0'))
1213@@ -843,7 +854,7 @@
1214 def test_deploy_service(self):
1215 env = EnvJujuClient1X(
1216 SimpleEnvironment('foo', {'type': 'local'}), '1.234-76', None)
1217- with patch.object(env, 'juju') as mock_juju:
1218+ with patch_juju_call(env) as mock_juju:
1219 env.deploy('local:mondogb', service='my-mondogb')
1220 mock_juju.assert_called_with(
1221 'deploy', ('local:mondogb', 'my-mondogb',))
1222@@ -851,7 +862,7 @@
1223 def test_upgrade_charm(self):
1224 client = EnvJujuClient1X(
1225 SimpleEnvironment('foo', {'type': 'local'}), '1.234-76', None)
1226- with patch.object(client, 'juju') as mock_juju:
1227+ with patch_juju_call(client) as mock_juju:
1228 client.upgrade_charm('foo-service',
1229 '/bar/repository/angsty/mongodb')
1230 mock_juju.assert_called_once_with(
1231@@ -861,7 +872,7 @@
1232 def test_remove_service(self):
1233 client = EnvJujuClient1X(
1234 SimpleEnvironment('foo', {'type': 'local'}), '1.234-76', None)
1235- with patch.object(client, 'juju') as mock_juju:
1236+ with patch_juju_call(client) as mock_juju:
1237 client.remove_service('mondogb')
1238 mock_juju.assert_called_with('destroy-service', ('mondogb',))
1239
1240@@ -1348,7 +1359,7 @@
1241
1242 def test_set_model_constraints(self):
1243 client = EnvJujuClient1X(SimpleEnvironment('bar', {}), None, '/foo')
1244- with patch.object(client, 'juju') as juju_mock:
1245+ with patch_juju_call(client) as juju_mock:
1246 client.set_model_constraints({'bar': 'baz'})
1247 juju_mock.assert_called_once_with('set-constraints', ('bar=baz',))
1248
1249@@ -1592,7 +1603,7 @@
1250 def test_enable_ha(self):
1251 env = SimpleEnvironment('qux')
1252 client = EnvJujuClient1X(env, None, '/foobar/baz')
1253- with patch.object(client, 'juju', autospec=True) as eha_mock:
1254+ with patch_juju_call(client) as eha_mock:
1255 client.enable_ha()
1256 eha_mock.assert_called_once_with('ensure-availability', ('-n', '3'))
1257
1258@@ -1669,7 +1680,7 @@
1259 def test_deploy_bundle_1x(self):
1260 client = EnvJujuClient1X(SimpleEnvironment('an_env', None),
1261 '1.23-series-arch', None)
1262- with patch.object(client, 'juju') as mock_juju:
1263+ with patch_juju_call(client) as mock_juju:
1264 client.deploy_bundle('bundle:~juju-qa/some-bundle')
1265 mock_juju.assert_called_with(
1266 'deployer', ('--debug', '--deploy-delay', '10', '--timeout',
1267@@ -1678,7 +1689,7 @@
1268 def test_deploy_bundle_template(self):
1269 client = EnvJujuClient1X(SimpleEnvironment('an_env', None),
1270 '1.23-series-arch', None)
1271- with patch.object(client, 'juju') as mock_juju:
1272+ with patch_juju_call(client) as mock_juju:
1273 client.deploy_bundle('bundle:~juju-qa/some-{container}-bundle')
1274 mock_juju.assert_called_with(
1275 'deployer', (
1276@@ -1923,14 +1934,14 @@
1277 def test_add_space(self):
1278 client = EnvJujuClient1X(SimpleEnvironment(None, {'type': 'local'}),
1279 '1.23-series-arch', None)
1280- with patch.object(client, 'juju', autospec=True) as juju_mock:
1281+ with patch_juju_call(client) as juju_mock:
1282 client.add_space('foo-space')
1283 juju_mock.assert_called_once_with('space create', ('foo-space'))
1284
1285 def test_add_subnet(self):
1286 client = EnvJujuClient1X(SimpleEnvironment(None, {'type': 'local'}),
1287 '1.23-series-arch', None)
1288- with patch.object(client, 'juju', autospec=True) as juju_mock:
1289+ with patch_juju_call(client) as juju_mock:
1290 client.add_subnet('bar-subnet', 'foo-space')
1291 juju_mock.assert_called_once_with('subnet add',
1292 ('bar-subnet', 'foo-space'))
1293@@ -1944,7 +1955,7 @@
1294
1295 def test_set_config(self):
1296 client = EnvJujuClient1X(SimpleEnvironment('bar', {}), None, '/foo')
1297- with patch.object(client, 'juju') as juju_mock:
1298+ with patch_juju_call(client) as juju_mock:
1299 client.set_config('foo', {'bar': 'baz'})
1300 juju_mock.assert_called_once_with('set', ('foo', 'bar=baz'))
1301
1302@@ -2065,13 +2076,13 @@
1303
1304 def test_disable_command(self):
1305 client = EnvJujuClient1X(SimpleEnvironment('foo'), None, None)
1306- with patch.object(client, 'juju', autospec=True) as mock:
1307+ with patch_juju_call(client) as mock:
1308 client.disable_command('all', 'message')
1309 mock.assert_called_once_with('block all', ('message', ))
1310
1311 def test_enable_command(self):
1312 client = EnvJujuClient1X(SimpleEnvironment('foo'), None, None)
1313- with patch.object(client, 'juju', autospec=True) as mock:
1314+ with patch_juju_call(client) as mock:
1315 client.enable_command('all')
1316 mock.assert_called_once_with('unblock', 'all')
1317
1318
1319=== modified file 'jujupy/version_client.py'
1320--- jujupy/version_client.py 2017-03-10 19:54:30 +0000
1321+++ jujupy/version_client.py 2017-03-23 04:46:32 +0000
1322@@ -9,6 +9,8 @@
1323 import yaml
1324
1325 from jujupy.client import (
1326+ BaseCondition,
1327+ CommandComplete,
1328 Controller,
1329 _DEFAULT_BUNDLE_TIMEOUT,
1330 get_cache_path,
1331@@ -356,7 +358,8 @@
1332
1333 def set_model_constraints(self, constraints):
1334 constraint_strings = self._dict_as_option_strings(constraints)
1335- return self.juju('set-constraints', constraint_strings)
1336+ retvar, ct = self.juju('set-constraints', constraint_strings)
1337+ return retvar, CommandComplete(BaseCondition(), ct)
1338
1339 def set_config(self, service, options):
1340 option_strings = ['{}={}'.format(*item) for item in options.items()]
1341@@ -377,11 +380,13 @@
1342 def set_env_option(self, option, value):
1343 """Set the value of the option in the environment."""
1344 option_value = "%s=%s" % (option, value)
1345- return self.juju('set-env', (option_value,))
1346+ retvar, ct = self.juju('set-env', (option_value,))
1347+ return retvar, CommandComplete(BaseCondition(), ct)
1348
1349 def unset_env_option(self, option):
1350 """Unset the value of the option in the environment."""
1351- return self.juju('set-env', ('{}='.format(option),))
1352+ retvar, ct = self.juju('set-env', ('{}='.format(option),))
1353+ return retvar, CommandComplete(BaseCondition(), ct)
1354
1355 def get_model_defaults(self, model_key, cloud=None, region=None):
1356 log.info('No model-defaults stored for client (attempted get).')
1357@@ -480,25 +485,27 @@
1358 """Destroy the environment, with force. Hard kill option.
1359
1360 :return: Subprocess's exit code."""
1361- return self.juju(
1362+ retvar, ct = self.juju(
1363 'destroy-environment', (self.env.environment, '--force', '-y'),
1364 check=check, include_e=False, timeout=get_teardown_timeout(self))
1365+ return retvar, CommandComplete(BaseCondition(), ct)
1366
1367 def destroy_controller(self, all_models=False):
1368 """Destroy the environment, with force. Soft kill option.
1369
1370 :param all_models: Ignored.
1371 :raises: subprocess.CalledProcessError if the operation fails."""
1372- return self.juju(
1373+ retvar, ct = self.juju(
1374 'destroy-environment', (self.env.environment, '-y'),
1375 include_e=False, timeout=get_teardown_timeout(self))
1376+ return retvar, CommandComplete(BaseCondition(), ct)
1377
1378 def destroy_environment(self, force=True, delete_jenv=False):
1379 if force:
1380 force_arg = ('--force',)
1381 else:
1382 force_arg = ()
1383- exit_status = self.juju(
1384+ exit_status, _ = self.juju(
1385 'destroy-environment',
1386 (self.env.environment,) + force_arg + ('-y',),
1387 check=False, include_e=False,
1388@@ -555,7 +562,8 @@
1389 args.extend(['--storage', storage])
1390 if constraints is not None:
1391 args.extend(['--constraints', constraints])
1392- return self.juju('deploy', tuple(args))
1393+ retvar, ct = self.juju('deploy', tuple(args))
1394+ return retvar, CommandComplete(BaseCondition(), ct)
1395
1396 def upgrade_charm(self, service, charm_path=None):
1397 args = (service,)
1398@@ -644,11 +652,13 @@
1399
1400 def disable_command(self, command_set, message=''):
1401 """Disable a command-set."""
1402- return self.juju('block {}'.format(command_set), (message, ))
1403+ retvar, ct = self.juju('block {}'.format(command_set), (message, ))
1404+ return retvar, CommandComplete(BaseCondition(), ct)
1405
1406 def enable_command(self, args):
1407 """Enable a command-set."""
1408- return self.juju('unblock', args)
1409+ retvar, ct = self.juju('unblock', args)
1410+ return retvar, CommandComplete(BaseCondition(), ct)
1411
1412
1413 class EnvJujuClient22(EnvJujuClient1X):
1414
1415=== modified file 'tests/__init__.py'
1416--- tests/__init__.py 2017-03-10 19:46:30 +0000
1417+++ tests/__init__.py 2017-03-23 04:46:32 +0000
1418@@ -20,6 +20,7 @@
1419 from unittest.mock import patch
1420 import yaml
1421
1422+from jujupy.client import CommandTime
1423 import utility
1424
1425
1426@@ -152,12 +153,25 @@
1427 os.environ[key] = org_value
1428
1429
1430+@contextmanager
1431+def patch_juju_call(client, return_value=0):
1432+ """Simple patch for client.juju call.
1433+
1434+ :param return_value: A tuple to return representing the retvar and
1435+ CommandTime object
1436+ """
1437+ with patch.object(
1438+ client, 'juju',
1439+ return_value=make_fake_juju_return(retvar=return_value)) as mock:
1440+ yield mock
1441+
1442+
1443 def assert_juju_call(test_case, mock_method, client, expected_args,
1444 call_index=None):
1445 """Check a mock's positional arguments.
1446
1447 :param test_case: The test case currently being run.
1448- :param mock_mothod: The mock object to be checked.
1449+ :param mock_method: The mock object to be checked.
1450 :param client: Ignored.
1451 :param expected_args: The expected positional arguments for the call.
1452 :param call_index: Index of the call to check, if None checks first call
1453@@ -248,3 +262,9 @@
1454 },
1455 }
1456 }
1457+
1458+
1459+def make_fake_juju_return(
1460+ retvar=0, cmd='mock_cmd', full_args=[], envvars=None, start=None):
1461+ """Shadow fake that defaults construction arguments."""
1462+ return (retvar, CommandTime(cmd, full_args, envvars, start))
1463
1464=== modified file 'tests/test_assess_block.py'
1465--- tests/test_assess_block.py 2017-01-20 20:49:41 +0000
1466+++ tests/test_assess_block.py 2017-03-23 04:46:32 +0000
1467@@ -18,6 +18,7 @@
1468 )
1469 from tests import (
1470 parse_error,
1471+ patch_juju_call,
1472 FakeHomeTestCase,
1473 TestCase,
1474 )
1475@@ -149,7 +150,7 @@
1476 autospec=True, side_effect=side_effects):
1477 with patch('assess_block.deploy_dummy_stack', autospec=True):
1478 with patch.object(client, 'remove_service') as mock_rs:
1479- with patch.object(client, 'juju') as mock_juju:
1480+ with patch_juju_call(client) as mock_juju:
1481 with patch.object(
1482 client, 'wait_for_started') as mock_ws:
1483 assess_block(client, 'trusty')
1484
1485=== modified file 'tests/test_assess_bootstrap.py'
1486--- tests/test_assess_bootstrap.py 2017-03-01 19:02:24 +0000
1487+++ tests/test_assess_bootstrap.py 2017-03-23 04:46:32 +0000
1488@@ -29,6 +29,7 @@
1489 from tests import (
1490 FakeHomeTestCase,
1491 TestCase,
1492+ make_fake_juju_return,
1493 )
1494 from utility import (
1495 JujuAssertionError,
1496@@ -361,7 +362,7 @@
1497 args.temp_env_name = 'qux'
1498 with extended_bootstrap_cxt('2.0.0'):
1499 with patch('jujupy.ModelClient.juju', autospec=True,
1500- side_effect=['', '']) as j_mock:
1501+ return_value=make_fake_juju_return()) as j_mock:
1502 with patch('assess_bootstrap.get_controller_hostname',
1503 return_value='test-host', autospec=True):
1504 bs_manager = BootstrapManager.from_args(args)
1505
1506=== modified file 'tests/test_assess_container_networking.py'
1507--- tests/test_assess_container_networking.py 2017-01-20 20:58:41 +0000
1508+++ tests/test_assess_container_networking.py 2017-03-23 04:46:32 +0000
1509@@ -17,6 +17,7 @@
1510 LXD_MACHINE,
1511 SimpleEnvironment,
1512 )
1513+from jujupy.client import CommandTime
1514
1515 import assess_container_networking as jcnet
1516 from tests import (
1517@@ -78,6 +79,29 @@
1518 if cmd != 'bootstrap':
1519 self.commands.append((cmd, args))
1520 if cmd == 'ssh':
1521+ ct = CommandTime(cmd, args)
1522+ if len(self._ssh_output) == 0:
1523+ return "", ct
1524+
1525+ try:
1526+ ct = CommandTime(cmd, args)
1527+ return self._ssh_output[self._call_number()], ct
1528+ except IndexError:
1529+ # If we ran out of values, just return the last one
1530+ return self._ssh_output[-1], ct
1531+ else:
1532+ return super(JujuMock, self).juju(cmd, *rargs, **kwargs)
1533+
1534+ def get_juju_output(self, cmd, *rargs, **kwargs):
1535+ # Almost exactly like juju() except get_juju_output doesn't return
1536+ # a CommandTime
1537+ if len(rargs) == 1:
1538+ args = rargs[0]
1539+ else:
1540+ args = rargs
1541+ if cmd != 'bootstrap':
1542+ self.commands.append((cmd, args))
1543+ if cmd == 'ssh':
1544 if len(self._ssh_output) == 0:
1545 return ""
1546
1547@@ -87,7 +111,7 @@
1548 # If we ran out of values, just return the last one
1549 return self._ssh_output[-1]
1550 else:
1551- return super(JujuMock, self).juju(cmd, *rargs, **kwargs)
1552+ return super(JujuMock, self).get_juju_output(cmd, *rargs, **kwargs)
1553
1554 def _call_number(self):
1555 call_number = self._call_n
1556@@ -117,7 +141,9 @@
1557 patch.object(self.client, 'wait_for', lambda *args, **kw: None),
1558 patch.object(self.client, 'wait_for_started',
1559 self.juju_mock.get_status),
1560- patch.object(self.client, 'get_juju_output', self.juju_mock.juju),
1561+ patch.object(
1562+ self.client, 'get_juju_output',
1563+ self.juju_mock.get_juju_output),
1564 ]
1565
1566 for patcher in patches:
1567
1568=== modified file 'tests/test_assess_user_grant_revoke.py'
1569--- tests/test_assess_user_grant_revoke.py 2017-01-20 21:35:27 +0000
1570+++ tests/test_assess_user_grant_revoke.py 2017-03-23 04:46:32 +0000
1571@@ -27,6 +27,7 @@
1572 from tests import (
1573 parse_error,
1574 TestCase,
1575+ patch_juju_call,
1576 )
1577
1578
1579@@ -150,7 +151,7 @@
1580 def test_assert_write_model(self):
1581 fake_client = fake_juju_client()
1582 with patch.object(fake_client, 'wait_for_started'):
1583- with patch.object(fake_client, 'juju', return_value=True):
1584+ with patch_juju_call(fake_client):
1585 assert_write_model(fake_client, 'write', True)
1586 with self.assertRaises(JujuAssertionError):
1587 assert_write_model(fake_client, 'write', False)
1588
1589=== modified file 'tests/test_deploy_stack.py'
1590--- tests/test_deploy_stack.py 2017-03-10 00:50:40 +0000
1591+++ tests/test_deploy_stack.py 2017-03-23 04:46:32 +0000
1592@@ -82,6 +82,7 @@
1593 assert_juju_call,
1594 FakeHomeTestCase,
1595 FakePopen,
1596+ make_fake_juju_return,
1597 observable_temp_file,
1598 temp_os_env,
1599 use_context,
1600@@ -1757,7 +1758,7 @@
1601 def do_check(*args, **kwargs):
1602 with client.check_timeouts():
1603 with tear_down_client.check_timeouts():
1604- pass
1605+ return make_fake_juju_return()
1606
1607 with patch.object(bs_manager.tear_down_client, 'juju',
1608 side_effect=do_check, autospec=True):
1609@@ -1996,7 +1997,9 @@
1610 bs_manager.has_controller = False
1611 with patch('deploy_stack.safe_print_status',
1612 autospec=True) as sp_mock:
1613- with patch.object(client, 'juju', wrap=client.juju) as juju_mock:
1614+ with patch.object(
1615+ client, 'juju', wrap=client.juju,
1616+ return_value=make_fake_juju_return()) as juju_mock:
1617 with patch.object(client, 'get_juju_output',
1618 wraps=client.get_juju_output) as gjo_mock:
1619 with patch.object(bs_manager, '_should_dump',
1620@@ -2085,7 +2088,9 @@
1621 """Preform patches to focus on the call to bootstrap."""
1622 with patch.object(bs_manager, 'dump_all_logs'):
1623 with patch.object(bs_manager, 'runtime_context'):
1624- with patch.object(bs_manager.client, 'juju'):
1625+ with patch.object(
1626+ bs_manager.client, 'juju',
1627+ return_value=make_fake_juju_return()):
1628 with patch.object(bs_manager.client, 'bootstrap') as mock:
1629 yield mock
1630

Subscribers

People subscribed via source and target branches