Merge lp:~wgrant/launchpad/no-key-oopses into lp:launchpad

Proposed by William Grant
Status: Merged
Approved by: William Grant
Approved revision: no longer in the source branch.
Merged at revision: 14071
Proposed branch: lp:~wgrant/launchpad/no-key-oopses
Merge into: lp:launchpad
Diff against target: 240 lines (+55/-50)
5 files modified
lib/lp/archiveuploader/nascentupload.py (+2/-15)
lib/lp/archiveuploader/tests/nascentupload.txt (+2/-2)
lib/lp/archiveuploader/tests/test_uploadprocessor.py (+31/-23)
lib/lp/archiveuploader/uploadprocessor.py (+17/-7)
lib/lp/soyuz/doc/soyuz-upload.txt (+3/-3)
To merge this branch: bzr merge lp:~wgrant/launchpad/no-key-oopses
Reviewer Review Type Date Requested Status
Jeroen T. Vermeulen (community) Approve
Review via email: mp+77447@code.launchpad.net

Commit message

[r=jtv][bug=862032] Don't OOPS on FatalUploadErrors. Even though we can't tell anyone about them, they are still user error.

Description of the change

This branch fixes some user-error Soyuz OOPSes: FatalUploadErrors from process-upload.

FatalUploadErrors are caused by ChangesFile in case of signature issues (no signature, bad signature, unrecognized key, etc.) or corrupt changes files. Since we use the changes file to determine the notification recipient, such a failure means that we're unable to email anyone about it.

Long ago it was decided to turn such unreportable user errors into OOPSes. But that runs counter to ZeroOopsPolicy, so we should not do it. This branch logs them at DEBUG instead, with the rest of the normal processing stuff.

To post a comment you must log in.
Revision history for this message
Jeroen T. Vermeulen (jtv) wrote :
Download full text (6.6 KiB)

This is a change that I've been craving. So much kudos for implementing it.

I heartily approve of most of this branch. Of course as a reviewer, I have a reputation to uphold and thus:

=== modified file 'lib/lp/archiveuploader/nascentupload.py'
--- lib/lp/archiveuploader/nascentupload.py 2011-08-23 14:35:43 +0000
+++ lib/lp/archiveuploader/nascentupload.py 2011-09-29 05:22:06 +0000

