← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~cjwatson/launchpad/copy-set-phase into lp:launchpad

 

Colin Watson has proposed merging lp:~cjwatson/launchpad/copy-set-phase into lp:launchpad.

Commit message:
Allow setting the phased-update-percentage for binary publications created by copies.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/copy-set-phase/+merge/170775

== Summary ==

Copying packages to the -updates pocket but gradually phasing them in using the phased_update_percentage mechanism effectively requires the PUP to be set to 0 after the copy.  However, doing this with BPPH.changeOverride is problematic: it requires archive owner privileges rather than merely the ability to copy, and it's racy because you have to wait for the async copy to complete before you can change its overrides and you have to make sure you squeeze the override change in before the next publisher run.  It would be better to be able to specify an initial PUP for the copied binaries.  Bug 1192286 has more details.

== Proposed fix ==

Add a phased_update_percentage argument to Archive.copyPackage and propagate it all the way down.  The only fiddly bit is choosing exactly how to pass it through PublishingSet.copyBinaries; I opted for making it a parameter you can tweak when creating an override policy, which the policy then remembers and applies as appropriate.

== LOC Rationale ==

+133.  This is making an existing feature properly usable so I think it's reasonable; and I have a good track record at removing LoC from LP ...

== Tests ==

bin/test -vvct lp.soyuz.adapters.tests.test_overrides -t lp.soyuz.tests.test_archive -t lp.soyuz.tests.test_packagecopyjob -t lp.soyuz.scripts.tests.test_copypackage -t lp.soyuz.tests.test_publishing -t lp.soyuz.tests.test_packageupload

== Demo and Q/A ==

''Explain how to demonstrate the fix and how to carry out QA on it.''

On DF, pick two packages in PROPOSED pockets but not elsewhere and copy them to UPDATES, one with phased_update_percentage=0 and one with that argument unspecified.  Run the publisher.  The first should be published with "Phased-Update-Percentage: 0" in Packages, and the second should have that field missing.
-- 
https://code.launchpad.net/~cjwatson/launchpad/copy-set-phase/+merge/170775
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~cjwatson/launchpad/copy-set-phase into lp:launchpad.
=== modified file 'lib/lp/soyuz/adapters/overrides.py'
--- lib/lp/soyuz/adapters/overrides.py	2013-06-20 05:50:00 +0000
+++ lib/lp/soyuz/adapters/overrides.py	2013-06-21 10:28:33 +0000
@@ -70,6 +70,8 @@
         "The IDistroArchSeries for the publication")
     priority = Attribute(
         "The PackagePublishingPriority that's being overridden")
+    phased_update_percentage = Attribute(
+        "The phased update percentage that's being overridden")
 
 
 class Override:
@@ -109,11 +111,12 @@
     implements(IBinaryOverride)
 
     def __init__(self, binary_package_name, distro_arch_series, component,
-                 section, priority):
+                 section, priority, phased_update_percentage):
         super(BinaryOverride, self).__init__(component, section)
         self.binary_package_name = binary_package_name
         self.distro_arch_series = distro_arch_series
         self.priority = priority
+        self.phased_update_percentage = phased_update_percentage
 
     def __eq__(self, other):
         return (
@@ -121,13 +124,16 @@
             self.distro_arch_series == other.distro_arch_series and
             self.component == other.component and
             self.section == other.section and
-            self.priority == other.priority)
+            self.priority == other.priority and
+            self.phased_update_percentage == other.phased_update_percentage)
 
     def __repr__(self):
         return ("<BinaryOverride at %x component=%r section=%r "
-            "binary_package_name=%r distro_arch_series=%r priority=%r>" %
+            "binary_package_name=%r distro_arch_series=%r priority=%r "
+            "phased_update_percentage=%r>" %
             (id(self), self.component, self.section, self.binary_package_name,
-             self.distro_arch_series, self.priority))
+             self.distro_arch_series, self.priority,
+             self.phased_update_percentage))
 
 
 class IOverridePolicy(Interface):
