Merge lp:~cjwatson/launchpad/copy-explicit-pocket into lp:launchpad

Proposed by Colin Watson
Status: Merged
Approved by: Curtis Hovey
Approved revision: no longer in the source branch.
Merged at revision: 16183
Proposed branch: lp:~cjwatson/launchpad/copy-explicit-pocket
Merge into: lp:launchpad
Diff against target: 352 lines (+130/-27)
6 files modified
lib/lp/soyuz/interfaces/archive.py (+12/-3)
lib/lp/soyuz/interfaces/packagecopyjob.py (+19/-1)
lib/lp/soyuz/model/archive.py (+14/-5)
lib/lp/soyuz/model/packagecopyjob.py (+43/-18)
lib/lp/soyuz/tests/test_archive.py (+26/-0)
lib/lp/soyuz/tests/test_packagecopyjob.py (+16/-0)
To merge this branch: bzr merge lp:~cjwatson/launchpad/copy-explicit-pocket
Reviewer Review Type Date Requested Status
Curtis Hovey (community) code Approve
Review via email: mp+131002@code.launchpad.net

Commit message

Allow specifying source distroseries and pocket in Archive.copyPackage and in PackageCopyJob metadata.

Description of the change

== Summary ==

Bug 1070324: Incremental copying of binaries doesn't work. Fixing this would save us a lot of (variously) manual recovery work and careful attention to avoid manual recovery work, particularly now that we're about to start automatically copying things from PROPOSED to RELEASE.

== Proposed fix ==

Allow optionally passing a source distroseries and pocket to Archive.copyPackage, and pass it all the way down the chain. I didn't bother with Archive.copyPackages as I don't think we need this sort of fine-grained control there, although most of the pieces are there if somebody wants to do that later for some reason.

The underlying copy script workflow is tested in lib/lp/soyuz/scripts/tests/test_copypackage.py:TestCopyBuildRecords.test_incremental_binary_copies.

== Implementation details ==

I moved PCJ's Archive.getPublishedSources call into its own method to make it more conveniently testable.

== LOC Rationale ==

+103. I have 5754 lines of credit at the moment, and I think this is worth it to avoid occasional manual recovery work.

== Tests ==

bin/test -vvct lp.soyuz.tests.test_archive.TestCopyPackage -t lp.soyuz.tests.test_packagecopyjob

== Demo and Q/A ==

