blob: 421c3702d675716bc1362e36b6f0671fafd68fcc [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',
Shuqian Zhao9febd452017-01-31 15:36:40 -0800117 '-m%s' % host, '%s' % cmd],
118 stderr=subprocess.STDOUT)
Alex Miller885543d2014-02-04 20:51:21 -0800119 return out
120 return runner
121
122
Prashanth Balasubramanian3a9b9a12014-11-13 20:05:10 -0800123def execute_command(host, cmd, **kwargs):
Alex Miller885543d2014-02-04 20:51:21 -0800124 """
125 Executes a command on the host `host`. This an optimization that if
126 we're already chromeos-test, we can just ssh to the machine in question.
127 Or if we're local, we don't have to ssh at all.
128
129 @param host: The hostname to execute the command on.
130 @param cmd: The command to run. Special shell syntax (such as pipes)
131 is allowed.
Prashanth Balasubramanian3a9b9a12014-11-13 20:05:10 -0800132 @param kwargs: Key word arguments for the runner functions.
Alex Miller885543d2014-02-04 20:51:21 -0800133 @returns: The output of the command.
134 """
Dan Shif6c65bd2014-08-29 16:15:07 -0700135 if utils.is_localhost(host):
Alex Miller885543d2014-02-04 20:51:21 -0800136 runner = local_runner
137 elif getpass.getuser() == 'chromeos-test':
138 runner = host_object_runner(host)
139 else:
Fang Deng51240a42015-12-17 14:52:52 -0800140 runner = googlesh_runner(host)
Alex Miller885543d2014-02-04 20:51:21 -0800141
Prashanth Balasubramanian3a9b9a12014-11-13 20:05:10 -0800142 return runner(cmd, **kwargs)
Alex Miller885543d2014-02-04 20:51:21 -0800143
144
145def _csv_to_list(s):
146 """
147 Converts a list seperated by commas into a list of strings.
148
149 >>> _csv_to_list('')
150 []
151 >>> _csv_to_list('one')
152 ['one']
153 >>> _csv_to_list('one, two,three')
154 ['one', 'two', 'three']
155 """
156 return [x.strip() for x in s.split(',') if x]
157
158
159# The goal with these functions is to give you a list of hosts that are valid
160# arguments to ssh. Note that this only really works since our instances use
161# names that are findable by our default /etc/resolv.conf `search` domains,
162# because all of our instances have names under .corp
163def sam_servers():
164 """
165 Generate a list of all scheduler/afe instances of autotest.
166
167 Note that we don't include the mysql database host if the database is split
168 from the rest of the system.
169 """
170 sams_config = global_config.global_config.get_config_value(
Alex Millerb0b2d252014-06-25 17:17:01 -0700171 'CROS', 'sam_instances', default='')
Alex Miller885543d2014-02-04 20:51:21 -0800172 sams = _csv_to_list(sams_config)
Alex Millerb0b2d252014-06-25 17:17:01 -0700173 return set(sams)
174
175
176def extra_servers():
177 """
178 Servers that have an autotest checkout in /usr/local/autotest, but aren't
179 in any other list.
180
181 @returns: A set of hosts.
182 """
183 servers = global_config.global_config.get_config_value(
184 'CROS', 'extra_servers', default='')
185 return set(_csv_to_list(servers))
186
187
188def test_instance():
189 """
190 A server that is set up to run tests of the autotest infrastructure.
191
192 @returns: A hostname
193 """
194 server = global_config.global_config.get_config_value(
195 'CROS', 'test_instance', default='')
196 return server
Alex Miller885543d2014-02-04 20:51:21 -0800197
198
199# The most reliable way to pull information about the state of the lab is to
200# look at the global/shadow config on each server. The best way to do this is
201# via the global_config module. Therefore, we invoke python on the remote end
202# to call global_config to get whatever values we want.
203_VALUE_FROM_CONFIG = '''
204cd /usr/local/autotest
205python -c "
206import common
207from autotest_lib.client.common_lib import global_config
208print global_config.global_config.get_config_value(
209 '%s', '%s', default='')
210"
211'''
212# There's possibly cheaper ways to do some of this, for example, we could scrape
213# instance:13467 for the list of drones, but this way you can get the list of
214# drones that is what should/will be running, and not what the scheduler thinks
215# is running. (It could have kicked one out, or we could be bringing a new one
216# into rotation.) So scraping the config on remote servers, while slow, gives
217# us consistent logical results.
218
219
220def _scrape_from_instances(section, key):
221 sams = sam_servers()
222 all_servers = set()
223 for sam in sams:
224 servers_csv = execute_command(sam, _VALUE_FROM_CONFIG % (section, key))
225 servers = _csv_to_list(servers_csv)
226 for server in servers:
227 if server == 'localhost':
228 all_servers.add(sam)
229 else:
230 all_servers.add(server)
231 return all_servers
232
233
234def database_servers():
235 """
236 Generate a list of all database servers running for instances of autotest.
237
238 @returns: An iterable of all hosts.
239 """
240 return _scrape_from_instances('AUTOTEST_WEB', 'host')
241
242
243def drone_servers():
244 """
245 Generate a list of all drones used by all instances of autotest in
246 production.
247
Jakob Juelich0a331872014-09-25 14:45:43 -0700248 @returns: An iterable of drone servers.
Alex Miller885543d2014-02-04 20:51:21 -0800249 """
250 return _scrape_from_instances('SCHEDULER', 'drones')
251
252
253def devserver_servers():
254 """
255 Generate a list of all devservers.
256
257 @returns: An iterable of all hosts.
258 """
259 zone = global_config.global_config.get_config_value(
Hung-ying Tyancbdd1982014-09-03 16:54:08 +0800260 'CLIENT', 'dns_zone')
Alex Miller885543d2014-02-04 20:51:21 -0800261 servers = _scrape_from_instances('CROS', 'dev_server_hosts')
262 # The default text we get back here isn't something you can ssh into unless
263 # you've set up your /etc/resolve.conf to automatically try .cros, so we
264 # append the zone to try and make this more in line with everything else.
265 return set([server+'.'+zone for server in servers])
Jakob Juelich0a331872014-09-25 14:45:43 -0700266
267
268def shard_servers():
269 """
270 Generate a list of all shard servers.
271
272 @returns: An iterable of all shard servers.
273 """
274 shard_hostnames = set()
275 sams = sam_servers()
276 for sam in sams:
277 afe = frontend_wrappers.RetryingAFE(server=sam)
278 shards = afe.run('get_shards')
279 for shard in shards:
280 shard_hostnames.add(shard['hostname'])
281
282 return list(shard_hostnames)