@@ -140,6 +146,9 @@
     keep the same component and section as their ancestor publications.
     """
 
+    phased_update_percentage = Attribute(
+        "The phased update percentage to apply to binary publications.")
+
     def calculateSourceOverrides(archive, distroseries, pocket, sources,
                                  source_component=None):
         """Calculate source overrides.
@@ -173,6 +182,10 @@
 
     implements(IOverridePolicy)
 
+    def __init__(self, phased_update_percentage=None):
+        super(BaseOverridePolicy, self).__init__()
+        self.phased_update_percentage = phased_update_percentage
+
     def calculateSourceOverrides(self, archive, distroseries, pocket,
                                  sources, source_component=None):
         raise NotImplementedError()
@@ -245,6 +258,8 @@
             for bpn, das in expanded if das is not None]
         if len(candidates) == 0:
             return []
+        # Do not copy phased_update_percentage from existing publications;
+        # it is too context-dependent to copy.
         already_published = DecoratedResultSet(
             store.find(
                 (BinaryPackagePublishingHistory.binarypackagenameID,
@@ -269,7 +284,9 @@
                 None)),
             pre_iter_hook=eager_load)
         return [
-            BinaryOverride(name, das, component, section, priority)
+            BinaryOverride(
+                name, das, component, section, priority,
+                self.phased_update_percentage)
             for name, das, component, section, priority in already_published]
 
 
@@ -324,7 +341,9 @@
         default_component = archive.default_component or getUtility(
             IComponentSet)['universe']
         return [
-            BinaryOverride(binary, das, default_component, None, None)
+            BinaryOverride(
+                binary, das, default_component, None, None,
+                self.phased_update_percentage)
             for binary, das in calculate_target_das(distroseries, binaries)]
 
 

=== modified file 'lib/lp/soyuz/adapters/tests/test_overrides.py'
--- lib/lp/soyuz/adapters/tests/test_overrides.py	2013-05-23 07:06:42 +0000
+++ lib/lp/soyuz/adapters/tests/test_overrides.py	2013-06-21 10:28:33 +0000
@@ -1,4 +1,4 @@
-# Copyright 2011 Canonical Ltd.  This software is licensed under the
+# Copyright 2011-2013 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 """Test generic override policy classes."""
@@ -124,7 +124,7 @@
             BinaryOverride(
                 bpph.binarypackagerelease.binarypackagename,
                 bpph.distroarchseries, bpph.component, bpph.section,
-                bpph.priority)]
+                bpph.priority, None)]
         self.assertEqual(expected, overrides)
 
     def test_binary_overrides_constant_query_count(self):
@@ -203,7 +203,7 @@
         expected = [
             BinaryOverride(
                 bpph.binarypackagerelease.binarypackagename,
-                bpph.distroarchseries, universe, None, None)]
+                bpph.distroarchseries, universe, None, None, None)]
         self.assertEqual(expected, overrides)
 
     def test_ubuntu_override_policy_sources(self):
@@ -259,13 +259,14 @@
             expected.append(
                 BinaryOverride(
                     bpn, distroarchseries, bpph.component, bpph.section,
-                    bpph.priority))
+                    bpph.priority, None))
         for i in xrange(2):
             distroarchseries = self.factory.makeDistroArchSeries(
                 distroseries=distroseries)
             bpns.append((bpn, distroarchseries.architecturetag))
             expected.append(
-                BinaryOverride(bpn, distroarchseries, universe, None, None))
+                BinaryOverride(
+                    bpn, distroarchseries, universe, None, None, None))
         distroseries.nominatedarchindep = distroarchseries
         policy = UbuntuOverridePolicy()
         overrides = policy.calculateBinaryOverrides(
@@ -294,3 +295,43 @@
             distroseries.main_archive, distroseries, pocket, ((bpn, 'i386'),))
 
         self.assertEqual([], overrides)
