← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~allenap/launchpad/package-copy-job-bug-779949-model into lp:launchpad/db-devel

 

Gavin Panella has proposed merging lp:~allenap/launchpad/package-copy-job-bug-779949-model into lp:launchpad/db-devel with lp:~allenap/launchpad/package-copy-job-bug-776283-db-patch as a prerequisite.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)
Related bugs:
  Bug #779949 in Launchpad itself: "PackageCopyJob needs separate archive and series columns for query purposes"
  https://bugs.launchpad.net/launchpad/+bug/779949

For more details, see:
https://code.launchpad.net/~allenap/launchpad/package-copy-job-bug-779949-model/+merge/60473

This is almost all boilerplate changes, to move PackageCopyJob over to
its own independent Job class hierarchy, instead of piggy-backing on
DistributionJob. It relies on a new PackageCopyJob table that has been
created in the prerequisite branch.

-- 
https://code.launchpad.net/~allenap/launchpad/package-copy-job-bug-779949-model/+merge/60473
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~allenap/launchpad/package-copy-job-bug-779949-model into lp:launchpad/db-devel.
=== modified file 'configs/development/launchpad-lazr.conf'
--- configs/development/launchpad-lazr.conf	2011-04-26 02:31:48 +0000
+++ configs/development/launchpad-lazr.conf	2011-05-10 09:36:26 +0000
@@ -144,7 +144,7 @@
 oops_prefix: IDSJ
 error_dir: /var/tmp/soyuz.test
 
-[IPackageCopyJobSource]
+[IPlainPackageCopyJobSource]
 oops_prefix: PCJ
 error_dir: /var/tmp/soyuz.test
 

=== modified file 'lib/canonical/config/schema-lazr.conf'
--- lib/canonical/config/schema-lazr.conf	2011-05-06 13:55:19 +0000
+++ lib/canonical/config/schema-lazr.conf	2011-05-10 09:36:26 +0000
@@ -1005,8 +1005,8 @@
 # See [error_reports].
 copy_to_zlog: false
 
-[IPackageCopyJobSource]
-module: lp.soyuz.interfaces.distributionjob
+[IPlainPackageCopyJobSource]
+module: lp.soyuz.interfaces.packagecopyjob
 # XXX: GavinPanella 2011-04-20 bug=770297: The sync_packages database
 # user should be renamed to copy_packages.
 dbuser: sync_packages
@@ -2144,8 +2144,8 @@
 # can be loaded from.
 job_sources:
     IMembershipNotificationJobSource,
-    IPackageCopyJobSource,
     IPersonMergeJobSource,
+    IPlainPackageCopyJobSource,
     IQuestionEmailJobSource
 
 [IMembershipNotificationJobSource]

=== modified file 'lib/lp/soyuz/configure.zcml'
--- lib/lp/soyuz/configure.zcml	2011-05-04 04:10:58 +0000
+++ lib/lp/soyuz/configure.zcml	2011-05-10 09:36:26 +0000
@@ -910,13 +910,13 @@
 
     <!-- PackageCopyJobSource -->
     <securedutility
-      component="lp.soyuz.model.packagecopyjob.PackageCopyJob"
-      provides="lp.soyuz.interfaces.distributionjob.IPackageCopyJobSource">
-        <allow interface="lp.soyuz.interfaces.distributionjob.IPackageCopyJobSource"/>
+        component=".model.packagecopyjob.PlainPackageCopyJob"
+        provides=".interfaces.packagecopyjob.IPlainPackageCopyJobSource">
+      <allow interface=".interfaces.packagecopyjob.IPlainPackageCopyJobSource"/>
     </securedutility>
-    <class class="lp.soyuz.model.packagecopyjob.PackageCopyJob">
-        <allow interface="lp.soyuz.interfaces.distributionjob.IPackageCopyJob" />
-        <allow interface="lp.soyuz.interfaces.distributionjob.IDistributionJob" />
+    <class class=".model.packagecopyjob.PlainPackageCopyJob">
+      <allow interface=".interfaces.packagecopyjob.IPackageCopyJob" />
+      <allow interface=".interfaces.packagecopyjob.IPlainPackageCopyJob" />
     </class>
 
    <webservice:register module="lp.soyuz.interfaces.webservice" />

