13668.1.21
by Curtis Hovey
Updated copyrights. |
1 |
# Copyright 2009-2011 Canonical Ltd. This software is licensed under the
|
8687.15.18
by Karl Fogel
Add the copyright header block to files under lib/canonical/. |
2 |
# GNU Affero General Public License version 3 (see the file LICENSE).
|
1702
by Canonical.com Patch Queue Manager
PO attach functional test, plus miscellaneous fixes (r=Steve) |
3 |
|
2570.1.2
by Carlos Perello Marin
Implemented initial support for the translation import queue and removed the old attach script |
4 |
"""Functions used with the Rosetta PO import script."""
|
1702
by Canonical.com Patch Queue Manager
PO attach functional test, plus miscellaneous fixes (r=Steve) |
5 |
|
6 |
__metaclass__ = type |
|
7 |
||
4422.3.1
by Jeroen Vermeulen
Limit PO import cron script's runtime to "a bit over 8 minutes." |
8 |
|
9480.5.1
by Jeroen Vermeulen
Split out approval script code. Clean up queues for obsolete distroseries. Prepare for gc'ing Failed entries. |
9 |
__all__ = [ |
10100.1.3
by Jonathan Lange
Fix flakes and don't import names from pytz |
10 |
'TranslationsImport', |
9480.5.1
by Jeroen Vermeulen
Split out approval script code. Clean up queues for obsolete distroseries. Prepare for gc'ing Failed entries. |
11 |
]
|
12 |
||
11403.1.4
by Henning Eggers
Reformatted imports using format-imports script r32. |
13 |
from datetime import ( |
14 |
datetime, |
|
15 |
timedelta, |
|
16 |
)
|
|
9592.3.1
by Jeroen Vermeulen
Reconstructed changes after merge trouble. |
17 |
import sys |
4422.3.1
by Jeroen Vermeulen
Limit PO import cron script's runtime to "a bit over 8 minutes." |
18 |
|
10100.1.3
by Jonathan Lange
Fix flakes and don't import names from pytz |
19 |
import pytz |
1789
by Canonical.com Patch Queue Manager
PoTemplateAdmin implementation (finally) r=jamesh,mpt |
20 |
from zope.component import getUtility |
21 |
||
5238.1.1
by Jeroen Vermeulen
Cleaned up import mechanism; importFromQueue now returns details for confirmation email. Calling script does the sending, which will not happen in the presence of exceptions. |
22 |
from canonical.config import config |
23 |
from canonical.launchpad import helpers |
|
14565.2.19
by Curtis Hovey
Import MailWrapper from true location. |
24 |
from lp.services.mail.mailwrapper import MailWrapper |
9592.3.1
by Jeroen Vermeulen
Reconstructed changes after merge trouble. |
25 |
from canonical.launchpad.webapp import errorlog |
13130.1.12
by Curtis Hovey
Sorted imports. |
26 |
from lp.app.interfaces.launchpad import ILaunchpadCelebrities |
13668.1.22
by Curtis Hovey
Sorted imports. |
27 |
from lp.services.mail.sendmail import simple_sendmail |
9592.3.1
by Jeroen Vermeulen
Reconstructed changes after merge trouble. |
28 |
from lp.services.scripts.base import LaunchpadCronScript |
11818.3.1
by Jeroen Vermeulen
Split out lp.translations.enums and lp.translations.interfaces.hastranslationimports. |
29 |
from lp.translations.enums import RosettaImportStatus |
11403.1.4
by Henning Eggers
Reformatted imports using format-imports script r32. |
30 |
from lp.translations.interfaces.translationimportqueue import ( |
31 |
ITranslationImportQueue, |
|
32 |
)
|
|
9592.3.1
by Jeroen Vermeulen
Reconstructed changes after merge trouble. |
33 |
|
34 |
||
35 |
class TranslationsImport(LaunchpadCronScript): |
|
1998
by Canonical.com Patch Queue Manager
TranslationValidation implementation for .po imports r=spiv |
36 |
"""Import .po and .pot files attached to Rosetta."""
|
9592.3.1
by Jeroen Vermeulen
Reconstructed changes after merge trouble. |
37 |
# Time goal for one run. It is not exact. The script can run for
|
38 |
# longer than this, but will know to stop taking on new work.
|
|
9592.3.5
by Jeroen Vermeulen
Cosmetic changes from review. |
39 |
# Since the script is run every 9 or 10 minutes, we set the goal
|
9592.3.1
by Jeroen Vermeulen
Reconstructed changes after merge trouble. |
40 |
# at 8 minutes. That leaves a bit of time to complete the last
|
41 |
# ongoing batch of imports.
|
|
42 |
time_to_run = timedelta(minutes=8) |
|
43 |
||
44 |
# Failures to be reported in bulk at closing, so we don't accumulate
|
|
45 |
# thousands of oopses for repetitive errors.
|
|
46 |
failures = None |
|
47 |
||
48 |
def __init__(self, *args, **kwargs): |
|
49 |
super(TranslationsImport, self).__init__(*args, **kwargs) |
|
50 |
self.failures = {} |
|
51 |
||
52 |
def _describeEntry(self, entry): |
|
53 |
"""Identify `entry` in a human-readable way."""
|
|
54 |
if entry.import_into: |
|
55 |
return "%s (id %d)" % (entry.import_into.title, entry.id) |
|
56 |
||
57 |
if entry.sourcepackagename: |
|
58 |
return "'%s' (id %d) in %s %s package %s" % ( |
|
59 |
entry.path, entry.id, |
|
60 |
entry.distroseries.distribution.name, |
|
61 |
entry.distroseries.displayname, |
|
62 |
entry.sourcepackagename.name) |
|
63 |
else: |
|
64 |
return "'%s' (id %d) in %s" % ( |
|
65 |
entry.path, entry.id, entry.productseries.title) |
|
66 |
||
67 |
def _reportOops(self, reason, entries, exc_info=None): |
|
68 |
"""Register an oops."""
|
|
69 |
if exc_info is None: |
|
70 |
exc_info = sys.exc_info() |
|
71 |
description = [ |
|
72 |
('Reason', reason), |
|
9719.4.1
by Jeroen Vermeulen
Fix bug in oops reporting for SystemErrors and assertion failures. |
73 |
('Entries', str(entries)), |
9592.3.1
by Jeroen Vermeulen
Reconstructed changes after merge trouble. |
74 |
]
|
75 |
errorlog.globalErrorUtility.raising( |
|
76 |
exc_info, errorlog.ScriptRequest(description)) |
|
77 |
||
78 |
def _registerFailure(self, entry, reason, traceback=False, abort=False): |
|
79 |
"""Note that a queue entry is unusable in some way."""
|
|
9719.4.1
by Jeroen Vermeulen
Fix bug in oops reporting for SystemErrors and assertion failures. |
80 |
reason_text = unicode(reason) |
9893.3.1
by Henning Eggers
Merged API work. |
81 |
entry.setStatus(RosettaImportStatus.FAILED, |
82 |
getUtility(ILaunchpadCelebrities).rosetta_experts) |
|
9719.4.1
by Jeroen Vermeulen
Fix bug in oops reporting for SystemErrors and assertion failures. |
83 |
entry.setErrorOutput(reason_text) |
9592.3.1
by Jeroen Vermeulen
Reconstructed changes after merge trouble. |
84 |
|
85 |
if abort: |
|
86 |
traceback = True |
|
87 |
||
88 |
description = self._describeEntry(entry) |
|
9719.4.1
by Jeroen Vermeulen
Fix bug in oops reporting for SystemErrors and assertion failures. |
89 |
message = "%s -- %s" % (reason_text, description) |
9592.3.1
by Jeroen Vermeulen
Reconstructed changes after merge trouble. |
90 |
self.logger.error(message, exc_info=traceback) |
91 |
||
92 |
if abort: |
|
93 |
# Serious enough to stop the script. Register as an
|
|
94 |
# individual oops.
|
|
9719.4.1
by Jeroen Vermeulen
Fix bug in oops reporting for SystemErrors and assertion failures. |
95 |
self._reportOops(reason, [description]) |
9592.3.1
by Jeroen Vermeulen
Reconstructed changes after merge trouble. |
96 |
else: |
97 |
# Register problem for bulk reporting later.
|
|
9719.4.1
by Jeroen Vermeulen
Fix bug in oops reporting for SystemErrors and assertion failures. |
98 |
if not self.failures.get(reason_text): |
99 |
self.failures[reason_text] = [] |
|
100 |
self.failures[reason_text].append(description) |
|
9592.3.1
by Jeroen Vermeulen
Reconstructed changes after merge trouble. |
101 |
|
102 |
def _checkEntry(self, entry): |
|
103 |
"""Sanity-check `entry` before importing."""
|
|
104 |
if entry.import_into is None: |
|
105 |
self._registerFailure( |
|
106 |
entry, "Entry is approved but has no place to import to.") |
|
107 |
return False |
|
108 |
||
109 |
template = entry.potemplate |
|
110 |
if template: |
|
111 |
if template.distroseries != entry.distroseries: |
|
112 |
self._registerFailure( |
|
113 |
entry, "Entry was approved for the wrong distroseries.") |
|
114 |
return False |
|
115 |
if template.productseries != entry.productseries: |
|
116 |
self._registerFailure( |
|
117 |
entry, "Entry was approved for the wrong productseries.") |
|
118 |
return False |
|
119 |
||
120 |
return True |
|
121 |
||
11551.5.1
by Jeroen Vermeulen
Don't send import notifications to vcs-imports. |
122 |
def _shouldNotify(self, person): |
123 |
"""Is `person` someone we should send notification emails?"""
|
|
124 |
# We don't notify the vcs-imports team, which owns all mirrored
|
|
125 |
# branches. Templates generated in the build farm based on
|
|
126 |
# mirrored branches are uploaded in the name of this team, but
|
|
127 |
# there is no point in sending out notifications to them.
|
|
128 |
return person != getUtility(ILaunchpadCelebrities).vcs_imports |
|
129 |
||
9592.3.1
by Jeroen Vermeulen
Reconstructed changes after merge trouble. |
130 |
def _importEntry(self, entry): |
131 |
"""Perform the import of one entry, and notify the uploader."""
|
|
132 |
target = entry.import_into |
|
11551.5.1
by Jeroen Vermeulen
Don't send import notifications to vcs-imports. |
133 |
self.logger.info('Importing: %s' % target.title) |
9592.3.1
by Jeroen Vermeulen
Reconstructed changes after merge trouble. |
134 |
(mail_subject, mail_body) = target.importFromQueue(entry, self.logger) |
135 |
||
11551.5.1
by Jeroen Vermeulen
Don't send import notifications to vcs-imports. |
136 |
if mail_subject is not None and self._shouldNotify(entry.importer): |
9592.3.1
by Jeroen Vermeulen
Reconstructed changes after merge trouble. |
137 |
# A `mail_subject` of None indicates that there
|
138 |
# is no notification worth sending out.
|
|
14168.1.1
by Danilo Segan
Replace all use of rosetta@launchpad.net with more appropriate addresses. |
139 |
from_email = config.rosetta.notification_address |
9592.3.1
by Jeroen Vermeulen
Reconstructed changes after merge trouble. |
140 |
katie = getUtility(ILaunchpadCelebrities).katie |
141 |
if entry.importer == katie: |
|
142 |
# Email import state to Debian imports email.
|
|
14168.1.1
by Danilo Segan
Replace all use of rosetta@launchpad.net with more appropriate addresses. |
143 |
to_email = None |
9592.3.1
by Jeroen Vermeulen
Reconstructed changes after merge trouble. |
144 |
else: |
145 |
to_email = helpers.get_contact_email_addresses(entry.importer) |
|
146 |
||
147 |
if to_email: |
|
148 |
text = MailWrapper().format(mail_body) |
|
149 |
simple_sendmail(from_email, to_email, mail_subject, text) |
|
150 |
||
9826.11.59
by Aaron Bentley
Fix test that was altering oops reporting behaviour. |
151 |
def run(self, *args, **kwargs): |
152 |
errorlog.globalErrorUtility.configure('poimport') |
|
153 |
LaunchpadCronScript.run(self, *args, **kwargs) |
|
154 |
||
9592.3.1
by Jeroen Vermeulen
Reconstructed changes after merge trouble. |
155 |
def main(self): |
156 |
"""Import entries from the queue."""
|
|
157 |
self.logger.debug("Starting the import process.") |
|
158 |
||
10100.1.3
by Jonathan Lange
Fix flakes and don't import names from pytz |
159 |
self.deadline = datetime.now(pytz.UTC) + self.time_to_run |
3200.1.1
by Carlos Perello Marin
Changed the way the import queue works and removed the old rawimports from POFile and POTemplate. Now we have just one place with all imports + a ton of tests fixes |
160 |
translation_import_queue = getUtility(ITranslationImportQueue) |
161 |
||
3691.8.100
by Carlos Perello Marin
Applied patch to refresh more often the list of products/distroseries with translations waiting to be imported |
162 |
# Get the list of each product or distroseries with pending imports.
|
4422.3.1
by Jeroen Vermeulen
Limit PO import cron script's runtime to "a bit over 8 minutes." |
163 |
# We'll serve these queues in turn, one request each, until either the
|
164 |
# queue is drained or our time is up.
|
|
5967.6.3
by Jeroen Vermeulen
Renamed getPillarObjectsWithImports to getRequestTargets. |
165 |
importqueues = translation_import_queue.getRequestTargets( |
4268.3.23
by Carlos Perello Marin
Ported old import queue page to use the new infrastructure |
166 |
RosettaImportStatus.APPROVED) |
4422.3.1
by Jeroen Vermeulen
Limit PO import cron script's runtime to "a bit over 8 minutes." |
167 |
|
168 |
if not importqueues: |
|
169 |
self.logger.info("No requests pending.") |
|
170 |
return
|
|
171 |
||
9592.3.1
by Jeroen Vermeulen
Reconstructed changes after merge trouble. |
172 |
have_work = True |
173 |
||
10100.1.3
by Jonathan Lange
Fix flakes and don't import names from pytz |
174 |
while have_work and datetime.now(pytz.UTC) < self.deadline: |
9592.3.1
by Jeroen Vermeulen
Reconstructed changes after merge trouble. |
175 |
have_work = False |
176 |
||
177 |
# For fairness, service all queues at least once; don't
|
|
178 |
# check for deadlines here or we'd favour some
|
|
179 |
# products/packages over others.
|
|
4268.1.1
by Danilo Šegan
Implement round-robin translation import queue per-product and per-distrorelease. |
180 |
for queue in importqueues: |
9592.3.1
by Jeroen Vermeulen
Reconstructed changes after merge trouble. |
181 |
entry = queue.getFirstEntryToImport() |
182 |
if entry is None: |
|
183 |
continue
|
|
184 |
||
185 |
have_work = True |
|
186 |
||
187 |
try: |
|
188 |
if self._checkEntry(entry): |
|
189 |
self._importEntry(entry) |
|
190 |
if self.txn: |
|
191 |
self.txn.commit() |
|
192 |
except KeyboardInterrupt: |
|
193 |
raise
|
|
194 |
except (AssertionError, SystemError), e: |
|
195 |
self._registerFailure(entry, e, abort=True) |
|
196 |
raise
|
|
197 |
except Exception, e: |
|
198 |
if self.txn: |
|
199 |
self.txn.abort() |
|
200 |
self._registerFailure(entry, e, traceback=True) |
|
201 |
if self.txn: |
|
202 |
self.txn.commit() |
|
203 |
||
204 |
if have_work: |
|
205 |
self.logger.info("Used up available time.") |
|
206 |
else: |
|
4422.3.1
by Jeroen Vermeulen
Limit PO import cron script's runtime to "a bit over 8 minutes." |
207 |
self.logger.info("Import requests completed.") |
9592.3.1
by Jeroen Vermeulen
Reconstructed changes after merge trouble. |
208 |
|
209 |
self._reportFailures() |
|
9592.3.5
by Jeroen Vermeulen
Cosmetic changes from review. |
210 |
|
9592.3.1
by Jeroen Vermeulen
Reconstructed changes after merge trouble. |
211 |
self.logger.debug("Finished the import process.") |
212 |
||
213 |
def _reportFailures(self): |
|
214 |
"""Bulk-report deferred failures as oopses."""
|
|
215 |
for reason, entries in self.failures.iteritems(): |
|
216 |
self._reportOops(reason, entries) |