launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #03811
[Merge] lp:~jtv/launchpad/db-bug-790761 into lp:launchpad/db-devel
Jeroen T. Vermeulen has proposed merging lp:~jtv/launchpad/db-bug-790761 into lp:launchpad/db-devel.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
Related bugs:
Bug #790761 in Launchpad itself: "Package copy policy adapter"
https://bugs.launchpad.net/launchpad/+bug/790761
For more details, see:
https://code.launchpad.net/~jtv/launchpad/db-bug-790761/+merge/63202
= Summary =
We need to look up package-copying policies based on the database representation.
== Proposed fix ==
A new interface ICopyPolicy defines what we expect from copy policies. A new adapter gives you the ICopyPolicy for a given PackageCopyPolicy enum value.
== Pre-implementation notes ==
This was originally spec'ed to take an identifying policy string. But pushback on review for a preceding branch made us use an enumeration instead.
The ZCML registration for the adapter actually takes any DBItem value; I'm not aware of any way to adapt a specific DBEnumeration. Robert didn't seem to know of one either.
== Implementation details ==
I rearranged the copy-policy inheritance hierarchy a bit. Now that there's an interface for ICopyPolicy, there's no more need for a base class that raises "not implemented" errors from every method. Instead we can have tests verify directly whether each policy class implements the interface, which is slightly more like what a compiled language would do.
That also made it reasonable to remodel the two concrete policy classes not as one being a modification of the other, but as both delegating most of their logic to a separate base class. That should make the consequences of change slightly easier to oversee: the style rule of "don't inherit from concrete classes" is surprisingly effective at improving designs.
== Tests ==
{{{
./bin/test -vvc lp.soyuz.adapters.tests.test_copypolicy
./bin/test -vvc lp.soyuz.browser.tests.test_archive
}}}
== Demo and Q/A ==
The new stuff isn't really reachable yet. As long as we can still request package updates and asynchronous package syncs, it should be fine.
= Launchpad lint =
Checking for conflicts and issues in changed files.
Linting changed files:
lib/lp/soyuz/adapters/tests/test_copypolicy.py
lib/lp/soyuz/adapters/copypolicy.py
lib/lp/soyuz/model/packagecopyjob.py
lib/lp/registry/interfaces/distroseriesdifference.py
database/schema/patch-2208-73-0.sql
lib/lp/soyuz/configure.zcml
lib/lp/soyuz/interfaces/packagecopyjob.py
lib/lp/testing/factory.py
lib/lp/soyuz/tests/test_packagecopyjob.py
lib/lp/registry/browser/tests/test_distroseries.py
lib/lp/soyuz/enums.py
lib/lp/soyuz/interfaces/copypolicy.py
lib/lp/soyuz/browser/tests/test_package_copying_mixin.py
lib/lp/registry/browser/distroseries.py
lib/lp/soyuz/browser/archive.py
--
https://code.launchpad.net/~jtv/launchpad/db-bug-790761/+merge/63202
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~jtv/launchpad/db-bug-790761 into lp:launchpad/db-devel.
=== added file 'database/schema/patch-2208-73-0.sql'
--- database/schema/patch-2208-73-0.sql 1970-01-01 00:00:00 +0000
+++ database/schema/patch-2208-73-0.sql 2011-06-02 08:48:54 +0000
@@ -0,0 +1,16 @@
+-- Copyright 2011 Canonical Ltd. This software is licensed under the
+-- GNU Affero General Public License version 3 (see the file LICENSE).
+
+SET client_min_messages=ERROR;
+
+-- Thanks to our feature flags, this table should be empty. If not,
+-- adding a NOT NULL column would be problematic!
+ALTER TABLE PackageCopyJob ADD COLUMN package_name text NOT NULL;
+
+ALTER TABLE PackageCopyJob ADD COLUMN copy_policy integer;
+
+-- For getPendingJobsForTargetSeries, which happens on web-request time.
+CREATE UNIQUE INDEX packagecopyjob__job_type__target_ds__id__key
+ ON PackageCopyJob(job_type, target_distroseries, id);
+
+INSERT INTO LaunchpadDatabaseRevision VALUES (2208, 73, 0);
=== modified file 'lib/lp/registry/browser/distroseries.py'
--- lib/lp/registry/browser/distroseries.py 2011-05-28 11:35:38 +0000
+++ lib/lp/registry/browser/distroseries.py 2011-06-02 08:48:54 +0000
@@ -109,7 +109,6 @@
)
from lp.soyuz.interfaces.packagecopyjob import IPlainPackageCopyJobSource
from lp.soyuz.interfaces.queue import IPackageUploadSet
-from lp.soyuz.model.packagecopyjob import specify_dsd_package
from lp.soyuz.model.queue import PackageUploadQueue
from lp.translations.browser.distroseries import (
check_distroseries_translations_viewable,
@@ -860,8 +859,7 @@
def pending_syncs(self):
"""Pending synchronization jobs for this distroseries.
- :return: A dict mapping (name, version) package specifications to
- pending sync jobs.
+ :return: A dict mapping package names to pending sync jobs.
"""
job_source = getUtility(IPlainPackageCopyJobSource)
return job_source.getPendingJobsPerPackage(self.context)
@@ -883,7 +881,8 @@
def hasPendingSync(self, dsd):
"""Is there a package-copying job pending to resolve `dsd`?"""
- return self.pending_syncs.get(specify_dsd_package(dsd)) is not None
+ pending_sync = self.pending_syncs.get(dsd.source_package_name.name)
+ return pending_sync is not None
def isNewerThanParent(self, dsd):
"""Is the child's version of this package newer than the parent's?
@@ -1082,9 +1081,10 @@
for dsd in self.getUpgrades():
job_source.create(
- [specify_dsd_package(dsd)], dsd.parent_series.main_archive,
- target_archive, target_distroseries,
- PackagePublishingPocket.UPDATES)
+ dsd.source_package_name.name,
+ dsd.parent_series.main_archive, target_archive,
+ target_distroseries, PackagePublishingPocket.UPDATES,
+ package_version=dsd.parent_source_version)
self.request.response.addInfoNotification(
(u"Upgrades of {context.displayname} packages have been "
=== modified file 'lib/lp/registry/browser/tests/test_distroseries.py'
--- lib/lp/registry/browser/tests/test_distroseries.py 2011-05-31 11:55:38 +0000
+++ lib/lp/registry/browser/tests/test_distroseries.py 2011-06-02 08:48:54 +0000
@@ -75,7 +75,6 @@
)
from lp.soyuz.model.archivepermission import ArchivePermission
from lp.soyuz.model import distroseriesdifferencejob
-from lp.soyuz.model.packagecopyjob import specify_dsd_package
from lp.testing import (
anonymous_logged_in,
celebrity_logged_in,
@@ -1058,11 +1057,8 @@
self.assertEquals(1, len(jobs))
job = jobs[0]
self.assertEquals(series, job.target_distroseries)
- source_package_info = list(job.source_packages)
- self.assertEquals(1, len(source_package_info))
- self.assertEqual(
- (dsd.source_package_name.name, dsd.parent_source_version),
- source_package_info[0][:2])
+ self.assertEqual(dsd.source_package_name.name, job.package_name)
+ self.assertEqual(dsd.parent_source_version, job.package_version)
def test_upgrade_gives_feedback(self):
# requestUpgrades doesn't instantly perform package upgrades,
@@ -1392,7 +1388,7 @@
dsd = self.factory.makeDistroSeriesDifference()
view = create_initialized_view(
dsd.derived_series, '+localpackagediffs')
- view.pending_syncs = {specify_dsd_package(dsd): object()}
+ view.pending_syncs = {dsd.source_package_name.name: object()}
self.assertTrue(view.hasPendingSync(dsd))
def test_isNewerThanParent_compares_versions_not_strings(self):
@@ -1444,7 +1440,7 @@
dsd = self.factory.makeDistroSeriesDifference()
view = create_initialized_view(
dsd.derived_series, '+localpackagediffs')
- view.pending_syncs = {specify_dsd_package(dsd): object()}
+ view.pending_syncs = {dsd.source_package_name.name: object()}
self.assertFalse(view.canRequestSync(dsd))
def test_canRequestSync_returns_False_if_child_is_newer(self):
=== modified file 'lib/lp/registry/interfaces/distroseriesdifference.py'
--- lib/lp/registry/interfaces/distroseriesdifference.py 2011-05-30 07:42:21 +0000
+++ lib/lp/registry/interfaces/distroseriesdifference.py 2011-06-02 08:48:54 +0000
@@ -303,7 +303,8 @@
:param difference_type: The type of difference to include in the
results.
:type difference_type: `DistroSeriesDifferenceType`.
- :param source_package_name_filter: Package source name filter.
+ :param source_package_name_filter: Name of a source package. If
+ given, restricts the search to this package.
:type source_package_name_filter: unicode.
:param status: Only differences matching the status(es) will be
included.
=== modified file 'lib/lp/soyuz/adapters/copypolicy.py'
--- lib/lp/soyuz/adapters/copypolicy.py 2011-05-30 11:47:46 +0000
+++ lib/lp/soyuz/adapters/copypolicy.py 2011-06-02 08:48:54 +0000
@@ -15,57 +15,30 @@
]
+from zope.interface import implements
+
from lp.registry.interfaces.pocket import PackagePublishingPocket
from lp.registry.interfaces.series import SeriesStatus
-
-
-class BaseCopyPolicy:
- """Encapsulation of the policies for copying a package in Launchpad."""
-
- @property
- def name(self):
- """The name of this policy that's used in adapter lookups."""
- raise AssertionError("Subclass must provide name property")
-
- def autoApprove(self, archive, distroseries, pocket):
- """Decide if the upload can be approved automatically or
- should be held in the queue.
-
- This should only be called for packages that are known not new.
-
- :param archive: The target `IArchive` for the upload.
- :param distroseries: The target `IDistroSeries` for the upload.
- :param pocket: The target `PackagePublishingPocket` for the upload.
- """
- raise AssertionError("Subclass must provide autoApprove")
-
- def autoApproveNew(self, archive, distroseries, pocket):
- """Decide if a previously unknown package is approved automatically
- or should be held in the queue.
-
- :param archive: The target `IArchive` for the upload.
- :param distroseries: The target `IDistroSeries` for the upload.
- :param pocket: The target `PackagePublishingPocket` for the upload.
- """
- raise AssertionError("Subclass must provide autoApproveNew")
-
- @property
- def send_email(self):
- """Whether or not the copy should send emails after completing."""
- raise AssertionError("Subclass must provide send_email")
-
-
-class InsecureCopyPolicy(BaseCopyPolicy):
- """A policy for copying from insecure sources."""
-
- name = "insecure"
+from lp.soyuz.enums import PackageCopyPolicy
+from lp.soyuz.interfaces.copypolicy import ICopyPolicy
+
+
+class BasicCopyPolicy:
+ """Useful standard copy policy.
+
+ This policy auto-approves all PPA uploads. For distribution archives it
+ auto-approves only uploads to the Release pocket, and only while the
+ series is not frozen.
+ """
def autoApproveNew(self, archive, distroseries=None, pocket=None):
+ """See `ICopyPolicy`."""
if archive.is_ppa:
return True
return False
def autoApprove(self, archive, distroseries, pocket):
+ """See `ICopyPolicy`."""
if archive.is_ppa:
return True
@@ -80,16 +53,34 @@
return False
- @property
- def send_email(self):
- return True
-
-
-class SyncCopyPolicy(InsecureCopyPolicy):
+
+class InsecureCopyPolicy(BasicCopyPolicy):
+ """A policy for copying from insecure sources."""
+ implements(ICopyPolicy)
+
+ enum_value = PackageCopyPolicy.INSECURE
+
+ send_email = True
+
+
+class SyncCopyPolicy(BasicCopyPolicy):
"""A policy for mass 'sync' copies."""
-
- name = "sync"
-
- @property
- def send_email(self):
- return False
+ implements(ICopyPolicy)
+
+ enum_value = PackageCopyPolicy.SYNC
+
+ send_email = False
+
+
+policies = [
+ InsecureCopyPolicy,
+ SyncCopyPolicy,
+ ]
+
+
+enum_to_policy = dict((policy.enum_value, policy()) for policy in policies)
+
+
+def get_icopypolicy_for_packagecopypolicy(packagecopypolicy):
+ """Look up the `ICopyPolicy` for a given `PackageCopyPolicy`."""
+ return enum_to_policy[packagecopypolicy]
=== modified file 'lib/lp/soyuz/adapters/tests/test_copypolicy.py'
--- lib/lp/soyuz/adapters/tests/test_copypolicy.py 2011-05-30 11:47:46 +0000
+++ lib/lp/soyuz/adapters/tests/test_copypolicy.py 2011-06-02 08:48:54 +0000
@@ -1,16 +1,20 @@
# Copyright 2011 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
+from canonical.launchpad.webapp.testing import verifyObject
from canonical.testing.layers import ZopelessDatabaseLayer
-from lp.testing import TestCaseWithFactory
-
from lp.registry.interfaces.pocket import PackagePublishingPocket
from lp.registry.interfaces.series import SeriesStatus
from lp.soyuz.adapters.copypolicy import (
InsecureCopyPolicy,
SyncCopyPolicy,
)
-from lp.soyuz.enums import ArchivePurpose
+from lp.soyuz.interfaces.copypolicy import ICopyPolicy
+from lp.soyuz.enums import (
+ ArchivePurpose,
+ PackageCopyPolicy,
+ )
+from lp.testing import TestCaseWithFactory
class TestCopyPolicy(TestCaseWithFactory):
@@ -30,7 +34,8 @@
self.assertTrue(approved)
def assertUnapproved(self, archive_purpose, method):
- archive, distroseries, pocket = self._getUploadCriteria(archive_purpose)
+ archive, distroseries, pocket = self._getUploadCriteria(
+ archive_purpose)
approved = method(archive, distroseries, pocket)
self.assertFalse(approved)
@@ -42,7 +47,7 @@
cp = InsecureCopyPolicy()
self.assertApproved(ArchivePurpose.PPA, cp.autoApproveNew)
- def test_insecure_approves_existing_distro_package_to_unfrozen_release(self):
+ def test_insecure_approves_known_distro_package_to_unfrozen_release(self):
archive = self.factory.makeArchive(purpose=ArchivePurpose.PRIMARY)
distroseries = self.factory.makeDistroSeries()
pocket = PackagePublishingPocket.RELEASE
@@ -80,8 +85,6 @@
cp = SyncCopyPolicy()
self.assertFalse(cp.send_email)
- def test_policy_names(self):
- icp = InsecureCopyPolicy()
- scp = SyncCopyPolicy()
- self.assertEquals("insecure", icp.name)
- self.assertEquals("sync", scp.name)
+ def test_policies_implement_ICopyPolicy(self):
+ for policy in PackageCopyPolicy.items:
+ self.assertTrue(verifyObject(ICopyPolicy, ICopyPolicy(policy)))
=== modified file 'lib/lp/soyuz/browser/archive.py'
--- lib/lp/soyuz/browser/archive.py 2011-05-29 21:18:09 +0000
+++ lib/lp/soyuz/browser/archive.py 2011-06-02 08:48:54 +0000
@@ -1285,17 +1285,6 @@
dest_display_name)
-def name_pubs_with_versions(source_pubs):
- """Annotate each entry from `source_pubs` with its version.
-
- :param source_pubs: A sequence of `SourcePackagePublishingHistory`.
- :return: A list of tuples (name, version), one for each respective
- entry in `source_pubs`.
- """
- sprs = [spph.sourcepackagerelease for spph in source_pubs]
- return [(spr.sourcepackagename.name, spr.version) for spr in sprs]
-
-
def copy_asynchronously(source_pubs, dest_archive, dest_series, dest_pocket,
include_binaries, dest_url=None,
dest_display_name=None, person=None,
@@ -1317,8 +1306,9 @@
job_source = getUtility(IPlainPackageCopyJobSource)
for spph in source_pubs:
job_source.create(
- name_pubs_with_versions([spph]), spph.archive, dest_archive,
- dest_series, dest_pocket, include_binaries=include_binaries)
+ spph.source_package_name, spph.archive, dest_archive, dest_series,
+ dest_pocket, include_binaries=include_binaries,
+ package_version=spph.sourcepackagerelease.version)
return structured("""
<p>Requested sync of %s packages.</p>
=== modified file 'lib/lp/soyuz/browser/tests/test_package_copying_mixin.py'
--- lib/lp/soyuz/browser/tests/test_package_copying_mixin.py 2011-05-26 09:07:28 +0000
+++ lib/lp/soyuz/browser/tests/test_package_copying_mixin.py 2011-06-02 08:48:54 +0000
@@ -17,7 +17,6 @@
copy_asynchronously,
copy_synchronously,
FEATURE_FLAG_MAX_SYNCHRONOUS_SYNCS,
- name_pubs_with_versions,
PackageCopyingMixin,
render_cannotcopy_as_html,
)
@@ -103,15 +102,6 @@
packages = [self.getUniqueString() for counter in range(300)]
self.assertFalse(PackageCopyingMixin().canCopySynchronously(packages))
- def test_name_pubs_with_versions_lists_packages_and_versions(self):
- # name_pubs_with_versions returns a list of tuples of source
- # package name and source package version, one per SPPH.
- spph = FakeSPPH()
- spr = spph.sourcepackagerelease
- self.assertEqual(
- [(spr.sourcepackagename.name, spr.version)],
- name_pubs_with_versions([spph]))
-
def test_render_cannotcopy_as_html_lists_errors(self):
# render_cannotcopy_as_html includes a CannotCopy error message
# into its HTML notice.
@@ -257,10 +247,10 @@
jobs = list(getUtility(IPlainPackageCopyJobSource).getActiveJobs(
archive))
self.assertEqual(1, len(jobs))
+ job = jobs[0]
spr = spph.sourcepackagerelease
- self.assertEqual(
- [[spr.sourcepackagename.name, spr.version]],
- jobs[0].metadata['source_packages'])
+ self.assertEqual(spr.sourcepackagename.name, job.package_name)
+ self.assertEqual(spr.version, job.package_version)
def test_do_copy_goes_async_if_canCopySynchronously_says_so(self):
# The view opts for asynchronous copying if canCopySynchronously
=== modified file 'lib/lp/soyuz/configure.zcml'
--- lib/lp/soyuz/configure.zcml 2011-05-29 21:18:09 +0000
+++ lib/lp/soyuz/configure.zcml 2011-06-02 08:48:54 +0000
@@ -919,6 +919,18 @@
<allow interface=".interfaces.packagecopyjob.IPlainPackageCopyJob" />
</class>
+ <!-- PackageCopyPolicy -->
+ <class class=".adapters.copypolicy.InsecureCopyPolicy">
+ <allow interface=".interfaces.copypolicy.ICopyPolicy"/>
+ </class>
+ <class class=".adapters.copypolicy.SyncCopyPolicy">
+ <allow interface=".interfaces.copypolicy.ICopyPolicy"/>
+ </class>
+ <adapter
+ for="lazr.enum.DBItem"
+ provides="lp.soyuz.interfaces.copypolicy.ICopyPolicy"
+ factory="lp.soyuz.adapters.copypolicy.get_icopypolicy_for_packagecopypolicy"/>
+
<!-- PackageCopyRequestSet -->
<securedutility
class="lp.soyuz.model.packagecopyrequest.PackageCopyRequestSet"
=== modified file 'lib/lp/soyuz/enums.py'
--- lib/lp/soyuz/enums.py 2011-03-30 04:10:29 +0000
+++ lib/lp/soyuz/enums.py 2011-06-02 08:48:54 +0000
@@ -13,6 +13,7 @@
'archive_suffixes',
'BinaryPackageFileType',
'BinaryPackageFormat',
+ 'PackageCopyPolicy',
'PackageCopyStatus',
'PackageDiffStatus',
'PackagePublishingPriority',
@@ -234,6 +235,25 @@
in Ubuntu and similar distributions.""")
+class PackageCopyPolicy(DBEnumeratedType):
+ """Package copying policy.
+
+ Each of these is associated with one `ICopyPolicy`.
+ """
+
+ INSECURE = DBItem(1, """
+ Copy from insecure source.
+
+ This is the default.
+ """)
+
+ SYNC = DBItem(2, """
+ Package sync.
+
+ This policy applies when synchronizing packages en masses.
+ """)
+
+
class PackageCopyStatus(DBEnumeratedType):
"""Package copy status type.
@@ -282,7 +302,6 @@
class PackageDiffStatus(DBEnumeratedType):
"""The status of a PackageDiff request."""
-
PENDING = DBItem(0, """
Pending
=== added file 'lib/lp/soyuz/interfaces/copypolicy.py'
--- lib/lp/soyuz/interfaces/copypolicy.py 1970-01-01 00:00:00 +0000
+++ lib/lp/soyuz/interfaces/copypolicy.py 2011-06-02 08:48:54 +0000
@@ -0,0 +1,53 @@
+# Copyright 2011 Canonical Ltd. This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""Package copy policies."""
+
+__metaclass__ = type
+__all__ = [
+ 'ICopyPolicy',
+ ]
+
+
+from zope.interface import Interface
+from zope.schema import (
+ Bool,
+ Choice,
+ )
+
+from canonical.launchpad import _
+from lp.soyuz.enums import PackageCopyPolicy
+
+
+class ICopyPolicy(Interface):
+ """Policies for copying packages, as enumerated by `PackageCopyPolicy`."""
+
+ enum_value = Choice(
+ title=_("PackageCopyPolicy number associated with this policy."),
+ values=PackageCopyPolicy, readonly=True, required=True)
+
+ send_email = Bool(
+ title=_("Should completion of this copy be announced by email?"),
+ readonly=True, required=True)
+
+ def autoApprove(archive, distroseries, pocket):
+ """Can this upload of a known package be approved automatically?
+
+ This should only be called for packages that are known not new.
+
+ :param archive: The target `IArchive` for the upload.
+ :param distroseries: The target `IDistroSeries` for the upload.
+ :param pocket: The target `PackagePublishingPocket` for the upload.
+ :return: True if the upload can be auto-approved, or False if it
+ should be held in the queue.
+ """
+
+ def autoApproveNew(archive, distroseries, pocket):
+ """Can this upload of a new package be approved automatically?
+
+ :param archive: The target `IArchive` for the upload.
+ :param distroseries: The target `IDistroSeries` for the upload.
+ :param pocket: The target `PackagePublishingPocket` for the upload.
+ :return: True if the upload can be auto-approved, or False if it
+ should be held in the queue.
+ """
=== modified file 'lib/lp/soyuz/interfaces/packagecopyjob.py'
--- lib/lp/soyuz/interfaces/packagecopyjob.py 2011-05-19 09:53:13 +0000
+++ lib/lp/soyuz/interfaces/packagecopyjob.py 2011-06-02 08:48:54 +0000
@@ -22,8 +22,7 @@
from zope.schema import (
Bool,
Int,
- List,
- Tuple,
+ TextLine,
)
from canonical.launchpad import _
@@ -63,6 +62,9 @@
schema=IDistroSeries, title=_('Target DistroSeries.'),
required=True, readonly=True)
+ package_name = TextLine(
+ title=_("Package name"), required=True, readonly=True)
+
job = Reference(
schema=IJob, title=_('The common Job attributes'),
required=True, readonly=True)
@@ -82,14 +84,13 @@
class IPlainPackageCopyJobSource(IJobSource):
"""An interface for acquiring `IPackageCopyJobs`."""
- def create(cls, source_packages, source_archive,
+ def create(cls, package_name, source_archive,
target_archive, target_distroseries, target_pocket,
- include_binaries=False):
+ include_binaries=False, package_version=None,
+ copy_policy=None):
"""Create a new `IPackageCopyJob`.
- :param source_packages: An iterable of `(source_package_name,
- version)` tuples, where both `source_package_name` and `version`
- are strings.
+ :param package_name: The name of the source package to copy.
:param source_archive: The `IArchive` in which `source_packages` are
found.
:param target_archive: The `IArchive` to which to copy the packages.
@@ -98,6 +99,9 @@
:param target_pocket: The pocket into which to copy the packages. Must
be a member of `PackagePublishingPocket`.
:param include_binaries: See `do_copy`.
+ :param package_version: The version string for the package version
+ that is to be copied.
+ :param copy_policy: Applicable `PackageCopyPolicy`.
"""
def getActiveJobs(target_archive):
@@ -107,7 +111,7 @@
"""Find pending jobs for each package in `target_series`.
This is meant for finding jobs that will resolve specific
- `DistroSeriesDifference`s, so see also `specify_dsd_package`.
+ `DistroSeriesDifference`s.
:param target_series: Target `DistroSeries`; this corresponds to
`DistroSeriesDifference.derived_series`.
@@ -122,15 +126,13 @@
class IPlainPackageCopyJob(IRunnableJob):
"""A no-frills job to copy packages between `IArchive`s."""
- source_packages = List(
- title=_("Source Packages"),
- value_type=Tuple(min_length=3, max_length=3),
- required=True, readonly=True)
-
target_pocket = Int(
title=_("Target package publishing pocket"), required=True,
readonly=True)
+ package_version = TextLine(
+ title=_("Package version"), required=True, readonly=True)
+
include_binaries = Bool(
title=_("Copy binaries"),
required=False, readonly=True)
=== modified file 'lib/lp/soyuz/model/packagecopyjob.py'
--- lib/lp/soyuz/model/packagecopyjob.py 2011-05-31 13:31:05 +0000
+++ lib/lp/soyuz/model/packagecopyjob.py 2011-06-02 08:48:54 +0000
@@ -6,7 +6,6 @@
__all__ = [
"PackageCopyJob",
"PlainPackageCopyJob",
- "specify_dsd_package",
]
from lazr.delegates import delegates
@@ -38,7 +37,6 @@
IDistroSeriesDifferenceSource,
)
from lp.registry.interfaces.pocket import PackagePublishingPocket
-from lp.registry.interfaces.sourcepackagename import ISourcePackageNameSet
from lp.registry.model.distroseries import DistroSeries
from lp.registry.interfaces.distroseriesdifferencecomment import (
IDistroSeriesDifferenceCommentSource,
@@ -47,6 +45,7 @@
from lp.services.job.interfaces.job import JobStatus
from lp.services.job.model.job import Job
from lp.services.job.runner import BaseRunnableJob
+from lp.soyuz.enums import PackageCopyPolicy
from lp.soyuz.interfaces.archive import CannotCopy
from lp.soyuz.interfaces.packagecopyjob import (
IPackageCopyJob,
@@ -58,17 +57,6 @@
from lp.soyuz.scripts.packagecopier import do_copy
-def specify_dsd_package(dsd):
- """Return (name, parent version) for `dsd`'s package.
-
- This describes the package that `dsd` is for in a format suitable for
- `PlainPackageCopyJobSource`.
-
- :param dsd: A `DistroSeriesDifference`.
- """
- return (dsd.source_package_name.name, dsd.parent_source_version)
-
-
class PackageCopyJob(StormBase):
"""Base class for package copying jobs."""
@@ -90,18 +78,23 @@
target_distroseries_id = Int(name='target_distroseries')
target_distroseries = Reference(target_distroseries_id, DistroSeries.id)
+ package_name = Unicode('package_name')
+ copy_policy = EnumCol(enum=PackageCopyPolicy)
+
job_type = EnumCol(enum=PackageCopyJobType, notNull=True)
_json_data = Unicode('json_data')
def __init__(self, source_archive, target_archive, target_distroseries,
- job_type, metadata):
+ job_type, metadata, package_name=None, copy_policy=None):
super(PackageCopyJob, self).__init__()
self.job = Job()
+ self.job_type = job_type
self.source_archive = source_archive
self.target_archive = target_archive
self.target_distroseries = target_distroseries
- self.job_type = job_type
+ self.package_name = unicode(package_name)
+ self.copy_policy = copy_policy
self._json_data = self.serializeMetadata(metadata)
@classmethod
@@ -168,7 +161,7 @@
class PlainPackageCopyJob(PackageCopyJobDerived):
- """Job that copies packages between archives."""
+ """Job that copies a package from one archive to another."""
# This job type serves in different places: it supports copying
# packages between archives, but also the syncing of packages from
# parents into a derived distroseries. We may split these into
@@ -181,20 +174,24 @@
classProvides(IPlainPackageCopyJobSource)
@classmethod
- def create(cls, source_packages, source_archive,
+ def create(cls, package_name, source_archive,
target_archive, target_distroseries, target_pocket,
- include_binaries=False):
+ include_binaries=False, package_version=None,
+ copy_policy=None):
"""See `IPlainPackageCopyJobSource`."""
+ assert package_version is not None, "No package version specified."
metadata = {
- 'source_packages': source_packages,
'target_pocket': target_pocket.value,
+ 'package_version': package_version,
'include_binaries': bool(include_binaries),
}
job = PackageCopyJob(
+ job_type=cls.class_job_type,
source_archive=source_archive,
target_archive=target_archive,
target_distroseries=target_distroseries,
- job_type=cls.class_job_type,
+ package_name=package_name,
+ copy_policy=copy_policy,
metadata=metadata)
IMasterStore(PackageCopyJob).add(job)
return cls(job)
@@ -232,22 +229,18 @@
# getPendingJobsForTargetSeries orders its results, the first
# will be the oldest and thus presumably the first to finish.
for job in cls.getPendingJobsForTargetSeries(target_series):
- for package in job.metadata["source_packages"]:
- result.setdefault(tuple(package), job)
+ result.setdefault(job.package_name, job)
return result
@property
- def source_packages(self):
- getPublishedSources = self.source_archive.getPublishedSources
- for name, version in self.metadata['source_packages']:
- yield name, version, getPublishedSources(
- name=name, version=version, exact_match=True).first()
-
- @property
def target_pocket(self):
return PackagePublishingPocket.items[self.metadata['target_pocket']]
@property
+ def package_version(self):
+ return self.metadata["package_version"]
+
+ @property
def include_binaries(self):
return self.metadata['include_binaries']
@@ -270,16 +263,15 @@
raise CannotCopy(
"Destination pocket must be 'release' for a PPA.")
- source_packages = set()
- for name, version, source_package in self.source_packages:
- if source_package is None:
- raise CannotCopy(
- "Package %r %r not found." % (name, version))
- else:
- source_packages.add(source_package)
+ name = self.package_name
+ version = self.package_version
+ source_package = self.source_archive.getPublishedSources(
+ name=name, version=version, exact_match=True).first()
+ if source_package is None:
+ raise CannotCopy("Package %r %r not found." % (name, version))
do_copy(
- sources=source_packages, archive=self.target_archive,
+ sources=[source_package], archive=self.target_archive,
series=self.target_distroseries, pocket=self.target_pocket,
include_binaries=self.include_binaries, check_permissions=False)
@@ -292,20 +284,18 @@
dsd_source = getUtility(IDistroSeriesDifferenceSource)
target_series = self.target_distroseries
candidates = dsd_source.getForDistroSeries(
- distro_series=target_series)
+ distro_series=target_series,
+ source_package_name_filter=self.package_name)
+
# The job doesn't know what distroseries a given package is
# coming from, and the version number in the DSD may have
# changed. We can however filter out DSDs that are from
# different distributions, based on the job's target archive.
source_distro_id = self.source_archive.distributionID
- package_ids = set(
- getUtility(ISourcePackageNameSet).queryByName(name).id
- for name, version in self.metadata["source_packages"])
return [
dsd
for dsd in candidates
- if dsd.parent_series.distributionID == source_distro_id and
- dsd.source_package_name_id in package_ids]
+ if dsd.parent_series.distributionID == source_distro_id]
def reportFailure(self, cannotcopy_exception):
"""Attempt to report failure to the user."""
@@ -326,11 +316,10 @@
def __repr__(self):
"""Returns an informative representation of the job."""
parts = ["%s to copy" % self.__class__.__name__]
- source_packages = self.metadata["source_packages"]
- if len(source_packages) == 0:
- parts.append(" no packages (!)")
+ if self.package_name is None:
+ parts.append(" no package (!)")
else:
- parts.append(" %d package(s)" % len(source_packages))
+ parts.append(" package %s" % self.package_name)
parts.append(
" from %s/%s" % (
self.source_archive.distribution.name,
=== modified file 'lib/lp/soyuz/tests/test_packagecopyjob.py'
--- lib/lp/soyuz/tests/test_packagecopyjob.py 2011-05-30 07:56:15 +0000
+++ lib/lp/soyuz/tests/test_packagecopyjob.py 2011-06-02 08:48:54 +0000
@@ -33,7 +33,6 @@
from lp.soyuz.interfaces.sourcepackageformat import (
ISourcePackageFormatSelectionSet,
)
-from lp.soyuz.model.packagecopyjob import specify_dsd_package
from lp.soyuz.tests.test_publishing import SoyuzTestPublisher
from lp.testing import (
run_script,
@@ -56,14 +55,14 @@
"""Create a `PlainPackageCopyJob` that would resolve `dsd`."""
if dsd is None:
dsd = self.factory.makeDistroSeriesDifference()
- source_packages = [specify_dsd_package(dsd)]
source_archive = dsd.parent_series.main_archive
target_archive = dsd.derived_series.main_archive
target_distroseries = dsd.derived_series
target_pocket = self.factory.getAnyPocket()
return getUtility(IPlainPackageCopyJobSource).create(
- source_packages, source_archive, target_archive,
- target_distroseries, target_pocket)
+ dsd.source_package_name.name, source_archive, target_archive,
+ target_distroseries, target_pocket,
+ package_version=dsd.parent_source_version)
def runJob(self, job):
"""Helper to switch to the right DB user and run the job."""
@@ -84,11 +83,10 @@
archive2 = self.factory.makeArchive(distroseries.distribution)
source = getUtility(IPlainPackageCopyJobSource)
job = source.create(
- source_packages=[("foo", "1.0-1"), ("bar", "2.4")],
- source_archive=archive1, target_archive=archive2,
- target_distroseries=distroseries,
+ package_name="foo", source_archive=archive1,
+ target_archive=archive2, target_distroseries=distroseries,
target_pocket=PackagePublishingPocket.RELEASE,
- include_binaries=False)
+ package_version="1.0-1", include_binaries=False)
self.assertProvides(job, IPackageCopyJob)
self.assertEquals(archive1.id, job.source_archive_id)
self.assertEquals(archive1, job.source_archive)
@@ -96,9 +94,8 @@
self.assertEquals(archive2, job.target_archive)
self.assertEquals(distroseries, job.target_distroseries)
self.assertEquals(PackagePublishingPocket.RELEASE, job.target_pocket)
- self.assertContentEqual(
- job.source_packages,
- [("foo", "1.0-1", None), ("bar", "2.4", None)])
+ self.assertEqual("foo", job.package_name)
+ self.assertEqual("1.0-1", job.package_version)
self.assertEquals(False, job.include_binaries)
def test_getActiveJobs(self):
@@ -108,10 +105,10 @@
archive2 = self.factory.makeArchive(distroseries.distribution)
source = getUtility(IPlainPackageCopyJobSource)
job = source.create(
- source_packages=[("foo", "1.0-1")], source_archive=archive1,
+ package_name="foo", source_archive=archive1,
target_archive=archive2, target_distroseries=distroseries,
target_pocket=PackagePublishingPocket.RELEASE,
- include_binaries=False)
+ package_version="1.0-1", include_binaries=False)
self.assertContentEqual([job], source.getActiveJobs(archive2))
def test_getActiveJobs_gets_oldest_first(self):
@@ -167,10 +164,10 @@
archive2 = self.factory.makeArchive(distroseries.distribution)
job_source = getUtility(IPlainPackageCopyJobSource)
job = job_source.create(
- source_packages=[("foo", "1.0-1")], source_archive=archive1,
+ package_name="foo", source_archive=archive1,
target_archive=archive2, target_distroseries=distroseries,
target_pocket=PackagePublishingPocket.RELEASE,
- include_binaries=False)
+ package_version="1.0-1", include_binaries=False)
naked_job = removeSecurityProxy(job)
naked_job.reportFailure = FakeMethod()
@@ -181,14 +178,15 @@
def test_target_ppa_non_release_pocket(self):
# When copying to a PPA archive the target must be the release pocket.
distroseries = self.factory.makeDistroSeries()
+ package = self.factory.makeSourcePackageName()
archive1 = self.factory.makeArchive(distroseries.distribution)
archive2 = self.factory.makeArchive(distroseries.distribution)
source = getUtility(IPlainPackageCopyJobSource)
job = source.create(
- source_packages=[], source_archive=archive1,
+ package_name=package.name, source_archive=archive1,
target_archive=archive2, target_distroseries=distroseries,
target_pocket=PackagePublishingPocket.UPDATES,
- include_binaries=False)
+ include_binaries=False, package_version='1.0')
naked_job = removeSecurityProxy(job)
naked_job.reportFailure = FakeMethod()
@@ -218,20 +216,20 @@
getUtility(ISourcePackageFormatSelectionSet).add(
target_series, SourcePackageFormat.FORMAT_1_0)
- source_package = publisher.getPubSource(
+ publisher.getPubSource(
distroseries=distroseries, sourcename="libc",
version="2.8-1", status=PackagePublishingStatus.PUBLISHED,
archive=breezy_archive)
source = getUtility(IPlainPackageCopyJobSource)
job = source.create(
- source_packages=[("libc", "2.8-1")],
+ package_name="libc",
source_archive=breezy_archive, target_archive=target_archive,
target_distroseries=target_series,
target_pocket=PackagePublishingPocket.RELEASE,
- include_binaries=False)
- self.assertContentEqual(
- job.source_packages, [("libc", "2.8-1", source_package)])
+ package_version="2.8-1", include_binaries=False)
+ self.assertEqual("libc", job.package_name)
+ self.assertEqual("2.8-1", job.package_version)
# Make sure everything hits the database, switching db users
# aborts.
@@ -256,10 +254,10 @@
archive2 = self.factory.makeArchive(distroseries.distribution)
source = getUtility(IPlainPackageCopyJobSource)
job = source.create(
- source_packages=[("foo", "1.0-1")], source_archive=archive1,
+ package_name="foo", source_archive=archive1,
target_archive=archive2, target_distroseries=distroseries,
target_pocket=PackagePublishingPocket.RELEASE,
- include_binaries=False)
+ package_version="1.0-1", include_binaries=False)
oops_vars = job.getOopsVars()
naked_job = removeSecurityProxy(job)
self.assertIn(
@@ -285,10 +283,10 @@
version="2.8-1", status=PackagePublishingStatus.PUBLISHED,
archive=archive1)
getUtility(IPlainPackageCopyJobSource).create(
- source_packages=[("libc", "2.8-1")], source_archive=archive1,
+ package_name="libc", source_archive=archive1,
target_archive=archive2, target_distroseries=distroseries,
target_pocket=PackagePublishingPocket.RELEASE,
- include_binaries=False)
+ package_version="2.8-1", include_binaries=False)
transaction.commit()
out, err, exit_code = run_script(
@@ -309,13 +307,12 @@
archive2 = self.factory.makeArchive(distroseries.distribution)
source = getUtility(IPlainPackageCopyJobSource)
job = source.create(
- source_packages=[("foo", "1.0-1"), ("bar", "2.4")],
- source_archive=archive1, target_archive=archive2,
- target_distroseries=distroseries,
+ package_name="foo", source_archive=archive1,
+ target_archive=archive2, target_distroseries=distroseries,
target_pocket=PackagePublishingPocket.RELEASE,
- include_binaries=True)
+ package_version="1.0-1", include_binaries=True)
self.assertEqual(
- ("<PlainPackageCopyJob to copy 2 package(s) from "
+ ("<PlainPackageCopyJob to copy package foo from "
"{distroseries.distribution.name}/{archive1.name} to "
"{distroseries.distribution.name}/{archive2.name}, "
"RELEASE pocket, in {distroseries.distribution.name} "
@@ -331,7 +328,7 @@
job = self.makeJob(dsd)
job_source = getUtility(IPlainPackageCopyJobSource)
self.assertEqual(
- {specify_dsd_package(dsd): job},
+ {dsd.source_package_name.name: job},
job_source.getPendingJobsPerPackage(dsd.derived_series))
def test_getPendingJobsPerPackage_ignores_other_distroseries(self):
@@ -347,7 +344,6 @@
# getPendingJobsPerPackage ignores jobs that have already been
# run.
dsd = self.factory.makeDistroSeriesDifference()
- package = specify_dsd_package(dsd)
job = self.makeJob(dsd)
job_source = getUtility(IPlainPackageCopyJobSource)
found_by_state = {}
@@ -355,7 +351,7 @@
removeSecurityProxy(job).job._status = status
result = job_source.getPendingJobsPerPackage(dsd.derived_series)
if len(result) > 0:
- found_by_state[status] = result[package]
+ found_by_state[status] = result[dsd.source_package_name.name]
expected = {
JobStatus.WAITING: job,
JobStatus.RUNNING: job,
@@ -374,7 +370,7 @@
jobs = map(self.makeJob, dsds)
job_source = getUtility(IPlainPackageCopyJobSource)
self.assertEqual(
- dict(zip(map(specify_dsd_package, dsds), jobs)),
+ dict(zip([dsd.source_package_name.name for dsd in dsds], jobs)),
job_source.getPendingJobsPerPackage(derived_series))
def test_getPendingJobsPerPackage_picks_oldest_job_for_dsd(self):
@@ -384,7 +380,7 @@
jobs = [self.makeJob(dsd) for counter in xrange(2)]
job_source = getUtility(IPlainPackageCopyJobSource)
self.assertEqual(
- {specify_dsd_package(dsd): jobs[0]},
+ {dsd.source_package_name.name: jobs[0]},
job_source.getPendingJobsPerPackage(dsd.derived_series))
def test_getPendingJobsPerPackage_ignores_dsds_without_jobs(self):
=== modified file 'lib/lp/testing/factory.py'
--- lib/lp/testing/factory.py 2011-05-29 21:18:09 +0000
+++ lib/lp/testing/factory.py 2011-06-02 08:48:54 +0000
@@ -4084,7 +4084,6 @@
if package_name is None and package_version is None:
package_name = self.makeSourcePackageName().name
package_version = unicode(self.getUniqueInteger()) + 'version'
- package_tuple = (package_name, package_version)
if source_archive is None:
source_archive = self.makeArchive()
if target_archive is None:
@@ -4094,8 +4093,9 @@
if target_pocket is None:
target_pocket = self.getAnyPocket()
return getUtility(IPlainPackageCopyJobSource).create(
- package_tuple, source_archive, target_archive,
- target_distroseries, target_pocket)
+ package_name, source_archive, target_archive,
+ target_distroseries, target_pocket,
+ package_version=package_version)
# Some factory methods return simple Python types. We don't add