=== modified file 'lib/lp/soyuz/interfaces/distributionjob.py'
--- lib/lp/soyuz/interfaces/distributionjob.py	2011-05-05 17:59:36 +0000
+++ lib/lp/soyuz/interfaces/distributionjob.py	2011-05-10 09:36:26 +0000
@@ -10,25 +10,19 @@
     "IDistroSeriesDifferenceJobSource",
     "IInitialiseDistroSeriesJob",
     "IInitialiseDistroSeriesJobSource",
-    "IPackageCopyJob",
-    "IPackageCopyJobSource",
 ]
 
 from lazr.enum import (
     DBEnumeratedType,
     DBItem,
     )
-from lazr.restful.fields import Reference
 from zope.interface import (
     Attribute,
     Interface,
     )
 from zope.schema import (
-    Bool,
     Int,
-    List,
     Object,
-    Tuple,
     )
 
 from canonical.launchpad import _
@@ -39,7 +33,6 @@
     IJobSource,
     IRunnableJob,
     )
-from lp.soyuz.interfaces.archive import IArchive
 
 
 class IDistributionJob(Interface):
@@ -100,81 +93,10 @@
         """
 
 
-class IPackageCopyJobSource(IJobSource):
-    """An interface for acquiring IIPackageCopyJobs."""
-
-    def create(cls, source_archive, source_packages,
-               target_archive, target_distroseries, target_pocket,
-               include_binaries=False):
-        """Create a new sync package job.
-
-        :param source_archive: The `IArchive` in which `source_packages` are
-            found.
-        :param source_packages: This is an iterable of `(source_package_name,
-            version)` tuples, where both `source_package_name` and `version`
-            are strings.
-        :param target_archive: The `IArchive` to which to copy the packages.
-        :param target_distroseries: The `IDistroSeries` to which to copy the
-            packages.
-        :param target_pocket: The pocket into which to copy the packages. Must
-            be a member of `PackagePublishingPocket`.
-        :param include_binaries: See `do_copy`.
-        """
-
-    def getActiveJobs(archive):
-        """Retrieve all active sync jobs for an archive."""
-
-
 class IInitialiseDistroSeriesJob(IRunnableJob):
     """A Job that performs actions on a distribution."""
 
 
-class IPackageCopyJob(IRunnableJob):
-    """A Job that synchronizes packages."""
-
-    source_packages = List(
-        title=_("Source Packages"),
-        value_type=Tuple(min_length=3, max_length=3),
-        required=True, readonly=True,
-        )
-
-    source_archive_id = Int(
-        title=_('Source Archive ID'), required=True, readonly=True,
-        )
-
-    source_archive = Reference(
-        schema=IArchive, title=_('Source Archive'),
-        required=True, readonly=True,
-        )
-
-    target_archive_id = Int(
-        title=_('Target Archive ID'), required=True, readonly=True,
-        )
-
-    target_archive = Reference(
-        schema=IArchive, title=_('Target Archive'),
-        required=True, readonly=True,
-        )
-
-    target_distroseries = Reference(
-        schema=IDistroSeries, title=_('Target DistroSeries.'),
-        required=True, readonly=True)
-
-    target_pocket = Int(
-        title=_('Target package publishing pocket'), required=True,
-        readonly=True,
-        )
-
-    include_binaries = Bool(
-        title=_("Copy binaries"),
-        required=False, readonly=True,
-        )
-
-
-class IDistroSeriesDifferenceJob(IRunnableJob):
-        """A Job that performs actions related to DSDs."""
-
-
 class IDistroSeriesDifferenceJobSource(IJobSource):
     """An `IJob` for creating `DistroSeriesDifference`s."""
 
@@ -187,3 +109,7 @@
             published in `distroseries`.
         :param pocket: The `PackagePublishingPocket` for the publication.
         """
