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
=== modified file 'branch-source-builder/bsbuilder/resources/v1.py'
--- branch-source-builder/bsbuilder/resources/v1.py 2014-03-10 01:35:59 +0000
+++ branch-source-builder/bsbuilder/resources/v1.py 2014-03-10 20:31:16 +0000
@@ -31,11 +31,11 @@
31 return restish_utils.json_ok(status.results)31 return restish_utils.json_ok(status.results)
3232
3333
34def _build_source(ticket_id, source_packages, series, ppa, archive,34def _build_source(ticket_id, subtickets, series, ppa, archive,
35 progress_trigger):35 progress_trigger):
36 params = {36 params = {
37 'ticket_id': ticket_id,37 'ticket_id': ticket_id,
38 'source_packages': source_packages,38 'subtickets': subtickets,
39 'series': series,39 'series': series,
40 'ppa': ppa,40 'ppa': ppa,
41 'archive': archive,41 'archive': archive,
4242
=== modified file 'branch-source-builder/bsbuilder/run_worker.py'
--- branch-source-builder/bsbuilder/run_worker.py 2014-03-06 05:57:13 +0000
+++ branch-source-builder/bsbuilder/run_worker.py 2014-03-10 20:31:16 +0000
@@ -16,12 +16,18 @@
1616
17import json17import json
18import logging18import logging
19import os
19import time20import time
21import yaml
2022
21import upload_package23import upload_package
22import watch_ppa24import watch_ppa
2325
24from ci_utils import amqp_utils, dump_stack26from ci_utils import (
27 amqp_utils,
28 data_store,
29 dump_stack,
30)
2531
26logging.basicConfig(level=logging.INFO)32logging.basicConfig(level=logging.INFO)
27log = logging.getLogger(__name__)33log = logging.getLogger(__name__)
@@ -29,51 +35,128 @@
29TIME_BETWEEN_CHECKS = 6035TIME_BETWEEN_CHECKS = 60
3036
3137
38def get_auth_config():
39 DEFAULT_CONFIG_YAML = os.path.join(
40 os.path.dirname(__file__), '../../unit_config')
41 with open(DEFAULT_CONFIG_YAML) as f:
42 config = yaml.safe_load(f)
43 return config
44
45
46def create_result_file(datastore, content):
47 '''Creates a result file based on the final upload and build status.
48
49 The input content has the following format:
50 {
51 'message': 'The high level status of the upload',
52 'subticket_status': [{
53 'name': source_package_name,
54 'version': source_package_version,
55 'step': numeric_workflow_step,
56 'step_text': text_workflow_step,
57 'status': numeric_workflow_status,
58 'status_text': text_workflow_status,
59 'message': subticket_specific_message,
60 }]
61 }
62
63 The return is an artifact entry for the progress message.'''
64 package_build_result_filename = 'package_build.output.log'
65 body = '{}\n\n'.format(content['message'])
66 body += 'Uploaded source packages:\n'
67 for subticket_status in content['subticket_status']:
68 body += 'name: {}\n'.format(subticket_status['name'])
69 body += 'version: {}\n'.format(subticket_status['version'])
70 body += 'status: {}\n'.format(subticket_status['status_text'])
71 if subticket_status.get('message'):
72 body += 'message: {}\n'.format(subticket_status['message'])
73 location = datastore.put_file(package_build_result_filename, str(body),
74 content_type='text/plain')
75 return {
76 'name': package_build_result_filename,
77 'type': 'LOGS',
78 'reference': location,
79 }
80
81
32def on_message(msg):82def on_message(msg):
33 log.info('on_message: {}'.format(msg.body))83 log.info('on_message: {}'.format(msg.body))
34 params = json.loads(msg.body)84 params = json.loads(msg.body)
35 sources = params['source_packages']85 ticket_id = params['ticket_id']
86 subtickets = params['subtickets']
36 series = params['series']87 series = params['series']
37 ppa = params['ppa']88 ppa = params['ppa']
38 archive = params['archive']89 archive = params['archive']
39 log.info('The PPA is: {}'.format(ppa))
40 trigger = params['progress_trigger']90 trigger = params['progress_trigger']
4191
42 # Setup the output data to send back to the caller92 # Setup the output data to send back to the caller
43 # TODO: sources_packages are just the passed in source files, this
44 # can be made to be more useful.
45 # TODO: artifacts will be populated with artifacts from this build.93 # TODO: artifacts will be populated with artifacts from this build.
46 out_data = {'source_packages': sources,94 out_data = {
47 'ppa': ppa,95 'message': 'Build in progress.',
48 'artifacts': []}96 'ppa': ppa,
97 'archive': archive,
98 'artifacts': [],
99 }
49 amqp_utils.progress_update(trigger, params)100 amqp_utils.progress_update(trigger, params)
50101
51 try:102 try:
52 upload_list = upload_package.upload_source_packages(ppa, sources)103 datastore = data_store.create_for_ticket(ticket_id, get_auth_config())
104
105 upload_list = upload_package.upload_source_packages(ppa, subtickets)
53 log.info('upload_list: {}'.format(upload_list))106 log.info('upload_list: {}'.format(upload_list))
54 start_time = time.time()107
55 while True:108 # Check that an upload isn't going to fail due to version
56 (ret, status) = watch_ppa.watch_ppa(start_time, series, ppa,109 (failed, subticket_status) = watch_ppa.check_ppa(
57 archive, None,110 series, ppa, archive, None, upload_list)
58 upload_list)111 if failed:
59 progress = {}112 # This is the end of the line, the upload is blocked.
60 for key in status:113 out_data['subticket_status'] = subticket_status
61 progress[key] = str(status[key])114 message = 'Source package upload failed.'
62 log.info('progress: {}'.format(progress))115 log.error(message)
63 out_data['status'] = progress116 out_data['message'] = message
64 amqp_utils.progress_update(trigger, out_data)117 out_data['artifacts'].append(
65 if ret == -1:118 create_result_file(datastore, out_data))
66 log.info('Going to sleep for {}'.format(TIME_BETWEEN_CHECKS))119 amqp_utils.progress_failed(trigger, out_data)
67 time.sleep(TIME_BETWEEN_CHECKS)120 else:
121 # Set ret to 1, the upload is failed until it succeeds
122 ret = 1
123 start_time = time.time()
124 while True:
125 (ret, subticket_status) = watch_ppa.watch_ppa(
126 start_time, series, ppa, archive, None, upload_list)
127 log.info('subticket_status: {}'.format(subticket_status))
128 out_data['subticket_status'] = subticket_status
129 if ret == -1:
130 # -1 indicates that the task is not finished
131 # Send regular progress back to the lander
132 amqp_utils.progress_update(trigger, out_data)
133 log.info('Going to sleep for '
134 '{}'.format(TIME_BETWEEN_CHECKS))
135 time.sleep(TIME_BETWEEN_CHECKS)
136 else:
137 log.info('All done')
138 break
139
140 if ret == 1:
141 # '1' indicates a failure occurred during the upload/build
142 message = 'A package failed to upload or build.'
143 log.error(message)
144 out_data['message'] = message
145 out_data['artifacts'].append(
146 create_result_file(datastore, out_data))
147 amqp_utils.progress_failed(trigger, out_data)
68 else:148 else:
69 log.info('All done')149 # '0' indicates the build was successful
70 break150 message = 'The package build was successful.'
71151 log.error(message)
72 amqp_utils.progress_completed(trigger, out_data)152 out_data['message'] = message
153 out_data['artifacts'].append(
154 create_result_file(datastore, out_data))
155 amqp_utils.progress_completed(trigger, out_data)
73 except (KeyboardInterrupt, Exception) as e:156 except (KeyboardInterrupt, Exception) as e:
74 error_msg = 'Exception: {}'.format(e)157 message = 'Exception: {}'.format(e)
75 log.error(error_msg)158 log.error(message)
76 out_data['error_message'] = error_msg159 out_data['message'] = message
77 amqp_utils.progress_failed(trigger, out_data)160 amqp_utils.progress_failed(trigger, out_data)
78 if isinstance(e, KeyboardInterrupt):161 if isinstance(e, KeyboardInterrupt):
79 raise # re-raise so amqp_utils.process_queue can exit162 raise # re-raise so amqp_utils.process_queue can exit
80163
=== added file 'branch-source-builder/bsbuilder/tests/test_upload.py'
--- branch-source-builder/bsbuilder/tests/test_upload.py 1970-01-01 00:00:00 +0000
+++ branch-source-builder/bsbuilder/tests/test_upload.py 2014-03-10 20:31:16 +0000
@@ -0,0 +1,89 @@
1# Ubuntu CI Engine
2# Copyright 2013 Canonical Ltd.
3
4# This program is free software: you can redistribute it and/or modify it
5# under the terms of the GNU Affero General Public License version 3, as
6# published by the Free Software Foundation.
7
8# This program is distributed in the hope that it will be useful, but
9# WITHOUT ANY WARRANTY; without even the implied warranties of
10# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11# PURPOSE. See the GNU Affero General Public License for more details.
12
13# You should have received a copy of the GNU Affero General Public License
14# along with this program. If not, see <http://www.gnu.org/licenses/>.
15
16import mock
17import unittest
18
19import upload_package
20
21
22class TestUploadPackage(unittest.TestCase):
23 '''Tests for the upload_package module.'''
24
25 subticket_list = [{
26 'status': 'New',
27 'current_workflow_step': 'New',
28 'artifact': [{
29 'id': 6,
30 'type': 'SPU',
31 'name': 'package_0.1_source.changes',
32 'reference': 'https://swift.url/package_0.1_source.changes',
33 'resource_uri': '/api/v1/fullsubticketartifact/6/'
34 }, {
35 'id': 5,
36 'type': 'SPU',
37 'name': 'package_0.1.tar.gz',
38 'reference': 'https://swift.url/package_0.1.tar.gz',
39 'resource_uri': '/api/v1/fullsubticketartifact/5/'
40 }, {
41 'id': 4,
42 'type': 'SPU',
43 'name': 'package_0.1.dsc',
44 'reference': 'https://swift.url/package_0.1.dsc',
45 'resource_uri': '/api/v1/fullsubticketartifact/4/'
46 }],
47 'assignee': 'user_id',
48 'source_package_upload': {
49 'version': '0.1',
50 'sourcepackage': {
51 'id': 1,
52 'name': 'package',
53 'resource_uri': '/api/v1/sourcepackage/1/'
54 },
55 'id': 2,
56 'resource_uri': '/api/v1/spu/2/'
57 },
58 'id': 2,
59 'resource_uri': '/api/v1/fullsubticket/2/'
60 }]
61
62 def setUp(self):
63 super(TestUploadPackage, self).setUp()
64
65 def _get_context_manager_mock(self):
66 file_mock = mock.Mock()
67 context_manager_mock = mock.Mock()
68 context_manager_mock.__enter__ = mock.Mock()
69 context_manager_mock.__enter__.return_value = file_mock
70 context_manager_mock.__exit__ = mock.Mock()
71 return context_manager_mock
72
73 @mock.patch('upload_package.create_temp_dir')
74 @mock.patch('__builtin__.open')
75 def test_get_source_files(self, open_mock, temp_dir_mock):
76 temp_dir_mock.return_value = 'mock_location'
77 open_mock.return_value = self._get_context_manager_mock()
78 file_list = []
79 for item in self.subticket_list[0]['artifact']:
80 file_list.append(item['name'])
81
82 result = upload_package.get_source_files(self.subticket_list)
83 self.assertEqual(1, len(result))
84 spu = self.subticket_list[0]['source_package_upload']
85 self.assertEqual(spu['sourcepackage']['name'], result[0]['name'])
86 self.assertEqual(spu['version'], result[0]['version'])
87 self.assertEqual('/api/v1/fullsubticket/2/', result[0]['resource'])
88 self.assertEqual('mock_location', result[0]['location'])
89 self.assertItemsEqual(file_list, result[0]['files'])
090
=== modified file 'branch-source-builder/bsbuilder/tests/test_v1.py'
--- branch-source-builder/bsbuilder/tests/test_v1.py 2014-03-10 01:35:59 +0000
+++ branch-source-builder/bsbuilder/tests/test_v1.py 2014-03-10 20:31:16 +0000
@@ -55,7 +55,7 @@
55 get_config.return_value = None55 get_config.return_value = None
56 params = {56 params = {
57 'ticket_id': 1,57 'ticket_id': 1,
58 'source_packages': 'foo',58 'subtickets': 'foo',
59 'series': 'foo',59 'series': 'foo',
60 'ppa': 'foo',60 'ppa': 'foo',
61 'archive': 'foo',61 'archive': 'foo',
@@ -74,7 +74,7 @@
74 send.return_value = None74 send.return_value = None
75 params = {75 params = {
76 'ticket_id': 1,76 'ticket_id': 1,
77 'source_packages': 'foo',77 'subtickets': 'foo',
78 'series': 'foo',78 'series': 'foo',
79 'ppa': 'foo',79 'ppa': 'foo',
80 'archive': 'foo',80 'archive': 'foo',
8181
=== modified file 'branch-source-builder/cupstream2distro/packageinppamanager.py'
--- branch-source-builder/cupstream2distro/packageinppamanager.py 2014-02-19 16:34:46 +0000
+++ branch-source-builder/cupstream2distro/packageinppamanager.py 2014-03-10 20:31:16 +0000
@@ -49,12 +49,6 @@
49 rev = _get_current_rev_from_config(substract[0])49 rev = _get_current_rev_from_config(substract[0])
50 branch = _get_parent_branch(substract[0])50 branch = _get_parent_branch(substract[0])
51 result.add((substract[0], version, rev, branch))51 result.add((substract[0], version, rev, branch))
52 foo = set()
53 foo.add(('autopilot', '1.3.1+13.10.20131003.1-0ubuntu2~fginther.1',
54 '100', 'lp:autopilot'))
55 foo.add(('autopilot', '1.3.1+13.10.20131003.1-0ubuntu2~fginther.2',
56 '105', 'lp:autopilot'))
57 return foo
58 return result52 return result
5953
6054
@@ -74,7 +68,9 @@
74 return result68 return result
7569
7670
77def update_all_packages_status(packages_not_in_ppa, packages_building, packages_failed, particular_arch=None):71def update_all_packages_status(packages_not_in_ppa, packages_building,
72 packages_failed, packages_complete,
73 particular_arch=None):
78 '''Update all packages status, checking in the ppa'''74 '''Update all packages status, checking in the ppa'''
7975
80 for current_package in (packages_not_in_ppa.union(packages_building)):76 for current_package in (packages_not_in_ppa.union(packages_building)):
@@ -93,6 +89,7 @@
93 elif package_status == PackageInPPA.PUBLISHED:89 elif package_status == PackageInPPA.PUBLISHED:
94 _ensure_removed_from_set(packages_building, current_package) # in case we missed the "build" step90 _ensure_removed_from_set(packages_building, current_package) # in case we missed the "build" step
95 _ensure_removed_from_set(packages_not_in_ppa, current_package) # in case we missed the "wait" step91 _ensure_removed_from_set(packages_not_in_ppa, current_package) # in case we missed the "wait" step
92 packages_complete.add(current_package)
9693
9794
98def _get_current_packaging_version_from_config(source_package_name):95def _get_current_packaging_version_from_config(source_package_name):
9996
=== modified file 'branch-source-builder/setup.py'
--- branch-source-builder/setup.py 2014-02-28 09:25:47 +0000
+++ branch-source-builder/setup.py 2014-03-10 20:31:16 +0000
@@ -25,6 +25,10 @@
25requires = [25requires = [
26 'WebTest==2.0.10',26 'WebTest==2.0.10',
27 'amqplib==1.0.0',27 'amqplib==1.0.0',
28 'chardet>=2.0.1',
29 'dput>=1.6',
30 'launchpadlib==1.10.2',
31 'lazr.enum>=1.1.2',
28 'mock==1.0.1',32 'mock==1.0.1',
29 'restish==0.12.1',33 'restish==0.12.1',
30]34]
3135
=== modified file 'branch-source-builder/upload_package.py'
--- branch-source-builder/upload_package.py 2014-02-19 16:34:46 +0000
+++ branch-source-builder/upload_package.py 2014-03-10 20:31:16 +0000
@@ -64,18 +64,28 @@
64 raise EnvironmentError('Failed to open url [{}]: {}'.format(url, e))64 raise EnvironmentError('Failed to open url [{}]: {}'.format(url, e))
6565
6666
67def get_source_files(source_files):67def get_source_files(subtickets):
68 location = create_temp_dir()68 '''Retrieves the source package files and creates initial upload_list.'''
69 local_files = []69 upload_list = []
70 for source in source_files:70 for subticket in subtickets:
71 # Co-locate all the files into a temp directory71 package = {}
72 file_name = os.path.basename(source)72 spu = subticket['source_package_upload']
73 file_path = os.path.join(location, file_name)73 package['id'] = spu['id']
74 logging.info('Retrieving source file: {}'.format(file_name))74 package['name'] = spu['sourcepackage']['name']
75 with open(file_path, 'w') as out_file:75 package['version'] = spu['version']
76 out_file.write(get_url_contents(source))76 package['resource'] = subticket['resource_uri']
77 local_files.append(file_name)77 package['location'] = create_temp_dir()
78 return (location, local_files)78 package['files'] = []
79 for artifact in subticket['artifact']:
80 source = artifact['reference']
81 file_name = os.path.basename(source)
82 file_path = os.path.join(package['location'], file_name)
83 logging.info('Retrieving source file: {}'.format(file_name))
84 with open(file_path, 'w') as out_file:
85 out_file.write(get_url_contents(source))
86 package['files'].append(file_name)
87 upload_list.append(package)
88 return upload_list
7989
8090
81def parse_dsc_file(dsc_file):91def parse_dsc_file(dsc_file):
@@ -87,41 +97,43 @@
87 return arch_lists[0]97 return arch_lists[0]
8898
8999
90def parse_source_files(source_directory, source_files):100def parse_source_files(upload_list):
91 package_list = []101 '''Perform a second pass of the upload list and parse out more info.
92 for source in source_files:102
93 source_name = os.path.join(source_directory, source)103 The purpose of this is to make sure all source package files have
94 if source.endswith('changes'):104 been downloaded and identify the source package architecture.
105 This must be done after all files in a subticket have been acquired.'''
106 for package in upload_list:
107 for source in package['files']:
108 source_name = os.path.join(package['location'], source)
109 if source.endswith('changes'):
95 changes = parse_changes_file(filename=source_name,110 changes = parse_changes_file(filename=source_name,
96 directory=source_directory)111 directory=package['location'])
97 package = {}
98 package['name'] = changes.get('Source')
99 package['version'] = changes.get('Version')
100 package['files'] = []
101 package['files'].append(changes.get_changes_file())
102 for package_file in changes.get_files():112 for package_file in changes.get_files():
103 if os.path.exists(package_file):113 if os.path.exists(package_file):
104 if package_file.endswith('.dsc'):114 if package_file.endswith('.dsc'):
105 package['architecture'] = parse_dsc_file(115 package['architecture'] = parse_dsc_file(
106 package_file)116 package_file)
107 package['files'].append(package_file)
108 else:117 else:
109 raise EnvironmentError(118 raise EnvironmentError(
110 'Not found: {}'.format(package_file))119 'Not found: {}'.format(package_file))
111 package_list.append(package)120
112 return package_list121
113122def upload_source_packages(ppa, subtickets):
114
115def upload_source_packages(ppa, upload_files):
116 '''Attempts source file upload into the PPA.'''123 '''Attempts source file upload into the PPA.'''
117 logging.info('Upload to the ppa: {}'.format(ppa))124 logging.info('Upload to the ppa: {}'.format(ppa))
118 (source_directory, source_files) = get_source_files(upload_files)125 upload_list = get_source_files(subtickets)
119 source_packages = parse_source_files(source_directory, source_files)126 parse_source_files(upload_list)
120 for package in source_packages:127 for package in upload_list:
121 packagemanager.upload_package(package['name'], package['version'],128 packagemanager.upload_package(package['name'], package['version'],
122 ppa, source_directory)129 ppa, package['location'])
123 # TODO Return the data set of packages uploaded130
124 return source_packages131 directory = package['location']
132 if os.path.exists(directory) and os.path.isdir(directory):
133 logging.info('Removing temp directory: {}'.format(directory))
134 shutil.rmtree(directory)
135
136 return upload_list
125137
126138
127def main():139def main():
128140
=== modified file 'branch-source-builder/watch_ppa.py'
--- branch-source-builder/watch_ppa.py 2014-02-28 09:25:47 +0000
+++ branch-source-builder/watch_ppa.py 2014-03-10 20:31:16 +0000
@@ -22,7 +22,9 @@
22import json22import json
23import logging23import logging
24import os24import os
25import subprocess
25import sys26import sys
27import textwrap
26import time28import time
2729
28from cupstream2distro import (launchpadmanager, packageinppamanager)30from cupstream2distro import (launchpadmanager, packageinppamanager)
@@ -33,6 +35,27 @@
3335
34sys.path.append(os.path.join(os.path.dirname(__file__), '../ci-utils'))36sys.path.append(os.path.join(os.path.dirname(__file__), '../ci-utils'))
35from ci_utils import dump_stack37from ci_utils import dump_stack
38from ci_utils.ticket_states import (
39 SubTicketWorkflowStep,
40 SubTicketWorkflowStepStatus
41)
42
43EXISTING_SOURCE_MESSAGE = textwrap.dedent('''\
44 ERROR: A source package with an equal or higher version
45 of {} exists in {}.
46 The upload of {} with version {} is failed.
47 Source package uploads must have a higher version.''')
48FAILED_SOURCE_MESSAGE = textwrap.dedent('''\
49 The source package build has failed in {}.
50 Please review the build logs for the failed package(s).''')
51MISSING_SOURCE_MESSAGE = textwrap.dedent('''\
52 The source package for {} with version {}
53 failed to upload to {}.
54 Please check that the source package is signed with a signature of
55 a launchpad user with permission to upload to the PPA.
56 Also check for a rejection message sent to the package signer.''')
57PASSED_SOURCE_MESSAGE = textwrap.dedent('''\
58 The source packages have been built in {}.''')
3659
3760
38def parse_arguments():61def parse_arguments():
@@ -72,6 +95,117 @@
72 return launchpadmanager.get_ppa(ppa)95 return launchpadmanager.get_ppa(ppa)
7396
7497
98def create_package_status(package, step, status, message=None):
99 return {
100 'name': package.source_name,
101 'version': package.version,
102 'step': step.value,
103 'step_text': step.title,
104 'status': status.value,
105 'status_text': status.title,
106 'message': message,
107 }
108
109
110def collect_subticket_status(ppa, packages_not_in_ppa, packages_building,
111 packages_failed, packages_complete):
112 '''Collects the sets of package build status into a subticket_status.'''
113 subticket_status = []
114 for package in packages_not_in_ppa:
115 subticket_status.append(create_package_status(
116 package, SubTicketWorkflowStep.QUEUED,
117 SubTicketWorkflowStepStatus.PKG_BUILDING_WAITING))
118
119 for package in packages_building:
120 subticket_status.append(create_package_status(
121 package, SubTicketWorkflowStep.PKG_BUILDING,
122 SubTicketWorkflowStepStatus.PKG_BUILDING_INPROGRESS))
123
124 for package in packages_failed:
125 subticket_status.append(create_package_status(
126 package, SubTicketWorkflowStep.COMPLETED,
127 SubTicketWorkflowStepStatus.PKG_BUILDING_FAILED,
128 FAILED_SOURCE_MESSAGE.format(ppa.web_link)))
129
130 for package in packages_complete:
131 subticket_status.append(create_package_status(
132 package, SubTicketWorkflowStep.COMPLETED,
133 SubTicketWorkflowStepStatus.PKG_BUILDING_COMPLETED,
134 PASSED_SOURCE_MESSAGE.format(ppa.web_link)))
135
136 return subticket_status
137
138
139def get_versions_for_source_package(series, ppa, source_name):
140 try:
141 source = ppa.getPublishedSources(exact_match=True,
142 source_name=source_name,
143 distro_series=series)[0]
144 return source.source_package_version
145 except (KeyError, IndexError):
146 return None
147
148
149def check_ppa(series, ppa, dest_ppa, arch, upload_list):
150 # Prepare launchpad connection:
151 lp_series = launchpadmanager.get_series(series)
152 monitored_ppa = launchpadmanager.get_ppa(ppa)
153 if dest_ppa:
154 dest_archive = get_ppa(dest_ppa)
155 else:
156 dest_archive = launchpadmanager.get_ubuntu_archive()
157 logging.info('Series: {}'.format(lp_series))
158 logging.info('Monitoring PPA: {}'.format(monitored_ppa))
159 logging.info('Destination Archive: {}'.format(dest_archive))
160
161 failed = False
162 check_status = []
163 for source_package in upload_list:
164 source = source_package['name']
165 version = source_package['version']
166 logging.info('Inspecting upload: {} - {}'.format(source, version))
167 message_list = []
168 subticket_status = {
169 'name': source,
170 'id': source_package['id'],
171 'version': version,
172 }
173 wf_step = SubTicketWorkflowStep.NEW
174 wf_status = SubTicketWorkflowStepStatus.NEW
175 check_status.append(subticket_status)
176 for ppa in [monitored_ppa, dest_archive]:
177 last_version = get_versions_for_source_package(lp_series, ppa,
178 source)
179 logging.info('Last source: {}'.format(last_version))
180
181 if last_version:
182 try:
183 subprocess.check_call(['dpkg', '--compare-versions',
184 last_version, 'lt', version])
185 except subprocess.CalledProcessError:
186 # The version in the PPA is equal or higher then the
187 # source package, the build will fail
188 wf_step = SubTicketWorkflowStep.COMPLETED
189 wf_status = SubTicketWorkflowStepStatus.PKG_BUILDING_FAILED
190 message_list.append(EXISTING_SOURCE_MESSAGE.format(
191 last_version, ppa.web_link, source, version))
192 failed = True
193 subticket_status['step'] = wf_step.value
194 subticket_status['step_text'] = wf_step.title
195 subticket_status['status'] = wf_status.value
196 subticket_status['status_text'] = wf_status.title
197 subticket_status['message'] = '\n'.join(message_list)
198
199 return (failed, check_status)
200
201
202def find_subticket(upload_list, name, version):
203 for subticket in upload_list:
204 if subticket['name'] == name and subticket['version'] == version:
205 return subticket
206 return None
207
208
75def watch_ppa(time_start, series, ppa, dest_ppa, arch, upload_list):209def watch_ppa(time_start, series, ppa, dest_ppa, arch, upload_list):
76 # Prepare launchpad connection:210 # Prepare launchpad connection:
77 lp_series = launchpadmanager.get_series(series)211 lp_series = launchpadmanager.get_series(series)
@@ -82,7 +216,6 @@
82 dest_archive = launchpadmanager.get_ubuntu_archive()216 dest_archive = launchpadmanager.get_ubuntu_archive()
83 logging.info('Series: {}'.format(lp_series))217 logging.info('Series: {}'.format(lp_series))
84 logging.info('Monitoring PPA: {}'.format(monitored_ppa))218 logging.info('Monitoring PPA: {}'.format(monitored_ppa))
85
86 logging.info('Destination Archive: {}'.format(dest_archive))219 logging.info('Destination Archive: {}'.format(dest_archive))
87220
88 # Get archs available and archs to ignore221 # Get archs available and archs to ignore
@@ -102,6 +235,7 @@
102 packages_not_in_ppa = set()235 packages_not_in_ppa = set()
103 packages_building = set()236 packages_building = set()
104 packages_failed = set()237 packages_failed = set()
238 packages_complete = set()
105 for source_package in upload_list:239 for source_package in upload_list:
106 source = source_package['name']240 source = source_package['name']
107 version = source_package['version']241 version = source_package['version']
@@ -119,8 +253,6 @@
119 # to eventually appear in the ppa.253 # to eventually appear in the ppa.
120 logging.info('Packages not in PPA: {}'.format(254 logging.info('Packages not in PPA: {}'.format(
121 list_packages_info_in_str(packages_not_in_ppa)))255 list_packages_info_in_str(packages_not_in_ppa)))
122 logging.info('Packages building: {}'.format(packages_building))
123 logging.info('Packages failed: {}'.format(packages_failed))
124256
125 # Check the status regularly on all packages257 # Check the status regularly on all packages
126 # TODO The following is the original check loop. This can be extracted258 # TODO The following is the original check loop. This can be extracted
@@ -129,11 +261,24 @@
129 list_packages_info_in_str(261 list_packages_info_in_str(
130 packages_not_in_ppa.union(packages_building))))262 packages_not_in_ppa.union(packages_building))))
131 packageinppamanager.update_all_packages_status(263 packageinppamanager.update_all_packages_status(
132 packages_not_in_ppa, packages_building, packages_failed, arch)264 packages_not_in_ppa, packages_building, packages_failed,
133265 packages_complete, arch)
134 status = {'pending': packages_not_in_ppa,266
135 'building': packages_building,267 status = collect_subticket_status(monitored_ppa, packages_not_in_ppa,
136 'failed': packages_failed}268 packages_building, packages_failed,
269 packages_complete)
270 for package in status:
271 subticket = find_subticket(upload_list, package['name'],
272 package['version'])
273 if not subticket:
274 # No match, this should not happen
275 return (1, status)
276
277 # Populate the remaining fields necessary to update ticket status
278 package['id'] = subticket['id']
279 package['resource'] = subticket['resource']
280
281 logging.info("Status: {}".format(status))
137 # if we have no package building or failing and have wait for282 # if we have no package building or failing and have wait for
138 # long enough to have some package appearing in the ppa, exit283 # long enough to have some package appearing in the ppa, exit
139 if (packages_not_in_ppa and not packages_building and284 if (packages_not_in_ppa and not packages_building and
@@ -143,6 +288,16 @@
143 logging.info(288 logging.info(
144 "Some source packages were never published in the ppa: "289 "Some source packages were never published in the ppa: "
145 "{}".format(list_packages_info_in_str(packages_not_in_ppa)))290 "{}".format(list_packages_info_in_str(packages_not_in_ppa)))
291 for package in status:
292 # If any packages are WAITING, set them to FAILED
293 if package['status'] == SubTicketWorkflowStepStatus.WAITING.value:
294 package['step'] = SubTicketWorkflowStep.COMPLETED.value,
295 package['status'] = \
296 SubTicketWorkflowStepStatus.PKG_BUILDING_COMPLETED.value
297 # Add a message indicating the cause of the failure
298 message_list.append(MISSING_SOURCE_MESSAGE.format(
299 package['name'], package['version'], monitored_ppa.web_link))
300 status['message'] = '\n'.join(message_list)
146 return (1, status)301 return (1, status)
147302
148 # break out of status check loop if all packages have arrived in303 # break out of status check loop if all packages have arrived in
149304
=== modified file 'juju-deployer/branch-source-builder.yaml.tmpl'
--- juju-deployer/branch-source-builder.yaml.tmpl 2014-03-10 01:35:59 +0000
+++ juju-deployer/branch-source-builder.yaml.tmpl 2014-03-10 20:31:16 +0000
@@ -29,7 +29,7 @@
29 branch: ${CI_BRANCH}29 branch: ${CI_BRANCH}
30 tarball: ${CI_PAYLOAD_URL}30 tarball: ${CI_PAYLOAD_URL}
31 unit-config: include-base64://configs/unit_config.yaml31 unit-config: include-base64://configs/unit_config.yaml
32 packages: "dput python-dput python-swiftclient"32 packages: "dput python-dput python-swiftclient lazr.enum"
33 install_sources: |33 install_sources: |
34 - ${CI_PPA}34 - ${CI_PPA}
35 install_keys: |35 install_keys: |
3636
=== modified file 'lander/bin/lander_service_wrapper.py'
--- lander/bin/lander_service_wrapper.py 2014-03-10 14:57:34 +0000
+++ lander/bin/lander_service_wrapper.py 2014-03-10 20:31:16 +0000
@@ -48,14 +48,18 @@
48class MessageSync(object):48class MessageSync(object):
49 progress = logging.getLogger('PROGRESS_TRIGGER')49 progress = logging.getLogger('PROGRESS_TRIGGER')
5050
51 def __init__(self, ticket, started_cb=None, completed_cb=None):51 def __init__(self, ticket, started_cb=None, completed_cb=None,
52 message_cb=None):
52 if not started_cb:53 if not started_cb:
53 started_cb = lambda: True54 started_cb = lambda: True
54 if not completed_cb:55 if not completed_cb:
55 completed_cb = lambda x: True56 completed_cb = lambda x: True
57 if not message_cb:
58 message_cb = lambda x: True
56 self.ticket = ticket59 self.ticket = ticket
57 self.started_cb = started_cb60 self.started_cb = started_cb
58 self.completed_cb = completed_cb61 self.completed_cb = completed_cb
62 self.message_cb = message_cb
5963
60 def on_message(self, msg):64 def on_message(self, msg):
61 data = json.loads(msg.body)65 data = json.loads(msg.body)
@@ -68,6 +72,8 @@
68 else:72 else:
69 self.progress.info(state)73 self.progress.info(state)
7074
75 self.message_cb(data)
76
71 if state == 'STATUS':77 if state == 'STATUS':
72 self.started_cb()78 self.started_cb()
73 elif state == 'COMPLETED':79 elif state == 'COMPLETED':
@@ -85,8 +91,8 @@
8591
8692
87def _drain_progress(amqp_config, queue, ticket, started_cb=None,93def _drain_progress(amqp_config, queue, ticket, started_cb=None,
88 completed_cb=None):94 completed_cb=None, message_cb=None):
89 sync = MessageSync(ticket, started_cb, completed_cb)95 sync = MessageSync(ticket, started_cb, completed_cb, message_cb)
90 amqp_utils.process_queue(amqp_config, queue, sync.on_message, delete=True)96 amqp_utils.process_queue(amqp_config, queue, sync.on_message, delete=True)
91 return sync.data97 return sync.data
9298
@@ -169,17 +175,10 @@
169175
170 queue = '%s-bsbuilder' % config['master']['progress_trigger']176 queue = '%s-bsbuilder' % config['master']['progress_trigger']
171177
172 # Extract the source files from the master request parameters
173 request_parameters = ticket.get_full_ticket()178 request_parameters = ticket.get_full_ticket()
174 source_files = []
175 for subticket in request_parameters.get('subticket', []):
176 for artifact in subticket.get('artifact', []):
177 if artifact.get('type', None) == 'SPU':
178 source_files.append(artifact['reference'])
179
180 params = {179 params = {
181 'ticket_id': config['master']['request_id'],180 'ticket_id': config['master']['request_id'],
182 'source_packages': source_files,181 'subtickets': request_parameters['subticket'],
183 'series': request_parameters.get('series', ''),182 'series': request_parameters.get('series', ''),
184 'ppa': config['ppa_assigner']['ppa'],183 'ppa': config['ppa_assigner']['ppa'],
185 'archive': request_parameters.get('master_ppa', ''),184 'archive': request_parameters.get('master_ppa', ''),
@@ -193,7 +192,8 @@
193 logger.info('starting progress handler...')192 logger.info('starting progress handler...')
194 return _drain_progress(args.amqp_config, queue, ticket,193 return _drain_progress(args.amqp_config, queue, ticket,
195 ticket.set_pkg_inprogress,194 ticket.set_pkg_inprogress,
196 ticket.set_pkg_complete)195 ticket.set_pkg_complete,
196 ticket.set_subticket_progress)
197 except:197 except:
198 ticket.set_pkg_complete(False)198 ticket.set_pkg_complete(False)
199 raise199 raise
200200
=== modified file 'lander/bin/ticket_api.py'
--- lander/bin/ticket_api.py 2014-03-07 15:31:20 +0000
+++ lander/bin/ticket_api.py 2014-03-10 20:31:16 +0000
@@ -65,6 +65,18 @@
65 state = TicketWorkflowStepStatus.PKG_BUILDING_FAILED65 state = TicketWorkflowStepStatus.PKG_BUILDING_FAILED
66 self._patch_pkg_status(state)66 self._patch_pkg_status(state)
6767
68 def _patch_subticket_status(self, subticket_id, step, status):
69 url = self._url + '/updatesubticketstatus/' + str(subticket_id) + '/'
70 self._patch(url, {'status': status, 'current_workflow_step': step})
71
72 def set_subticket_progress(self, data):
73 '''Processes the content of messages with subticket_status.
74
75 Not all messages will have this content.'''
76 for subticket in data.get('subticket_status', []):
77 self._patch_subticket_status(subticket['id'], subticket['step'],
78 subticket['status'])
79
68 def _patch_ticket_status(self, status):80 def _patch_ticket_status(self, status):
69 step = TicketWorkflowStep.IMAGE_BUILDING.value81 step = TicketWorkflowStep.IMAGE_BUILDING.value
70 status = status.value82 status = status.value

Subscribers

People subscribed via source and target branches