Merge lp:~cjwatson/launchpad/show-failed-copies into lp:launchpad

Proposed by Colin Watson
Status: Merged
Approved by: j.c.sackett
Approved revision: no longer in the source branch.
Merged at revision: 15620
Proposed branch: lp:~cjwatson/launchpad/show-failed-copies
Merge into: lp:launchpad
Diff against target: 160 lines (+47/-9)
3 files modified
lib/lp/soyuz/interfaces/packagecopyjob.py (+6/-0)
lib/lp/soyuz/model/packagecopyjob.py (+25/-3)
lib/lp/soyuz/tests/test_packagecopyjob.py (+16/-6)
To merge this branch: bzr merge lp:~cjwatson/launchpad/show-failed-copies
Reviewer Review Type Date Requested Status
j.c.sackett (community) Approve
Review via email: mp+114589@code.launchpad.net

Commit message

If copying to a PPA, turn copy failures into job failures so that they can show up in the UI, and send failure mail to the requester.

Description of the change

== Summary ==

Bug 812869: PCJs can't be used to copy to PPAs, because the failure mode is uninformative. See also bug 575450.

== Proposed fix ==

Raphaël Badin fixed most of this in r14665, by showing job notifications on Archive:+packages. The unit tests for this manually construct failed jobs. However, this doesn't quite work in practice because, as Julian points out: "This is in part due to the code in PCJ.run() that captures CannotCopy exceptions and swallows them (it assumes that all failures have a DistroSeriesDifference)." So the final glue needed here just seems to be to turn copy failures into job failures in the case where we're copying to a PPA, much as there's already a separate error handling path in PlainPackageCopyJob.reportFailure.

While we're here, we might as well hook up the job runner facility to send an e-mail to the requester on failure.

== LOC Rationale ==

+38. This is part of allowing us to remove synchronous copying from the PPA UI, and later to remove delayed copies, worth at least 1100 lines.

== Tests ==

bin/test -vvct test_packagecopyjob

== Demo and Q/A ==

Somewhat like https://code.launchpad.net/~cjwatson/launchpad/copy-packages-with-default-series/+merge/112557, but try copying something that fails, and make sure that an error message shows up on Archive:+packages after the PCJ has been processed.

To post a comment you must log in.
Revision history for this message
j.c.sackett (jcsackett) wrote :