+
+    def test_phased_update_percentage(self):
+        # A policy with a phased_update_percentage applies it to new binary
+        # overrides.
+        universe = getUtility(IComponentSet)['universe']
+        distroseries = self.factory.makeDistroSeries()
+        pocket = self.factory.getAnyPocket()
+        bpn = self.factory.makeBinaryPackageName()
+        bpns = []
+        expected = []
+        distroarchseries = self.factory.makeDistroArchSeries(
+            distroseries=distroseries)
+        bpb = self.factory.makeBinaryPackageBuild(
+            distroarchseries=distroarchseries)
+        bpr = self.factory.makeBinaryPackageRelease(
+            build=bpb, binarypackagename=bpn, architecturespecific=True)
+        bpph = self.factory.makeBinaryPackagePublishingHistory(
+            binarypackagerelease=bpr, distroarchseries=distroarchseries,
+            archive=distroseries.main_archive, pocket=pocket)
+        bpns.append((bpn, distroarchseries.architecturetag))
+        expected.append(
+            BinaryOverride(
+                bpn, distroarchseries, bpph.component, bpph.section,
+                bpph.priority, 50))
+        distroarchseries = self.factory.makeDistroArchSeries(
+            distroseries=distroseries)
+        bpns.append((bpn, distroarchseries.architecturetag))
+        expected.append(
+            BinaryOverride(bpn, distroarchseries, universe, None, None, 50))
+        distroseries.nominatedarchindep = distroarchseries
+        policy = UbuntuOverridePolicy(phased_update_percentage=50)
+        overrides = policy.calculateBinaryOverrides(
+            distroseries.main_archive, distroseries, pocket, bpns)
+        self.assertEqual(2, len(overrides))
+        key = attrgetter("binary_package_name.name",
+            "distro_arch_series.architecturetag",
+            "component.name")
+        sorted_expected = sorted(expected, key=key)
+        sorted_overrides = sorted(overrides, key=key)
+        self.assertEqual(sorted_expected, sorted_overrides)

=== modified file 'lib/lp/soyuz/interfaces/archive.py'
--- lib/lp/soyuz/interfaces/archive.py	2013-05-24 04:49:14 +0000
+++ lib/lp/soyuz/interfaces/archive.py	2013-06-21 10:28:33 +0000
@@ -1042,7 +1042,7 @@
     def getPockets():
         """Return iterable containing valid pocket names for this archive."""
 
-    def getOverridePolicy():
+    def getOverridePolicy(phased_update_percentage=None):
         """Returns an instantiated `IOverridePolicy` for the archive."""
 
     buildd_secret = TextLine(
@@ -1356,13 +1356,20 @@
         from_pocket=TextLine(title=_("Source pocket name"), required=False),
         from_series=TextLine(
             title=_("Source distroseries name"), required=False),
+        phased_update_percentage=Int(
+            title=_("Phased update percentage"),
+            description=_("The percentage of users for whom this package"
+                          " should be recommended, or None to publish the"
+                          " update for everyone."),
+            required=False),
         )
     @export_write_operation()
     @operation_for_version('devel')
     def copyPackage(source_name, version, from_archive, to_pocket,
                     person, to_series=None, include_binaries=False,
                     sponsored=None, unembargo=False, auto_approve=False,
-                    from_pocket=None, from_series=None):
+                    from_pocket=None, from_series=None,
+                    phased_update_percentage=None):
         """Copy a single named source into this archive.
 
         Asynchronously copy a specific version of a named source to the
@@ -1393,6 +1400,8 @@
             copy from any pocket with a matching version.
         :param from_series: the source distroseries (as a string). If
             omitted, copy from any series with a matching version.
+        :param phased_update_percentage: the phased update percentage to
+            apply to the copied publication.
 
         :raises NoSuchSourcePackageName: if the source name is invalid
         :raises PocketNotFound: if the pocket name is invalid

=== modified file 'lib/lp/soyuz/interfaces/packagecopyjob.py'
--- lib/lp/soyuz/interfaces/packagecopyjob.py	2012-10-23 12:36:50 +0000
+++ lib/lp/soyuz/interfaces/packagecopyjob.py	2013-06-21 10:28:33 +0000
@@ -1,4 +1,4 @@
-# Copyright 2010-2012 Canonical Ltd.  This software is licensed under the
+# Copyright 2010-2013 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 __metaclass__ = type
@@ -129,7 +129,8 @@
                include_binaries=False, package_version=None,
                copy_policy=PackageCopyPolicy.INSECURE, requester=None,
                sponsored=None, unembargo=False, auto_approve=False,
-               source_distroseries=None, source_pocket=None):
+               source_distroseries=None, source_pocket=None,
+               phased_update_percentage=None):
         """Create a new `IPlainPackageCopyJob`.
 
         :param package_name: The name of the source package to copy.
