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
1=== modified file 'lib/lp/soyuz/interfaces/packagecopyjob.py'
2--- lib/lp/soyuz/interfaces/packagecopyjob.py 2012-06-19 23:49:20 +0000
3+++ lib/lp/soyuz/interfaces/packagecopyjob.py 2012-07-12 09:12:22 +0000
4@@ -228,3 +228,9 @@
5 copy_policy = Choice(
6 title=_("Applicable copy policy"),
7 values=PackageCopyPolicy, required=True, readonly=True)
8+
9+ def getOperationDescription():
10+ """Return a description of the copy operation."""
11+
12+ def getErrorRecipients():
13+ """Return a list of email-ids to notify about copy errors."""
14
15=== modified file 'lib/lp/soyuz/model/packagecopyjob.py'
16--- lib/lp/soyuz/model/packagecopyjob.py 2012-06-29 08:40:05 +0000
17+++ lib/lp/soyuz/model/packagecopyjob.py 2012-07-12 09:12:22 +0000
18@@ -55,6 +55,7 @@
19 Job,
20 )
21 from lp.services.job.runner import BaseRunnableJob
22+from lp.services.mail.sendmail import format_address_for_person
23 from lp.soyuz.adapters.overrides import (
24 FromExistingOverridePolicy,
25 SourceOverride,
26@@ -224,6 +225,14 @@
27 ])
28 return vars
29
30+ def getOperationDescription(self):
31+ """See `IPlainPackageCopyJob`."""
32+ return "copying a package"
33+
34+ def getErrorRecipients(self):
35+ """See `IPlainPackageCopyJob`."""
36+ return [format_address_for_person(self.requester)]
37+
38 @property
39 def copy_policy(self):
40 """See `PlainPackageCopyJob`."""
41@@ -243,6 +252,7 @@
42 class_job_type = PackageCopyJobType.PLAIN
43 classProvides(IPlainPackageCopyJobSource)
44 config = config.IPlainPackageCopyJobSource
45+ user_error_types = (CannotCopy,)
46
47 @classmethod
48 def _makeMetadata(cls, target_pocket, package_version,
49@@ -494,6 +504,9 @@
50 try:
51 self.attemptCopy()
52 except CannotCopy as e:
53+ # Remember the target archive purpose, as otherwise aborting the
54+ # transaction will forget it.
55+ target_archive_purpose = self.target_archive.purpose
56 logger = logging.getLogger()
57 logger.info("Job:\n%s\nraised CannotCopy:\n%s" % (self, e))
58 self.abort() # Abort the txn.
59@@ -503,9 +516,18 @@
60 # else it will sit in ACCEPTED forever.
61 self._rejectPackageUpload()
62
63- # Rely on the job runner to do the final commit. Note that
64- # we're not raising any exceptions here, failure of a copy is
65- # not a failure of the job.
66+ if target_archive_purpose == ArchivePurpose.PPA:
67+ # If copying to a PPA, commit the failure and re-raise the
68+ # exception. We turn a copy failure into a job failure in
69+ # order that it can show up in the UI.
70+ transaction.commit()
71+ raise
72+ else:
73+ # Otherwise, rely on the job runner to do the final commit,
74+ # and do not consider a failure of a copy to be a failure of
75+ # the job. We will normally have a DistroSeriesDifference
76+ # in this case.
77+ pass
78 except SuspendJobException:
79 raise
80 except:
81
82=== modified file 'lib/lp/soyuz/tests/test_packagecopyjob.py'
83--- lib/lp/soyuz/tests/test_packagecopyjob.py 2012-07-01 17:19:29 +0000
84+++ lib/lp/soyuz/tests/test_packagecopyjob.py 2012-07-12 09:12:22 +0000
85@@ -29,6 +29,7 @@
86 block_on_job,
87 pop_remote_notifications,
88 )
89+from lp.services.mail.sendmail import format_address_for_person
90 from lp.services.webapp.testing import verifyObject
91 from lp.soyuz.adapters.overrides import SourceOverride
92 from lp.soyuz.enums import (
93@@ -199,6 +200,12 @@
94 job_source = getUtility(IPlainPackageCopyJobSource)
95 self.assertTrue(verifyObject(IPlainPackageCopyJobSource, job_source))
96
97+ def test_getErrorRecipients_requester(self):
98+ # The job requester is the recipient.
99+ job = self.makeJob()
100+ email = format_address_for_person(job.requester)
101+ self.assertEqual([email], job.getErrorRecipients())
102+
103 def test_create(self):
104 # A PackageCopyJob can be created and stores its arguments.
105 distroseries = self.factory.makeDistroSeries()
106@@ -325,7 +332,9 @@
107 # exception and posts a DistroSeriesDifferenceComment with the
108 # failure message.
109 dsd = self.factory.makeDistroSeriesDifference()
110- self.factory.makeArchive(distribution=dsd.derived_series.distribution)
111+ self.factory.makeArchive(
112+ distribution=dsd.derived_series.distribution,
113+ purpose=ArchivePurpose.PRIMARY)
114 job = self.makeJob(dsd)
115 removeSecurityProxy(job).attemptCopy = FakeMethod(
116 failure=CannotCopy("Server meltdown"))
117@@ -357,7 +366,7 @@
118 naked_job = removeSecurityProxy(job)
119 naked_job.reportFailure = FakeMethod()
120
121- job.run()
122+ self.assertRaises(CannotCopy, job.run)
123
124 self.assertEqual(1, naked_job.reportFailure.call_count)
125
126@@ -397,13 +406,13 @@
127 naked_job = removeSecurityProxy(job)
128 naked_job.reportFailure = FakeMethod()
129
130- job.run()
131+ self.assertRaises(CannotCopy, job.run)
132
133 self.assertEqual(1, naked_job.reportFailure.call_count)
134
135 def test_target_ppa_message(self):
136 # When copying to a PPA archive the error message is stored in the
137- # job's metadatas.
138+ # job's metadata and the job fails.
139 distroseries = self.factory.makeDistroSeries()
140 package = self.factory.makeSourcePackageName()
141 archive1 = self.factory.makeArchive(distroseries.distribution)
142@@ -415,7 +424,8 @@
143 include_binaries=False, package_version='1.0',
144 requester=self.factory.makePerson())
145 transaction.commit()
146- job.run()
147+ self.runJob(job)
148+ self.assertEqual(JobStatus.FAILED, job.status)
149
150 self.assertEqual(
151 "Destination pocket must be 'release' for a PPA.",
152@@ -1033,7 +1043,7 @@
153
154 # Now put the same named package in the target archive at the
155 # oldest version in the changelog.
156- target_source_pub = self.publisher.getPubSource(
157+ self.publisher.getPubSource(
158 distroseries=self.distroseries, sourcename="libc",
159 version="2.8-0", status=PackagePublishingStatus.PUBLISHED,
160 archive=target_archive)