+
+
+class IDistroSeriesDifferenceJob(IRunnableJob):
+        """A Job that performs actions related to DSDs."""

=== added file 'lib/lp/soyuz/interfaces/packagecopyjob.py'
--- lib/lp/soyuz/interfaces/packagecopyjob.py	1970-01-01 00:00:00 +0000
+++ lib/lp/soyuz/interfaces/packagecopyjob.py	2011-05-10 09:36:26 +0000
@@ -0,0 +1,124 @@
+# Copyright 2010 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+__metaclass__ = type
+
+__all__ = [
+    "IPackageCopyJob",
+    "IPlainPackageCopyJob",
+    "IPlainPackageCopyJobSource",
+    "PackageCopyJobType",
+    ]
+
+from lazr.enum import (
+    DBEnumeratedType,
+    DBItem,
+    )
+from lazr.restful.fields import Reference
+from zope.interface import (
+    Attribute,
+    Interface,
+    )
+from zope.schema import (
+    Bool,
+    Int,
+    List,
+    Tuple,
+    )
+
+from canonical.launchpad import _
+from lp.registry.interfaces.distroseries import IDistroSeries
+from lp.services.job.interfaces.job import (
+    IJob,
+    IJobSource,
+    IRunnableJob,
+    )
+from lp.soyuz.interfaces.archive import IArchive
+
+
+class IPackageCopyJob(Interface):
+    """A Job that initialises acts on a distribution."""
+
+    id = Int(
+        title=_('DB ID'), required=True, readonly=True,
+        description=_("The tracking number for this job."))
+
+    source_archive_id = Int(
+        title=_('Source Archive ID'),
+        required=True, readonly=True)
+
+    source_archive = Reference(
+        schema=IArchive, title=_('Source Archive'),
+        required=True, readonly=True)
+
+    target_archive_id = Int(
+        title=_('Target Archive ID'),
+        required=True, readonly=True)
+
+    target_archive = Reference(
+        schema=IArchive, title=_('Target Archive'),
+        required=True, readonly=True)
+
+    target_distroseries = Reference(
+        schema=IDistroSeries, title=_('Target DistroSeries.'),
+        required=True, readonly=True)
+
+    job = Reference(
+        schema=IJob, title=_('The common Job attributes'),
+        required=True, readonly=True)
+
+    metadata = Attribute('A dict of data about the job.')
+
+
+class PackageCopyJobType(DBEnumeratedType):
+
+    PLAIN = DBItem(1, """
+        Copy packages between archives.
+
+        This job copies one or more packages, optionally including binaries.
+        """)
+
+
+class IPlainPackageCopyJobSource(IJobSource):
+    """An interface for acquiring IIPackageCopyJobs."""
+
+    def create(cls, source_archive, source_packages,
+               target_archive, target_distroseries, target_pocket,
+               include_binaries=False):
+        """Create a new `IPackageCopyJob`.
+
+        :param source_archive: The `IArchive` in which `source_packages` are
+            found.
+        :param source_packages: This is an iterable of `(source_package_name,
+            version)` tuples, where both `source_package_name` and `version`
+            are strings.
+        :param target_archive: The `IArchive` to which to copy the packages.
+        :param target_distroseries: The `IDistroSeries` to which to copy the
+            packages.
+        :param target_pocket: The pocket into which to copy the packages. Must
+            be a member of `PackagePublishingPocket`.
+        :param include_binaries: See `do_copy`.
+        """
+
+    def getActiveJobs(target_archive):
+        """Retrieve all active sync jobs for an archive."""
+
+
+class IPlainPackageCopyJob(IRunnableJob):
+    """A Job that synchronizes packages."""
+
+    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,
+        )
+
+    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-05 13:01:50 +0000
+++ lib/lp/soyuz/model/packagecopyjob.py	2011-05-10 09:36:26 +0000
@@ -5,74 +5,174 @@
 
 __all__ = [
     "PackageCopyJob",
+    "PlainPackageCopyJob",
 ]
 
