Merge lp:~jelmer/launchpad/syncpackagejob-1 into lp:launchpad

Proposed by Jelmer Vernooij on 2010-11-05
Status: Merged
Approved by: Jelmer Vernooij on 2010-11-05
Approved revision: no longer in the source branch.
Merged at revision: 11870
Proposed branch: lp:~jelmer/launchpad/syncpackagejob-1
Merge into: lp:launchpad
Diff against target: 325 lines (+231/-12)
5 files modified
lib/lp/soyuz/configure.zcml (+15/-3)
lib/lp/soyuz/interfaces/distributionjob.py (+68/-5)
lib/lp/soyuz/model/syncpackagejob.py (+101/-0)
lib/lp/soyuz/tests/test_initialisedistroseriesjob.py (+0/-4)
lib/lp/soyuz/tests/test_syncpackagejob.py (+47/-0)
To merge this branch: bzr merge lp:~jelmer/launchpad/syncpackagejob-1
Reviewer Review Type Date Requested Status
Abel Deuring (community) code 2010-11-05 Approve on 2010-11-05
Review via email: mp+40169@code.launchpad.net

Commit Message

Add a job that can sync packages.

Description of the Change

This branch adds the SyncPackageJob class for jobs that can copy packages between distributions.

Pre-Implementation Call: with Julian
Tests: ./bin/test lp.soyuz.tests.test_syncpackagejob

To post a comment you must log in.
Jelmer Vernooij (jelmer) wrote :

QA doesn't really apply here yet, as none of this gets used (yet).

I've left implementing run for my next branch, to keep the size of the branch manageable. None of this code gets used anywhere yet (other than exercised by the tests).

Abel Deuring (adeuring) wrote :

(12:48:00) adeuring: jelmer: your TODO comment in getctiveJobs() says that you don't expect a larger result set for the find() call. What about logging a warning if the result set is greater than a reasonable threshold?
(12:48:52) jelmer: adeuring: That makes sense.

And a nitpick:

+ # TODO: JRV 20101104. This iterates manually over all active
+ # SyncPackageJobs. This should usually be a short enough list,
+ # but if it really becomes and issue target_archive should

s/and/an/

