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
=== modified file 'assess_container_networking.py'
--- assess_container_networking.py 2017-02-24 18:28:04 +0000
+++ assess_container_networking.py 2017-03-23 19:29:02 +0000
@@ -248,7 +248,7 @@
248248
249 d = re.search(r'^default\s+via\s+([\d\.]+)\s+', routes, re.MULTILINE)249 d = re.search(r'^default\s+via\s+([\d\.]+)\s+', routes, re.MULTILINE)
250 if d:250 if d:
251 rc = client.juju('ssh', ('--proxy', target,251 rc, _ = client.juju('ssh', ('--proxy', target,
252 'ping -c1 -q ' + d.group(1)), check=False)252 'ping -c1 -q ' + d.group(1)), check=False)
253 if rc != 0:253 if rc != 0:
254 raise ValueError('%s unable to ping default route' % target)254 raise ValueError('%s unable to ping default route' % target)
255255
=== modified file 'chaos.py'
--- chaos.py 2016-06-27 11:41:04 +0000
+++ chaos.py 2017-03-23 19:29:02 +0000
@@ -146,8 +146,9 @@
146 check_cmd += '/chaos_monkey.' + self.monkey_ids[unit_name]146 check_cmd += '/chaos_monkey.' + self.monkey_ids[unit_name]
147 check_cmd += '/chaos_runner.lock'147 check_cmd += '/chaos_runner.lock'
148 check_cmd += ' ]'148 check_cmd += ' ]'
149 if self.client.juju('run', ('--unit', unit_name, check_cmd),149 retvar, _ = self.client.juju(
150 check=False):150 'run', ('--unit', unit_name, check_cmd), check=False)
151 if retvar != 0:
151 return 'done'152 return 'done'
152 return 'running'153 return 'running'
153154
154155
=== modified file 'jujupy/client.py'
--- jujupy/client.py 2017-03-23 19:29:02 +0000
+++ jujupy/client.py 2017-03-23 19:29:02 +0000
@@ -1079,7 +1079,7 @@
1079 self.feature_flags = feature_flags1079 self.feature_flags = feature_flags
1080 self.debug = debug1080 self.debug = debug
1081 self._timeout_path = get_timeout_path()1081 self._timeout_path = get_timeout_path()
1082 self.juju_timings = {}1082 self.juju_timings = []
1083 self.soft_deadline = soft_deadline1083 self.soft_deadline = soft_deadline
1084 self._ignore_soft_deadline = False1084 self._ignore_soft_deadline = False
10851085
@@ -1181,7 +1181,11 @@
1181 def juju(self, command, args, used_feature_flags,1181 def juju(self, command, args, used_feature_flags,
1182 juju_home, model=None, check=True, timeout=None, extra_env=None,1182 juju_home, model=None, check=True, timeout=None, extra_env=None,
1183 suppress_err=False):1183 suppress_err=False):
1184 """Run a command under juju for the current environment."""1184 """Run a command under juju for the current environment.
1185
1186 :return: Tuple rval, CommandTime rval being the commands exit code and
1187 a CommandTime object used for storing command timing data.
1188 """
1185 args = self.full_args(command, args, model, timeout)1189 args = self.full_args(command, args, model, timeout)
1186 log.info(' '.join(args))1190 log.info(' '.join(args))
1187 env = self.shell_environ(used_feature_flags, juju_home)1191 env = self.shell_environ(used_feature_flags, juju_home)
@@ -1191,17 +1195,17 @@
1191 call_func = subprocess.check_call1195 call_func = subprocess.check_call
1192 else:1196 else:
1193 call_func = subprocess.call1197 call_func = subprocess.call
1194 start_time = time.time()
1195 # Mutate os.environ instead of supplying env parameter so Windows can1198 # Mutate os.environ instead of supplying env parameter so Windows can
1196 # search env['PATH']1199 # search env['PATH']
1197 stderr = subprocess.PIPE if suppress_err else None1200 stderr = subprocess.PIPE if suppress_err else None
1201 # Keep track of commands and how long the take.
1202 command_time = CommandTime(command, args, env)
1198 with scoped_environ(env):1203 with scoped_environ(env):
1199 log.debug('Running juju with env: {}'.format(env))1204 log.debug('Running juju with env: {}'.format(env))
1200 with self._check_timeouts():1205 with self._check_timeouts():
1201 rval = call_func(args, stderr=stderr)1206 rval = call_func(args, stderr=stderr)
1202 self.juju_timings.setdefault(args, []).append(1207 self.juju_timings.append(command_time)
1203 (time.time() - start_time))1208 return rval, command_time
1204 return rval
12051209
1206 def expect(self, command, args, used_feature_flags, juju_home, model=None,1210 def expect(self, command, args, used_feature_flags, juju_home, model=None,
1207 timeout=None, extra_env=None):1211 timeout=None, extra_env=None):
@@ -1926,7 +1930,7 @@
1926 '--config', config_file))1930 '--config', config_file))
19271931
1928 def destroy_model(self):1932 def destroy_model(self):
1929 exit_status = self.juju(1933 exit_status, _ = self.juju(
1930 'destroy-model', (self.env.environment, '-y',),1934 'destroy-model', (self.env.environment, '-y',),
1931 include_e=False, timeout=get_teardown_timeout(self))1935 include_e=False, timeout=get_teardown_timeout(self))
1932 return exit_status1936 return exit_status
@@ -1960,7 +1964,7 @@
1960 self.destroy_controller(all_models=True)1964 self.destroy_controller(all_models=True)
1961 except subprocess.CalledProcessError:1965 except subprocess.CalledProcessError:
1962 logging.warning('tear_down destroy-controller failed')1966 logging.warning('tear_down destroy-controller failed')
1963 retval = self.kill_controller()1967 retval, _ = self.kill_controller()
1964 message = 'tear_down kill-controller result={}'.format(retval)1968 message = 'tear_down kill-controller result={}'.format(retval)
1965 if retval == 0:1969 if retval == 0:
1966 logging.info(message)1970 logging.info(message)
19671971
=== modified file 'jujupy/fake.py'
--- jujupy/fake.py 2017-03-17 20:43:14 +0000
+++ jujupy/fake.py 2017-03-23 19:29:02 +0000
@@ -19,6 +19,7 @@
19 JujuData,19 JujuData,
20 SoftDeadlineExceeded,20 SoftDeadlineExceeded,
21)21)
22from jujupy.client import CommandTime
2223
23__metaclass__ = type24__metaclass__ = type
2425
@@ -977,6 +978,7 @@
977 self.controller_state.shares.remove(username)978 self.controller_state.shares.remove(username)
978 if command == 'restore-backup':979 if command == 'restore-backup':
979 model_state.restore_backup()980 model_state.restore_backup()
981 return 0, CommandTime(command, args)
980982
981 @contextmanager983 @contextmanager
982 def juju_async(self, command, args, used_feature_flags,984 def juju_async(self, command, args, used_feature_flags,
983985
=== modified file 'jujupy/tests/test_client.py'
--- jujupy/tests/test_client.py 2017-03-23 19:29:02 +0000
+++ jujupy/tests/test_client.py 2017-03-23 19:29:02 +0000
@@ -104,6 +104,7 @@
104 FakeHomeTestCase,104 FakeHomeTestCase,
105 FakePopen,105 FakePopen,
106 observable_temp_file,106 observable_temp_file,
107 patch_juju_call,
107 TestCase,108 TestCase,
108 )109 )
109from jujupy.utility import (110from jujupy.utility import (
@@ -904,7 +905,7 @@
904 def test_destroy_model(self):905 def test_destroy_model(self):
905 env = JujuData('foo', {'type': 'ec2'})906 env = JujuData('foo', {'type': 'ec2'})
906 client = ModelClient(env, None, None)907 client = ModelClient(env, None, None)
907 with patch.object(client, 'juju') as mock:908 with patch_juju_call(client) as mock:
908 client.destroy_model()909 client.destroy_model()
909 mock.assert_called_with(910 mock.assert_called_with(
910 'destroy-model', ('foo', '-y'),911 'destroy-model', ('foo', '-y'),
@@ -913,7 +914,7 @@
913 def test_destroy_model_azure(self):914 def test_destroy_model_azure(self):
914 env = JujuData('foo', {'type': 'azure'})915 env = JujuData('foo', {'type': 'azure'})
915 client = ModelClient(env, None, None)916 client = ModelClient(env, None, None)
916 with patch.object(client, 'juju') as mock:917 with patch_juju_call(client) as mock:
917 client.destroy_model()918 client.destroy_model()
918 mock.assert_called_with(919 mock.assert_called_with(
919 'destroy-model', ('foo', '-y'),920 'destroy-model', ('foo', '-y'),
@@ -922,7 +923,7 @@
922 def test_destroy_model_gce(self):923 def test_destroy_model_gce(self):
923 env = JujuData('foo', {'type': 'gce'})924 env = JujuData('foo', {'type': 'gce'})
924 client = ModelClient(env, None, None)925 client = ModelClient(env, None, None)
925 with patch.object(client, 'juju') as mock:926 with patch_juju_call(client) as mock:
926 client.destroy_model()927 client.destroy_model()
927 mock.assert_called_with(928 mock.assert_called_with(
928 'destroy-model', ('foo', '-y'),929 'destroy-model', ('foo', '-y'),
929930
=== modified file 'jujupy/tests/test_version_client.py'
--- jujupy/tests/test_version_client.py 2017-03-10 21:15:12 +0000
+++ jujupy/tests/test_version_client.py 2017-03-23 19:29:02 +0000
@@ -573,7 +573,8 @@
573 def test_destroy_environment(self):573 def test_destroy_environment(self):
574 env = SimpleEnvironment('foo', {'type': 'ec2'})574 env = SimpleEnvironment('foo', {'type': 'ec2'})
575 client = EnvJujuClient1X(env, None, None)575 client = EnvJujuClient1X(env, None, None)
576 with patch.object(client, 'juju') as mock:576 with patch.object(
577 client, 'juju', autospec=True, return_value=(0, None)) as mock:
577 client.destroy_environment()578 client.destroy_environment()
578 mock.assert_called_with(579 mock.assert_called_with(
579 'destroy-environment', ('foo', '--force', '-y'),580 'destroy-environment', ('foo', '--force', '-y'),
@@ -582,7 +583,9 @@
582 def test_destroy_environment_no_force(self):583 def test_destroy_environment_no_force(self):
583 env = SimpleEnvironment('foo', {'type': 'ec2'})584 env = SimpleEnvironment('foo', {'type': 'ec2'})
584 client = EnvJujuClient1X(env, None, None)585 client = EnvJujuClient1X(env, None, None)
585 with patch.object(client, 'juju') as mock:586 with patch.object(
587 client, 'juju',
588 autospec=True, return_value=(0, None)) as mock:
586 client.destroy_environment(force=False)589 client.destroy_environment(force=False)
587 mock.assert_called_with(590 mock.assert_called_with(
588 'destroy-environment', ('foo', '-y'),591 'destroy-environment', ('foo', '-y'),
@@ -591,7 +594,8 @@
591 def test_destroy_environment_azure(self):594 def test_destroy_environment_azure(self):
592 env = SimpleEnvironment('foo', {'type': 'azure'})595 env = SimpleEnvironment('foo', {'type': 'azure'})
593 client = EnvJujuClient1X(env, None, None)596 client = EnvJujuClient1X(env, None, None)
594 with patch.object(client, 'juju') as mock:597 with patch.object(
598 client, 'juju', autospec=True, return_value=(0, None)) as mock:
595 client.destroy_environment(force=False)599 client.destroy_environment(force=False)
596 mock.assert_called_with(600 mock.assert_called_with(
597 'destroy-environment', ('foo', '-y'),601 'destroy-environment', ('foo', '-y'),
@@ -600,7 +604,9 @@
600 def test_destroy_environment_gce(self):604 def test_destroy_environment_gce(self):
601 env = SimpleEnvironment('foo', {'type': 'gce'})605 env = SimpleEnvironment('foo', {'type': 'gce'})
602 client = EnvJujuClient1X(env, None, None)606 client = EnvJujuClient1X(env, None, None)
603 with patch.object(client, 'juju') as mock:607 with patch.object(
608 client, 'juju',
609 autospec=True, return_value=(0, None)) as mock:
604 client.destroy_environment(force=False)610 client.destroy_environment(force=False)
605 mock.assert_called_with(611 mock.assert_called_with(
606 'destroy-environment', ('foo', '-y'),612 'destroy-environment', ('foo', '-y'),
@@ -609,7 +615,8 @@
609 def test_destroy_environment_delete_jenv(self):615 def test_destroy_environment_delete_jenv(self):
610 env = SimpleEnvironment('foo', {'type': 'ec2'})616 env = SimpleEnvironment('foo', {'type': 'ec2'})
611 client = EnvJujuClient1X(env, None, None)617 client = EnvJujuClient1X(env, None, None)
612 with patch.object(client, 'juju'):618 with patch.object(
619 client, 'juju', autospec=True, return_value=(0, None)):
613 with temp_env({}) as juju_home:620 with temp_env({}) as juju_home:
614 client.env.juju_home = juju_home621 client.env.juju_home = juju_home
615 jenv_path = get_jenv_path(juju_home, 'foo')622 jenv_path = get_jenv_path(juju_home, 'foo')
@@ -622,7 +629,8 @@
622 def test_destroy_model(self):629 def test_destroy_model(self):
623 env = SimpleEnvironment('foo', {'type': 'ec2'})630 env = SimpleEnvironment('foo', {'type': 'ec2'})
624 client = EnvJujuClient1X(env, None, None)631 client = EnvJujuClient1X(env, None, None)
625 with patch.object(client, 'juju') as mock:632 with patch.object(
633 client, 'juju', autospec=True, return_value=(0, None)) as mock:
626 client.destroy_model()634 client.destroy_model()
627 mock.assert_called_with(635 mock.assert_called_with(
628 'destroy-environment', ('foo', '-y'),636 'destroy-environment', ('foo', '-y'),
@@ -631,7 +639,9 @@
631 def test_kill_controller(self):639 def test_kill_controller(self):
632 client = EnvJujuClient1X(640 client = EnvJujuClient1X(
633 SimpleEnvironment('foo', {'type': 'ec2'}), None, None)641 SimpleEnvironment('foo', {'type': 'ec2'}), None, None)
634 with patch.object(client, 'juju') as juju_mock:642 with patch.object(
643 client, 'juju',
644 autospec=True, return_value=(0, None)) as juju_mock:
635 client.kill_controller()645 client.kill_controller()
636 juju_mock.assert_called_once_with(646 juju_mock.assert_called_once_with(
637 'destroy-environment', ('foo', '--force', '-y'), check=False,647 'destroy-environment', ('foo', '--force', '-y'), check=False,
638648
=== modified file 'jujupy/version_client.py'
--- jujupy/version_client.py 2017-03-10 19:54:30 +0000
+++ jujupy/version_client.py 2017-03-23 19:29:02 +0000
@@ -498,7 +498,7 @@
498 force_arg = ('--force',)498 force_arg = ('--force',)
499 else:499 else:
500 force_arg = ()500 force_arg = ()
501 exit_status = self.juju(501 exit_status, _ = self.juju(
502 'destroy-environment',502 'destroy-environment',
503 (self.env.environment,) + force_arg + ('-y',),503 (self.env.environment,) + force_arg + ('-y',),
504 check=False, include_e=False,504 check=False, include_e=False,
505505
=== modified file 'tests/__init__.py'
--- tests/__init__.py 2017-03-10 19:46:30 +0000
+++ tests/__init__.py 2017-03-23 19:29:02 +0000
@@ -152,6 +152,17 @@
152 os.environ[key] = org_value152 os.environ[key] = org_value
153153
154154
155@contextmanager
156def patch_juju_call(client, return_value=(0, None)):
157 """Simple patch for client.juju call.
158
159 :param return_value: A tuple to return representing the retvar and
160 CommandTime object
161 """
162 with patch.object(client, 'juju', return_value=return_value) as mock:
163 yield mock
164
165
155def assert_juju_call(test_case, mock_method, client, expected_args,166def assert_juju_call(test_case, mock_method, client, expected_args,
156 call_index=None):167 call_index=None):
157 """Check a mock's positional arguments.168 """Check a mock's positional arguments.
158169
=== modified file 'tests/test_assess_container_networking.py'
--- tests/test_assess_container_networking.py 2017-01-20 20:58:41 +0000
+++ tests/test_assess_container_networking.py 2017-03-23 19:29:02 +0000
@@ -17,6 +17,7 @@
17 LXD_MACHINE,17 LXD_MACHINE,
18 SimpleEnvironment,18 SimpleEnvironment,
19 )19 )
20from jujupy.client import CommandTime
2021
21import assess_container_networking as jcnet22import assess_container_networking as jcnet
22from tests import (23from tests import (
@@ -78,6 +79,29 @@
78 if cmd != 'bootstrap':79 if cmd != 'bootstrap':
79 self.commands.append((cmd, args))80 self.commands.append((cmd, args))
80 if cmd == 'ssh':81 if cmd == 'ssh':
82 ct = CommandTime(cmd, args)
83 if len(self._ssh_output) == 0:
84 return "", ct
85
86 try:
87 ct = CommandTime(cmd, args)
88 return self._ssh_output[self._call_number()], ct
89 except IndexError:
90 # If we ran out of values, just return the last one
91 return self._ssh_output[-1], ct
92 else:
93 return super(JujuMock, self).juju(cmd, *rargs, **kwargs)
94
95 def get_juju_output(self, cmd, *rargs, **kwargs):
96 # Almost exactly like juju() except get_juju_output doesn't return
97 # a CommandTime
98 if len(rargs) == 1:
99 args = rargs[0]
100 else:
101 args = rargs
102 if cmd != 'bootstrap':
103 self.commands.append((cmd, args))
104 if cmd == 'ssh':
81 if len(self._ssh_output) == 0:105 if len(self._ssh_output) == 0:
82 return ""106 return ""
83107
@@ -87,7 +111,7 @@
87 # If we ran out of values, just return the last one111 # If we ran out of values, just return the last one
88 return self._ssh_output[-1]112 return self._ssh_output[-1]
89 else:113 else:
90 return super(JujuMock, self).juju(cmd, *rargs, **kwargs)114 return super(JujuMock, self).get_juju_output(cmd, *rargs, **kwargs)
91115
92 def _call_number(self):116 def _call_number(self):
93 call_number = self._call_n117 call_number = self._call_n
@@ -117,7 +141,9 @@
117 patch.object(self.client, 'wait_for', lambda *args, **kw: None),141 patch.object(self.client, 'wait_for', lambda *args, **kw: None),
118 patch.object(self.client, 'wait_for_started',142 patch.object(self.client, 'wait_for_started',
119 self.juju_mock.get_status),143 self.juju_mock.get_status),
120 patch.object(self.client, 'get_juju_output', self.juju_mock.juju),144 patch.object(
145 self.client, 'get_juju_output',
146 self.juju_mock.get_juju_output),
121 ]147 ]
122148
123 for patcher in patches:149 for patcher in patches:

Subscribers

People subscribed via source and target branches