-from zope.component import getUtility
+from lazr.delegates import delegates
+import simplejson
+from storm.locals import (
+    And,
+    Int,
+    Reference,
+    Unicode,
+    )
 from zope.interface import (
     classProvides,
     implements,
     )
 
+from canonical.database.enumcol import EnumCol
+from canonical.launchpad.components.decoratedresultset import (
+    DecoratedResultSet,
+    )
 from canonical.launchpad.interfaces.lpstorm import (
     IMasterStore,
     IStore,
     )
+from lp.app.errors import NotFoundError
 from lp.registry.interfaces.pocket import PackagePublishingPocket
-from lp.soyuz.interfaces.archive import (
-    CannotCopy,
-    IArchiveSet,
-    )
-from lp.soyuz.interfaces.distributionjob import (
-    DistributionJobType,
+from lp.registry.model.distroseries import DistroSeries
+from lp.services.database.stormbase import StormBase
+from lp.services.job.model.job import Job
+from lp.services.job.runner import BaseRunnableJob
+from lp.soyuz.interfaces.archive import CannotCopy
+from lp.soyuz.interfaces.packagecopyjob import (
     IPackageCopyJob,
-    IPackageCopyJobSource,
-    )
-from lp.soyuz.model.distributionjob import (
-    DistributionJob,
-    DistributionJobDerived,
-    )
+    IPlainPackageCopyJob,
+    IPlainPackageCopyJobSource,
+    PackageCopyJobType,
+    )
+from lp.soyuz.model.archive import Archive
 from lp.soyuz.scripts.packagecopier import do_copy
 
 
-class PackageCopyJob(DistributionJobDerived):
-    """Job that copies a package between archives."""
+class PackageCopyJob(StormBase):
+    """Base class for package copying jobs."""
 
     implements(IPackageCopyJob)
 
-    class_job_type = DistributionJobType.COPY_PACKAGE
-    classProvides(IPackageCopyJobSource)
+    __storm_table__ = 'PackageCopyJob'
+
+    id = Int(primary=True)
+
+    job_id = Int(name='job')
+    job = Reference(job_id, Job.id)
+
+    source_archive_id = Int(name='source_archive')
+    source_archive = Reference(source_archive_id, Archive.id)
+
+    target_archive_id = Int(name='target_archive')
+    target_archive = Reference(target_archive_id, Archive.id)
+
+    target_distroseries_id = Int(name='target_distroseries')
+    target_distroseries = Reference(target_distroseries_id, DistroSeries.id)
+
+    job_type = EnumCol(enum=PackageCopyJobType, notNull=True)
+
+    _json_data = Unicode('json_data')
+
+    def __init__(self, source_archive, target_archive, target_distroseries,
+                 job_type, metadata):
+        super(PackageCopyJob, self).__init__()
+        self.job = Job()
+        self.source_archive = source_archive
+        self.target_archive = target_archive
+        self.target_distroseries = target_distroseries
+        self.job_type = job_type
+        self._json_data = self.serializeMetadata(metadata)
+
+    @classmethod
+    def serializeMetadata(cls, metadata_dict):
+        """Serialize a dict of metadata into a unicode string."""
+        return simplejson.dumps(metadata_dict).decode('utf-8')
+
+    @property
+    def metadata(self):
+        return simplejson.loads(self._json_data)
+
+
+class PackageCopyJobDerived(BaseRunnableJob):
+    """Abstract class for deriving from PackageCopyJob."""
+
+    delegates(IPackageCopyJob)
+
+    def __init__(self, job):
+        self.context = job
+
+    @classmethod
+    def get(cls, job_id):
+        """Get a job by id.
+
+        :return: the PackageCopyJob with the specified id, as the current
+            PackageCopyJobDerived subclass.
+        :raises: NotFoundError if there is no job with the specified id, or
+            its job_type does not match the desired subclass.
+        """
+        job = PackageCopyJob.get(job_id)
+        if job.job_type != cls.class_job_type:
+            raise NotFoundError(
+                'No object found with id %d and type %s' % (job_id,
+                cls.class_job_type.title))
+        return cls(job)
+
+    @classmethod
+    def iterReady(cls):
+        """Iterate through all ready PackageCopyJobs."""
+        jobs = IStore(PackageCopyJob).find(
+            PackageCopyJob,
+            And(PackageCopyJob.job_type == cls.class_job_type,
+                PackageCopyJob.job == Job.id,
+                Job.id.is_in(Job.ready_jobs)))
+        return (cls(job) for job in jobs)
+
+    def getOopsVars(self):
+        """See `IRunnableJob`."""
+        vars = super(PackageCopyJobDerived, self).getOopsVars()
+        vars.extend([
+            ('source_archive_id', self.context.source_archive_id),
+            ('target_archive_id', self.context.target_archive_id),
+            ('target_distroseries_id', self.context.target_distroseries_id),
+            ('package_copy_job_id', self.context.id),
+            ('package_copy_job_type', self.context.job_type.title),
+            ])
+        return vars
+
+
+class PlainPackageCopyJob(PackageCopyJobDerived):
+    """Job that copies packages between archives."""
+
+    implements(IPlainPackageCopyJob)
+
+    class_job_type = PackageCopyJobType.PLAIN
+    classProvides(IPlainPackageCopyJobSource)
 
     @classmethod
     def create(cls, source_packages, source_archive,
                target_archive, target_distroseries, target_pocket,
                include_binaries=False):
