~ubuntu-archive/ubuntu-archive-tools/trunk

1374.1.1 by Brian Murray
phased-updater: transition from python2 to python3
1
#!/usr/bin/python3
705.1.1 by Brian Murray
first draft of phased-updater
2
3
# Copyright (C) 2013 Canonical Ltd.
4
# Author: Brian Murray <brian.murray@canonical.com>
5
6
# This program is free software: you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License as published by
8
# the Free Software Foundation; version 3 of the License.
9
#
10
# This program is distributed in the hope that it will be useful,
11
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
# GNU General Public License for more details.
14
#
15
# You should have received a copy of the GNU General Public License
16
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
18
'''Increment the Phased-Update-Percentage for a package
19
705.1.31 by Brian Murray
do not set pup to 0 if it is already 0, fix line length issues
20
Check to see whether or not there is a regression (new crash bucket or
834.1.1 by Brian Murray
phased-updater: only add version to web_link if it wasn't provided
21
increase in rate of errors about a package) using errors.ubuntu.com and if
705.1.31 by Brian Murray
do not set pup to 0 if it is already 0, fix line length issues
22
not increment the Phased-Update-Percentage for the package.
705.1.38 by Brian Murray
for stopped updates show the percentage the update was stopped at and link to the binary publishing history
23
Additionally, generate an html report regarding state of phasing of
24
packages and email uploaders regarding issues with their uploads.
705.1.1 by Brian Murray
first draft of phased-updater
25
'''
26
27
705.1.7 by Brian Murray
create code for using errors api package-version-new-buckets
28
import apt
1027.2.1 by Brian Murray
phased-updater: using codes.open and a utf-8 encoding in case signer's have unicode in their name.
29
import codecs
705.1.4 by Brian Murray
implement whitelist support
30
import csv
705.1.1 by Brian Murray
first draft of phased-updater
31
import datetime
1100.1.1 by Brian Murray
phased-updater: workaround LP timing out when we call getPublishedBinaries()
32
import lazr
705.1.45 by Brian Murray
switched from print to logging
33
import logging
705.1.43 by Brian Murray
change html report to be a tmpfile which is then copied to the correct location
34
import os
705.1.1 by Brian Murray
first draft of phased-updater
35
import simplejson as json
36
import time
37
705.1.9 by Brian Murray
display the number of days since the update was published
38
from collections import defaultdict, OrderedDict
892.1.1 by Brian Murray
phased-updater: use the launchpad changes file to find an email address to notify about a stopped phased update
39
from email import utils
1322.1.1 by Brian Murray
phased-updater: improve email, and creator and signer to the report, link to investigative tips
40
from functools import cmp_to_key
705.1.25 by Brian Murray
add in support for other launchpad instances, add % sign to update percentage
41
from optparse import OptionParser
1374.1.1 by Brian Murray
phased-updater: transition from python2 to python3
42
from urllib.parse import quote
43
from urllib.request import urlopen
705.1.4 by Brian Murray
implement whitelist support
44
705.1.55 by Brian Murray
directly call changeOverride in the Launchpad API rather than using subprocess to call change-override
45
import lputils
46
705.1.1 by Brian Murray
first draft of phased-updater
47
from launchpadlib.launchpad import Launchpad
48
757 by Stéphane Graber
Fix all PEP-8 warnings.
49
705.1.37 by Brian Murray
create a function for determing a launchpad user's email address
50
def get_primary_email(lp_user):
51
    try:
52
        lp_user_email = lp_user.preferred_email_address.email
53
    except ValueError as e:
1374.1.1 by Brian Murray
phased-updater: transition from python2 to python3
54
        if 'server-side permission' in str(e):
705.1.45 by Brian Murray
switched from print to logging
55
            logging.info("%s has hidden their email addresses" %
757 by Stéphane Graber
Fix all PEP-8 warnings.
56
                         lp_user.web_link)
705.1.37 by Brian Murray
create a function for determing a launchpad user's email address
57
            return ''
705.1.45 by Brian Murray
switched from print to logging
58
        logging.info("Error accessing %s's preferred email address: %s" %
757 by Stéphane Graber
Fix all PEP-8 warnings.
59
                     (lp_user.web_link, e.message))
705.1.37 by Brian Murray
create a function for determing a launchpad user's email address
60
        return ''
61
    return lp_user_email
62
757 by Stéphane Graber
Fix all PEP-8 warnings.
63
705.1.55 by Brian Murray
directly call changeOverride in the Launchpad API rather than using subprocess to call change-override
64
def set_pup(current_pup, new_pup, release, suite, src_pkg):
65
    options.series = release
66
    options.suite = suite
67
    options.pocket = 'Updates'
68
    options.version = None
69
    source = lputils.find_latest_published_source(options, src_pkg)
959 by William Grant
Fix a few missed cases that should be skipping ddebs. One never manipulates a ddeb directly.
70
    publications = [
71
        binary for binary in source.getPublishedBinaries()
72
        if not binary.is_debug]
705.1.55 by Brian Murray
directly call changeOverride in the Launchpad API rather than using subprocess to call change-override
73
74
    for pub in publications:
964 by Colin Watson
phased-updater: Don't override if latest publication isn't Published
75
        if pub.status != 'Published':
76
            continue
705.1.55 by Brian Murray
directly call changeOverride in the Launchpad API rather than using subprocess to call change-override
77
        pub.changeOverride(new_phased_update_percentage=new_pup)
78
        if new_pup != 0:
79
            logging.info('Incremented p-u-p for %s %s from %s%% to %s%%' %
757 by Stéphane Graber
Fix all PEP-8 warnings.
80
                         (suite, pub.binary_package_name,
81
                          current_pup, new_pup))
705.1.55 by Brian Murray
directly call changeOverride in the Launchpad API rather than using subprocess to call change-override
82
        else:
757 by Stéphane Graber
Fix all PEP-8 warnings.
83
            logging.info('Set p-u-p to 0%% from %s%% for %s %s' %
84
                         (current_pup, suite, pub.binary_package_name))
85
705.1.28 by Brian Murray
implemented setting of phased_update_percentage to 0 or incrementing it
86
705.1.12 by Brian Murray
create an increased rate of crashes email, use one dictionary for problem and rate information
87
def generate_html_report(releases, buckets):
705.1.43 by Brian Murray
change html report to be a tmpfile which is then copied to the correct location
88
    import tempfile
89
    import shutil
1374.1.1 by Brian Murray
phased-updater: transition from python2 to python3
90
    with tempfile.NamedTemporaryFile(mode='w') as report:
705.1.43 by Brian Murray
change html report to be a tmpfile which is then copied to the correct location
91
        report.write('''<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
705.1.1 by Brian Murray
first draft of phased-updater
92
 "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
93
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
94
<head>
95
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
96
  <title>Released Ubuntu SRUs</title>
97
  <style type="text/css">
98
    body { background: #CCCCB0; color: black; }
99
    a { text-decoration: none; }
100
    table { border-collapse: collapse; border-style: solid none;
101
            border-width: 3px; margin-bottom: 3ex; empty-cells: show; }
102
    table th { text-align: left; border-style: none none dotted none;
103
               border-width: 1px; padding-right: 10px; }
104
    table td { text-align: left; border-style: none none dotted none;
105
               border-width: 1px; padding-right: 10px; }
106
    .noborder { border-style: none; }
107
    a { color: blue; }
705.1.14 by Brian Murray
keep track of emails sent by the phased-updater
108
    a:visited { color: black; }
705.1.1 by Brian Murray
first draft of phased-updater
109
  </style>
110
</head>
111
<body>
705.1.55 by Brian Murray
directly call changeOverride in the Launchpad API rather than using subprocess to call change-override
112
<h1>Phasing %sUbuntu Stable Release Updates</h1>
113
''' % ('', 'and Released ')[options.fully_phased])
757 by Stéphane Graber
Fix all PEP-8 warnings.
114
        report.write(
115
            '<p>Generated: %s by '
116
            '<a href="http://bazaar.launchpad.net/'
117
            '~ubuntu-archive/ubuntu-archive-tools/trunk/annotate/head%%3A/'
118
            'phased-updater">phased-updater</a></p>' %
1073 by Colin Watson
Report ISO dates.
119
            time.strftime('%F %T UTC', time.gmtime()))
