Merge lp:~veebers/juju-ci-tools/introduce-commandtime into lp:juju-ci-tools

Proposed by Christopher Lee
Status: Merged
Merged at revision: 1982
Proposed branch: lp:~veebers/juju-ci-tools/introduce-commandtime
Merge into: lp:juju-ci-tools
Prerequisite: lp:~veebers/juju-ci-tools/poc-juju-ci-timing
Diff against target: 327 lines (+79/-24)
9 files modified
assess_container_networking.py (+1/-1)
chaos.py (+3/-2)
jujupy/client.py (+12/-8)
jujupy/fake.py (+2/-0)
jujupy/tests/test_client.py (+4/-3)
jujupy/tests/test_version_client.py (+17/-7)
jujupy/version_client.py (+1/-1)
tests/__init__.py (+11/-0)
tests/test_assess_container_networking.py (+28/-2)
To merge this branch: bzr merge lp:~veebers/juju-ci-tools/introduce-commandtime
Reviewer Review Type Date Requested Status
Curtis Hovey code Pending
Review via email: mp+320857@code.launchpad.net

This proposal supersedes a proposal from 2017-03-22.

Description of the change

Introduce CommandTime to ModelClient.juju().

This is the next step in getting CommandTime and CommandComplete introduced.
Included in this MP is an update to tests (due to the slightly different ModelClient.juju return behaviour) as well as consideration for any existing code that store the return from .juju() or any of the wrapper commands (e.g. deploy)

To post a comment you must log in.
Revision history for this message
Curtis Hovey (sinzui) wrote : Posted in a previous version of this proposal

Thank you. I don't like the name actual_completion() I have some ideas inline. I leave it to you to decide the name you like. I also have a suggestion for a a test.

review: Approve (code)
Revision history for this message
Aaron Bentley (abentley) wrote : Posted in a previous version of this proposal

