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
=== modified file 'lib/lp/soyuz/interfaces/archive.py'
--- lib/lp/soyuz/interfaces/archive.py 2012-09-21 07:02:26 +0000
+++ lib/lp/soyuz/interfaces/archive.py 2012-10-23 13:31:46 +0000
@@ -1318,8 +1318,9 @@
1318 version=TextLine(title=_("Version")),1318 version=TextLine(title=_("Version")),
1319 from_archive=Reference(schema=Interface),1319 from_archive=Reference(schema=Interface),
1320 # Really IArchive, see below1320 # Really IArchive, see below
1321 to_pocket=TextLine(title=_("Pocket name")),1321 to_pocket=TextLine(title=_("Target pocket name")),
1322 to_series=TextLine(title=_("Distroseries name"), required=False),1322 to_series=TextLine(
1323 title=_("Target distroseries name"), required=False),
1323 include_binaries=Bool(1324 include_binaries=Bool(
1324 title=_("Include Binaries"),1325 title=_("Include Binaries"),
1325 description=_("Whether or not to copy binaries already built for"1326 description=_("Whether or not to copy binaries already built for"
@@ -1335,12 +1336,16 @@
1335 description=_("Automatically approve this copy (queue admins "1336 description=_("Automatically approve this copy (queue admins "
1336 "only)."),1337 "only)."),
1337 required=False),1338 required=False),
1339 from_pocket=TextLine(title=_("Source pocket name"), required=False),
1340 from_series=TextLine(
1341 title=_("Source distroseries name"), required=False),
1338 )1342 )
1339 @export_write_operation()1343 @export_write_operation()
1340 @operation_for_version('devel')1344 @operation_for_version('devel')
1341 def copyPackage(source_name, version, from_archive, to_pocket,1345 def copyPackage(source_name, version, from_archive, to_pocket,
1342 person, to_series=None, include_binaries=False,1346 person, to_series=None, include_binaries=False,
1343 sponsored=None, unembargo=False, auto_approve=False):1347 sponsored=None, unembargo=False, auto_approve=False,
1348 from_pocket=None, from_series=None):
1344 """Copy a single named source into this archive.1349 """Copy a single named source into this archive.
13451350
1346 Asynchronously copy a specific version of a named source to the1351 Asynchronously copy a specific version of a named source to the
@@ -1367,6 +1372,10 @@
1367 :param auto_approve: if True and the `IPerson` requesting the sync1372 :param auto_approve: if True and the `IPerson` requesting the sync
1368 has queue admin permissions on the target, accept the copy1373 has queue admin permissions on the target, accept the copy
1369 immediately rather than setting it to unapproved.1374 immediately rather than setting it to unapproved.
1375 :param from_pocket: the source pocket (as a string). If omitted,
1376 copy from any pocket with a matching version.
1377 :param from_series: the source distroseries (as a string). If
1378 omitted, copy from any series with a matching version.
13701379
1371 :raises NoSuchSourcePackageName: if the source name is invalid1380 :raises NoSuchSourcePackageName: if the source name is invalid
1372 :raises PocketNotFound: if the pocket name is invalid1381 :raises PocketNotFound: if the pocket name is invalid
13731382
=== modified file 'lib/lp/soyuz/interfaces/packagecopyjob.py'
--- lib/lp/soyuz/interfaces/packagecopyjob.py 2012-07-30 16:48:37 +0000
+++ lib/lp/soyuz/interfaces/packagecopyjob.py 2012-10-23 13:31:46 +0000
@@ -128,7 +128,8 @@
128 target_archive, target_distroseries, target_pocket,128 target_archive, target_distroseries, target_pocket,
129 include_binaries=False, package_version=None,129 include_binaries=False, package_version=None,
130 copy_policy=PackageCopyPolicy.INSECURE, requester=None,130 copy_policy=PackageCopyPolicy.INSECURE, requester=None,
131 sponsored=None, unembargo=False, auto_approve=False):131 sponsored=None, unembargo=False, auto_approve=False,
132 source_distroseries=None, source_pocket=None):
132 """Create a new `IPlainPackageCopyJob`.133 """Create a new `IPlainPackageCopyJob`.
133134
134 :param package_name: The name of the source package to copy.135 :param package_name: The name of the source package to copy.
@@ -150,6 +151,12 @@
150 :param auto_approve: if True and the user requesting the sync has151 :param auto_approve: if True and the user requesting the sync has
151 queue admin permissions on the target, accept the copy152 queue admin permissions on the target, accept the copy
152 immediately rather than setting it to unapproved.153 immediately rather than setting it to unapproved.
154 :param source_distroseries: The `IDistroSeries` from which to copy
155 the packages. If omitted, copy from any series with a matching
156 version.
157 :param source_pocket: The pocket from which to copy the packages.
158 Must be a member of `PackagePublishingPocket`. If omitted, copy
159 from any pocket with a matching version.
153 """160 """
154161
155 def createMultiple(target_distroseries, copy_tasks, requester,162 def createMultiple(target_distroseries, copy_tasks, requester,
@@ -227,6 +234,14 @@
227 title=_("Automatic approval"),234 title=_("Automatic approval"),
228 required=False, readonly=True)235 required=False, readonly=True)
229236
237 source_distroseries = Reference(
238 schema=IDistroSeries, title=_('Source DistroSeries.'),
239 required=False, readonly=True)
240
241 source_pocket = Int(
242 title=_("Source package publishing pocket"), required=False,
243 readonly=True)
244
230 def addSourceOverride(override):245 def addSourceOverride(override):
231 """Add an `ISourceOverride` to the metadata."""246 """Add an `ISourceOverride` to the metadata."""
232247
@@ -236,6 +251,9 @@
236 def getSourceOverride():251 def getSourceOverride():
237 """Get an `ISourceOverride` from the metadata."""252 """Get an `ISourceOverride` from the metadata."""
238253
254 def findSourcePublication():
255 """Find the appropriate origin `ISourcePackagePublishingHistory`."""
256
239 copy_policy = Choice(257 copy_policy = Choice(
240 title=_("Applicable copy policy"),258 title=_("Applicable copy policy"),
241 values=PackageCopyPolicy, required=True, readonly=True)259 values=PackageCopyPolicy, required=True, readonly=True)
242260
=== modified file 'lib/lp/soyuz/model/archive.py'
--- lib/lp/soyuz/model/archive.py 2012-10-19 13:11:51 +0000
+++ lib/lp/soyuz/model/archive.py 2012-10-23 13:31:46 +0000
@@ -1639,13 +1639,15 @@
1639 sources, to_pocket, to_series, include_binaries,1639 sources, to_pocket, to_series, include_binaries,
1640 person=person)1640 person=person)
16411641
1642 def _validateAndFindSource(self, from_archive, source_name, version):1642 def _validateAndFindSource(self, from_archive, source_name, version,
1643 from_series=None, from_pocket=None):
1643 # Check to see if the source package exists, and raise a useful error1644 # Check to see if the source package exists, and raise a useful error
1644 # if it doesn't.1645 # if it doesn't.
1645 getUtility(ISourcePackageNameSet)[source_name]1646 getUtility(ISourcePackageNameSet)[source_name]
1646 # Find and validate the source package version required.1647 # Find and validate the source package version required.
1647 source = from_archive.getPublishedSources(1648 source = from_archive.getPublishedSources(
1648 name=source_name, version=version, exact_match=True).first()1649 name=source_name, version=version, exact_match=True,
1650 distroseries=from_series, pocket=from_pocket).first()
1649 if source is None:1651 if source is None:
1650 raise CannotCopy(1652 raise CannotCopy(
1651 "%s is not published in %s." %1653 "%s is not published in %s." %
@@ -1664,15 +1666,21 @@
16641666
1665 def copyPackage(self, source_name, version, from_archive, to_pocket,1667 def copyPackage(self, source_name, version, from_archive, to_pocket,
1666 person, to_series=None, include_binaries=False,1668 person, to_series=None, include_binaries=False,
1667 sponsored=None, unembargo=False, auto_approve=False):1669 sponsored=None, unembargo=False, auto_approve=False,
1670 from_pocket=None, from_series=None):
1668 """See `IArchive`."""1671 """See `IArchive`."""
1669 # Asynchronously copy a package using the job system.1672 # Asynchronously copy a package using the job system.
1670 pocket = self._text_to_pocket(to_pocket)1673 pocket = self._text_to_pocket(to_pocket)
1671 series = self._text_to_series(to_series)1674 series = self._text_to_series(to_series)
1675 if from_pocket:
1676 from_pocket = self._text_to_pocket(from_pocket)
1677 if from_series:
1678 from_series = self._text_to_series(from_series)
1672 # Upload permission checks, this will raise CannotCopy as1679 # Upload permission checks, this will raise CannotCopy as
1673 # necessary.1680 # necessary.
1674 source = self._validateAndFindSource(1681 source = self._validateAndFindSource(
1675 from_archive, source_name, version)1682 from_archive, source_name, version, from_series=from_series,
1683 from_pocket=from_pocket)
1676 if series is None:1684 if series is None:
1677 series = source.distroseries1685 series = source.distroseries
1678 check_copy_permissions(person, self, series, pocket, [source])1686 check_copy_permissions(person, self, series, pocket, [source])
@@ -1685,7 +1693,8 @@
1685 package_version=version, include_binaries=include_binaries,1693 package_version=version, include_binaries=include_binaries,
1686 copy_policy=PackageCopyPolicy.INSECURE, requester=person,1694 copy_policy=PackageCopyPolicy.INSECURE, requester=person,
1687 sponsored=sponsored, unembargo=unembargo,1695 sponsored=sponsored, unembargo=unembargo,
1688 auto_approve=auto_approve)1696 auto_approve=auto_approve, source_distroseries=from_series,
1697 source_pocket=from_pocket)
16891698
1690 def copyPackages(self, source_names, from_archive, to_pocket,1699 def copyPackages(self, source_names, from_archive, to_pocket,
1691 person, to_series=None, from_series=None,1700 person, to_series=None, from_series=None,
16921701
=== modified file 'lib/lp/soyuz/model/packagecopyjob.py'
--- lib/lp/soyuz/model/packagecopyjob.py 2012-10-17 16:05:07 +0000
+++ lib/lp/soyuz/model/packagecopyjob.py 2012-10-23 13:31:46 +0000
@@ -270,19 +270,19 @@
270 @classmethod270 @classmethod
271 def _makeMetadata(cls, target_pocket, package_version,271 def _makeMetadata(cls, target_pocket, package_version,
272 include_binaries, sponsored=None, unembargo=False,272 include_binaries, sponsored=None, unembargo=False,
273 auto_approve=False):273 auto_approve=False, source_distroseries=None,
274 source_pocket=None):
274 """Produce a metadata dict for this job."""275 """Produce a metadata dict for this job."""
275 if sponsored:
276 sponsored_name = sponsored.name
277 else:
278 sponsored_name = None
279 return {276 return {
280 'target_pocket': target_pocket.value,277 'target_pocket': target_pocket.value,
281 'package_version': package_version,278 'package_version': package_version,
282 'include_binaries': bool(include_binaries),279 'include_binaries': bool(include_binaries),
283 'sponsored': sponsored_name,280 'sponsored': sponsored.name if sponsored else None,
284 'unembargo': unembargo,281 'unembargo': unembargo,
285 'auto_approve': auto_approve,282 'auto_approve': auto_approve,
283 'source_distroseries':
284 source_distroseries.name if source_distroseries else None,
285 'source_pocket': source_pocket.value if source_pocket else None,
286 }286 }
287287
288 @classmethod288 @classmethod
@@ -290,13 +290,14 @@
290 target_archive, target_distroseries, target_pocket,290 target_archive, target_distroseries, target_pocket,
291 include_binaries=False, package_version=None,291 include_binaries=False, package_version=None,
292 copy_policy=PackageCopyPolicy.INSECURE, requester=None,292 copy_policy=PackageCopyPolicy.INSECURE, requester=None,
293 sponsored=None, unembargo=False, auto_approve=False):293 sponsored=None, unembargo=False, auto_approve=False,
294 source_distroseries=None, source_pocket=None):
294 """See `IPlainPackageCopyJobSource`."""295 """See `IPlainPackageCopyJobSource`."""
295 assert package_version is not None, "No package version specified."296 assert package_version is not None, "No package version specified."
296 assert requester is not None, "No requester specified."297 assert requester is not None, "No requester specified."
297 metadata = cls._makeMetadata(298 metadata = cls._makeMetadata(
298 target_pocket, package_version, include_binaries, sponsored,299 target_pocket, package_version, include_binaries, sponsored,
299 unembargo, auto_approve)300 unembargo, auto_approve, source_distroseries, source_pocket)
300 job = PackageCopyJob(301 job = PackageCopyJob(
301 job_type=cls.class_job_type,302 job_type=cls.class_job_type,
302 source_archive=source_archive,303 source_archive=source_archive,
@@ -435,6 +436,20 @@
435 def auto_approve(self):436 def auto_approve(self):
436 return self.metadata.get('auto_approve', False)437 return self.metadata.get('auto_approve', False)
437438
439 @property
440 def source_distroseries(self):
441 name = self.metadata.get('source_distroseries')
442 if name is None:
443 return None
444 return self.source_archive.distribution[name]
445
446 @property
447 def source_pocket(self):
448 name = self.metadata.get('source_pocket')
449 if name is None:
450 return None
451 return PackagePublishingPocket.items[name]
452
438 def _createPackageUpload(self, unapproved=False):453 def _createPackageUpload(self, unapproved=False):
439 pu = self.target_distroseries.createQueueEntry(454 pu = self.target_distroseries.createQueueEntry(
440 pocket=self.target_pocket, archive=self.target_archive,455 pocket=self.target_pocket, archive=self.target_archive,
@@ -472,6 +487,18 @@
472487
473 return SourceOverride(source_package_name, component, section)488 return SourceOverride(source_package_name, component, section)
474489
490 def findSourcePublication(self):
491 """Find the appropriate origin `ISourcePackagePublishingHistory`."""
492 name = self.package_name
493 version = self.package_version
494 source_package = self.source_archive.getPublishedSources(
495 name=name, version=version, exact_match=True,
496 distroseries=self.source_distroseries,
497 pocket=self.source_pocket).first()
498 if source_package is None:
499 raise CannotCopy("Package %r %r not found." % (name, version))
500 return source_package
501
475 def _checkPolicies(self, source_name, source_component=None,502 def _checkPolicies(self, source_name, source_component=None,
476 auto_approve=False):503 auto_approve=False):
477 # This helper will only return if it's safe to carry on with the504 # This helper will only return if it's safe to carry on with the
@@ -589,13 +616,7 @@
589 raise CannotCopy(616 raise CannotCopy(
590 "Destination pocket must be 'release' for a PPA.")617 "Destination pocket must be 'release' for a PPA.")
591618
592 name = self.package_name619 source_package = self.findSourcePublication()
593 version = self.package_version
594 source_package = self.source_archive.getPublishedSources(
595 name=name, version=version, exact_match=True).first()
596 if source_package is None:
597 raise CannotCopy("Package %r %r not found." % (name, version))
598 source_name = getUtility(ISourcePackageNameSet)[name]
599620
600 # If there's a PackageUpload associated with this job then this621 # If there's a PackageUpload associated with this job then this
601 # job has just been released by an archive admin from the queue.622 # job has just been released by an archive admin from the queue.
@@ -604,13 +625,14 @@
604 pu = getUtility(IPackageUploadSet).getByPackageCopyJobIDs(625 pu = getUtility(IPackageUploadSet).getByPackageCopyJobIDs(
605 [self.context.id]).any()626 [self.context.id]).any()
606 if pu is None:627 if pu is None:
628 source_name = getUtility(ISourcePackageNameSet)[self.package_name]
607 self._checkPolicies(629 self._checkPolicies(
608 source_name, source_package.sourcepackagerelease.component,630 source_name, source_package.sourcepackagerelease.component,
609 self.auto_approve)631 self.auto_approve)
610632
611 # The package is free to go right in, so just copy it now.633 # The package is free to go right in, so just copy it now.
612 ancestry = self.target_archive.getPublishedSources(634 ancestry = self.target_archive.getPublishedSources(
613 name=name, distroseries=self.target_distroseries,635 name=self.package_name, distroseries=self.target_distroseries,
614 pocket=self.target_pocket, exact_match=True)636 pocket=self.target_pocket, exact_match=True)
615 override = self.getSourceOverride()637 override = self.getSourceOverride()
616 copy_policy = self.getPolicyImplementation()638 copy_policy = self.getPolicyImplementation()
@@ -698,12 +720,15 @@
698 " from %s/%s" % (720 " from %s/%s" % (
699 self.source_archive.distribution.name,721 self.source_archive.distribution.name,
700 self.source_archive.name))722 self.source_archive.name))
723 if self.source_pocket is not None:
724 parts.append(", %s pocket," % self.source_pocket.name)
725 if self.source_distroseries is not None:
726 parts.append(" in %s" % self.source_distroseries)
701 parts.append(727 parts.append(
702 " to %s/%s" % (728 " to %s/%s" % (
703 self.target_archive.distribution.name,729 self.target_archive.distribution.name,
704 self.target_archive.name))730 self.target_archive.name))
705 parts.append(731 parts.append(", %s pocket," % self.target_pocket.name)
706 ", %s pocket," % self.target_pocket.name)
707 if self.target_distroseries is not None:732 if self.target_distroseries is not None:
708 parts.append(" in %s" % self.target_distroseries)733 parts.append(" in %s" % self.target_distroseries)
709 if self.include_binaries:734 if self.include_binaries:
710735
=== modified file 'lib/lp/soyuz/tests/test_archive.py'
--- lib/lp/soyuz/tests/test_archive.py 2012-10-19 13:11:51 +0000
+++ lib/lp/soyuz/tests/test_archive.py 2012-10-23 13:31:46 +0000
@@ -2374,6 +2374,32 @@
2374 source_name, version, target_archive, to_pocket.name,2374 source_name, version, target_archive, to_pocket.name,
2375 target_archive.owner)2375 target_archive.owner)
23762376
2377 def test_copyPackage_with_source_series_and_pocket(self):
2378 # The from_series and from_pocket parameters cause copyPackage to
2379 # select a matching source publication.
2380 (source, source_archive, source_name, target_archive, to_pocket,
2381 to_series, version) = self._setup_copy_data()
2382 other_series = self.factory.makeDistroSeries(
2383 distribution=source_archive.distribution,
2384 status=SeriesStatus.DEVELOPMENT)
2385 with person_logged_in(source_archive.owner):
2386 source.copyTo(
2387 other_series, PackagePublishingPocket.UPDATES, source_archive)
2388 source.requestDeletion(source_archive.owner)
2389 with person_logged_in(target_archive.owner):
2390 target_archive.copyPackage(
2391 source_name, version, source_archive, to_pocket.name,
2392 include_binaries=False, person=target_archive.owner,
2393 from_series=source.distroseries.name,
2394 from_pocket=source.pocket.name)
2395
2396 # There should be one copy job, with the source distroseries and
2397 # pocket set.
2398 job_source = getUtility(IPlainPackageCopyJobSource)
2399 copy_job = job_source.getActiveJobs(target_archive).one()
2400 self.assertEqual(source.distroseries, copy_job.source_distroseries)
2401 self.assertEqual(source.pocket, copy_job.source_pocket)
2402
2377 def test_copyPackages_with_single_package(self):2403 def test_copyPackages_with_single_package(self):
2378 (source, source_archive, source_name, target_archive, to_pocket,2404 (source, source_archive, source_name, target_archive, to_pocket,
2379 to_series, version) = self._setup_copy_data()2405 to_series, version) = self._setup_copy_data()
23802406
=== modified file 'lib/lp/soyuz/tests/test_packagecopyjob.py'
--- lib/lp/soyuz/tests/test_packagecopyjob.py 2012-10-17 12:33:21 +0000
+++ lib/lp/soyuz/tests/test_packagecopyjob.py 2012-10-23 13:31:46 +0000
@@ -1478,6 +1478,22 @@
14781478
1479 self.assertEqual(override, pcj.getSourceOverride())1479 self.assertEqual(override, pcj.getSourceOverride())
14801480
1481 def test_findSourcePublication_with_source_series_and_pocket(self):
1482 # The source_distroseries and source_pocket parameters cause
1483 # findSourcePublication to select a matching source publication.
1484 spph = self.publisher.getPubSource()
1485 other_series = self.factory.makeDistroSeries(
1486 distribution=spph.distroseries.distribution,
1487 status=SeriesStatus.DEVELOPMENT)
1488 spph.copyTo(
1489 other_series, PackagePublishingPocket.PROPOSED, spph.archive)
1490 spph.requestDeletion(spph.archive.owner)
1491 job = self.createCopyJobForSPPH(
1492 spph, spph.archive, spph.archive,
1493 target_pocket=PackagePublishingPocket.UPDATES,
1494 source_distroseries=spph.distroseries, source_pocket=spph.pocket)
1495 self.assertEqual(spph, job.findSourcePublication())
1496
1481 def test_getPolicyImplementation_returns_policy(self):1497 def test_getPolicyImplementation_returns_policy(self):
1482 # getPolicyImplementation returns the ICopyPolicy that was1498 # getPolicyImplementation returns the ICopyPolicy that was
1483 # chosen for the job.1499 # chosen for the job.