@@ -157,6 +158,8 @@
         :param source_pocket: The pocket from which to copy the packages.
             Must be a member of `PackagePublishingPocket`. If omitted, copy
             from any pocket with a matching version.
+        :param phased_update_percentage: The phased update percentage to
+            apply to the copied publication.
         """
 
     def createMultiple(target_distroseries, copy_tasks, requester,
@@ -242,6 +245,9 @@
         title=_("Source package publishing pocket"), required=False,
         readonly=True)
 
+    phased_update_percentage = Int(
+        title=_("Phased update percentage"), required=False, readonly=True)
+
     def addSourceOverride(override):
         """Add an `ISourceOverride` to the metadata."""
 

=== modified file 'lib/lp/soyuz/interfaces/publishing.py'
--- lib/lp/soyuz/interfaces/publishing.py	2013-05-31 04:00:53 +0000
+++ lib/lp/soyuz/interfaces/publishing.py	2013-06-21 10:28:33 +0000
@@ -991,7 +991,7 @@
         :param pocket: The target `PackagePublishingPocket`.
         :param binaries: A dict mapping `BinaryPackageReleases` to their
             desired overrides as (`Component`, `Section`,
-            `PackagePublishingPriority`) tuples.
+            `PackagePublishingPriority`, `phased_update_percentage`) tuples.
 
         :return: A list of new `IBinaryPackagePublishingHistory` records.
         """

=== modified file 'lib/lp/soyuz/model/archive.py'
--- lib/lp/soyuz/model/archive.py	2013-06-20 05:50:00 +0000
+++ lib/lp/soyuz/model/archive.py	2013-06-21 10:28:33 +0000
@@ -1635,9 +1635,17 @@
     def copyPackage(self, source_name, version, from_archive, to_pocket,
                     person, to_series=None, include_binaries=False,
                     sponsored=None, unembargo=False, auto_approve=False,
-                    from_pocket=None, from_series=None):
+                    from_pocket=None, from_series=None,
+                    phased_update_percentage=None):
         """See `IArchive`."""
         # Asynchronously copy a package using the job system.
+        if phased_update_percentage is not None:
+            if phased_update_percentage < 0 or phased_update_percentage > 100:
+                raise ValueError(
+                    "phased_update_percentage must be between 0 and 100 "
+                    "(inclusive).")
+            elif phased_update_percentage == 100:
+                phased_update_percentage = None
         pocket = self._text_to_pocket(to_pocket)
         series = self._text_to_series(to_series)
         if from_pocket:
@@ -1663,7 +1671,8 @@
             copy_policy=PackageCopyPolicy.INSECURE, requester=person,
             sponsored=sponsored, unembargo=unembargo,
             auto_approve=auto_approve, source_distroseries=from_series,