705.1.43 by Brian Murray
change html report to be a tmpfile which is then copied to the correct location
120
        report.write('''<p>A <a
1322.1.1 by Brian Murray
phased-updater: improve email, and creator and signer to the report, link to investigative tips
121
href="https://wiki.ubuntu.com/StableReleaseUpdates">stable release
122
update</a> is either being phased (update percentage is not 0%) or phasing has
123
been stopped (update percentage is 0%) for the following packages and releases.
124
<p>The phasing stops if there is either an increased rate of crashes or an
125
error has been found that has only been seen with the SRU'ed version of the
126
package.<p>
127
<a href="https://wiki.ubuntu.com/StableReleaseUpdates#Phasing">Learn more</a>
128
about investigating halted phased updates.''')
705.1.43 by Brian Murray
change html report to be a tmpfile which is then copied to the correct location
129
        for release in releases:
130
            rname = release.name
131
            if not buckets[rname]:
132
                continue
133
            report.write('''<h3>%s</h3>\n''' % rname)
134
            report.write('''<table>\n''')
705.1.31 by Brian Murray
do not set pup to 0 if it is already 0, fix line length issues
135
            report.write('''<tr>
705.1.43 by Brian Murray
change html report to be a tmpfile which is then copied to the correct location
136
      <th>Package</th>
1322.1.1 by Brian Murray
phased-updater: improve email, and creator and signer to the report, link to investigative tips
137
      <th>Version (signer, creator)</th>
705.1.43 by Brian Murray
change html report to be a tmpfile which is then copied to the correct location
138
      <th>Update Percentage</th>
139
      <th>Rate Increase</th>
1322.1.1 by Brian Murray
phased-updater: improve email, and creator and signer to the report, link to investigative tips
140
      <th>Errors</th>
705.1.43 by Brian Murray
change html report to be a tmpfile which is then copied to the correct location
141
      <th>Days</th>
142
    </tr>''')
1374.1.1 by Brian Murray
phased-updater: transition from python2 to python3
143
            for spph_link in buckets[rname]:
144
                # need to reload the pub_source information
145
                pub_source = launchpad.load(spph_link)
705.1.43 by Brian Murray
change html report to be a tmpfile which is then copied to the correct location
146
                pkg = pub_source.source_package_name
147
                version = pub_source.source_package_version
767.2.1 by Brian Murray
phased-updater: identify security updates
148
                # Identify security updates
149
                if options.fully_phased:
150
                    sec_pubs = [(ps.source_package_name,
151
                                 ps.source_package_version)
152
                                for ps in archive.getPublishedSources(
153
                                distro_series=release, pocket='Security',
154
                                source_name=pkg, version=version,
155
                                exact_match=True)]
156
                    if (pkg, version) in sec_pubs:
157
                        version += ' (security)'
705.1.43 by Brian Murray
change html report to be a tmpfile which is then copied to the correct location
158
                age = (datetime.datetime.now() -
757 by Stéphane Graber
Fix all PEP-8 warnings.
159
                       pub_source.date_published.replace(tzinfo=None)).days
1374.1.1 by Brian Murray
phased-updater: transition from python2 to python3
160
                update_percentage = buckets[rname][spph_link].get('pup', 100)
705.1.48 by Brian Murray
remove testing code add switch for showing updates that have been fully phased
161
                if not options.fully_phased and update_percentage == 100:
162
                    continue
1322.1.1 by Brian Murray
phased-updater: improve email, and creator and signer to the report, link to investigative tips
163
                signer = str(pub_source.package_signer).split('~')[-1]
164
                uploaders = '<a href="%s/~%s">%s</a>' % \
165
                    (LP_BASE_URL, signer, signer)
166
                if pub_source.package_creator \
167
                        and pub_source.package_creator != pub_source.package_signer:
168
                    creator = str(pub_source.package_creator).split('~')[-1]
169
                    uploaders += ', <a href="%s/~%s">%s</a>' % \
170
                        (LP_BASE_URL, creator, creator)
705.1.48 by Brian Murray
remove testing code add switch for showing updates that have been fully phased
171
                lpurl = '%s/ubuntu/+source/%s/' % (LP_BASE_URL, pkg)
705.1.43 by Brian Murray
change html report to be a tmpfile which is then copied to the correct location
172
                report.write('''<tr>
173
      <td><a href="%s">%s</a></td>
1322.1.1 by Brian Murray
phased-updater: improve email, and creator and signer to the report, link to investigative tips
174
      <td><a href="%s">%s</a> (%s)</td>\n''' %
175
                             (lpurl, pkg, lpurl + version, version, uploaders))
705.1.43 by Brian Murray
change html report to be a tmpfile which is then copied to the correct location
176
                report.write('  <td>')
177
                if update_percentage == 0:
178
                    binary_pub = pub_source.getPublishedBinaries()[0]
179
                    arch = binary_pub.distro_arch_series.architecture_tag
757 by Stéphane Graber
Fix all PEP-8 warnings.
180
                    bpph_url = ('%s/ubuntu/%s/%s/%s' %
181
                                (LP_BASE_URL, rname, arch,
182
                                 binary_pub.binary_package_name))
705.1.43 by Brian Murray
change html report to be a tmpfile which is then copied to the correct location
183
                    report.write('<a href="%s">%s%% of users' %
757 by Stéphane Graber
Fix all PEP-8 warnings.
184
                                 (bpph_url, update_percentage))
705.1.43 by Brian Murray
change html report to be a tmpfile which is then copied to the correct location
185
                    previous_pup = \
1374.1.1 by Brian Murray
phased-updater: transition from python2 to python3
186
                        buckets[rname][spph_link]['previous_pup']
705.1.43 by Brian Murray
change html report to be a tmpfile which is then copied to the correct location
187
                    if previous_pup != 0:
188
                        report.write(' (was %s%%)</a>' % previous_pup)
705.1.13 by Brian Murray
sort releases, use real_new_bucket, print a has in the html report
189
                    else:
705.1.43 by Brian Murray
change html report to be a tmpfile which is then copied to the correct location
190
                        report.write('</a>')
191
                else:
192
                    report.write('%s%% of users' % update_percentage)
193
                report.write('</td>\n')
1374.1.1 by Brian Murray
phased-updater: transition from python2 to python3
194
                if 'rate' in buckets[rname][spph_link]:
195
                    data = buckets[rname][spph_link]['rate']
705.1.43 by Brian Murray
change html report to be a tmpfile which is then copied to the correct location
196
                    report.write('  <td><a href="%s">+%s</a></td>\n' %
197
                                 (data[1], data[0]))
198
                else:
199
                    report.write('  <td></td>\n')
200
                report.write('  <td>')
1374.1.1 by Brian Murray
phased-updater: transition from python2 to python3
201
                if 'buckets' in buckets[rname][spph_link]:
839.1.1 by Brian Murray
phased-updater: if there are no problems with a source package upload, don't check the signer etc, and if the package has no signer (like with a copy from debian) stop trying to create an email
202
                    # TODO: it'd be great if these were sorted
1374.1.1 by Brian Murray
phased-updater: transition from python2 to python3
203
                    for bucket in buckets[rname][spph_link]['buckets']:
705.1.43 by Brian Murray
change html report to be a tmpfile which is then copied to the correct location
204
                        if 'problem' in bucket:
205
                            # create a short version of the problem's hash
206
                            phash = bucket.replace(
207
                                'https://errors.ubuntu.com/problem/', '')[0:6]
208
                            report.write('<a href="%s">%s</a> ' % (bucket,
757 by Stéphane Graber
Fix all PEP-8 warnings.
209
                                         phash))
1400.2.3 by Brian Murray
phased-updater: check to see who set the phased_update_percentage and respect it
210
                        elif 'force-stop' in bucket:
211
                            report.write('The phasing of this update was '
212
                                         'manually stopped by an AA.')
705.1.43 by Brian Murray
change html report to be a tmpfile which is then copied to the correct location
213
                        else:
214
                            report.write('<a href="%s">problem</a> ' % bucket)
215
                else:
216
                    report.write('')
217
                report.write('</td>\n')
218
                report.write('  <td>%s</td>\n' % age)
219
                report.write('</tr>\n')
220
            report.write('''</table>\n''')
221
        report.write('''</body>\n''')
222
        report.write('''</html>''')
223
        report.flush()
939.1.1 by Brian Murray
phased-updater: include a link to the html report showing phasing progress when e-mailing people.
224
        shutil.copy2(report.name, '%s/%s' % (os.getcwd(), REPORT_FILE))
225
        os.chmod('%s/%s' % (os.getcwd(), REPORT_FILE), 0o644)