-        """See `IPackageCopyJobSource`."""
+        """See `IPlainPackageCopyJobSource`."""
         metadata = {
             'source_packages': source_packages,
-            'source_archive_id': source_archive.id,
-            'target_archive_id': target_archive.id,
             'target_pocket': target_pocket.value,
-            'include_binaries': include_binaries,
+            'include_binaries': bool(include_binaries),
             }
-        job = DistributionJob(
-            target_distroseries.distribution, target_distroseries,
-            cls.class_job_type, metadata)
-        IMasterStore(DistributionJob).add(job)
+        job = PackageCopyJob(
+            source_archive=source_archive,
+            target_archive=target_archive,
+            target_distroseries=target_distroseries,
+            job_type=cls.class_job_type,
+            metadata=metadata)
+        IMasterStore(PackageCopyJob).add(job)
         return cls(job)
 
     @classmethod
-    def getActiveJobs(cls, archive):
-        """See `IPackageCopyJobSource`."""
-        # TODO: JRV 20101104. This iterates manually over all active
-        # PackageCopyJobs. This should usually be a short enough list,
-        # but if it really becomes an issue target_archive should
-        # be moved into a separate database field.
-        jobs = IStore(DistributionJob).find(
-            DistributionJob,
-            DistributionJob.job_type == cls.class_job_type,
-            DistributionJob.distribution == archive.distribution)
-        jobs = [cls(job) for job in jobs]
-        return (job for job in jobs if job.target_archive_id == archive.id)
+    def getActiveJobs(cls, target_archive):
+        """See `IPlainPackageCopyJobSource`."""
+        jobs = IStore(PackageCopyJob).find(
+            PackageCopyJob,
+            PackageCopyJob.job_type == cls.class_job_type,
+            PackageCopyJob.target_archive == target_archive)
+        return DecoratedResultSet(jobs, cls)
 
     @property
     def source_packages(self):
@@ -82,26 +182,6 @@
                 name=name, version=version, exact_match=True).first()
 
     @property
-    def source_archive_id(self):
-        return self.metadata['source_archive_id']
-
-    @property
-    def source_archive(self):
-        return getUtility(IArchiveSet).get(self.source_archive_id)
-
-    @property
-    def target_archive_id(self):
-        return self.metadata['target_archive_id']
-
-    @property
-    def target_archive(self):
-        return getUtility(IArchiveSet).get(self.target_archive_id)
-
-    @property
-    def target_distroseries(self):
-        return self.distroseries
-
-    @property
     def target_pocket(self):
         return PackagePublishingPocket.items[self.metadata['target_pocket']]
 
