| OLD | NEW |
| 1 # Copyright 2015 The Chromium Authors. All rights reserved. | 1 # Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
| 4 | 4 |
| 5 import collections | 5 import collections |
| 6 import contextlib | 6 import contextlib |
| 7 import datetime | 7 import datetime |
| 8 import dateutil.tz | 8 import dateutil.tz |
| 9 import distutils.util | 9 import distutils.util |
| 10 import json | 10 import json |
| (...skipping 73 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 84 parser.add_argument( | 84 parser.add_argument( |
| 85 'masters', type=str, nargs='+', | 85 'masters', type=str, nargs='+', |
| 86 help='Master(s) to restart. "master." prefix can be omitted.') | 86 help='Master(s) to restart. "master." prefix can be omitted.') |
| 87 parser.add_argument( | 87 parser.add_argument( |
| 88 '-m', '--minutes-in-future', default=15, type=int, | 88 '-m', '--minutes-in-future', default=15, type=int, |
| 89 help='how many minutes in the future to schedule the restart. ' | 89 help='how many minutes in the future to schedule the restart. ' |
| 90 'use 0 for "now." default %(default)d') | 90 'use 0 for "now." default %(default)d') |
| 91 parser.add_argument( | 91 parser.add_argument( |
| 92 '--eod', action='store_true', | 92 '--eod', action='store_true', |
| 93 help='schedules restart for 6:30PM Google Standard Time.') | 93 help='schedules restart for 6:30PM Google Standard Time.') |
| 94 parser.add_argument('-b', '--bug', default=None, type=str, | 94 parser.add_argument( |
| 95 help='Bug containing master restart request.') | 95 '-b', '--bug', default=None, type=str, |
| 96 help='Bug containing master restart request.') |
| 96 parser.add_argument( | 97 parser.add_argument( |
| 97 '-r', '--reviewer', action='append', type=str, | 98 '-r', '--reviewer', action='append', type=str, |
| 98 help=('Reviewer (ldap or ldap@google.com) to TBR the CL to. ' | 99 help=('Reviewer (ldap or ldap@google.com) to TBR the CL to. ' |
| 99 'If not specified, chooses a random reviewer from OWNERS file')) | 100 'If not specified, chooses a random reviewer from OWNERS file')) |
| 100 parser.add_argument( | 101 parser.add_argument( |
| 101 '-f', '--force', action='store_true', | 102 '-f', '--force', action='store_true', |
| 102 help='don\'t ask for confirmation, just commit') | 103 help='don\'t ask for confirmation, just commit') |
| 103 parser.add_argument( | 104 parser.add_argument( |
| 104 '-n', '--no-commit', action='store_true', | 105 '-n', '--no-commit', action='store_true', |
| 105 help='update the file, but refrain from performing the actual commit') | 106 help='update the file, but refrain from performing the actual commit') |
| 106 parser.add_argument( | 107 parser.add_argument( |
| 107 '-s', '--desired-state', default='running', | 108 '-s', '--desired-state', default='running', |
| 108 choices=buildbot_state.STATES['desired_buildbot_state'], | 109 choices=buildbot_state.STATES['desired_buildbot_state'], |
| 109 help='which desired state to put the buildbot master in ' | 110 help='which desired state to put the buildbot master in ' |
| 110 '(default %(default)s)') | 111 '(default %(default)s)') |
| 112 parser.add_argument( |
| 113 '-e', '--reason', type=str, default='', |
| 114 help='reason for restarting the master') |
| 111 | 115 |
| 112 | 116 |
| 113 def get_restart_spec(name, restart_time): | 117 def get_restart_spec(name, restart_time): |
| 114 def _trim_prefix(v, prefix): | 118 def _trim_prefix(v, prefix): |
| 115 if v.startswith(prefix): | 119 if v.startswith(prefix): |
| 116 return v[len(prefix):] | 120 return v[len(prefix):] |
| 117 return v | 121 return v |
| 118 name = _trim_prefix(name, 'master.') | 122 name = _trim_prefix(name, 'master.') |
| 119 | 123 |
| 120 def _get_restart_config(name, seen): | 124 def _get_restart_config(name, seen): |
| (...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 181 _, domain = r.split('@', 1) | 185 _, domain = r.split('@', 1) |
| 182 if domain != 'google.com': | 186 if domain != 'google.com': |
| 183 other.append(r) | 187 other.append(r) |
| 184 else: | 188 else: |
| 185 google.append(r) | 189 google.append(r) |
| 186 else: | 190 else: |
| 187 google.append('%s@google.com' % r) | 191 google.append('%s@google.com' % r) |
| 188 return google, other | 192 return google, other |
| 189 | 193 |
| 190 def commit( | 194 def commit( |
| 191 target, specs, reviewers, bug, force, no_commit, desired_state): | 195 target, specs, reviewers, bug, force, no_commit, desired_state, reason): |
| 192 """Commits the local CL via the CQ.""" | 196 """Commits the local CL via the CQ.""" |
| 193 if desired_state == 'running': | 197 if desired_state == 'running': |
| 194 action = 'Restarting' | 198 action = 'Restarting' |
| 195 else: | 199 else: |
| 196 action = desired_state.title() + 'ing' | 200 action = desired_state.title() + 'ing' |
| 197 | 201 |
| 198 desc = '%s master(s) %s\n' % ( | 202 desc = '%(action)s master%(plural)s %(names)s\n\n%(reason)s\n' % { |
| 199 action, ', '.join([s.name for s in specs])) | 203 'action': action, |
| 204 'plural': 's' if len(specs) > 1 else '', |
| 205 'names': ', '.join([s.name for s in specs]), |
| 206 'reason': reason, |
| 207 } |
| 200 if bug: | 208 if bug: |
| 201 desc += '\nBUG=%s' % bug | 209 desc += '\nBUG=%s' % bug |
| 202 tbr_whom = 'an owner' | 210 tbr_whom = 'an owner' |
| 203 if reviewers: | 211 if reviewers: |
| 204 google, other = autocomplete_and_partition(reviewers) | 212 google, other = autocomplete_and_partition(reviewers) |
| 205 if other: | 213 if other: |
| 206 print | 214 print |
| 207 print 'Error: not @google.com email(s) for reviewers found:' | 215 print 'Error: not @google.com email(s) for reviewers found:' |
| 208 print ' %s' % ('\n '.join(other)) | 216 print ' %s' % ('\n '.join(other)) |
| 209 print 'Hint: save your fingertips - use just ldap: -r <ldap>' | 217 print 'Hint: save your fingertips - use just ldap: -r <ldap>' |
| (...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 265 if not reviewers: | 273 if not reviewers: |
| 266 upload_cmd.append('--tbr-owners') | 274 upload_cmd.append('--tbr-owners') |
| 267 if not no_commit: | 275 if not no_commit: |
| 268 upload_cmd.append('-c') | 276 upload_cmd.append('-c') |
| 269 else: | 277 else: |
| 270 LOGGER.info('CQ bit not set, please commit manually. (--no-commit)') | 278 LOGGER.info('CQ bit not set, please commit manually. (--no-commit)') |
| 271 subprocess.check_call(upload_cmd, cwd=target) | 279 subprocess.check_call(upload_cmd, cwd=target) |
| 272 | 280 |
| 273 | 281 |
| 274 def run(masters, restart_time, reviewers, bug, force, no_commit, | 282 def run(masters, restart_time, reviewers, bug, force, no_commit, |
| 275 desired_state): | 283 desired_state, reason): |
| 276 """Restart all the masters in the list of masters. | 284 """Restart all the masters in the list of masters. |
| 277 | 285 |
| 278 Schedules the restart for restart_time. | 286 Schedules the restart for restart_time. |
| 279 | 287 |
| 280 Args: | 288 Args: |
| 281 masters - a list(str) of masters to restart | 289 masters - a list(str) of masters to restart |
| 282 restart_time - a datetime in UTC of when to restart them. If None, restart | 290 restart_time - a datetime in UTC of when to restart them. If None, restart |
| 283 at a predefined "end of day". | 291 at a predefined "end of day". |
| 284 reviewers - a list(str) of reviewers for the CL (may be empty) | 292 reviewers - a list(str) of reviewers for the CL (may be empty) |
| 285 bug - an integer bug number to include in the review or None | 293 bug - an integer bug number to include in the review or None |
| 286 force - a bool which causes commit not to prompt if true | 294 force - a bool which causes commit not to prompt if true |
| 287 no_commit - doesn't set the CQ bit on upload | 295 no_commit - doesn't set the CQ bit on upload |
| 288 desired_state - nominally 'running', picks which desired_state | 296 desired_state - nominally 'running', picks which desired_state |
| 289 to put the buildbot in | 297 to put the buildbot in |
| 298 reason - a short message saying why the master is being restarted |
| 290 """ | 299 """ |
| 291 masters = [get_restart_spec(m, restart_time) for m in sorted(set(masters))] | 300 masters = [get_restart_spec(m, restart_time) for m in sorted(set(masters))] |
| 292 | 301 |
| 302 reason = reason.strip() |
| 303 if not reason: |
| 304 reason = raw_input('Please provide a reason for this restart: ').strip() |
| 305 if not reason: |
| 306 print 'No reason provided, exiting' |
| 307 return 0 |
| 308 |
| 293 # Step 1: Acquire a clean master state checkout. | 309 # Step 1: Acquire a clean master state checkout. |
| 294 # This repo is too small to consider caching. | 310 # This repo is too small to consider caching. |
| 295 with get_master_state_checkout() as master_state_dir: | 311 with get_master_state_checkout() as master_state_dir: |
| 296 master_state_json = os.path.join( | 312 master_state_json = os.path.join( |
| 297 master_state_dir, 'desired_master_state.json') | 313 master_state_dir, 'desired_master_state.json') |
| 298 | 314 |
| 299 # Step 2: make modifications to the master state json. | 315 # Step 2: make modifications to the master state json. |
| 300 LOGGER.info('Reading %s' % master_state_json) | 316 LOGGER.info('Reading %s' % master_state_json) |
| 301 with open(master_state_json, 'r') as f: | 317 with open(master_state_json, 'r') as f: |
| 302 desired_master_state = json.load(f) | 318 desired_master_state = json.load(f) |
| (...skipping 21 matching lines...) Expand all Loading... |
| 324 entries += 1 | 340 entries += 1 |
| 325 | 341 |
| 326 LOGGER.info('Writing back to JSON file, %d new entries' % (entries,)) | 342 LOGGER.info('Writing back to JSON file, %d new entries' % (entries,)) |
| 327 desired_state_parser.write_master_state( | 343 desired_state_parser.write_master_state( |
| 328 desired_master_state, master_state_json, | 344 desired_master_state, master_state_json, |
| 329 prune_only_masters=set(m.desired_state_name for m in masters)) | 345 prune_only_masters=set(m.desired_state_name for m in masters)) |
| 330 | 346 |
| 331 # Step 3: Send the patch to Rietveld and commit it via the CQ. | 347 # Step 3: Send the patch to Rietveld and commit it via the CQ. |
| 332 LOGGER.info('Committing back into repository') | 348 LOGGER.info('Committing back into repository') |
| 333 commit(master_state_dir, masters, reviewers, bug, force, no_commit, | 349 commit(master_state_dir, masters, reviewers, bug, force, no_commit, |
| 334 desired_state) | 350 desired_state, reason) |
| OLD | NEW |