blob: 4c6fb695c96bb2066c50ff22b3f98e83bc574d53 [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
Alex Miller885543d2014-02-04 20:51:21 -080043 @returns: The output of cmd.
44 @raises CalledProcessError: If there was a non-0 return code.
45 """
Prashanth Balasubramanian3a9b9a12014-11-13 20:05:10 -080046 if not stream_output:
47 return subprocess.check_output(cmd, shell=True)
Shuqian Zhaoae2d0782016-11-15 16:58:47 -080048
49 proc = subprocess.Popen(
50 cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Prashanth Balasubramanian3a9b9a12014-11-13 20:05:10 -080051 while proc.poll() is None:
52 print proc.stdout.readline().rstrip('\n')
Shuqian Zhaoae2d0782016-11-15 16:58:47 -080053 if proc.returncode !=0:
54 raise subprocess.CalledProcessError(proc.returncode, cmd)
Alex Miller885543d2014-02-04 20:51:21 -080055
56
57_host_objects = {}
58
Prashanth Balasubramanian3a9b9a12014-11-13 20:05:10 -080059def host_object_runner(host, **kwargs):
Alex Miller885543d2014-02-04 20:51:21 -080060 """
61 Returns a function that returns the output of running a command via a host
62 object.
63
64 @param host: The host to run a command on.
65 @returns: A function that can invoke a command remotely.
66 """
67 try:
68 host_object = _host_objects[host]
69 except KeyError:
Alex Miller792e2072014-02-18 21:00:20 -080070 username = global_config.global_config.get_config_value(
71 'CROS', 'infrastructure_user')
72 host_object = ssh_host.SSHHost(host, user=username)
Alex Miller885543d2014-02-04 20:51:21 -080073 _host_objects[host] = host_object
74
75 def runner(cmd):
76 """
77 Runs a command via a host object on the enclosed host. Translates
78 host.run errors to the subprocess equivalent to expose a common API.
79
80 @param cmd: The command to run.
81 @returns: The output of cmd.
82 @raises CalledProcessError: If there was a non-0 return code.
83 """
84 try:
Alex Miller792e2072014-02-18 21:00:20 -080085 return host_object.run(cmd).stdout
Alex Miller885543d2014-02-04 20:51:21 -080086 except error.AutotestHostRunError as e:
87 exit_status = e.result_obj.exit_status
88 command = e.result_obj.command
89 raise subprocess.CalledProcessError(exit_status, command)
90 return runner
91
92
Fang Deng51240a42015-12-17 14:52:52 -080093def googlesh_runner(host, **kwargs):
Alex Miller885543d2014-02-04 20:51:21 -080094 """
95 Returns a function that return the output of running a command via shelling
Fang Deng51240a42015-12-17 14:52:52 -080096 out to `googlesh`.
Alex Miller885543d2014-02-04 20:51:21 -080097
98 @param host: The host to run a command on
99 @returns: A function that can invoke a command remotely.
100 """
101 def runner(cmd):
102 """
Fang Deng51240a42015-12-17 14:52:52 -0800103 Runs a command via googlesh on the enclosed host.
Alex Miller885543d2014-02-04 20:51:21 -0800104
105 @param cmd: The command to run.
106 @returns: The output of cmd.
107 @raises CalledProcessError: If there was a non-0 return code.
108 """
Fang Deng51240a42015-12-17 14:52:52 -0800109 out = subprocess.check_output(['googlesh', '-s', '-uchromeos-test',
110 '-m%s' % host, '%s' % cmd])
Alex Miller885543d2014-02-04 20:51:21 -0800111 return out
112 return runner
113
114
Prashanth Balasubramanian3a9b9a12014-11-13 20:05:10 -0800115def execute_command(host, cmd, **kwargs):
Alex Miller885543d2014-02-04 20:51:21 -0800116 """
117 Executes a command on the host `host`. This an optimization that if
118 we're already chromeos-test, we can just ssh to the machine in question.
119 Or if we're local, we don't have to ssh at all.
120
121 @param host: The hostname to execute the command on.
122 @param cmd: The command to run. Special shell syntax (such as pipes)
123 is allowed.
Prashanth Balasubramanian3a9b9a12014-11-13 20:05:10 -0800124 @param kwargs: Key word arguments for the runner functions.
Alex Miller885543d2014-02-04 20:51:21 -0800125 @returns: The output of the command.
126 """
Dan Shif6c65bd2014-08-29 16:15:07 -0700127 if utils.is_localhost(host):
Alex Miller885543d2014-02-04 20:51:21 -0800128 runner = local_runner
129 elif getpass.getuser() == 'chromeos-test':
130 runner = host_object_runner(host)
131 else:
Fang Deng51240a42015-12-17 14:52:52 -0800132 runner = googlesh_runner(host)
Alex Miller885543d2014-02-04 20:51:21 -0800133
Prashanth Balasubramanian3a9b9a12014-11-13 20:05:10 -0800134 return runner(cmd, **kwargs)
Alex Miller885543d2014-02-04 20:51:21 -0800135
136
137def _csv_to_list(s):
138 """
139 Converts a list seperated by commas into a list of strings.
140
141 >>> _csv_to_list('')
142 []
143 >>> _csv_to_list('one')
144 ['one']
145 >>> _csv_to_list('one, two,three')
146 ['one', 'two', 'three']
147 """
148 return [x.strip() for x in s.split(',') if x]
149
150
151# The goal with these functions is to give you a list of hosts that are valid
152# arguments to ssh. Note that this only really works since our instances use
153# names that are findable by our default /etc/resolv.conf `search` domains,
154# because all of our instances have names under .corp
155def sam_servers():
156 """
157 Generate a list of all scheduler/afe instances of autotest.
158
159 Note that we don't include the mysql database host if the database is split
160 from the rest of the system.
161 """
162 sams_config = global_config.global_config.get_config_value(
Alex Millerb0b2d252014-06-25 17:17:01 -0700163 'CROS', 'sam_instances', default='')
Alex Miller885543d2014-02-04 20:51:21 -0800164 sams = _csv_to_list(sams_config)
Alex Millerb0b2d252014-06-25 17:17:01 -0700165 return set(sams)
166
167
168def extra_servers():
169 """
170 Servers that have an autotest checkout in /usr/local/autotest, but aren't
171 in any other list.
172
173 @returns: A set of hosts.
174 """
175 servers = global_config.global_config.get_config_value(
176 'CROS', 'extra_servers', default='')
177 return set(_csv_to_list(servers))
178
179
180def test_instance():
181 """
182 A server that is set up to run tests of the autotest infrastructure.
183
184 @returns: A hostname
185 """
186 server = global_config.global_config.get_config_value(
187 'CROS', 'test_instance', default='')
188 return server
Alex Miller885543d2014-02-04 20:51:21 -0800189
190
191# The most reliable way to pull information about the state of the lab is to
192# look at the global/shadow config on each server. The best way to do this is
193# via the global_config module. Therefore, we invoke python on the remote end
194# to call global_config to get whatever values we want.
195_VALUE_FROM_CONFIG = '''
196cd /usr/local/autotest
197python -c "
198import common
199from autotest_lib.client.common_lib import global_config
200print global_config.global_config.get_config_value(
201 '%s', '%s', default='')
202"
203'''
204# There's possibly cheaper ways to do some of this, for example, we could scrape
205# instance:13467 for the list of drones, but this way you can get the list of
206# drones that is what should/will be running, and not what the scheduler thinks
207# is running. (It could have kicked one out, or we could be bringing a new one
208# into rotation.) So scraping the config on remote servers, while slow, gives
209# us consistent logical results.
210
211
212def _scrape_from_instances(section, key):
213 sams = sam_servers()
214 all_servers = set()
215 for sam in sams:
216 servers_csv = execute_command(sam, _VALUE_FROM_CONFIG % (section, key))
217 servers = _csv_to_list(servers_csv)
218 for server in servers:
219 if server == 'localhost':
220 all_servers.add(sam)
221 else:
222 all_servers.add(server)
223 return all_servers
224
225
226def database_servers():
227 """
228 Generate a list of all database servers running for instances of autotest.
229
230 @returns: An iterable of all hosts.
231 """
232 return _scrape_from_instances('AUTOTEST_WEB', 'host')
233
234
235def drone_servers():
236 """
237 Generate a list of all drones used by all instances of autotest in
238 production.
239
Jakob Juelich0a331872014-09-25 14:45:43 -0700240 @returns: An iterable of drone servers.
Alex Miller885543d2014-02-04 20:51:21 -0800241 """
242 return _scrape_from_instances('SCHEDULER', 'drones')
243
244
245def devserver_servers():
246 """
247 Generate a list of all devservers.
248
249 @returns: An iterable of all hosts.
250 """
251 zone = global_config.global_config.get_config_value(
Hung-ying Tyancbdd1982014-09-03 16:54:08 +0800252 'CLIENT', 'dns_zone')
Alex Miller885543d2014-02-04 20:51:21 -0800253 servers = _scrape_from_instances('CROS', 'dev_server_hosts')
254 # The default text we get back here isn't something you can ssh into unless
255 # you've set up your /etc/resolve.conf to automatically try .cros, so we
256 # append the zone to try and make this more in line with everything else.
257 return set([server+'.'+zone for server in servers])
Jakob Juelich0a331872014-09-25 14:45:43 -0700258
259
260def shard_servers():
261 """
262 Generate a list of all shard servers.
263
264 @returns: An iterable of all shard servers.
265 """
266 shard_hostnames = set()
267 sams = sam_servers()
268 for sam in sams:
269 afe = frontend_wrappers.RetryingAFE(server=sam)
270 shards = afe.run('get_shards')
271 for shard in shards:
272 shard_hostnames.add(shard['hostname'])
273
274 return list(shard_hostnames)