launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #14958
[Merge] lp:~cjwatson/launchpad/bpph-phase into lp:launchpad
Colin Watson has proposed merging lp:~cjwatson/launchpad/bpph-phase into lp:launchpad.
Commit message:
Implement BPPH.phased_update_percentage, published as a new Phased-Update-Percentage control field.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
Related bugs:
Bug #1100748 in Launchpad itself: "Support phased updates via Phased-Update-Percentage control field"
https://bugs.launchpad.net/launchpad/+bug/1100748
For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/bpph-phase/+merge/144154
== Summary ==
Bug 1100748: We need a way to distribute updates to subsets of our users in phases, using hints in the Packages file to smart clients such as update-manager. We'll automatically monitor errors.ubuntu.com to detect regressions.
== Proposed fix ==
Add a phased_update_percentage column to BPPH (database patch already landed and QAed, awaiting deployment); support modifying this using changeOverride; publish it in Phased-Update-Percentage fields; show it in publishing history views.
== Implementation details ==
I needed to fiddle about a bit in archivepublisher to be able to support different Phased-Update-Percentage fields on different architectures, which seemed logical given the data model. I could potentially have implemented correct support for differing priorities and sections across architectures too, but resisted the temptation.
== LOC Rationale ==
+83. I have a ridiculous amount of LoC credit so hopefully this can be overlooked for now.
== Tests ==
bin/test -vvct lp.archivepublisher.tests.test_ftparchive -t lp.soyuz.tests.test_publish_archive_indexes -t lp.soyuz.tests.test_publishing -t lib/lp/soyuz/browser/tests/publishing-views.txt
== Demo and Q/A ==
Set a phased update percentage on a package on dogfood (preferably in a small pocket, i.e. non-RELEASE), run the publisher, check for appropriate Phased-Update-Percentage fields, and check https://dogfood.launchpad.net/ubuntu/DS/ARCH/PKG to see that the publishing history looks correct.
--
https://code.launchpad.net/~cjwatson/launchpad/bpph-phase/+merge/144154
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~cjwatson/launchpad/bpph-phase into lp:launchpad.
=== modified file 'lib/lp/archivepublisher/model/ftparchive.py'
--- lib/lp/archivepublisher/model/ftparchive.py 2012-09-28 06:25:44 +0000
+++ lib/lp/archivepublisher/model/ftparchive.py 2013-01-21 17:01:26 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009-2012 Canonical Ltd. This software is licensed under the
+# Copyright 2009-2013 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
from collections import defaultdict
@@ -276,7 +276,8 @@
"""Fetch override information about all published binaries.
The override information consists of tuples with 'binaryname',
- 'component', 'section' and 'priority' strings, in this order.
+ 'architecture', 'component', 'section' and 'priority' strings and
+ 'phased_update_percentage' integers, in this order.
:param distroseries: target `IDistroSeries`
:param pocket: target `PackagePublishingPocket`
@@ -296,6 +297,9 @@
Join(BinaryPackageName,
BinaryPackageName.id ==
BinaryPackageRelease.binarypackagenameID),
+ Join(DistroArchSeries,
+ DistroArchSeries.id ==
+ BinaryPackagePublishingHistory.distroarchseriesID),
)
architectures_ids = [arch.id for arch in distroseries.architectures]
@@ -303,8 +307,10 @@
return EmptyResultSet()
result_set = store.using(*origins).find(
- (BinaryPackageName.name, Component.name, Section.name,
- BinaryPackagePublishingHistory.priority),
+ (BinaryPackageName.name, DistroArchSeries.architecturetag,
+ Component.name, Section.name,
+ BinaryPackagePublishingHistory.priority,
+ BinaryPackagePublishingHistory.phased_update_percentage),
BinaryPackagePublishingHistory.archive == self.publisher.archive,
BinaryPackagePublishingHistory.distroarchseriesID.is_in(
architectures_ids),
@@ -343,7 +349,8 @@
Attributes which must be present in sourceoverrides are:
drname, spname, cname, sname
Attributes which must be present in binaryoverrides are:
- drname, spname, cname, sname, priority
+ drname, spname, cname, sname, archtag, priority,
+ phased_update_percentage
The binary priority will be mapped via the values in
dbschema.py.
@@ -354,34 +361,37 @@
# overrides[component][src/bin] = sets of tuples
overrides = defaultdict(lambda: defaultdict(set))
- def updateOverride(packagename, component, section, priority=None):
+ def updateOverride(packagename, component, section, archtag=None,
+ priority=None, phased_update_percentage=None):
"""Generates and packs tuples of data required for overriding.
- If priority is provided, it's a binary tuple; otherwise,
- it's a source tuple.
+ If archtag is provided, it's a binary tuple; otherwise, it's a
+ source tuple.
- Note that these tuples must contain /strings/, and not
- objects, because they will be printed out verbatim into the
- override files. This is why we use priority_displayed here,
- and why we get the string names of the publication's foreign
- keys to component, section, etc.
+ Note that these tuples must contain /strings/ (or integers in
+ the case of phased_update_percentage), and not objects, because
+ they will be printed out verbatim into the override files. This
+ is why we use priority_displayed here, and why we get the string
+ names of the publication's foreign keys to component, section,
+ etc.
"""
if component != DEFAULT_COMPONENT:
section = "%s/%s" % (component, section)
override = overrides[component]
# We use sets in this structure to avoid generating
- # duplicated overrides. This issue is an outcome of the fact
- # that the PublishingHistory views select across all
- # architectures -- and therefore we have N binaries for N
- # archs.
- if priority:
+ # duplicated overrides.
+ if archtag:
priority = priority.title.lower()
- # We pick up debian-installer packages here
+ # We pick up debian-installer packages here, although they
+ # do not need phased updates.
if section.endswith("debian-installer"):
override['d-i'].add((packagename, priority, section))
else:
- override['bin'].add((packagename, priority, section))
+ package_arch = "%s/%s" % (packagename, archtag)
+ override['bin'].add((
+ package_arch, priority, section,
+ phased_update_percentage))
else:
override['src'].add((packagename, section))
@@ -403,8 +413,7 @@
suite, component))
self.generateOverrideForComponent(overrides, suite, component)
- def generateOverrideForComponent(self, overrides, distroseries,
- component):
+ def generateOverrideForComponent(self, overrides, suite, component):
"""Generates overrides for a specific component."""
src_overrides = sorted(overrides[component]['src'])
bin_overrides = sorted(overrides[component]['bin'])
@@ -412,43 +421,50 @@
# Set up filepaths for the overrides we read
extra_extra_overrides = os.path.join(self._config.miscroot,
- "more-extra.override.%s.main" % distroseries)
+ "more-extra.override.%s.main" % suite)
if not os.path.exists(extra_extra_overrides):
- unpocketed_series = "-".join(distroseries.split('-')[:-1])
+ unpocketed_series = "-".join(suite.split('-')[:-1])
extra_extra_overrides = os.path.join(self._config.miscroot,
"more-extra.override.%s.main" % unpocketed_series)
# And for the overrides we write out
main_override = os.path.join(self._config.overrideroot,
- "override.%s.%s" %
- (distroseries, component))
+ "override.%s.%s" % (suite, component))
ef_override = os.path.join(self._config.overrideroot,
- "override.%s.extra.%s" %
- (distroseries, component))
+ "override.%s.extra.%s" % (suite, component))
di_override = os.path.join(self._config.overrideroot,
"override.%s.%s.debian-installer" %
- (distroseries, component))
+ (suite, component))
source_override = os.path.join(self._config.overrideroot,
"override.%s.%s.src" %
- (distroseries, component))
+ (suite, component))
# Start to write the files out
ef = open(ef_override, "w")
f = open(main_override, "w")
- for package, priority, section in bin_overrides:
- origin = "\t".join([package, "Origin", "Ubuntu"])
- bugs = "\t".join([package, "Bugs",
- "https://bugs.launchpad.net/ubuntu/+filebug"])
+ basic_override_seen = set()
+ for (package_arch, priority, section,
+ phased_update_percentage) in bin_overrides:
+ package = package_arch.split("/")[0]
+ if package not in basic_override_seen:
+ basic_override_seen.add(package)
+ f.write("\t".join((package, priority, section)))
+ f.write("\n")
- f.write("\t".join((package, priority, section)))
- f.write("\n")
- # XXX: dsilvers 2006-08-23 bug=3900:
- # This needs to be made databaseish and be actually managed within
- # Launchpad. (Or else we need to change the ubuntu as appropriate
- # and look for bugs addresses etc in launchpad.
- ef.write(origin)
- ef.write("\n")
- ef.write(bugs)
- ef.write("\n")
+ # XXX: dsilvers 2006-08-23 bug=3900:
+ # This needs to be made databaseish and be actually managed
+ # within Launchpad. (Or else we need to change Ubuntu as
+ # appropriate and look for bugs addresses etc in Launchpad.)
+ ef.write("\t".join([package, "Origin", "Ubuntu"]))
+ ef.write("\n")
+ ef.write("\t".join([
+ package, "Bugs",
+ "https://bugs.launchpad.net/ubuntu/+filebug"]))
+ ef.write("\n")
+ if phased_update_percentage is not None:
+ ef.write("\t".join([
+ package_arch, "Phased-Update-Percentage",
+ str(phased_update_percentage)]))
+ ef.write("\n")
f.close()
if os.path.exists(extra_extra_overrides):
=== modified file 'lib/lp/archivepublisher/tests/test_ftparchive.py'
--- lib/lp/archivepublisher/tests/test_ftparchive.py 2012-08-20 09:23:49 +0000
+++ lib/lp/archivepublisher/tests/test_ftparchive.py 2013-01-21 17:01:26 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009-2012 Canonical Ltd. This software is licensed under the
+# Copyright 2009-2013 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Tests for ftparchive.py"""
@@ -137,10 +137,12 @@
self._logger, self._config, self._dp, self._distribution,
self._publisher)
- def _publishDefaultOverrides(self, fa, component):
+ def _publishDefaultOverrides(self, fa, component,
+ phased_update_percentage=None):
source_overrides = FakeSelectResult([('foo', component, 'misc')])
- binary_overrides = FakeSelectResult(
- [('foo', component, 'misc', PackagePublishingPriority.EXTRA)])
+ binary_overrides = FakeSelectResult([(
+ 'foo', component, 'misc', 'i386', PackagePublishingPriority.EXTRA,
+ phased_update_percentage)])
fa.publishOverrides('hoary-test', source_overrides, binary_overrides)
def _publishDefaultFileLists(self, fa, component):
@@ -188,9 +190,10 @@
published_binaries = fa.getBinariesForOverrides(
hoary, PackagePublishingPocket.RELEASE)
expectedBinaries = [
- ('pmount', 'main', 'base', PackagePublishingPriority.EXTRA),
- ('pmount', 'universe', 'editors',
- PackagePublishingPriority.IMPORTANT),
+ ('pmount', 'hppa', 'main', 'base',
+ PackagePublishingPriority.EXTRA, None),
+ ('pmount', 'i386', 'universe', 'editors',
+ PackagePublishingPriority.IMPORTANT, None),
]
self.assertEqual(expectedBinaries, list(published_binaries))
@@ -233,6 +236,18 @@
with open(result_path) as result_file:
self.assertIn("\t".join(sentinel), result_file.read().splitlines())
+ def test_publishOverrides_phase(self):
+ # Publications with a non-None phased update percentage produce
+ # Phased-Update-Percentage extra overrides.
+ fa = self._setUpFTPArchiveHandler()
+ self._publishDefaultOverrides(fa, 'main', phased_update_percentage=50)
+
+ path = os.path.join(self._overdir, "override.hoary-test.extra.main")
+ with open(path) as result_file:
+ self.assertIn(
+ "foo/i386\tPhased-Update-Percentage\t50",
+ result_file.read().splitlines())
+
def test_getSourceFiles(self):
# getSourceFiles returns a list of tuples containing:
# (sourcename, filename, component)
=== removed file 'lib/lp/soyuz/browser/publishedpackage.py'
--- lib/lp/soyuz/browser/publishedpackage.py 2009-06-25 04:06:00 +0000
+++ lib/lp/soyuz/browser/publishedpackage.py 1970-01-01 00:00:00 +0000
@@ -1,49 +0,0 @@
-# Copyright 2009 Canonical Ltd. This software is licensed under the
-# GNU Affero General Public License version 3 (see the file LICENSE).
-
-__metaclass__ = type
-
-__all__ = [
- 'PkgBuild',
- 'PkgVersion',
- 'DistroSeriesVersions',
- 'BinPackage',
- ]
-
-class PkgBuild:
-
- def __init__(self, id, processorfamilyname,
- distroarchseries):
- self.id = id
- self.processorfamilyname = processorfamilyname
- self.distroarchseries = distroarchseries
-
- def html(self):
- return (
- '<a href="/soyuz/packages/%s">%s</a>'
- % (self.id, self.processorfamilyname))
-
-class PkgVersion:
-
- def __init__(self, version):
- self.version = version
- self.builds = []
-
- def buildlisthtml(self):
- return ', '.join([ build.html() for build in self.builds ])
-
-class DistroSeriesVersions:
-
- def __init__(self, distroseriesname):
- self.distroseriesname = distroseriesname
- self.versions = {}
-
-class BinPackage:
-
- def __init__(self, name, summary, description):
- self.name = name
- self.summary = summary
- self.description = description
- self.distroseriess = {}
-
-
=== modified file 'lib/lp/soyuz/browser/publishing.py'
--- lib/lp/soyuz/browser/publishing.py 2012-11-26 08:40:20 +0000
+++ lib/lp/soyuz/browser/publishing.py 2013-01-21 17:01:26 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009 Canonical Ltd. This software is licensed under the
+# Copyright 2009-2013 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Browser views for Soyuz publishing records."""
@@ -190,6 +190,14 @@
return removal_comment
+ @property
+ def phased_update_percentage(self):
+ """Return the formatted phased update percentage, or empty."""
+ if (self.is_binary and
+ self.context.phased_update_percentage is not None):
+ return u"%d%% of users" % self.context.phased_update_percentage
+ return u""
+
class SourcePublishingRecordView(BasePublishingRecordView):
"""View class for `ISourcePackagePublishingHistory`."""
=== modified file 'lib/lp/soyuz/browser/tests/publishing-views.txt'
--- lib/lp/soyuz/browser/tests/publishing-views.txt 2012-09-27 02:53:00 +0000
+++ lib/lp/soyuz/browser/tests/publishing-views.txt 2013-01-21 17:01:26 +0000
@@ -1,4 +1,6 @@
-= Source Package Publishing Views =
+===============================
+Source Package Publishing Views
+===============================
The default view for SourcePackagePublishingHistory offers a
convenience property that can be used to display files that are
@@ -6,8 +8,6 @@
files. The property returns a sorted list of dictionaries with URLs,
filenames and filesizes.
- >>> from zope.component import getMultiAdapter
- >>> from lp.services.webapp.servers import LaunchpadTestRequest
>>> from lp.testing import celebrity_logged_in
We'll create SourcePackagePublishingHistory entries for
@@ -30,8 +30,7 @@
If the publishing record does not include a removal comment, then
the view property returns 'None provided.'
- >>> view = getMultiAdapter(
- ... (foo_pub, LaunchpadTestRequest()), name="+listing-compact")
+ >>> view = create_initialized_view(foo_pub, "+listing-compact")
>>> view.wasDeleted()
True
>>> print view.context.removal_comment
@@ -61,9 +60,7 @@
for each file related with the alsa-utils source publication in ubuntu.
- >>> view = getMultiAdapter(
- ... (alsa_pub, LaunchpadTestRequest()),
- ... name="+listing-archive-detailed")
+ >>> view = create_initialized_view(alsa_pub, "+listing-archive-detailed")
>>> view.published_source_and_binary_files
[{'url': u'http://launchpad.dev/ubuntutest/+archive/primary/+files/alsa-utils-test_666.dsc',
@@ -80,9 +77,8 @@
>>> iceweasel_source_pub = cprov.archive.getPublishedSources(
... u'iceweasel').first()
- >>> ppa_source_view = getMultiAdapter(
- ... (iceweasel_source_pub, LaunchpadTestRequest()),
- ... name="+listing-archive-detailed")
+ >>> ppa_source_view = create_initialized_view(
+ ... iceweasel_source_pub, "+listing-archive-detailed")
>>> ppa_source_view.published_source_and_binary_files
[{'url': u'http://launchpad.dev/~cprov/+archive/ppa/+files/firefox_0.9.2.orig.tar.gz',
@@ -104,9 +100,8 @@
Continuing to use the 'iceweasel' source publication in Celso's PPA.
- >>> source_details_view = getMultiAdapter(
- ... (iceweasel_source_pub, LaunchpadTestRequest()),
- ... name="+record-details")
+ >>> source_details_view = create_initialized_view(
+ ... iceweasel_source_pub, "+record-details")
We probe the 'is_source' and 'is_binary' properties.
@@ -122,9 +117,8 @@
>>> iceweasel_binary_pub = iceweasel_source_pub.getPublishedBinaries()[0]
- >>> binary_details_view = getMultiAdapter(
- ... (iceweasel_binary_pub, LaunchpadTestRequest()),
- ... name="+record-details")
+ >>> binary_details_view = create_initialized_view(
+ ... iceweasel_binary_pub, "+record-details")
>>> print binary_details_view.is_source
False
@@ -157,8 +151,34 @@
...
KeyError: 'key_not_there'
-
-== SourcePublishingRecordView ==
+The view knows how to render a publication's phased update percentage.
+
+ >>> print binary_details_view.phased_update_percentage
+
+ >>> login('celso.providelo@xxxxxxxxxxxxx')
+ >>> iceweasel_binary_pub_phased = iceweasel_binary_pub.changeOverride(
+ ... new_phased_update_percentage=50)
+ >>> binary_details_view = create_initialized_view(
+ ... iceweasel_binary_pub_phased, "+record-details")
+ >>> print binary_details_view.phased_update_percentage
+ 50% of users
+
+BinaryPackagePublishingHistory:+listing-summary is included in
+DistroArchSeriesBinaryPackage:+index, showing a summary of each publication.
+It handles phased update percentages correctly.
+
+ >>> binary_summary_view = create_initialized_view(
+ ... iceweasel_binary_pub, "+listing-summary")
+ >>> print binary_summary_view.phased_update_percentage
+
+ >>> binary_summary_view_phased = create_initialized_view(
+ ... iceweasel_binary_pub_phased, "+listing-summary")
+ >>> print binary_summary_view_phased.phased_update_percentage
+ 50% of users
+
+
+SourcePublishingRecordView
+==========================
The SourcePublishingRecordView includes a build_status_summary property
that returns a dict summary of the build status for the context record:
@@ -200,4 +220,3 @@
>>> for build in src_pub_record_view.pending_builds:
... print build.title
i386 build of iceweasel 1.0 in ubuntu warty RELEASE
-
=== modified file 'lib/lp/soyuz/interfaces/distroarchseriesbinarypackagerelease.py'
--- lib/lp/soyuz/interfaces/distroarchseriesbinarypackagerelease.py 2013-01-07 02:40:55 +0000
+++ lib/lp/soyuz/interfaces/distroarchseriesbinarypackagerelease.py 2013-01-21 17:01:26 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009 Canonical Ltd. This software is licensed under the
+# Copyright 2009-2013 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Binary package release in Distribution Architecture Release interfaces."""
@@ -52,6 +52,11 @@
component = Attribute("The component in which this package is "
"published or None if it is not currently published.")
+ phased_update_percentage = Attribute(
+ "The percentage of users for whom this package should be recommended, "
+ "or None to publish the update for everyone or if it is not currently "
+ "published.")
+
publishing_history = Attribute("Return a list of publishing "
"records for this binary package release in this series "
"and this architecture, of the distribution.")
=== modified file 'lib/lp/soyuz/interfaces/publishing.py'
--- lib/lp/soyuz/interfaces/publishing.py 2013-01-07 02:40:55 +0000
+++ lib/lp/soyuz/interfaces/publishing.py 2013-01-21 17:01:26 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009-2012 Canonical Ltd. This software is licensed under the
+# Copyright 2009-2013 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Publishing interfaces."""
@@ -784,6 +784,12 @@
title=_('The priority being published into'),
required=False, readonly=False,
)
+ phased_update_percentage = exported(
+ Int(
+ title=_('The percentage of users for whom this package should be '
+ 'recommended, or None to publish the update for everyone'),
+ required=False, readonly=True,
+ ))
datepublished = exported(
Datetime(
title=_("Date Published"),
@@ -961,15 +967,21 @@
# save manually looking up the priority name, but it doesn't work in
# this case: the title is wrong, and tests fail when a string value
# is passed over the webservice.
- new_priority=TextLine(title=u"The new priority name."))
+ new_priority=TextLine(title=u"The new priority name."),
+ new_phased_update_percentage=Int(
+ title=u"The new phased update percentage."))
@export_write_operation()
@operation_for_version("devel")
def changeOverride(new_component=None, new_section=None,
- new_priority=None):
- """Change the component, section and/or priority of this publication.
+ new_priority=None, new_phased_update_percentage=None):
+ """Change the component/section/priority/phase of this publication.
It is changed only if the argument is not None.
+ Passing new_phased_update_percentage=100 has the effect of setting
+ the phased update percentage to None (i.e. recommended for all
+ users).
+
Return the overridden publishing record, a
`IBinaryPackagePublishingHistory`.
"""
=== modified file 'lib/lp/soyuz/model/distroarchseriesbinarypackagerelease.py'
--- lib/lp/soyuz/model/distroarchseriesbinarypackagerelease.py 2013-01-07 02:40:55 +0000
+++ lib/lp/soyuz/model/distroarchseriesbinarypackagerelease.py 2013-01-21 17:01:26 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009 Canonical Ltd. This software is licensed under the
+# Copyright 2009-2013 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Classes to represent binary package releases in a
@@ -156,6 +156,14 @@
return None
return pub.priority
+ @property
+ def phased_update_percentage(self):
+ """See `IDistroArchSeriesBinaryPackageRelease`."""
+ pub = self._latest_publishing_record()
+ if pub is None:
+ return None
+ return pub.phased_update_percentage
+
# map the BinaryPackageRelease attributes up to this class so it
# responds to the same interface
=== modified file 'lib/lp/soyuz/model/publishing.py'
--- lib/lp/soyuz/model/publishing.py 2013-01-03 00:16:08 +0000
+++ lib/lp/soyuz/model/publishing.py 2013-01-21 17:01:26 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009-2012 Canonical Ltd. This software is licensed under the
+# Copyright 2009-2013 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
__metaclass__ = type
@@ -25,6 +25,7 @@
import pytz
from sqlobject import (
ForeignKey,
+ IntCol,
StringCol,
)
from storm.expr import (
@@ -955,6 +956,8 @@
section = ForeignKey(foreignKey='Section', dbName='section')
priority = EnumCol(dbName='priority', schema=PackagePublishingPriority)
status = EnumCol(dbName='status', schema=PackagePublishingStatus)
+ phased_update_percentage = IntCol(
+ dbName='phased_update_percentage', notNull=False, default=None)
scheduleddeletiondate = UtcDateTimeCol(default=None)
datepublished = UtcDateTimeCol(default=None)
datecreated = UtcDateTimeCol(default=UTC_NOW)
@@ -1095,6 +1098,8 @@
fields.append('Size', bin_size)
fields.append('MD5sum', bin_md5)
fields.append('SHA1', bin_sha1)
+ fields.append(
+ 'Phased-Update-Percentage', self.phased_update_percentage)
fields.append('Description', bin_description)
if bpr.user_defined_fields:
fields.extend(bpr.user_defined_fields)
@@ -1207,14 +1212,15 @@
dominated.supersede(dominant, logger)
def changeOverride(self, new_component=None, new_section=None,
- new_priority=None):
+ new_priority=None, new_phased_update_percentage=None):
"""See `IBinaryPackagePublishingHistory`."""
# Check we have been asked to do something
if (new_component is None and new_section is None
- and new_priority is None):
- raise AssertionError("changeOverride must be passed a new"
- "component, section and/or priority.")
+ and new_priority is None and new_phased_update_percentage is None):
+ raise AssertionError("changeOverride must be passed a new "
+ "component, section, priority and/or "
+ "phased_update_percentage.")
# Check there is a change to make
if new_component is None:
@@ -1229,10 +1235,20 @@
new_priority = self.priority
elif isinstance(new_priority, basestring):
new_priority = name_priority_map[new_priority]
+ if new_phased_update_percentage is None:
+ new_phased_update_percentage = self.phased_update_percentage
+ elif (new_phased_update_percentage < 0 or
+ new_phased_update_percentage > 100):
+ raise ValueError(
+ "new_phased_update_percentage must be between 0 and 100 "
+ "(inclusive).")
+ elif new_phased_update_percentage == 100:
+ new_phased_update_percentage = None
if (new_component == self.component and
new_section == self.section and
- new_priority == self.priority):
+ new_priority == self.priority and
+ new_phased_update_percentage == self.phased_update_percentage):
return
if new_component != self.component:
@@ -1264,7 +1280,8 @@
component=new_component,
section=new_section,
priority=new_priority,
- archive=self.archive)
+ archive=self.archive,
+ phased_update_percentage=new_phased_update_percentage)
def copyTo(self, distroseries, pocket, archive):
"""See `BinaryPackagePublishingHistory`."""
=== modified file 'lib/lp/soyuz/templates/distroarchseriesbinarypackage-index.pt'
--- lib/lp/soyuz/templates/distroarchseriesbinarypackage-index.pt 2009-09-03 15:17:24 +0000
+++ lib/lp/soyuz/templates/distroarchseriesbinarypackage-index.pt 2013-01-21 17:01:26 +0000
@@ -33,6 +33,8 @@
<th>Pocket</th>
<th>Component</th>
<th>Section</th>
+ <th>Priority</th>
+ <th>Phased updates</th>
<th>Version</th>
</tr>
</thead>
=== modified file 'lib/lp/soyuz/templates/distroarchseriesbinarypackagerelease-portlet-details.pt'
--- lib/lp/soyuz/templates/distroarchseriesbinarypackagerelease-portlet-details.pt 2009-09-02 11:56:16 +0000
+++ lib/lp/soyuz/templates/distroarchseriesbinarypackagerelease-portlet-details.pt 2013-01-21 17:01:26 +0000
@@ -37,6 +37,12 @@
<dd tal:content="context/priority/title" />
</dl>
+ <dl tal:define="phased_update_percentage view/phased_update_percentage"
+ tal:condition="phased_update_percentage">
+ <dt>Phased update:</dt>
+ <dd tal:content="phased_update_percentage">50% of users</dd>
+ </dl>
+
</div>
</div>
=== modified file 'lib/lp/soyuz/templates/publishinghistory-macros.pt'
--- lib/lp/soyuz/templates/publishinghistory-macros.pt 2011-07-18 09:23:10 +0000
+++ lib/lp/soyuz/templates/publishinghistory-macros.pt 2013-01-21 17:01:26 +0000
@@ -24,6 +24,10 @@
<td tal:content="context/pocket/title/fmt:lower">Release</td>
<td tal:content="context/component/name">main</td>
<td tal:content="context/section/name">web</td>
+ <tal:binary_history condition="view/is_binary">
+ <td tal:content="context/priority/title">Optional</td>
+ <td tal:content="view/phased_update_percentage">50% of users</td>
+ </tal:binary_history>
<td>
<a tal:content="version_name"
tal:attributes="href version_url"
=== modified file 'lib/lp/soyuz/tests/test_publish_archive_indexes.py'
--- lib/lp/soyuz/tests/test_publish_archive_indexes.py 2011-12-14 12:30:28 +0000
+++ lib/lp/soyuz/tests/test_publish_archive_indexes.py 2013-01-21 17:01:26 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009-2010 Canonical Ltd. This software is licensed under the
+# Copyright 2009-2013 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Test native archive index generation for Soyuz."""
@@ -95,7 +95,8 @@
pub_binaries = self.getPubBinaries(
depends='biscuit', recommends='foo-dev', suggests='pyfoo',
conflicts='old-foo', replaces='old-foo', provides='foo-master',
- pre_depends='master-foo', enhances='foo-super', breaks='old-foo')
+ pre_depends='master-foo', enhances='foo-super', breaks='old-foo',
+ phased_update_percentage=50)
pub_binary = pub_binaries[0]
self.assertEqual(
[u'Package: foo-bin',
@@ -119,6 +120,7 @@
u'Size: 18',
u'MD5sum: 008409e7feb1c24a6ccab9f6a62d24c5',
u'SHA1: 30b7b4e583fa380772c5a40e428434628faef8cf',
+ u'Phased-Update-Percentage: 50',
u'Description: Foo app is great',
u' Well ...',
u' it does nothing, though'],
=== modified file 'lib/lp/soyuz/tests/test_publishing.py'
--- lib/lp/soyuz/tests/test_publishing.py 2012-11-15 16:48:31 +0000
+++ lib/lp/soyuz/tests/test_publishing.py 2013-01-21 17:01:26 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009-2012 Canonical Ltd. This software is licensed under the
+# Copyright 2009-2013 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Test native publication workflow for Soyuz. """
@@ -309,6 +309,7 @@
architecturespecific=False,
builder=None,
component='main',
+ phased_update_percentage=None,
with_debug=False, user_defined_fields=None):
"""Return a list of binary publishing records."""
if distroseries is None:
@@ -345,7 +346,8 @@
breaks, BinaryPackageFormat.DDEB, version=version)
pub_binaries += self.publishBinaryInArchive(
binarypackagerelease_ddeb, archive.debug_archive, status,
- pocket, scheduleddeletiondate, dateremoved)
+ pocket, scheduleddeletiondate, dateremoved,
+ phased_update_percentage)
else:
binarypackagerelease_ddeb = None
@@ -357,7 +359,7 @@
user_defined_fields=user_defined_fields)
pub_binaries += self.publishBinaryInArchive(
binarypackagerelease, archive, status, pocket,
- scheduleddeletiondate, dateremoved)
+ scheduleddeletiondate, dateremoved, phased_update_percentage)
published_binaries.extend(pub_binaries)
package_upload = self.addPackageUpload(
archive, distroseries, pocket,
@@ -450,7 +452,8 @@
self, binarypackagerelease, archive,
status=PackagePublishingStatus.PENDING,
pocket=PackagePublishingPocket.RELEASE,
- scheduleddeletiondate=None, dateremoved=None):
+ scheduleddeletiondate=None, dateremoved=None,
+ phased_update_percentage=None):
"""Return the corresponding BinaryPackagePublishingHistory."""
distroarchseries = binarypackagerelease.build.distro_arch_series
@@ -474,7 +477,8 @@
dateremoved=dateremoved,
datecreated=UTC_NOW,
pocket=pocket,
- archive=archive)
+ archive=archive,
+ phased_update_percentage=phased_update_percentage)
if status == PackagePublishingStatus.PUBLISHED:
pub.datepublished = UTC_NOW
pub_binaries.append(pub)
@@ -1689,6 +1693,11 @@
if "new_priority" in kwargs:
self.assertEqual(
kwargs["new_priority"], new_pub.priority.name.lower())
+ if "new_phased_update_percentage" in kwargs:
+ self.assertEqual(
+ kwargs["new_phased_update_percentage"],
+ new_pub.phased_update_percentage)
+ return new_pub
def assertCannotOverride(self, **kwargs):
self.assertRaises(OverrideError, self.setUpOverride, **kwargs)
@@ -1701,7 +1710,16 @@
# BPPH.changeOverride changes the properties of binary publications.
self.assertCanOverride(
binary=True,
- new_component="universe", new_section="misc", new_priority="extra")
+ new_component="universe", new_section="misc", new_priority="extra",
+ new_phased_update_percentage=90)
+
+ def test_set_and_clear_phased_update_percentage(self):
+ # new_phased_update_percentage=<integer> sets a phased update
+ # percentage; new_phased_update_percentage=100 clears it.
+ pub = self.assertCanOverride(
+ binary=True, new_phased_update_percentage=50)
+ new_pub = pub.changeOverride(new_phased_update_percentage=100)
+ self.assertIsNone(new_pub.phased_update_percentage)
def test_no_change(self):
# changeOverride does not create a new publication if the existing