705.1.1 by Brian Murray
first draft of phased-updater
226
757 by Stéphane Graber
Fix all PEP-8 warnings.
227
705.1.12 by Brian Murray
create an increased rate of crashes email, use one dictionary for problem and rate information
228
def create_email_notifications(releases, spph_buckets):
705.1.5 by Brian Murray
create email using email package from python
229
    import smtplib
230
    from email.mime.text import MIMEText
763 by Colin Watson
phased-updater: allow phased-updates-emails.txt to be missing
231
    notifications = defaultdict(list)
232
    try:
1027.2.1 by Brian Murray
phased-updater: using codes.open and a utf-8 encoding in case signer's have unicode in their name.
233
        with codecs.open(NOTIFICATIONS, 'r', encoding='utf-8') as notify_file:
234
            for line in notify_file.readlines():
235
                line = line.strip('\n').split(', ')
763 by Colin Watson
phased-updater: allow phased-updates-emails.txt to be missing
236
                # LP name, problem, pkg_version
1027.2.2 by Brian Murray
address reviewer feedback
237
                person = line[0]
1027.2.1 by Brian Murray
phased-updater: using codes.open and a utf-8 encoding in case signer's have unicode in their name.
238
                problem = line[1]
239
                pkg = line[2]
240
                pkg_version = line[3]
763 by Colin Watson
phased-updater: allow phased-updates-emails.txt to be missing
241
                notifications[person].append((problem, pkg, pkg_version))
242
    except IOError:
243
        pass
793.1.1 by Brian Murray
phased-updater: add my email address as reply-to and include myself in the cc's
244
    bdmurray_mail = 'brian@ubuntu.com'
705.1.50 by Brian Murray
conditionally pluralize email body and format log as sru-report does
245
    b_body = ('Your upload of %s version %s to %s has resulted in %s'
1322.1.1 by Brian Murray
phased-updater: improve email, and creator and signer to the report, link to investigative tips
246
              'error%s[1] that %s first reported about this version of the '
705.1.50 by Brian Murray
conditionally pluralize email body and format log as sru-report does
247
              'package.  The error%s follow%s:\n\n'
705.1.22 by Brian Murray
make email code slightly less redundant
248
              '%s\n\n')
705.1.12 by Brian Murray
create an increased rate of crashes email, use one dictionary for problem and rate information
249
    i_body = ('Your upload of %s version %s to %s has resulted in an '
1322.1.1 by Brian Murray
phased-updater: improve email, and creator and signer to the report, link to investigative tips
250
              'increased daily rate of errors[1] for the package compared '
705.1.36 by Brian Murray
protect against hidden email addresses and clarify some text
251
              'to the previous two weeks. For problems currently being '
252
              'reported about the package see:\n\n'
1184.1.1 by Brian Murray
phased-updater: switch from period of day to week
253
              '%s&period=week\n\n')
1322.1.1 by Brian Murray
phased-updater: improve email, and creator and signer to the report, link to investigative tips
254
    remedy = ('The current status of the phasing of all stable release '
255
              'updates, including yours, is available at:\n\n'
939.1.2 by Brian Murray
phased-updater: add and extra \n
256
              'http://people.canonical.com/~ubuntu-archive/%s\n\n'
939.1.1 by Brian Murray
phased-updater: include a link to the html report showing phasing progress when e-mailing people.
257
              'Further phasing of this update has been stopped until the '
1322.1.1 by Brian Murray
phased-updater: improve email, and creator and signer to the report, link to investigative tips
258
              'errors have either been fixed or determined[2] to not be a '
259
              'result of this stable release update.  In the event of '
705.1.31 by Brian Murray
do not set pup to 0 if it is already 0, fix line length issues
260
              'the latter please let a member of the Ubuntu Stable Release '
1322.1.1 by Brian Murray
phased-updater: improve email, and creator and signer to the report, link to investigative tips
261
              'Updates team[3] know so that phasing of the update can '
262
              'proceed.\n\n'
263
              '[1] Request access to the Ubuntu Error Tracker at '
264
              'https://forms.canonical.com/reports/.\n'
265
              '[2] Learn more about investigating at '
266
              'https://wiki.ubuntu.com/StableReleaseUpdates#Phasing\n' 
267
              '[3] https://launchpad.net/~ubuntu-sru' %
268
              (REPORT_FILE))
705.1.11 by Brian Murray
improve email formatting
269
    for release in releases:
705.1.31 by Brian Murray
do not set pup to 0 if it is already 0, fix line length issues
270
        rname = release.name
1374.1.1 by Brian Murray
phased-updater: transition from python2 to python3
271
        for spph_link in spph_buckets[rname]:
272
            # need to reload the pub_source information
273
            pub_source = launchpad.load(spph_link)
274
            update_percentage = spph_buckets[rname][spph_link].get('pup', 100)
764.2.1 by Brian Murray
phased-updater: do not send emails about fully phased updates
275
            # never send emails about updates that are fully phased
276
            if update_percentage == 100:
277
                continue
1374.1.1 by Brian Murray
phased-updater: transition from python2 to python3
278
            if 'buckets' not in spph_buckets[rname][spph_link] and \
279
                    'rate' not in spph_buckets[rname][spph_link]:
839.1.1 by Brian Murray
phased-updater: if there are no problems with a source package upload, don't check the signer etc, and if the package has no signer (like with a copy from debian) stop trying to create an email
280
                continue
1374.1.1 by Brian Murray
phased-updater: transition from python2 to python3
281
            signer = pub_source.package_signer
839.1.1 by Brian Murray
phased-updater: if there are no problems with a source package upload, don't check the signer etc, and if the package has no signer (like with a copy from debian) stop trying to create an email
282
            # copies of packages from debian won't have a signer
283
            if not signer:
284
                continue
705.1.22 by Brian Murray
make email code slightly less redundant
285
            # not an active user of Launchpad
286
            if not signer.is_valid:
757 by Stéphane Graber
Fix all PEP-8 warnings.
287
                logging.info('%s not mailed as they are not a valid LP user' %
288
                             signer)
705.1.22 by Brian Murray
make email code slightly less redundant
289
                continue
965.1.1 by Brian Murray
phased-updater: set signer_name to nothing to prevent a traceback.
290
            signer_email = get_primary_email(signer)
1114.1.1 by Brian Murray
phased-updater: if looking up an email address in LP fails, really fall back to using the changes file.
291
            signer_name = signer.name
292
            # use the changes file as a backup method for determining email addresses
1374.1.1 by Brian Murray
phased-updater: transition from python2 to python3
293
            changes_file_url = pub_source.changesFileUrl()
1228.1.1 by Brian Murray
phased-updater: set a default value for changer_name and changer_email
294
            changer_name = ''
295
            changer_email = ''
1114.1.1 by Brian Murray
phased-updater: if looking up an email address in LP fails, really fall back to using the changes file.
296
            try:
892.1.1 by Brian Murray
phased-updater: use the launchpad changes file to find an email address to notify about a stopped phased update
297
                changes_file = urlopen(changes_file_url)
298
                for line in changes_file.readlines():
1374.1.1 by Brian Murray
phased-updater: transition from python2 to python3
299
                    line = line.decode('utf-8').strip()
892.1.1 by Brian Murray
phased-updater: use the launchpad changes file to find an email address to notify about a stopped phased update
300
                    if line.startswith('Changed-By:'):
1374.1.1 by Brian Murray
phased-updater: transition from python2 to python3
301
                        changer = line.lstrip('Changed-By: ')
1114.1.1 by Brian Murray
phased-updater: if looking up an email address in LP fails, really fall back to using the changes file.
302
                        changer_name, changer_email = utils.parseaddr(changer.strip())
892.1.1 by Brian Murray
phased-updater: use the launchpad changes file to find an email address to notify about a stopped phased update
303
                        break
1114.1.1 by Brian Murray
phased-updater: if looking up an email address in LP fails, really fall back to using the changes file.
304
            except IOError:
305
                pass
1374.1.1 by Brian Murray
phased-updater: transition from python2 to python3
306
            creator = pub_source.package_creator
1114.1.1 by Brian Murray
phased-updater: if looking up an email address in LP fails, really fall back to using the changes file.
307
            creator_email = ''
1374.1.1 by Brian Murray
phased-updater: transition from python2 to python3
308
            pkg = pub_source.source_package_name
309
            version = pub_source.source_package_version
1114.1.1 by Brian Murray
phased-updater: if looking up an email address in LP fails, really fall back to using the changes file.
310
            if not signer_email and signer_name == creator.name:
311
                if not changer_email:
312
                    logging.info("No contact email found for %s %s %s" %
313
                                 (rname, pkg, version))
314
                    continue
315
                signer_email = changer_email
316
                logging.info("Used changes file to find contact email for %s %s %s" %
766.1.3 by Brian Murray
be more verbose about why an email address was looked up and skipped
317
                             (rname, pkg, version))
1374.1.1 by Brian Murray
phased-updater: transition from python2 to python3
318
            if 'buckets' in spph_buckets[rname][spph_link]:
705.1.31 by Brian Murray
do not set pup to 0 if it is already 0, fix line length issues
319
                # see if they've been emailed about the bucket before
320
                notices = []
705.1.14 by Brian Murray
keep track of emails sent by the phased-updater
321
                if signer_name in notifications:
705.1.31 by Brian Murray
do not set pup to 0 if it is already 0, fix line length issues
322
                    notices = notifications[signer_name]
705.1.49 by Brian Murray
additionally record package name to notifications file so that uploaders are emailed multiple times about an increased package crash rate
323
                for notice, notified_pkg, notified_version in notices:
1374.1.1 by Brian Murray
phased-updater: transition from python2 to python3
324
                    if notice in spph_buckets[rname][spph_link]['buckets']:
705.1.49 by Brian Murray
additionally record package name to notifications file so that uploaders are emailed multiple times about an increased package crash rate
325
                        if (notified_pkg != pkg and
326
                                notified_version != version):
705.1.42 by Brian Murray
to the email notifications file also write and read the package version which the notification was about
327
                            continue
1374.1.1 by Brian Murray
phased-updater: transition from python2 to python3
328
                        spph_buckets[rname][spph_link]['buckets'].remove(notice)
329
                if len(spph_buckets[rname][spph_link]['buckets']) == 0:
705.1.14 by Brian Murray
keep track of emails sent by the phased-updater
330
                    continue
794 by Steve Langasek
Merge lp:~brian-murray/ubuntu-archive-tools/phased-updater-notify-bdmurray
331
                receivers = [bdmurray_mail]
1374.1.1 by Brian Murray
phased-updater: transition from python2 to python3
332
                quantity = len(spph_buckets[rname][spph_link]['buckets'])
757 by Stéphane Graber
Fix all PEP-8 warnings.
333
                msg = MIMEText(
334
                    b_body % (pkg, version, rname, ('an ', '')[quantity != 1],
335
                              ('', 's')[quantity != 1],
336
                              ('was', 'were')[quantity != 1],
337
                              ('', 's')[quantity != 1],
338
                              ('s', '')[quantity != 1],
1374.1.1 by Brian Murray
phased-updater: transition from python2 to python3
339
                              '\n'.join(spph_buckets[rname][spph_link]['buckets']))
757 by Stéphane Graber
Fix all PEP-8 warnings.
340
                    + remedy)
1322.1.1 by Brian Murray
phased-updater: improve email, and creator and signer to the report, link to investigative tips
341
                subject = '[%s/%s] Package Phasing Stopped' % (rname, pkg)
705.1.45 by Brian Murray
switched from print to logging
342
                msg['Subject'] = subject
705.1.22 by Brian Murray
make email code slightly less redundant
343
                msg['From'] = EMAIL_SENDER
793.1.1 by Brian Murray
phased-updater: add my email address as reply-to and include myself in the cc's
344
                msg['Reply-To'] = bdmurray_mail
705.1.12 by Brian Murray
create an increased rate of crashes email, use one dictionary for problem and rate information
345
                receivers.append(signer_email)
346
                msg['To'] = signer_email
1114.1.1 by Brian Murray
phased-updater: if looking up an email address in LP fails, really fall back to using the changes file.
347
                if creator != signer and creator.is_valid:
348
                    creator_email = get_primary_email(creator)
349
                    # fall back to the email found in the changes file
350
                    if not creator_email:
351
                        creator_email = changer_email
352
                    receivers.append(creator_email)
353
                    msg['Cc'] = '%s' % changer_email
705.1.12 by Brian Murray
create an increased rate of crashes email, use one dictionary for problem and rate information
354
                smtp = smtplib.SMTP('localhost')
705.1.48 by Brian Murray
remove testing code add switch for showing updates that have been fully phased
355
                smtp.sendmail(EMAIL_SENDER, receivers,
757 by Stéphane Graber
Fix all PEP-8 warnings.
356
                              msg.as_string())
975.2.1 by Brian Murray
resolve Traceback regarding unicode when using signer information from changes file
357
                smtp.quit()
705.1.45 by Brian Murray
switched from print to logging
358
                logging.info('%s mailed about %s' % (receivers, subject))
705.1.49 by Brian Murray
additionally record package name to notifications file so that uploaders are emailed multiple times about an increased package crash rate
359
                # add signer, problem, pkg, version to notifications csv file
1027.2.1 by Brian Murray
phased-updater: using codes.open and a utf-8 encoding in case signer's have unicode in their name.
360
                with codecs.open(NOTIFICATIONS, 'a', encoding='utf-8') as notify_file:
1374.1.1 by Brian Murray
phased-updater: transition from python2 to python3
361
                    for bucket in spph_buckets[rname][spph_link]['buckets']:
975.2.1 by Brian Murray
resolve Traceback regarding unicode when using signer information from changes file
362
                        notify_file.write('%s, %s, %s, %s\n' % \
1027.2.2 by Brian Murray
address reviewer feedback
363
                                          (signer_name, bucket,
1026.1.1 by Brian Murray
phased-updater: utils.parseaddr returns a string so no encoding / decoding is needed.
364
                                           pkg, version))
1114.1.1 by Brian Murray
phased-updater: if looking up an email address in LP fails, really fall back to using the changes file.
365
                        if changer_email:
366
                            notify_file.write('%s, %s, %s, %s\n' % \
367
                                              (creator.name, bucket,
368
                                               pkg, version))
1374.1.1 by Brian Murray
phased-updater: transition from python2 to python3
369
            if 'rate' in spph_buckets[rname][spph_link]:
705.1.49 by Brian Murray
additionally record package name to notifications file so that uploaders are emailed multiple times about an increased package crash rate
370
                # see if they have been emailed about the increased rate
371
                # for this package version before
372
                notices = []
373
                if signer_name in notifications:
374
                    notices = notifications[signer_name]
375
                if ('increased-rate', pkg, version) in notices:
376
                    continue
794 by Steve Langasek
Merge lp:~brian-murray/ubuntu-archive-tools/phased-updater-notify-bdmurray
377
                receivers = [bdmurray_mail]
827.1.1 by Brian Murray
phased-updater: urlquote the package version so the link in emails will work
378
                msg = MIMEText(i_body % (pkg, quote(version), rname,
1374.1.1 by Brian Murray
phased-updater: transition from python2 to python3
379
                                         spph_buckets[rname][spph_link]['rate'][1])
757 by Stéphane Graber
Fix all PEP-8 warnings.
380
                               + remedy)
1322.1.1 by Brian Murray
phased-updater: improve email, and creator and signer to the report, link to investigative tips
381
                subject = '[%s/%s] Package Phasing Stopped' % (rname, pkg)
705.1.45 by Brian Murray
switched from print to logging
382
                msg['Subject'] = subject
705.1.22 by Brian Murray
make email code slightly less redundant
383
                msg['From'] = EMAIL_SENDER
793.1.1 by Brian Murray
phased-updater: add my email address as reply-to and include myself in the cc's
384
                msg['Reply-To'] = bdmurray_mail
705.1.12 by Brian Murray
create an increased rate of crashes email, use one dictionary for problem and rate information
385
                receivers.append(signer_email)
386
                msg['To'] = signer_email
1114.1.1 by Brian Murray
phased-updater: if looking up an email address in LP fails, really fall back to using the changes file.
387
                if creator != signer and creator.is_valid:
388
                    # fall back to the email found in the changes file
389
                    if not creator_email:
390
                        creator_email = changer_email
391
                    receivers.append(creator_email)
392
                    msg['Cc'] = '%s' % creator_email
705.1.12 by Brian Murray
create an increased rate of crashes email, use one dictionary for problem and rate information
393
                smtp = smtplib.SMTP('localhost')
705.1.48 by Brian Murray
remove testing code add switch for showing updates that have been fully phased
394
                smtp.sendmail(EMAIL_SENDER, receivers,
757 by Stéphane Graber
Fix all PEP-8 warnings.
395
                              msg.as_string())
