Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(148)

Side by Side Diff: scripts/slave/gatekeeper_ng.py

Issue 105773005: Add ability to specify email subject for gatekeeper_ng. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/build
Patch Set: Created 7 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | scripts/slave/unittests/gatekeeper_ng_test.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 #!/usr/bin/env python 1 #!/usr/bin/env python
2 # Copyright 2013 The Chromium Authors. All rights reserved. 2 # Copyright 2013 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be 3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file. 4 # found in the LICENSE file.
5 5
6 """Closes tree if configured masters have failed tree-closing steps. 6 """Closes tree if configured masters have failed tree-closing steps.
7 7
8 Given a list of masters, gatekeeper_ng will get a list of the latest builds from 8 Given a list of masters, gatekeeper_ng will get a list of the latest builds from
9 the specified masters. It then checks if any tree-closing steps have failed, and 9 the specified masters. It then checks if any tree-closing steps have failed, and
10 if so closes the tree and emails appropriate parties. Configuration for which 10 if so closes the tree and emails appropriate parties. Configuration for which
(...skipping 135 matching lines...) Expand 10 before | Expand all | Expand 10 after
146 failed_builds = [] 146 failed_builds = []
147 for build_json, master_url in master_builds: 147 for build_json, master_url in master_builds:
148 gatekeeper_sections = gatekeeper_config.get(master_url, []) 148 gatekeeper_sections = gatekeeper_config.get(master_url, [])
149 for gatekeeper_section in gatekeeper_sections: 149 for gatekeeper_section in gatekeeper_sections:
150 gatekeeper = gatekeeper_section.get(build_json['builderName'], {}) 150 gatekeeper = gatekeeper_section.get(build_json['builderName'], {})
151 steps = build_json['steps'] 151 steps = build_json['steps']
152 forgiving = set(gatekeeper.get('forgiving_steps', [])) 152 forgiving = set(gatekeeper.get('forgiving_steps', []))
153 closing_steps = set(gatekeeper.get('closing_steps', [])) | forgiving 153 closing_steps = set(gatekeeper.get('closing_steps', [])) | forgiving
154 tree_notify = set(gatekeeper.get('tree_notify', [])) 154 tree_notify = set(gatekeeper.get('tree_notify', []))
155 sheriff_classes = set(gatekeeper.get('sheriff_classes', [])) 155 sheriff_classes = set(gatekeeper.get('sheriff_classes', []))
156 subject_template = gatekeeper['subject_template']
156 finished = [s for s in steps if s.get('isFinished')] 157 finished = [s for s in steps if s.get('isFinished')]
157 close_tree = gatekeeper.get('close_tree', True) 158 close_tree = gatekeeper.get('close_tree', True)
158 respect_build_status = gatekeeper.get('respect_build_status', False) 159 respect_build_status = gatekeeper.get('respect_build_status', False)
159 160
160 successful_steps = set(s['name'] for s in finished 161 successful_steps = set(s['name'] for s in finished
161 if (s.get('results', [FAILURE])[0] == SUCCESS or 162 if (s.get('results', [FAILURE])[0] == SUCCESS or
162 s.get('results', [FAILURE])[0] == WARNINGS)) 163 s.get('results', [FAILURE])[0] == WARNINGS))
163 164
164 unsatisfied_steps = closing_steps - successful_steps 165 unsatisfied_steps = closing_steps - successful_steps
165 166
(...skipping 28 matching lines...) Expand all
194 build_db[master_url][build_json['builderName']] = max( 195 build_db[master_url][build_json['builderName']] = max(
195 build_json['number'], 196 build_json['number'],
196 build_db[master_url][build_json['builderName']]) 197 build_db[master_url][build_json['builderName']])
197 198
198 failed_builds.append({'base_url': buildbot_url, 199 failed_builds.append({'base_url': buildbot_url,
199 'build': build_json, 200 'build': build_json,
200 'close_tree': close_tree, 201 'close_tree': close_tree,
201 'forgiving_steps': forgiving, 202 'forgiving_steps': forgiving,
202 'project_name': project_name, 203 'project_name': project_name,
203 'sheriff_classes': sheriff_classes, 204 'sheriff_classes': sheriff_classes,
205 'subject_template': subject_template,
204 'tree_notify': tree_notify, 206 'tree_notify': tree_notify,
205 'unsatisfied': unsatisfied_steps, 207 'unsatisfied': unsatisfied_steps,
206 }) 208 })
207 209
208 return failed_builds 210 return failed_builds
209 211
210 212
211 def allowed_keys(test_dict, *keys): 213 def allowed_keys(test_dict, *keys):
212 keys = keys + ('comment',) 214 keys = keys + ('comment',)
213 assert all(k in keys for k in test_dict), ( 215 assert all(k in keys for k in test_dict), (
(...skipping 22 matching lines...) Expand all
236 is considered blank (empty set), and inheritance is always constructive (you 238 is considered blank (empty set), and inheritance is always constructive (you
237 can't remove a property by inheriting or overwriting it). Builders can inherit 239 can't remove a property by inheriting or overwriting it). Builders can inherit
238 categories from their master. 240 categories from their master.
239 241
240 A master consists of zero or more sections, which specify which builders are 242 A master consists of zero or more sections, which specify which builders are
241 watched by the section and what action should be taken. A section can specify 243 watched by the section and what action should be taken. A section can specify
242 tree_closing to be false, which causes the section to only send out emails 244 tree_closing to be false, which causes the section to only send out emails
243 instead of closing the tree. A section or builder can also specify to respect 245 instead of closing the tree. A section or builder can also specify to respect
244 a build's failure status with respect_build_status. 246 a build's failure status with respect_build_status.
245 247
248 The 'subject_template' key is the template used for the email subjects. Its
249 formatting arguments are found at https://chromium.googlesource.com/chromium/
250 tools/chromium-build/+/master/gatekeeper_mailer.py, but the list is
251 reproduced here:
252
253 %(result)s: 'warning' or 'failure'
254 %(projectName): 'Chromium', 'Chromium Perf', etc.
255 %(builder): the builder name
256 %(reason): reason for launching the build
257 %(revision): build revision
258 %(buildnumber): buildnumber
259
246 The 'comment' key can be put anywhere and is ignored by the parser. 260 The 'comment' key can be put anywhere and is ignored by the parser.
247 261
248 # Python, not JSON. 262 # Python, not JSON.
249 { 263 {
250 'masters': { 264 'masters': {
251 'http://build.chromium.org/p/chromium.win': [ 265 'http://build.chromium.org/p/chromium.win': [
252 { 266 {
253 'sheriff_classes': ['sheriff_win'], 267 'sheriff_classes': ['sheriff_win'],
254 'tree_notify': ['a_watcher@chromium.org'], 268 'tree_notify': ['a_watcher@chromium.org'],
255 'categories': ['win_extra'], 269 'categories': ['win_extra'],
(...skipping 12 matching lines...) Expand all
268 'categories': { 282 'categories': {
269 'win_tests': { 283 'win_tests': {
270 'comment': 'this is for all windows testers', 284 'comment': 'this is for all windows testers',
271 'closing_steps': ['startup_test'], 285 'closing_steps': ['startup_test'],
272 'forgiving_steps': ['boot_windows'], 286 'forgiving_steps': ['boot_windows'],
273 'tree_notify': ['win_watchers@chromium.org'], 287 'tree_notify': ['win_watchers@chromium.org'],
274 'sheriff_classes': ['sheriff_win_test'] 288 'sheriff_classes': ['sheriff_win_test']
275 }, 289 },
276 'win_extra': { 290 'win_extra': {
277 'closing_steps': ['extra_win_step'] 291 'closing_steps': ['extra_win_step']
292 'subject_template': 'windows heads up on %(builder)',
278 } 293 }
279 } 294 }
280 } 295 }
281 296
282 In this case, XP Tests (1) would be flattened down to: 297 In this case, XP Tests (1) would be flattened down to:
283 closing_steps: ['startup_test', 'win_tests'] 298 closing_steps: ['startup_test', 'win_tests']
284 forgiving_steps: ['archive', 'boot_windows'] 299 forgiving_steps: ['archive', 'boot_windows']
285 tree_notify: ['xp_watchers@chromium.org', 'win_watchers@chromium.org', 300 tree_notify: ['xp_watchers@chromium.org', 'win_watchers@chromium.org',
286 'a_watcher@chromium.org'] 301 'a_watcher@chromium.org']
287 sheriff_classes: ['sheriff_win', 'sheriff_win_test', 'sheriff_xp'] 302 sheriff_classes: ['sheriff_win', 'sheriff_win_test', 'sheriff_xp']
288 303
289 Again, fields are optional and treated as empty lists/sets if not present. 304 Again, fields are optional and treated as empty lists/sets if not present.
290 """ 305 """
291 306
292 master_keys = ['sheriff_classes', 'tree_notify'] 307 # Keys which are allowed in a master or builder section.
308 master_keys = ['sheriff_classes', 'tree_notify', 'subject_template']
293 builder_keys = ['forgiving_steps', 'closing_steps', 'tree_notify', 309 builder_keys = ['forgiving_steps', 'closing_steps', 'tree_notify',
294 'sheriff_classes'] 310 'sheriff_classes', 'subject_template']
311
312
313 # Keys which have defaults besides None or set([]).
314 defaults = {
315 'subject_template': ('buildbot %(result)s in %(projectName)s on '
316 '%(builder)s, revision %(revision)s'),
317 }
318
319 # These keys are strings instead of sets. Strings can't be merged,
320 # so more specific (master -> category -> builder) strings clobber
321 # more generic ones.
322 strings = ['subject_template']
295 323
296 with open(filename) as f: 324 with open(filename) as f:
297 raw_gatekeeper_config = json.load(f) 325 raw_gatekeeper_config = json.load(f)
298 326
299 allowed_keys(raw_gatekeeper_config, 'categories', 'masters') 327 allowed_keys(raw_gatekeeper_config, 'categories', 'masters')
300 328
301 categories = raw_gatekeeper_config.get('categories', {}) 329 categories = raw_gatekeeper_config.get('categories', {})
302 masters = raw_gatekeeper_config.get('masters', {}) 330 masters = raw_gatekeeper_config.get('masters', {})
303 331
304 for category in categories.values(): 332 for category in categories.values():
305 allowed_keys(category, *builder_keys) 333 allowed_keys(category, *builder_keys)
306 334
307 gatekeeper_config = {} 335 gatekeeper_config = {}
308 for master_url, master_sections in masters.iteritems(): 336 for master_url, master_sections in masters.iteritems():
309 for master_section in master_sections: 337 for master_section in master_sections:
310 gatekeeper_config.setdefault(master_url, []).append({}) 338 gatekeeper_config.setdefault(master_url, []).append({})
311 allowed_keys(master_section, 'builders', 'categories', 'close_tree', 339 allowed_keys(master_section, 'builders', 'categories', 'close_tree',
312 'respect_build_status', *master_keys) 340 'respect_build_status', *master_keys)
313 341
314 builders = master_section.get('builders', {}) 342 builders = master_section.get('builders', {})
315 for buildername, builder in builders.iteritems(): 343 for buildername, builder in builders.iteritems():
316 allowed_keys(builder, 'categories', *builder_keys) 344 allowed_keys(builder, 'categories', *builder_keys)
317 for item in builder.values(): 345 for key, item in builder.iteritems():
318 assert isinstance(item, list) 346 if key in strings:
319 assert all(isinstance(elem, basestring) for elem in item) 347 assert isinstance(item, basestring)
348 else:
349 assert isinstance(item, list)
350 assert all(isinstance(elem, basestring) for elem in item)
320 351
321 gatekeeper_config[master_url][-1].setdefault(buildername, {}) 352 gatekeeper_config[master_url][-1].setdefault(buildername, {})
322 gatekeeper_builder = gatekeeper_config[master_url][-1][buildername] 353 gatekeeper_builder = gatekeeper_config[master_url][-1][buildername]
323 354
324 # Populate with empty defaults. 355 # Populate with specified defaults.
325 for k in builder_keys: 356 for k in builder_keys:
326 gatekeeper_builder.setdefault(k, set()) 357 if k in defaults:
358 gatekeeper_builder.setdefault(k, defaults[k])
359 elif k in strings:
360 gatekeeper_builder.setdefault(k, '')
361 else:
362 gatekeeper_builder.setdefault(k, set())
327 363
328 # Inherit any values from the master. 364 # Inherit any values from the master.
329 for k in master_keys: 365 for k in master_keys:
330 gatekeeper_builder[k] |= set(master_section.get(k, [])) 366 if k in strings:
367 if k in master_section:
368 gatekeeper_builder[k] = master_section[k]
369 else:
370 gatekeeper_builder[k] |= set(master_section.get(k, []))
331 371
332 gatekeeper_builder['close_tree'] = master_section.get('close_tree', 372 gatekeeper_builder['close_tree'] = master_section.get('close_tree',
333 True) 373 True)
334 gatekeeper_builder['respect_build_status'] = master_section.get( 374 gatekeeper_builder['respect_build_status'] = master_section.get(
335 'respect_build_status', False) 375 'respect_build_status', False)
336 376
337 # Inherit any values from the categories. 377 # Inherit any values from the categories.
338 all_categories = (builder.get('categories', []) + 378 all_categories = (builder.get('categories', []) +
339 master_section.get( 'categories', [])) 379 master_section.get( 'categories', []))
340 for c in all_categories: 380 for c in all_categories:
341 for k in builder_keys: 381 for k in builder_keys:
342 gatekeeper_builder[k] |= set(categories[c].get(k, [])) 382 if k in strings:
383 if k in categories[c]:
384 gatekeeper_builder[k] = categories[c][k]
385 else:
386 gatekeeper_builder[k] |= set(categories[c].get(k, []))
343 387
344 # Add in any builder-specific values. 388 # Add in any builder-specific values.
345 for k in builder_keys: 389 for k in builder_keys:
346 gatekeeper_builder[k] |= set(builder.get(k, [])) 390 if k in strings:
391 if k in builder:
392 gatekeeper_builder[k] = builder[k]
393 else:
394 gatekeeper_builder[k] |= set(builder.get(k, []))
347 395
348 return gatekeeper_config 396 return gatekeeper_config
iannucci 2013/12/17 00:46:23 This whole function is pretty funky. It may be wor
ghost stip (do not use) 2013/12/17 18:37:14 Good point, might make it a bit easier to traverse
349 397
350 398
351 def parse_sheriff_file(url): 399 def parse_sheriff_file(url):
352 """Given a sheriff url, download and parse the appropirate sheriff list.""" 400 """Given a sheriff url, download and parse the appropirate sheriff list."""
353 with closing(urllib2.urlopen(url)) as f: 401 with closing(urllib2.urlopen(url)) as f:
354 line = f.readline() 402 line = f.readline()
355 usernames_matcher_ = re.compile(r'document.write\(\'([\w, ]+)\'\)') 403 usernames_matcher_ = re.compile(r'document.write\(\'([\w, ]+)\'\)')
356 usernames_match = usernames_matcher_.match(line) 404 usernames_match = usernames_matcher_.match(line)
357 sheriffs = set() 405 sheriffs = set()
358 if usernames_match: 406 if usernames_match:
(...skipping 83 matching lines...) Expand 10 before | Expand all | Expand 10 after
442 tree_notify = failed_build['tree_notify'] 490 tree_notify = failed_build['tree_notify']
443 491
444 if failed_build['unsatisfied'] <= failed_build['forgiving_steps']: 492 if failed_build['unsatisfied'] <= failed_build['forgiving_steps']:
445 blamelist = set() 493 blamelist = set()
446 else: 494 else:
447 blamelist = set(failed_build['build']['blame']) 495 blamelist = set(failed_build['build']['blame'])
448 496
449 sheriffs = get_sheriffs(failed_build['sheriff_classes'], sheriff_url) 497 sheriffs = get_sheriffs(failed_build['sheriff_classes'], sheriff_url)
450 watchers = list(tree_notify | blamelist | sheriffs) 498 watchers = list(tree_notify | blamelist | sheriffs)
451 499
452
453 build_data = { 500 build_data = {
454 'build_url': build_url, 501 'build_url': build_url,
455 'from_addr': fromaddr, 502 'from_addr': fromaddr,
456 'project_name': project_name, 503 'project_name': project_name,
504 'subject_template': failed_build['subject_template'],
457 'steps': [], 505 'steps': [],
458 'unsatisfied': list(failed_build['unsatisfied']), 506 'unsatisfied': list(failed_build['unsatisfied']),
459 'waterfall_url': waterfall_url, 507 'waterfall_url': waterfall_url,
460 } 508 }
461 509
462 for field in ['builderName', 'number', 'reason']: 510 for field in ['builderName', 'number', 'reason']:
463 build_data[field] = failed_build['build'][field] 511 build_data[field] = failed_build['build'][field]
464 512
465 build_data['result'] = failed_build['build'].get('results', 0) 513 build_data['result'] = failed_build['build'].get('results', 0)
466 build_data['blamelist'] = failed_build['build']['blame'] 514 build_data['blamelist'] = failed_build['build']['blame']
(...skipping 191 matching lines...) Expand 10 before | Expand all | Expand 10 after
658 options.disable_domain_filter) 706 options.disable_domain_filter)
659 707
660 if not options.skip_build_db_update: 708 if not options.skip_build_db_update:
661 save_build_db(build_db, options.build_db) 709 save_build_db(build_db, options.build_db)
662 710
663 return 0 711 return 0
664 712
665 713
666 if __name__ == '__main__': 714 if __name__ == '__main__':
667 sys.exit(main()) 715 sys.exit(main())
OLDNEW
« no previous file with comments | « no previous file | scripts/slave/unittests/gatekeeper_ng_test.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698