blob: 4fb8ffb0c492257b044538aaaf559c520617d11e [file] [log] [blame]
Alex Miller885543d2014-02-04 20:51:21 -08001# Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
Prashanth Balasubramanian3a9b9a12014-11-13 20:05:10 -08005import contextlib
Alex Miller885543d2014-02-04 20:51:21 -08006import getpass
Alex Miller885543d2014-02-04 20:51:21 -08007import subprocess
Prashanth Balasubramanian3a9b9a12014-11-13 20:05:10 -08008import os
Alex Miller885543d2014-02-04 20:51:21 -08009
10import common
11from autotest_lib.server.hosts import ssh_host
12from autotest_lib.client.common_lib import error
13from autotest_lib.client.common_lib import global_config
Dan Shif6c65bd2014-08-29 16:15:07 -070014from autotest_lib.client.common_lib import utils
Jakob Juelich0a331872014-09-25 14:45:43 -070015from autotest_lib.server.cros.dynamic_suite import frontend_wrappers
Alex Miller885543d2014-02-04 20:51:21 -080016
17
Prashanth Balasubramanian3a9b9a12014-11-13 20:05:10 -080018@contextlib.contextmanager
19def chdir(dirname=None):
20 """A context manager to help change directories.
21
22 Will chdir into the provided dirname for the lifetime of the context and
23 return to cwd thereafter.
24
25 @param dirname: The dirname to chdir into.
26 """
27 curdir = os.getcwd()
28 try:
29 if dirname is not None:
30 os.chdir(dirname)
31 yield
32 finally:
33 os.chdir(curdir)
34
35
36def local_runner(cmd, stream_output=False):
Alex Miller885543d2014-02-04 20:51:21 -080037 """
38 Runs a command on the local system as the current user.
39
40 @param cmd: The command to run.
Prashanth Balasubramanian3a9b9a12014-11-13 20:05:10 -080041 @param stream_output: If True, streams the stdout of the process.
42
Shuqian Zhaoa482c4a2016-11-21 18:49:41 -080043 @returns: The output of cmd, will be stdout and stderr.
Alex Miller885543d2014-02-04 20:51:21 -080044 @raises CalledProcessError: If there was a non-0 return code.
45 """
Shuqian Zhaoa482c4a2016-11-21 18:49:41 -080046 print 'Running command: %s' % cmd
Shuqian Zhaoae2d0782016-11-15 16:58:47 -080047 proc = subprocess.Popen(
Shuqian Zhaoa482c4a2016-11-21 18:49:41 -080048 cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
49 if stream_output:
50 output = ''
51 for newline in iter(proc.stdout.readline, ''):
52 output += newline
53 print newline.rstrip(os.linesep)
54 else:
55 output = proc.communicate()[0]
56
57 return_code = proc.wait()
58 if return_code !=0:
59 print "ERROR: '%s' failed with error:\n%s" % (cmd, output)
60 raise subprocess.CalledProcessError(return_code, cmd, output[:1024])
61 return output
Alex Miller885543d2014-02-04 20:51:21 -080062
63
64_host_objects = {}
65
Prashanth Balasubramanian3a9b9a12014-11-13 20:05:10 -080066def host_object_runner(host, **kwargs):
Alex Miller885543d2014-02-04 20:51:21 -080067 """
68 Returns a function that returns the output of running a command via a host
69 object.
70
71 @param host: The host to run a command on.
72 @returns: A function that can invoke a command remotely.
73 """
74 try:
75 host_object = _host_objects[host]
76 except KeyError:
Alex Miller792e2072014-02-18 21:00:20 -080077 username = global_config.global_config.get_config_value(
78 'CROS', 'infrastructure_user')
79 host_object = ssh_host.SSHHost(host, user=username)
Alex Miller885543d2014-02-04 20:51:21 -080080 _host_objects[host] = host_object
81
82 def runner(cmd):
83 """
84 Runs a command via a host object on the enclosed host. Translates
85 host.run errors to the subprocess equivalent to expose a common API.
86
87 @param cmd: The command to run.
88 @returns: The output of cmd.
89 @raises CalledProcessError: If there was a non-0 return code.
90 """
91 try:
Alex Miller792e2072014-02-18 21:00:20 -080092 return host_object.run(cmd).stdout
Alex Miller885543d2014-02-04 20:51:21 -080093 except error.AutotestHostRunError as e:
94 exit_status = e.result_obj.exit_status
95 command = e.result_obj.command
96 raise subprocess.CalledProcessError(exit_status, command)
97 return runner
98
99
Fang Deng51240a42015-12-17 14:52:52 -0800100def googlesh_runner(host, **kwargs):
Alex Miller885543d2014-02-04 20:51:21 -0800101 """
102 Returns a function that return the output of running a command via shelling
Fang Deng51240a42015-12-17 14:52:52 -0800103 out to `googlesh`.
Alex Miller885543d2014-02-04 20:51:21 -0800104
105 @param host: The host to run a command on
106 @returns: A function that can invoke a command remotely.
107 """
108 def runner(cmd):
109 """
Fang Deng51240a42015-12-17 14:52:52 -0800110 Runs a command via googlesh on the enclosed host.
Alex Miller885543d2014-02-04 20:51:21 -0800111
112 @param cmd: The command to run.
113 @returns: The output of cmd.
114 @raises CalledProcessError: If there was a non-0 return code.
115 """
Fang Deng51240a42015-12-17 14:52:52 -0800116 out = subprocess.check_output(['googlesh', '-s', '-uchromeos-test',
117 '-m%s' % host, '%s' % cmd])
Alex Miller885543d2014-02-04 20:51:21 -0800118 return out
119 return runner
120
121
Prashanth Balasubramanian3a9b9a12014-11-13 20:05:10 -0800122def execute_command(host, cmd, **kwargs):
Alex Miller885543d2014-02-04 20:51:21 -0800123 """
124 Executes a command on the host `host`. This an optimization that if
125 we're already chromeos-test, we can just ssh to the machine in question.
126 Or if we're local, we don't have to ssh at all.
127
128 @param host: The hostname to execute the command on.
129 @param cmd: The command to run. Special shell syntax (such as pipes)
130 is allowed.
Prashanth Balasubramanian3a9b9a12014-11-13 20:05:10 -0800131 @param kwargs: Key word arguments for the runner functions.
Alex Miller885543d2014-02-04 20:51:21 -0800132 @returns: The output of the command.
133 """
Dan Shif6c65bd2014-08-29 16:15:07 -0700134 if utils.is_localhost(host):
Alex Miller885543d2014-02-04 20:51:21 -0800135 runner = local_runner
136 elif getpass.getuser() == 'chromeos-test':
137 runner = host_object_runner(host)
138 else:
Fang Deng51240a42015-12-17 14:52:52 -0800139 runner = googlesh_runner(host)
Alex Miller885543d2014-02-04 20:51:21 -0800140
Prashanth Balasubramanian3a9b9a12014-11-13 20:05:10 -0800141 return runner(cmd, **kwargs)
Alex Miller885543d2014-02-04 20:51:21 -0800142
143
144def _csv_to_list(s):
145 """
146 Converts a list seperated by commas into a list of strings.
147
148 >>> _csv_to_list('')
149 []
150 >>> _csv_to_list('one')
151 ['one']
152 >>> _csv_to_list('one, two,three')
153 ['one', 'two', 'three']
154 """
155 return [x.strip() for x in s.split(',') if x]
156
157
158# The goal with these functions is to give you a list of hosts that are valid
159# arguments to ssh. Note that this only really works since our instances use
160# names that are findable by our default /etc/resolv.conf `search` domains,
161# because all of our instances have names under .corp
162def sam_servers():
163 """
164 Generate a list of all scheduler/afe instances of autotest.
165
166 Note that we don't include the mysql database host if the database is split
167 from the rest of the system.
168 """
169 sams_config = global_config.global_config.get_config_value(
Alex Millerb0b2d252014-06-25 17:17:01 -0700170 'CROS', 'sam_instances', default='')
Alex Miller885543d2014-02-04 20:51:21 -0800171 sams = _csv_to_list(sams_config)
Alex Millerb0b2d252014-06-25 17:17:01 -0700172 return set(sams)
173
174
175def extra_servers():
176 """
177 Servers that have an autotest checkout in /usr/local/autotest, but aren't
178 in any other list.
179
180 @returns: A set of hosts.
181 """
182 servers = global_config.global_config.get_config_value(
183 'CROS', 'extra_servers', default='')
184 return set(_csv_to_list(servers))
185
186
187def test_instance():
188 """
189 A server that is set up to run tests of the autotest infrastructure.
190
191 @returns: A hostname
192 """
193 server = global_config.global_config.get_config_value(
194 'CROS', 'test_instance', default='')
195 return server
Alex Miller885543d2014-02-04 20:51:21 -0800196
197
198# The most reliable way to pull information about the state of the lab is to
199# look at the global/shadow config on each server. The best way to do this is
200# via the global_config module. Therefore, we invoke python on the remote end
201# to call global_config to get whatever values we want.
202_VALUE_FROM_CONFIG = '''
203cd /usr/local/autotest
204python -c "
205import common
206from autotest_lib.client.common_lib import global_config
207print global_config.global_config.get_config_value(
208 '%s', '%s', default='')
209"
210'''
211# There's possibly cheaper ways to do some of this, for example, we could scrape
212# instance:13467 for the list of drones, but this way you can get the list of
213# drones that is what should/will be running, and not what the scheduler thinks
214# is running. (It could have kicked one out, or we could be bringing a new one
215# into rotation.) So scraping the config on remote servers, while slow, gives
216# us consistent logical results.
217
218
219def _scrape_from_instances(section, key):
220 sams = sam_servers()
221 all_servers = set()
222 for sam in sams:
223 servers_csv = execute_command(sam, _VALUE_FROM_CONFIG % (section, key))
224 servers = _csv_to_list(servers_csv)
225 for server in servers:
226 if server == 'localhost':
227 all_servers.add(sam)
228 else:
229 all_servers.add(server)
230 return all_servers
231
232
233def database_servers():
234 """
235 Generate a list of all database servers running for instances of autotest.
236
237 @returns: An iterable of all hosts.
238 """
239 return _scrape_from_instances('AUTOTEST_WEB', 'host')
240
241
242def drone_servers():
243 """
244 Generate a list of all drones used by all instances of autotest in
245 production.
246
Jakob Juelich0a331872014-09-25 14:45:43 -0700247 @returns: An iterable of drone servers.
Alex Miller885543d2014-02-04 20:51:21 -0800248 """
249 return _scrape_from_instances('SCHEDULER', 'drones')
250
251
252def devserver_servers():
253 """
254 Generate a list of all devservers.
255
256 @returns: An iterable of all hosts.
257 """
258 zone = global_config.global_config.get_config_value(
Hung-ying Tyancbdd1982014-09-03 16:54:08 +0800259 'CLIENT', 'dns_zone')
Alex Miller885543d2014-02-04 20:51:21 -0800260 servers = _scrape_from_instances('CROS', 'dev_server_hosts')
261 # The default text we get back here isn't something you can ssh into unless
262 # you've set up your /etc/resolve.conf to automatically try .cros, so we
263 # append the zone to try and make this more in line with everything else.
264 return set([server+'.'+zone for server in servers])
Jakob Juelich0a331872014-09-25 14:45:43 -0700265
266
267def shard_servers():
268 """
269 Generate a list of all shard servers.
270
271 @returns: An iterable of all shard servers.
272 """
273 shard_hostnames = set()
274 sams = sam_servers()
275 for sam in sams:
276 afe = frontend_wrappers.RetryingAFE(server=sam)
277 shards = afe.run('get_shards')
278 for shard in shards:
279 shard_hostnames.add(shard['hostname'])
280
281 return list(shard_hostnames)