~launchpad-pqm/launchpad/devel

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)