Merge lp:~fginther/ubuntu-ci-services-itself/bsbuilder-fixes into lp:ubuntu-ci-services-itself

Proposed by Francis Ginther
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
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.

To post a comment you must log in.
335. By Francis Ginther

Merge to trunk.

336. By Francis Ginther

Merge trunk again.

Revision history for this message
Francis Ginther (fginther) wrote :

Found 2 issues in the result file:
1) mime-type isn't being set.
2) \n, not /n, when joining messages.

review: Needs Fixing
337. By Francis Ginther

Fix content_type and us '\n' instead of '/n' when joining messages.

Revision history for this message
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

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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

Subscribers

People subscribed via source and target branches