# 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)