Merge lp:~fginther/ubuntu-ci-services-itself/bsbuilder-fixes into lp:ubuntu-ci-services-itself
- bsbuilder-fixes
- Merge into trunk
Status: | Rejected | ||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Rejected by: | Andy Doan | ||||||||||||||||
Proposed branch: | lp:~fginther/ubuntu-ci-services-itself/bsbuilder-fixes | ||||||||||||||||
Merge into: | lp:ubuntu-ci-services-itself | ||||||||||||||||
Diff against target: |
819 lines (+449/-97) 11 files modified
branch-source-builder/bsbuilder/resources/v1.py (+2/-2) branch-source-builder/bsbuilder/run_worker.py (+113/-30) branch-source-builder/bsbuilder/tests/test_upload.py (+89/-0) branch-source-builder/bsbuilder/tests/test_v1.py (+2/-2) branch-source-builder/cupstream2distro/packageinppamanager.py (+4/-7) branch-source-builder/setup.py (+4/-0) branch-source-builder/upload_package.py (+47/-35) branch-source-builder/watch_ppa.py (+163/-8) juju-deployer/branch-source-builder.yaml.tmpl (+1/-1) lander/bin/lander_service_wrapper.py (+12/-12) lander/bin/ticket_api.py (+12/-0) |
||||||||||||||||
To merge this branch: | bzr merge lp:~fginther/ubuntu-ci-services-itself/bsbuilder-fixes | ||||||||||||||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Francis Ginther | Needs Fixing | ||
Review via email: mp+210204@code.launchpad.net |
Commit message
Pass subtickets directly to the branch source builder, allowing it to create subticket progress updates that are then updated by the lander. Check contents of the PPAs before uploading to ensure that the build will succeed. Create a results file to pass back as a global result artifact and pass a failure status when the build fails.
Description of the change
Pass subtickets directly to the branch source builder, allowing it to create subticket progress updates that are then updated by the lander. Check contents of the PPAs before uploading to ensure that the build will succeed. Create a results file to pass back as a global result artifact and pass a failure status when the build fails.
- 335. By Francis Ginther
-
Merge to trunk.
- 336. By Francis Ginther
-
Merge trunk again.
- 337. By Francis Ginther
-
Fix content_type and us '\n' instead of '/n' when joining messages.
Andy Doan (doanac) wrote : | # |
this was broken up and merged.
Unmerged revisions
- 337. By Francis Ginther
-
Fix content_type and us '\n' instead of '/n' when joining messages.
- 336. By Francis Ginther
-
Merge trunk again.
- 335. By Francis Ginther
-
Merge to trunk.
- 334. By Francis Ginther
-
More renaming and a few renaming fixes.
- 333. By Francis Ginther
-
Remove _get_versions_
for_source_ package_ in_ppa added to cupstream2distro. - 332. By Francis Ginther
-
Some minor refactoring and renaming.
- 331. By Francis Ginther
-
Updates from first round of deployed testing.
- 330. By Francis Ginther
-
Pass subtickets directly to the branch source builder, allowing it to create subticket progress updates that are then updated by the lander. Check contents of the PPAs before uploading to ensure that the build will succeed. Create a results file to pass back as a global result artifact.
Preview Diff
1 | === modified file 'branch-source-builder/bsbuilder/resources/v1.py' |
2 | --- branch-source-builder/bsbuilder/resources/v1.py 2014-03-10 01:35:59 +0000 |
3 | +++ branch-source-builder/bsbuilder/resources/v1.py 2014-03-10 20:31:16 +0000 |
4 | @@ -31,11 +31,11 @@ |
5 | return restish_utils.json_ok(status.results) |
6 | |
7 | |
8 | -def _build_source(ticket_id, source_packages, series, ppa, archive, |
9 | +def _build_source(ticket_id, subtickets, series, ppa, archive, |
10 | progress_trigger): |
11 | params = { |
12 | 'ticket_id': ticket_id, |
13 | - 'source_packages': source_packages, |
14 | + 'subtickets': subtickets, |
15 | 'series': series, |
16 | 'ppa': ppa, |
17 | 'archive': archive, |
18 | |
19 | === modified file 'branch-source-builder/bsbuilder/run_worker.py' |
20 | --- branch-source-builder/bsbuilder/run_worker.py 2014-03-06 05:57:13 +0000 |
21 | +++ branch-source-builder/bsbuilder/run_worker.py 2014-03-10 20:31:16 +0000 |
22 | @@ -16,12 +16,18 @@ |
23 | |
24 | import json |
25 | import logging |
26 | +import os |
27 | import time |
28 | +import yaml |
29 | |
30 | import upload_package |
31 | import watch_ppa |
32 | |
33 | -from ci_utils import amqp_utils, dump_stack |
34 | +from ci_utils import ( |
35 | + amqp_utils, |
36 | + data_store, |
37 | + dump_stack, |
38 | +) |
39 | |
40 | logging.basicConfig(level=logging.INFO) |
41 | log = logging.getLogger(__name__) |
42 | @@ -29,51 +35,128 @@ |
43 | TIME_BETWEEN_CHECKS = 60 |
44 | |
45 | |
46 | +def get_auth_config(): |
47 | + DEFAULT_CONFIG_YAML = os.path.join( |
48 | + os.path.dirname(__file__), '../../unit_config') |
49 | + with open(DEFAULT_CONFIG_YAML) as f: |
50 | + config = yaml.safe_load(f) |
51 | + return config |
52 | + |
53 | + |
54 | +def create_result_file(datastore, content): |
55 | + '''Creates a result file based on the final upload and build status. |
56 | + |
57 | + The input content has the following format: |
58 | + { |
59 | + 'message': 'The high level status of the upload', |
60 | + 'subticket_status': [{ |
61 | + 'name': source_package_name, |
62 | + 'version': source_package_version, |
63 | + 'step': numeric_workflow_step, |
64 | + 'step_text': text_workflow_step, |
65 | + 'status': numeric_workflow_status, |
66 | + 'status_text': text_workflow_status, |
67 | + 'message': subticket_specific_message, |
68 | + }] |
69 | + } |
70 | + |
71 | + The return is an artifact entry for the progress message.''' |
72 | + package_build_result_filename = 'package_build.output.log' |
73 | + body = '{}\n\n'.format(content['message']) |
74 | + body += 'Uploaded source packages:\n' |
75 | + for subticket_status in content['subticket_status']: |
76 | + body += 'name: {}\n'.format(subticket_status['name']) |
77 | + body += 'version: {}\n'.format(subticket_status['version']) |
78 | + body += 'status: {}\n'.format(subticket_status['status_text']) |
79 | + if subticket_status.get('message'): |
80 | + body += 'message: {}\n'.format(subticket_status['message']) |
81 | + location = datastore.put_file(package_build_result_filename, str(body), |
82 | + content_type='text/plain') |
83 | + return { |
84 | + 'name': package_build_result_filename, |
85 | + 'type': 'LOGS', |
86 | + 'reference': location, |
87 | + } |
88 | + |
89 | + |
90 | def on_message(msg): |
91 | log.info('on_message: {}'.format(msg.body)) |
92 | params = json.loads(msg.body) |
93 | - sources = params['source_packages'] |
94 | + ticket_id = params['ticket_id'] |
95 | + subtickets = params['subtickets'] |
96 | series = params['series'] |
97 | ppa = params['ppa'] |
98 | archive = params['archive'] |
99 | - log.info('The PPA is: {}'.format(ppa)) |
100 | trigger = params['progress_trigger'] |
101 | |
102 | # Setup the output data to send back to the caller |
103 | - # TODO: sources_packages are just the passed in source files, this |
104 | - # can be made to be more useful. |
105 | # TODO: artifacts will be populated with artifacts from this build. |
106 | - out_data = {'source_packages': sources, |
107 | - 'ppa': ppa, |
108 | - 'artifacts': []} |
109 | + out_data = { |
110 | + 'message': 'Build in progress.', |
111 | + 'ppa': ppa, |
112 | + 'archive': archive, |
113 | + 'artifacts': [], |
114 | + } |
115 | amqp_utils.progress_update(trigger, params) |
116 | |
117 | try: |
118 | - upload_list = upload_package.upload_source_packages(ppa, sources) |
119 | + datastore = data_store.create_for_ticket(ticket_id, get_auth_config()) |
120 | + |
121 | + upload_list = upload_package.upload_source_packages(ppa, subtickets) |
122 | log.info('upload_list: {}'.format(upload_list)) |
123 | - start_time = time.time() |
124 | - while True: |
125 | - (ret, status) = watch_ppa.watch_ppa(start_time, series, ppa, |
126 | - archive, None, |
127 | - upload_list) |
128 | - progress = {} |
129 | - for key in status: |
130 | - progress[key] = str(status[key]) |
131 | - log.info('progress: {}'.format(progress)) |
132 | - out_data['status'] = progress |
133 | - amqp_utils.progress_update(trigger, out_data) |
134 | - if ret == -1: |
135 | - log.info('Going to sleep for {}'.format(TIME_BETWEEN_CHECKS)) |
136 | - time.sleep(TIME_BETWEEN_CHECKS) |
137 | + |
138 | + # Check that an upload isn't going to fail due to version |
139 | + (failed, subticket_status) = watch_ppa.check_ppa( |
140 | + series, ppa, archive, None, upload_list) |
141 | + if failed: |
142 | + # This is the end of the line, the upload is blocked. |
143 | + out_data['subticket_status'] = subticket_status |
144 | + message = 'Source package upload failed.' |
145 | + log.error(message) |
146 | + out_data['message'] = message |
147 | + out_data['artifacts'].append( |
148 | + create_result_file(datastore, out_data)) |
149 | + amqp_utils.progress_failed(trigger, out_data) |
150 | + else: |
151 | + # Set ret to 1, the upload is failed until it succeeds |
152 | + ret = 1 |
153 | + start_time = time.time() |
154 | + while True: |
155 | + (ret, subticket_status) = watch_ppa.watch_ppa( |
156 | + start_time, series, ppa, archive, None, upload_list) |
157 | + log.info('subticket_status: {}'.format(subticket_status)) |
158 | + out_data['subticket_status'] = subticket_status |
159 | + if ret == -1: |
160 | + # -1 indicates that the task is not finished |
161 | + # Send regular progress back to the lander |
162 | + amqp_utils.progress_update(trigger, out_data) |
163 | + log.info('Going to sleep for ' |
164 | + '{}'.format(TIME_BETWEEN_CHECKS)) |
165 | + time.sleep(TIME_BETWEEN_CHECKS) |
166 | + else: |
167 | + log.info('All done') |
168 | + break |
169 | + |
170 | + if ret == 1: |
171 | + # '1' indicates a failure occurred during the upload/build |
172 | + message = 'A package failed to upload or build.' |
173 | + log.error(message) |
174 | + out_data['message'] = message |
175 | + out_data['artifacts'].append( |
176 | + create_result_file(datastore, out_data)) |
177 | + amqp_utils.progress_failed(trigger, out_data) |
178 | else: |
179 | - log.info('All done') |
180 | - break |
181 | - |
182 | - amqp_utils.progress_completed(trigger, out_data) |
183 | + # '0' indicates the build was successful |
184 | + message = 'The package build was successful.' |
185 | + log.error(message) |
186 | + out_data['message'] = message |
187 | + out_data['artifacts'].append( |
188 | + create_result_file(datastore, out_data)) |
189 | + amqp_utils.progress_completed(trigger, out_data) |
190 | except (KeyboardInterrupt, Exception) as e: |
191 | - error_msg = 'Exception: {}'.format(e) |
192 | - log.error(error_msg) |
193 | - out_data['error_message'] = error_msg |
194 | + message = 'Exception: {}'.format(e) |
195 | + log.error(message) |
196 | + out_data['message'] = message |
197 | amqp_utils.progress_failed(trigger, out_data) |
198 | if isinstance(e, KeyboardInterrupt): |
199 | raise # re-raise so amqp_utils.process_queue can exit |
200 | |
201 | === added file 'branch-source-builder/bsbuilder/tests/test_upload.py' |
202 | --- branch-source-builder/bsbuilder/tests/test_upload.py 1970-01-01 00:00:00 +0000 |
203 | +++ branch-source-builder/bsbuilder/tests/test_upload.py 2014-03-10 20:31:16 +0000 |
204 | @@ -0,0 +1,89 @@ |
205 | +# Ubuntu CI Engine |
206 | +# Copyright 2013 Canonical Ltd. |
207 | + |
208 | +# This program is free software: you can redistribute it and/or modify it |
209 | +# under the terms of the GNU Affero General Public License version 3, as |
210 | +# published by the Free Software Foundation. |
211 | + |
212 | +# This program is distributed in the hope that it will be useful, but |
213 | +# WITHOUT ANY WARRANTY; without even the implied warranties of |
214 | +# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
215 | +# PURPOSE. See the GNU Affero General Public License for more details. |
216 | + |
217 | +# You should have received a copy of the GNU Affero General Public License |
218 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
219 | + |
220 | +import mock |
221 | +import unittest |
222 | + |
223 | +import upload_package |
224 | + |
225 | + |
226 | +class TestUploadPackage(unittest.TestCase): |
227 | + '''Tests for the upload_package module.''' |
228 | + |
229 | + subticket_list = [{ |
230 | + 'status': 'New', |
231 | + 'current_workflow_step': 'New', |
232 | + 'artifact': [{ |
233 | + 'id': 6, |
234 | + 'type': 'SPU', |
235 | + 'name': 'package_0.1_source.changes', |
236 | + 'reference': 'https://swift.url/package_0.1_source.changes', |
237 | + 'resource_uri': '/api/v1/fullsubticketartifact/6/' |
238 | + }, { |
239 | + 'id': 5, |
240 | + 'type': 'SPU', |
241 | + 'name': 'package_0.1.tar.gz', |
242 | + 'reference': 'https://swift.url/package_0.1.tar.gz', |
243 | + 'resource_uri': '/api/v1/fullsubticketartifact/5/' |
244 | + }, { |
245 | + 'id': 4, |
246 | + 'type': 'SPU', |
247 | + 'name': 'package_0.1.dsc', |
248 | + 'reference': 'https://swift.url/package_0.1.dsc', |
249 | + 'resource_uri': '/api/v1/fullsubticketartifact/4/' |
250 | + }], |
251 | + 'assignee': 'user_id', |
252 | + 'source_package_upload': { |
253 | + 'version': '0.1', |
254 | + 'sourcepackage': { |
255 | + 'id': 1, |
256 | + 'name': 'package', |
257 | + 'resource_uri': '/api/v1/sourcepackage/1/' |
258 | + }, |
259 | + 'id': 2, |
260 | + 'resource_uri': '/api/v1/spu/2/' |
261 | + }, |
262 | + 'id': 2, |
263 | + 'resource_uri': '/api/v1/fullsubticket/2/' |
264 | + }] |
265 | + |
266 | + def setUp(self): |
267 | + super(TestUploadPackage, self).setUp() |
268 | + |
269 | + def _get_context_manager_mock(self): |
270 | + file_mock = mock.Mock() |
271 | + context_manager_mock = mock.Mock() |
272 | + context_manager_mock.__enter__ = mock.Mock() |
273 | + context_manager_mock.__enter__.return_value = file_mock |
274 | + context_manager_mock.__exit__ = mock.Mock() |
275 | + return context_manager_mock |
276 | + |
277 | + @mock.patch('upload_package.create_temp_dir') |
278 | + @mock.patch('__builtin__.open') |
279 | + def test_get_source_files(self, open_mock, temp_dir_mock): |
280 | + temp_dir_mock.return_value = 'mock_location' |
281 | + open_mock.return_value = self._get_context_manager_mock() |
282 | + file_list = [] |
283 | + for item in self.subticket_list[0]['artifact']: |
284 | + file_list.append(item['name']) |
285 | + |
286 | + result = upload_package.get_source_files(self.subticket_list) |
287 | + self.assertEqual(1, len(result)) |
288 | + spu = self.subticket_list[0]['source_package_upload'] |
289 | + self.assertEqual(spu['sourcepackage']['name'], result[0]['name']) |
290 | + self.assertEqual(spu['version'], result[0]['version']) |
291 | + self.assertEqual('/api/v1/fullsubticket/2/', result[0]['resource']) |
292 | + self.assertEqual('mock_location', result[0]['location']) |
293 | + self.assertItemsEqual(file_list, result[0]['files']) |
294 | |
295 | === modified file 'branch-source-builder/bsbuilder/tests/test_v1.py' |
296 | --- branch-source-builder/bsbuilder/tests/test_v1.py 2014-03-10 01:35:59 +0000 |
297 | +++ branch-source-builder/bsbuilder/tests/test_v1.py 2014-03-10 20:31:16 +0000 |
298 | @@ -55,7 +55,7 @@ |
299 | get_config.return_value = None |
300 | params = { |
301 | 'ticket_id': 1, |
302 | - 'source_packages': 'foo', |
303 | + 'subtickets': 'foo', |
304 | 'series': 'foo', |
305 | 'ppa': 'foo', |
306 | 'archive': 'foo', |
307 | @@ -74,7 +74,7 @@ |
308 | send.return_value = None |
309 | params = { |
310 | 'ticket_id': 1, |
311 | - 'source_packages': 'foo', |
312 | + 'subtickets': 'foo', |
313 | 'series': 'foo', |
314 | 'ppa': 'foo', |
315 | 'archive': 'foo', |
316 | |
317 | === modified file 'branch-source-builder/cupstream2distro/packageinppamanager.py' |
318 | --- branch-source-builder/cupstream2distro/packageinppamanager.py 2014-02-19 16:34:46 +0000 |
319 | +++ branch-source-builder/cupstream2distro/packageinppamanager.py 2014-03-10 20:31:16 +0000 |
320 | @@ -49,12 +49,6 @@ |
321 | rev = _get_current_rev_from_config(substract[0]) |
322 | branch = _get_parent_branch(substract[0]) |
323 | result.add((substract[0], version, rev, branch)) |
324 | - foo = set() |
325 | - foo.add(('autopilot', '1.3.1+13.10.20131003.1-0ubuntu2~fginther.1', |
326 | - '100', 'lp:autopilot')) |
327 | - foo.add(('autopilot', '1.3.1+13.10.20131003.1-0ubuntu2~fginther.2', |
328 | - '105', 'lp:autopilot')) |
329 | - return foo |
330 | return result |
331 | |
332 | |
333 | @@ -74,7 +68,9 @@ |
334 | return result |
335 | |
336 | |
337 | -def update_all_packages_status(packages_not_in_ppa, packages_building, packages_failed, particular_arch=None): |
338 | +def update_all_packages_status(packages_not_in_ppa, packages_building, |
339 | + packages_failed, packages_complete, |
340 | + particular_arch=None): |
341 | '''Update all packages status, checking in the ppa''' |
342 | |
343 | for current_package in (packages_not_in_ppa.union(packages_building)): |
344 | @@ -93,6 +89,7 @@ |
345 | elif package_status == PackageInPPA.PUBLISHED: |
346 | _ensure_removed_from_set(packages_building, current_package) # in case we missed the "build" step |
347 | _ensure_removed_from_set(packages_not_in_ppa, current_package) # in case we missed the "wait" step |
348 | + packages_complete.add(current_package) |
349 | |
350 | |
351 | def _get_current_packaging_version_from_config(source_package_name): |
352 | |
353 | === modified file 'branch-source-builder/setup.py' |
354 | --- branch-source-builder/setup.py 2014-02-28 09:25:47 +0000 |
355 | +++ branch-source-builder/setup.py 2014-03-10 20:31:16 +0000 |
356 | @@ -25,6 +25,10 @@ |
357 | requires = [ |
358 | 'WebTest==2.0.10', |
359 | 'amqplib==1.0.0', |
360 | + 'chardet>=2.0.1', |
361 | + 'dput>=1.6', |
362 | + 'launchpadlib==1.10.2', |
363 | + 'lazr.enum>=1.1.2', |
364 | 'mock==1.0.1', |
365 | 'restish==0.12.1', |
366 | ] |
367 | |
368 | === modified file 'branch-source-builder/upload_package.py' |
369 | --- branch-source-builder/upload_package.py 2014-02-19 16:34:46 +0000 |
370 | +++ branch-source-builder/upload_package.py 2014-03-10 20:31:16 +0000 |
371 | @@ -64,18 +64,28 @@ |
372 | raise EnvironmentError('Failed to open url [{}]: {}'.format(url, e)) |
373 | |
374 | |
375 | -def get_source_files(source_files): |
376 | - location = create_temp_dir() |
377 | - local_files = [] |
378 | - for source in source_files: |
379 | - # Co-locate all the files into a temp directory |
380 | - file_name = os.path.basename(source) |
381 | - file_path = os.path.join(location, file_name) |
382 | - logging.info('Retrieving source file: {}'.format(file_name)) |
383 | - with open(file_path, 'w') as out_file: |
384 | - out_file.write(get_url_contents(source)) |
385 | - local_files.append(file_name) |
386 | - return (location, local_files) |
387 | +def get_source_files(subtickets): |
388 | + '''Retrieves the source package files and creates initial upload_list.''' |
389 | + upload_list = [] |
390 | + for subticket in subtickets: |
391 | + package = {} |
392 | + spu = subticket['source_package_upload'] |
393 | + package['id'] = spu['id'] |
394 | + package['name'] = spu['sourcepackage']['name'] |
395 | + package['version'] = spu['version'] |
396 | + package['resource'] = subticket['resource_uri'] |
397 | + package['location'] = create_temp_dir() |
398 | + package['files'] = [] |
399 | + for artifact in subticket['artifact']: |
400 | + source = artifact['reference'] |
401 | + file_name = os.path.basename(source) |
402 | + file_path = os.path.join(package['location'], file_name) |
403 | + logging.info('Retrieving source file: {}'.format(file_name)) |
404 | + with open(file_path, 'w') as out_file: |
405 | + out_file.write(get_url_contents(source)) |
406 | + package['files'].append(file_name) |
407 | + upload_list.append(package) |
408 | + return upload_list |
409 | |
410 | |
411 | def parse_dsc_file(dsc_file): |
412 | @@ -87,41 +97,43 @@ |
413 | return arch_lists[0] |
414 | |
415 | |
416 | -def parse_source_files(source_directory, source_files): |
417 | - package_list = [] |
418 | - for source in source_files: |
419 | - source_name = os.path.join(source_directory, source) |
420 | - if source.endswith('changes'): |
421 | +def parse_source_files(upload_list): |
422 | + '''Perform a second pass of the upload list and parse out more info. |
423 | + |
424 | + The purpose of this is to make sure all source package files have |
425 | + been downloaded and identify the source package architecture. |
426 | + This must be done after all files in a subticket have been acquired.''' |
427 | + for package in upload_list: |
428 | + for source in package['files']: |
429 | + source_name = os.path.join(package['location'], source) |
430 | + if source.endswith('changes'): |
431 | changes = parse_changes_file(filename=source_name, |
432 | - directory=source_directory) |
433 | - package = {} |
434 | - package['name'] = changes.get('Source') |
435 | - package['version'] = changes.get('Version') |
436 | - package['files'] = [] |
437 | - package['files'].append(changes.get_changes_file()) |
438 | + directory=package['location']) |
439 | for package_file in changes.get_files(): |
440 | if os.path.exists(package_file): |
441 | if package_file.endswith('.dsc'): |
442 | package['architecture'] = parse_dsc_file( |
443 | package_file) |
444 | - package['files'].append(package_file) |
445 | else: |
446 | raise EnvironmentError( |
447 | 'Not found: {}'.format(package_file)) |
448 | - package_list.append(package) |
449 | - return package_list |
450 | - |
451 | - |
452 | -def upload_source_packages(ppa, upload_files): |
453 | + |
454 | + |
455 | +def upload_source_packages(ppa, subtickets): |
456 | '''Attempts source file upload into the PPA.''' |
457 | logging.info('Upload to the ppa: {}'.format(ppa)) |
458 | - (source_directory, source_files) = get_source_files(upload_files) |
459 | - source_packages = parse_source_files(source_directory, source_files) |
460 | - for package in source_packages: |
461 | + upload_list = get_source_files(subtickets) |
462 | + parse_source_files(upload_list) |
463 | + for package in upload_list: |
464 | packagemanager.upload_package(package['name'], package['version'], |
465 | - ppa, source_directory) |
466 | - # TODO Return the data set of packages uploaded |
467 | - return source_packages |
468 | + ppa, package['location']) |
469 | + |
470 | + directory = package['location'] |
471 | + if os.path.exists(directory) and os.path.isdir(directory): |
472 | + logging.info('Removing temp directory: {}'.format(directory)) |
473 | + shutil.rmtree(directory) |
474 | + |
475 | + return upload_list |
476 | |
477 | |
478 | def main(): |
479 | |
480 | === modified file 'branch-source-builder/watch_ppa.py' |
481 | --- branch-source-builder/watch_ppa.py 2014-02-28 09:25:47 +0000 |
482 | +++ branch-source-builder/watch_ppa.py 2014-03-10 20:31:16 +0000 |
483 | @@ -22,7 +22,9 @@ |
484 | import json |
485 | import logging |
486 | import os |
487 | +import subprocess |
488 | import sys |
489 | +import textwrap |
490 | import time |
491 | |
492 | from cupstream2distro import (launchpadmanager, packageinppamanager) |
493 | @@ -33,6 +35,27 @@ |
494 | |
495 | sys.path.append(os.path.join(os.path.dirname(__file__), '../ci-utils')) |
496 | from ci_utils import dump_stack |
497 | +from ci_utils.ticket_states import ( |
498 | + SubTicketWorkflowStep, |
499 | + SubTicketWorkflowStepStatus |
500 | +) |
501 | + |
502 | +EXISTING_SOURCE_MESSAGE = textwrap.dedent('''\ |
503 | + ERROR: A source package with an equal or higher version |
504 | + of {} exists in {}. |
505 | + The upload of {} with version {} is failed. |
506 | + Source package uploads must have a higher version.''') |
507 | +FAILED_SOURCE_MESSAGE = textwrap.dedent('''\ |
508 | + The source package build has failed in {}. |
509 | + Please review the build logs for the failed package(s).''') |
510 | +MISSING_SOURCE_MESSAGE = textwrap.dedent('''\ |
511 | + The source package for {} with version {} |
512 | + failed to upload to {}. |
513 | + Please check that the source package is signed with a signature of |
514 | + a launchpad user with permission to upload to the PPA. |
515 | + Also check for a rejection message sent to the package signer.''') |
516 | +PASSED_SOURCE_MESSAGE = textwrap.dedent('''\ |
517 | + The source packages have been built in {}.''') |
518 | |
519 | |
520 | def parse_arguments(): |
521 | @@ -72,6 +95,117 @@ |
522 | return launchpadmanager.get_ppa(ppa) |
523 | |
524 | |
525 | +def create_package_status(package, step, status, message=None): |
526 | + return { |
527 | + 'name': package.source_name, |
528 | + 'version': package.version, |
529 | + 'step': step.value, |
530 | + 'step_text': step.title, |
531 | + 'status': status.value, |
532 | + 'status_text': status.title, |
533 | + 'message': message, |
534 | + } |
535 | + |
536 | + |
537 | +def collect_subticket_status(ppa, packages_not_in_ppa, packages_building, |
538 | + packages_failed, packages_complete): |
539 | + '''Collects the sets of package build status into a subticket_status.''' |
540 | + subticket_status = [] |
541 | + for package in packages_not_in_ppa: |
542 | + subticket_status.append(create_package_status( |
543 | + package, SubTicketWorkflowStep.QUEUED, |
544 | + SubTicketWorkflowStepStatus.PKG_BUILDING_WAITING)) |
545 | + |
546 | + for package in packages_building: |
547 | + subticket_status.append(create_package_status( |
548 | + package, SubTicketWorkflowStep.PKG_BUILDING, |
549 | + SubTicketWorkflowStepStatus.PKG_BUILDING_INPROGRESS)) |
550 | + |
551 | + for package in packages_failed: |
552 | + subticket_status.append(create_package_status( |
553 | + package, SubTicketWorkflowStep.COMPLETED, |
554 | + SubTicketWorkflowStepStatus.PKG_BUILDING_FAILED, |
555 | + FAILED_SOURCE_MESSAGE.format(ppa.web_link))) |
556 | + |
557 | + for package in packages_complete: |
558 | + subticket_status.append(create_package_status( |
559 | + package, SubTicketWorkflowStep.COMPLETED, |
560 | + SubTicketWorkflowStepStatus.PKG_BUILDING_COMPLETED, |
561 | + PASSED_SOURCE_MESSAGE.format(ppa.web_link))) |
562 | + |
563 | + return subticket_status |
564 | + |
565 | + |
566 | +def get_versions_for_source_package(series, ppa, source_name): |
567 | + try: |
568 | + source = ppa.getPublishedSources(exact_match=True, |
569 | + source_name=source_name, |
570 | + distro_series=series)[0] |
571 | + return source.source_package_version |
572 | + except (KeyError, IndexError): |
573 | + return None |
574 | + |
575 | + |
576 | +def check_ppa(series, ppa, dest_ppa, arch, upload_list): |
577 | + # Prepare launchpad connection: |
578 | + lp_series = launchpadmanager.get_series(series) |
579 | + monitored_ppa = launchpadmanager.get_ppa(ppa) |
580 | + if dest_ppa: |
581 | + dest_archive = get_ppa(dest_ppa) |
582 | + else: |
583 | + dest_archive = launchpadmanager.get_ubuntu_archive() |
584 | + logging.info('Series: {}'.format(lp_series)) |
585 | + logging.info('Monitoring PPA: {}'.format(monitored_ppa)) |
586 | + logging.info('Destination Archive: {}'.format(dest_archive)) |
587 | + |
588 | + failed = False |
589 | + check_status = [] |
590 | + for source_package in upload_list: |
591 | + source = source_package['name'] |
592 | + version = source_package['version'] |
593 | + logging.info('Inspecting upload: {} - {}'.format(source, version)) |
594 | + message_list = [] |
595 | + subticket_status = { |
596 | + 'name': source, |
597 | + 'id': source_package['id'], |
598 | + 'version': version, |
599 | + } |
600 | + wf_step = SubTicketWorkflowStep.NEW |
601 | + wf_status = SubTicketWorkflowStepStatus.NEW |
602 | + check_status.append(subticket_status) |
603 | + for ppa in [monitored_ppa, dest_archive]: |
604 | + last_version = get_versions_for_source_package(lp_series, ppa, |
605 | + source) |
606 | + logging.info('Last source: {}'.format(last_version)) |
607 | + |
608 | + if last_version: |
609 | + try: |
610 | + subprocess.check_call(['dpkg', '--compare-versions', |
611 | + last_version, 'lt', version]) |
612 | + except subprocess.CalledProcessError: |
613 | + # The version in the PPA is equal or higher then the |
614 | + # source package, the build will fail |
615 | + wf_step = SubTicketWorkflowStep.COMPLETED |
616 | + wf_status = SubTicketWorkflowStepStatus.PKG_BUILDING_FAILED |
617 | + message_list.append(EXISTING_SOURCE_MESSAGE.format( |
618 | + last_version, ppa.web_link, source, version)) |
619 | + failed = True |
620 | + subticket_status['step'] = wf_step.value |
621 | + subticket_status['step_text'] = wf_step.title |
622 | + subticket_status['status'] = wf_status.value |
623 | + subticket_status['status_text'] = wf_status.title |
624 | + subticket_status['message'] = '\n'.join(message_list) |
625 | + |
626 | + return (failed, check_status) |
627 | + |
628 | + |
629 | +def find_subticket(upload_list, name, version): |
630 | + for subticket in upload_list: |
631 | + if subticket['name'] == name and subticket['version'] == version: |
632 | + return subticket |
633 | + return None |
634 | + |
635 | + |
636 | def watch_ppa(time_start, series, ppa, dest_ppa, arch, upload_list): |
637 | # Prepare launchpad connection: |
638 | lp_series = launchpadmanager.get_series(series) |
639 | @@ -82,7 +216,6 @@ |
640 | dest_archive = launchpadmanager.get_ubuntu_archive() |
641 | logging.info('Series: {}'.format(lp_series)) |
642 | logging.info('Monitoring PPA: {}'.format(monitored_ppa)) |
643 | - |
644 | logging.info('Destination Archive: {}'.format(dest_archive)) |
645 | |
646 | # Get archs available and archs to ignore |
647 | @@ -102,6 +235,7 @@ |
648 | packages_not_in_ppa = set() |
649 | packages_building = set() |
650 | packages_failed = set() |
651 | + packages_complete = set() |
652 | for source_package in upload_list: |
653 | source = source_package['name'] |
654 | version = source_package['version'] |
655 | @@ -119,8 +253,6 @@ |
656 | # to eventually appear in the ppa. |
657 | logging.info('Packages not in PPA: {}'.format( |
658 | list_packages_info_in_str(packages_not_in_ppa))) |
659 | - logging.info('Packages building: {}'.format(packages_building)) |
660 | - logging.info('Packages failed: {}'.format(packages_failed)) |
661 | |
662 | # Check the status regularly on all packages |
663 | # TODO The following is the original check loop. This can be extracted |
664 | @@ -129,11 +261,24 @@ |
665 | list_packages_info_in_str( |
666 | packages_not_in_ppa.union(packages_building)))) |
667 | packageinppamanager.update_all_packages_status( |
668 | - packages_not_in_ppa, packages_building, packages_failed, arch) |
669 | - |
670 | - status = {'pending': packages_not_in_ppa, |
671 | - 'building': packages_building, |
672 | - 'failed': packages_failed} |
673 | + packages_not_in_ppa, packages_building, packages_failed, |
674 | + packages_complete, arch) |
675 | + |
676 | + status = collect_subticket_status(monitored_ppa, packages_not_in_ppa, |
677 | + packages_building, packages_failed, |
678 | + packages_complete) |
679 | + for package in status: |
680 | + subticket = find_subticket(upload_list, package['name'], |
681 | + package['version']) |
682 | + if not subticket: |
683 | + # No match, this should not happen |
684 | + return (1, status) |
685 | + |
686 | + # Populate the remaining fields necessary to update ticket status |
687 | + package['id'] = subticket['id'] |
688 | + package['resource'] = subticket['resource'] |
689 | + |
690 | + logging.info("Status: {}".format(status)) |
691 | # if we have no package building or failing and have wait for |
692 | # long enough to have some package appearing in the ppa, exit |
693 | if (packages_not_in_ppa and not packages_building and |
694 | @@ -143,6 +288,16 @@ |
695 | logging.info( |
696 | "Some source packages were never published in the ppa: " |
697 | "{}".format(list_packages_info_in_str(packages_not_in_ppa))) |
698 | + for package in status: |
699 | + # If any packages are WAITING, set them to FAILED |
700 | + if package['status'] == SubTicketWorkflowStepStatus.WAITING.value: |
701 | + package['step'] = SubTicketWorkflowStep.COMPLETED.value, |
702 | + package['status'] = \ |
703 | + SubTicketWorkflowStepStatus.PKG_BUILDING_COMPLETED.value |
704 | + # Add a message indicating the cause of the failure |
705 | + message_list.append(MISSING_SOURCE_MESSAGE.format( |
706 | + package['name'], package['version'], monitored_ppa.web_link)) |
707 | + status['message'] = '\n'.join(message_list) |
708 | return (1, status) |
709 | |
710 | # break out of status check loop if all packages have arrived in |
711 | |
712 | === modified file 'juju-deployer/branch-source-builder.yaml.tmpl' |
713 | --- juju-deployer/branch-source-builder.yaml.tmpl 2014-03-10 01:35:59 +0000 |
714 | +++ juju-deployer/branch-source-builder.yaml.tmpl 2014-03-10 20:31:16 +0000 |
715 | @@ -29,7 +29,7 @@ |
716 | branch: ${CI_BRANCH} |
717 | tarball: ${CI_PAYLOAD_URL} |
718 | unit-config: include-base64://configs/unit_config.yaml |
719 | - packages: "dput python-dput python-swiftclient" |
720 | + packages: "dput python-dput python-swiftclient lazr.enum" |
721 | install_sources: | |
722 | - ${CI_PPA} |
723 | install_keys: | |
724 | |
725 | === modified file 'lander/bin/lander_service_wrapper.py' |
726 | --- lander/bin/lander_service_wrapper.py 2014-03-10 14:57:34 +0000 |
727 | +++ lander/bin/lander_service_wrapper.py 2014-03-10 20:31:16 +0000 |
728 | @@ -48,14 +48,18 @@ |
729 | class MessageSync(object): |
730 | progress = logging.getLogger('PROGRESS_TRIGGER') |
731 | |
732 | - def __init__(self, ticket, started_cb=None, completed_cb=None): |
733 | + def __init__(self, ticket, started_cb=None, completed_cb=None, |
734 | + message_cb=None): |
735 | if not started_cb: |
736 | started_cb = lambda: True |
737 | if not completed_cb: |
738 | completed_cb = lambda x: True |
739 | + if not message_cb: |
740 | + message_cb = lambda x: True |
741 | self.ticket = ticket |
742 | self.started_cb = started_cb |
743 | self.completed_cb = completed_cb |
744 | + self.message_cb = message_cb |
745 | |
746 | def on_message(self, msg): |
747 | data = json.loads(msg.body) |
748 | @@ -68,6 +72,8 @@ |
749 | else: |
750 | self.progress.info(state) |
751 | |
752 | + self.message_cb(data) |
753 | + |
754 | if state == 'STATUS': |
755 | self.started_cb() |
756 | elif state == 'COMPLETED': |
757 | @@ -85,8 +91,8 @@ |
758 | |
759 | |
760 | def _drain_progress(amqp_config, queue, ticket, started_cb=None, |
761 | - completed_cb=None): |
762 | - sync = MessageSync(ticket, started_cb, completed_cb) |
763 | + completed_cb=None, message_cb=None): |
764 | + sync = MessageSync(ticket, started_cb, completed_cb, message_cb) |
765 | amqp_utils.process_queue(amqp_config, queue, sync.on_message, delete=True) |
766 | return sync.data |
767 | |
768 | @@ -169,17 +175,10 @@ |
769 | |
770 | queue = '%s-bsbuilder' % config['master']['progress_trigger'] |
771 | |
772 | - # Extract the source files from the master request parameters |
773 | request_parameters = ticket.get_full_ticket() |
774 | - source_files = [] |
775 | - for subticket in request_parameters.get('subticket', []): |
776 | - for artifact in subticket.get('artifact', []): |
777 | - if artifact.get('type', None) == 'SPU': |
778 | - source_files.append(artifact['reference']) |
779 | - |
780 | params = { |
781 | 'ticket_id': config['master']['request_id'], |
782 | - 'source_packages': source_files, |
783 | + 'subtickets': request_parameters['subticket'], |
784 | 'series': request_parameters.get('series', ''), |
785 | 'ppa': config['ppa_assigner']['ppa'], |
786 | 'archive': request_parameters.get('master_ppa', ''), |
787 | @@ -193,7 +192,8 @@ |
788 | logger.info('starting progress handler...') |
789 | return _drain_progress(args.amqp_config, queue, ticket, |
790 | ticket.set_pkg_inprogress, |
791 | - ticket.set_pkg_complete) |
792 | + ticket.set_pkg_complete, |
793 | + ticket.set_subticket_progress) |
794 | except: |
795 | ticket.set_pkg_complete(False) |
796 | raise |
797 | |
798 | === modified file 'lander/bin/ticket_api.py' |
799 | --- lander/bin/ticket_api.py 2014-03-07 15:31:20 +0000 |
800 | +++ lander/bin/ticket_api.py 2014-03-10 20:31:16 +0000 |
801 | @@ -65,6 +65,18 @@ |
802 | state = TicketWorkflowStepStatus.PKG_BUILDING_FAILED |
803 | self._patch_pkg_status(state) |
804 | |
805 | + def _patch_subticket_status(self, subticket_id, step, status): |
806 | + url = self._url + '/updatesubticketstatus/' + str(subticket_id) + '/' |
807 | + self._patch(url, {'status': status, 'current_workflow_step': step}) |
808 | + |
809 | + def set_subticket_progress(self, data): |
810 | + '''Processes the content of messages with subticket_status. |
811 | + |
812 | + Not all messages will have this content.''' |
813 | + for subticket in data.get('subticket_status', []): |
814 | + self._patch_subticket_status(subticket['id'], subticket['step'], |
815 | + subticket['status']) |
816 | + |
817 | def _patch_ticket_status(self, status): |
818 | step = TicketWorkflowStep.IMAGE_BUILDING.value |
819 | status = status.value |
Found 2 issues in the result file:
1) mime-type isn't being set.
2) \n, not /n, when joining messages.