This looks alright. Thanks.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/lp/soyuz/interfaces/packagecopyjob.py'
--- lib/lp/soyuz/interfaces/packagecopyjob.py 2012-06-19 23:49:20 +0000
+++ lib/lp/soyuz/interfaces/packagecopyjob.py 2012-07-12 09:12:22 +0000
@@ -228,3 +228,9 @@
228 copy_policy = Choice(228 copy_policy = Choice(
229 title=_("Applicable copy policy"),229 title=_("Applicable copy policy"),
230 values=PackageCopyPolicy, required=True, readonly=True)230 values=PackageCopyPolicy, required=True, readonly=True)
231
232 def getOperationDescription():
233 """Return a description of the copy operation."""
234
235 def getErrorRecipients():
236 """Return a list of email-ids to notify about copy errors."""
231237
=== modified file 'lib/lp/soyuz/model/packagecopyjob.py'
--- lib/lp/soyuz/model/packagecopyjob.py 2012-06-29 08:40:05 +0000
+++ lib/lp/soyuz/model/packagecopyjob.py 2012-07-12 09:12:22 +0000
@@ -55,6 +55,7 @@
55 Job,55 Job,
56 )56 )
57from lp.services.job.runner import BaseRunnableJob57from lp.services.job.runner import BaseRunnableJob
58from lp.services.mail.sendmail import format_address_for_person
58from lp.soyuz.adapters.overrides import (59from lp.soyuz.adapters.overrides import (
59 FromExistingOverridePolicy,60 FromExistingOverridePolicy,
60 SourceOverride,61 SourceOverride,
@@ -224,6 +225,14 @@
224 ])225 ])
225 return vars226 return vars
226227
228 def getOperationDescription(self):
229 """See `IPlainPackageCopyJob`."""
230 return "copying a package"
231
232 def getErrorRecipients(self):
233 """See `IPlainPackageCopyJob`."""
234 return [format_address_for_person(self.requester)]
235
227 @property236 @property
228 def copy_policy(self):237 def copy_policy(self):
229 """See `PlainPackageCopyJob`."""238 """See `PlainPackageCopyJob`."""
@@ -243,6 +252,7 @@
243 class_job_type = PackageCopyJobType.PLAIN252 class_job_type = PackageCopyJobType.PLAIN
244 classProvides(IPlainPackageCopyJobSource)253 classProvides(IPlainPackageCopyJobSource)
245 config = config.IPlainPackageCopyJobSource254 config = config.IPlainPackageCopyJobSource
255 user_error_types = (CannotCopy,)
246256
247 @classmethod257 @classmethod
248 def _makeMetadata(cls, target_pocket, package_version,258 def _makeMetadata(cls, target_pocket, package_version,
@@ -494,6 +504,9 @@
494 try:504 try:
495 self.attemptCopy()505 self.attemptCopy()
496 except CannotCopy as e:506 except CannotCopy as e:
507 # Remember the target archive purpose, as otherwise aborting the
508 # transaction will forget it.
509 target_archive_purpose = self.target_archive.purpose
497 logger = logging.getLogger()510 logger = logging.getLogger()
498 logger.info("Job:\n%s\nraised CannotCopy:\n%s" % (self, e))511 logger.info("Job:\n%s\nraised CannotCopy:\n%s" % (self, e))
499 self.abort() # Abort the txn.512 self.abort() # Abort the txn.
@@ -503,9 +516,18 @@
503 # else it will sit in ACCEPTED forever.516 # else it will sit in ACCEPTED forever.
504 self._rejectPackageUpload()517 self._rejectPackageUpload()
505518
506 # Rely on the job runner to do the final commit. Note that519 if target_archive_purpose == ArchivePurpose.PPA:
507 # we're not raising any exceptions here, failure of a copy is520 # If copying to a PPA, commit the failure and re-raise the
508 # not a failure of the job.521 # exception. We turn a copy failure into a job failure in
522 # order that it can show up in the UI.
523 transaction.commit()
524 raise
525 else:
526 # Otherwise, rely on the job runner to do the final commit,
527 # and do not consider a failure of a copy to be a failure of
528 # the job. We will normally have a DistroSeriesDifference
529 # in this case.
530 pass
509 except SuspendJobException:531 except SuspendJobException:
510 raise532 raise
511 except:533 except:
512534
=== modified file 'lib/lp/soyuz/tests/test_packagecopyjob.py'
--- lib/lp/soyuz/tests/test_packagecopyjob.py 2012-07-01 17:19:29 +0000
+++ lib/lp/soyuz/tests/test_packagecopyjob.py 2012-07-12 09:12:22 +0000
@@ -29,6 +29,7 @@
29 block_on_job,29 block_on_job,
30 pop_remote_notifications,30 pop_remote_notifications,
31 )31 )
32from lp.services.mail.sendmail import format_address_for_person
32from lp.services.webapp.testing import verifyObject33from lp.services.webapp.testing import verifyObject
33from lp.soyuz.adapters.overrides import SourceOverride34from lp.soyuz.adapters.overrides import SourceOverride
34from lp.soyuz.enums import (35from lp.soyuz.enums import (
@@ -199,6 +200,12 @@
199 job_source = getUtility(IPlainPackageCopyJobSource)200 job_source = getUtility(IPlainPackageCopyJobSource)
200 self.assertTrue(verifyObject(IPlainPackageCopyJobSource, job_source))201 self.assertTrue(verifyObject(IPlainPackageCopyJobSource, job_source))
201202
203 def test_getErrorRecipients_requester(self):
204 # The job requester is the recipient.
205 job = self.makeJob()
206 email = format_address_for_person(job.requester)
207 self.assertEqual([email], job.getErrorRecipients())
208
202 def test_create(self):209 def test_create(self):
203 # A PackageCopyJob can be created and stores its arguments.210 # A PackageCopyJob can be created and stores its arguments.
204 distroseries = self.factory.makeDistroSeries()211 distroseries = self.factory.makeDistroSeries()
@@ -325,7 +332,9 @@
325 # exception and posts a DistroSeriesDifferenceComment with the332 # exception and posts a DistroSeriesDifferenceComment with the
326 # failure message.333 # failure message.
327 dsd = self.factory.makeDistroSeriesDifference()334 dsd = self.factory.makeDistroSeriesDifference()
328 self.factory.makeArchive(distribution=dsd.derived_series.distribution)335 self.factory.makeArchive(
336 distribution=dsd.derived_series.distribution,
337 purpose=ArchivePurpose.PRIMARY)
329 job = self.makeJob(dsd)338 job = self.makeJob(dsd)
330 removeSecurityProxy(job).attemptCopy = FakeMethod(339 removeSecurityProxy(job).attemptCopy = FakeMethod(
331 failure=CannotCopy("Server meltdown"))340 failure=CannotCopy("Server meltdown"))
@@ -357,7 +366,7 @@
357 naked_job = removeSecurityProxy(job)366 naked_job = removeSecurityProxy(job)
358 naked_job.reportFailure = FakeMethod()367 naked_job.reportFailure = FakeMethod()
359368
360 job.run()369 self.assertRaises(CannotCopy, job.run)
361370
362 self.assertEqual(1, naked_job.reportFailure.call_count)371 self.assertEqual(1, naked_job.reportFailure.call_count)
363372
@@ -397,13 +406,13 @@
397 naked_job = removeSecurityProxy(job)406 naked_job = removeSecurityProxy(job)
398 naked_job.reportFailure = FakeMethod()407 naked_job.reportFailure = FakeMethod()
399408
400 job.run()409 self.assertRaises(CannotCopy, job.run)
401410
402 self.assertEqual(1, naked_job.reportFailure.call_count)411 self.assertEqual(1, naked_job.reportFailure.call_count)
403412
404 def test_target_ppa_message(self):413 def test_target_ppa_message(self):
405 # When copying to a PPA archive the error message is stored in the414 # When copying to a PPA archive the error message is stored in the
406 # job's metadatas.415 # job's metadata and the job fails.
407 distroseries = self.factory.makeDistroSeries()416 distroseries = self.factory.makeDistroSeries()
408 package = self.factory.makeSourcePackageName()417 package = self.factory.makeSourcePackageName()
409 archive1 = self.factory.makeArchive(distroseries.distribution)418 archive1 = self.factory.makeArchive(distroseries.distribution)
@@ -415,7 +424,8 @@
415 include_binaries=False, package_version='1.0',424 include_binaries=False, package_version='1.0',
416 requester=self.factory.makePerson())425 requester=self.factory.makePerson())
417 transaction.commit()426 transaction.commit()
418 job.run()427 self.runJob(job)
428 self.assertEqual(JobStatus.FAILED, job.status)
419429
420 self.assertEqual(430 self.assertEqual(
421 "Destination pocket must be 'release' for a PPA.",431 "Destination pocket must be 'release' for a PPA.",
@@ -1033,7 +1043,7 @@
10331043
1034 # Now put the same named package in the target archive at the1044 # Now put the same named package in the target archive at the
1035 # oldest version in the changelog.1045 # oldest version in the changelog.
1036 target_source_pub = self.publisher.getPubSource(1046 self.publisher.getPubSource(
1037 distroseries=self.distroseries, sourcename="libc",1047 distroseries=self.distroseries, sourcename="libc",
1038 version="2.8-0", status=PackagePublishingStatus.PUBLISHED,1048 version="2.8-0", status=PackagePublishingStatus.PUBLISHED,
1039 archive=target_archive)1049 archive=target_archive)