blob: 64d9003536708a503732619a1269c4c22026da48 [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
@contextlib.contextmanager
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()
try:
if dirname is not None:
os.chdir(dirname)
yield
finally:
os.chdir(curdir)
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)
else:
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
object.
@param host: The host to run a command on.
@returns: A function that can invoke a command remotely.
"""
try:
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
host.run 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.
"""
try:
return host_object.run(cmd).stdout
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],
stderr=subprocess.STDOUT)
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)
else:
runner = googlesh_runner(host)
return runner(cmd, **kwargs)