Merge lp:~brian-murray/ubuntu-archive-tools/phased-updater into lp:ubuntu-archive-tools

Proposed by Brian Murray
Status: Merged
Merged at revision: 744
Proposed branch: lp:~brian-murray/ubuntu-archive-tools/phased-updater
Merge into: lp:ubuntu-archive-tools
Diff against target: 522 lines (+518/-0)
1 file modified
phased-updater (+518/-0)
To merge this branch: bzr merge lp:~brian-murray/ubuntu-archive-tools/phased-updater
Reviewer Review Type Date Requested Status
Colin Watson Approve
Steve Langasek Needs Fixing
Review via email: mp+171142@code.launchpad.net

Description of the change

This new tool, phased-updater, will be the glue between Launchpad (for setting the phased updater percentage) and the Ubuntu Error Tracker (for finding regressions in package from -updates).

In the event that no regressions (defined as errors about the version of the package in -updates that were not seen in the previous version, or an increased rate of crashes) are found about the package the phased-update-percentage for the package will be incremented by 10%. If regressions are found the phased-update-percentage for the package will be set to 0%. There is also a mechanism for indicating that individual buckets are not regressions in a particular version of the package.

In either case an html report (sample visible at http://people.canonical.com/~brian/tmp/phased-updates.html) will be generated that will display the phased update percentage for the package and any issues found with that package. This will also be useful for seeing which packages are currently undergoing phasing.

In the event that a regression is found about a package in -updates the uploader and signer (if different from the uploader) wil be emailed of the regression or regressions in that upload. There is a csv file used to keep track of email notifications sent to uploaders to ensure that we do not email them multiple times about the same issue.

Its possible to test this on dogfood by initially setting the phased update percentage to something less than one hundred percent and observe the script increment the phased update percentage. However, this needs to be done using quantal and a start date in early May as the database for dogfood is rather old.

To post a comment you must log in.
758. By Brian Murray

log original phased update percentage

759. By Brian Murray

clarify last todo

Revision history for this message
Steve Langasek (vorlon) wrote :

+def set_pup(new_pup, suite, src_pkg):
+ import subprocess
+ with open(os.devnull, 'w') as fnull:
+ retcode = subprocess.call([
+ "%s/change-override" % os.getcwd(),
+ "--confirm-all",
+ "--percentage", '%s' % new_pup,
+ "--suite", suite,
+ "--source-and-binary", src_pkg,
+ "--launchpad", options.launchpad_instance],
+ stdout = fnull)
+ if retcode != 0:
+ logging.error("There was an error (%s) running change-override."
+ % retcode)
+ return retcode

os.getcwd() doesn't do what you want here in all cases. I, for example, have ubuntu-archive-tools on my path, so my getcwd() returns a location completely unrelated to where the tools are.

Instead, I think you want:

  os.path.dirname(sys.argv[0]) or "."

The other use of os.getcwd() seem fine, as they refer to the output directory and I think it's reasonable to use the current dir as the convention for that. But in production, I think we aren't going to be writing the report to the checkout dir.

Otherwise, this looks good to me and ready to merge.

review: Needs Fixing
Revision history for this message
Colin Watson (cjwatson) wrote :

On Mon, Jun 24, 2013 at 10:54:25PM -0000, Steve Langasek wrote:
> os.getcwd() doesn't do what you want here in all cases. I, for example, have ubuntu-archive-tools on my path, so my getcwd() returns a location completely unrelated to where the tools are.
>
> Instead, I think you want:
>
> os.path.dirname(sys.argv[0]) or "."

I would prefer doing the override change directly using API calls,
rather than shelling out to change-override.

760. By Brian Murray

directly call changeOverride in the Launchpad API rather than using subprocess to call change-override

Revision history for this message
Brian Murray (brian-murray) wrote :

I've switched to using the API directly instead of using change-override which I believe addresses both comments about the merge proposal.

Revision history for this message
Colin Watson (cjwatson) wrote :

OK, thanks for that fix. Let's give this a go ...

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'phased-updater'
2--- phased-updater 1970-01-01 00:00:00 +0000
3+++ phased-updater 2013-06-27 16:06:38 +0000
4@@ -0,0 +1,518 @@
5+#!/usr/bin/python
6+
7+# Copyright (C) 2013 Canonical Ltd.
8+# Author: Brian Murray <brian.murray@canonical.com>
9+
10+# This program is free software: you can redistribute it and/or modify
11+# it under the terms of the GNU General Public License as published by
12+# the Free Software Foundation; version 3 of the License.
13+#
14+# This program is distributed in the hope that it will be useful,
15+# but WITHOUT ANY WARRANTY; without even the implied warranty of
16+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17+# GNU General Public License for more details.
18+#
19+# You should have received a copy of the GNU General Public License
20+# along with this program. If not, see <http://www.gnu.org/licenses/>.
21+
22+'''Increment the Phased-Update-Percentage for a package
23+
24+Check to see whether or not there is a regression (new crash bucket or
25+increase rate of errors about a package) using errors.ubuntu.com and if
26+not increment the Phased-Update-Percentage for the package.
27+Additionally, generate an html report regarding state of phasing of
28+packages and email uploaders regarding issues with their uploads.
29+'''
30+
31+from __future__ import print_function
32+
33+import apt
34+import csv
35+import datetime
36+import logging
37+import os
38+import simplejson as json
39+import time
40+
41+from collections import defaultdict, OrderedDict
42+from optparse import OptionParser
43+
44+import lputils
45+
46+try:
47+ from urllib.parse import quote
48+ from urllib.request import urlopen
49+except ImportError:
50+ from urllib import quote, urlopen
51+
52+from launchpadlib.launchpad import Launchpad
53+
54+def get_primary_email(lp_user):
55+ try:
56+ lp_user_email = lp_user.preferred_email_address.email
57+ except ValueError as e:
58+ if 'server-side permission' in e.message:
59+ logging.info("%s has hidden their email addresses" %
60+ lp_user.web_link)
61+ return ''
62+ logging.info("Error accessing %s's preferred email address: %s" %
63+ (lp_user.web_link, e.message))
64+ return ''
65+ return lp_user_email
66+
67+def set_pup(current_pup, new_pup, release, suite, src_pkg):
68+ options.series = release
69+ options.suite = suite
70+ options.pocket = 'Updates'
71+ options.version = None
72+ source = lputils.find_latest_published_source(options, src_pkg)
73+ publications = [binary for binary in source.getPublishedBinaries()]
74+
75+ for pub in publications:
76+ pub.changeOverride(new_phased_update_percentage=new_pup)
77+ if new_pup != 0:
78+ logging.info('Incremented p-u-p for %s %s from %s%% to %s%%' %
79+ (suite, pub.binary_package_name, current_pup, new_pup))
80+ else:
81+ logging.info('Set p-u-p to 0%% from %s%% for %s %s'
82+ % (current_pup, suite, pub.binary_package_name))
83+
84+def generate_html_report(releases, buckets):
85+ import tempfile
86+ import shutil
87+ with tempfile.NamedTemporaryFile() as report:
88+ report.write('''<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
89+ "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
90+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
91+<head>
92+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
93+ <title>Released Ubuntu SRUs</title>
94+ <style type="text/css">
95+ body { background: #CCCCB0; color: black; }
96+ a { text-decoration: none; }
97+ table { border-collapse: collapse; border-style: solid none;
98+ border-width: 3px; margin-bottom: 3ex; empty-cells: show; }
99+ table th { text-align: left; border-style: none none dotted none;
100+ border-width: 1px; padding-right: 10px; }
101+ table td { text-align: left; border-style: none none dotted none;
102+ border-width: 1px; padding-right: 10px; }
103+ .noborder { border-style: none; }
104+ a { color: blue; }
105+ a:visited { color: black; }
106+ </style>
107+</head>
108+<body>
109+<h1>Phasing %sUbuntu Stable Release Updates</h1>
110+''' % ('', 'and Released ')[options.fully_phased])
111+ report.write('<p>Generated: %s by '
112+ '<a href="http://bazaar.launchpad.net/'
113+ '~ubuntu-archive/ubuntu-archive-tools/trunk/annotate/head%%3A/'
114+ 'phased-updater">phased-updater</a></p>' %
115+ time.strftime('%x %X UTC', time.gmtime()))
116+ report.write('''<p>A <a
117+href="https://wiki.ubuntu.com/StableReleaseUpdates ">stable release
118+update</a> has been created for the following packages, i. e. they have
119+a new version in -updates, and either an increased rate of crashes has
120+been detected or an error has been found that only exists with the new
121+version of the package.\n''')
122+ for release in releases:
123+ rname = release.name
124+ if not buckets[rname]:
125+ continue
126+ report.write('''<h3>%s</h3>\n''' % rname)
127+ report.write('''<table>\n''')
128+ report.write('''<tr>
129+ <th>Package</th>
130+ <th>Version</th>
131+ <th>Update Percentage</th>
132+ <th>Rate Increase</th>
133+ <th>Problems</th>
134+ <th>Days</th>
135+ </tr>''')
136+ for pub_source in buckets[rname]:
137+ pkg = pub_source.source_package_name
138+ version = pub_source.source_package_version
139+ age = (datetime.datetime.now() -
140+ pub_source.date_published.replace(tzinfo=None)).days
141+ update_percentage = buckets[rname][pub_source].get('pup',
142+ 100)
143+ if not options.fully_phased and update_percentage == 100:
144+ continue
145+ lpurl = '%s/ubuntu/+source/%s/' % (LP_BASE_URL, pkg)
146+ report.write('''<tr>
147+ <td><a href="%s">%s</a></td>
148+ <td><a href="%s">%s</a></td>\n''' %
149+ (lpurl, pkg, lpurl + version, version))
150+ report.write(' <td>')
151+ if update_percentage == 0:
152+ binary_pub = pub_source.getPublishedBinaries()[0]
153+ arch = binary_pub.distro_arch_series.architecture_tag
154+ bpph_url = ('%s/ubuntu/%s/%s/%s'
155+ % (LP_BASE_URL, rname, arch,
156+ binary_pub.binary_package_name))
157+ report.write('<a href="%s">%s%% of users' %
158+ (bpph_url, update_percentage))
159+ previous_pup = \
160+ buckets[rname][pub_source]['previous_pup']
161+ if previous_pup != 0:
162+ report.write(' (was %s%%)</a>' % previous_pup)
163+ else:
164+ report.write('</a>')
165+ else:
166+ report.write('%s%% of users' % update_percentage)
167+ report.write('</td>\n')
168+ if 'rate' in buckets[rname][pub_source]:
169+ data = buckets[rname][pub_source]['rate']
170+ report.write(' <td><a href="%s">+%s</a></td>\n' %
171+ (data[1], data[0]))
172+ else:
173+ report.write(' <td></td>\n')
174+ report.write(' <td>')
175+ if 'buckets' in buckets[rname][pub_source]:
176+ for bucket in buckets[rname][pub_source]['buckets']:
177+ if 'problem' in bucket:
178+ # create a short version of the problem's hash
179+ phash = bucket.replace(
180+ 'https://errors.ubuntu.com/problem/', '')[0:6]
181+ report.write('<a href="%s">%s</a> ' % (bucket,
182+ phash))
183+ else:
184+ report.write('<a href="%s">problem</a> ' % bucket)
185+ else:
186+ report.write('')
187+ report.write('</td>\n')
188+ report.write(' <td>%s</td>\n' % age)
189+ report.write('</tr>\n')
190+ report.write('''</table>\n''')
191+ report.write('''</body>\n''')
192+ report.write('''</html>''')
193+ report.flush()
194+ shutil.copy2(report.name, '%s/phased-updates.html' % os.getcwd())
195+
196+def create_email_notifications(releases, spph_buckets):
197+ import smtplib
198+ from email.mime.text import MIMEText
199+ with open(NOTIFICATIONS, 'r') as notify_file:
200+ notify_csv = csv.reader(notify_file)
201+ notifications = defaultdict(list)
202+ for row in notify_csv:
203+ # LP name, problem, pkg_version
204+ person = row[0].strip()
205+ problem = row[1].strip()
206+ pkg = row[2].strip()
207+ pkg_version = row[3].strip()
208+ notifications[person].append((problem, pkg, pkg_version))
209+ b_body = ('Your upload of %s version %s to %s has resulted in %s'
210+ 'error%s that %s first reported about this version of the '
211+ 'package. The error%s follow%s:\n\n'
212+ '%s\n\n')
213+ i_body = ('Your upload of %s version %s to %s has resulted in an '
214+ 'increased daily rate of errors for the package compared '
215+ 'to the previous two weeks. For problems currently being '
216+ 'reported about the package see:\n\n'
217+ '%s\n\n')
218+ remedy = ('Further phasing of this update has been stopped until the '
219+ 'errors have either been fixed or determined to not be a '
220+ 'result of this Stable Release Update. In the event of '
221+ 'the latter please let a member of the Ubuntu Stable Release '
222+ 'Updates team (~ubuntu-sru) know so that phasing of the update '
223+ 'can proceed.')
224+ for release in releases:
225+ rname = release.name
226+ for spph in spph_buckets[rname]:
227+ signer = spph.package_signer
228+ # not an active user of Launchpad
229+ if not signer.is_valid:
230+ logging.info('%s not mailed as they are not a valid LP user'
231+ % signer)
232+ continue
233+ signer_email = get_primary_email(signer)
234+ if not signer_email:
235+ continue
236+ pkg = spph.source_package_name
237+ version = spph.source_package_version
238+ signer_name = signer.name
239+ changer = spph.package_creator
240+ if 'buckets' in spph_buckets[rname][spph]:
241+ # see if they've been emailed about the bucket before
242+ notices = []
243+ if signer_name in notifications:
244+ notices = notifications[signer_name]
245+ for notice, notified_pkg, notified_version in notices:
246+ if notice in spph_buckets[rname][spph]['buckets']:
247+ if (notified_pkg != pkg and
248+ notified_version != version):
249+ continue
250+ spph_buckets[rname][spph]['buckets'].remove(notice)
251+ if len(spph_buckets[rname][spph]['buckets']) == 0:
252+ continue
253+ receivers = []
254+ quantity = len(spph_buckets[rname][spph]['buckets'])
255+ msg = MIMEText(b_body % (pkg, version, rname,
256+ ('an ', '')[quantity != 1], ('', 's')[quantity != 1],
257+ ('was', 'were')[quantity != 1],
258+ ('', 's')[quantity != 1], ('s', '')[quantity != 1],
259+ '\n'.join(spph_buckets[rname][spph]['buckets'])) + remedy)
260+ subject = '[%s/%s] Possible Regression' % (rname, pkg)
261+ msg['Subject'] = subject
262+ msg['From'] = EMAIL_SENDER
263+ receivers.append(signer_email)
264+ msg['To'] = signer_email
265+ if changer != signer and changer.is_valid:
266+ changer_email = get_primary_email(changer)
267+ if changer_email:
268+ receivers.append(changer_email)
269+ msg['Cc'] = '%s' % changer_email
270+ smtp = smtplib.SMTP('localhost')
271+ smtp.sendmail(EMAIL_SENDER, receivers,
272+ msg.as_string())
273+ logging.info('%s mailed about %s' % (receivers, subject))
274+ smtp.quit()
275+ # add signer, problem, pkg, version to notifications csv file
276+ with open(NOTIFICATIONS, 'a') as notify_file:
277+ for bucket in spph_buckets[rname][spph]['buckets']:
278+ notify_file.write('%s, %s, %s, %s\n' % (signer.name,
279+ bucket, pkg, version))
280+ if 'rate' in spph_buckets[rname][spph]:
281+ # see if they have been emailed about the increased rate
282+ # for this package version before
283+ notices = []
284+ if signer_name in notifications:
285+ notices = notifications[signer_name]
286+ if ('increased-rate', pkg, version) in notices:
287+ continue
288+ receivers = []
289+ msg = MIMEText(i_body % (pkg, version, rname,
290+ spph_buckets[rname][spph]['rate'][1]) + remedy)
291+ subject = '[%s/%s] Increase in crash rate' % (rname, pkg)
292+ msg['Subject'] = subject
293+ msg['From'] = EMAIL_SENDER
294+ receivers.append(signer_email)
295+ msg['To'] = signer_email
296+ if changer != signer and changer.is_valid:
297+ changer_email = get_primary_email(changer)
298+ if changer_email:
299+ receivers.append(changer_email)
300+ msg['Cc'] = '%s' % changer_email
301+ smtp = smtplib.SMTP('localhost')
302+ smtp.sendmail(EMAIL_SENDER, receivers,
303+ msg.as_string())
304+ logging.info('%s mailed about %s' % (receivers, subject))
305+ # add signer, increased-rate, pkg, version to
306+ # notifications csv
307+ with open(NOTIFICATIONS, 'a') as notify_file:
308+ notify_file.write('%s, increased-rate, %s, %s\n' %
309+ (signer.name, pkg, version))
310+ smtp.quit()
311+
312+def new_buckets(archive, release, src_pkg, version):
313+ # can't use created_since here because it have may been uploaded
314+ # before the release date
315+ spph = archive.getPublishedSources(distro_series=release,
316+ source_name=src_pkg, exact_match=True)
317+ pubs = [(ph.date_published, ph.source_package_version) for ph in spph
318+ if ph.status != 'Deleted' and ph.pocket != 'Backports'
319+ and ph.pocket != 'Proposed'
320+ and ph.date_published is not None]
321+ pubs = sorted(pubs)
322+ # it is possible for the same version to appear multiple times
323+ numbers = set([pub[1] for pub in pubs])
324+ versions = sorted(numbers, cmp=apt.apt_pkg.version_compare)
325+ # it never appeared in release e.g. cedarview-drm-drivers in precise
326+ try:
327+ previous_version = versions[-2]
328+ except IndexError:
329+ return False
330+ new_version = versions[-1]
331+ new_buckets_url = '%spackage-version-new-buckets/?format=json&' % \
332+ (BASE_ERRORS_URL) + \
333+ 'package=%s&previous_version=%s&new_version=%s' % \
334+ (quote(src_pkg), quote(previous_version), quote(new_version))
335+ new_buckets_file = urlopen(new_buckets_url)
336+ # LP: #1193022 - 404 returned when a package version is not found
337+ if new_buckets_file.getcode() == 404:
338+ logging.error('404 with %s' % new_buckets_url)
339+ return False
340+ try:
341+ new_buckets_data = json.load(new_buckets_file)
342+ except json.decoder.JSONDecodeError:
343+ logging.error("Error decoding %s" % new_buckets_url)
344+ return False
345+ if 'error_message' in new_buckets_data.keys():
346+ logging.error('Error getting new buckets at %s' % new_buckets_url)
347+ return False
348+ if len(new_buckets_data['objects']) == 0:
349+ return False
350+ buckets = []
351+ for bucket in new_buckets_data['objects']:
352+ # In the event that package install failures are not useful
353+ #if 'dpkg: error' in bucket['function']:
354+ # continue
355+ # Skip failed buckets as they don't have useful tracebacks
356+ if 'failed:' in bucket['function']:
357+ logging.info('Skipped failed to retrace bucket %s' %
358+ bucket['web_link'])
359+ continue
360+ buckets.append(bucket['web_link'])
361+ return buckets
362+
363+def crash_rate_increase(release_version, src_pkg, version):
364+ release = 'Ubuntu ' + release_version
365+ # Is this not being package version specific a problem?
366+ rate_url = BASE_ERRORS_URL + 'package-rate-of-crashes/?format=json' + \
367+ '&release=%s&package=%s' % \
368+ (quote(release), quote(src_pkg))
369+ rate_file = urlopen(rate_url)
370+ # LP: #1193022 - 404 returned when a package version is not found
371+ if rate_file.getcode() == 404:
372+ logging.error('404 with %s' % rate_url)
373+ return False
374+ rate_data = json.load(rate_file)
375+ if 'error_message' in rate_data.keys():
376+ logging.error('Error getting rate at %s' % rate_url)
377+ return False
378+ # this may not be useful if the buckets creating the increase have
379+ # failed to retrace
380+ for data in rate_data['objects']:
381+ if data['increase']:
382+ previous_amount = data['previous_average']
383+ # appears in errors r439
384+ if 'difference' in data:
385+ increase = data['difference']
386+ elif 'this_count' in data:
387+ # 2013-06-17 this can be negative due to the portion of the
388+ # day math (we take the average crashes and multiple them by
389+ # the fraction of hours that have passed so far in the day)
390+ current_amount = data['this_count']
391+ increase = current_amount - previous_amount
392+ # 2013-06-20 - Fix errors api to add this
393+ link = data['web_link'] + '&version=%s' % version
394+ return(increase, link)
395+
396+def main():
397+ # TODO: make email code less redundant
398+ # TODO: Determine how to override an increased rate
399+ # TODO: modify HTTP_USER_AGENT (both versions of urllib)
400+ # TODO: Open bugs for regressions when false positives reduced
401+ # TODO: determine if starting p-u-p over from 0% is an issue
402+ ubuntu = launchpad.distributions['ubuntu']
403+ archive = ubuntu.getArchive(name='primary')
404+ options.archive = archive
405+
406+ overrides = defaultdict(list)
407+ override_file = csv.reader(open(OVERRIDES, 'r'))
408+ for row in override_file:
409+ # package, version, problem
410+ if row[0].startswith('#'):
411+ continue
412+ package = row[0].strip()
413+ version = row[1].strip()
414+ problem = row[2].strip()
415+ overrides[(package, version)].append(problem)
416+
417+ releases = []
418+ for series in ubuntu.series:
419+ if series.active:
420+ if series.status == 'Active Development':
421+ continue
422+ releases.append(series)
423+ releases.reverse()
424+ issues = {}
425+ for release in releases:
426+ rname = release.name
427+ rvers = release.version
428+ issues[rname] = OrderedDict()
429+ # XXX - starting with raring
430+ if rname != 'raring':
431+ continue
432+ # XXX - only look at updates since 21/06/2013
433+ start_date = datetime.datetime(2013, 6, 21, 0, 0, 0, 0)
434+ pub_sources = archive.getPublishedSources(
435+ created_since_date=start_date,
436+ pocket='Updates', status='Published', distro_series=release)
437+ sorted_pub_sources = sorted([(ps.date_published, ps)
438+ for ps in pub_sources if ps.date_published is not None])
439+ for date, pub_source in sorted_pub_sources:
440+ src_pkg = pub_source.source_package_name
441+ version = pub_source.source_package_version
442+ problems = new_buckets(archive, release, src_pkg, version)
443+ if problems:
444+ if (src_pkg, version) in overrides:
445+ not_overrode = set(problems).difference(
446+ set(overrides[(src_pkg, version)]))
447+ if len(not_overrode) > 0:
448+ issues[rname][pub_source] = {}
449+ issues[rname][pub_source]['buckets'] = not_overrode
450+ else:
451+ issues[rname][pub_source] = {}
452+ issues[rname][pub_source]['buckets'] = problems
453+ rate_increase = crash_rate_increase(rvers, src_pkg, version)
454+ if rate_increase:
455+ if pub_source not in issues[rname]:
456+ issues[rname][pub_source] = {}
457+ issues[rname][pub_source]['rate'] = rate_increase
458+ pups = [pb.phased_update_percentage
459+ for pb in pub_source.getPublishedBinaries()
460+ if pb.phased_update_percentage]
461+ if pups:
462+ if pub_source not in issues[rname]:
463+ issues[rname][pub_source] = {}
464+ issues[rname][pub_source]['pup'] = pups[0]
465+ suite = rname + '-updates'
466+ if pub_source not in issues[rname]:
467+ continue
468+ elif ('rate' not in issues[rname][pub_source] and
469+ 'buckets' not in issues[rname][pub_source] and
470+ pups):
471+ # there is not an error so increment the phasing
472+ current_pup = issues[rname][pub_source]['pup']
473+ new_pup = current_pup + PUP_INCREMENT
474+ if not options.no_act:
475+ set_pup(current_pup, new_pup, release, suite, src_pkg)
476+ issues[rname][pub_source]['pup'] = new_pup
477+ elif pups:
478+ # there is an error and pup is not None so stop the phasing
479+ current_pup = issues[rname][pub_source]['pup']
480+ issues[rname][pub_source]['previous_pup'] = current_pup
481+ new_pup = 0
482+ if (not options.no_act and
483+ issues[rname][pub_source]['pup'] != 0):
484+ set_pup(current_pup, new_pup, release, suite, src_pkg)
485+ issues[rname][pub_source]['pup'] = new_pup
486+ generate_html_report(releases, issues)
487+ if options.email:
488+ create_email_notifications(releases, issues)
489+
490+if __name__ == '__main__':
491+ start_time = time.time()
492+ BASE_ERRORS_URL = 'https://errors.ubuntu.com/api/1.0/'
493+ LOCAL_ERRORS_URL = 'http://10.0.3.182/api/1.0/'
494+ LP_BASE_URL = 'https://launchpad.net'
495+ OVERRIDES = 'phased-updates-overrides.txt'
496+ NOTIFICATIONS = 'phased-updates-emails.txt'
497+ EMAIL_SENDER = 'brian.murray@ubuntu.com'
498+ PUP_INCREMENT = 10
499+ parser = OptionParser(usage = "usage: %prog [options]")
500+ parser.add_option(
501+ "-l", "--launchpad", dest="launchpad_instance", default="production")
502+ parser.add_option(
503+ "-n", "--no-act", default=False, action="store_true",
504+ help="do not modify phased update percentages")
505+ parser.add_option(
506+ "-e", "--email", default=False, action="store_true",
507+ help="send email notifications to uploaders")
508+ parser.add_option(
509+ "-f", "--fully-phased", default=False, action="store_true",
510+ help="show packages which have been fully phased")
511+ options, args = parser.parse_args()
512+ if options.launchpad_instance != 'production':
513+ LP_BASE_URL = 'https://%s.launchpad.net' % options.launchpad_instance
514+ launchpad = Launchpad.login_with(
515+ 'phased-updater', options.launchpad_instance, version='devel')
516+ logging.basicConfig(filename='phased-updates.log',
517+ format='%(asctime)s - %(levelname)s - %(message)s',
518+ level=logging.INFO)
519+ logging.info('Starting phased-updater')
520+ main()
521+ end_time = time.time()
522+ logging.info("Elapsed time was %g seconds" % (end_time - start_time))

Subscribers

People subscribed via source and target branches