-            source_pocket=from_pocket)
+            source_pocket=from_pocket,
+            phased_update_percentage=phased_update_percentage)
 
     def copyPackages(self, source_names, from_archive, to_pocket,
                      person, to_series=None, from_series=None,
@@ -1996,14 +2005,15 @@
         # understandiung EnumItems.
         return list(PackagePublishingPocket.items)
 
-    def getOverridePolicy(self):
+    def getOverridePolicy(self, phased_update_percentage=None):
         """See `IArchive`."""
         # Circular imports.
         from lp.soyuz.adapters.overrides import UbuntuOverridePolicy
         # XXX StevenK: bug=785004 2011-05-19 Return PPAOverridePolicy() for
         # a PPA that overrides the component/pocket to main/RELEASE.
         if self.purpose in MAIN_ARCHIVE_PURPOSES:
-            return UbuntuOverridePolicy()
+            return UbuntuOverridePolicy(
+                phased_update_percentage=phased_update_percentage)
         return None
 
     def removeCopyNotification(self, job_id):

=== modified file 'lib/lp/soyuz/model/packagecopyjob.py'
--- lib/lp/soyuz/model/packagecopyjob.py	2013-06-20 05:50:00 +0000
+++ lib/lp/soyuz/model/packagecopyjob.py	2013-06-21 10:28:33 +0000
@@ -272,7 +272,7 @@
     def _makeMetadata(cls, target_pocket, package_version,
                       include_binaries, sponsored=None, unembargo=False,
                       auto_approve=False, source_distroseries=None,
-                      source_pocket=None):
+                      source_pocket=None, phased_update_percentage=None):
         """Produce a metadata dict for this job."""
         return {
             'target_pocket': target_pocket.value,
@@ -284,6 +284,7 @@
             'source_distroseries':
                 source_distroseries.name if source_distroseries else None,
             'source_pocket': source_pocket.value if source_pocket else None,
+            'phased_update_percentage': phased_update_percentage,
         }
 
     @classmethod
@@ -292,13 +293,15 @@
                include_binaries=False, package_version=None,
                copy_policy=PackageCopyPolicy.INSECURE, requester=None,
                sponsored=None, unembargo=False, auto_approve=False,
-               source_distroseries=None, source_pocket=None):
+               source_distroseries=None, source_pocket=None,
+               phased_update_percentage=None):
         """See `IPlainPackageCopyJobSource`."""
         assert package_version is not None, "No package version specified."
         assert requester is not None, "No requester specified."
         metadata = cls._makeMetadata(
             target_pocket, package_version, include_binaries, sponsored,
-            unembargo, auto_approve, source_distroseries, source_pocket)
+            unembargo, auto_approve, source_distroseries, source_pocket,
+            phased_update_percentage)
         job = PackageCopyJob(
             job_type=cls.class_job_type,
             source_archive=source_archive,
@@ -451,6 +454,10 @@
             return None
         return PackagePublishingPocket.items[name]
 
+    @property
+    def phased_update_percentage(self):
+        return self.metadata.get('phased_update_percentage')
+
     def _createPackageUpload(self, unapproved=False):
         pu = self.target_distroseries.createQueueEntry(
             pocket=self.target_pocket, archive=self.target_archive,

=== modified file 'lib/lp/soyuz/model/publishing.py'
--- lib/lp/soyuz/model/publishing.py	2013-06-21 02:49:33 +0000
+++ lib/lp/soyuz/model/publishing.py	2013-06-21 10:28:33 +0000
@@ -1445,20 +1445,18 @@
             return []
 
         BPPH = BinaryPackagePublishingHistory
-        # XXX cjwatson 2013-02-13: We do not currently set the
-        # phased_update_percentage here.  However, it might be useful in
-        # future to be able to copy a package from PROPOSED to UPDATES and
-        # immediately set its phased_update_percentage to zero; this should
-        # perhaps be an option.
         return bulk.create(
             (BPPH.archive, BPPH.distroarchseries, BPPH.pocket,
              BPPH.binarypackagerelease, BPPH.binarypackagename,
-             BPPH.component, BPPH.section, BPPH.priority, BPPH.status,
-             BPPH.datecreated),
+             BPPH.component, BPPH.section, BPPH.priority,
+             BPPH.phased_update_percentage, BPPH.status, BPPH.datecreated),
             [(archive, das, pocket, bpr, bpr.binarypackagename,
               get_component(archive, das.distroseries, component),
-              section, priority, PackagePublishingStatus.PENDING, UTC_NOW)
-              for (das, bpr, (component, section, priority)) in needed],
+              section, priority, phased_update_percentage,
+              PackagePublishingStatus.PENDING, UTC_NOW)
+              for (das, bpr,
+                   (component, section, priority,
+                    phased_update_percentage)) in needed],
             get_objects=True)
 
     def copyBinaries(self, archive, distroseries, pocket, bpphs, policy=None):
