Alex Miller | b0b2d25 | 2014-06-25 17:17:01 -0700 | [diff] [blame] | 1 | #!/usr/bin/python |
| 2 | |
Don Garrett | 4003636 | 2014-12-08 15:52:44 -0800 | [diff] [blame] | 3 | from __future__ import print_function |
| 4 | |
| 5 | import argparse |
J. Richard Barnette | 868cf64 | 2014-07-21 16:34:38 -0700 | [diff] [blame] | 6 | import subprocess |
| 7 | import sys |
Alex Miller | b0b2d25 | 2014-06-25 17:17:01 -0700 | [diff] [blame] | 8 | |
Don Garrett | 4003636 | 2014-12-08 15:52:44 -0800 | [diff] [blame] | 9 | import common |
Don Garrett | 5071346 | 2015-01-07 18:04:05 -0800 | [diff] [blame] | 10 | from autotest_lib.server import frontend |
Alex Miller | b0b2d25 | 2014-06-25 17:17:01 -0700 | [diff] [blame] | 11 | from autotest_lib.site_utils.lib import infra |
| 12 | |
Alex Miller | b0b2d25 | 2014-06-25 17:17:01 -0700 | [diff] [blame] | 13 | |
Dan Shi | 57d4c73 | 2015-01-22 18:38:50 -0800 | [diff] [blame] | 14 | def discover_servers(afe, server_filter=set()): |
Don Garrett | 4003636 | 2014-12-08 15:52:44 -0800 | [diff] [blame] | 15 | """Discover the in-production servers to update. |
Alex Miller | b0b2d25 | 2014-06-25 17:17:01 -0700 | [diff] [blame] | 16 | |
Don Garrett | eecbc13 | 2015-01-08 17:26:20 -0800 | [diff] [blame] | 17 | @param afe: Server to contact with RPC requests. |
Dan Shi | 57d4c73 | 2015-01-22 18:38:50 -0800 | [diff] [blame] | 18 | @param server_filter: A set of servers to get status for. |
Don Garrett | eecbc13 | 2015-01-08 17:26:20 -0800 | [diff] [blame] | 19 | |
Dan Shi | 57d4c73 | 2015-01-22 18:38:50 -0800 | [diff] [blame] | 20 | @returns: A list of tuple of (server_name, server_status), the list in |
| 21 | sorted by the order to be updated. |
Don Garrett | 4003636 | 2014-12-08 15:52:44 -0800 | [diff] [blame] | 22 | """ |
Don Garrett | 5071346 | 2015-01-07 18:04:05 -0800 | [diff] [blame] | 23 | # Example server details.... |
| 24 | # { |
| 25 | # 'hostname': 'server1', |
| 26 | # 'status': 'backup', |
| 27 | # 'roles': ['drone', 'scheduler'], |
| 28 | # 'attributes': {'max_processes': 300} |
| 29 | # } |
Don Garrett | eecbc13 | 2015-01-08 17:26:20 -0800 | [diff] [blame] | 30 | rpc = frontend.AFE(server=afe) |
Don Garrett | 5071346 | 2015-01-07 18:04:05 -0800 | [diff] [blame] | 31 | servers = rpc.run('get_servers') |
Don Garrett | 4003636 | 2014-12-08 15:52:44 -0800 | [diff] [blame] | 32 | |
Dan Shi | 57d4c73 | 2015-01-22 18:38:50 -0800 | [diff] [blame] | 33 | # Do not update servers that need repair, and filter the server list by |
| 34 | # given server_filter if needed. |
| 35 | servers = [s for s in servers |
| 36 | if (s['status'] != 'repair_required' and |
| 37 | (not server_filter or s['hostname'] in server_filter))] |
Don Garrett | 4003636 | 2014-12-08 15:52:44 -0800 | [diff] [blame] | 38 | |
Dan Shi | a179738 | 2015-05-28 10:59:52 -0700 | [diff] [blame^] | 39 | # Do not update devserver or crash_server (not YET supported). |
| 40 | servers = [s for s in servers if 'devserver' not in s['roles'] and |
| 41 | 'crash_server' not in s['roles']] |
Don Garrett | 5071346 | 2015-01-07 18:04:05 -0800 | [diff] [blame] | 42 | |
| 43 | def update_order(s): |
| 44 | """Sort order for updating servers (lower first). |
| 45 | |
| 46 | @param s: Server details for a single server. |
| 47 | """ |
| 48 | if 'database' in s['roles']: |
| 49 | return 0 |
| 50 | if 'scheduler' in s['roles']: |
| 51 | return 1 |
| 52 | return 2 |
| 53 | |
| 54 | # Order in which servers are updated. |
| 55 | servers.sort(key=update_order) |
| 56 | |
Dan Shi | 57d4c73 | 2015-01-22 18:38:50 -0800 | [diff] [blame] | 57 | # Build the return list of (hostname, status) |
| 58 | server_status = [(s['hostname'], s['status']) for s in servers] |
| 59 | found_servers = set([s['hostname'] for s in servers]) |
| 60 | # Inject the servers passed in by user but not found in server database. |
| 61 | for server in server_filter-found_servers: |
| 62 | server_status.append((server, 'unknown')) |
| 63 | |
| 64 | return server_status |
Alex Miller | b0b2d25 | 2014-06-25 17:17:01 -0700 | [diff] [blame] | 65 | |
J. Richard Barnette | f533b18 | 2014-09-04 18:24:42 -0700 | [diff] [blame] | 66 | |
Don Garrett | 4003636 | 2014-12-08 15:52:44 -0800 | [diff] [blame] | 67 | def parse_arguments(args): |
| 68 | """Parse command line arguments. |
| 69 | |
| 70 | @param args: The command line arguments to parse. (usually sys.argv[1:]) |
| 71 | |
| 72 | @returns An argparse.Namespace populated with argument values. |
| 73 | """ |
| 74 | parser = argparse.ArgumentParser( |
Don Garrett | 3f2b660 | 2014-12-16 18:19:16 -0800 | [diff] [blame] | 75 | formatter_class=argparse.RawDescriptionHelpFormatter, |
| 76 | description='Command to update an entire autotest installation.', |
| 77 | epilog=('Update all servers:\n' |
| 78 | ' deploy_production.py\n' |
| 79 | '\n' |
| 80 | 'Update one server:\n' |
| 81 | ' deploy_production.py <server>\n' |
| 82 | '\n' |
| 83 | 'Send arguments to remote deploy_production_local.py:\n' |
| 84 | ' deploy_production.py -- --dryrun\n' |
| 85 | '\n' |
| 86 | 'See what arguments would be run on specified servers:\n' |
| 87 | ' deploy_production.py --dryrun <server_a> <server_b> --' |
| 88 | ' --skip-update\n')) |
| 89 | |
Don Garrett | 4003636 | 2014-12-08 15:52:44 -0800 | [diff] [blame] | 90 | parser.add_argument('--continue', action='store_true', dest='cont', |
Don Garrett | eecbc13 | 2015-01-08 17:26:20 -0800 | [diff] [blame] | 91 | help='Continue to the next server on failure.') |
| 92 | parser.add_argument('--afe', default='cautotest', |
| 93 | help='What is the main server for this installation? (cautotest).') |
Don Garrett | 4003636 | 2014-12-08 15:52:44 -0800 | [diff] [blame] | 94 | parser.add_argument('--dryrun', action='store_true', |
Don Garrett | eecbc13 | 2015-01-08 17:26:20 -0800 | [diff] [blame] | 95 | help='Don\'t actually run remote commands.') |
Don Garrett | 4003636 | 2014-12-08 15:52:44 -0800 | [diff] [blame] | 96 | parser.add_argument('args', nargs=argparse.REMAINDER, |
Don Garrett | eecbc13 | 2015-01-08 17:26:20 -0800 | [diff] [blame] | 97 | help=('<server>, <server> ... -- <remote_arg>, <remote_arg> ...')) |
Don Garrett | 4003636 | 2014-12-08 15:52:44 -0800 | [diff] [blame] | 98 | |
| 99 | results = parser.parse_args(args) |
| 100 | |
Don Garrett | 3f2b660 | 2014-12-16 18:19:16 -0800 | [diff] [blame] | 101 | # We take the args list and further split it down. Everything before -- |
| 102 | # is a server name, and everything after it is an argument to pass along |
| 103 | # to deploy_production_local.py. |
| 104 | # |
| 105 | # This: |
| 106 | # server_a, server_b -- --dryrun --skip-report |
| 107 | # |
| 108 | # Becomes: |
| 109 | # args.servers['server_a', 'server_b'] |
| 110 | # args.args['--dryrun', '--skip-report'] |
| 111 | try: |
| 112 | local_args_index = results.args.index('--') + 1 |
| 113 | except ValueError: |
| 114 | # If -- isn't present, they are all servers. |
| 115 | results.servers = results.args |
| 116 | results.args = [] |
| 117 | else: |
| 118 | # Split arguments. |
| 119 | results.servers = results.args[:local_args_index-1] |
| 120 | results.args = results.args[local_args_index:] |
Don Garrett | 4003636 | 2014-12-08 15:52:44 -0800 | [diff] [blame] | 121 | |
| 122 | return results |
J. Richard Barnette | f533b18 | 2014-09-04 18:24:42 -0700 | [diff] [blame] | 123 | |
| 124 | |
Don Garrett | 4003636 | 2014-12-08 15:52:44 -0800 | [diff] [blame] | 125 | def main(args): |
| 126 | """Main routine that drives all the real work. |
Alex Miller | b0b2d25 | 2014-06-25 17:17:01 -0700 | [diff] [blame] | 127 | |
Don Garrett | 4003636 | 2014-12-08 15:52:44 -0800 | [diff] [blame] | 128 | @param args: The command line arguments to parse. (usually sys.argv[1:]) |
J. Richard Barnette | 868cf64 | 2014-07-21 16:34:38 -0700 | [diff] [blame] | 129 | |
Don Garrett | 4003636 | 2014-12-08 15:52:44 -0800 | [diff] [blame] | 130 | @returns The system exit code. |
| 131 | """ |
| 132 | options = parse_arguments(args) |
Alex Miller | b0b2d25 | 2014-06-25 17:17:01 -0700 | [diff] [blame] | 133 | |
Dan Shi | 57d4c73 | 2015-01-22 18:38:50 -0800 | [diff] [blame] | 134 | print('Retrieving server status...') |
| 135 | server_status = discover_servers(options.afe, set(options.servers or [])) |
Alex Miller | b0b2d25 | 2014-06-25 17:17:01 -0700 | [diff] [blame] | 136 | |
Don Garrett | 4003636 | 2014-12-08 15:52:44 -0800 | [diff] [blame] | 137 | # Display what we plan to update. |
| 138 | print('Will update (in this order):') |
Dan Shi | 57d4c73 | 2015-01-22 18:38:50 -0800 | [diff] [blame] | 139 | for server, status in server_status: |
| 140 | print('\t%-36s:\t%s' % (server, status)) |
Don Garrett | 4003636 | 2014-12-08 15:52:44 -0800 | [diff] [blame] | 141 | print() |
Alex Miller | b0b2d25 | 2014-06-25 17:17:01 -0700 | [diff] [blame] | 142 | |
Don Garrett | 4003636 | 2014-12-08 15:52:44 -0800 | [diff] [blame] | 143 | # Do the updating. |
Dan Shi | 57d4c73 | 2015-01-22 18:38:50 -0800 | [diff] [blame] | 144 | for server, status in server_status: |
| 145 | if status == 'backup': |
| 146 | extra_args = ['--skip-service-status'] |
| 147 | else: |
| 148 | extra_args = [] |
| 149 | |
Don Garrett | 4003636 | 2014-12-08 15:52:44 -0800 | [diff] [blame] | 150 | cmd = ('/usr/local/autotest/contrib/deploy_production_local.py ' + |
Dan Shi | 57d4c73 | 2015-01-22 18:38:50 -0800 | [diff] [blame] | 151 | ' '.join(options.args + extra_args)) |
Don Garrett | 4003636 | 2014-12-08 15:52:44 -0800 | [diff] [blame] | 152 | print('%s: %s' % (server, cmd)) |
| 153 | if not options.dryrun: |
| 154 | try: |
Don Garrett | 699b4b3 | 2014-12-11 13:10:15 -0800 | [diff] [blame] | 155 | out = infra.execute_command(server, cmd) |
| 156 | print(out) |
Don Garrett | 4003636 | 2014-12-08 15:52:44 -0800 | [diff] [blame] | 157 | print('Success') |
| 158 | print() |
| 159 | except subprocess.CalledProcessError as e: |
| 160 | print('Error:') |
| 161 | print(e.output) |
| 162 | if not options.cont: |
| 163 | return 1 |
J. Richard Barnette | f533b18 | 2014-09-04 18:24:42 -0700 | [diff] [blame] | 164 | |
| 165 | |
| 166 | if __name__ == '__main__': |
Don Garrett | 4003636 | 2014-12-08 15:52:44 -0800 | [diff] [blame] | 167 | sys.exit(main(sys.argv[1:])) |