@@ -128,3 +208,27 @@
             sources=source_packages, archive=self.target_archive,
             series=self.target_distroseries, pocket=self.target_pocket,
             include_binaries=self.include_binaries, check_permissions=False)
+
+    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 (!)")
+        else:
+            parts.append(" %d package(s)" % len(source_packages))
+        parts.append(
+            " from %s/%s" % (
+                self.source_archive.distribution.name,
+                self.source_archive.name))
+        parts.append(
+            " to %s/%s" % (
+                self.target_archive.distribution.name,
+                self.target_archive.name))
+        parts.append(
+            ", %s pocket," % self.target_pocket.name)
+        if self.target_distroseries is not None:
+            parts.append(" in %s" % self.target_distroseries)
+        if self.include_binaries:
+            parts.append(", including binaries")
+        return "<%s>" % "".join(parts)

=== modified file 'lib/lp/soyuz/tests/test_packagecopyjob.py'
--- lib/lp/soyuz/tests/test_packagecopyjob.py	2011-04-25 14:41:03 +0000
+++ lib/lp/soyuz/tests/test_packagecopyjob.py	2011-05-10 09:36:26 +0000
@@ -11,9 +11,9 @@
 from canonical.testing import LaunchpadZopelessLayer
 from lp.registry.interfaces.pocket import PackagePublishingPocket
 from lp.soyuz.interfaces.archive import CannotCopy
-from lp.soyuz.interfaces.distributionjob import (
+from lp.soyuz.interfaces.packagecopyjob import (
     IPackageCopyJob,
-    IPackageCopyJobSource,
+    IPlainPackageCopyJobSource,
     )
 from lp.soyuz.interfaces.publishing import PackagePublishingStatus
 from lp.soyuz.tests.test_publishing import SoyuzTestPublisher
@@ -23,8 +23,8 @@
     )
 
 
-class PackageCopyJobTests(TestCaseWithFactory):
-    """Test case for PackageCopyJob."""
+class PlainPackageCopyJobTests(TestCaseWithFactory):
+    """Test case for PlainPackageCopyJob."""
 
     layer = LaunchpadZopelessLayer
 
@@ -33,7 +33,7 @@
         distroseries = self.factory.makeDistroSeries()
         archive1 = self.factory.makeArchive(distroseries.distribution)
         archive2 = self.factory.makeArchive(distroseries.distribution)
-        source = getUtility(IPackageCopyJobSource)
+        source = getUtility(IPlainPackageCopyJobSource)
         job = source.create(
             source_packages=[("foo", "1.0-1"), ("bar", "2.4")],
             source_archive=archive1, target_archive=archive2,
@@ -41,7 +41,6 @@
             target_pocket=PackagePublishingPocket.RELEASE,
             include_binaries=False)
         self.assertProvides(job, IPackageCopyJob)
-        self.assertEquals(distroseries, job.distroseries)
         self.assertEquals(archive1.id, job.source_archive_id)
         self.assertEquals(archive1, job.source_archive)
         self.assertEquals(archive2.id, job.target_archive_id)
@@ -58,7 +57,7 @@
         distroseries = self.factory.makeDistroSeries()
         archive1 = self.factory.makeArchive(distroseries.distribution)
         archive2 = self.factory.makeArchive(distroseries.distribution)
