Merge lp:~cjwatson/launchpad/uefi-signing-failures into lp:launchpad

Proposed by Colin Watson
Status: Merged
Approved by: Curtis Hovey
Approved revision: no longer in the source branch.
Merged at revision: 16251
Proposed branch: lp:~cjwatson/launchpad/uefi-signing-failures
Merge into: lp:launchpad
Diff against target: 299 lines (+59/-62)
7 files modified
lib/lp/archivepublisher/customupload.py (+2/-1)
lib/lp/archivepublisher/ddtp_tarball.py (+2/-2)
lib/lp/archivepublisher/debian_installer.py (+2/-2)
lib/lp/archivepublisher/dist_upgrader.py (+2/-2)
lib/lp/archivepublisher/tests/test_uefi.py (+15/-12)
lib/lp/archivepublisher/uefi.py (+30/-37)
lib/lp/soyuz/model/queue.py (+6/-6)
To merge this branch: bzr merge lp:~cjwatson/launchpad/uefi-signing-failures
Reviewer Review Type Date Requested Status
Curtis Hovey (community) code Approve
Review via email: mp+133666@code.launchpad.net

Commit message

Make some UEFI-related errors into logged warnings instead, since they aren't important enough to justify the resulting publisher chaos.

Description of the change

== Summary ==

Bug 1071562: Failures when publishing UEFI custom uploads cause continuous republication attempts, resulting in vast numbers of excess BPPH rows.

== Proposed fix ==

The fundamental problem is that errors when publishing custom uploads involve non-transactional filesystem changes which are difficult to roll back; until that's fixed (perhaps by moving custom upload publication from process-accepted to the publisher proper), this is never going to be very graceful. However, we can at least avoid causing gratuitous trouble for ourselves by making some less important errors into logged warnings.

If nothing else, it is relatively common for people to copy kernel source from quantal to their PPA without removing the UEFI tarball emission or arranging for their PPA to have UEFI signing keys. This should be handled more gracefully by just not signing the resulting files.

== Tests ==

bin/test -vvct lp.archivepublisher.tests.test_uefi -t distroseriesqueue

== Demo and Q/A ==

Copy a version of efilinux that emits a UEFI tarball to a PPA without signing configuration, and make sure it gets published correctly.

To post a comment you must log in.
Revision history for this message
Curtis Hovey (sinzui) wrote :

Thank you.