A lack of dogfood/staging builders makes it hard to QA anything involving real builds, never mind for multiple architectures; but given the tests and that this is a new optional facility it's probably reasonable to just check that copies without from_series/from_pocket still work, along with copies with from_series/from_pocket matching the SPPH to be copied.

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/soyuz/interfaces/archive.py'
2--- lib/lp/soyuz/interfaces/archive.py 2012-09-21 07:02:26 +0000
3+++ lib/lp/soyuz/interfaces/archive.py 2012-10-23 13:31:46 +0000
4@@ -1318,8 +1318,9 @@
5 version=TextLine(title=_("Version")),
6 from_archive=Reference(schema=Interface),
7 # Really IArchive, see below
8- to_pocket=TextLine(title=_("Pocket name")),
9- to_series=TextLine(title=_("Distroseries name"), required=False),
10+ to_pocket=TextLine(title=_("Target pocket name")),
11+ to_series=TextLine(
12+ title=_("Target distroseries name"), required=False),
13 include_binaries=Bool(
14 title=_("Include Binaries"),
15 description=_("Whether or not to copy binaries already built for"
16@@ -1335,12 +1336,16 @@
17 description=_("Automatically approve this copy (queue admins "
18 "only)."),
19 required=False),
20+ from_pocket=TextLine(title=_("Source pocket name"), required=False),
21+ from_series=TextLine(
22+ title=_("Source distroseries name"), required=False),
23 )
24 @export_write_operation()
25 @operation_for_version('devel')
26 def copyPackage(source_name, version, from_archive, to_pocket,
27 person, to_series=None, include_binaries=False,
28- sponsored=None, unembargo=False, auto_approve=False):
29+ sponsored=None, unembargo=False, auto_approve=False,
30+ from_pocket=None, from_series=None):
31 """Copy a single named source into this archive.
32
33 Asynchronously copy a specific version of a named source to the
34@@ -1367,6 +1372,10 @@
35 :param auto_approve: if True and the `IPerson` requesting the sync
36 has queue admin permissions on the target, accept the copy
37 immediately rather than setting it to unapproved.
38+ :param from_pocket: the source pocket (as a string). If omitted,
39+ copy from any pocket with a matching version.
40+ :param from_series: the source distroseries (as a string). If
41+ omitted, copy from any series with a matching version.
42
43 :raises NoSuchSourcePackageName: if the source name is invalid
44 :raises PocketNotFound: if the pocket name is invalid
45
46=== modified file 'lib/lp/soyuz/interfaces/packagecopyjob.py'
47--- lib/lp/soyuz/interfaces/packagecopyjob.py 2012-07-30 16:48:37 +0000
48+++ lib/lp/soyuz/interfaces/packagecopyjob.py 2012-10-23 13:31:46 +0000
49@@ -128,7 +128,8 @@
50 target_archive, target_distroseries, target_pocket,
51 include_binaries=False, package_version=None,
52 copy_policy=PackageCopyPolicy.INSECURE, requester=None,
53- sponsored=None, unembargo=False, auto_approve=False):
54+ sponsored=None, unembargo=False, auto_approve=False,
55+ source_distroseries=None, source_pocket=None):
56 """Create a new `IPlainPackageCopyJob`.
57
58 :param package_name: The name of the source package to copy.
59@@ -150,6 +151,12 @@
60 :param auto_approve: if True and the user requesting the sync has
61 queue admin permissions on the target, accept the copy
62 immediately rather than setting it to unapproved.
63+ :param source_distroseries: The `IDistroSeries` from which to copy
64+ the packages. If omitted, copy from any series with a matching
65+ version.
66+ :param source_pocket: The pocket from which to copy the packages.
67+ Must be a member of `PackagePublishingPocket`. If omitted, copy
68+ from any pocket with a matching version.
69 """
70
71 def createMultiple(target_distroseries, copy_tasks, requester,
72@@ -227,6 +234,14 @@
73 title=_("Automatic approval"),
74 required=False, readonly=True)
75
76+ source_distroseries = Reference(
77+ schema=IDistroSeries, title=_('Source DistroSeries.'),
78+ required=False, readonly=True)
79+
80+ source_pocket = Int(
81+ title=_("Source package publishing pocket"), required=False,
82+ readonly=True)
83+
84 def addSourceOverride(override):
85 """Add an `ISourceOverride` to the metadata."""
86
87@@ -236,6 +251,9 @@
88 def getSourceOverride():
89 """Get an `ISourceOverride` from the metadata."""
90
91+ def findSourcePublication():
92+ """Find the appropriate origin `ISourcePackagePublishingHistory`."""
93+
94 copy_policy = Choice(
95 title=_("Applicable copy policy"),
96 values=PackageCopyPolicy, required=True, readonly=True)
97
98=== modified file 'lib/lp/soyuz/model/archive.py'
99--- lib/lp/soyuz/model/archive.py 2012-10-19 13:11:51 +0000
100+++ lib/lp/soyuz/model/archive.py 2012-10-23 13:31:46 +0000
101@@ -1639,13 +1639,15 @@
102 sources, to_pocket, to_series, include_binaries,
103 person=person)
104
105- def _validateAndFindSource(self, from_archive, source_name, version):
106+ def _validateAndFindSource(self, from_archive, source_name, version,
107+ from_series=None, from_pocket=None):
108 # Check to see if the source package exists, and raise a useful error
109 # if it doesn't.
110 getUtility(ISourcePackageNameSet)[source_name]
111 # Find and validate the source package version required.
112 source = from_archive.getPublishedSources(
113- name=source_name, version=version, exact_match=True).first()
114+ name=source_name, version=version, exact_match=True,
115+ distroseries=from_series, pocket=from_pocket).first()
116 if source is None:
117 raise CannotCopy(
118 "%s is not published in %s." %
119@@ -1664,15 +1666,21 @@
120
121 def copyPackage(self, source_name, version, from_archive, to_pocket,
122 person, to_series=None, include_binaries=False,
123- sponsored=None, unembargo=False, auto_approve=False):
124+ sponsored=None, unembargo=False, auto_approve=False,
125+ from_pocket=None, from_series=None):
126 """See `IArchive`."""
127 # Asynchronously copy a package using the job system.
128 pocket = self._text_to_pocket(to_pocket)
129 series = self._text_to_series(to_series)
130+ if from_pocket:
131+ from_pocket = self._text_to_pocket(from_pocket)
132+ if from_series:
133+ from_series = self._text_to_series(from_series)
134 # Upload permission checks, this will raise CannotCopy as
135 # necessary.
136 source = self._validateAndFindSource(
137- from_archive, source_name, version)
138+ from_archive, source_name, version, from_series=from_series,
139+ from_pocket=from_pocket)
140 if series is None:
141 series = source.distroseries
142 check_copy_permissions(person, self, series, pocket, [source])
143@@ -1685,7 +1693,8 @@
144 package_version=version, include_binaries=include_binaries,
145 copy_policy=PackageCopyPolicy.INSECURE, requester=person,
146 sponsored=sponsored, unembargo=unembargo,
147- auto_approve=auto_approve)
148+ auto_approve=auto_approve, source_distroseries=from_series,
149+ source_pocket=from_pocket)
150
151 def copyPackages(self, source_names, from_archive, to_pocket,
152 person, to_series=None, from_series=None,
153
154=== modified file 'lib/lp/soyuz/model/packagecopyjob.py'
155--- lib/lp/soyuz/model/packagecopyjob.py 2012-10-17 16:05:07 +0000
156+++ lib/lp/soyuz/model/packagecopyjob.py 2012-10-23 13:31:46 +0000
157@@ -270,19 +270,19 @@
158 @classmethod
159 def _makeMetadata(cls, target_pocket, package_version,
160 include_binaries, sponsored=None, unembargo=False,
161- auto_approve=False):
162+ auto_approve=False, source_distroseries=None,
163+ source_pocket=None):
164 """Produce a metadata dict for this job."""
165- if sponsored:
166- sponsored_name = sponsored.name
167- else:
168- sponsored_name = None
169 return {
170 'target_pocket': target_pocket.value,
171 'package_version': package_version,
172 'include_binaries': bool(include_binaries),
173- 'sponsored': sponsored_name,
174+ 'sponsored': sponsored.name if sponsored else None,
175 'unembargo': unembargo,
176 'auto_approve': auto_approve,
177+ 'source_distroseries':
178+ source_distroseries.name if source_distroseries else None,
179+ 'source_pocket': source_pocket.value if source_pocket else None,
180 }
181
182 @classmethod
183@@ -290,13 +290,14 @@
184 target_archive, target_distroseries, target_pocket,
185 include_binaries=False, package_version=None,
186 copy_policy=PackageCopyPolicy.INSECURE, requester=None,
187- sponsored=None, unembargo=False, auto_approve=False):
188+ sponsored=None, unembargo=False, auto_approve=False,
189+ source_distroseries=None, source_pocket=None):
190 """See `IPlainPackageCopyJobSource`."""
191 assert package_version is not None, "No package version specified."
192 assert requester is not None, "No requester specified."
193 metadata = cls._makeMetadata(
194 target_pocket, package_version, include_binaries, sponsored,
195- unembargo, auto_approve)
196+ unembargo, auto_approve, source_distroseries, source_pocket)
197 job = PackageCopyJob(
198 job_type=cls.class_job_type,
199 source_archive=source_archive,
200@@ -435,6 +436,20 @@
201 def auto_approve(self):
202 return self.metadata.get('auto_approve', False)
203
204+ @property
205+ def source_distroseries(self):
206+ name = self.metadata.get('source_distroseries')
207+ if name is None:
208+ return None
209+ return self.source_archive.distribution[name]
210+
211+ @property
212+ def source_pocket(self):
213+ name = self.metadata.get('source_pocket')
214+ if name is None:
215+ return None
216+ return PackagePublishingPocket.items[name]
217+
218 def _createPackageUpload(self, unapproved=False):
219 pu = self.target_distroseries.createQueueEntry(
220 pocket=self.target_pocket, archive=self.target_archive,
221@@ -472,6 +487,18 @@
222
223 return SourceOverride(source_package_name, component, section)
224
225+ def findSourcePublication(self):
226+ """Find the appropriate origin `ISourcePackagePublishingHistory`."""
227+ name = self.package_name
228+ version = self.package_version
229+ source_package = self.source_archive.getPublishedSources(
230+ name=name, version=version, exact_match=True,
231+ distroseries=self.source_distroseries,
232+ pocket=self.source_pocket).first()
233+ if source_package is None:
234+ raise CannotCopy("Package %r %r not found." % (name, version))
235+ return source_package
236+
237 def _checkPolicies(self, source_name, source_component=None,
238 auto_approve=False):
239 # This helper will only return if it's safe to carry on with the
240@@ -589,13 +616,7 @@
241 raise CannotCopy(
242 "Destination pocket must be 'release' for a PPA.")
243
244- name = self.package_name
245- version = self.package_version
246- source_package = self.source_archive.getPublishedSources(
247- name=name, version=version, exact_match=True).first()
248- if source_package is None:
249- raise CannotCopy("Package %r %r not found." % (name, version))
250- source_name = getUtility(ISourcePackageNameSet)[name]
251+ source_package = self.findSourcePublication()
252
253 # If there's a PackageUpload associated with this job then this
254 # job has just been released by an archive admin from the queue.
255@@ -604,13 +625,14 @@
256 pu = getUtility(IPackageUploadSet).getByPackageCopyJobIDs(
257 [self.context.id]).any()
258 if pu is None:
259+ source_name = getUtility(ISourcePackageNameSet)[self.package_name]
260 self._checkPolicies(
261 source_name, source_package.sourcepackagerelease.component,
262 self.auto_approve)
263
264 # The package is free to go right in, so just copy it now.
265 ancestry = self.target_archive.getPublishedSources(
266- name=name, distroseries=self.target_distroseries,
267+ name=self.package_name, distroseries=self.target_distroseries,
268 pocket=self.target_pocket, exact_match=True)
269 override = self.getSourceOverride()
270 copy_policy = self.getPolicyImplementation()
271@@ -698,12 +720,15 @@
272 " from %s/%s" % (
273 self.source_archive.distribution.name,
274 self.source_archive.name))
275+ if self.source_pocket is not None:
276+ parts.append(", %s pocket," % self.source_pocket.name)
277+ if self.source_distroseries is not None:
278+ parts.append(" in %s" % self.source_distroseries)
279 parts.append(
280 " to %s/%s" % (
281 self.target_archive.distribution.name,
282 self.target_archive.name))
283- parts.append(
284- ", %s pocket," % self.target_pocket.name)
285+ parts.append(", %s pocket," % self.target_pocket.name)
286 if self.target_distroseries is not None:
287 parts.append(" in %s" % self.target_distroseries)
288 if self.include_binaries:
289
290=== modified file 'lib/lp/soyuz/tests/test_archive.py'
291--- lib/lp/soyuz/tests/test_archive.py 2012-10-19 13:11:51 +0000
292+++ lib/lp/soyuz/tests/test_archive.py 2012-10-23 13:31:46 +0000
293@@ -2374,6 +2374,32 @@
294 source_name, version, target_archive, to_pocket.name,
295 target_archive.owner)
296
297+ def test_copyPackage_with_source_series_and_pocket(self):
298+ # The from_series and from_pocket parameters cause copyPackage to
299+ # select a matching source publication.
300+ (source, source_archive, source_name, target_archive, to_pocket,
301+ to_series, version) = self._setup_copy_data()
302+ other_series = self.factory.makeDistroSeries(
303+ distribution=source_archive.distribution,
304+ status=SeriesStatus.DEVELOPMENT)
305+ with person_logged_in(source_archive.owner):
306+ source.copyTo(
307+ other_series, PackagePublishingPocket.UPDATES, source_archive)
308+ source.requestDeletion(source_archive.owner)
309+ with person_logged_in(target_archive.owner):
310+ target_archive.copyPackage(
311+ source_name, version, source_archive, to_pocket.name,
312+ include_binaries=False, person=target_archive.owner,
313+ from_series=source.distroseries.name,
314+ from_pocket=source.pocket.name)
315+
316+ # There should be one copy job, with the source distroseries and
317+ # pocket set.
318+ job_source = getUtility(IPlainPackageCopyJobSource)
319+ copy_job = job_source.getActiveJobs(target_archive).one()
320+ self.assertEqual(source.distroseries, copy_job.source_distroseries)
321+ self.assertEqual(source.pocket, copy_job.source_pocket)
322+
323 def test_copyPackages_with_single_package(self):
324 (source, source_archive, source_name, target_archive, to_pocket,
325 to_series, version) = self._setup_copy_data()
326
327=== modified file 'lib/lp/soyuz/tests/test_packagecopyjob.py'
328--- lib/lp/soyuz/tests/test_packagecopyjob.py 2012-10-17 12:33:21 +0000
329+++ lib/lp/soyuz/tests/test_packagecopyjob.py 2012-10-23 13:31:46 +0000
330@@ -1478,6 +1478,22 @@
331
332 self.assertEqual(override, pcj.getSourceOverride())
333
334+ def test_findSourcePublication_with_source_series_and_pocket(self):
335+ # The source_distroseries and source_pocket parameters cause
336+ # findSourcePublication to select a matching source publication.
337+ spph = self.publisher.getPubSource()
338+ other_series = self.factory.makeDistroSeries(
339+ distribution=spph.distroseries.distribution,
340+ status=SeriesStatus.DEVELOPMENT)
341+ spph.copyTo(
342+ other_series, PackagePublishingPocket.PROPOSED, spph.archive)
343+ spph.requestDeletion(spph.archive.owner)
344+ job = self.createCopyJobForSPPH(
345+ spph, spph.archive, spph.archive,
346+ target_pocket=PackagePublishingPocket.UPDATES,
347+ source_distroseries=spph.distroseries, source_pocket=spph.pocket)
348+ self.assertEqual(spph, job.findSourcePublication())
349+
350 def test_getPolicyImplementation_returns_policy(self):
351 # getPolicyImplementation returns the ICopyPolicy that was
352 # chosen for the job.