blob: dff7e5a131a7194d412c11df5d7777776f59dee2 [file] [log] [blame]
# Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import contextlib
import getpass
import subprocess
import os
import common
from autotest_lib.server.hosts import ssh_host
from autotest_lib.client.common_lib import error
from autotest_lib.client.common_lib import global_config
from autotest_lib.client.common_lib import utils
from autotest_lib.server.cros.dynamic_suite import frontend_wrappers
def chdir(dirname=None):
"""A context manager to help change directories.
Will chdir into the provided dirname for the lifetime of the context and
return to cwd thereafter.
@param dirname: The dirname to chdir into.
curdir = os.getcwd()
if dirname is not None:
def local_runner(cmd, stream_output=False):
Runs a command on the local system as the current user.
@param cmd: The command to run.
@param stream_output: If True, streams the stdout of the process.
@returns: The output of cmd, will be stdout and stderr.
@raises CalledProcessError: If there was a non-0 return code.
print 'Running command: %s' % cmd
proc = subprocess.Popen(
cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
if stream_output:
output = ''
for newline in iter(proc.stdout.readline, ''):
output += newline
print newline.rstrip(os.linesep)
output = proc.communicate()[0]
return_code = proc.wait()
if return_code !=0:
print "ERROR: '%s' failed with error:\n%s" % (cmd, output)
raise subprocess.CalledProcessError(return_code, cmd, output[:1024])
return output
_host_objects = {}
def host_object_runner(host, **kwargs):
Returns a function that returns the output of running a command via a host
@param host: The host to run a command on.
@returns: A function that can invoke a command remotely.
host_object = _host_objects[host]
except KeyError:
username = global_config.global_config.get_config_value(
'CROS', 'infrastructure_user')
host_object = ssh_host.SSHHost(host, user=username)
_host_objects[host] = host_object
def runner(cmd):
Runs a command via a host object on the enclosed host. Translates errors to the subprocess equivalent to expose a common API.
@param cmd: The command to run.
@returns: The output of cmd.
@raises CalledProcessError: If there was a non-0 return code.
except error.AutotestHostRunError as e:
exit_status = e.result_obj.exit_status
command = e.result_obj.command
raise subprocess.CalledProcessError(exit_status, command)
return runner
def googlesh_runner(host, **kwargs):
Returns a function that return the output of running a command via shelling
out to `googlesh`.
@param host: The host to run a command on
@returns: A function that can invoke a command remotely.
def runner(cmd):
Runs a command via googlesh on the enclosed host.
@param cmd: The command to run.
@returns: The output of cmd.
@raises CalledProcessError: If there was a non-0 return code.
out = subprocess.check_output(['googlesh', '-s', '-uchromeos-test',
'-m%s' % host, '%s' % cmd],
return out
return runner
def execute_command(host, cmd, **kwargs):
Executes a command on the host `host`. This an optimization that if
we're already chromeos-test, we can just ssh to the machine in question.
Or if we're local, we don't have to ssh at all.
@param host: The hostname to execute the command on.
@param cmd: The command to run. Special shell syntax (such as pipes)
is allowed.
@param kwargs: Key word arguments for the runner functions.
@returns: The output of the command.
if utils.is_localhost(host):
runner = local_runner
elif getpass.getuser() == 'chromeos-test':
runner = host_object_runner(host)
runner = googlesh_runner(host)
return runner(cmd, **kwargs)
def _csv_to_list(s):
Converts a list seperated by commas into a list of strings.
>>> _csv_to_list('')
>>> _csv_to_list('one')
>>> _csv_to_list('one, two,three')
['one', 'two', 'three']
return [x.strip() for x in s.split(',') if x]
# The goal with these functions is to give you a list of hosts that are valid
# arguments to ssh. Note that this only really works since our instances use
# names that are findable by our default /etc/resolv.conf `search` domains,
# because all of our instances have names under .corp
def sam_servers():
Generate a list of all scheduler/afe instances of autotest.
Note that we don't include the mysql database host if the database is split
from the rest of the system.
sams_config = global_config.global_config.get_config_value(
'CROS', 'sam_instances', default='')
sams = _csv_to_list(sams_config)
return set(sams)
def extra_servers():
Servers that have an autotest checkout in /usr/local/autotest, but aren't
in any other list.
@returns: A set of hosts.
servers = global_config.global_config.get_config_value(
'CROS', 'extra_servers', default='')
return set(_csv_to_list(servers))
def test_instance():
A server that is set up to run tests of the autotest infrastructure.
@returns: A hostname
server = global_config.global_config.get_config_value(
'CROS', 'test_instance', default='')
return server
# The most reliable way to pull information about the state of the lab is to
# look at the global/shadow config on each server. The best way to do this is
# via the global_config module. Therefore, we invoke python on the remote end
# to call global_config to get whatever values we want.
cd /usr/local/autotest
python -c "
import common
from autotest_lib.client.common_lib import global_config
print global_config.global_config.get_config_value(
'%s', '%s', default='')
# There's possibly cheaper ways to do some of this, for example, we could scrape
# instance:13467 for the list of drones, but this way you can get the list of
# drones that is what should/will be running, and not what the scheduler thinks
# is running. (It could have kicked one out, or we could be bringing a new one
# into rotation.) So scraping the config on remote servers, while slow, gives
# us consistent logical results.
def _scrape_from_instances(section, key):
sams = sam_servers()
all_servers = set()
for sam in sams:
servers_csv = execute_command(sam, _VALUE_FROM_CONFIG % (section, key))
servers = _csv_to_list(servers_csv)
for server in servers:
if server == 'localhost':
return all_servers
def database_servers():
Generate a list of all database servers running for instances of autotest.
@returns: An iterable of all hosts.
return _scrape_from_instances('AUTOTEST_WEB', 'host')
def drone_servers():
Generate a list of all drones used by all instances of autotest in
@returns: An iterable of drone servers.
return _scrape_from_instances('SCHEDULER', 'drones')
def shard_servers():
Generate a list of all shard servers.
@returns: An iterable of all shard servers.
shard_hostnames = set()
sams = sam_servers()
for sam in sams:
afe = frontend_wrappers.RetryingAFE(server=sam)
shards ='get_shards')
for shard in shards:
return list(shard_hostnames)