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

Proposed by Christopher Lee
Status: Merged
Merged at revision: 1982
Proposed branch: lp:~veebers/juju-ci-tools/record_timing_actions
Merge into: lp:juju-ci-tools
Prerequisite: lp:~veebers/juju-ci-tools/juju_timing_reporting
Diff against target: 192 lines (+43/-17)
5 files modified
jujupy/client.py (+27/-5)
jujupy/tests/test_client.py (+6/-6)
jujupy/tests/test_version_client.py (+4/-4)
jujupy/version_client.py (+2/-1)
tests/test_deploy_stack.py (+4/-1)
To merge this branch: bzr merge lp:~veebers/juju-ci-tools/record_timing_actions
Reviewer Review Type Date Requested Status
Aaron Bentley (community) Approve
Review via email: mp+321354@code.launchpad.net

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

Commit message

Enable collection of timing data for bootstrap, kill/destroy controller and deploy.

Description of the change

Enable collection of timing data for bootstrap, kill/destroy controller and deploy.

Timing collection for bootstrap and kill/destroy controller are collected without any need for any intervention from a test author.
deploy on the other hand needs to make a change where instead of:
  client.deploy(...)
  client.wait_for_started()
Instead:
  _, deploy_complete = client.deploy(...)
  client.wait_for(deploy_complete)

The resulting timing data looks like: http://paste.ubuntu.com/24265304/

To post a comment you must log in.
Revision history for this message
Aaron Bentley (abentley) wrote :

It seems strange CommandComplete objects for synchronous operations. I think we only need that for asynchronous ones. Why do you want this?

review: Needs Information
Revision history for this message
Christopher Lee (veebers) wrote :

The CommandComplete objects for synchronous operations are so the end time gets completed. It possible we could explicitly do that without using a CommandComplete object, but as noted inline I wanted to keep return patterns consistent.

Revision history for this message
Aaron Bentley (abentley) wrote :

I think return pattern consistency makes calling code ugly, and does not have an obvious advantage. I think there are three types of methods that should have different return patterns.

1. Output queries like get_status. These should return an object representing the
   subprocess's stdout.
2. Synchronous operations like bootstrap and kill_controller. These may return an object
   representing the outcome of the operation.
   So far, only destroy_controller, backup, and run have this, and each return value is
   different, so I'm hesitant to generalize further.
3. Asynchronous operations. These should return an object that supports wait_for so the caller
   can wait until the operation completes. They cannot return an object representing the
   outcome of the operation, because it has not completed when they return.

I can't see significant benefits in making 2 and 3 consistent. We don't treat juju operations as interchangeable. bootstrap, destroy-controller, and kill-controller in particular should rarely be seen in user code, because BootstrapManager handles them.

Revision history for this message
Aaron Bentley (abentley) wrote :

Oh, I forgot to mention bootstrap's WaitAgentsStarted. Bootstrap is one of the few operations we already have reasonable timings for, and they are based on the time it takes the juju command to run. When bootstrap is complete, the controller should be ready for use.

1966. By Christopher Lee

Don't actually return non-needed CommandComplete objects.

Revision history for this message
Aaron Bentley (abentley) wrote :

Thank you for making synchronous operations return simply their retval.

Why do you want deploy to return (0, CommandComplete)? It will always be zero. If the exit status were not zero, subprocess.CalledProcessError would be raised. The same is true of all async commands.

Revision history for this message
Aaron Bentley (abentley) wrote :

Oh, and do you want to do something with CommandTime on exception? Or are you happy just letting the end time never be set as an indication that the command should be ignored?

Revision history for this message
Christopher Lee (veebers) wrote :

I'm happy with leaving it, if an exception occurred it's unlikely that the command actually completed and thus that time should be ignored.
Although I am interested what your suggestion would have been for handling that exception in regards to CommandTime.

Regarding deploy returning (0, CommandComplete) I just replaced what was originally there which was to return what self.juju(...) returned. You make a good point. I can change this if we desire it, but I would do it in a separate MP.

Revision history for this message
Aaron Bentley (abentley) wrote :

I don't know what the right exception handling option is. I think leaving it as is would be fine, but I thought it was worth mentioning. I would record the end time and mark it failed, but I just know someone would accidentally combine the failed operations with the successful ones and muddy the data. Maybe there could be a different list with only failed operations. But I can't see much interest in how long operations take to fail, so deleting them might make more sense.

