| OLD | NEW |
| (Empty) | |
| 1 #!/usr/bin/python |
| 2 # Copyright 2015 Google Inc. All Rights Reserved. |
| 3 # pylint: disable=F0401 |
| 4 |
| 5 """Start, restart and shut down masters as needed.""" |
| 6 |
| 7 import argparse |
| 8 import json |
| 9 import logging |
| 10 import os |
| 11 import subprocess |
| 12 import sys |
| 13 |
| 14 from functools import partial |
| 15 |
| 16 from infra.libs import logs |
| 17 from infra.libs.buildbot import master |
| 18 from infra.libs.service_utils import daemon |
| 19 from infra.libs.service_utils import outer_loop |
| 20 from infra.services.master_lifecycle import buildbot_state |
| 21 |
| 22 |
| 23 def parse_args(): # pragma: no cover |
| 24 parser = argparse.ArgumentParser( |
| 25 description='Manage the state of a buildbot master. NOTE: Does nothing ' |
| 26 'unless --prod is specified') |
| 27 parser.add_argument('directory', nargs='?', |
| 28 help='location of the master to manage') |
| 29 parser.add_argument('--desired-state-file', default='desired_state.json', |
| 30 help='location of the state file to look up information') |
| 31 parser.add_argument('--list-all-states', action='store_true', |
| 32 help='list all states with their actions and exit') |
| 33 parser.add_argument('--enable-gclient-sync', action='store_true', |
| 34 help='perform a gclient sync before every master start') |
| 35 parser.add_argument('--emergency-file', |
| 36 default='.stop_master_lifecycle', |
| 37 help='filename of the emergency stop file. if this file is found in the ' |
| 38 'master directory, exit immediately') |
| 39 parser.add_argument('--prod', action='store_true', |
| 40 help='actually run commands instead of printing them.') |
| 41 parser.add_argument('--loop', action='store_true', |
| 42 help='repeatedly run the state machine. will not terminate unless killed') |
| 43 parser.add_argument('--loop-sleep-secs', type=int, default=5, |
| 44 help='how many seconds to wait between loop runs. default %(default)s') |
| 45 parser.add_argument('--connection-timeout', type=int, default=30, |
| 46 help='how many seconds to wait for a master http request before timing ' |
| 47 'out.') |
| 48 outer_loop.add_argparse_options(parser) |
| 49 logs.add_argparse_options(parser) |
| 50 |
| 51 args = parser.parse_args() |
| 52 logs.process_argparse_options(args) |
| 53 |
| 54 if not args.list_all_states and not args.directory: |
| 55 parser.error('A master directory must be specified.') |
| 56 return args |
| 57 |
| 58 |
| 59 def run_state_machine_pass( |
| 60 logger, matchlist, abs_master_directory, emergency_file, desired_state_file, |
| 61 enable_gclient_sync, prod, connection_timeout): # pragma: no cover |
| 62 if os.path.exists(os.path.join(abs_master_directory, emergency_file)): |
| 63 logger.error('%s detected in %s, aborting!', |
| 64 emergency_file, abs_master_directory) |
| 65 return 1 |
| 66 |
| 67 desired_state_file = os.path.abspath(desired_state_file) |
| 68 master_directory = os.path.basename(abs_master_directory) |
| 69 evidence = buildbot_state.collect_evidence( |
| 70 abs_master_directory, connection_timeout=connection_timeout) |
| 71 with open(desired_state_file) as f: |
| 72 evidence['desired_buildbot_state'] = json.load(f).get(master_directory) |
| 73 if not evidence['desired_buildbot_state']: |
| 74 raise KeyError('Couldn\'t get evidence for master %s from %s.' % |
| 75 (master_directory, desired_state_file)) |
| 76 |
| 77 state, action_name, action_items = matchlist.execution_list(evidence) |
| 78 execution_list = list( |
| 79 master.convert_action_items_to_cli( |
| 80 action_items, abs_master_directory, |
| 81 enable_gclient=enable_gclient_sync)) |
| 82 logger.info('current state: %s', state) |
| 83 logger.info('performing action: %s', action_name) |
| 84 |
| 85 if execution_list: |
| 86 if prod: |
| 87 logger.info('production run, executing:') |
| 88 else: |
| 89 logger.info('dry run, not executing:') |
| 90 for cmd in execution_list: |
| 91 logger.info('* %s (in %s)', cmd['cmd'], cmd['cwd']) |
| 92 if prod: |
| 93 try: |
| 94 with daemon.flock(cmd['lockfile']): |
| 95 subprocess.check_call([str(x) for x in cmd['cmd']], cwd=cmd['cwd']) |
| 96 except daemon.LockAlreadyLocked: |
| 97 logger.warn(' lock on %s could not be acquired, no action taken.', |
| 98 cmd['lockfile']) |
| 99 else: |
| 100 logger.info('no action to be taken.') |
| 101 return 0 |
| 102 |
| 103 |
| 104 def main(): # pragma: no cover |
| 105 args = parse_args() |
| 106 matchlist = buildbot_state.construct_pattern_matcher() |
| 107 logger = logging.getLogger(__name__) |
| 108 |
| 109 if args.list_all_states: |
| 110 matchlist.print_all_states() |
| 111 return 0 |
| 112 |
| 113 abs_master_directory = os.path.abspath(args.directory) |
| 114 |
| 115 state_machine = partial(run_state_machine_pass, logger, |
| 116 matchlist, abs_master_directory, args.emergency_file, |
| 117 args.desired_state_file, args.enable_gclient_sync, args.prod, |
| 118 args.connection_timeout) |
| 119 |
| 120 if args.loop: |
| 121 loop_opts = outer_loop.process_argparse_options(args) |
| 122 outer_loop.loop( |
| 123 state_machine, lambda: args.loop_sleep_secs, **loop_opts) |
| 124 else: |
| 125 return state_machine() |
| 126 |
| 127 return 0 |
| 128 |
| 129 |
| 130 if __name__ == '__main__': # pragma: no cover |
| 131 sys.exit(main()) |
| OLD | NEW |