Merge lp:~vila/uci-engine/britney-results into lp:uci-engine
- britney-results
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Vincent Ladeuil |
Approved revision: | 828 |
Merged at revision: | 825 |
Proposed branch: | lp:~vila/uci-engine/britney-results |
Merge into: | lp:uci-engine |
Prerequisite: | lp:~vila/uci-engine/data-store-fix |
Diff against target: |
599 lines (+370/-48) 9 files modified
britney_proxy/britney/process_requests.py (+15/-28) britney_proxy/britney/process_results.py (+153/-5) britney_proxy/britney/queues.py (+34/-0) britney_proxy/britney/tests/test_process_results.py (+94/-0) ci-utils/ci_utils/testing/fixtures.py (+4/-0) ci-utils/ci_utils/tests/test_fixtures.py (+5/-0) juju-deployer/britney-proxy.yaml.tmpl (+4/-2) test_runner/tstrun/run_worker.py (+4/-1) tests/test_britney.py (+57/-12) |
To merge this branch: | bzr merge lp:~vila/uci-engine/britney-results |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Martin Pitt (community) | Needs Fixing | ||
PS Jenkins bot (community) | continuous-integration | Approve | |
Evan (community) | Approve | ||
Review via email: mp+236909@code.launchpad.net |
Commit message
britney results processing.
Description of the change
This implement the britney result queue handling and comes with an end-to-end test that covers:
- britney request published to the queue,
- request translated and forwarded to the test runner,
- test runner processing the test request,
- britney test result acquired from the test runner and put in the britney swift container
I.e. the uci-engine part for supporting britney is done \o/
Many (small) bits are still missing and several FIXMEs introduced here already correspond to planned tasks.
The test runner results copy into the britney swift container may (ok, will ;) need adjustments but the heavy wiring work is done and further MPs will be smaller.
I'll better document how this is designed in another MP, here is a rough sketch:
- the 'britney.requests' queue is handled by the ci-airline-
- the test runner does its job and send progress/completion messages to the 'britney.results' queue
- the 'britney.results' queue is handled by the ci-airline-
PS Jenkins bot (ps-jenkins) wrote : | # |
Looks good. Comments inline, but nothing that cannot be fixed with a follow-up.
Vincent Ladeuil (vila) wrote : | # |
Replied inline, yes, follow ups are planned to address the FIXMEs.
And thanks again for the lightbulb about dead letter ;)
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:827
http://
Executed test runs:
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:828
http://
Executed test runs:
Click here to trigger a rebuild:
http://
Ubuntu CI Bot (uci-bot) wrote : | # |
The attempt to merge lp:~vila/uci-engine/britney-results into lp:uci-engine failed. Below is the output from the failed tests.
Running cm...
Updating source dependencies...
Updating source dependencies...
Updating source dependencies...
Updating source dependencies...
Updating source dependencies...
Updating source dependencies...
uploading webui-content.tgz to swift
Updating source dependencies...
Checking juju status
Private PPAs: disabled
Preparing local branch upload...
Uploading local branch, fingerprint efec3cf7de7c720
Building charm: lander
Building charm: wsgi-app
Building charm: rabbitmq-worker
Building charm: chroot-builder
Building charm: key-secret-
Building charm: webui
Building charm: system-image-server
Installing keys from bzr+ssh:
Running juju-deployer -v -c /tmp/tmpY4DSzB/
Tests running...
ci-utils.
ci-utils.
ci-utils.
ci-utils.
ci-utils.
ci-utils.
ci-utils.
ci-utils.
ci-utils.
ci-utils.
ci-utils.
ci-utils.
ci-utils.
ci-utils.
ci-utils.
ci-utils.
ci-utils.
ci-utils.
ci-utils.
Vincent Ladeuil (vila) wrote : | # |
=======
ERROR: tests.test_
-------
Traceback (most recent call last):
File "/tmp/tarmac/
num_tickets = self.get_
File "/tmp/tarmac/
response.
File "/dev/shm/
raise HTTPError(
HTTPError: 500 Server Error: INTERNAL SERVER ERROR
=======
ERROR: tests.test_
-------
Traceback (most recent call last):
File "/tmp/tarmac/
num_tickets = self.get_
File "/tmp/tarmac/
response.
File "/dev/shm/
raise HTTPError(
HTTPError: 500 Server Error: INTERNAL SERVER ERROR
=======
ERROR: tests.test_
-------
Traceback (most recent call last):
File "build/
return func(*args, **keywargs)
File "/tmp/tarmac/
num_tickets = self.get_
File "/tmp/tarmac/
response.
File "/dev/shm/
raise HTTPError(
HTTPError: 500 Server Error: INTERNAL SERVER ERROR
=======
FAIL: tests.test_
-------
Traceback (most recent call last):
File "/tmp/tarmac/
ticket_id = self.create_
File "/tmp/tarmac/
0, proc.returncode, '{} failed:
File "/usr/lib/
assertion_
File "/usr/lib/
raise self.failureExc
AssertionError: ('python', '/tmp/tarmac/
Ubuntu CI Bot (uci-bot) wrote : | # |
The attempt to merge lp:~vila/uci-engine/britney-results into lp:uci-engine failed. Below is the output from the failed tests.
Running cm...
Updating source dependencies...
Updating source dependencies...
Updating source dependencies...
Updating source dependencies...
Updating source dependencies...
Updating source dependencies...
uploading webui-content.tgz to swift
Updating source dependencies...
2014-10-03 12:33:34 INFO juju.cmd supercommand.go:37 running jujud [1.20.8.
2014-10-03 12:33:34 DEBUG juju.agent agent.go:377 read agent config, format "1.18"
2014-10-03 12:33:34 INFO juju.jujud unit.go:78 unit agent unit-ci-
2014-10-03 12:33:34 INFO juju.worker runner.go:260 start "api"
2014-10-03 12:33:34 INFO juju.state.api apiclient.go:242 dialing "wss://
2014-10-03 12:33:34 INFO juju.state.api apiclient.go:176 connection established to "wss://
2014-10-03 12:33:35 INFO juju.state.api apiclient.go:242 dialing "wss://
2014-10-03 12:33:35 INFO juju.state.api apiclient.go:176 connection established to "wss://
2014-10-03 12:33:37 INFO juju.state.api apiclient.go:242 dialing "wss://
2014-10-03 12:33:37 INFO juju.state.api apiclient.go:176 connection established to "wss://
2014-10-03 12:33:38 INFO juju.worker runner.go:260 start "upgrader"
2014-10-03 12:33:38 INFO juju.worker runner.go:260 start "logger"
2014-10-03 12:33:38 DEBUG juju.worker.logger logger.go:35 initial log config: "<root>=DEBUG"
2014-10-03 12:33:38 INFO juju.worker runner.go:260 start "uniter"
2014-10-03 12:33:38 DEBUG juju.worker.logger logger.go:60 logger setup
2014-10-03 12:33:38 INFO juju.worker runner.go:260 start "apiaddressupdater"
2014-10-03 12:33:38 INFO juju.worker runner.go:260 start "rsyslog"
2014-10-03 12:33:38 DEBUG juju.worker.rsyslog worker.go:75 starting rsyslog worker mode 1 for "unit-ci-
2014-10-03 12:33:38 DEBUG juju.worker.logger logger.go:45 reconfiguring logging from "<root>=DEBUG" to "<root>
2014-10-03 12:33:59 INFO juju-log Installing ['python-psycopg2', 'python-jinja2'] with options: ['--option=
2014-10-03 12:34:29 INFO install Reading package lists...
2014-10-03 12:34:31 INFO install Building dependency tree...
2014-10-03 12:34:31 INFO install Reading state information...
2014-10-03 12:34:32 INFO install The following extra packages will be installed:
2014-10-03 12:34:32 INFO install libpq5 python-
2014-10-03 12:34:32 INFO install Suggested packages:
2014-10-03 12:34:32 INFO install python-
2014-10-03 12:34:32 INFO install python-
2014-10-03 12:34:32 INFO install python-psycopg2-doc
2014-10-03 12:34:32 INFO install The following NEW packages will be installed:
2014-10-03 12:34:32 INFO install libpq5 python-
2014-10-03 12:34:32 INFO install ...
Martin Pitt (pitti) wrote : | # |
Thanks! This already happened (national holiday yesterday), but I still have some comments and questions.
Vincent Ladeuil (vila) wrote : | # |
Replied inline for completing the discussions on IRC.
Vincent Ladeuil (vila) wrote : | # |
Yet Another Uncommit Comment. This one was added during the review discussion :-/
Preview Diff
1 | === modified file 'britney_proxy/britney/process_requests.py' |
2 | --- britney_proxy/britney/process_requests.py 2014-09-25 14:19:29 +0000 |
3 | +++ britney_proxy/britney/process_requests.py 2014-10-03 08:44:06 +0000 |
4 | @@ -18,9 +18,11 @@ |
5 | |
6 | import json |
7 | import logging |
8 | +import sys |
9 | import uuid |
10 | |
11 | |
12 | +from britney import queues |
13 | from ci_utils import ( |
14 | amqp_utils, |
15 | dump_stack, |
16 | @@ -69,39 +71,29 @@ |
17 | series = params['series'] |
18 | architecture = params['architecture'] |
19 | package = params['package'] |
20 | + # The output queue can be overridden for a given request (for tests and |
21 | + # debug purposes). |
22 | + output_queue = params.get('output_queue', self.output_queue) |
23 | + report_queue = params.get('report_queue', None) |
24 | # Only amd64 architecture is supported for now, everything else is |
25 | # dropped on the floor as we can't send feedback to the user at this |
26 | - # point ( the data flow is: britney.requests -> test_runner -> |
27 | + # point (the data flow is: britney.requests -> test_runner -> |
28 | # britney.results). See |
29 | # https://app.asana.com/0/8312550429058/12699749556376 'reporting test |
30 | # requests errors' |
31 | if architecture == 'amd64': |
32 | params = dict(ticket_id=str(uuid.uuid1()), |
33 | - progress_trigger=self.output_queue, |
34 | + progress_trigger=output_queue, |
35 | series=series, |
36 | image_id=self.get_image_id(series), |
37 | ppa_list=[], |
38 | package_list=[package]) |
39 | + if report_queue is not None: |
40 | + params['report_queue'] = report_queue |
41 | self.test_runner_queue.publish(params) |
42 | |
43 | |
44 | -class TestRunnerQueue(object): |
45 | - """A proxy to the queue libray. |
46 | - |
47 | - This implementation targets cit_utils/amqp_utils but can be replaced for |
48 | - tests. |
49 | - |
50 | - """ |
51 | - |
52 | - def __init__(self, queue_name): |
53 | - self.queue_name = queue_name |
54 | - |
55 | - def publish(self, msg): |
56 | - amqp_utils.send(self.queue_name, json.dumps(msg), |
57 | - raise_errors=True) |
58 | - |
59 | - |
60 | -class BritneyQueue(object): |
61 | +class BritneyRequestsQueue(object): |
62 | """The queue receiving the britney test requests. |
63 | |
64 | The requests are forwarded to the request handler. |
65 | @@ -132,17 +124,12 @@ |
66 | |
67 | if __name__ == '__main__': |
68 | dump_stack.install_stack_dump_signal() |
69 | + logging.root.setLevel(logging.INFO) |
70 | logging.info('Waiting for messages. ^C to exit.') |
71 | - tr_queue = TestRunnerQueue(amqp_utils.TEST_RUNNER_QUEUE) |
72 | + tr_queue = queues.PublisherQueue(amqp_utils.TEST_RUNNER_QUEUE) |
73 | request_handler = BritneyRequestHandler(tr_queue, 'britney.results') |
74 | # Build the britney requests queue subscribing to the rabbit queue and |
75 | # proving the handler that will forward to the test runner queue. |
76 | - queue = BritneyQueue('britney.requests', request_handler) |
77 | + queue = BritneyRequestsQueue('britney.requests', request_handler) |
78 | queue.consume() |
79 | - |
80 | - |
81 | -# MISSINGTEST: Integration running a simple test (precise/amd64/libpng) under |
82 | -# ./tests/test_britney.py (will require a dedicated fixture to not run on |
83 | -# tarmac :-/ (which raises the issue of running all deployments on tarmac)) |
84 | -# 'process_results.py' only needs to implement a sink: just consume the |
85 | -# messages without touching them. |
86 | + sys.exit(0) |
87 | |
88 | === modified file 'britney_proxy/britney/process_results.py' |
89 | --- britney_proxy/britney/process_results.py 2014-09-24 14:44:55 +0000 |
90 | +++ britney_proxy/britney/process_results.py 2014-10-03 08:44:06 +0000 |
91 | @@ -15,19 +15,167 @@ |
92 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
93 | from __future__ import unicode_literals |
94 | |
95 | + |
96 | +import datetime |
97 | +import json |
98 | import logging |
99 | +import os |
100 | import sys |
101 | -import time |
102 | - |
103 | - |
104 | + |
105 | + |
106 | +from britney import queues |
107 | from ci_utils import ( |
108 | + amqp_utils, |
109 | + data_store, |
110 | dump_stack, |
111 | + unit_config, |
112 | ) |
113 | |
114 | |
115 | +class BritneyTestRunnerArtifactMapper(object): |
116 | + |
117 | + def __init__(self, release, architecture, package, time_stamp, host_name): |
118 | + if package.startswith('lib'): |
119 | + prefix = package[0:4] |
120 | + else: |
121 | + prefix = package[0] |
122 | + self.dir_name = os.path.join(release, architecture, prefix, package, |
123 | + '{}_{}'.format(time_stamp, host_name)) |
124 | + |
125 | + def get_artifact_name(self, name): |
126 | + return os.path.join(self.dir_name, name) |
127 | + |
128 | + |
129 | +# FIXME: We do store *all* britney test results in a single swift |
130 | +# container. This won't scale but we'll revisit the layout when we get a better |
131 | +# understanding of the britney needs (and numbers !) -- vila 2014-10-03 |
132 | +class BritneyDataStore(data_store.DataStore): |
133 | + |
134 | + def file_name(self, filename): |
135 | + # Allow paths to be used as object names in the container by overriding |
136 | + # the filtering in the base class. |
137 | + return filename |
138 | + |
139 | + |
140 | +class BritneyResultHandler(object): |
141 | + """Process a britney test request. |
142 | + |
143 | + This is fed by messages received on a queue or directly for tests by |
144 | + calling the ``handle()`` method. |
145 | + """ |
146 | + |
147 | + def __init__(self, britney_data_store, _tr_data_store=None, |
148 | + _time_stamp=None): |
149 | + """Britney results handler. |
150 | + |
151 | + :param britney_data_store: The data store where the test results are |
152 | + stored for britney. |
153 | + |
154 | + :param _tr_data_store: The data store containing the test results. |
155 | + This is intended to be used by tests, the normal use case is to |
156 | + retrieve the test runner data store from the request parameter in |
157 | + the received message. |
158 | + |
159 | + :param _time_stamp: The date and time the test result is |
160 | + processed. This is intended to be used by tests and default to |
161 | + utcnow(). |
162 | + |
163 | + """ |
164 | + self.britney_data_store = britney_data_store |
165 | + self.tr_data_store = _tr_data_store |
166 | + self._time_stamp = _time_stamp |
167 | + |
168 | + def handle_progress(self, params): |
169 | + # We just ignore the progress messages |
170 | + pass |
171 | + |
172 | + def copy_artifact(self, name): |
173 | + # FIXME: If swift becomes unavailable we can't acquire the test results |
174 | + # from the test runner swift container nor can we upload them to the |
175 | + # britney container. It's quite unlikely though that swift becomes |
176 | + # unavailable *after* the test runner processed the request so just |
177 | + # logging an exception should be enough. -- vila 2014-10-02 |
178 | + value = self.tr_data_store.get_file(name) |
179 | + target = self.mapper.get_artifact_name(name) |
180 | + self.britney_data_store.put_file(target, value, 'text/plain') |
181 | + |
182 | + def handle_result(self, params): |
183 | + # Copy the produced results from the test request swift container into |
184 | + # the britney swift container. |
185 | + request = params['request'] |
186 | + ticket_id = request['ticket_id'] |
187 | + series = request['series'] |
188 | + # FIXME: Needs to be supported by the test runner -- vila 2014-10-01 |
189 | + architecture = 'amd64' |
190 | + if self.tr_data_store is None: |
191 | + self.tr_data_store = data_store.create_for_ticket( |
192 | + ticket_id, unit_config.get_auth_config()) |
193 | + time_stamp = self._time_stamp |
194 | + if time_stamp is None: |
195 | + time_stamp = datetime.datetime.utcnow().strftime('%Y%m%d_%H%M%S') |
196 | + package = request['package_list'][0] |
197 | + self.mapper = BritneyTestRunnerArtifactMapper( |
198 | + series, architecture, package, time_stamp, |
199 | + # uci-engine testbed hostnames are meaningless, so we just use a |
200 | + # generic name here. |
201 | + 'uci-testbed') |
202 | + self.copy_artifact('{}.log'.format(package)) |
203 | + # FIXME: We could delete the test runner swift container at that point |
204 | + # to reduce cluttering swift -- vila 2014-10-02 |
205 | + report_queue = request.get('report_queue', None) |
206 | + if report_queue is not None: |
207 | + # Publish a message to report where the results have been stored |
208 | + queue = queues.PublisherQueue(report_queue) |
209 | + msg = dict(result_at=self.mapper.dir_name) |
210 | + queue.publish(msg) |
211 | + |
212 | + def handle(self, params): |
213 | + state = params['state'] |
214 | + if state == 'STATUS': |
215 | + self.handle_progress(params) |
216 | + elif state == 'COMPLETED': |
217 | + self.handle_result(params) |
218 | + else: |
219 | + # This indicates a programming error in the test runner producing |
220 | + # an unexpected message |
221 | + logging.error('Unable to handle {}'.format(params)) |
222 | + # FIXME: properly testing the above will require a logger object |
223 | + # for the handler that can be overriden by tests -- vila 2014-09-30 |
224 | + |
225 | + |
226 | +class BritneyResultsQueue(object): |
227 | + """The queue receiving the test runner results for the britney requests.""" |
228 | + |
229 | + def __init__(self, queue_name, request_handler): |
230 | + """Britney test results queue. |
231 | + |
232 | + :param queue_name: The queue name where the test results are received. |
233 | + |
234 | + :param request_handler: The handler that processes the results. |
235 | + """ |
236 | + self.queue_name = queue_name |
237 | + self.request_handler = request_handler |
238 | + |
239 | + def consume(self): |
240 | + conf = amqp_utils.get_config() |
241 | + # Process all messages and delete the queue when done |
242 | + amqp_utils.process_queue(conf, self.queue_name, self.handle_request, |
243 | + delete=True) |
244 | + |
245 | + def handle_request(self, msg): |
246 | + body = json.loads(msg.body) |
247 | + ret = self.request_handler.handle(body) |
248 | + msg.channel.basic_ack(msg.delivery_tag) |
249 | + return ret |
250 | + |
251 | + |
252 | if __name__ == '__main__': |
253 | dump_stack.install_stack_dump_signal() |
254 | + logging.root.setLevel(logging.INFO) |
255 | logging.info('Waiting for messages. ^C to exit.') |
256 | - while True: |
257 | - time.sleep(10) |
258 | + britney_data_store = BritneyDataStore( |
259 | + 'britney.results', unit_config.get_auth_config(), public=True) |
260 | + request_handler = BritneyResultHandler(britney_data_store) |
261 | + queue = BritneyResultsQueue('britney.results', request_handler) |
262 | + queue.consume() |
263 | sys.exit(0) |
264 | |
265 | === added file 'britney_proxy/britney/queues.py' |
266 | --- britney_proxy/britney/queues.py 1970-01-01 00:00:00 +0000 |
267 | +++ britney_proxy/britney/queues.py 2014-10-03 08:44:06 +0000 |
268 | @@ -0,0 +1,34 @@ |
269 | +# Ubuntu CI Engine |
270 | +# Copyright 2014 Canonical Ltd. |
271 | + |
272 | +# This program is free software: you can redistribute it and/or modify it |
273 | +# under the terms of the GNU Affero General Public License version 3, as |
274 | +# published by the Free Software Foundation. |
275 | + |
276 | +# This program is distributed in the hope that it will be useful, but |
277 | +# WITHOUT ANY WARRANTY; without even the implied warranties of |
278 | +# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
279 | +# PURPOSE. See the GNU Affero General Public License for more details. |
280 | + |
281 | +# You should have received a copy of the GNU Affero General Public License |
282 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
283 | + |
284 | +import json |
285 | + |
286 | + |
287 | +from ci_utils import amqp_utils |
288 | + |
289 | + |
290 | +class PublisherQueue(object): |
291 | + """A proxy to the queue library. |
292 | + |
293 | + This implementation targets ci_utils/amqp_utils but can be replaced for |
294 | + tests. |
295 | + """ |
296 | + |
297 | + def __init__(self, queue_name): |
298 | + self.queue_name = queue_name |
299 | + |
300 | + def publish(self, msg): |
301 | + amqp_utils.send(self.queue_name, json.dumps(msg), |
302 | + raise_errors=True) |
303 | |
304 | === added file 'britney_proxy/britney/tests/test_process_results.py' |
305 | --- britney_proxy/britney/tests/test_process_results.py 1970-01-01 00:00:00 +0000 |
306 | +++ britney_proxy/britney/tests/test_process_results.py 2014-10-03 08:44:06 +0000 |
307 | @@ -0,0 +1,94 @@ |
308 | +# Ubuntu CI Engine |
309 | +# Copyright 2014 Canonical Ltd. |
310 | + |
311 | +# This program is free software: you can redistribute it and/or modify it |
312 | +# under the terms of the GNU Affero General Public License version 3, as |
313 | +# published by the Free Software Foundation. |
314 | + |
315 | +# This program is distributed in the hope that it will be useful, but |
316 | +# WITHOUT ANY WARRANTY; without even the implied warranties of |
317 | +# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
318 | +# PURPOSE. See the GNU Affero General Public License for more details. |
319 | + |
320 | +# You should have received a copy of the GNU Affero General Public License |
321 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
322 | + |
323 | +import os |
324 | +import unittest |
325 | + |
326 | + |
327 | +from ucitests import fixtures as uci_fixtures |
328 | + |
329 | + |
330 | +from britney import process_results |
331 | +from ci_utils.testing import ( |
332 | + features, |
333 | + fixtures, |
334 | +) |
335 | + |
336 | + |
337 | +class TestBritneyTestRunnerArtifactMapper(unittest.TestCase): |
338 | + |
339 | + def setUp(self): |
340 | + super(TestBritneyTestRunnerArtifactMapper, self).setUp() |
341 | + # Set some defaults to make the tests less verbose |
342 | + self.release = 'utopic' |
343 | + self.architecture = 'amd64' |
344 | + self.time_stamp = '20141001_000000' |
345 | + self.host_name = 'localhost' |
346 | + |
347 | + def get_mapper(self, package): |
348 | + mapper = process_results.BritneyTestRunnerArtifactMapper( |
349 | + self.release, self.architecture, package, self.time_stamp, |
350 | + self.host_name) |
351 | + return mapper |
352 | + |
353 | + def test_package_name(self): |
354 | + mapper = self.get_mapper('hello') |
355 | + self.assertEquals( |
356 | + 'utopic/amd64/h/hello/20141001_000000_localhost/hello.log', |
357 | + mapper.get_artifact_name('hello.log')) |
358 | + |
359 | + def test_lib_package_name(self): |
360 | + mapper = self.get_mapper('libpng') |
361 | + self.assertEquals( |
362 | + 'utopic/amd64/libp/libpng/20141001_000000_localhost/libpng.log', |
363 | + mapper.get_artifact_name('libpng.log')) |
364 | + |
365 | + |
366 | +# FIXME: This works around https://app.asana.com/0/8499154990155/14496314215756 |
367 | +# 'unit_config as a fixture for tests that requires it' -- vila 2014-09-25 |
368 | +@features.requires(features.nova_creds) |
369 | +class TestBritneyRequestHandler(unittest.TestCase): |
370 | + |
371 | + def setUp(self): |
372 | + super(TestBritneyRequestHandler, self).setUp() |
373 | + uci_fixtures.set_uniq_cwd(self) |
374 | + self.tr_ds_path = os.path.join(self.uniq_dir, 'tr_data_store') |
375 | + self.tr_data_store = fixtures.FakeDataStore(self.tr_ds_path) |
376 | + self.britney_ds_path = os.path.join(self.uniq_dir, |
377 | + 'britney_data_store') |
378 | + self.britney_data_store = fixtures.FakeDataStore(self.britney_ds_path) |
379 | + self.handler = process_results.BritneyResultHandler( |
380 | + self.britney_data_store, |
381 | + _tr_data_store=self.tr_data_store, |
382 | + _time_stamp='20141001_000000') |
383 | + |
384 | + def test_status_message(self): |
385 | + params = dict(state='STATUS') |
386 | + # Since the handler does nothing, this is mostly a smoke test |
387 | + self.handler.handle(params) |
388 | + |
389 | + def test_completed_message(self): |
390 | + request = dict(ticket_id='not-used', series='utopic', |
391 | + package_list=['fake']) |
392 | + fake_content = 'fake.log content\n' |
393 | + fake_url = self.tr_data_store.put_file('fake.log', fake_content, |
394 | + 'text/plain') |
395 | + params = dict(state='COMPLETED', request=request, |
396 | + artifacts=[dict(name='fake.log', reference=fake_url, |
397 | + type='LOGS')]) |
398 | + self.handler.handle(params) |
399 | + britney_name = self.handler.mapper.get_artifact_name('fake.log') |
400 | + actual_content = self.britney_data_store.get_file(britney_name) |
401 | + self.assertEquals(fake_content, actual_content) |
402 | |
403 | === modified file 'ci-utils/ci_utils/testing/fixtures.py' |
404 | --- ci-utils/ci_utils/testing/fixtures.py 2014-07-03 14:43:54 +0000 |
405 | +++ ci-utils/ci_utils/testing/fixtures.py 2014-10-03 08:44:06 +0000 |
406 | @@ -56,6 +56,10 @@ |
407 | return sorted(os.listdir(self.container_id)) |
408 | |
409 | def put_file(self, filename, contents, content_type=None): |
410 | + if os.sep in filename: |
411 | + base_dir = os.path.join(self.container_id, |
412 | + os.path.dirname(filename)) |
413 | + os.makedirs(base_dir) |
414 | full_path = os.path.join(self.container_id, filename) |
415 | with open(full_path, 'w') as f: |
416 | f.write(contents) |
417 | |
418 | === modified file 'ci-utils/ci_utils/tests/test_fixtures.py' |
419 | --- ci-utils/ci_utils/tests/test_fixtures.py 2014-05-06 15:05:18 +0000 |
420 | +++ ci-utils/ci_utils/tests/test_fixtures.py 2014-10-03 08:44:06 +0000 |
421 | @@ -43,6 +43,11 @@ |
422 | self.assertEqual(['foo'], self.ds.list_files()) |
423 | self.assertEqual('foo content\n', self.ds.get_file('foo')) |
424 | |
425 | + def test_put_file_with_slashes(self): |
426 | + url = self.ds.put_file('foo/bar/baz', 'baz content\n') |
427 | + self.assertTrue(url.endswith(os.path.join(self.ds_path, |
428 | + 'foo/bar/baz'))) |
429 | + |
430 | def test_list_files_empty(self): |
431 | self.assertEqual([], self.ds.list_files()) |
432 | |
433 | |
434 | === modified file 'juju-deployer/britney-proxy.yaml.tmpl' |
435 | --- juju-deployer/britney-proxy.yaml.tmpl 2014-10-01 08:55:19 +0000 |
436 | +++ juju-deployer/britney-proxy.yaml.tmpl 2014-10-03 08:44:06 +0000 |
437 | @@ -10,9 +10,10 @@ |
438 | |
439 | # Until we do: https://app.asana.com/0/8312550429058/16178663570516 'minimal |
440 | # images for britney', we use the ones defined in ci_utils/testing/features |
441 | -# which requires ucitests |
442 | +# which requires ucitests, python-swiftclient and python-novaclient (and |
443 | +# indirectly mock and gnupg :-/) |
444 | |
445 | - packages: "python-uci-tests" |
446 | + packages: "python-uci-tests python-swiftclient python-novaclient python-mock python-gnupg" |
447 | unit-config: include-base64://configs/unit_config.yaml |
448 | uid: ubuntu |
449 | gid: ubuntu |
450 | @@ -26,6 +27,7 @@ |
451 | main: ./run-python ./britney_proxy/britney/process_results.py |
452 | current_code: ${CI_PAYLOAD_URL} |
453 | available_code: ${CI_PAYLOAD_URL} |
454 | + packages: "python-swiftclient" |
455 | unit-config: include-base64://configs/unit_config.yaml |
456 | uid: ubuntu |
457 | gid: ubuntu |
458 | |
459 | === modified file 'test_runner/tstrun/run_worker.py' |
460 | --- test_runner/tstrun/run_worker.py 2014-09-23 14:54:03 +0000 |
461 | +++ test_runner/tstrun/run_worker.py 2014-10-03 08:44:06 +0000 |
462 | @@ -232,7 +232,10 @@ |
463 | package_list = params['package_list'] |
464 | data_store = self._create_data_store(params['ticket_id']) |
465 | |
466 | - results = dict(artifacts=[]) |
467 | + # The request parameters are forwarded to the rabbit worker processing |
468 | + # the status/result queue to enable unidirectional communication |
469 | + # between the requester and the ultimate receiver. |
470 | + results = dict(artifacts=[], request=params) |
471 | |
472 | # The tests will succeed unless they fail ;) |
473 | notify = amqp_utils.progress_completed |
474 | |
475 | === modified file 'tests/test_britney.py' |
476 | --- tests/test_britney.py 2014-09-24 16:44:08 +0000 |
477 | +++ tests/test_britney.py 2014-10-03 08:44:06 +0000 |
478 | @@ -15,6 +15,7 @@ |
479 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
480 | |
481 | import json |
482 | +import os |
483 | import unittest |
484 | |
485 | |
486 | @@ -27,6 +28,9 @@ |
487 | ) |
488 | |
489 | |
490 | +from britney import process_results |
491 | + |
492 | + |
493 | class TestProcessRequests(deployers.DeployerTest): |
494 | |
495 | def setUp(self): |
496 | @@ -50,12 +54,12 @@ |
497 | else: |
498 | self.test_images = features.canonistack_test_images |
499 | |
500 | - def drain_queue(self, queue_name): |
501 | - self.messages = [] |
502 | + def drain_tr_queue(self, queue_name): |
503 | + messages = [] |
504 | |
505 | def on_message(msg): |
506 | body = json.loads(msg.body) |
507 | - self.messages.append(body) |
508 | + messages.append(body) |
509 | msg.channel.basic_ack(msg.delivery_tag) |
510 | if body.get('exit'): |
511 | # this will kick us out of the run-forever worker loop, relying |
512 | @@ -65,7 +69,7 @@ |
513 | conf = amqp_utils.get_config() |
514 | # Process all messages and delete the queue when done |
515 | amqp_utils.process_queue(conf, queue_name, on_message, delete=True) |
516 | - return self.messages |
517 | + return messages |
518 | |
519 | def assertStatusMsg(self, expected, msg): |
520 | self.assertEqual('STATUS', msg['state']) |
521 | @@ -76,22 +80,21 @@ |
522 | def get_image_id(self, series): |
523 | return self.test_images[series] |
524 | |
525 | - def test_process_test_request(self): |
526 | + def test_tr_process_test_request(self): |
527 | # Exercise the britney API: feed a test request on the britney requests |
528 | # queue and ensures the test runner receive a translated request and |
529 | - # process it properly. |
530 | - |
531 | - # Until we have a proper implementation for the britney results queue, |
532 | - # this will hijack the queue here. |
533 | - progress_queue_name = 'britney.results' |
534 | + # process it properly. We force a specific result queue to avoid |
535 | + # involving the britney results service. |
536 | + result_queue_name = 'britney.test_tr_process_test_request' |
537 | series = 'precise' |
538 | architecture = 'amd64' |
539 | params = dict(series=series, |
540 | architecture=architecture, |
541 | - package='libpng') |
542 | + package='libpng', |
543 | + output_queue=result_queue_name) |
544 | amqp_utils.send('britney.requests', json.dumps(params), |
545 | raise_errors=True) |
546 | - msgs = self.drain_queue(progress_queue_name) |
547 | + msgs = self.drain_tr_queue(result_queue_name) |
548 | assertions.assertLength(self, 6, msgs) |
549 | # The test runner received the translated request |
550 | tr_params = msgs[0] |
551 | @@ -111,6 +114,48 @@ |
552 | self.assertEqual('FAILED', final['result']) |
553 | self.assertEqual('COMPLETED', final['state']) |
554 | |
555 | + def drain_report_queue(self, queue_name): |
556 | + |
557 | + messages = [] |
558 | + |
559 | + def on_message(msg): |
560 | + # We expect a single message here so we just ack it and leave the |
561 | + # loop |
562 | + body = json.loads(msg.body) |
563 | + messages.append(body) |
564 | + msg.channel.basic_ack(msg.delivery_tag) |
565 | + msg.channel.basic_cancel(msg.consumer_tag) |
566 | + |
567 | + conf = amqp_utils.get_config() |
568 | + # Process all messages and delete the queue when done |
569 | + amqp_utils.process_queue(conf, queue_name, on_message, delete=True) |
570 | + return messages |
571 | + |
572 | + def test_process_test_and_result_request(self): |
573 | + # Exercise the britney API: feed a test request on the britney requests |
574 | + # queue and check that the britney result is processed. |
575 | + report_queue_name = 'test-britney.results' |
576 | + series = 'precise' |
577 | + architecture = 'amd64' |
578 | + params = dict(series=series, |
579 | + architecture=architecture, |
580 | + package='libpng', |
581 | + report_queue=report_queue_name) |
582 | + amqp_utils.send('britney.requests', json.dumps(params), |
583 | + raise_errors=True) |
584 | + msgs = self.drain_report_queue(report_queue_name) |
585 | + # The results are in the britney swift container |
586 | + assertions.assertLength(self, 1, msgs) |
587 | + # Check the swift container content |
588 | + dir_name = msgs[0]['result_at'] |
589 | + log_file_name = os.path.join(dir_name, 'libpng.log') |
590 | + # We delay the import so it won't fail when the 'unit_config' file |
591 | + # is not available |
592 | + from ci_utils import unit_config |
593 | + britney_data_store = process_results.BritneyDataStore( |
594 | + 'britney.results', unit_config.get_auth_config(), public=True) |
595 | + self.assertIn(log_file_name, britney_data_store.list_files()) |
596 | + |
597 | |
598 | if __name__ == "__main__": |
599 | unittest.main() |
PASSED: Continuous integration, rev:826 s-jenkins. ubuntu- ci:8080/ job/uci- engine- ci/1511/
http://
Executed test runs:
Click here to trigger a rebuild: s-jenkins. ubuntu- ci:8080/ job/uci- engine- ci/1511/ rebuild
http://