review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/archivepublisher/customupload.py'
2--- lib/lp/archivepublisher/customupload.py 2012-07-03 17:09:00 +0000
3+++ lib/lp/archivepublisher/customupload.py 2012-11-09 12:55:31 +0000
4@@ -94,12 +94,13 @@
5 # This should be set as a class property on each subclass.
6 custom_type = None
7
8- def __init__(self):
9+ def __init__(self, logger=None):
10 self.targetdir = None
11 self.version = None
12 self.arch = None
13
14 self.tmpdir = None
15+ self.logger = logger
16
17 def process(self, pubconf, tarfile_path, distroseries):
18 """Process the upload and install it into the archive."""
19
20=== modified file 'lib/lp/archivepublisher/ddtp_tarball.py'
21--- lib/lp/archivepublisher/ddtp_tarball.py 2012-07-10 11:21:11 +0000
22+++ lib/lp/archivepublisher/ddtp_tarball.py 2012-11-09 12:55:31 +0000
23@@ -81,12 +81,12 @@
24 pass
25
26
27-def process_ddtp_tarball(pubconf, tarfile_path, distroseries):
28+def process_ddtp_tarball(pubconf, tarfile_path, distroseries, logger=None):
29 """Process a raw-ddtp-tarball tarfile.
30
31 Unpacking it into the given archive for the given distroseries.
32 Raises CustomUploadError (or some subclass thereof) if
33 anything goes wrong.
34 """
35- upload = DdtpTarballUpload()
36+ upload = DdtpTarballUpload(logger=logger)
37 upload.process(pubconf, tarfile_path, distroseries)
38
39=== modified file 'lib/lp/archivepublisher/debian_installer.py'
40--- lib/lp/archivepublisher/debian_installer.py 2012-07-10 11:21:11 +0000
41+++ lib/lp/archivepublisher/debian_installer.py 2012-11-09 12:55:31 +0000
42@@ -74,12 +74,12 @@
43 return filename.startswith('%s/' % self.version)
44
45
46-def process_debian_installer(pubconf, tarfile_path, distroseries):
47+def process_debian_installer(pubconf, tarfile_path, distroseries, logger=None):
48 """Process a raw-installer tarfile.
49
50 Unpacking it into the given archive for the given distroseries.
51 Raises CustomUploadError (or some subclass thereof) if anything goes
52 wrong.
53 """
54- upload = DebianInstallerUpload()
55+ upload = DebianInstallerUpload(logger=logger)
56 upload.process(pubconf, tarfile_path, distroseries)
57
58=== modified file 'lib/lp/archivepublisher/dist_upgrader.py'
59--- lib/lp/archivepublisher/dist_upgrader.py 2012-07-10 11:21:11 +0000
60+++ lib/lp/archivepublisher/dist_upgrader.py 2012-11-09 12:55:31 +0000
61@@ -100,12 +100,12 @@
62 return version and not filename.startswith('current')
63
64
65-def process_dist_upgrader(pubconf, tarfile_path, distroseries):
66+def process_dist_upgrader(pubconf, tarfile_path, distroseries, logger=None):
67 """Process a raw-dist-upgrader tarfile.
68
69 Unpacking it into the given archive for the given distroseries.
70 Raises CustomUploadError (or some subclass thereof) if anything goes
71 wrong.
72 """
73- upload = DistUpgraderUpload()
74+ upload = DistUpgraderUpload(logger=logger)
75 upload.process(pubconf, tarfile_path, distroseries)
76
77=== modified file 'lib/lp/archivepublisher/tests/test_uefi.py'
78--- lib/lp/archivepublisher/tests/test_uefi.py 2012-06-28 16:32:27 +0000
79+++ lib/lp/archivepublisher/tests/test_uefi.py 2012-11-09 12:55:31 +0000
80@@ -11,11 +11,7 @@
81 CustomUploadAlreadyExists,
82 CustomUploadBadUmask,
83 )
84-from lp.archivepublisher.uefi import (
85- UefiConfigurationError,
86- UefiNothingToSign,
87- UefiUpload,
88- )
89+from lp.archivepublisher.uefi import UefiUpload
90 from lp.services.osutils import write_file
91 from lp.services.tarfile_helpers import LaunchpadWriteTarFile
92 from lp.testing import TestCase
93@@ -67,23 +63,30 @@
94 "%s-%s" % (loader_type, arch))
95
96 def test_unconfigured(self):
97- # If there is no key/cert configuration, processing fails.
98+ # If there is no key/cert configuration, processing succeeds but
99+ # nothing is signed.
100 self.pubconf = FakeConfig(self.temp_dir, None)
101 self.openArchive("test", "1.0", "amd64")
102- self.assertRaises(UefiConfigurationError, self.process)
103+ self.archive.add_file("1.0/empty.efi", "")
104+ upload = self.process()
105+ self.assertEqual(0, upload.sign.call_count)
106
107 def test_missing_key_and_cert(self):
108- # If the configured key/cert are missing, processing fails.
109+ # If the configured key/cert are missing, processing succeeds but
110+ # nothing is signed.
111 self.openArchive("test", "1.0", "amd64")
112 self.archive.add_file("1.0/empty.efi", "")
113- self.assertRaises(UefiConfigurationError, self.process)
114+ upload = self.process()
115+ self.assertEqual(0, upload.sign.call_count)
116
117 def test_no_efi_files(self):
118- # Tarballs containing no *.efi files are rejected.
119+ # Tarballs containing no *.efi files are extracted without complaint.
120 self.setUpKeyAndCert()
121 self.openArchive("empty", "1.0", "amd64")
122- self.archive.add_file("hello", "world")
123- self.assertRaises(UefiNothingToSign, self.process)
124+ self.archive.add_file("1.0/hello", "world")
125+ self.process()
126+ self.assertTrue(os.path.exists(os.path.join(
127+ self.getUefiPath("empty", "amd64"), "1.0", "hello")))
128
129 def test_already_exists(self):
130 # If the target directory already exists, processing fails.
131
132=== modified file 'lib/lp/archivepublisher/uefi.py'
133--- lib/lp/archivepublisher/uefi.py 2012-07-10 11:21:11 +0000
134+++ lib/lp/archivepublisher/uefi.py 2012-11-09 12:55:31 +0000
135@@ -19,27 +19,10 @@
136 import os
137 import subprocess
138
139-from lp.archivepublisher.customupload import (
140- CustomUpload,
141- CustomUploadError,
142- )
143+from lp.archivepublisher.customupload import CustomUpload
144 from lp.services.osutils import remove_if_exists
145
146
147-class UefiConfigurationError(CustomUploadError):
148- """No signing key location is configured."""
149- def __init__(self, message):
150- CustomUploadError.__init__(
151- self, "UEFI signing configuration error: %s" % message)
152-
153-
154-class UefiNothingToSign(CustomUploadError):
155- """The tarball contained no *.efi files."""
156- def __init__(self, tarfile_path):
157- CustomUploadError.__init__(
158- self, "UEFI upload '%s' contained no *.efi files" % tarfile_path)
159-
160-
161 class UefiUpload(CustomUpload):
162 """UEFI boot loader custom upload.
163
164@@ -77,16 +60,23 @@
165
166 def setTargetDirectory(self, pubconf, tarfile_path, distroseries):
167 if pubconf.uefiroot is None:
168- raise UefiConfigurationError(
169- "no UEFI root configured for this archive")
170- self.key = os.path.join(pubconf.uefiroot, "uefi.key")
171- self.cert = os.path.join(pubconf.uefiroot, "uefi.crt")
172- if not os.access(self.key, os.R_OK):
173- raise UefiConfigurationError(
174- "UEFI private key %s not readable" % self.key)
175- if not os.access(self.cert, os.R_OK):
176- raise UefiConfigurationError(
177- "UEFI certificate %s not readable" % self.cert)
178+ if self.logger is not None:
179+ self.logger.warning("No UEFI root configured for this archive")
180+ self.key = None
181+ self.cert = None
182+ else:
183+ self.key = os.path.join(pubconf.uefiroot, "uefi.key")
184+ self.cert = os.path.join(pubconf.uefiroot, "uefi.crt")
185+ if not os.access(self.key, os.R_OK):
186+ if self.logger is not None:
187+ self.logger.warning(
188+ "UEFI private key %s not readable" % self.key)
189+ self.key = None
190+ if not os.access(self.cert, os.R_OK):
191+ if self.logger is not None:
192+ self.logger.warning(
193+ "UEFI certificate %s not readable" % self.cert)
194+ self.cert = None
195
196 loader_type, self.version, self.arch = self.parsePath(tarfile_path)
197 self.targetdir = os.path.join(
198@@ -114,7 +104,11 @@
199
200 def sign(self, image):
201 """Sign an image."""
202- subprocess.check_call(self.getSigningCommand(image))
203+ if subprocess.call(self.getSigningCommand(image)) != 0:
204+ # Just log this rather than failing, since custom upload errors
205+ # tend to make the publisher rather upset.
206+ if self.logger is not None:
207+ self.logger.warning("Failed to sign %s" % image)
208
209 def extract(self):
210 """Copy the custom upload to a temporary directory, and sign it.
211@@ -122,23 +116,22 @@
212 No actual extraction is required.
213 """
214 super(UefiUpload, self).extract()
215- efi_filenames = list(self.findEfiFilenames())
216- if not efi_filenames:
217- raise UefiNothingToSign(self.tarfile_path)
218- for efi_filename in efi_filenames:
219- remove_if_exists("%s.signed" % efi_filename)
220- self.sign(efi_filename)
221+ if self.key is not None and self.cert is not None:
222+ efi_filenames = list(self.findEfiFilenames())
223+ for efi_filename in efi_filenames:
224+ remove_if_exists("%s.signed" % efi_filename)
225+ self.sign(efi_filename)
226
227 def shouldInstall(self, filename):
228 return filename.startswith("%s/" % self.version)
229
230
231-def process_uefi(pubconf, tarfile_path, distroseries):
232+def process_uefi(pubconf, tarfile_path, distroseries, logger=None):
233 """Process a raw-uefi tarfile.
234
235 Unpacking it into the given archive for the given distroseries.
236 Raises CustomUploadError (or some subclass thereof) if anything goes
237 wrong.
238 """
239- upload = UefiUpload()
240+ upload = UefiUpload(logger=logger)
241 upload.process(pubconf, tarfile_path, distroseries)
242
243=== modified file 'lib/lp/soyuz/model/queue.py'
244--- lib/lp/soyuz/model/queue.py 2012-08-13 01:53:37 +0000
245+++ lib/lp/soyuz/model/queue.py 2012-11-09 12:55:31 +0000
246@@ -1484,7 +1484,7 @@
247 copy_and_close(self.libraryfilealias, temp_file)
248 return temp_file_name
249
250- def _publishCustom(self, action_method):
251+ def _publishCustom(self, action_method, logger=None):
252 """Publish custom formats.
253
254 Publish Either an installer, an upgrader or a ddtp upload using the
255@@ -1496,7 +1496,7 @@
256 try:
257 # See the XXX near the import for getPubConfig.
258 archive_config = getPubConfig(self.packageupload.archive)
259- action_method(archive_config, temp_filename, suite)
260+ action_method(archive_config, temp_filename, suite, logger=logger)
261 finally:
262 shutil.rmtree(os.path.dirname(temp_filename))
263
264@@ -1507,7 +1507,7 @@
265 from lp.archivepublisher.debian_installer import (
266 process_debian_installer)
267
268- self._publishCustom(process_debian_installer)
269+ self._publishCustom(process_debian_installer, logger=logger)
270
271 def publishDistUpgrader(self, logger=None):
272 """See `IPackageUploadCustom`."""
273@@ -1516,7 +1516,7 @@
274 from lp.archivepublisher.dist_upgrader import (
275 process_dist_upgrader)
276
277- self._publishCustom(process_dist_upgrader)
278+ self._publishCustom(process_dist_upgrader, logger=logger)
279
280 def publishDdtpTarball(self, logger=None):
281 """See `IPackageUploadCustom`."""
282@@ -1525,7 +1525,7 @@
283 from lp.archivepublisher.ddtp_tarball import (
284 process_ddtp_tarball)
285
286- self._publishCustom(process_ddtp_tarball)
287+ self._publishCustom(process_ddtp_tarball, logger=logger)
288
289 def publishRosettaTranslations(self, logger=None):
290 """See `IPackageUploadCustom`."""
291@@ -1607,7 +1607,7 @@
292 # to instantiate the object in question and avoid circular imports
293 from lp.archivepublisher.uefi import process_uefi
294
295- self._publishCustom(process_uefi)
296+ self._publishCustom(process_uefi, logger=logger)
297
298 publisher_dispatch = {
299 PackageUploadCustomFormat.DEBIAN_INSTALLER: publishDebianInstaller,