I concede that returning retvar is consistent with the previous version. I guess I tend to return a callee's return value when I'm writing a thin wrapper. Still, I think it would be worth changing in the future.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'jujupy/client.py'
2--- jujupy/client.py 2017-04-05 04:20:57 +0000
3+++ jujupy/client.py 2017-04-05 04:20:58 +0000
4@@ -1451,6 +1451,22 @@
5 raise VersionsNotUpdated(model_name, status)
6
7
8+class WaitAgentsStarted(BaseCondition):
9+
10+ def __init__(self, timeout=1200):
11+ super(WaitAgentsStarted, self).__init__(timeout)
12+
13+ def iter_blocking_state(self, status):
14+ states = Status.check_agents_started(status)
15+
16+ if states is not None:
17+ for state, item in states.items():
18+ yield item[0], state
19+
20+ def do_raise(self, model_name, status):
21+ raise AgentsNotStarted(model_name, status)
22+
23+
24 class CommandTime:
25 """Store timing details for a juju command."""
26
27@@ -1946,7 +1962,9 @@
28 upload_tools, config_filename, bootstrap_series, credential,
29 auto_upgrade, metadata_source, no_gui, agent_version)
30 self.update_user_name()
31- self.juju('bootstrap', args, include_e=False)
32+ retvar, ct = self.juju('bootstrap', args, include_e=False)
33+ ct.actual_completion()
34+ return retvar
35
36 @contextmanager
37 def bootstrap_async(self, upload_tools=False, bootstrap_series=None,
38@@ -1989,7 +2007,9 @@
39 retvar, ct = self.juju(
40 'kill-controller', (self.env.controller.name, '-y'),
41 include_e=False, check=check, timeout=get_teardown_timeout(self))
42- return retvar, CommandComplete(NoopCondition(), ct)
43+ # Already satisfied as this is a sync, operation.
44+ ct.actual_completion()
45+ return retvar
46
47 def destroy_controller(self, all_models=False):
48 """Destroy a controller and its models. Soft kill option.
49@@ -2005,7 +2025,9 @@
50 retvar, ct = self.juju(
51 'destroy-controller', args, include_e=False,
52 timeout=get_teardown_timeout(self))
53- return retvar, CommandComplete(NoopCondition(), ct)
54+ # Already satisfied as this is a sync, operation.
55+ ct.actual_completion()
56+ return retvar
57
58 def tear_down(self):
59 """Tear down the client as cleanly as possible.
60@@ -2016,7 +2038,7 @@
61 self.destroy_controller(all_models=True)
62 except subprocess.CalledProcessError:
63 logging.warning('tear_down destroy-controller failed')
64- retval, _ = self.kill_controller()
65+ retval = self.kill_controller()
66 message = 'tear_down kill-controller result={}'.format(retval)
67 if retval == 0:
68 logging.info(message)
69@@ -2243,7 +2265,7 @@
70 if alias is not None:
71 args.extend([alias])
72 retvar, ct = self.juju('deploy', tuple(args))
73- return CommandComplete(NoopCondition(), ct)
74+ return retvar, CommandComplete(WaitAgentsStarted(), ct)
75
76 def attach(self, service, resource):
77 args = (service, resource)
78
79=== modified file 'jujupy/tests/test_client.py'
80--- jujupy/tests/test_client.py 2017-04-05 04:20:57 +0000
81+++ jujupy/tests/test_client.py 2017-04-05 04:20:58 +0000
82@@ -661,7 +661,7 @@
83
84 def test_bootstrap_maas(self):
85 env = JujuData('maas', {'type': 'foo', 'region': 'asdf'})
86- with patch.object(ModelClient, 'juju') as mock:
87+ with patch_juju_call(ModelClient) as mock:
88 client = ModelClient(env, '2.0-zeta1', None)
89 with patch.object(client.env, 'maas', lambda: True):
90 with observable_temp_file() as config_file:
91@@ -679,7 +679,7 @@
92 # Disable space constraint with environment variable
93 os.environ['JUJU_CI_SPACELESSNESS'] = "1"
94 env = JujuData('maas', {'type': 'foo', 'region': 'asdf'})
95- with patch.object(ModelClient, 'juju') as mock:
96+ with patch_juju_call(ModelClient) as mock:
97 client = ModelClient(env, '2.0-zeta1', None)
98 with patch.object(client.env, 'maas', lambda: True):
99 with observable_temp_file() as config_file:
100@@ -695,13 +695,13 @@
101 def test_bootstrap_joyent(self):
102 env = JujuData('joyent', {
103 'type': 'joyent', 'sdc-url': 'https://foo.api.joyentcloud.com'})
104- with patch.object(ModelClient, 'juju', autospec=True) as mock:
105- client = ModelClient(env, '2.0-zeta1', None)
106+ client = ModelClient(env, '2.0-zeta1', None)
107+ with patch_juju_call(client) as mock:
108 with patch.object(client.env, 'joyent', lambda: True):
109 with observable_temp_file() as config_file:
110 client.bootstrap()
111 mock.assert_called_once_with(
112- client, 'bootstrap', (
113+ 'bootstrap', (
114 '--constraints', 'mem=2G cpu-cores=1',
115 'joyent/foo', 'joyent',
116 '--config', config_file.name,
117@@ -711,7 +711,7 @@
118 def test_bootstrap(self):
119 env = JujuData('foo', {'type': 'bar', 'region': 'baz'})
120 with observable_temp_file() as config_file:
121- with patch.object(ModelClient, 'juju') as mock:
122+ with patch_juju_call(ModelClient) as mock:
123 client = ModelClient(env, '2.0-zeta1', None)
124 client.bootstrap()
125 mock.assert_called_with(
126
127=== modified file 'jujupy/tests/test_version_client.py'
128--- jujupy/tests/test_version_client.py 2017-04-05 04:20:57 +0000
129+++ jujupy/tests/test_version_client.py 2017-04-05 04:20:58 +0000
130@@ -381,7 +381,7 @@
131 def test_bootstrap(self):
132 env = JujuData('foo', {'type': 'bar', 'region': 'baz'})
133 with observable_temp_file() as config_file:
134- with patch.object(ModelClientRC, 'juju') as mock:
135+ with patch_juju_call(ModelClientRC) as mock:
136 client = ModelClientRC(env, '2.0-zeta1', None)
137 client.bootstrap()
138 mock.assert_called_with(
139@@ -481,7 +481,7 @@
140
141 def test_bootstrap_maas(self):
142 env = SimpleEnvironment('maas')
143- with patch.object(EnvJujuClient1X, 'juju') as mock:
144+ with patch_juju_call(EnvJujuClient1X) as mock:
145 client = EnvJujuClient1X(env, None, None)
146 with patch.object(client.env, 'maas', lambda: True):
147 client.bootstrap()
148@@ -489,12 +489,12 @@
149
150 def test_bootstrap_joyent(self):
151 env = SimpleEnvironment('joyent')
152- with patch.object(EnvJujuClient1X, 'juju', autospec=True) as mock:
153+ with patch_juju_call(EnvJujuClient1X) as mock:
154 client = EnvJujuClient1X(env, None, None)
155 with patch.object(client.env, 'joyent', lambda: True):
156 client.bootstrap()
157 mock.assert_called_once_with(
158- client, 'bootstrap', ('--constraints', 'mem=2G cpu-cores=1'))
159+ 'bootstrap', ('--constraints', 'mem=2G cpu-cores=1'))
160
161 def test_bootstrap(self):
162 env = SimpleEnvironment('foo')
163
164=== modified file 'jujupy/version_client.py'
165--- jujupy/version_client.py 2017-04-05 04:20:57 +0000
166+++ jujupy/version_client.py 2017-04-05 04:20:58 +0000
167@@ -454,7 +454,8 @@
168 """Bootstrap a controller."""
169 self._check_bootstrap()
170 args = self.get_bootstrap_args(upload_tools, bootstrap_series)
171- self.juju('bootstrap', args)
172+ retvar, ct = self.juju('bootstrap', args)
173+ ct.actual_completion()
174
175 @contextmanager
176 def bootstrap_async(self, upload_tools=False):
177
178=== modified file 'tests/test_deploy_stack.py'
179--- tests/test_deploy_stack.py 2017-04-05 04:20:57 +0000
180+++ tests/test_deploy_stack.py 2017-04-05 04:20:58 +0000
181@@ -70,7 +70,10 @@
182 Machine,
183 )
184
185-from jujupy.client import CommandTime
186+from jujupy.client import (
187+ NoopCondition,
188+ CommandTime,
189+)
190 from jujupy.configuration import (
191 get_environments_path,
192 get_jenv_path,

Subscribers

People subscribed via source and target branches