@@ -1500,7 +1498,14 @@
                 new_component = override.component or bpph.component
                 new_section = override.section or bpph.section
                 new_priority = override.priority or bpph.priority
-                calculated = (new_component, new_section, new_priority)
+                # No "or bpph.phased_update_percentage" here; if the
+                # override doesn't specify one then we leave it at None
+                # (a.k.a. 100% of users).
+                new_phased_update_percentage = (
+                    override.phased_update_percentage)
+                calculated = (
+                    new_component, new_section, new_priority,
+                    new_phased_update_percentage)
                 with_overrides[bpph.binarypackagerelease] = calculated
 
                 # If there is a corresponding DDEB then give it our
@@ -1514,7 +1519,7 @@
         else:
             with_overrides = dict(
                 (bpph.binarypackagerelease, (bpph.component, bpph.section,
-                 bpph.priority)) for bpph in bpphs)
+                 bpph.priority, None)) for bpph in bpphs)
         if not with_overrides:
             return list()
         return self.publishBinaries(

=== modified file 'lib/lp/soyuz/model/queue.py'
--- lib/lp/soyuz/model/queue.py	2013-06-20 05:50:00 +0000
+++ lib/lp/soyuz/model/queue.py	2013-06-21 10:28:33 +0000
@@ -1207,7 +1207,8 @@
                 binary.version,
                 'Specific' if binary.architecturespecific else 'Independent',
                 ))
-            bins[binary] = (binary.component, binary.section, binary.priority)
+            bins[binary] = (
+                binary.component, binary.section, binary.priority, None)
         return getUtility(IPublishingSet).publishBinaries(
             self.packageupload.archive, distroseries,
             self.packageupload.pocket, bins)

=== modified file 'lib/lp/soyuz/scripts/packagecopier.py'
--- lib/lp/soyuz/scripts/packagecopier.py	2013-06-21 02:39:33 +0000
+++ lib/lp/soyuz/scripts/packagecopier.py	2013-06-21 10:28:33 +0000
@@ -506,7 +506,8 @@
             person=None, check_permissions=True, overrides=None,
             send_email=False, strict_binaries=True, close_bugs=True,
             create_dsd_job=True, announce_from_person=None, sponsored=None,
