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 contextlib | 5 import contextlib |
6 import datetime | 6 import datetime |
| 7 import distutils.util |
7 import json | 8 import json |
8 import logging | 9 import logging |
9 import os | 10 import os |
10 import re | 11 import re |
11 import shutil | 12 import shutil |
12 import subprocess | 13 import subprocess |
13 import sys | 14 import sys |
14 import tempfile | 15 import tempfile |
15 | 16 |
16 | 17 |
17 from infra.libs.time_functions import zulu | 18 from infra.libs.time_functions import zulu |
18 | 19 |
19 | 20 |
20 LOGGER = logging.getLogger(__name__) | 21 LOGGER = logging.getLogger(__name__) |
21 | 22 |
| 23 MM_REPO = 'https://chrome-internal.googlesource.com/infradata/master-manager' |
| 24 |
22 | 25 |
23 class MasterNotFoundException(Exception): | 26 class MasterNotFoundException(Exception): |
24 pass | 27 pass |
25 | 28 |
26 | 29 |
27 def add_argparse_options(parser): | 30 def add_argparse_options(parser): |
28 parser.add_argument( | 31 parser.add_argument( |
29 'masters', type=str, nargs='+', | 32 'masters', type=str, nargs='+', |
30 help='Master(s) to restart. "master." prefix can be omitted.') | 33 help='Master(s) to restart. "master." prefix can be omitted.') |
31 parser.add_argument( | 34 parser.add_argument( |
32 '-m', '--minutes-in-future', default=15, type=int, | 35 '-m', '--minutes-in-future', default=15, type=int, |
33 help='how many minutes in the future to schedule the restart. ' | 36 help='how many minutes in the future to schedule the restart. ' |
34 'use 0 for "now." default %(default)d') | 37 'use 0 for "now." default %(default)d') |
35 parser.add_argument('-b', '--bug', default=None, type=str, | 38 parser.add_argument('-b', '--bug', default=None, type=str, |
36 help='Bug containing master restart request.') | 39 help='Bug containing master restart request.') |
| 40 parser.add_argument( |
| 41 '-f', '--force', action='store_true', |
| 42 help='don\'t ask for confirmation, just commit') |
37 | 43 |
38 | 44 |
39 def get_restart_time(delta): | 45 def get_restart_time(delta): |
40 """Returns a zulu time string of when to restart a master, now + delta.""" | 46 """Returns a zulu time string of when to restart a master, now + delta.""" |
41 restart_time = datetime.datetime.utcnow() + delta | 47 restart_time = datetime.datetime.utcnow() + delta |
42 return zulu.to_zulu_string(restart_time) | 48 return zulu.to_zulu_string(restart_time) |
43 | 49 |
44 | 50 |
45 @contextlib.contextmanager | 51 @contextlib.contextmanager |
46 def get_master_state_checkout(): | 52 def get_master_state_checkout(): |
47 target_dir = tempfile.mkdtemp() | 53 target_dir = tempfile.mkdtemp() |
48 mm_repo = 'https://chrome-internal.googlesource.com/infradata/master-manager' | |
49 try: | 54 try: |
50 LOGGER.info('Cloning %s into %s' % (mm_repo, target_dir)) | 55 LOGGER.info('Cloning %s into %s' % (MM_REPO, target_dir)) |
51 subprocess.call(['git', 'clone', mm_repo, target_dir]) | 56 subprocess.call(['git', 'clone', MM_REPO, target_dir]) |
52 LOGGER.info('done') | 57 LOGGER.info('done') |
53 yield target_dir | 58 yield target_dir |
54 finally: | 59 finally: |
55 shutil.rmtree(target_dir) | 60 shutil.rmtree(target_dir) |
56 | 61 |
57 | 62 |
58 def commit(target, masters, bug): | 63 def commit(target, masters, bug, timestring, delta, force): |
59 """Commits the local CL via the CQ.""" | 64 """Commits the local CL via the CQ.""" |
60 desc = 'Restarting master(s) %s' % ', '.join(masters) | 65 desc = 'Restarting master(s) %s' % ', '.join(masters) |
61 if bug: | 66 if bug: |
62 desc = '%s\nBUG=%s' % (desc, bug) | 67 desc = '%s\nBUG=%s' % (desc, bug) |
63 subprocess.check_call( | 68 subprocess.check_call( |
64 ['git', 'commit', '--all', '--message', desc], cwd=target) | 69 ['git', 'commit', '--all', '--message', desc], cwd=target) |
| 70 |
| 71 print |
| 72 print 'Restarting the following masters in %d minutes (%s)' % ( |
| 73 delta.total_seconds() / 60, timestring) |
| 74 for master in sorted(masters): |
| 75 print ' %s' % master |
| 76 print |
| 77 |
| 78 print "This will upload a CL for master_manager.git, TBR an owner, and " |
| 79 print "commit the CL through the CQ." |
| 80 print |
| 81 |
| 82 if not force: |
| 83 print 'Commit? [Y/n]:', |
| 84 input_string = raw_input() |
| 85 if input_string != '' and not distutils.util.strtobool(input_string): |
| 86 print 'Aborting.' |
| 87 return |
| 88 |
| 89 print 'To cancel, edit desired_master_state.json in %s.' % MM_REPO |
| 90 print |
| 91 |
65 LOGGER.info('Uploading to Rietveld and CQ.') | 92 LOGGER.info('Uploading to Rietveld and CQ.') |
66 subprocess.check_call( | 93 subprocess.check_call( |
67 ['git', 'cl', 'upload', '-m', desc, '-t', desc, | 94 ['git', 'cl', 'upload', '-m', desc, '-t', desc, |
68 '--tbr-owners', '-c', '-f'], cwd=target) | 95 '--tbr-owners', '-c', '-f'], cwd=target) |
69 | 96 |
70 | 97 |
71 def run(masters, delta, bug): | 98 def run(masters, delta, bug, force): |
72 """Restart all the masters in the list of masters. | 99 """Restart all the masters in the list of masters. |
73 | 100 |
74 Schedules the restart for now + delta. | 101 Schedules the restart for now + delta. |
75 """ | 102 """ |
76 # Step 1: Acquire a clean master state checkout. | 103 # Step 1: Acquire a clean master state checkout. |
77 # This repo is too small to consider caching. | 104 # This repo is too small to consider caching. |
78 with get_master_state_checkout() as master_state_dir: | 105 with get_master_state_checkout() as master_state_dir: |
79 master_state_json = os.path.join( | 106 master_state_json = os.path.join( |
80 master_state_dir, 'desired_master_state.json') | 107 master_state_dir, 'desired_master_state.json') |
81 restart_time = get_restart_time(delta) | 108 restart_time = get_restart_time(delta) |
(...skipping 16 matching lines...) Expand all Loading... |
98 'desired_state': 'running', 'transition_time_utc': restart_time | 125 'desired_state': 'running', 'transition_time_utc': restart_time |
99 }) | 126 }) |
100 | 127 |
101 LOGGER.info('Writing back to JSON file, %d new entries' % len(master_state)) | 128 LOGGER.info('Writing back to JSON file, %d new entries' % len(master_state)) |
102 with open(master_state_json, 'w') as f: | 129 with open(master_state_json, 'w') as f: |
103 json.dump( | 130 json.dump( |
104 master_state, f, sort_keys=True, indent=2, separators=(',', ':')) | 131 master_state, f, sort_keys=True, indent=2, separators=(',', ':')) |
105 | 132 |
106 # Step 3: Send the patch to Rietveld and commit it via the CQ. | 133 # Step 3: Send the patch to Rietveld and commit it via the CQ. |
107 LOGGER.info('Committing back into repository') | 134 LOGGER.info('Committing back into repository') |
108 commit(master_state_dir, masters, bug) | 135 commit(master_state_dir, masters, bug, restart_time, delta, force) |
OLD | NEW |