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