-            packageupload=None, unembargo=False, logger=None):
+            packageupload=None, unembargo=False, phased_update_percentage=None,
+            logger=None):
     """Perform the complete copy of the given sources incrementally.
 
     Verifies if each copy can be performed using `CopyChecker` and
@@ -553,6 +554,8 @@
     :param unembargo: If True, allow copying restricted files from a private
         archive to a public archive, and unrestrict their library files when
         doing so.
+    :param phased_update_percentage: The phased update percentage to apply
+        to the copied publication.
     :param logger: An optional logger.
 
     :raise CannotCopy when one or more copies were not allowed. The error
@@ -628,7 +631,8 @@
             source, archive, destination_series, pocket, include_binaries,
             override, close_bugs=close_bugs, create_dsd_job=create_dsd_job,
             close_bugs_since_version=old_version, creator=creator,
-            sponsor=sponsor, packageupload=packageupload, logger=logger)
+            sponsor=sponsor, packageupload=packageupload,
+            phased_update_percentage=phased_update_percentage, logger=logger)
         if send_email:
             notify(
                 person, source.sourcepackagerelease, [], [], archive,
@@ -655,7 +659,8 @@
 def _do_direct_copy(source, archive, series, pocket, include_binaries,
                     override=None, close_bugs=True, create_dsd_job=True,
                     close_bugs_since_version=None, creator=None,
-                    sponsor=None, packageupload=None, logger=None):
+                    sponsor=None, packageupload=None,
+                    phased_update_percentage=None, logger=None):
     """Copy publishing records to another location.
 
     Copy each item of the given list of `SourcePackagePublishingHistory`
@@ -685,6 +690,8 @@
     :param sponsor: the sponsor `IPerson`, if this copy is being sponsored.
     :param packageupload: The `IPackageUpload` that caused this publication
         to be created.
+    :param phased_update_percentage: The phased update percentage to apply
+        to the copied publication.
     :param logger: An optional logger.
 
     :return: a list of `ISourcePackagePublishingHistory` and
@@ -703,7 +710,8 @@
         version=source.sourcepackagerelease.version,
         status=active_publishing_status,
         distroseries=series, pocket=pocket)
-    policy = archive.getOverridePolicy()
+    policy = archive.getOverridePolicy(
+        phased_update_percentage=phased_update_percentage)
     if source_in_destination.is_empty():
         # If no manual overrides were specified and the archive has an
         # override policy then use that policy to get overrides.

=== modified file 'lib/lp/soyuz/scripts/tests/test_copypackage.py'
--- lib/lp/soyuz/scripts/tests/test_copypackage.py	2013-05-29 07:24:13 +0000
+++ lib/lp/soyuz/scripts/tests/test_copypackage.py	2013-06-21 10:28:33 +0000
@@ -1380,6 +1380,27 @@
             section=override.section)
         self.assertThat(copied_source, matcher)
 
+    def test_copy_with_phased_update_percentage(self):
+        # Test the phased_update_percentage parameter for do_copy.
+        nobby, archive, source = self._setup_archive(
+            use_nobby=True, pocket=PackagePublishingPocket.PROPOSED)
+        [bin_i386, bin_hppa] = self.test_publisher.getPubBinaries(
+            pub_source=source, distroseries=nobby,
+            pocket=PackagePublishingPocket.PROPOSED)
+        transaction.commit()
+        switch_dbuser('archivepublisher')
+        [copied_source, copied_bin_i386, copied_bin_hppa] = do_copy(
+            [source], archive, nobby, PackagePublishingPocket.UPDATES,
+            include_binaries=True, check_permissions=False)
+        self.assertIsNone(copied_bin_i386.phased_update_percentage)
+        self.assertIsNone(copied_bin_hppa.phased_update_percentage)
+        [copied_source, copied_bin_i386, copied_bin_hppa] = do_copy(
+            [source], archive, nobby, PackagePublishingPocket.BACKPORTS,
+            include_binaries=True, check_permissions=False,
+            phased_update_percentage=50)
+        self.assertEqual(50, copied_bin_i386.phased_update_percentage)
+        self.assertEqual(50, copied_bin_hppa.phased_update_percentage)
+
     def test_copy_ppa_generates_notification(self):
         # When a copy into a PPA is performed, a notification is sent.
         nobby, archive, source = self._setup_archive()

