Merge lp:~ursinha/launchpad/convert-translationuploads-to-job into lp:launchpad

Proposed by Ursula Junque
Status: Superseded
Proposed branch: lp:~ursinha/launchpad/convert-translationuploads-to-job
Merge into: lp:launchpad
Prerequisite: lp:~stevenk/launchpad/packagediff-job
Diff against target: 613 lines (+314/-163) (has conflicts)
9 files modified
database/schema/security.cfg (+6/-2)
lib/lp/services/config/schema-lazr.conf (+4/-0)
lib/lp/services/job/interfaces/job.py (+7/-0)
lib/lp/soyuz/configure.zcml (+12/-0)
lib/lp/soyuz/doc/distroseriesqueue-translations.txt (+14/-149)
lib/lp/soyuz/interfaces/packagetranslationsuploadjob.py (+25/-0)
lib/lp/soyuz/model/packagetranslationsuploadjob.py (+94/-0)
lib/lp/soyuz/model/queue.py (+5/-12)
lib/lp/soyuz/tests/test_packagetranslationsuploadjob.py (+147/-0)
Text conflict in lib/lp/services/config/schema-lazr.conf
To merge this branch: bzr merge lp:~ursinha/launchpad/convert-translationuploads-to-job
Reviewer Review Type Date Requested Status
Ursula Junque (community) Needs Resubmitting
William Grant code Needs Fixing
Review via email: mp+176415@code.launchpad.net

This proposal has been superseded by a proposal from 2013-07-23.

Commit message

publishRosettaTranslations now fires a job that uploads and attaches the translation files, to stop slowing down the publisher

Description of the change

This branch creates a TranslationsUploadJob that is responsible for uploading and attaching translation files to a sourcepackagerelease. Now, whenever a PackageUploadCustom of format ROSETTA_TRANSLATIONS is processed in the publisher queue, a TranslationsUploadJob is created and the publisher can move along.

To post a comment you must log in.
Revision history for this message
William Grant (wgrant) wrote :

There are a few bits of lint. I'd suggest running 'make lint' and utilities/format-new-and-modified-imports.

8 +[ITranslationsUploadJobSource]
9 +module: lp.soyuz.interfaces.translationsuploadjob
10 +dbuser: process_accepted

This needs to use a separate DB user. As a quick fix you can add a new alias user in database/schema/security.cfg and use that.

23 + UPLOAD_TRANSLATIONS_FILES = DBItem(1, """
24 + Upload Translations Files
25 +
26 + Job to upload translations files and attach them to a
27 + SourcePackageRelease.
28 + """)

I'd call this UPLOAD_PACKAGE_TRANSLATIONS, because we'll probably eventually have a job for non-package translation uploads.

277 +class ITranslationsUploadJobSource(IJobSource):
278 + """An interface for acquiring ITranslationsUploadJob."""

The classes and interfaces probably want to be renamed to include "Package" too.

323 + return None

I'm not quite sure why you change the 'return' to 'return None' here. It doesn't make any difference in terms of functionality, but you'd normally only 'return None' when the other returns return a different value.

393 + @classmethod
394 + def get(cls, sourcepackagerelease, libraryfilealias):
395 + metadata = simplejson.dumps(
396 + {'sourcepackagerelease': sourcepackagerelease.id,
397 + 'libraryfilealias': libraryfilealias.id})
398 + return cls(IStore(Job).find(Job, Job.base_json_data == metadata).one())

Dumping to JSON is non-deterministic (eg. key order can change, quotes can change, etc.) so comparing it isn't always going to match. We have no need to retrieve a job by SPR and LFA afterwards, so I wouldn't have added this method.

562 + entries_in_queue = translation_import_queue.getAllEntries(
563 + target=spr.sourcepackage).count()
564 + self.assertEqual(2, entries_in_queue)

Can you check that the filenames are correct?

review: Needs Fixing (code)
Revision history for this message
Ursula Junque (ursinha) wrote :

> There are a few bits of lint. I'd suggest running 'make lint' and utilities
> /format-new-and-modified-imports.

Fixed.