975.2.1 by Brian Murray
resolve Traceback regarding unicode when using signer information from changes file
396
                smtp.quit()
705.1.45 by Brian Murray
switched from print to logging
397
                logging.info('%s mailed about %s' % (receivers, subject))
705.1.49 by Brian Murray
additionally record package name to notifications file so that uploaders are emailed multiple times about an increased package crash rate
398
                # add signer, increased-rate, pkg, version to
399
                # notifications csv
1027.2.1 by Brian Murray
phased-updater: using codes.open and a utf-8 encoding in case signer's have unicode in their name.
400
                with codecs.open(NOTIFICATIONS, 'a', encoding='utf-8') as notify_file:
705.1.49 by Brian Murray
additionally record package name to notifications file so that uploaders are emailed multiple times about an increased package crash rate
401
                    notify_file.write('%s, increased-rate, %s, %s\n' %
1027.2.2 by Brian Murray
address reviewer feedback
402
                                      (signer_name, pkg, version))
1114.1.1 by Brian Murray
phased-updater: if looking up an email address in LP fails, really fall back to using the changes file.
403
                    if creator_email:
404
                        notify_file.write('%s, increased-rate, %s, %s\n' %
405
                                          (creator.name, pkg, version))
705.1.1 by Brian Murray
first draft of phased-updater
406
757 by Stéphane Graber
Fix all PEP-8 warnings.
407
705.1.16 by Brian Murray
phased-updater: remove new_buckets function that used first_appearance
408
def new_buckets(archive, release, src_pkg, version):
705.1.31 by Brian Murray
do not set pup to 0 if it is already 0, fix line length issues
409
    # can't use created_since here because it have may been uploaded
410
    # before the release date
411
    spph = archive.getPublishedSources(distro_series=release,
757 by Stéphane Graber
Fix all PEP-8 warnings.
412
                                       source_name=src_pkg, exact_match=True)
705.1.7 by Brian Murray
create code for using errors api package-version-new-buckets
413
    pubs = [(ph.date_published, ph.source_package_version) for ph in spph
705.1.31 by Brian Murray
do not set pup to 0 if it is already 0, fix line length issues
414
            if ph.status != 'Deleted' and ph.pocket != 'Backports'
415
            and ph.pocket != 'Proposed'
705.1.24 by Brian Murray
display the phased_update_percentage for binary packages which have no errors but are phasing
416
            and ph.date_published is not None]
705.1.7 by Brian Murray
create code for using errors api package-version-new-buckets
417
    pubs = sorted(pubs)
418
    # it is possible for the same version to appear multiple times
419
    numbers = set([pub[1] for pub in pubs])
1322.1.1 by Brian Murray
phased-updater: improve email, and creator and signer to the report, link to investigative tips
420
    versions = sorted(numbers, key=cmp_to_key(apt.apt_pkg.version_compare))
705.1.13 by Brian Murray
sort releases, use real_new_bucket, print a has in the html report
421
    # it never appeared in release e.g. cedarview-drm-drivers in precise
422
    try:
423
        previous_version = versions[-2]
424
    except IndexError:
705.1.19 by Brian Murray
multiple improvements
425
        return False
705.1.7 by Brian Murray
create code for using errors api package-version-new-buckets
426
    new_version = versions[-1]
705.1.31 by Brian Murray
do not set pup to 0 if it is already 0, fix line length issues
427
    new_buckets_url = '%spackage-version-new-buckets/?format=json&' % \
813.2.2 by Brian Murray
phased-updater: switch back to using errors instead of a local version of it
428
        (BASE_ERRORS_URL) + \
705.1.31 by Brian Murray
do not set pup to 0 if it is already 0, fix line length issues
429
        'package=%s&previous_version=%s&new_version=%s' % \
430
        (quote(src_pkg), quote(previous_version), quote(new_version))
964.1.1 by Brian Murray
Improve error handling when communicating with errors.u.c.
431
    try:
432
        new_buckets_file = urlopen(new_buckets_url)
433
    except IOError:
434
        return 'error'
952.1.1 by Brian Murray
phased-updater: return True for rate increase or new crashes if we don't receive a 200 response from the Error Tracker
435
    # If we don't receive an OK response from the Error Tracker we should not
436
    # increment the phased-update-percentage.
437
    if new_buckets_file.getcode() != 200:
745.1.1 by Brian Murray
also handled 502 error codes
438
        logging.error('HTTP error retrieving %s' % new_buckets_url)
964.1.1 by Brian Murray
Improve error handling when communicating with errors.u.c.
439
        return 'error'
966.1.1 by Brian Murray
Handle a traceback when decoding json from errors.u.c.
440
    try:
441
        new_buckets_data = json.load(new_buckets_file)
442
    except json.decoder.JSONDecodeError:
443
        logging.error('Error getting new buckets at %s' % new_buckets_url)
444
        return 'error'
1374.1.1 by Brian Murray
phased-updater: transition from python2 to python3
445
    if 'error_message' in list(new_buckets_data.keys()):
705.1.45 by Brian Murray
switched from print to logging
446
        logging.error('Error getting new buckets at %s' % new_buckets_url)
964.1.1 by Brian Murray
Improve error handling when communicating with errors.u.c.
447
        return 'error'
705.1.7 by Brian Murray
create code for using errors api package-version-new-buckets
448
    if len(new_buckets_data['objects']) == 0:
705.1.19 by Brian Murray
multiple improvements
449
        return False
705.1.7 by Brian Murray
create code for using errors api package-version-new-buckets
450
    buckets = []
451
    for bucket in new_buckets_data['objects']:
791.1.1 by Brian Murray
phased-updater: ignore package install failures
452
        # Do not consider package install failures until they have more
1020.1.1 by Brian Murray
phased-updater: skip buckets regarding already installed and configured errors again
453
        # information added to the instances.
791.1.1 by Brian Murray
phased-updater: ignore package install failures
454
        if bucket['function'].startswith('package:'):
455
            continue
1020.1.1 by Brian Murray
phased-updater: skip buckets regarding already installed and configured errors again
456
        # 16.04's duplicate signature for ProblemType: Package doesn't
457
        # start with 'package:' so check for strings in the bucket.
458
        if 'is already installed and configured' in bucket['function']:
459
            logging.info('Skipped already installed bucket %s' %
460
                         bucket['web_link'])
461
            continue
705.1.44 by Brian Murray
do not try again if there is an error getting the json file from errors
462
        # Skip failed buckets as they don't have useful tracebacks
813.1.1 by Brian Murray
phased-updater: disable rate increase checking while new errors API is being rolled out
463
        if bucket['function'].startswith('failed:'):
705.1.45 by Brian Murray
switched from print to logging
464
            logging.info('Skipped failed to retrace bucket %s' %
757 by Stéphane Graber
Fix all PEP-8 warnings.
465
                         bucket['web_link'])
705.1.7 by Brian Murray
create code for using errors api package-version-new-buckets
466
            continue
1052.1.1 by Brian Murray
confirm that the package version has crash reports for the release the SRU appears in
467
        # check to see if the version appears for the affected release
468
        versions_url = '%sversions/?format=json&id=%s' % \
469
            ((BASE_ERRORS_URL) , quote(bucket['function'].encode('utf-8')))
470
        try:
471
            versions_data_file = urlopen(versions_url)
472
        except IOError:
473
            logging.error('Error getting release versions at %s' % versions_url)
474
            # don't return an error because its better to have a false positive
475
            # in this case
476
            buckets.append(bucket['web_link'])
477
            continue
478
        try:
479
            versions_data = json.load(versions_data_file)
480
        except json.decoder.JSONDecodeError:
481
            logging.error('Error getting release versions at %s' % versions_url)
482
            # don't return an error because its better to have a false positive
483
            # in this case
484
            buckets.append(bucket['web_link'])
485
            continue
1057.1.1 by Brian Murray
phased-updater: resolve a traceback when errors is unresponsive
486
        if 'error_message' in versions_data:
487
            # don't return an error because its better to have a false positive
488
            # in this case
489
            buckets.append(bucket['web_link'])
490
            continue
1052.1.1 by Brian Murray
confirm that the package version has crash reports for the release the SRU appears in
491
        # -1 means that release isn't affected
492
        if len([vd[release.name] for vd in versions_data['objects'] \
493
                if vd['version'] == new_version and vd[release.name] != -1]) == 0:
494
            continue
