Merge lp:~sbaldassin/qakit/ust_worker into lp:qakit
- ust_worker
- Merge into trunk
Proposed by
Santiago Baldassin
Status: | Needs review |
---|---|
Proposed branch: | lp:~sbaldassin/qakit/ust_worker |
Merge into: | lp:qakit |
Diff against target: |
429 lines (+394/-0) 7 files modified
qakit/britney/__init__.py (+19/-0) qakit/britney/config.py (+67/-0) qakit/britney/domains.json (+8/-0) qakit/britney/report.py (+120/-0) qakit/britney/utils.py (+47/-0) qakit/britney/worker.py (+94/-0) start_worker.py (+39/-0) |
To merge this branch: | bzr merge lp:~sbaldassin/qakit/ust_worker |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Allan LeSage (community) | Needs Fixing | ||
Sergio Cazzolato | Needs Fixing | ||
Review via email: mp+303564@code.launchpad.net |
Commit message
Ubuntu system test worker
Description of the change
Ubuntu system test worker
To post a comment you must log in.
Revision history for this message
Sergio Cazzolato (sergio-j-cazzolato) wrote : | # |
review:
Needs Fixing
Revision history for this message
Allan LeSage (allanlesage) wrote : | # |
Admit that this is all a bit abstract for me :) but can I ask that you move your entry-level ("main") script into your dir? Or if you insist on leaving it in the root dir would you indicate to which sub-project it belongs in its name, e.g.?
review:
Needs Fixing
Unmerged revisions
- 152. By Santiago Baldassin
-
Ubuntu system test worker
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === added directory 'qakit/britney' |
2 | === added file 'qakit/britney/__init__.py' |
3 | --- qakit/britney/__init__.py 1970-01-01 00:00:00 +0000 |
4 | +++ qakit/britney/__init__.py 2016-08-22 14:09:13 +0000 |
5 | @@ -0,0 +1,19 @@ |
6 | +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- |
7 | + |
8 | +# |
9 | +# Ubuntu System Tests |
10 | +# Copyright (C) 2016 Canonical |
11 | +# |
12 | +# This program is free software: you can redistribute it and/or modify |
13 | +# it under the terms of the GNU General Public License as published by |
14 | +# the Free Software Foundation, either version 3 of the License, or |
15 | +# (at your option) any later version. |
16 | +# |
17 | +# This program is distributed in the hope that it will be useful, |
18 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
19 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
20 | +# GNU General Public License for more details. |
21 | +# |
22 | +# You should have received a copy of the GNU General Public License |
23 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
24 | +# |
25 | |
26 | === added file 'qakit/britney/config.py' |
27 | --- qakit/britney/config.py 1970-01-01 00:00:00 +0000 |
28 | +++ qakit/britney/config.py 2016-08-22 14:09:13 +0000 |
29 | @@ -0,0 +1,67 @@ |
30 | +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- |
31 | + |
32 | +# |
33 | +# Ubuntu System Tests |
34 | +# Copyright (C) 2016 Canonical |
35 | +# |
36 | +# This program is free software: you can redistribute it and/or modify |
37 | +# it under the terms of the GNU General Public License as published by |
38 | +# the Free Software Foundation, either version 3 of the License, or |
39 | +# (at your option) any later version. |
40 | +# |
41 | +# This program is distributed in the hope that it will be useful, |
42 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
43 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
44 | +# GNU General Public License for more details. |
45 | +# |
46 | +# You should have received a copy of the GNU General Public License |
47 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
48 | +# |
49 | + |
50 | +from ubuntu_system_tests.common.config import UbuntuSystemTestsConfig |
51 | + |
52 | + |
53 | +class WorkerConfig: |
54 | + |
55 | + def __init__(self): |
56 | + self._default = None |
57 | + self._practitest = None |
58 | + self._amqp = None |
59 | + self._britney = None |
60 | + self._swift = None |
61 | + |
62 | + @property |
63 | + def default(self): |
64 | + if self._default is None: |
65 | + self._default = self.__get_config('default') |
66 | + return self._default |
67 | + |
68 | + @property |
69 | + def practitest(self): |
70 | + if self._practitest is None: |
71 | + self._practitest = self.__get_config('practitest') |
72 | + return self._practitest |
73 | + |
74 | + @property |
75 | + def amqp(self): |
76 | + if self._amqp is None: |
77 | + self._amqp = self.__get_config('amqp') |
78 | + return self._amqp |
79 | + |
80 | + @property |
81 | + def britney(self): |
82 | + if self._britney is None: |
83 | + self._britney = self.__get_config('britney') |
84 | + return self._britney |
85 | + |
86 | + @property |
87 | + def swift(self): |
88 | + if self._swift is None: |
89 | + self._swift = self.__get_config('swift') |
90 | + return self._swift |
91 | + |
92 | + @staticmethod |
93 | + def __get_config(section): |
94 | + cfg = UbuntuSystemTestsConfig(section=section) |
95 | + cfg.get_config_from_file() |
96 | + return cfg |
97 | |
98 | === added file 'qakit/britney/domains.json' |
99 | --- qakit/britney/domains.json 1970-01-01 00:00:00 +0000 |
100 | +++ qakit/britney/domains.json 2016-08-22 14:09:13 +0000 |
101 | @@ -0,0 +1,8 @@ |
102 | +{ |
103 | + "autopilot": ["63805"], |
104 | + "ubuntu-ui-toolkit": ["63805"], |
105 | + "account-plugins": ["63805"], |
106 | + "signon-plugin-oauth2": ["63805"], |
107 | + "messaging-app": ["63805"], |
108 | + "goget-ubuntu-touch": ["63805"] |
109 | +} |
110 | |
111 | === added file 'qakit/britney/report.py' |
112 | --- qakit/britney/report.py 1970-01-01 00:00:00 +0000 |
113 | +++ qakit/britney/report.py 2016-08-22 14:09:13 +0000 |
114 | @@ -0,0 +1,120 @@ |
115 | +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- |
116 | + |
117 | +# |
118 | +# Ubuntu System Tests |
119 | +# Copyright (C) 2016 Canonical |
120 | +# |
121 | +# This program is free software: you can redistribute it and/or modify |
122 | +# it under the terms of the GNU General Public License as published by |
123 | +# the Free Software Foundation, either version 3 of the License, or |
124 | +# (at your option) any later version. |
125 | +# |
126 | +# This program is distributed in the hope that it will be useful, |
127 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
128 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
129 | +# GNU General Public License for more details. |
130 | +# |
131 | +# You should have received a copy of the GNU General Public License |
132 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
133 | +# |
134 | +import gzip |
135 | +import os |
136 | +from abc import abstractmethod |
137 | + |
138 | +import subprocess |
139 | +import swiftclient |
140 | + |
141 | + |
142 | +class Reporter: |
143 | + |
144 | + def __init__(self, output_dir): |
145 | + self.output_dir = output_dir |
146 | + |
147 | + @abstractmethod |
148 | + def report(self): |
149 | + pass |
150 | + |
151 | + |
152 | +class SwiftReporter(Reporter): |
153 | + |
154 | + def __init__(self, swift_config, britney_config, output_dir): |
155 | + self.container = britney_config.get('container') |
156 | + self.expected_results_files = britney_config.get( |
157 | + 'expected_results_files').split(',') |
158 | + self.result_file = britney_config.get('result_file') |
159 | + self.log_file = britney_config.get('log_file') |
160 | + self.artifacts_file = britney_config.get('artifacts_file') |
161 | + |
162 | + self.region = swift_config.get('region_name') |
163 | + self.auth_url = swift_config.get('auth_url') |
164 | + self.username = swift_config.get('username') |
165 | + self.tenant = swift_config.get('tenant') |
166 | + self.password = swift_config.get('password') |
167 | + |
168 | + super().__init__(output_dir) |
169 | + |
170 | + def report(self): |
171 | + ''' |
172 | + Reports to swift should include three files: |
173 | + 1. artifacts.tar.gz |
174 | + 2. log.gz |
175 | + 3. result.tar |
176 | + |
177 | + This method will connect to the swift backend and will |
178 | + put the files above in the container defined within the |
179 | + configuration file |
180 | + |
181 | + ''' |
182 | + |
183 | + # create it if it does not exist yet |
184 | + swift_con = swiftclient.Connection(authurl=self.auth_url, |
185 | + user=self.username, |
186 | + key=self.password) |
187 | + # self.process_output_dir() |
188 | + |
189 | + try: |
190 | + swift_con.get_container(self.container, limit=1) |
191 | + except swiftclient.exceptions.ClientException: |
192 | + swift_con.put_container(self.container, headers={ |
193 | + 'X-Container-Read': '.rlistings,.r:*'}) |
194 | + |
195 | + result_file = os.path.join(self.output_dir, self.result_file) |
196 | + with open(result_file) as contents: |
197 | + swift_con.put_object(self.container, |
198 | + self.result_file, |
199 | + contents=contents.read()) |
200 | + |
201 | + log_file = os.path.join(self.output_dir, self.log_file) |
202 | + artifacts_file = os.path.join(self.output_dir, self.artifacts_file) |
203 | + with gzip.open(log_file) as contents: |
204 | + swift_con.put_object(self.container, |
205 | + self.log_file, |
206 | + contents=contents.read(), |
207 | + content_type='text/plain; charset=UTF-8', |
208 | + headers={'Content-Encoding': 'utf-8'}) |
209 | + with gzip.open(artifacts_file) as contents: |
210 | + swift_con.put_object(self.container, |
211 | + self.artifacts_file, |
212 | + contents=contents) |
213 | + swift_con.close() |
214 | + |
215 | + def process_output_dir(self): |
216 | + '''Post-process output directory''' |
217 | + results_files = list(set(os.listdir(self.output_dir))) |
218 | + for file in self.expected_results_files: |
219 | + if file not in results_files: |
220 | + raise RuntimeError("File {} not found within the " |
221 | + "result files".format(file)) |
222 | + else: |
223 | + results_files.remove(file) |
224 | + |
225 | + subprocess.check_call(['tar', 'cf', 'result.tar'] + |
226 | + self.expected_results_files, |
227 | + cwd=self.output_dir) |
228 | + |
229 | + subprocess.check_call(['gzip', '-9', |
230 | + os.path.join(self.output_dir, 'log')]) |
231 | + results_files.remove('log') |
232 | + |
233 | + subprocess.check_call(['tar', '-czf', 'artifacts.tar.gz'] + |
234 | + results_files, cwd=self.output_dir) |
235 | |
236 | === added file 'qakit/britney/utils.py' |
237 | --- qakit/britney/utils.py 1970-01-01 00:00:00 +0000 |
238 | +++ qakit/britney/utils.py 2016-08-22 14:09:13 +0000 |
239 | @@ -0,0 +1,47 @@ |
240 | +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- |
241 | + |
242 | +# |
243 | +# Ubuntu System Tests |
244 | +# Copyright (C) 2016 Canonical |
245 | +# |
246 | +# This program is free software: you can redistribute it and/or modify |
247 | +# it under the terms of the GNU General Public License as published by |
248 | +# the Free Software Foundation, either version 3 of the License, or |
249 | +# (at your option) any later version. |
250 | +# |
251 | +# This program is distributed in the hope that it will be useful, |
252 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
253 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
254 | +# GNU General Public License for more details. |
255 | +# |
256 | +# You should have received a copy of the GNU General Public License |
257 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
258 | +# |
259 | +import os |
260 | +import json |
261 | + |
262 | +from launchpadlib.launchpad import Launchpad |
263 | + |
264 | + |
265 | +def get_filters_for_src_pkgs(src_pkgs): |
266 | + britney_dir = os.path.dirname(__file__) |
267 | + domains_file = os.path.join(str(britney_dir), 'domains.json') |
268 | + with open(domains_file) as domains: |
269 | + domains_json = json.loads(domains.read()) |
270 | + filters = [domains_json[package] for package in src_pkgs] |
271 | + |
272 | + # Flat the lists and remove duplicates |
273 | + filters_list = list(set([item for sublist in filters for item in sublist])) |
274 | + return ','.join(filter for filter in filters_list) |
275 | + |
276 | + |
277 | +def get_source_packages(message_body): |
278 | + msg_json = json.loads(message_body.decode('utf-8')) |
279 | + team_name, ppa_name = msg_json['ppa'].split('/') |
280 | + launchpad = Launchpad.login_anonymously('ci job', 'production') |
281 | + team = launchpad.people.findTeam(text=team_name)[0] |
282 | + archive = team.getPPAByName(name=ppa_name) |
283 | + src_packages = archive.getPublishedSources(status='Published') |
284 | + packages = list( |
285 | + set([package.source_package_name for package in src_packages])) |
286 | + return packages |
287 | |
288 | === added file 'qakit/britney/worker.py' |
289 | --- qakit/britney/worker.py 1970-01-01 00:00:00 +0000 |
290 | +++ qakit/britney/worker.py 2016-08-22 14:09:13 +0000 |
291 | @@ -0,0 +1,94 @@ |
292 | +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- |
293 | + |
294 | +# |
295 | +# Ubuntu System Tests |
296 | +# Copyright (C) 2016 Canonical |
297 | +# |
298 | +# This program is free software: you can redistribute it and/or modify |
299 | +# it under the terms of the GNU General Public License as published by |
300 | +# the Free Software Foundation, either version 3 of the License, or |
301 | +# (at your option) any later version. |
302 | +# |
303 | +# This program is distributed in the hope that it will be useful, |
304 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
305 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
306 | +# GNU General Public License for more details. |
307 | +# |
308 | +# You should have received a copy of the GNU General Public License |
309 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
310 | +# |
311 | + |
312 | +import logging |
313 | + |
314 | +from pika import BlockingConnection, ConnectionParameters |
315 | +from qakit.britney.config import WorkerConfig |
316 | +from qakit.britney.report import SwiftReporter |
317 | + |
318 | +from qakit.britney.utils import ( |
319 | + get_filters_for_src_pkgs, get_source_packages) |
320 | +from ubuntu_system_tests.host.command_line import ( |
321 | + parse_arguments, run_system_tests, setup_system_tests) |
322 | +from ubuntu_system_tests.host.practitest import PractitestSession |
323 | + |
324 | + |
325 | +class UstWorker: |
326 | + |
327 | + def __init__(self, config): |
328 | + self.config = config |
329 | + |
330 | + def start(self): |
331 | + logging.info('Connecting to AMQP server: {}'.format( |
332 | + self.config.amqp.get('host'))) |
333 | + connection = BlockingConnection(ConnectionParameters( |
334 | + host=self.config.amqp.get('host'))) |
335 | + channel = connection.channel() |
336 | + |
337 | + # TODO: Support multiple (queue, request) set in the configuration file |
338 | + queue = self.config.amqp.get('queue') |
339 | + logging.info('Subscribing to queue: {}'.format( |
340 | + self.config.amqp.get('queue'))) |
341 | + channel.queue_declare(queue, durable=True, auto_delete=False) |
342 | + channel.basic_consume( |
343 | + self.process_ust_request, queue=queue, no_ack=True) |
344 | + channel.start_consuming() |
345 | + |
346 | + def process_ust_request(self, ch, method, properties, body): |
347 | + '''Callback for ubuntu system test request''' |
348 | + # Parse the message body to get the source packages. Get the |
349 | + # practitests filters ids associated to the source packages |
350 | + # and get the tests based on the filters |
351 | + source_packages = get_source_packages(body) |
352 | + filters = get_filters_for_src_pkgs(source_packages) |
353 | + |
354 | + # pt = PractitestSession(self.config.practitest.get( |
355 | + # '_practitest_api_token'), |
356 | + # self.config.practitest.get( |
357 | + # '_practitest_project_id')) |
358 | + # tests = ','.join(test for test in pt.get_tests(filters)) |
359 | + |
360 | + tests = 'ubuntu_system_tests.tests.test_calculator' |
361 | + # Setup the device with ubuntu-system-tests and run the tests |
362 | + setup_args = parse_arguments(['setup', tests]) |
363 | + setup_system_tests(setup_args) |
364 | + |
365 | + run_args = parse_arguments(['run', tests]) |
366 | + run_system_tests(run_args) |
367 | + |
368 | + # publish results into swift |
369 | + reporter = SwiftReporter(self.config.swift, |
370 | + self.config.britney, |
371 | + self.config.default.get('output_dir')) |
372 | + reporter.report() |
373 | + |
374 | + logging.info('Acknowledging request %s' % body) |
375 | + logging.info(body) |
376 | + |
377 | + |
378 | +def main(): |
379 | + worker_config = WorkerConfig() |
380 | + worker = UstWorker(worker_config) |
381 | + worker.start() |
382 | + |
383 | + |
384 | +if __name__ == '__main__': |
385 | + main() |
386 | |
387 | === added file 'start_worker.py' |
388 | --- start_worker.py 1970-01-01 00:00:00 +0000 |
389 | +++ start_worker.py 2016-08-22 14:09:13 +0000 |
390 | @@ -0,0 +1,39 @@ |
391 | +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- |
392 | + |
393 | +# |
394 | +# Ubuntu System Tests |
395 | +# Copyright (C) 2016 Canonical |
396 | +# |
397 | +# This program is free software: you can redistribute it and/or modify |
398 | +# it under the terms of the GNU General Public License as published by |
399 | +# the Free Software Foundation, either version 3 of the License, or |
400 | +# (at your option) any later version. |
401 | +# |
402 | +# This program is distributed in the hope that it will be useful, |
403 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
404 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
405 | +# GNU General Public License for more details. |
406 | +# |
407 | +# You should have received a copy of the GNU General Public License |
408 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
409 | +# |
410 | + |
411 | +import sys |
412 | +import logging |
413 | +from qakit.britney import worker |
414 | + |
415 | + |
416 | +root = logging.getLogger() |
417 | +root.setLevel(logging.DEBUG) |
418 | + |
419 | +ch = logging.StreamHandler(sys.stdout) |
420 | +ch.setLevel(logging.INFO) |
421 | +formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') |
422 | +ch.setFormatter(formatter) |
423 | +root.addHandler(ch) |
424 | + |
425 | +def main(): |
426 | + return worker.main() |
427 | + |
428 | +if __name__ == '__main__': |
429 | + sys.exit(main()) |
some minor comments inline