@@ -114,23 +110,14 @@
     def from_changesfile_path(cls, changesfile_path, policy, logger):
         """Create a NascentUpload from the given changesfile path.

- May raise FatalUploadError due to unrecoverable problems building
+ May raise UploadError due to unrecoverable problems building
         the ChangesFile object.

If we're crossing the boundary of "fatal" here, consider replacing or removing the word "unrecoverable" as well. It's not so much the error that is unrecoverable, as an extent of code. But what extent exactly? Like "Fatal," "unrecoverable" sort of suggests that that extent is the entire script run.

=== modified file 'lib/lp/archiveuploader/tests/test_uploadprocessor.py'
--- lib/lp/archiveuploader/tests/test_uploadprocessor.py 2011-08-26 06:14:54 +0000
+++ lib/lp/archiveuploader/tests/test_uploadprocessor.py 2011-09-29 05:22:06 +0000

@@ -1361,28 +1363,27 @@
         self.options.builds = False
         processor = self.getUploadProcessor(self.layer.txn)

- upload_dir = self.queueUpload("foocomm_1.0-1_proposed")
- bogus_changesfile_data = '''
- Ubuntu is a community developed, Linux-based operating system that is
- perfect for laptops, desktops and servers. It contains all the
- applications you need - a web browser, presentation, document and
- spreadsheet software, instant messaging and much more.
- '''
- file_handle = open(
- '%s/%s' % (upload_dir, 'bogus.changes'), 'w')
- file_handle.write(bogus_changesfile_data)
- file_handle.close()
+ self.queueUpload("foocomm_1.0-1_proposed")
+
+ # Any code that causes an OOPS is a bug that must be fixed, so
+ # let's monkeypatch something in that we know will OOPS.

I find this explanation unnecessarily confusing. To a hurried reader (and we all are from time to time) it seems to say "let's create bugs that must be fixed," which of course is fine from a job-security standpoint but...

What I think you mean is: "This test verifies that oopses get handled properly, which is very important because oopses must be caught and fixed. The following simulates an error that will be treated as an oops. We verify that the oops gets handled properly later."

If so, the first of those sentences probably belongs at the top of the test (if roughly the same thing isn't being said there already), and the last belongs at the point of verification.

For some reason the term "let's" seems to be a very reliable indicator of this kind of muddled expression in tests, so treat it as inherently suspicious.

+ class SomeException(Exception):
+ pass
+
+ def from_changesfile_path(cls, changesfile_path, policy, logger):
+ raise SomeException("I am an explanation.")
+ ...

Read more...

review: Approve
Revision history for this message
William Grant (wgrant) wrote :

> Well done, well explained. Just one question here: wouldn't logger.info make
> more sense than logger.debug? After all this situation is still a bit of a
> problem.
>
> In particular, I'm assuming that sometimes we'll want to search the logs for
> this message to find out what happened to an upload. If so, I imagine we
> would want this logged by default.

Good point. I've bumped it to INFO and adding the changes file path to the message, as everything else that mentions the path is at DEBUG.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/lp/archiveuploader/nascentupload.py'
--- lib/lp/archiveuploader/nascentupload.py 2011-08-23 14:35:43 +0000
+++ lib/lp/archiveuploader/nascentupload.py 2011-09-29 12:10:29 +0000
@@ -49,10 +49,6 @@
49PARTNER_COMPONENT_NAME = 'partner'49PARTNER_COMPONENT_NAME = 'partner'
5050
5151
52class FatalUploadError(Exception):
53 """A fatal error occurred processing the upload; processing aborted."""
54
55
56class EarlyReturnUploadError(Exception):52class EarlyReturnUploadError(Exception):
57 """An error occurred that prevented further error collection."""53 """An error occurred that prevented further error collection."""
5854
@@ -114,23 +110,14 @@
114 def from_changesfile_path(cls, changesfile_path, policy, logger):110 def from_changesfile_path(cls, changesfile_path, policy, logger):
115 """Create a NascentUpload from the given changesfile path.111 """Create a NascentUpload from the given changesfile path.
116112
117 May raise FatalUploadError due to unrecoverable problems building113 May raise UploadError due to unrecoverable problems building
118 the ChangesFile object.114 the ChangesFile object.
119115
120 :param changesfile_path: path to the changesfile to be uploaded.116 :param changesfile_path: path to the changesfile to be uploaded.
121 :param policy: the upload policy to be used.117 :param policy: the upload policy to be used.
122 :param logger: the logger to be used.118 :param logger: the logger to be used.
123 """119 """
124 try:120 changesfile = ChangesFile(changesfile_path, policy, logger)
125 changesfile = ChangesFile(changesfile_path, policy, logger)
126 except UploadError, e:
127 # We can't run reject() because unfortunately we don't have
128 # the address of the uploader to notify -- we broke in that
129 # exact step.
130 # XXX cprov 2007-03-26: we should really be emailing this
131 # rejection to the archive admins. For now, this will end
132 # up in the script log.
133 raise FatalUploadError(str(e))
134 return cls(changesfile, policy, logger)121 return cls(changesfile, policy, logger)
135122
136 def process(self, build=None):123 def process(self, build=None):
137124
=== modified file 'lib/lp/archiveuploader/tests/nascentupload.txt'
--- lib/lp/archiveuploader/tests/nascentupload.txt 2011-06-28 15:04:29 +0000
+++ lib/lp/archiveuploader/tests/nascentupload.txt 2011-09-29 12:10:29 +0000
@@ -45,14 +45,14 @@
45Constructing a NascentUpload instance verifies that the changes file45Constructing a NascentUpload instance verifies that the changes file
46specified exists and tries to build a ChangesFile (see46specified exists and tries to build a ChangesFile (see
47doc/nascentuploadfile.txt) object based on that. If anything goes47doc/nascentuploadfile.txt) object based on that. If anything goes
48wrong during this process FatalUploadError is raised:48wrong during this process UploadError is raised:
4949
50 >>> from lp.services.log.logger import DevNullLogger, FakeLogger50 >>> from lp.services.log.logger import DevNullLogger, FakeLogger
51 >>> NascentUpload.from_changesfile_path(51 >>> NascentUpload.from_changesfile_path(
52 ... datadir("DOES-NOT-EXIST"), buildd_policy, FakeLogger())52 ... datadir("DOES-NOT-EXIST"), buildd_policy, FakeLogger())
53 Traceback (most recent call last):53 Traceback (most recent call last):
54 ...54 ...
55 FatalUploadError:...55 UploadError:...
5656
57Otherwise a ChangesFile object is ready to use.57Otherwise a ChangesFile object is ready to use.
5858
5959
=== modified file 'lib/lp/archiveuploader/tests/test_uploadprocessor.py'
--- lib/lp/archiveuploader/tests/test_uploadprocessor.py 2011-08-26 06:14:54 +0000
+++ lib/lp/archiveuploader/tests/test_uploadprocessor.py 2011-09-29 12:10:29 +0000
@@ -15,6 +15,7 @@
15from StringIO import StringIO15from StringIO import StringIO
16import tempfile16import tempfile
1717
18from fixtures import MonkeyPatch
18from storm.locals import Store19from storm.locals import Store
19import transaction20import transaction
20from zope.component import (21from zope.component import (
@@ -42,6 +43,7 @@
42 parse_build_upload_leaf_name,43 parse_build_upload_leaf_name,
43 UploadHandler,44 UploadHandler,
44 UploadProcessor,45 UploadProcessor,
46 UploadStatusEnum,
45 )47 )
46from lp.buildmaster.enums import (48from lp.buildmaster.enums import (
47 BuildFarmJobType,49 BuildFarmJobType,
@@ -92,6 +94,7 @@
92 TestCase,94 TestCase,
93 TestCaseWithFactory,95 TestCaseWithFactory,
94 )96 )
97from lp.testing.fakemethod import FakeMethod
95from lp.testing.mail_helpers import pop_notifications98from lp.testing.mail_helpers import pop_notifications
9699
97100
@@ -1352,37 +1355,27 @@
1352 self.checkComponentOverride("bar_1.0-1", "universe")1355 self.checkComponentOverride("bar_1.0-1", "universe")
13531356
1354 def testOopsCreation(self):1357 def testOopsCreation(self):
1355 """Test the the creation of an OOPS upon upload processing failure.1358 """Test that an unhandled exception generates an OOPS."""
1356
1357 In order to trigger the exception needed a bogus changes file will be
1358 used.
1359 That exception will then initiate the creation of an OOPS report.
1360 """
1361 self.options.builds = False1359 self.options.builds = False
1362 processor = self.getUploadProcessor(self.layer.txn)1360 processor = self.getUploadProcessor(self.layer.txn)
13631361
1364 upload_dir = self.queueUpload("foocomm_1.0-1_proposed")1362 self.queueUpload("foocomm_1.0-1_proposed")
1365 bogus_changesfile_data = '''1363
1366 Ubuntu is a community developed, Linux-based operating system that is1364 # Inject an unhandled exception into the upload processor.
1367 perfect for laptops, desktops and servers. It contains all the1365 class SomeException(Exception):
1368 applications you need - a web browser, presentation, document and1366 pass
1369 spreadsheet software, instant messaging and much more.1367 self.useFixture(
1370 '''1368 MonkeyPatch(
1371 file_handle = open(1369 'lp.archiveuploader.nascentupload.NascentUpload.'
1372 '%s/%s' % (upload_dir, 'bogus.changes'), 'w')1370 'from_changesfile_path',
1373 file_handle.write(bogus_changesfile_data)1371 FakeMethod(failure=SomeException("I am an explanation."))))
1374 file_handle.close()
13751372
1376 processor.processUploadQueue()1373 processor.processUploadQueue()
13771374
1378 error_utility = ErrorReportingUtility()1375 error_utility = ErrorReportingUtility()
1379 error_report = error_utility.getLastOopsReport()1376 error_report = error_utility.getLastOopsReport()
1380 self.assertEqual('FatalUploadError', error_report.type)1377 self.assertEqual('SomeException', error_report.type)
1381 # The upload policy requires a signature but none is present, so1378 self.assertIn("I am an explanation", error_report.tb_text)
1382 # we get gpg verification errors.
1383 expected_explanation = (
1384 "Verification failed 3 times: ['No data', 'No data', 'No data']")
1385 self.assertIn(expected_explanation, error_report.tb_text)
13861379
1387 def testLZMADebUpload(self):1380 def testLZMADebUpload(self):
1388 """Make sure that data files compressed with lzma in Debs work.1381 """Make sure that data files compressed with lzma in Debs work.
@@ -1919,6 +1912,21 @@
1919 self.PGPSignatureNotPreserved(archive=self.breezy.main_archive)1912 self.PGPSignatureNotPreserved(archive=self.breezy.main_archive)
1920 self.switchToUploader()1913 self.switchToUploader()
19211914
1915 def test_unsigned_upload_is_silently_rejected(self):
1916 # Unsigned uploads are rejected without OOPS or email.
1917 uploadprocessor = self.setupBreezyAndGetUploadProcessor()
1918 upload_dir = self.queueUpload("netapplet_1.0-1")
1919
1920 last_oops = ErrorReportingUtility().getLastOopsReport()
1921
1922 [result] = self.processUpload(uploadprocessor, upload_dir)
1923
1924 self.assertEqual(UploadStatusEnum.REJECTED, result)
1925 self.assertLogContains(
1926 "INFO Failed to parse changes file")
1927 self.assertEqual(len(stub.test_emails), 0)
1928 self.assertNoNewOops(last_oops)
1929
19221930
1923class TestBuildUploadProcessor(TestUploadProcessorBase):1931class TestBuildUploadProcessor(TestUploadProcessorBase):
1924 """Test that processing build uploads works."""1932 """Test that processing build uploads works."""
19251933
=== modified file 'lib/lp/archiveuploader/uploadprocessor.py'
--- lib/lp/archiveuploader/uploadprocessor.py 2011-05-04 06:37:50 +0000
+++ lib/lp/archiveuploader/uploadprocessor.py 2011-09-29 12:10:29 +0000
@@ -63,8 +63,8 @@
63from lp.app.errors import NotFoundError63from lp.app.errors import NotFoundError
64from lp.archiveuploader.nascentupload import (64from lp.archiveuploader.nascentupload import (
65 EarlyReturnUploadError,65 EarlyReturnUploadError,
66 FatalUploadError,
67 NascentUpload,66 NascentUpload,
67 UploadError,
68 )68 )
69from lp.archiveuploader.uploadpolicy import (69from lp.archiveuploader.uploadpolicy import (
70 BuildDaemonUploadPolicy,70 BuildDaemonUploadPolicy,
@@ -370,8 +370,22 @@
370 # The path we want for NascentUpload is the path to the folder370 # The path we want for NascentUpload is the path to the folder
371 # containing the changes file (and the other files referenced by it).371 # containing the changes file (and the other files referenced by it).
372 changesfile_path = os.path.join(self.upload_path, changes_file)372 changesfile_path = os.path.join(self.upload_path, changes_file)
373 upload = NascentUpload.from_changesfile_path(373 try:
374 changesfile_path, policy, self.processor.log)374 upload = NascentUpload.from_changesfile_path(
375 changesfile_path, policy, self.processor.log)
376 except UploadError as e:
377 # We failed to parse the changes file, so we have no key or
378 # Changed-By to notify of the rejection. Just log it and
379 # move on.
380 # XXX wgrant 2011-09-29 bug=499438: With some refactoring we
381 # could do better here: if we have a signature then we have
382 # somebody to email, even if the rest of the file is
383 # corrupt.
384 logger.info(
385 "Failed to parse changes file '%s': %s" % (
386 os.path.join(self.upload_path, changes_file),
387 str(e)))
388 return UploadStatusEnum.REJECTED
375389
376 # Reject source upload to buildd upload paths.390 # Reject source upload to buildd upload paths.
377 first_path = relative_path.split(os.path.sep)[0]391 first_path = relative_path.split(os.path.sep)[0]
@@ -401,10 +415,6 @@
401 "%s " % e)415 "%s " % e)
402 logger.debug(416 logger.debug(
403 "UploadPolicyError escaped upload.process", exc_info=True)417 "UploadPolicyError escaped upload.process", exc_info=True)
404 except FatalUploadError, e:
405 upload.reject("UploadError escaped upload.process: %s" % e)
406 logger.debug(
407 "UploadError escaped upload.process", exc_info=True)
408 except (KeyboardInterrupt, SystemExit):418 except (KeyboardInterrupt, SystemExit):
409 raise419 raise
410 except EarlyReturnUploadError:420 except EarlyReturnUploadError:
411421
=== modified file 'lib/lp/soyuz/doc/soyuz-upload.txt'
--- lib/lp/soyuz/doc/soyuz-upload.txt 2011-09-18 10:22:51 +0000
+++ lib/lp/soyuz/doc/soyuz-upload.txt 2011-09-29 12:10:29 +0000
@@ -246,12 +246,12 @@
246 >>> process.returncode246 >>> process.returncode
247 0247 0
248248
249Check the four uploads are all where we expect - number 1 in failed,249Check the four uploads are all where we expect - number 1 in rejected,
250the other three still in incoming.250the other three still in incoming.
251251
252 >>> for i in range(4):252 >>> for i in range(4):
253 ... find_upload_dir_result(i + 1)253 ... find_upload_dir_result(i + 1)
254 'failed'254 'rejected'
255 'incoming'255 'incoming'
256 'incoming'256 'incoming'
257 'incoming'257 'incoming'
@@ -339,7 +339,7 @@
339339
340 >>> for i in range(0, 4):340 >>> for i in range(0, 4):
341 ... find_upload_dir_result(i + 1)341 ... find_upload_dir_result(i + 1)
342 'failed'342 'rejected'
343 'failed'343 'failed'
344344
345Also check the upload folders contain all the files we uploaded.345Also check the upload folders contain all the files we uploaded.