>
> 8 +[ITranslationsUploadJobSource]
> 9 +module: lp.soyuz.interfaces.translationsuploadjob
> 10 +dbuser: process_accepted
>
> This needs to use a separate DB user. As a quick fix you can add a new alias
> user in database/schema/security.cfg and use that.

I created an alias called upload_package_translations_job, inheriting queued, and removed the translation tables to it.

>
> 23 + UPLOAD_TRANSLATIONS_FILES = DBItem(1, """
> 24 + Upload Translations Files
> 25 +
> 26 + Job to upload translations files and attach them to a
> 27 + SourcePackageRelease.
> 28 + """)
>
> I'd call this UPLOAD_PACKAGE_TRANSLATIONS, because we'll probably eventually
> have a job for non-package translation uploads.

Done.

>
> 277 +class ITranslationsUploadJobSource(IJobSource):
> 278 + """An interface for acquiring ITranslationsUploadJob."""
>
> The classes and interfaces probably want to be renamed to include "Package"
> too.

I renamed everything to PackageTranslationsUploadJob, sounds a bit better.

>
> 323 + return None
>
> I'm not quite sure why you change the 'return' to 'return None' here. It
> doesn't make any difference in terms of functionality, but you'd normally only
> 'return None' when the other returns return a different value.

Reverted (that was StevenK idea :))

>
> 393 + @classmethod
> 394 + def get(cls, sourcepackagerelease, libraryfilealias):
> 395 + metadata = simplejson.dumps(
> 396 + {'sourcepackagerelease': sourcepackagerelease.id,
> 397 + 'libraryfilealias': libraryfilealias.id})
> 398 + return cls(IStore(Job).find(Job, Job.base_json_data ==
> metadata).one())
>
> Dumping to JSON is non-deterministic (eg. key order can change, quotes can
> change, etc.) so comparing it isn't always going to match. We have no need to
> retrieve a job by SPR and LFA afterwards, so I wouldn't have added this
> method.

Removed.

>
> 562 + entries_in_queue = translation_import_queue.getAllEntries(
> 563 + target=spr.sourcepackage).count()
> 564 + self.assertEqual(2, entries_in_queue)
>
> Can you check that the filenames are correct?

Done.