705.1.7 by Brian Murray
create code for using errors api package-version-new-buckets
495
        buckets.append(bucket['web_link'])
788.1.1 by Brian Murray
phased-updater: skip already installed and configured package install failures
496
    logging.info('Details (new buckets): %s' % new_buckets_url)
705.1.7 by Brian Murray
create code for using errors api package-version-new-buckets
497
    return buckets
705.1.1 by Brian Murray
first draft of phased-updater
498
757 by Stéphane Graber
Fix all PEP-8 warnings.
499
813.2.1 by Brian Murray
phased-updater: switch to using new crash rate api on the error tracker
500
def package_previous_version(release, src_pkg, version):
501
    # return previous package version from updates or release and
502
    # the publication date of the current package version
503
    ubuntu = launchpad.distributions['ubuntu']
504
    primary = ubuntu.getArchive(name='primary')
505
    current_version_date = None
506
    previous_version = None
813.2.4 by Brian Murray
phased-updater: add in a comment regarding ordering of getPublishedSources
507
    # Archive.getPublishedSources returns results ordered by
508
    # (name, id) where the id number is autocreated, subsequently
509
    # the newest package versions are returned first
813.2.1 by Brian Murray
phased-updater: switch to using new crash rate api on the error tracker
510
    for spph in primary.getPublishedSources(source_name=src_pkg,
511
                                            distro_series=release,
512
                                            exact_match=True):
513
        if spph.pocket == 'Proposed':
514
            continue
515
        if spph.status == 'Deleted':
516
            continue
517
        if spph.source_package_version == version:
518
            if not current_version_date:
519
                current_version_date = spph.date_published.date()
520
            elif spph.date_published.date() > current_version_date:
521
                current_version_date = spph.date_published.date()
522
        if spph.pocket == 'Updates' and spph.status == 'Superseded':
523
            return (spph.source_package_version, current_version_date)
524
        if spph.pocket == 'Release' and spph.status == 'Published':
525
            return (spph.source_package_version, current_version_date)
526
    return (None, None)
527
528
929.1.1 by Brian Murray
phased-updater: enable rate of crash checking again and utilize phased-update-percentage in comparing rate of crashes.
529
def crash_rate_increase(release, src_pkg, version, last_pup):
813.2.1 by Brian Murray
phased-updater: switch to using new crash rate api on the error tracker
530
    pvers, date = package_previous_version(release, src_pkg, version)
531
    date = str(date).replace('-', '')
532
    if not pvers:
533
        # joyent-mdata-client was put in updates w/o being in the release
534
        # pocket
535
        return False
536
    release_name = 'Ubuntu ' + release.version
813.2.2 by Brian Murray
phased-updater: switch back to using errors instead of a local version of it
537
    rate_url = BASE_ERRORS_URL + 'package-rate-of-crashes/?format=json' + \
948.1.1 by Brian Murray
phased-updater: utilize API ability to subtract crashes from -proposed users in rate of crashes check
538
        '&exclude_proposed=True' + \
929.1.1 by Brian Murray
phased-updater: enable rate of crash checking again and utilize phased-update-percentage in comparing rate of crashes.
539
        '&release=%s&package=%s&old_version=%s&new_version=%s&phased_update_percentage=%s&date=%s' % \
813.2.1 by Brian Murray
phased-updater: switch to using new crash rate api on the error tracker
540
        (quote(release_name), quote(src_pkg), quote(pvers), quote(version),
929.1.1 by Brian Murray
phased-updater: enable rate of crash checking again and utilize phased-update-percentage in comparing rate of crashes.
541
         last_pup, date)
964.1.1 by Brian Murray
Improve error handling when communicating with errors.u.c.
542
    try:
543
        rate_file = urlopen(rate_url)
544
    except IOError:
545
        return 'error'
952.1.1 by Brian Murray
phased-updater: return True for rate increase or new crashes if we don't receive a 200 response from the Error Tracker
546
    # If we don't receive an OK response from the Error Tracker we should not
547
    # increment the phased-update-percentage.
548
    if rate_file.getcode() != 200:
745.1.1 by Brian Murray
also handled 502 error codes
549
        logging.error('HTTP error retrieving %s' % rate_url)
964.1.1 by Brian Murray
Improve error handling when communicating with errors.u.c.
550
        return 'error'
966.1.1 by Brian Murray
Handle a traceback when decoding json from errors.u.c.
551
    try:
552
        rate_data = json.load(rate_file)
553
    except json.decoder.JSONDecodeError:
554
        logging.error('Error getting rate at %s' % rate_url)
555
        return 'error'
1374.1.1 by Brian Murray
phased-updater: transition from python2 to python3
556
    if 'error_message' in list(rate_data.keys()):
705.1.45 by Brian Murray
switched from print to logging
557
        logging.error('Error getting rate at %s' % rate_url)
964.1.1 by Brian Murray
Improve error handling when communicating with errors.u.c.
558
        return 'error'
818.1.4 by Brian Murray
modify comment and change where logging occurs
559
    logging.info('Details (rate increase): %s' % rate_url)
705.1.48 by Brian Murray
remove testing code add switch for showing updates that have been fully phased
560
    # this may not be useful if the buckets creating the increase have
561
    # failed to retrace
705.1.1 by Brian Murray
first draft of phased-updater
562
    for data in rate_data['objects']:
563
        if data['increase']:
705.1.33 by Brian Murray
resolve issue with rate of crashes query, and create a no act switch so pup is not changed
564
            previous_amount = data['previous_average']
818.1.4 by Brian Murray
modify comment and change where logging occurs
565
            # this may happen if there were no crashes reported about
566
            # the previous version of the package
818.1.3 by Brian Murray
phased-updater: deal with the case where an increase is seen because there is not data for the previous version of the package
567
            if not previous_amount:
568
                logging.info('No previous crash data found for %s %s' %
569
                             (src_pkg, pvers))
834.1.1 by Brian Murray
phased-updater: only add version to web_link if it wasn't provided
570
                previous_amount = 0
705.1.34 by Brian Murray
update rate increase calculation to deal with new output format from errors
571
            if 'difference' in data:
572
                increase = data['difference']
573
            elif 'this_count' in data:
574
            # 2013-06-17 this can be negative due to the portion of the
575
            # day math (we take the average crashes and multiple them by
576
            # the fraction of hours that have passed so far in the day)
577
                current_amount = data['this_count']
578
                increase = current_amount - previous_amount
766.1.4 by Brian Murray
always set the rate_increase to false so that phasing is not stopped because of it
579
            logging.info('[%s/%s] increase: %s, previous_avg: %s' %
813.2.1 by Brian Murray
phased-updater: switch to using new crash rate api on the error tracker
580
                         (release_name.replace('Ubuntu ', ''), src_pkg,
581
                          increase, previous_amount))
834.1.1 by Brian Murray
phased-updater: only add version to web_link if it wasn't provided
582
            if '&version=' not in data['web_link']:
583
                link = data['web_link'] + '&version=%s' % version
584
            else:
585
                link = data['web_link']
788.1.1 by Brian Murray
phased-updater: skip already installed and configured package install failures
586
            logging.info('Details (rate increase): %s' % link)
705.1.33 by Brian Murray
resolve issue with rate of crashes query, and create a no act switch so pup is not changed
587
            return(increase, link)
705.1.1 by Brian Murray
first draft of phased-updater
588
757 by Stéphane Graber
Fix all PEP-8 warnings.
589
705.1.1 by Brian Murray
first draft of phased-updater
590
def main():
705.1.22 by Brian Murray
make email code slightly less redundant
591
    #  TODO: make email code less redundant
705.1.35 by Brian Murray
clarify what the report is about
592
    #  TODO: modify HTTP_USER_AGENT (both versions of urllib)
705.1.31 by Brian Murray
do not set pup to 0 if it is already 0, fix line length issues
593
    #  TODO: Open bugs for regressions when false positives reduced
705.1.55 by Brian Murray
directly call changeOverride in the Launchpad API rather than using subprocess to call change-override
594
    options.archive = archive
705.1.1 by Brian Murray
first draft of phased-updater
595
705.1.6 by Brian Murray
rename whitelist to overrides
596
    overrides = defaultdict(list)
766.1.1 by Brian Murray
phased-updater: provide a way to override an increased rate of crashes, if a phased-update is being restarted start it from the highest p-u-p
597
    rate_overrides = []
705.1.6 by Brian Murray
rename whitelist to overrides
598
    override_file = csv.reader(open(OVERRIDES, 'r'))