I think maybe Chris forgot to make lp:~veebers/juju-ci-tools/poc-juju-ci-timing a prerequisite.

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 19:29:02 +0000
4@@ -248,7 +248,7 @@
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+ rc, _ = client.juju('ssh', ('--proxy', target,
10 'ping -c1 -q ' + d.group(1)), check=False)
11 if rc != 0:
12 raise ValueError('%s unable to ping default route' % target)
13
14=== modified file 'chaos.py'
15--- chaos.py 2016-06-27 11:41:04 +0000
16+++ chaos.py 2017-03-23 19:29:02 +0000
17@@ -146,8 +146,9 @@
18 check_cmd += '/chaos_monkey.' + self.monkey_ids[unit_name]
19 check_cmd += '/chaos_runner.lock'
20 check_cmd += ' ]'
21- if self.client.juju('run', ('--unit', unit_name, check_cmd),
22- check=False):
23+ retvar, _ = self.client.juju(
24+ 'run', ('--unit', unit_name, check_cmd), check=False)
25+ if retvar != 0:
26 return 'done'
27 return 'running'
28
29
30=== modified file 'jujupy/client.py'
31--- jujupy/client.py 2017-03-23 19:29:02 +0000
32+++ jujupy/client.py 2017-03-23 19:29:02 +0000
33@@ -1079,7 +1079,7 @@
34 self.feature_flags = feature_flags
35 self.debug = debug
36 self._timeout_path = get_timeout_path()
37- self.juju_timings = {}
38+ self.juju_timings = []
39 self.soft_deadline = soft_deadline
40 self._ignore_soft_deadline = False
41
42@@ -1181,7 +1181,11 @@
43 def juju(self, command, args, used_feature_flags,
44 juju_home, model=None, check=True, timeout=None, extra_env=None,
45 suppress_err=False):
46- """Run a command under juju for the current environment."""
47+ """Run a command under juju for the current environment.
48+
49+ :return: Tuple rval, CommandTime rval being the commands exit code and
50+ a CommandTime object used for storing command timing data.
51+ """
52 args = self.full_args(command, args, model, timeout)
53 log.info(' '.join(args))
54 env = self.shell_environ(used_feature_flags, juju_home)
55@@ -1191,17 +1195,17 @@
56 call_func = subprocess.check_call
57 else:
58 call_func = subprocess.call
59- start_time = time.time()
60 # Mutate os.environ instead of supplying env parameter so Windows can
61 # search env['PATH']
62 stderr = subprocess.PIPE if suppress_err else None
63+ # Keep track of commands and how long the take.
64+ command_time = CommandTime(command, args, env)
65 with scoped_environ(env):
66 log.debug('Running juju with env: {}'.format(env))
67 with self._check_timeouts():
68 rval = call_func(args, stderr=stderr)
69- self.juju_timings.setdefault(args, []).append(
70- (time.time() - start_time))
71- return rval
72+ self.juju_timings.append(command_time)
73+ return rval, command_time
74
75 def expect(self, command, args, used_feature_flags, juju_home, model=None,
76 timeout=None, extra_env=None):
77@@ -1926,7 +1930,7 @@
78 '--config', config_file))
79
80 def destroy_model(self):
81- exit_status = self.juju(
82+ exit_status, _ = self.juju(
83 'destroy-model', (self.env.environment, '-y',),
84 include_e=False, timeout=get_teardown_timeout(self))
85 return exit_status
86@@ -1960,7 +1964,7 @@
87 self.destroy_controller(all_models=True)
88 except subprocess.CalledProcessError:
89 logging.warning('tear_down destroy-controller failed')
90- retval = self.kill_controller()
91+ retval, _ = self.kill_controller()
92 message = 'tear_down kill-controller result={}'.format(retval)
93 if retval == 0:
94 logging.info(message)
95
96=== modified file 'jujupy/fake.py'
97--- jujupy/fake.py 2017-03-17 20:43:14 +0000
98+++ jujupy/fake.py 2017-03-23 19:29:02 +0000
99@@ -19,6 +19,7 @@
100 JujuData,
101 SoftDeadlineExceeded,
102 )
103+from jujupy.client import CommandTime
104
105 __metaclass__ = type
106
107@@ -977,6 +978,7 @@
108 self.controller_state.shares.remove(username)
109 if command == 'restore-backup':
110 model_state.restore_backup()
111+ return 0, CommandTime(command, args)
112
113 @contextmanager
114 def juju_async(self, command, args, used_feature_flags,
115
116=== modified file 'jujupy/tests/test_client.py'
117--- jujupy/tests/test_client.py 2017-03-23 19:29:02 +0000
118+++ jujupy/tests/test_client.py 2017-03-23 19:29:02 +0000
119@@ -104,6 +104,7 @@
120 FakeHomeTestCase,
121 FakePopen,
122 observable_temp_file,
123+ patch_juju_call,
124 TestCase,
125 )
126 from jujupy.utility import (
127@@ -904,7 +905,7 @@
128 def test_destroy_model(self):
129 env = JujuData('foo', {'type': 'ec2'})
130 client = ModelClient(env, None, None)
131- with patch.object(client, 'juju') as mock:
132+ with patch_juju_call(client) as mock:
133 client.destroy_model()
134 mock.assert_called_with(
135 'destroy-model', ('foo', '-y'),
136@@ -913,7 +914,7 @@
137 def test_destroy_model_azure(self):
138 env = JujuData('foo', {'type': 'azure'})
139 client = ModelClient(env, None, None)
140- with patch.object(client, 'juju') as mock:
141+ with patch_juju_call(client) as mock:
142 client.destroy_model()
143 mock.assert_called_with(
144 'destroy-model', ('foo', '-y'),
145@@ -922,7 +923,7 @@
146 def test_destroy_model_gce(self):
147 env = JujuData('foo', {'type': 'gce'})
148 client = ModelClient(env, None, None)
149- with patch.object(client, 'juju') as mock:
150+ with patch_juju_call(client) as mock:
151 client.destroy_model()
152 mock.assert_called_with(
153 'destroy-model', ('foo', '-y'),
154
155=== modified file 'jujupy/tests/test_version_client.py'
156--- jujupy/tests/test_version_client.py 2017-03-10 21:15:12 +0000
157+++ jujupy/tests/test_version_client.py 2017-03-23 19:29:02 +0000
158@@ -573,7 +573,8 @@
159 def test_destroy_environment(self):
160 env = SimpleEnvironment('foo', {'type': 'ec2'})
161 client = EnvJujuClient1X(env, None, None)
162- with patch.object(client, 'juju') as mock:
163+ with patch.object(
164+ client, 'juju', autospec=True, return_value=(0, None)) as mock:
165 client.destroy_environment()
166 mock.assert_called_with(
167 'destroy-environment', ('foo', '--force', '-y'),
168@@ -582,7 +583,9 @@
169 def test_destroy_environment_no_force(self):
170 env = SimpleEnvironment('foo', {'type': 'ec2'})
171 client = EnvJujuClient1X(env, None, None)
172- with patch.object(client, 'juju') as mock:
173+ with patch.object(
174+ client, 'juju',
175+ autospec=True, return_value=(0, None)) as mock:
176 client.destroy_environment(force=False)
177 mock.assert_called_with(
178 'destroy-environment', ('foo', '-y'),
179@@ -591,7 +594,8 @@
180 def test_destroy_environment_azure(self):
181 env = SimpleEnvironment('foo', {'type': 'azure'})
182 client = EnvJujuClient1X(env, None, None)
183- with patch.object(client, 'juju') as mock:
184+ with patch.object(
185+ client, 'juju', autospec=True, return_value=(0, None)) as mock:
186 client.destroy_environment(force=False)
187 mock.assert_called_with(
188 'destroy-environment', ('foo', '-y'),
189@@ -600,7 +604,9 @@
190 def test_destroy_environment_gce(self):
191 env = SimpleEnvironment('foo', {'type': 'gce'})
192 client = EnvJujuClient1X(env, None, None)
193- with patch.object(client, 'juju') as mock:
194+ with patch.object(
195+ client, 'juju',
196+ autospec=True, return_value=(0, None)) as mock:
197 client.destroy_environment(force=False)
198 mock.assert_called_with(
199 'destroy-environment', ('foo', '-y'),
200@@ -609,7 +615,8 @@
201 def test_destroy_environment_delete_jenv(self):
202 env = SimpleEnvironment('foo', {'type': 'ec2'})
203 client = EnvJujuClient1X(env, None, None)
204- with patch.object(client, 'juju'):
205+ with patch.object(
206+ client, 'juju', autospec=True, return_value=(0, None)):
207 with temp_env({}) as juju_home:
208 client.env.juju_home = juju_home
209 jenv_path = get_jenv_path(juju_home, 'foo')
210@@ -622,7 +629,8 @@
211 def test_destroy_model(self):
212 env = SimpleEnvironment('foo', {'type': 'ec2'})
213 client = EnvJujuClient1X(env, None, None)
214- with patch.object(client, 'juju') as mock:
215+ with patch.object(
216+ client, 'juju', autospec=True, return_value=(0, None)) as mock:
217 client.destroy_model()
218 mock.assert_called_with(
219 'destroy-environment', ('foo', '-y'),
220@@ -631,7 +639,9 @@
221 def test_kill_controller(self):
222 client = EnvJujuClient1X(
223 SimpleEnvironment('foo', {'type': 'ec2'}), None, None)
224- with patch.object(client, 'juju') as juju_mock:
225+ with patch.object(
226+ client, 'juju',
227+ autospec=True, return_value=(0, None)) as juju_mock:
228 client.kill_controller()
229 juju_mock.assert_called_once_with(
230 'destroy-environment', ('foo', '--force', '-y'), check=False,
231
232=== modified file 'jujupy/version_client.py'
233--- jujupy/version_client.py 2017-03-10 19:54:30 +0000
234+++ jujupy/version_client.py 2017-03-23 19:29:02 +0000
235@@ -498,7 +498,7 @@
236 force_arg = ('--force',)
237 else:
238 force_arg = ()
239- exit_status = self.juju(
240+ exit_status, _ = self.juju(
241 'destroy-environment',
242 (self.env.environment,) + force_arg + ('-y',),
243 check=False, include_e=False,
244
245=== modified file 'tests/__init__.py'
246--- tests/__init__.py 2017-03-10 19:46:30 +0000
247+++ tests/__init__.py 2017-03-23 19:29:02 +0000
248@@ -152,6 +152,17 @@
249 os.environ[key] = org_value
250
251
252+@contextmanager
253+def patch_juju_call(client, return_value=(0, None)):
254+ """Simple patch for client.juju call.
255+
256+ :param return_value: A tuple to return representing the retvar and
257+ CommandTime object
258+ """
259+ with patch.object(client, 'juju', return_value=return_value) as mock:
260+ yield mock
261+
262+
263 def assert_juju_call(test_case, mock_method, client, expected_args,
264 call_index=None):
265 """Check a mock's positional arguments.
266
267=== modified file 'tests/test_assess_container_networking.py'
268--- tests/test_assess_container_networking.py 2017-01-20 20:58:41 +0000
269+++ tests/test_assess_container_networking.py 2017-03-23 19:29:02 +0000
270@@ -17,6 +17,7 @@
271 LXD_MACHINE,
272 SimpleEnvironment,
273 )
274+from jujupy.client import CommandTime
275
276 import assess_container_networking as jcnet
277 from tests import (
278@@ -78,6 +79,29 @@
279 if cmd != 'bootstrap':
280 self.commands.append((cmd, args))
281 if cmd == 'ssh':
282+ ct = CommandTime(cmd, args)
283+ if len(self._ssh_output) == 0:
284+ return "", ct
285+
286+ try:
287+ ct = CommandTime(cmd, args)
288+ return self._ssh_output[self._call_number()], ct
289+ except IndexError:
290+ # If we ran out of values, just return the last one
291+ return self._ssh_output[-1], ct
292+ else:
293+ return super(JujuMock, self).juju(cmd, *rargs, **kwargs)
294+
295+ def get_juju_output(self, cmd, *rargs, **kwargs):
296+ # Almost exactly like juju() except get_juju_output doesn't return
297+ # a CommandTime
298+ if len(rargs) == 1:
299+ args = rargs[0]
300+ else:
301+ args = rargs
302+ if cmd != 'bootstrap':
303+ self.commands.append((cmd, args))
304+ if cmd == 'ssh':
305 if len(self._ssh_output) == 0:
306 return ""
307
308@@ -87,7 +111,7 @@
309 # If we ran out of values, just return the last one
310 return self._ssh_output[-1]
311 else:
312- return super(JujuMock, self).juju(cmd, *rargs, **kwargs)
313+ return super(JujuMock, self).get_juju_output(cmd, *rargs, **kwargs)
314
315 def _call_number(self):
316 call_number = self._call_n
317@@ -117,7 +141,9 @@
318 patch.object(self.client, 'wait_for', lambda *args, **kw: None),
319 patch.object(self.client, 'wait_for_started',
320 self.juju_mock.get_status),
321- patch.object(self.client, 'get_juju_output', self.juju_mock.juju),
322+ patch.object(
323+ self.client, 'get_juju_output',
324+ self.juju_mock.get_juju_output),
325 ]
326
327 for patcher in patches:

Subscribers

People subscribed via source and target branches