review: Needs Resubmitting

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'database/schema/security.cfg'
2--- database/schema/security.cfg 2013-05-24 04:48:09 +0000
3+++ database/schema/security.cfg 2013-07-23 18:52:29 +0000
4@@ -1500,8 +1500,6 @@
5 public.structuralsubscription = SELECT
6 public.teammembership = SELECT
7 public.teamparticipation = SELECT, INSERT
8-public.translationgroup = SELECT
9-public.translationimportqueueentry = SELECT, INSERT, UPDATE
10 public.validpersoncache = SELECT
11 public.validpersonorteamcache = SELECT
12 type=user
13@@ -1511,6 +1509,12 @@
14 groups=queued
15 public.processacceptedbugsjob = SELECT, INSERT
16
17+[upload_package_translations_job]
18+type=user
19+groups=queued
20+public.translationgroup = SELECT
21+public.translationimportqueueentry = SELECT, INSERT, UPDATE
22+
23 [session]
24 type=user
25
26
27=== modified file 'lib/lp/services/config/schema-lazr.conf'
28--- lib/lp/services/config/schema-lazr.conf 2013-07-23 18:52:28 +0000
29+++ lib/lp/services/config/schema-lazr.conf 2013-07-23 18:52:29 +0000
30@@ -1749,6 +1749,10 @@
31 module: lp.translations.interfaces.translationpackagingjob
32 dbuser: rosettaadmin
33
34+[IPackageTranslationsUploadJobSource]
35+module: lp.soyuz.interfaces.packagetranslationsuploadjob
36+dbuser: upload_package_translations_job
37+
38 >>>>>>> MERGE-SOURCE
39 [IPersonMergeJobSource]
40 module: lp.registry.interfaces.persontransferjob
41
42=== modified file 'lib/lp/services/job/interfaces/job.py'
43--- lib/lp/services/job/interfaces/job.py 2013-07-23 18:52:28 +0000
44+++ lib/lp/services/job/interfaces/job.py 2013-07-23 18:52:29 +0000
45@@ -78,6 +78,13 @@
46 Job to generate the diff between two SourcePackageReleases.
47 """)
48
49+ UPLOAD_PACKAGE_TRANSLATIONS = DBItem(1, """
50+ Upload Package Translations
51+
52+ Job to upload package translations files and attach them to a
53+ SourcePackageRelease.
54+ """)
55+
56
57 class IJob(Interface):
58 """Basic attributes of a job."""
59
60=== modified file 'lib/lp/soyuz/configure.zcml'
61--- lib/lp/soyuz/configure.zcml 2013-07-23 18:52:28 +0000
62+++ lib/lp/soyuz/configure.zcml 2013-07-23 18:52:29 +0000
63@@ -995,6 +995,18 @@
64 <allow interface=".interfaces.packagediffjob.IPackageDiffJob" />
65 </class>
66
67+ <!-- PackageTranslationsUploadJobSource -->
68+ <securedutility
69+ component=".model.packagetranslationsuploadjob.PackageTranslationsUploadJob"
70+ provides=".interfaces.packagetranslationsuploadjob.IPackageTranslationsUploadJobSource">
71+ <allow interface=".interfaces.packagetranslationsuploadjob.IPackageTranslationsUploadJobSource" />
72+ </securedutility>
73+
74+ <!-- PackageTranslationsUploadJob -->
75+ <class class=".model.packagetranslationsuploadjob.PackageTranslationsUploadJob">
76+ <allow
77+ interface=".interfaces.packagetranslationsuploadjob.IPackageTranslationsUploadJob" />
78+ </class>
79 <webservice:register module="lp.soyuz.interfaces.webservice" />
80
81 </configure>
82
83=== modified file 'lib/lp/soyuz/doc/distroseriesqueue-translations.txt'
84--- lib/lp/soyuz/doc/distroseriesqueue-translations.txt 2011-12-30 06:14:56 +0000
85+++ lib/lp/soyuz/doc/distroseriesqueue-translations.txt 2013-07-23 18:52:29 +0000
86@@ -12,7 +12,6 @@
87 ... SourcePackagePublishingHistory)
88 >>> from lp.registry.interfaces.distribution import IDistributionSet
89 >>> from lp.registry.interfaces.distroseries import (
90- ... IDistroSeries,
91 ... IDistroSeriesSet,
92 ... )
93 >>> from lp.registry.interfaces.pocket import PackagePublishingPocket
94@@ -31,6 +30,9 @@
95 >>> from lp.services.database.constants import UTC_NOW
96 >>> from lp.registry.interfaces.sourcepackage import SourcePackageUrgency
97
98+ >>> from lp.soyuz.model.packagetranslationsuploadjob import (
99+ ... PackageTranslationsUploadJob)
100+
101 # Login as an admin.
102 >>> login('foo.bar@canonical.com')
103
104@@ -157,6 +159,15 @@
105 ... status=PackageUploadStatus.NEW)[0]
106 >>> queue_item.customfiles[0].publish()
107
108+When publish() runs, it creates a PackageTranslationsUploadJob that will
109+process the package translation files. We need to find and run it to be
110+able to verify the imported files.
111+ >>> def runPendingPackageTranslationsUploadJob():
112+ ... job = list(PackageTranslationsUploadJob.iterReady())[0]
113+ ... job.run()
114+
115+ >>> runPendingPackageTranslationsUploadJob()
116+
117 As we can see from the translation import queue content.
118
119 >>> for entry in translation_import_queue.getAllEntries(target=ubuntu):
120@@ -206,6 +217,7 @@
121 >>> queue_item = dapper.getPackageUploads(PackageUploadStatus.NEW)[0]
122 >>> queue_item.pocket = PackagePublishingPocket.UPDATES
123 >>> queue_item.customfiles[0].publish()
124+ >>> runPendingPackageTranslationsUploadJob()
125
126 As we can see from the translation import queue content.
127
128@@ -239,6 +251,7 @@
129 >>> queue_item.builds[0].build.source_package_release.override(
130 ... component=restricted_component)
131 >>> queue_item.customfiles[0].publish()
132+ >>> runPendingPackageTranslationsUploadJob()
133
134 As we can see from the translation import queue content.
135
136@@ -340,154 +353,6 @@
137 >>> translation_import_queue.getAllEntries(target=ubuntu).count()
138 0
139
140-
141-Translations importer: publishRosettaTranslations
142--------------------------------------------------
143-
144-We create mock objects for SourcePackageRelease, PackageUpload and
145-PackageUploadCustom: these will emulate everything we need to document
146-different interpretations of "importer" in attachTranslationFiles.
147-
148- >>> from zope.interface import implements
149- >>> from lp.app.interfaces.launchpad import ILaunchpadCelebrities
150- >>> from lp.soyuz.model.queue import PackageUploadCustom
151- >>> from lp.soyuz.interfaces.archive import (
152- ... IArchive, ArchivePurpose)
153- >>> from lp.soyuz.interfaces.queue import (
154- ... IPackageUpload, IPackageUploadCustom)
155- >>> from lp.registry.interfaces.person import IPerson
156- >>> from lp.soyuz.enums import PackageUploadCustomFormat
157- >>> from lp.soyuz.interfaces.component import IComponentSet
158- >>> from lp.soyuz.interfaces.sourcepackagerelease import (
159- ... ISourcePackageRelease)
160- >>> from lp.registry.interfaces.pocket import PackagePublishingPocket
161-
162- >>> class MockArchive:
163- ... implements(IArchive)
164- ... def __init__(self, purpose):
165- ... self.purpose = purpose
166-
167- >>> class MockDistroSeries:
168- ... implements(IDistroSeries)
169- ... def __init__(self, version):
170- ... self.version = version
171-
172- >>> class MockSourcePackageRelease:
173- ... implements(ISourcePackageRelease)
174- ... def __init__(self, component, creator, upload_distroseries):
175- ... self.component = getUtility(IComponentSet)[component]
176- ... self.upload_distroseries = upload_distroseries
177- ... self.creator = creator
178- ... self.packageupload = 1
179- ...
180- ... def attachTranslationFiles(self, file, imported, importer):
181- ... if (importer is not None and
182- ... not IPerson.providedBy(importer)):
183- ... print "`importer` not a person!"
184- ... print "Imported by: %s" % (
185- ... getattr(importer, "name", "None"))
186-
187- >>> class MockPackageUpload:
188- ... implements(IPackageUpload)
189- ... def __init__(self, pocket, auto_sync, sourcepackagerelease,
190- ... archive):
191- ... self.id = 1
192- ... self.pocket = pocket
193- ... self.auto_sync = auto_sync
194- ... self.sourcepackagerelease = sourcepackagerelease
195- ... self.archive = archive
196- ...
197- ... def isAutoSyncUpload(self, changed_by_email=None):
198- ... return self.auto_sync
199-
200- >>> class MockPackageUploadCustom(PackageUploadCustom):
201- ... implements(IPackageUploadCustom)
202- ... packageupload = None
203- ...
204- ... def __init__(self):
205- ... self.customformat = (
206- ... PackageUploadCustomFormat.ROSETTA_TRANSLATIONS)
207-
208-For translations from auto-synced packages we consider the importer to be
209-'katie' (archive@ubuntu.com).
210-
211- >>> katie = getUtility(ILaunchpadCelebrities).katie
212- >>> release_pocket = PackagePublishingPocket.RELEASE
213- >>> archive = MockArchive(ArchivePurpose.PRIMARY)
214-
215- >>> distro_series = MockDistroSeries(u'9.04')
216- >>> katie_sourcepackagerelease = MockSourcePackageRelease(
217- ... 'main', katie, distro_series)
218- >>> sync_package_upload = MockPackageUpload(
219- ... release_pocket, True, katie_sourcepackagerelease, archive)
220- >>> sync_package_upload.isAutoSyncUpload()
221- True
222- >>> translations_upload = MockPackageUploadCustom()
223- >>> translations_upload.packageupload = sync_package_upload
224- >>> translations_upload.publishRosettaTranslations()
225- Imported by: katie
226-
227-Non-auto-sync uploads by 'katie' still indicate 'katie' as the uploader.
228-
229- >>> non_sync_package_upload = MockPackageUpload(
230- ... release_pocket, False, katie_sourcepackagerelease, archive)
231- >>> non_sync_package_upload.isAutoSyncUpload()
232- False
233- >>> translations_upload.packageupload = non_sync_package_upload
234- >>> translations_upload.publishRosettaTranslations()
235- Imported by: katie
236-
237-Uploads by anyone else are treated as if importer is the packager.
238-
239- >>> person_set = getUtility(IPersonSet)
240- >>> carlos = person_set.getByName('carlos')
241- >>> carlos_sourcepackagerelease = MockSourcePackageRelease(
242- ... 'main', carlos, distro_series)
243- >>> carlos_package_upload = MockPackageUpload(
244- ... release_pocket, False, carlos_sourcepackagerelease, archive)
245- >>> carlos_package_upload.isAutoSyncUpload()
246- False
247- >>> translations_upload.packageupload = carlos_package_upload
248- >>> translations_upload.publishRosettaTranslations()
249- Imported by: carlos
250-
251-Uploads for distroseries before Oneiric or later may not be targeted
252-to any component but 'main' and 'restricted'. The upload attempt is ignored.
253-
254- >>> katie_sourcepackagerelease = MockSourcePackageRelease(
255- ... 'universe', katie, distro_series)
256- >>> sync_package_upload = MockPackageUpload(
257- ... release_pocket, True, katie_sourcepackagerelease, archive)
258- >>> translations_upload = MockPackageUploadCustom()
259- >>> translations_upload.packageupload = sync_package_upload
260- >>> translations_upload.publishRosettaTranslations()
261-
262-For Oneiric the import succeeds for 'universe'.
263-
264- >>> distro_series = MockDistroSeries(u'11.10')
265- >>> katie_sourcepackagerelease = MockSourcePackageRelease(
266- ... 'universe', katie, distro_series)
267- >>> sync_package_upload = MockPackageUpload(
268- ... release_pocket, True, katie_sourcepackagerelease, archive)
269- >>> translations_upload = MockPackageUploadCustom()
270- >>> translations_upload.packageupload = sync_package_upload
271- >>> translations_upload.publishRosettaTranslations()
272- Imported by: katie
273-
274-And for the 12.04 release the import succeeds for 'universe'.
275-
276- >>> distro_series = MockDistroSeries(u'12.04')
277- >>> katie_sourcepackagerelease = MockSourcePackageRelease(
278- ... 'universe', katie, distro_series)
279- >>> sync_package_upload = MockPackageUpload(
280- ... release_pocket, True, katie_sourcepackagerelease, archive)
281- >>> translations_upload = MockPackageUploadCustom()
282- >>> translations_upload.packageupload = sync_package_upload
283- >>> translations_upload.publishRosettaTranslations()
284- Imported by: katie
285-
286-
287-
288 Translations tarball
289 ~~~~~~~~~~~~~~~~~~~~
290
291
292=== added file 'lib/lp/soyuz/interfaces/packagetranslationsuploadjob.py'
293--- lib/lp/soyuz/interfaces/packagetranslationsuploadjob.py 1970-01-01 00:00:00 +0000
294+++ lib/lp/soyuz/interfaces/packagetranslationsuploadjob.py 2013-07-23 18:52:29 +0000
295@@ -0,0 +1,25 @@
296+# Copyright 2013 Canonical Ltd. This software is licensed under the
297+# GNU Affero General Public License version 3 (see the file LICENSE).
298+
299+__metaclass__ = type
300+
301+__all__ = [
302+ "IPackageTranslationsUploadJob",
303+ "IPackageTranslationsUploadJobSource",
304+ ]
305+
306+from lp.services.job.interfaces.job import (
307+ IJobSource,
308+ IRunnableJob,
309+ )
310+
311+
312+class IPackageTranslationsUploadJobSource(IJobSource):
313+ """An interface for acquiring IPackageTranslationsUploadJob."""
314+
315+ def create(sourcepackagerelease, libraryfilealias):
316+ """Create new translations upload job for a source package release."""
317+
318+
319+class IPackageTranslationsUploadJob(IRunnableJob):
320+ """A `Job` that uploads and attaches files to a `ISourcePackageRelease`."""
321
322=== added file 'lib/lp/soyuz/model/packagetranslationsuploadjob.py'
323--- lib/lp/soyuz/model/packagetranslationsuploadjob.py 1970-01-01 00:00:00 +0000
324+++ lib/lp/soyuz/model/packagetranslationsuploadjob.py 2013-07-23 18:52:29 +0000
325@@ -0,0 +1,94 @@
326+# Copyright 2013 Canonical Ltd. This software is licensed under the
327+# GNU Affero General Public License version 3 (see the file LICENSE).
328+
329+__metaclass__ = type
330+
331+__all__ = [
332+ 'PackageTranslationsUploadJob',
333+ ]
334+
335+from lazr.delegates import delegates
336+import simplejson
337+from zope.component import getUtility
338+from zope.interface import (
339+ classProvides,
340+ implements,
341+ )
342+
343+from lp.services.config import config
344+from lp.services.database.interfaces import IStore
345+from lp.services.job.interfaces.job import JobType
346+from lp.services.job.model.job import (
347+ EnumeratedSubclass,
348+ Job,
349+ )
350+from lp.services.job.runner import BaseRunnableJob
351+from lp.services.librarian.interfaces import ILibraryFileAliasSet
352+from lp.soyuz.interfaces.packagetranslationsuploadjob import (
353+ IPackageTranslationsUploadJob,
354+ IPackageTranslationsUploadJobSource,
355+ )
356+from lp.soyuz.model.sourcepackagerelease import SourcePackageRelease
357+
358+
359+class PackageTranslationsUploadJobDerived(BaseRunnableJob):
360+
361+ __metaclass__ = EnumeratedSubclass
362+
363+ delegates(IPackageTranslationsUploadJob)
364+ classProvides(IPackageTranslationsUploadJobSource)
365+ config = config.IPackageTranslationsUploadJobSource
366+
367+ def __init__(self, job):
368+ assert job.base_job_type == JobType.UPLOAD_PACKAGE_TRANSLATIONS
369+ self.job = job
370+ self.context = self
371+
372+ @classmethod
373+ def create(cls, sourcepackagerelease, libraryfilealias):
374+ job = Job(
375+ base_job_type=JobType.UPLOAD_PACKAGE_TRANSLATIONS,
376+ requester=sourcepackagerelease.creator,
377+ base_json_data=simplejson.dumps(
378+ {'sourcepackagerelease': sourcepackagerelease.id,
379+ 'libraryfilealias': libraryfilealias.id}))
380+ derived = cls(job)
381+ derived.celeryRunOnCommit()
382+ return derived
383+
384+ @classmethod
385+ def iterReady(cls):
386+ jobs = IStore(Job).find(
387+ Job, Job.id.is_in(Job.ready_jobs),
388+ Job.base_job_type == JobType.UPLOAD_PACKAGE_TRANSLATIONS)
389+ return [cls(job) for job in jobs]
390+
391+
392+class PackageTranslationsUploadJob(PackageTranslationsUploadJobDerived):
393+
394+ implements(IPackageTranslationsUploadJob)
395+ classProvides(IPackageTranslationsUploadJobSource)
396+
397+ @property
398+ def sourcepackagerelease_id(self):
399+ return simplejson.loads(self.base_json_data)['sourcepackagerelease']
400+
401+ @property
402+ def libraryfilealias_id(self):
403+ return simplejson.loads(self.base_json_data)['libraryfilealias']
404+
405+ @property
406+ def sourcepackagerelease(self):
407+ return SourcePackageRelease.get(self.sourcepackagerelease_id)
408+
409+ @property
410+ def libraryfilealias(self):
411+ return getUtility(ILibraryFileAliasSet)[self.libraryfilealias_id]
412+
413+ def run(self):
414+ sourcepackagerelease = self.sourcepackagerelease
415+ if sourcepackagerelease is not None:
416+ libraryfilealias = self.libraryfilealias
417+ importer = sourcepackagerelease.creator
418+ sourcepackagerelease.attachTranslationFiles(
419+ libraryfilealias, True, importer=importer)
420
421=== modified file 'lib/lp/soyuz/model/queue.py'
422--- lib/lp/soyuz/model/queue.py 2013-07-16 08:10:32 +0000
423+++ lib/lp/soyuz/model/queue.py 2013-07-23 18:52:29 +0000
424@@ -73,7 +73,6 @@
425 )
426 from lp.services.features import getFeatureFlag
427 from lp.services.librarian.browser import ProxiedLibraryFileAlias
428-from lp.services.librarian.interfaces.client import DownloadFailed
429 from lp.services.librarian.model import (
430 LibraryFileAlias,
431 LibraryFileContent,
432@@ -103,6 +102,9 @@
433 IPublishingSet,
434 name_priority_map,
435 )
436+from lp.soyuz.interfaces.packagetranslationsuploadjob import (
437+ IPackageTranslationsUploadJobSource,
438+ )
439 from lp.soyuz.interfaces.queue import (
440 IPackageUpload,
441 IPackageUploadBuild,
442@@ -1453,17 +1455,8 @@
443 # packages in main.
444 return
445
446- # Set the importer to package creator.
447- importer = sourcepackagerelease.creator
448-
449- # Attach the translation tarball. It's always published.
450- try:
451- sourcepackagerelease.attachTranslationFiles(
452- self.libraryfilealias, True, importer=importer)
453- except DownloadFailed:
454- if logger is not None:
455- debug(logger, "Unable to fetch %s to import it into Rosetta" %
456- self.libraryfilealias.http_url)
457+ getUtility(IPackageTranslationsUploadJobSource).create(
458+ sourcepackagerelease, self.libraryfilealias)
459
460 def publishStaticTranslations(self, logger=None):
461 """See `IPackageUploadCustom`."""
462
463=== added file 'lib/lp/soyuz/tests/test_packagetranslationsuploadjob.py'
464--- lib/lp/soyuz/tests/test_packagetranslationsuploadjob.py 1970-01-01 00:00:00 +0000
465+++ lib/lp/soyuz/tests/test_packagetranslationsuploadjob.py 2013-07-23 18:52:29 +0000
466@@ -0,0 +1,147 @@
467+# Copyright 2013 Canonical Ltd. This software is licensed under the
468+# GNU Affero General Public License version 3 (see the file LICENSE).
469+
470+__metaclass__ = type
471+
472+from testtools.content import text_content
473+import transaction
474+from zope.component import getUtility
475+from zope.security.proxy import removeSecurityProxy
476+
477+from lp.soyuz.interfaces.packagetranslationsuploadjob import (
478+ IPackageTranslationsUploadJob,
479+ IPackageTranslationsUploadJobSource,
480+ )
481+from lp.soyuz.model.packagetranslationsuploadjob import (
482+ PackageTranslationsUploadJob,
483+ )
484+from lp.services.features.testing import FeatureFixture
485+from lp.services.job.interfaces.job import JobStatus
486+from lp.testing import (
487+ run_script,
488+ TestCaseWithFactory,
489+ verifyObject,
490+ )
491+from lp.services.job.tests import block_on_job
492+from lp.testing.fakemethod import FakeMethod
493+from lp.testing.layers import (
494+ CeleryJobLayer,
495+ LaunchpadZopelessLayer,
496+ )
497+from lp.services.tarfile_helpers import LaunchpadWriteTarFile
498+from lp.translations.interfaces.translationimportqueue import (
499+ ITranslationImportQueue,
500+ )
501+
502+
503+class LocalTestHelper(TestCaseWithFactory):
504+
505+ def makeJob(self, spr_creator=None, archive=None,
506+ sourcepackagerelease=None, libraryfilealias=None,
507+ tar_content=None):
508+ if spr_creator is None:
509+ creator = self.factory.makePerson()
510+ else:
511+ creator = self.factory.makePerson(name=spr_creator)
512+ if archive is None:
513+ archive = self.factory.makeArchive()
514+ if sourcepackagerelease is None:
515+ sourcepackagerelease = self.factory.makeSourcePackageRelease(
516+ archive=archive, creator=creator)
517+ if libraryfilealias is None:
518+ libraryfilealias = self.makeTranslationsLFA(tar_content)
519+ return (sourcepackagerelease,
520+ getUtility(IPackageTranslationsUploadJobSource).create(
521+ sourcepackagerelease, libraryfilealias))
522+
523+ def makeTranslationsLFA(self, tar_content=None):
524+ """Create an LibraryFileAlias containing dummy translation data."""
525+ if tar_content is None:
526+ tar_content = {
527+ 'source/po/foo.pot': 'Foo template',
528+ 'source/po/eo.po': 'Foo translation',
529+ }
530+ tarfile_content = LaunchpadWriteTarFile.files_to_string(
531+ tar_content)
532+ return self.factory.makeLibraryFileAlias(content=tarfile_content)
533+
534+
535+class TestPackageTranslationsUploadJob(LocalTestHelper):
536+
537+ layer = LaunchpadZopelessLayer
538+
539+ def test_job_implements_IPackageTranslationsUploadJob(self):
540+ _, job = self.makeJob()
541+ self.assertTrue(verifyObject(IPackageTranslationsUploadJob, job))
542+
543+ def test_job_source_implements_IPackageTranslationsUploadJobSource(self):
544+ job_source = getUtility(IPackageTranslationsUploadJobSource)
545+ self.assertTrue(verifyObject(IPackageTranslationsUploadJobSource,
546+ job_source))
547+
548+ def test_iterReady(self):
549+ _, job1 = self.makeJob()
550+ removeSecurityProxy(job1).job._status = JobStatus.COMPLETED
551+ _, job2 = self.makeJob()
552+ jobs = list(PackageTranslationsUploadJob.iterReady())
553+ self.assertEqual(1, len(jobs))
554+
555+ def test_importer_is_creator(self):
556+ spr, job = self.makeJob(spr_creator="foobar")
557+ transaction.commit()
558+ job.run()
559+ translation_import_queue = getUtility(ITranslationImportQueue)
560+ entries_in_queue = translation_import_queue.getAllEntries(
561+ target=spr.sourcepackage)
562+ self.assertEqual(entries_in_queue[0].importer.name, "foobar")
563+
564+ def test_run(self):
565+ archive = self.factory.makeArchive()
566+ foo_pkg = self.factory.makeSourcePackageRelease(archive=archive)
567+ method = FakeMethod()
568+ removeSecurityProxy(foo_pkg).attachTranslationFiles = method
569+ spr, job = self.makeJob(archive=archive, sourcepackagerelease=foo_pkg)
570+ transaction.commit()
571+ job.run()
572+ self.assertEqual(method.call_count, 1)
573+
574+ def test_smoke(self):
575+ tar_content = {
576+ 'source/po/foobar.pot': 'FooBar template',
577+ }
578+ spr, job = self.makeJob(tar_content=tar_content)
579+ transaction.commit()
580+ out, err, exit_code = run_script(
581+ "LP_DEBUG_SQL=1 cronscripts/process-job-source.py -vv %s" % (
582+ IPackageTranslationsUploadJobSource.getName()))
583+
584+ self.addDetail("stdout", text_content(out))
585+ self.addDetail("stderr", text_content(err))
586+
587+ self.assertEqual(0, exit_code)
588+ translation_import_queue = getUtility(ITranslationImportQueue)
589+ entries_in_queue = translation_import_queue.getAllEntries(
590+ target=spr.sourcepackage)
591+
592+ self.assertEqual(1, entries_in_queue.count())
593+ # Check if the file in tar_content is queued:
594+ self.assertTrue("po/foobar.pot", entries_in_queue[0].path)
595+
596+
597+class TestViaCelery(LocalTestHelper):
598+ """PackageTranslationsUploadJob runs under Celery."""
599+
600+ layer = CeleryJobLayer
601+
602+ def test_run(self):
603+ self.useFixture(FeatureFixture({
604+ 'jobs.celery.enabled_classes': 'PackageTranslationsUploadJob',
605+ }))
606+
607+ spr, job = self.makeJob()
608+ with block_on_job(self):
609+ transaction.commit()
610+ translation_import_queue = getUtility(ITranslationImportQueue)
611+ entries_in_queue = translation_import_queue.getAllEntries(
612+ target=spr.sourcepackage).count()
613+ self.assertEqual(2, entries_in_queue)