blob: 68a4e1f3aa219923264bc328853ab7d46ab77d1c [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
5import getpass
Alex Miller885543d2014-02-04 20:51:21 -08006import subprocess
7
8import common
9from autotest_lib.server.hosts import ssh_host
10from autotest_lib.client.common_lib import error
11from autotest_lib.client.common_lib import global_config
Dan Shif6c65bd2014-08-29 16:15:07 -070012from autotest_lib.client.common_lib import utils
Jakob Juelich0a331872014-09-25 14:45:43 -070013from autotest_lib.server.cros.dynamic_suite import frontend_wrappers
Alex Miller885543d2014-02-04 20:51:21 -080014
15
16def local_runner(cmd):
17 """
18 Runs a command on the local system as the current user.
19
20 @param cmd: The command to run.
21 @returns: The output of cmd.
22 @raises CalledProcessError: If there was a non-0 return code.
23 """
24 return subprocess.check_output(cmd, shell=True)
25
26
27_host_objects = {}
28
29def host_object_runner(host):
30 """
31 Returns a function that returns the output of running a command via a host
32 object.
33
34 @param host: The host to run a command on.
35 @returns: A function that can invoke a command remotely.
36 """
37 try:
38 host_object = _host_objects[host]
39 except KeyError:
Alex Miller792e2072014-02-18 21:00:20 -080040 username = global_config.global_config.get_config_value(
41 'CROS', 'infrastructure_user')
42 host_object = ssh_host.SSHHost(host, user=username)
Alex Miller885543d2014-02-04 20:51:21 -080043 _host_objects[host] = host_object
44
45 def runner(cmd):
46 """
47 Runs a command via a host object on the enclosed host. Translates
48 host.run errors to the subprocess equivalent to expose a common API.
49
50 @param cmd: The command to run.
51 @returns: The output of cmd.
52 @raises CalledProcessError: If there was a non-0 return code.
53 """
54 try:
Alex Miller792e2072014-02-18 21:00:20 -080055 return host_object.run(cmd).stdout
Alex Miller885543d2014-02-04 20:51:21 -080056 except error.AutotestHostRunError as e:
57 exit_status = e.result_obj.exit_status
58 command = e.result_obj.command
59 raise subprocess.CalledProcessError(exit_status, command)
60 return runner
61
62
63def become_runner(host):
64 """
65 Returns a function that return the output of running a command via shelling
66 out to `become`.
67
68 @param host: The host to run a command on
69 @returns: A function that can invoke a command remotely.
70 """
71 def runner(cmd):
72 """
73 Runs a command via become on the enclosed host.
74
75 @param cmd: The command to run.
76 @returns: The output of cmd.
77 @raises CalledProcessError: If there was a non-0 return code.
78 """
79 out = subprocess.check_output(['become', 'chromeos-test@%s' % host,
80 '--', cmd])
81 return out
82 return runner
83
84
85def execute_command(host, cmd):
86 """
87 Executes a command on the host `host`. This an optimization that if
88 we're already chromeos-test, we can just ssh to the machine in question.
89 Or if we're local, we don't have to ssh at all.
90
91 @param host: The hostname to execute the command on.
92 @param cmd: The command to run. Special shell syntax (such as pipes)
93 is allowed.
94 @returns: The output of the command.
95 """
Dan Shif6c65bd2014-08-29 16:15:07 -070096 if utils.is_localhost(host):
Alex Miller885543d2014-02-04 20:51:21 -080097 runner = local_runner
98 elif getpass.getuser() == 'chromeos-test':
99 runner = host_object_runner(host)
100 else:
101 runner = become_runner(host)
102
103 return runner(cmd)
104
105
106def _csv_to_list(s):
107 """
108 Converts a list seperated by commas into a list of strings.
109
110 >>> _csv_to_list('')
111 []
112 >>> _csv_to_list('one')
113 ['one']
114 >>> _csv_to_list('one, two,three')
115 ['one', 'two', 'three']
116 """
117 return [x.strip() for x in s.split(',') if x]
118
119
120# The goal with these functions is to give you a list of hosts that are valid
121# arguments to ssh. Note that this only really works since our instances use
122# names that are findable by our default /etc/resolv.conf `search` domains,
123# because all of our instances have names under .corp
124def sam_servers():
125 """
126 Generate a list of all scheduler/afe instances of autotest.
127
128 Note that we don't include the mysql database host if the database is split
129 from the rest of the system.
130 """
131 sams_config = global_config.global_config.get_config_value(
Alex Millerb0b2d252014-06-25 17:17:01 -0700132 'CROS', 'sam_instances', default='')
Alex Miller885543d2014-02-04 20:51:21 -0800133 sams = _csv_to_list(sams_config)
Alex Millerb0b2d252014-06-25 17:17:01 -0700134 return set(sams)
135
136
137def extra_servers():
138 """
139 Servers that have an autotest checkout in /usr/local/autotest, but aren't
140 in any other list.
141
142 @returns: A set of hosts.
143 """
144 servers = global_config.global_config.get_config_value(
145 'CROS', 'extra_servers', default='')
146 return set(_csv_to_list(servers))
147
148
149def test_instance():
150 """
151 A server that is set up to run tests of the autotest infrastructure.
152
153 @returns: A hostname
154 """
155 server = global_config.global_config.get_config_value(
156 'CROS', 'test_instance', default='')
157 return server
Alex Miller885543d2014-02-04 20:51:21 -0800158
159
160# The most reliable way to pull information about the state of the lab is to
161# look at the global/shadow config on each server. The best way to do this is
162# via the global_config module. Therefore, we invoke python on the remote end
163# to call global_config to get whatever values we want.
164_VALUE_FROM_CONFIG = '''
165cd /usr/local/autotest
166python -c "
167import common
168from autotest_lib.client.common_lib import global_config
169print global_config.global_config.get_config_value(
170 '%s', '%s', default='')
171"
172'''
173# There's possibly cheaper ways to do some of this, for example, we could scrape
174# instance:13467 for the list of drones, but this way you can get the list of
175# drones that is what should/will be running, and not what the scheduler thinks
176# is running. (It could have kicked one out, or we could be bringing a new one
177# into rotation.) So scraping the config on remote servers, while slow, gives
178# us consistent logical results.
179
180
181def _scrape_from_instances(section, key):
182 sams = sam_servers()
183 all_servers = set()
184 for sam in sams:
185 servers_csv = execute_command(sam, _VALUE_FROM_CONFIG % (section, key))
186 servers = _csv_to_list(servers_csv)
187 for server in servers:
188 if server == 'localhost':
189 all_servers.add(sam)
190 else:
191 all_servers.add(server)
192 return all_servers
193
194
195def database_servers():
196 """
197 Generate a list of all database servers running for instances of autotest.
198
199 @returns: An iterable of all hosts.
200 """
201 return _scrape_from_instances('AUTOTEST_WEB', 'host')
202
203
204def drone_servers():
205 """
206 Generate a list of all drones used by all instances of autotest in
207 production.
208
Jakob Juelich0a331872014-09-25 14:45:43 -0700209 @returns: An iterable of drone servers.
Alex Miller885543d2014-02-04 20:51:21 -0800210 """
211 return _scrape_from_instances('SCHEDULER', 'drones')
212
213
214def devserver_servers():
215 """
216 Generate a list of all devservers.
217
218 @returns: An iterable of all hosts.
219 """
220 zone = global_config.global_config.get_config_value(
Hung-ying Tyancbdd1982014-09-03 16:54:08 +0800221 'CLIENT', 'dns_zone')
Alex Miller885543d2014-02-04 20:51:21 -0800222 servers = _scrape_from_instances('CROS', 'dev_server_hosts')
223 # The default text we get back here isn't something you can ssh into unless
224 # you've set up your /etc/resolve.conf to automatically try .cros, so we
225 # append the zone to try and make this more in line with everything else.
226 return set([server+'.'+zone for server in servers])
Jakob Juelich0a331872014-09-25 14:45:43 -0700227
228
229def shard_servers():
230 """
231 Generate a list of all shard servers.
232
233 @returns: An iterable of all shard servers.
234 """
235 shard_hostnames = set()
236 sams = sam_servers()
237 for sam in sams:
238 afe = frontend_wrappers.RetryingAFE(server=sam)
239 shards = afe.run('get_shards')
240 for shard in shards:
241 shard_hostnames.add(shard['hostname'])
242
243 return list(shard_hostnames)