=== modified file 'lib/lp/soyuz/tests/test_archive.py'
--- lib/lp/soyuz/tests/test_archive.py	2013-06-20 05:50:00 +0000
+++ lib/lp/soyuz/tests/test_archive.py	2013-06-21 10:28:33 +0000
@@ -2154,7 +2154,8 @@
             target_archive.copyPackage(
                 source_name, version, source_archive, to_pocket.name,
                 to_series=to_series.name, include_binaries=False,
-                person=target_archive.owner, sponsored=sponsored)
+                person=target_archive.owner, sponsored=sponsored,
+                phased_update_percentage=30)
 
         # The source should not be published yet in the target_archive.
         published = target_archive.getPublishedSources(
@@ -2175,7 +2176,8 @@
             target_pocket=to_pocket,
             include_binaries=False,
             sponsored=sponsored,
-            copy_policy=PackageCopyPolicy.INSECURE))
+            copy_policy=PackageCopyPolicy.INSECURE,
+            phased_update_percentage=30))
 
     def test_copyPackage_disallows_non_primary_archive_uploaders(self):
         # If copying to a primary archive and you're not an uploader for

=== modified file 'lib/lp/soyuz/tests/test_packagecopyjob.py'
--- lib/lp/soyuz/tests/test_packagecopyjob.py	2013-06-20 05:50:00 +0000
+++ lib/lp/soyuz/tests/test_packagecopyjob.py	2013-06-21 10:28:33 +0000
@@ -206,7 +206,8 @@
             target_pocket=PackagePublishingPocket.RELEASE,
             package_version="1.0-1", include_binaries=False,
             copy_policy=PackageCopyPolicy.MASS_SYNC,
-            requester=requester, sponsored=sponsored)
+            requester=requester, sponsored=sponsored,
+            phased_update_percentage=20)
         self.assertProvides(job, IPackageCopyJob)
         self.assertEqual(archive1.id, job.source_archive_id)
         self.assertEqual(archive1, job.source_archive)
@@ -220,6 +221,7 @@
         self.assertEqual(PackageCopyPolicy.MASS_SYNC, job.copy_policy)
         self.assertEqual(requester, job.requester)
         self.assertEqual(sponsored, job.sponsored)
+        self.assertEqual(20, job.phased_update_percentage)
 
     def test_createMultiple_creates_one_job_per_copy(self):
         mother = self.factory.makeDistroSeriesParent()

=== modified file 'lib/lp/soyuz/tests/test_publishing.py'
--- lib/lp/soyuz/tests/test_publishing.py	2013-05-17 02:40:30 +0000
+++ lib/lp/soyuz/tests/test_publishing.py	2013-06-21 10:28:33 +0000
@@ -1422,7 +1422,7 @@
             'binaries': dict(
                 (bpr, (self.factory.makeComponent(),
                  self.factory.makeSection(),
-                 PackagePublishingPriority.REQUIRED)) for bpr in bprs),
+                 PackagePublishingPriority.REQUIRED, 50)) for bpr in bprs),
             }
 
     def test_architecture_dependent(self):
@@ -1445,7 +1445,9 @@
             (args['archive'], target_das, args['pocket']),
             (bpph.archive, bpph.distroarchseries, bpph.pocket))
         self.assertEqual(
-            overrides, (bpph.component, bpph.section, bpph.priority))
+            overrides,
+            (bpph.component, bpph.section, bpph.priority,
+             bpph.phased_update_percentage))
         self.assertEqual(PackagePublishingStatus.PENDING, bpph.status)
 
     def test_architecture_independent(self):

=== modified file 'lib/lp/testing/factory.py'
--- lib/lp/testing/factory.py	2013-06-20 05:50:00 +0000
+++ lib/lp/testing/factory.py	2013-06-21 10:28:33 +0000
@@ -3809,7 +3809,7 @@
             archive, distroarchseries.distroseries, pocket,
             {binarypackagerelease: (
                 binarypackagerelease.component, binarypackagerelease.section,
-                priority)})
+                priority, None)})
         for bpph in bpphs:
             naked_bpph = removeSecurityProxy(bpph)
             naked_bpph.status = status


Follow ups