599
    for row in override_file:
771 by Colin Watson
phased-updater: handle short rows in override file (e.g. blank lines)
600
        if len(row) < 3:
601
            continue
705.1.4 by Brian Murray
implement whitelist support
602
        # package, version, problem
603
        if row[0].startswith('#'):
604
            continue
605
        package = row[0].strip()
606
        version = row[1].strip()
607
        problem = row[2].strip()
766.1.1 by Brian Murray
phased-updater: provide a way to override an increased rate of crashes, if a phased-update is being restarted start it from the highest p-u-p
608
        if problem == 'increased-rate':
609
            rate_overrides.append((package, version))
610
        else:
611
            overrides[(package, version)].append(problem)
705.1.4 by Brian Murray
implement whitelist support
612
705.1.10 by Brian Murray
switched to one report for all releases
613
    releases = []
614
    for series in ubuntu.series:
615
        if series.active:
616
            if series.status == 'Active Development':
617
                continue
618
            releases.append(series)
705.1.13 by Brian Murray
sort releases, use real_new_bucket, print a has in the html report
619
    releases.reverse()
705.1.31 by Brian Murray
do not set pup to 0 if it is already 0, fix line length issues
620
    issues = {}
705.1.10 by Brian Murray
switched to one report for all releases
621
    for release in releases:
1100.1.1 by Brian Murray
phased-updater: workaround LP timing out when we call getPublishedBinaries()
622
        # We can't use release.datereleased because some SRUs are 0 day
623
        cdate = release.date_created
705.1.10 by Brian Murray
switched to one report for all releases
624
        rname = release.name
705.1.33 by Brian Murray
resolve issue with rate of crashes query, and create a no act switch so pup is not changed
625
        rvers = release.version
705.1.31 by Brian Murray
do not set pup to 0 if it is already 0, fix line length issues
626
        issues[rname] = OrderedDict()
1400.2.3 by Brian Murray
phased-updater: check to see who set the phased_update_percentage and respect it
627
        # XXX - precise and trusty are ESM
628
        if rname in ('precise', 'trusty'):
705.1.10 by Brian Murray
switched to one report for all releases
629
            continue
705.1.31 by Brian Murray
do not set pup to 0 if it is already 0, fix line length issues
630
        pub_sources = archive.getPublishedSources(
1100.1.1 by Brian Murray
phased-updater: workaround LP timing out when we call getPublishedBinaries()
631
            created_since_date=cdate,
964.1.1 by Brian Murray
Improve error handling when communicating with errors.u.c.
632
            order_by_date=True,
705.1.10 by Brian Murray
switched to one report for all releases
633
            pocket='Updates', status='Published', distro_series=release)
964.1.1 by Brian Murray
Improve error handling when communicating with errors.u.c.
634
        for pub_source in pub_sources:
705.1.10 by Brian Murray
switched to one report for all releases
635
            src_pkg = pub_source.source_package_name
636
            version = pub_source.source_package_version
1374.1.1 by Brian Murray
phased-updater: transition from python2 to python3
637
            spph_link = pub_source.self_link
813.2.1 by Brian Murray
phased-updater: switch to using new crash rate api on the error tracker
638
            pbs = None
1100.1.1 by Brian Murray
phased-updater: workaround LP timing out when we call getPublishedBinaries()
639
            try:
640
                pbs = [pb for pb in pub_source.getPublishedBinaries()
641
                       if pb.phased_update_percentage is not None]
642
            # workaround for LP: #1695113
643
            except lazr.restfulclient.errors.ServerError as e:
644
                if 'HTTP Error 503' in str(e):
645
                    logging.info('Skipping 503 Error for %s' % src_pkg)
646
                    pass
796.1.1 by Brian Murray
don't query errors for packages which don't have a phased_update_percentage
647
            if not pbs:
799 by Colin Watson
regularise indentation
648
                continue
766.1.2 by Brian Murray
updated based on reviewer feedback
649
            if pbs:
650
                # the p-u-p is currently the same for all binary packages
651
                last_pup = pbs[0].phased_update_percentage
929.1.1 by Brian Murray
phased-updater: enable rate of crash checking again and utilize phased-update-percentage in comparing rate of crashes.
652
            else:
653
                last_pup = None
1038.1.1 by Brian Murray
Don't set max_pup to None because we try and add it to an int.
654
            max_pup = 0
929.1.1 by Brian Murray
phased-updater: enable rate of crash checking again and utilize phased-update-percentage in comparing rate of crashes.
655
            if last_pup == 0:
1400.1.1 by Brian Murray
phased-updater: create a dictionary with info about packages which shouldn't phase
656
                for allpb in archive.getPublishedBinaries(
657
                        exact_match=True, pocket='Updates',
658
                        binary_name=pbs[0].binary_package_name):
659
                    if allpb.distro_arch_series.distroseries == release:
660
                        if allpb.phased_update_percentage > 0:
661
                            max_pup = allpb.phased_update_percentage
662
                            break
1400.2.3 by Brian Murray
phased-updater: check to see who set the phased_update_percentage and respect it
663
                if pbs[0].creator_link:
664
                    creator = pbs[0].creator_link.split('/')[-1]
665
                else:
666
                    creator = '~ubuntu-archive-robot'
667
                # the phasing was manually set to 0 by someone
668
                if creator != '~ubuntu-archive-robot':
669
                    logging.info('Not considering %s as it was manually stopped' % src_pkg)
670
                    # these all need to be set for the html report
671
                    issues[rname][spph_link] = {}
672
                    issues[rname][spph_link]['buckets'] = ['force-stop']
673
                    issues[rname][spph_link]['max_pup'] = max_pup
674
                    issues[rname][spph_link]['previous_pup'] = max_pup
675
                    issues[rname][spph_link]['pup'] = last_pup
676
                    continue
929.1.1 by Brian Murray
phased-updater: enable rate of crash checking again and utilize phased-update-percentage in comparing rate of crashes.
677
            if max_pup and last_pup == 0:
678
                rate_increase = crash_rate_increase(release, src_pkg, version, max_pup)
679
            else:
680
                rate_increase = crash_rate_increase(release, src_pkg, version, last_pup)
964.1.1 by Brian Murray
Improve error handling when communicating with errors.u.c.
681
            problems = new_buckets(archive, release, src_pkg, version)
682
            # In the event that there as an error connecting to errors.ubuntu.com then
683
            # neither increase nor stop the phased-update.
684
            if rate_increase == 'error' or problems == 'error':
685
                logging.info("Skipping %s due to failure to get data from Errors." % src_pkg)
686
                continue
687
            if problems:
688
                if (src_pkg, version) in overrides:
689
                    not_overrode = set(problems).difference(
690
                        set(overrides[(src_pkg, version)]))
691
                    if len(not_overrode) > 0:
1374.1.1 by Brian Murray
phased-updater: transition from python2 to python3
692
                        issues[rname][spph_link] = {}
693
                        issues[rname][spph_link]['buckets'] = not_overrode
964.1.1 by Brian Murray
Improve error handling when communicating with errors.u.c.
694
                else:
1374.1.1 by Brian Murray
phased-updater: transition from python2 to python3
695
                    issues[rname][spph_link] = {}
696
                    issues[rname][spph_link]['buckets'] = problems
929.1.1 by Brian Murray
phased-updater: enable rate of crash checking again and utilize phased-update-percentage in comparing rate of crashes.
697
            if rate_increase and (src_pkg, version) not in rate_overrides:
1374.1.1 by Brian Murray
phased-updater: transition from python2 to python3
698
                if spph_link not in issues[rname]:
699
                    issues[rname][spph_link] = {}
700
                issues[rname][spph_link]['rate'] = rate_increase
929.1.1 by Brian Murray
phased-updater: enable rate of crash checking again and utilize phased-update-percentage in comparing rate of crashes.
701
            if pbs:
1374.1.1 by Brian Murray
phased-updater: transition from python2 to python3
702
                if spph_link not in issues[rname]:
703
                    issues[rname][spph_link] = {}
929.1.1 by Brian Murray
phased-updater: enable rate of crash checking again and utilize phased-update-percentage in comparing rate of crashes.
704
                # phasing has stopped so check what the max value was
705
                if last_pup == 0:
1374.1.1 by Brian Murray
phased-updater: transition from python2 to python3
706
                    issues[rname][spph_link]['max_pup'] = max_pup
707
                issues[rname][spph_link]['pup'] = last_pup