review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/soyuz/configure.zcml'
2--- lib/lp/soyuz/configure.zcml 2010-10-12 08:14:07 +0000
3+++ lib/lp/soyuz/configure.zcml 2010-11-05 12:06:58 +0000
4@@ -899,18 +899,30 @@
5 <allow interface="canonical.launchpad.webapp.vocabulary.IHugeVocabulary"/>
6 </class>
7
8+ <!-- DistributionJob -->
9+ <class class="lp.soyuz.model.distributionjob.DistributionJob">
10+ <allow interface="lp.soyuz.interfaces.distributionjob.IDistributionJob" />
11+ </class>
12+
13 <!-- InitialiseDistroSeriesJobSource -->
14 <securedutility
15 component="lp.soyuz.model.initialisedistroseriesjob.InitialiseDistroSeriesJob"
16 provides="lp.soyuz.interfaces.distributionjob.IInitialiseDistroSeriesJobSource">
17 <allow interface="lp.soyuz.interfaces.distributionjob.IInitialiseDistroSeriesJobSource"/>
18 </securedutility>
19- <class class="lp.soyuz.model.distributionjob.DistributionJob">
20- <allow interface="lp.soyuz.interfaces.distributionjob.IDistributionJob" />
21- </class>
22 <class class="lp.soyuz.model.initialisedistroseriesjob.InitialiseDistroSeriesJob">
23 <allow interface="lp.soyuz.interfaces.distributionjob.IInitialiseDistroSeriesJob" />
24 <allow interface="lp.soyuz.interfaces.distributionjob.IDistributionJob" />
25 </class>
26
27+ <!-- SyncPackageJobSource -->
28+ <securedutility
29+ component="lp.soyuz.model.syncpackagejob.SyncPackageJob"
30+ provides="lp.soyuz.interfaces.distributionjob.ISyncPackageJobSource">
31+ <allow interface="lp.soyuz.interfaces.distributionjob.ISyncPackageJobSource"/>
32+ </securedutility>
33+ <class class="lp.soyuz.model.syncpackagejob.SyncPackageJob">
34+ <allow interface="lp.soyuz.interfaces.distributionjob.ISyncPackageJob" />
35+ <allow interface="lp.soyuz.interfaces.distributionjob.IDistributionJob" />
36+ </class>
37 </configure>
38
39=== modified file 'lib/lp/soyuz/interfaces/distributionjob.py'
40--- lib/lp/soyuz/interfaces/distributionjob.py 2010-10-12 07:46:56 +0000
41+++ lib/lp/soyuz/interfaces/distributionjob.py 2010-11-05 12:06:58 +0000
42@@ -8,15 +8,32 @@
43 "IDistributionJob",
44 "IInitialiseDistroSeriesJob",
45 "IInitialiseDistroSeriesJobSource",
46+ "ISyncPackageJob",
47+ "ISyncPackageJobSource",
48 ]
49
50-from lazr.enum import DBEnumeratedType, DBItem
51-from zope.interface import Attribute, Interface
52-from zope.schema import Int, Object
53+from lazr.enum import (
54+ DBEnumeratedType,
55+ DBItem,
56+ )
57+from zope.interface import (
58+ Attribute,
59+ Interface,
60+ )
61+from zope.schema import (
62+ Bool,
63+ Int,
64+ Object,
65+ TextLine,
66+ )
67
68 from canonical.launchpad import _
69
70-from lp.services.job.interfaces.job import IJob, IJobSource, IRunnableJob
71+from lp.services.job.interfaces.job import (
72+ IJob,
73+ IJobSource,
74+ IRunnableJob,
75+ )
76 from lp.registry.interfaces.distribution import IDistribution
77 from lp.registry.interfaces.distroseries import IDistroSeries
78
79@@ -54,13 +71,59 @@
80 populating the archive from the parent distroseries.
81 """)
82
83+ SYNC_PACKAGE = DBItem(2, """
84+ Synchronize a single package from another distribution.
85+
86+ This job copies a single package, optionally including binaries.
87+ """)
88+
89
90 class IInitialiseDistroSeriesJobSource(IJobSource):
91- """An interface for acquiring IDistributionJobs."""
92+ """An interface for acquiring IInitialiseDistroSeriesJobs."""
93
94 def create(distroseries, arches, packagesets, rebuild):
95 """Create a new initialisation job for a distroseries."""
96
97
98+class ISyncPackageJobSource(IJobSource):
99+ """An interface for acquiring IISyncPackageJobs."""
100+
101+ def create(source_archive, target_archive, distroseries, pocket,
102+ source_package_name, version, include_binaries):
103+ """Create a new sync package job."""
104+
105+ def getActiveJobs(archive):
106+ """Retrieve all active sync jobs for an archive."""
107+
108+
109 class IInitialiseDistroSeriesJob(IRunnableJob):
110 """A Job that performs actions on a distribution."""
111+
112+
113+class ISyncPackageJob(IRunnableJob):
114+ """A Job that synchronizes packages."""
115+
116+ pocket = Int(
117+ title=_('Target package publishing pocket'), required=True,
118+ readonly=True,
119+ )
120+
121+ source_archive = Int(
122+ title=_('Source Archive ID'), required=True, readonly=True,
123+ )
124+
125+ target_archive = Int(
126+ title=_('Target Archive ID'), required=True, readonly=True,
127+ )
128+
129+ source_package_name = TextLine(
130+ title=_("Source Package Name"),
131+ required=True, readonly=True)
132+
133+ source_package_version = TextLine(
134+ title=_("Source Package Version"),
135+ required=True, readonly=True)
136+
137+ include_binaries = Bool(
138+ title=_("Copy binaries"),
139+ required=False, readonly=True)
140
141=== added file 'lib/lp/soyuz/model/syncpackagejob.py'
142--- lib/lp/soyuz/model/syncpackagejob.py 1970-01-01 00:00:00 +0000
143+++ lib/lp/soyuz/model/syncpackagejob.py 2010-11-05 12:06:58 +0000
144@@ -0,0 +1,101 @@
145+# Copyright 2010 Canonical Ltd. This software is licensed under the
146+# GNU Affero General Public License version 3 (see the file LICENSE).
147+
148+__metaclass__ = type
149+
150+__all__ = [
151+ "SyncPackageJob",
152+]
153+
154+from zope.component import getUtility
155+from zope.interface import (
156+ classProvides,
157+ implements,
158+ )
159+
160+from canonical.launchpad.interfaces.lpstorm import (
161+ IMasterStore,
162+ IStore,
163+ )
164+
165+from lp.registry.interfaces.pocket import PackagePublishingPocket
166+from lp.soyuz.interfaces.archive import IArchiveSet
167+from lp.soyuz.interfaces.distributionjob import (
168+ DistributionJobType,
169+ ISyncPackageJob,
170+ ISyncPackageJobSource,
171+ )
172+from lp.soyuz.model.distributionjob import (
173+ DistributionJob,
174+ DistributionJobDerived,
175+ )
176+
177+
178+class SyncPackageJob(DistributionJobDerived):
179+ """Job that copies a package between archives."""
180+
181+ implements(ISyncPackageJob)
182+
183+ class_job_type = DistributionJobType.SYNC_PACKAGE
184+ classProvides(ISyncPackageJobSource)
185+
186+ @classmethod
187+ def create(cls, source_archive, target_archive, distroseries,
188+ pocket, source_package_name, source_package_version,
189+ include_binaries):
190+ """See `ISyncPackageJobSource`."""
191+ metadata = {
192+ 'source_archive_id': source_archive.id,
193+ 'target_archive_id': target_archive.id,
194+ 'pocket': pocket.value,
195+ 'source_package_name': source_package_name,
196+ 'source_package_version': source_package_version,
197+ 'include_binaries': include_binaries,
198+ }
199+ job = DistributionJob(
200+ distroseries.distribution, distroseries, cls.class_job_type,
201+ metadata)
202+ IMasterStore(DistributionJob).add(job)
203+ return cls(job)
204+
205+ @classmethod
206+ def getActiveJobs(cls, archive):
207+ """See `ISyncPackageJobSource`."""
208+ # TODO: JRV 20101104. This iterates manually over all active
209+ # SyncPackageJobs. This should usually be a short enough list,
210+ # but if it really becomes an issue target_archive should
211+ # be moved into a separate database field.
212+ jobs = IStore(DistributionJob).find(
213+ DistributionJob,
214+ DistributionJob.job_type == cls.class_job_type,
215+ DistributionJob.distribution == archive.distribution)
216+ jobs = [cls(job) for job in jobs]
217+ return (job for job in jobs if job.target_archive == archive)
218+
219+ @property
220+ def source_archive(self):
221+ return getUtility(IArchiveSet).get(self.metadata['source_archive_id'])
222+
223+ @property
224+ def target_archive(self):
225+ return getUtility(IArchiveSet).get(self.metadata['target_archive_id'])
226+
227+ @property
228+ def pocket(self):
229+ return PackagePublishingPocket.items[self.metadata['pocket']]
230+
231+ @property
232+ def include_binaries(self):
233+ return self.metadata['include_binaries']
234+
235+ @property
236+ def source_package_name(self):
237+ return self.metadata['source_package_name']
238+
239+ @property
240+ def source_package_version(self):
241+ return self.metadata['source_package_version']
242+
243+ def run(self):
244+ """See `IRunnableJob`."""
245+ raise NotImplementedError(self.run)
246
247=== modified file 'lib/lp/soyuz/tests/test_initialisedistroseriesjob.py'
248--- lib/lp/soyuz/tests/test_initialisedistroseriesjob.py 2010-10-25 23:13:55 +0000
249+++ lib/lp/soyuz/tests/test_initialisedistroseriesjob.py 2010-11-05 12:06:58 +0000
250@@ -6,15 +6,12 @@
251 import sys
252
253 from storm.exceptions import IntegrityError
254-from storm.store import Store
255 import transaction
256 from zope.component import getUtility
257 from zope.security.proxy import removeSecurityProxy
258
259 from canonical.config import config
260 from canonical.testing import LaunchpadZopelessLayer
261-from lp.buildmaster.enums import BuildStatus
262-from lp.registry.interfaces.pocket import PackagePublishingPocket
263 from lp.soyuz.interfaces.distributionjob import (
264 IInitialiseDistroSeriesJobSource,
265 )
266@@ -26,7 +23,6 @@
267 from lp.soyuz.scripts.initialise_distroseries import InitialisationError
268 from lp.soyuz.tests.test_publishing import SoyuzTestPublisher
269 from lp.testing import TestCaseWithFactory
270-from lp.testing.matchers import Contains
271
272
273 class InitialiseDistroSeriesJobTests(TestCaseWithFactory):
274
275=== added file 'lib/lp/soyuz/tests/test_syncpackagejob.py'
276--- lib/lp/soyuz/tests/test_syncpackagejob.py 1970-01-01 00:00:00 +0000
277+++ lib/lp/soyuz/tests/test_syncpackagejob.py 2010-11-05 12:06:58 +0000
278@@ -0,0 +1,47 @@
279+# Copyright 2010 Canonical Ltd. This software is licensed under the
280+# GNU Affero General Public License version 3 (see the file LICENSE).
281+
282+from zope.component import getUtility
283+
284+from canonical.testing import LaunchpadZopelessLayer
285+from lp.soyuz.interfaces.distributionjob import (
286+ ISyncPackageJob,
287+ ISyncPackageJobSource,
288+ )
289+from lp.registry.interfaces.pocket import PackagePublishingPocket
290+from lp.testing import TestCaseWithFactory
291+
292+
293+class SyncPackageJobTests(TestCaseWithFactory):
294+ """Test case for SyncPackageJob."""
295+
296+ layer = LaunchpadZopelessLayer
297+
298+ def test_create(self):
299+ # A SyncPackageJob can be created and stores its arguments.
300+ distroseries = self.factory.makeDistroSeries()
301+ archive1 = self.factory.makeArchive(distroseries.distribution)
302+ archive2 = self.factory.makeArchive(distroseries.distribution)
303+ source = getUtility(ISyncPackageJobSource)
304+ job = source.create(archive1, archive2, distroseries,
305+ PackagePublishingPocket.RELEASE,
306+ "foo", "1.0-1", include_binaries=False)
307+ self.assertProvides(job, ISyncPackageJob)
308+ self.assertEquals(distroseries, job.distroseries)
309+ self.assertEquals(archive1, job.source_archive)
310+ self.assertEquals(archive2, job.target_archive)
311+ self.assertEquals(PackagePublishingPocket.RELEASE, job.pocket)
312+ self.assertEquals("foo", job.source_package_name)
313+ self.assertEquals("1.0-1", job.source_package_version)
314+ self.assertEquals(False, job.include_binaries)
315+
316+ def test_getActiveJobs(self):
317+ # getActiveJobs() can retrieve all active jobs for an archive.
318+ distroseries = self.factory.makeDistroSeries()
319+ archive1 = self.factory.makeArchive(distroseries.distribution)
320+ archive2 = self.factory.makeArchive(distroseries.distribution)
321+ source = getUtility(ISyncPackageJobSource)
322+ job = source.create(archive1, archive2, distroseries,
323+ PackagePublishingPocket.RELEASE,
324+ "foo", "1.0-1", include_binaries=False)
325+ self.assertContentEqual([job], source.getActiveJobs(archive2))