-        source = getUtility(IPackageCopyJobSource)
+        source = getUtility(IPlainPackageCopyJobSource)
         job = source.create(
             source_packages=[("foo", "1.0-1")], source_archive=archive1,
             target_archive=archive2, target_distroseries=distroseries,
@@ -71,7 +70,7 @@
         distroseries = self.factory.makeDistroSeries()
         archive1 = self.factory.makeArchive(distroseries.distribution)
         archive2 = self.factory.makeArchive(distroseries.distribution)
-        source = getUtility(IPackageCopyJobSource)
+        source = getUtility(IPlainPackageCopyJobSource)
         job = source.create(
             source_packages=[("foo", "1.0-1")], source_archive=archive1,
             target_archive=archive2, target_distroseries=distroseries,
@@ -84,7 +83,7 @@
         distroseries = self.factory.makeDistroSeries()
         archive1 = self.factory.makeArchive(distroseries.distribution)
         archive2 = self.factory.makeArchive(distroseries.distribution)
-        source = getUtility(IPackageCopyJobSource)
+        source = getUtility(IPlainPackageCopyJobSource)
         job = source.create(
             source_packages=[], source_archive=archive1,
             target_archive=archive2, target_distroseries=distroseries,
@@ -106,7 +105,7 @@
             version="2.8-1", status=PackagePublishingStatus.PUBLISHED,
             archive=archive1)
 
-        source = getUtility(IPackageCopyJobSource)
+        source = getUtility(IPlainPackageCopyJobSource)
         job = source.create(
             source_packages=[("libc", "2.8-1")], source_archive=archive1,
             target_archive=archive2, target_distroseries=distroseries,
@@ -132,7 +131,7 @@
         distroseries = self.factory.makeDistroSeries()
         archive1 = self.factory.makeArchive(distroseries.distribution)
         archive2 = self.factory.makeArchive(distroseries.distribution)
-        source = getUtility(IPackageCopyJobSource)
+        source = getUtility(IPlainPackageCopyJobSource)
         job = source.create(
             source_packages=[("foo", "1.0-1")], source_archive=archive1,
             target_archive=archive2, target_distroseries=distroseries,
@@ -141,10 +140,16 @@
         oops_vars = job.getOopsVars()
         naked_job = removeSecurityProxy(job)
         self.assertIn(
-            ('distribution_id', distroseries.distribution.id), oops_vars)
-        self.assertIn(('distroseries_id', distroseries.id), oops_vars)
-        self.assertIn(
-            ('distribution_job_id', naked_job.context.id), oops_vars)
+            ('source_archive_id', archive1.id), oops_vars)
+        self.assertIn(
+            ('target_archive_id', archive2.id), oops_vars)
+        self.assertIn(
+            ('target_distroseries_id', distroseries.id), oops_vars)
+        self.assertIn(
+            ('package_copy_job_id', naked_job.context.id), oops_vars)
+        self.assertIn(
+            ('package_copy_job_type', naked_job.context.job_type.title),
+            oops_vars)
 
     def test_smoke(self):
         publisher = SoyuzTestPublisher()
@@ -156,7 +161,7 @@
             distroseries=distroseries, sourcename="libc",
             version="2.8-1", status=PackagePublishingStatus.PUBLISHED,
             archive=archive1)
-        getUtility(IPackageCopyJobSource).create(
+        getUtility(IPlainPackageCopyJobSource).create(
             source_packages=[("libc", "2.8-1")], source_archive=archive1,
             target_archive=archive2, target_distroseries=distroseries,
             target_pocket=PackagePublishingPocket.RELEASE,
@@ -165,7 +170,7 @@
 
         out, err, exit_code = run_script(
             "LP_DEBUG_SQL=1 cronscripts/process-job-source.py -vv %s" % (
-                IPackageCopyJobSource.getName()))
+                IPlainPackageCopyJobSource.getName()))
 
         self.addDetail("stdout", text_content(out))
         self.addDetail("stderr", text_content(err))
@@ -174,3 +179,24 @@
         copied_source_package = archive2.getPublishedSources(
             name="libc", version="2.8-1", exact_match=True).first()
         self.assertIsNot(copied_source_package, None)
+
+    def test___repr__(self):
+        distroseries = self.factory.makeDistroSeries()
+        archive1 = self.factory.makeArchive(distroseries.distribution)
+        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,
+            target_pocket=PackagePublishingPocket.RELEASE,
+            include_binaries=True)
+        self.assertEqual(
+            ("<PlainPackageCopyJob to copy 2 package(s) from "
+             "{distroseries.distribution.name}/{archive1.name} to "
+             "{distroseries.distribution.name}/{archive2.name}, "
+             "RELEASE pocket, in {distroseries.distribution.name} "
+             "{distroseries.name}, including binaries>").format(
+                distroseries=distroseries, archive1=archive1,
+                archive2=archive2),
+            repr(job))