705.1.28 by Brian Murray
implemented setting of phased_update_percentage to 0 or incrementing it
708
            suite = rname + '-updates'
1374.1.1 by Brian Murray
phased-updater: transition from python2 to python3
709
            if spph_link not in issues[rname]:
705.1.24 by Brian Murray
display the phased_update_percentage for binary packages which have no errors but are phasing
710
                continue
1374.1.1 by Brian Murray
phased-updater: transition from python2 to python3
711
            elif ('rate' not in issues[rname][spph_link] and
712
                  'buckets' not in issues[rname][spph_link] and
766.1.2 by Brian Murray
updated based on reviewer feedback
713
                  pbs):
705.1.28 by Brian Murray
implemented setting of phased_update_percentage to 0 or incrementing it
714
                # there is not an error so increment the phasing
1374.1.1 by Brian Murray
phased-updater: transition from python2 to python3
715
                current_pup = issues[rname][spph_link]['pup']
796.1.5 by Brian Murray
phased-updater: be sure to restart at max_pup
716
                # if this is an update that is restarting we want to start at
717
                # the same percentage the stoppage happened at
1374.1.1 by Brian Murray
phased-updater: transition from python2 to python3
718
                if 'max_pup' in issues[rname][spph_link]:
719
                    current_pup = issues[rname][spph_link]['max_pup']
705.1.38 by Brian Murray
for stopped updates show the percentage the update was stopped at and link to the binary publishing history
720
                new_pup = current_pup + PUP_INCREMENT
1380.1.1 by Dimitri John Ledkov
Implement slow phasing for secureboot-db.
721
                if src_pkg in SLOW_PACKAGES:
722
                    new_pup = current_pup + PUP_SLOW_INCREMENT
705.1.46 by Brian Murray
rename long option for -n and add switch for emailing developers
723
                if not options.no_act:
705.1.55 by Brian Murray
directly call changeOverride in the Launchpad API rather than using subprocess to call change-override
724
                    set_pup(current_pup, new_pup, release, suite, src_pkg)
1374.1.1 by Brian Murray
phased-updater: transition from python2 to python3
725
                issues[rname][spph_link]['pup'] = new_pup
766.1.2 by Brian Murray
updated based on reviewer feedback
726
            elif pbs:
705.1.52 by Brian Murray
move a comment
727
                # there is an error and pup is not None so stop the phasing
1374.1.1 by Brian Murray
phased-updater: transition from python2 to python3
728
                current_pup = issues[rname][spph_link]['pup']
729
                if 'max_pup' in issues[rname][spph_link]:
730
                    issues[rname][spph_link]['previous_pup'] = \
731
                        issues[rname][spph_link]['max_pup']
818.1.2 by Brian Murray
phased-updater: fix another traceback
732
                else:
1374.1.1 by Brian Murray
phased-updater: transition from python2 to python3
733
                    issues[rname][spph_link]['previous_pup'] = \
818.1.2 by Brian Murray
phased-updater: fix another traceback
734
                        current_pup
705.1.38 by Brian Murray
for stopped updates show the percentage the update was stopped at and link to the binary publishing history
735
                new_pup = 0
705.1.46 by Brian Murray
rename long option for -n and add switch for emailing developers
736
                if (not options.no_act and
1374.1.1 by Brian Murray
phased-updater: transition from python2 to python3
737
                        issues[rname][spph_link]['pup'] != 0):
705.1.55 by Brian Murray
directly call changeOverride in the Launchpad API rather than using subprocess to call change-override
738
                    set_pup(current_pup, new_pup, release, suite, src_pkg)
1374.1.1 by Brian Murray
phased-updater: transition from python2 to python3
739
                issues[rname][spph_link]['pup'] = new_pup
705.1.31 by Brian Murray
do not set pup to 0 if it is already 0, fix line length issues
740
    generate_html_report(releases, issues)
705.1.46 by Brian Murray
rename long option for -n and add switch for emailing developers
741
    if options.email:
742
        create_email_notifications(releases, issues)
705.1.1 by Brian Murray
first draft of phased-updater
743
744
if __name__ == '__main__':
705.1.14 by Brian Murray
keep track of emails sent by the phased-updater
745
    start_time = time.time()
705.1.1 by Brian Murray
first draft of phased-updater
746
    BASE_ERRORS_URL = 'https://errors.ubuntu.com/api/1.0/'
705.1.7 by Brian Murray
create code for using errors api package-version-new-buckets
747
    LOCAL_ERRORS_URL = 'http://10.0.3.182/api/1.0/'
705.1.48 by Brian Murray
remove testing code add switch for showing updates that have been fully phased
748
    LP_BASE_URL = 'https://launchpad.net'
705.1.6 by Brian Murray
rename whitelist to overrides
749
    OVERRIDES = 'phased-updates-overrides.txt'
705.1.14 by Brian Murray
keep track of emails sent by the phased-updater
750
    NOTIFICATIONS = 'phased-updates-emails.txt'
705.1.48 by Brian Murray
remove testing code add switch for showing updates that have been fully phased
751
    EMAIL_SENDER = 'brian.murray@ubuntu.com'
705.1.30 by Brian Murray
send output of change-override to devnull
752
    PUP_INCREMENT = 10
1380.1.1 by Dimitri John Ledkov
Implement slow phasing for secureboot-db.
753
    SLOW_PACKAGES = {'secureboot-db'}
754
    PUP_SLOW_INCREMENT = 1
939.1.1 by Brian Murray
phased-updater: include a link to the html report showing phasing progress when e-mailing people.
755
    REPORT_FILE = 'phased-updates.html'
757 by Stéphane Graber
Fix all PEP-8 warnings.
756
    parser = OptionParser(usage="usage: %prog [options]")
705.1.25 by Brian Murray
add in support for other launchpad instances, add % sign to update percentage
757
    parser.add_option(
758
        "-l", "--launchpad", dest="launchpad_instance", default="production")
705.1.33 by Brian Murray
resolve issue with rate of crashes query, and create a no act switch so pup is not changed
759
    parser.add_option(
705.1.46 by Brian Murray
rename long option for -n and add switch for emailing developers
760
        "-n", "--no-act", default=False, action="store_true",
705.1.33 by Brian Murray
resolve issue with rate of crashes query, and create a no act switch so pup is not changed
761
        help="do not modify phased update percentages")
705.1.46 by Brian Murray
rename long option for -n and add switch for emailing developers
762
    parser.add_option(
763
        "-e", "--email", default=False, action="store_true",
764
        help="send email notifications to uploaders")
705.1.48 by Brian Murray
remove testing code add switch for showing updates that have been fully phased
765
    parser.add_option(
766
        "-f", "--fully-phased", default=False, action="store_true",
767
        help="show packages which have been fully phased")
705.1.25 by Brian Murray
add in support for other launchpad instances, add % sign to update percentage
768
    options, args = parser.parse_args()
705.1.48 by Brian Murray
remove testing code add switch for showing updates that have been fully phased
769
    if options.launchpad_instance != 'production':
770
        LP_BASE_URL = 'https://%s.launchpad.net' % options.launchpad_instance
767.2.1 by Brian Murray
phased-updater: identify security updates
771
    if options.email and options.fully_phased:
772
        parser.error("Sending emails and fully phased updates are mutually \
773
exclusive.")
705.1.12 by Brian Murray
create an increased rate of crashes email, use one dictionary for problem and rate information
774
    launchpad = Launchpad.login_with(
705.1.25 by Brian Murray
add in support for other launchpad instances, add % sign to update percentage
775
        'phased-updater', options.launchpad_instance, version='devel')
705.1.45 by Brian Murray
switched from print to logging
776
    logging.basicConfig(filename='phased-updates.log',
757 by Stéphane Graber
Fix all PEP-8 warnings.
777
                        format='%(asctime)s - %(levelname)s - %(message)s',
778
                        level=logging.INFO)
705.1.47 by Brian Murray
log start and end times
779
    logging.info('Starting phased-updater')
767.2.1 by Brian Murray
phased-updater: identify security updates
780
    ubuntu = launchpad.distributions['ubuntu']
781
    archive = ubuntu.getArchive(name='primary')
705.1.1 by Brian Murray
first draft of phased-updater
782
    main()
705.1.14 by Brian Murray
keep track of emails sent by the phased-updater
783
    end_time = time.time()
705.1.47 by Brian Murray
log start and end times
784
    logging.info("Elapsed time was %g seconds" % (end_time - start_time))