xref: /aosp_15_r20/external/autotest/site_utils/lib/infra.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
1*9c5db199SXin Li# Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
2*9c5db199SXin Li# Use of this source code is governed by a BSD-style license that can be
3*9c5db199SXin Li# found in the LICENSE file.
4*9c5db199SXin Li
5*9c5db199SXin Liimport contextlib
6*9c5db199SXin Liimport getpass
7*9c5db199SXin Liimport subprocess
8*9c5db199SXin Liimport os
9*9c5db199SXin Li
10*9c5db199SXin Liimport common
11*9c5db199SXin Lifrom autotest_lib.server.hosts import ssh_host
12*9c5db199SXin Lifrom autotest_lib.client.common_lib import error
13*9c5db199SXin Lifrom autotest_lib.client.common_lib import global_config
14*9c5db199SXin Lifrom autotest_lib.client.common_lib import utils
15*9c5db199SXin Li
16*9c5db199SXin Li
17*9c5db199SXin Li@contextlib.contextmanager
18*9c5db199SXin Lidef chdir(dirname=None):
19*9c5db199SXin Li    """A context manager to help change directories.
20*9c5db199SXin Li
21*9c5db199SXin Li    Will chdir into the provided dirname for the lifetime of the context and
22*9c5db199SXin Li    return to cwd thereafter.
23*9c5db199SXin Li
24*9c5db199SXin Li    @param dirname: The dirname to chdir into.
25*9c5db199SXin Li    """
26*9c5db199SXin Li    curdir = os.getcwd()
27*9c5db199SXin Li    try:
28*9c5db199SXin Li        if dirname is not None:
29*9c5db199SXin Li            os.chdir(dirname)
30*9c5db199SXin Li        yield
31*9c5db199SXin Li    finally:
32*9c5db199SXin Li        os.chdir(curdir)
33*9c5db199SXin Li
34*9c5db199SXin Li
35*9c5db199SXin Lidef local_runner(cmd, stream_output=False):
36*9c5db199SXin Li    """
37*9c5db199SXin Li    Runs a command on the local system as the current user.
38*9c5db199SXin Li
39*9c5db199SXin Li    @param cmd: The command to run.
40*9c5db199SXin Li    @param stream_output: If True, streams the stdout of the process.
41*9c5db199SXin Li
42*9c5db199SXin Li    @returns: The output of cmd, will be stdout and stderr.
43*9c5db199SXin Li    @raises CalledProcessError: If there was a non-0 return code.
44*9c5db199SXin Li    """
45*9c5db199SXin Li    print('Running command: %s' % cmd)
46*9c5db199SXin Li    proc = subprocess.Popen(
47*9c5db199SXin Li        cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
48*9c5db199SXin Li    if stream_output:
49*9c5db199SXin Li        output = ''
50*9c5db199SXin Li        for newline in iter(proc.stdout.readline, ''):
51*9c5db199SXin Li            output += newline
52*9c5db199SXin Li            print(newline.rstrip(os.linesep))
53*9c5db199SXin Li    else:
54*9c5db199SXin Li        output = proc.communicate()[0]
55*9c5db199SXin Li
56*9c5db199SXin Li    return_code = proc.wait()
57*9c5db199SXin Li    if return_code !=0:
58*9c5db199SXin Li        print("ERROR: '%s' failed with error:\n%s" % (cmd, output))
59*9c5db199SXin Li        raise subprocess.CalledProcessError(return_code, cmd, output[:1024])
60*9c5db199SXin Li    return output
61*9c5db199SXin Li
62*9c5db199SXin Li
63*9c5db199SXin Li_host_objects = {}
64*9c5db199SXin Li
65*9c5db199SXin Lidef host_object_runner(host, **kwargs):
66*9c5db199SXin Li    """
67*9c5db199SXin Li    Returns a function that returns the output of running a command via a host
68*9c5db199SXin Li    object.
69*9c5db199SXin Li
70*9c5db199SXin Li    @param host: The host to run a command on.
71*9c5db199SXin Li    @returns: A function that can invoke a command remotely.
72*9c5db199SXin Li    """
73*9c5db199SXin Li    try:
74*9c5db199SXin Li        host_object = _host_objects[host]
75*9c5db199SXin Li    except KeyError:
76*9c5db199SXin Li        username = global_config.global_config.get_config_value(
77*9c5db199SXin Li                'CROS', 'infrastructure_user')
78*9c5db199SXin Li        host_object = ssh_host.SSHHost(host, user=username)
79*9c5db199SXin Li        _host_objects[host] = host_object
80*9c5db199SXin Li
81*9c5db199SXin Li    def runner(cmd):
82*9c5db199SXin Li        """
83*9c5db199SXin Li        Runs a command via a host object on the enclosed host.  Translates
84*9c5db199SXin Li        host.run errors to the subprocess equivalent to expose a common API.
85*9c5db199SXin Li
86*9c5db199SXin Li        @param cmd: The command to run.
87*9c5db199SXin Li        @returns: The output of cmd.
88*9c5db199SXin Li        @raises CalledProcessError: If there was a non-0 return code.
89*9c5db199SXin Li        """
90*9c5db199SXin Li        try:
91*9c5db199SXin Li            return host_object.run(cmd).stdout
92*9c5db199SXin Li        except error.AutotestHostRunError as e:
93*9c5db199SXin Li            exit_status = e.result_obj.exit_status
94*9c5db199SXin Li            command = e.result_obj.command
95*9c5db199SXin Li            raise subprocess.CalledProcessError(exit_status, command)
96*9c5db199SXin Li    return runner
97*9c5db199SXin Li
98*9c5db199SXin Li
99*9c5db199SXin Lidef googlesh_runner(host, **kwargs):
100*9c5db199SXin Li    """
101*9c5db199SXin Li    Returns a function that return the output of running a command via shelling
102*9c5db199SXin Li    out to `googlesh`.
103*9c5db199SXin Li
104*9c5db199SXin Li    @param host: The host to run a command on
105*9c5db199SXin Li    @returns: A function that can invoke a command remotely.
106*9c5db199SXin Li    """
107*9c5db199SXin Li    def runner(cmd):
108*9c5db199SXin Li        """
109*9c5db199SXin Li        Runs a command via googlesh on the enclosed host.
110*9c5db199SXin Li
111*9c5db199SXin Li        @param cmd: The command to run.
112*9c5db199SXin Li        @returns: The output of cmd.
113*9c5db199SXin Li        @raises CalledProcessError: If there was a non-0 return code.
114*9c5db199SXin Li        """
115*9c5db199SXin Li        out = subprocess.check_output(['googlesh', '-s', '-uchromeos-test',
116*9c5db199SXin Li                                       '-m%s' % host, '%s' % cmd],
117*9c5db199SXin Li                                      stderr=subprocess.STDOUT)
118*9c5db199SXin Li        return out
119*9c5db199SXin Li    return runner
120*9c5db199SXin Li
121*9c5db199SXin Li
122*9c5db199SXin Lidef execute_command(host, cmd, **kwargs):
123*9c5db199SXin Li    """
124*9c5db199SXin Li    Executes a command on the host `host`.  This an optimization that if
125*9c5db199SXin Li    we're already chromeos-test, we can just ssh to the machine in question.
126*9c5db199SXin Li    Or if we're local, we don't have to ssh at all.
127*9c5db199SXin Li
128*9c5db199SXin Li    @param host: The hostname to execute the command on.
129*9c5db199SXin Li    @param cmd: The command to run.  Special shell syntax (such as pipes)
130*9c5db199SXin Li                is allowed.
131*9c5db199SXin Li    @param kwargs: Key word arguments for the runner functions.
132*9c5db199SXin Li    @returns: The output of the command.
133*9c5db199SXin Li    """
134*9c5db199SXin Li    if utils.is_localhost(host):
135*9c5db199SXin Li        runner = local_runner
136*9c5db199SXin Li    elif getpass.getuser() == 'chromeos-test':
137*9c5db199SXin Li        runner = host_object_runner(host)
138*9c5db199SXin Li    else:
139*9c5db199SXin Li        runner = googlesh_runner(host)
140*9c5db199SXin Li
141*9c5db199SXin Li    return runner(cmd, **kwargs)
142