blob: ed133369e20d67efe6057abef06f27bd7a074987 [file] [log] [blame]
Alex Millerb0b2d252014-06-25 17:17:01 -07001#!/usr/bin/python
2
Don Garrett40036362014-12-08 15:52:44 -08003from __future__ import print_function
4
5import argparse
J. Richard Barnette868cf642014-07-21 16:34:38 -07006import subprocess
7import sys
Alex Millerb0b2d252014-06-25 17:17:01 -07008
Don Garrett40036362014-12-08 15:52:44 -08009import common
Don Garrett50713462015-01-07 18:04:05 -080010from autotest_lib.server import frontend
Alex Millerb0b2d252014-06-25 17:17:01 -070011from autotest_lib.site_utils.lib import infra
12
Alex Millerb0b2d252014-06-25 17:17:01 -070013
Dan Shi57d4c732015-01-22 18:38:50 -080014def discover_servers(afe, server_filter=set()):
Don Garrett40036362014-12-08 15:52:44 -080015 """Discover the in-production servers to update.
Alex Millerb0b2d252014-06-25 17:17:01 -070016
Don Garretteecbc132015-01-08 17:26:20 -080017 @param afe: Server to contact with RPC requests.
Dan Shi57d4c732015-01-22 18:38:50 -080018 @param server_filter: A set of servers to get status for.
Don Garretteecbc132015-01-08 17:26:20 -080019
Dan Shi57d4c732015-01-22 18:38:50 -080020 @returns: A list of tuple of (server_name, server_status), the list in
21 sorted by the order to be updated.
Don Garrett40036362014-12-08 15:52:44 -080022 """
Don Garrett50713462015-01-07 18:04:05 -080023 # Example server details....
24 # {
25 # 'hostname': 'server1',
26 # 'status': 'backup',
27 # 'roles': ['drone', 'scheduler'],
28 # 'attributes': {'max_processes': 300}
29 # }
Don Garretteecbc132015-01-08 17:26:20 -080030 rpc = frontend.AFE(server=afe)
Don Garrett50713462015-01-07 18:04:05 -080031 servers = rpc.run('get_servers')
Don Garrett40036362014-12-08 15:52:44 -080032
Dan Shi57d4c732015-01-22 18:38:50 -080033 # 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 Garrett40036362014-12-08 15:52:44 -080038
Dan Shia1797382015-05-28 10:59:52 -070039 # 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 Garrett50713462015-01-07 18:04:05 -080042
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 Shi57d4c732015-01-22 18:38:50 -080057 # 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 Millerb0b2d252014-06-25 17:17:01 -070065
J. Richard Barnettef533b182014-09-04 18:24:42 -070066
Don Garrett40036362014-12-08 15:52:44 -080067def 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 Garrett3f2b6602014-12-16 18:19:16 -080075 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 Garrett40036362014-12-08 15:52:44 -080090 parser.add_argument('--continue', action='store_true', dest='cont',
Don Garretteecbc132015-01-08 17:26:20 -080091 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 Garrett40036362014-12-08 15:52:44 -080094 parser.add_argument('--dryrun', action='store_true',
Don Garretteecbc132015-01-08 17:26:20 -080095 help='Don\'t actually run remote commands.')
Don Garrett40036362014-12-08 15:52:44 -080096 parser.add_argument('args', nargs=argparse.REMAINDER,
Don Garretteecbc132015-01-08 17:26:20 -080097 help=('<server>, <server> ... -- <remote_arg>, <remote_arg> ...'))
Don Garrett40036362014-12-08 15:52:44 -080098
99 results = parser.parse_args(args)
100
Don Garrett3f2b6602014-12-16 18:19:16 -0800101 # 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 Garrett40036362014-12-08 15:52:44 -0800121
122 return results
J. Richard Barnettef533b182014-09-04 18:24:42 -0700123
124
Don Garrett40036362014-12-08 15:52:44 -0800125def main(args):
126 """Main routine that drives all the real work.
Alex Millerb0b2d252014-06-25 17:17:01 -0700127
Don Garrett40036362014-12-08 15:52:44 -0800128 @param args: The command line arguments to parse. (usually sys.argv[1:])
J. Richard Barnette868cf642014-07-21 16:34:38 -0700129
Don Garrett40036362014-12-08 15:52:44 -0800130 @returns The system exit code.
131 """
132 options = parse_arguments(args)
Alex Millerb0b2d252014-06-25 17:17:01 -0700133
Dan Shi57d4c732015-01-22 18:38:50 -0800134 print('Retrieving server status...')
135 server_status = discover_servers(options.afe, set(options.servers or []))
Alex Millerb0b2d252014-06-25 17:17:01 -0700136
Don Garrett40036362014-12-08 15:52:44 -0800137 # Display what we plan to update.
138 print('Will update (in this order):')
Dan Shi57d4c732015-01-22 18:38:50 -0800139 for server, status in server_status:
140 print('\t%-36s:\t%s' % (server, status))
Don Garrett40036362014-12-08 15:52:44 -0800141 print()
Alex Millerb0b2d252014-06-25 17:17:01 -0700142
Don Garrett40036362014-12-08 15:52:44 -0800143 # Do the updating.
Dan Shi57d4c732015-01-22 18:38:50 -0800144 for server, status in server_status:
145 if status == 'backup':
146 extra_args = ['--skip-service-status']
147 else:
148 extra_args = []
149
Don Garrett40036362014-12-08 15:52:44 -0800150 cmd = ('/usr/local/autotest/contrib/deploy_production_local.py ' +
Dan Shi57d4c732015-01-22 18:38:50 -0800151 ' '.join(options.args + extra_args))
Don Garrett40036362014-12-08 15:52:44 -0800152 print('%s: %s' % (server, cmd))
153 if not options.dryrun:
154 try:
Don Garrett699b4b32014-12-11 13:10:15 -0800155 out = infra.execute_command(server, cmd)
156 print(out)
Don Garrett40036362014-12-08 15:52:44 -0800157 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 Barnettef533b182014-09-04 18:24:42 -0700164
165
166if __name__ == '__main__':
Don Garrett40036362014-12-08 15:52:44 -0800167 sys.exit(main(sys.argv[1:]))