xref: /aosp_15_r20/external/autotest/client/common_lib/utils.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
1*9c5db199SXin Li# Lint as: python2, python3
2*9c5db199SXin Li# Copyright (c) 2017 The Chromium Authors. All rights reserved.
3*9c5db199SXin Li# Use of this source code is governed by a BSD-style license that can be
4*9c5db199SXin Li# found in the LICENSE file.
5*9c5db199SXin Li
6*9c5db199SXin Li"""
7*9c5db199SXin LiConvenience functions for use by tests or whomever.
8*9c5db199SXin Li
9*9c5db199SXin LiThere's no really good way to do this, as this isn't a class we can do
10*9c5db199SXin Liinheritance with, just a collection of static methods.
11*9c5db199SXin Li"""
12*9c5db199SXin Li
13*9c5db199SXin Li# pylint: disable=missing-docstring
14*9c5db199SXin Li
15*9c5db199SXin Lifrom __future__ import absolute_import
16*9c5db199SXin Lifrom __future__ import division
17*9c5db199SXin Lifrom __future__ import print_function
18*9c5db199SXin Li
19*9c5db199SXin Liimport collections
20*9c5db199SXin Liimport datetime
21*9c5db199SXin Liimport errno
22*9c5db199SXin Liimport inspect
23*9c5db199SXin Liimport json
24*9c5db199SXin Liimport logging
25*9c5db199SXin Liimport os
26*9c5db199SXin Liimport pickle
27*9c5db199SXin Liimport random
28*9c5db199SXin Liimport re
29*9c5db199SXin Liimport resource
30*9c5db199SXin Liimport select
31*9c5db199SXin Liimport shutil
32*9c5db199SXin Liimport signal
33*9c5db199SXin Liimport socket
34*9c5db199SXin Liimport six
35*9c5db199SXin Lifrom six.moves import input
36*9c5db199SXin Lifrom six.moves import range
37*9c5db199SXin Lifrom six.moves import urllib
38*9c5db199SXin Lifrom six.moves import zip
39*9c5db199SXin Lifrom six.moves import zip_longest
40*9c5db199SXin Liimport six.moves.urllib.parse
41*9c5db199SXin Liimport string
42*9c5db199SXin Liimport struct
43*9c5db199SXin Liimport subprocess
44*9c5db199SXin Liimport textwrap
45*9c5db199SXin Liimport threading
46*9c5db199SXin Liimport time
47*9c5db199SXin Liimport six.moves.queue
48*9c5db199SXin Liimport uuid
49*9c5db199SXin Liimport warnings
50*9c5db199SXin Li
51*9c5db199SXin Litry:
52*9c5db199SXin Li    import hashlib
53*9c5db199SXin Liexcept ImportError as e:
54*9c5db199SXin Li    if six.PY2:
55*9c5db199SXin Li        import md5
56*9c5db199SXin Li        import sha
57*9c5db199SXin Li    else:
58*9c5db199SXin Li        raise ImportError("Broken hashlib imports %s", e)
59*9c5db199SXin Li
60*9c5db199SXin Liimport common
61*9c5db199SXin Li
62*9c5db199SXin Lifrom autotest_lib.client.common_lib import env
63*9c5db199SXin Lifrom autotest_lib.client.common_lib import error
64*9c5db199SXin Lifrom autotest_lib.client.common_lib import global_config
65*9c5db199SXin Lifrom autotest_lib.client.common_lib import logging_manager
66*9c5db199SXin Lifrom autotest_lib.client.common_lib import metrics_mock_class
67*9c5db199SXin Lifrom autotest_lib.client.cros import constants
68*9c5db199SXin Li
69*9c5db199SXin Li# pylint: disable=wildcard-import
70*9c5db199SXin Lifrom autotest_lib.client.common_lib.lsbrelease_utils import *
71*9c5db199SXin Li
72*9c5db199SXin Li
73*9c5db199SXin Lidef deprecated(func):
74*9c5db199SXin Li    """This is a decorator which can be used to mark functions as deprecated.
75*9c5db199SXin Li    It will result in a warning being emmitted when the function is used."""
76*9c5db199SXin Li    def new_func(*args, **dargs):
77*9c5db199SXin Li        warnings.warn("Call to deprecated function %s." % func.__name__,
78*9c5db199SXin Li                      category=DeprecationWarning)
79*9c5db199SXin Li        return func(*args, **dargs)
80*9c5db199SXin Li    new_func.__name__ = func.__name__
81*9c5db199SXin Li    new_func.__doc__ = func.__doc__
82*9c5db199SXin Li    new_func.__dict__.update(func.__dict__)
83*9c5db199SXin Li    return new_func
84*9c5db199SXin Li
85*9c5db199SXin Li
86*9c5db199SXin Liclass _NullStream(object):
87*9c5db199SXin Li    def write(self, data):
88*9c5db199SXin Li        pass
89*9c5db199SXin Li
90*9c5db199SXin Li
91*9c5db199SXin Li    def flush(self):
92*9c5db199SXin Li        pass
93*9c5db199SXin Li
94*9c5db199SXin Li
95*9c5db199SXin LiTEE_TO_LOGS = object()
96*9c5db199SXin Li_the_null_stream = _NullStream()
97*9c5db199SXin Li
98*9c5db199SXin LiDEVNULL = object()
99*9c5db199SXin Li
100*9c5db199SXin LiDEFAULT_STDOUT_LEVEL = logging.DEBUG
101*9c5db199SXin LiDEFAULT_STDERR_LEVEL = logging.ERROR
102*9c5db199SXin Li
103*9c5db199SXin Li# prefixes for logging stdout/stderr of commands
104*9c5db199SXin LiSTDOUT_PREFIX = '[stdout] '
105*9c5db199SXin LiSTDERR_PREFIX = '[stderr] '
106*9c5db199SXin Li
107*9c5db199SXin Li# safe characters for the shell (do not need quoting)
108*9c5db199SXin Li_SHELL_QUOTING_ALLOWLIST = frozenset(string.ascii_letters +
109*9c5db199SXin Li                                    string.digits +
110*9c5db199SXin Li                                    '_-+=>|')
111*9c5db199SXin Li
112*9c5db199SXin Lidef custom_warning_handler(message, category, filename, lineno, file=None,
113*9c5db199SXin Li                           line=None):
114*9c5db199SXin Li    """Custom handler to log at the WARNING error level. Ignores |file|."""
115*9c5db199SXin Li    logging.warning(warnings.formatwarning(message, category, filename, lineno,
116*9c5db199SXin Li                                           line))
117*9c5db199SXin Li
118*9c5db199SXin Liwarnings.showwarning = custom_warning_handler
119*9c5db199SXin Li
120*9c5db199SXin Lidef get_stream_tee_file(stream, level, prefix=''):
121*9c5db199SXin Li    if stream is None:
122*9c5db199SXin Li        return _the_null_stream
123*9c5db199SXin Li    if stream is DEVNULL:
124*9c5db199SXin Li        return None
125*9c5db199SXin Li    if stream is TEE_TO_LOGS:
126*9c5db199SXin Li        return logging_manager.LoggingFile(level=level, prefix=prefix)
127*9c5db199SXin Li    return stream
128*9c5db199SXin Li
129*9c5db199SXin Li
130*9c5db199SXin Lidef _join_with_nickname(base_string, nickname):
131*9c5db199SXin Li    if nickname:
132*9c5db199SXin Li        return '%s BgJob "%s" ' % (base_string, nickname)
133*9c5db199SXin Li    return base_string
134*9c5db199SXin Li
135*9c5db199SXin Li
136*9c5db199SXin Li# TODO: Cleanup and possibly eliminate |unjoinable|, which is only used in our
137*9c5db199SXin Li# ssh connection process, while fixing underlying
138*9c5db199SXin Li# semantics problem in BgJob. See crbug.com/279312
139*9c5db199SXin Liclass BgJob(object):
140*9c5db199SXin Li    def __init__(self, command, stdout_tee=None, stderr_tee=None, verbose=True,
141*9c5db199SXin Li                 stdin=None, stdout_level=DEFAULT_STDOUT_LEVEL,
142*9c5db199SXin Li                 stderr_level=DEFAULT_STDERR_LEVEL, nickname=None,
143*9c5db199SXin Li                 unjoinable=False, env=None, extra_paths=None):
144*9c5db199SXin Li        """Create and start a new BgJob.
145*9c5db199SXin Li
146*9c5db199SXin Li        This constructor creates a new BgJob, and uses Popen to start a new
147*9c5db199SXin Li        subprocess with given command. It returns without blocking on execution
148*9c5db199SXin Li        of the subprocess.
149*9c5db199SXin Li
150*9c5db199SXin Li        After starting a new BgJob, use output_prepare to connect the process's
151*9c5db199SXin Li        stdout and stderr pipes to the stream of your choice.
152*9c5db199SXin Li
153*9c5db199SXin Li        When the job is running, the jobs's output streams are only read from
154*9c5db199SXin Li        when process_output is called.
155*9c5db199SXin Li
156*9c5db199SXin Li        @param command: command to be executed in new subprocess. May be either
157*9c5db199SXin Li                        a list, or a string (in which case Popen will be called
158*9c5db199SXin Li                        with shell=True)
159*9c5db199SXin Li        @param stdout_tee: (Optional) a file like object, TEE_TO_LOGS or
160*9c5db199SXin Li                           DEVNULL.
161*9c5db199SXin Li                           If not given, after finishing the process, the
162*9c5db199SXin Li                           stdout data from subprocess is available in
163*9c5db199SXin Li                           result.stdout.
164*9c5db199SXin Li                           If a file like object is given, in process_output(),
165*9c5db199SXin Li                           the stdout data from the subprocess will be handled
166*9c5db199SXin Li                           by the given file like object.
167*9c5db199SXin Li                           If TEE_TO_LOGS is given, in process_output(), the
168*9c5db199SXin Li                           stdout data from the subprocess will be handled by
169*9c5db199SXin Li                           the standard logging_manager.
170*9c5db199SXin Li                           If DEVNULL is given, the stdout of the subprocess
171*9c5db199SXin Li                           will be just discarded. In addition, even after
172*9c5db199SXin Li                           cleanup(), result.stdout will be just an empty
173*9c5db199SXin Li                           string (unlike the case where stdout_tee is not
174*9c5db199SXin Li                           given).
175*9c5db199SXin Li        @param stderr_tee: Same as stdout_tee, but for stderr.
176*9c5db199SXin Li        @param verbose: Boolean, make BgJob logging more verbose.
177*9c5db199SXin Li        @param stdin: Stream object, will be passed to Popen as the new
178*9c5db199SXin Li                      process's stdin.
179*9c5db199SXin Li        @param stdout_level: A logging level value. If stdout_tee was set to
180*9c5db199SXin Li                             TEE_TO_LOGS, sets the level that tee'd
181*9c5db199SXin Li                             stdout output will be logged at. Ignored
182*9c5db199SXin Li                             otherwise.
183*9c5db199SXin Li        @param stderr_level: Same as stdout_level, but for stderr.
184*9c5db199SXin Li        @param nickname: Optional string, to be included in logging messages
185*9c5db199SXin Li        @param unjoinable: Optional bool, default False.
186*9c5db199SXin Li                           This should be True for BgJobs running in background
187*9c5db199SXin Li                           and will never be joined with join_bg_jobs(), such
188*9c5db199SXin Li                           as the ssh connection. Instead, it is
189*9c5db199SXin Li                           caller's responsibility to terminate the subprocess
190*9c5db199SXin Li                           correctly, e.g. by calling nuke_subprocess().
191*9c5db199SXin Li                           This will lead that, calling join_bg_jobs(),
192*9c5db199SXin Li                           process_output() or cleanup() will result in an
193*9c5db199SXin Li                           InvalidBgJobCall exception.
194*9c5db199SXin Li                           Also, |stdout_tee| and |stderr_tee| must be set to
195*9c5db199SXin Li                           DEVNULL, otherwise InvalidBgJobCall is raised.
196*9c5db199SXin Li        @param env: Dict containing environment variables used in subprocess.
197*9c5db199SXin Li        @param extra_paths: Optional string list, to be prepended to the PATH
198*9c5db199SXin Li                            env variable in env (or os.environ dict if env is
199*9c5db199SXin Li                            not specified).
200*9c5db199SXin Li        """
201*9c5db199SXin Li        self.command = command
202*9c5db199SXin Li        self.unjoinable = unjoinable
203*9c5db199SXin Li        if (unjoinable and (stdout_tee != DEVNULL or stderr_tee != DEVNULL)):
204*9c5db199SXin Li            raise error.InvalidBgJobCall(
205*9c5db199SXin Li                'stdout_tee and stderr_tee must be DEVNULL for '
206*9c5db199SXin Li                'unjoinable BgJob')
207*9c5db199SXin Li        self._stdout_tee = get_stream_tee_file(
208*9c5db199SXin Li                stdout_tee, stdout_level,
209*9c5db199SXin Li                prefix=_join_with_nickname(STDOUT_PREFIX, nickname))
210*9c5db199SXin Li        self._stderr_tee = get_stream_tee_file(
211*9c5db199SXin Li                stderr_tee, stderr_level,
212*9c5db199SXin Li                prefix=_join_with_nickname(STDERR_PREFIX, nickname))
213*9c5db199SXin Li        self.result = CmdResult(command)
214*9c5db199SXin Li
215*9c5db199SXin Li        # allow for easy stdin input by string, we'll let subprocess create
216*9c5db199SXin Li        # a pipe for stdin input and we'll write to it in the wait loop
217*9c5db199SXin Li        if isinstance(stdin, six.string_types):
218*9c5db199SXin Li            self.string_stdin = stdin
219*9c5db199SXin Li            stdin = subprocess.PIPE
220*9c5db199SXin Li        else:
221*9c5db199SXin Li            self.string_stdin = None
222*9c5db199SXin Li
223*9c5db199SXin Li        # Prepend extra_paths to env['PATH'] if necessary.
224*9c5db199SXin Li        if extra_paths:
225*9c5db199SXin Li            env = (os.environ if env is None else env).copy()
226*9c5db199SXin Li            oldpath = env.get('PATH')
227*9c5db199SXin Li            env['PATH'] = os.pathsep.join(
228*9c5db199SXin Li                    extra_paths + ([oldpath] if oldpath else []))
229*9c5db199SXin Li
230*9c5db199SXin Li        if verbose:
231*9c5db199SXin Li            logging.debug("Running '%s'", command)
232*9c5db199SXin Li
233*9c5db199SXin Li        if type(command) == list:
234*9c5db199SXin Li            shell = False
235*9c5db199SXin Li            executable = None
236*9c5db199SXin Li        else:
237*9c5db199SXin Li            shell = True
238*9c5db199SXin Li            executable = '/bin/bash'
239*9c5db199SXin Li
240*9c5db199SXin Li        with open('/dev/null', 'w') as devnull:
241*9c5db199SXin Li            # TODO b/169678884. close_fds was reverted to False, as there is a
242*9c5db199SXin Li            # large performance hit due to a docker + python2 bug. Eventually
243*9c5db199SXin Li            # update (everything) to python3. Moving this call to subprocess32
244*9c5db199SXin Li            # is also an option, but will require new packages to the drone/lxc
245*9c5db199SXin Li            # containers.
246*9c5db199SXin Li
247*9c5db199SXin Li            self.sp = subprocess.Popen(
248*9c5db199SXin Li                command,
249*9c5db199SXin Li                stdin=stdin,
250*9c5db199SXin Li                stdout=devnull if stdout_tee == DEVNULL else subprocess.PIPE,
251*9c5db199SXin Li                stderr=devnull if stderr_tee == DEVNULL else subprocess.PIPE,
252*9c5db199SXin Li                preexec_fn=self._reset_sigpipe,
253*9c5db199SXin Li                shell=shell, executable=executable,
254*9c5db199SXin Li                env=env, close_fds=False)
255*9c5db199SXin Li        self._cleanup_called = False
256*9c5db199SXin Li        self._stdout_file = (
257*9c5db199SXin Li            None if stdout_tee == DEVNULL else six.StringIO())
258*9c5db199SXin Li        self._stderr_file = (
259*9c5db199SXin Li            None if stderr_tee == DEVNULL else six.StringIO())
260*9c5db199SXin Li
261*9c5db199SXin Li    def process_output(self, stdout=True, final_read=False):
262*9c5db199SXin Li        """Read from process's output stream, and write data to destinations.
263*9c5db199SXin Li
264*9c5db199SXin Li        This function reads up to 1024 bytes from the background job's
265*9c5db199SXin Li        stdout or stderr stream, and writes the resulting data to the BgJob's
266*9c5db199SXin Li        output tee and to the stream set up in output_prepare.
267*9c5db199SXin Li
268*9c5db199SXin Li        Warning: Calls to process_output will block on reads from the
269*9c5db199SXin Li        subprocess stream, and will block on writes to the configured
270*9c5db199SXin Li        destination stream.
271*9c5db199SXin Li
272*9c5db199SXin Li        @param stdout: True = read and process data from job's stdout.
273*9c5db199SXin Li                       False = from stderr.
274*9c5db199SXin Li                       Default: True
275*9c5db199SXin Li        @param final_read: Do not read only 1024 bytes from stream. Instead,
276*9c5db199SXin Li                           read and process all data until end of the stream.
277*9c5db199SXin Li
278*9c5db199SXin Li        """
279*9c5db199SXin Li        if self.unjoinable:
280*9c5db199SXin Li            raise error.InvalidBgJobCall('Cannot call process_output on '
281*9c5db199SXin Li                                         'a job with unjoinable BgJob')
282*9c5db199SXin Li        if stdout:
283*9c5db199SXin Li            pipe, buf, tee = (
284*9c5db199SXin Li                self.sp.stdout, self._stdout_file, self._stdout_tee)
285*9c5db199SXin Li        else:
286*9c5db199SXin Li            pipe, buf, tee = (
287*9c5db199SXin Li                self.sp.stderr, self._stderr_file, self._stderr_tee)
288*9c5db199SXin Li
289*9c5db199SXin Li        if not pipe:
290*9c5db199SXin Li            return
291*9c5db199SXin Li
292*9c5db199SXin Li        if final_read:
293*9c5db199SXin Li            # read in all the data we can from pipe and then stop
294*9c5db199SXin Li            data = []
295*9c5db199SXin Li            while select.select([pipe], [], [], 0)[0]:
296*9c5db199SXin Li                data.append(self._read_data(pipe))
297*9c5db199SXin Li                if len(data[-1]) == 0:
298*9c5db199SXin Li                    break
299*9c5db199SXin Li            data = "".join(data)
300*9c5db199SXin Li        else:
301*9c5db199SXin Li            # perform a single read
302*9c5db199SXin Li            data = self._read_data(pipe)
303*9c5db199SXin Li        buf.write(data)
304*9c5db199SXin Li        tee.write(data)
305*9c5db199SXin Li
306*9c5db199SXin Li    def _read_data(self, pipe):
307*9c5db199SXin Li        """Read & return the data from the provided pipe.
308*9c5db199SXin Li
309*9c5db199SXin Li        Handles the changes to pipe reading & iostring writing in python 2/3.
310*9c5db199SXin Li        In python2 the buffer (iostring) can take bytes, where in python3 it
311*9c5db199SXin Li        must be a string. Formatting bytes to string in python 2 vs 3 seems
312*9c5db199SXin Li        to be a bit different. In 3, .decode() is needed, however in 2 that
313*9c5db199SXin Li        results in unicode (not str), breaking downstream users.
314*9c5db199SXin Li
315*9c5db199SXin Li        """
316*9c5db199SXin Li
317*9c5db199SXin Li        data = os.read(pipe.fileno(), 1024)
318*9c5db199SXin Li        if isinstance(data, bytes) and six.PY3:
319*9c5db199SXin Li            # On rare occasion, an invalid byte will be read, causing this to
320*9c5db199SXin Li            # crash. Ignoring these errors seems like the best option for now.
321*9c5db199SXin Li            return data.decode(errors='ignore')
322*9c5db199SXin Li        return data
323*9c5db199SXin Li
324*9c5db199SXin Li    def cleanup(self):
325*9c5db199SXin Li        """Clean up after BgJob.
326*9c5db199SXin Li
327*9c5db199SXin Li        Flush the stdout_tee and stderr_tee buffers, close the
328*9c5db199SXin Li        subprocess stdout and stderr buffers, and saves data from
329*9c5db199SXin Li        the configured stdout and stderr destination streams to
330*9c5db199SXin Li        self.result. Duplicate calls ignored with a warning.
331*9c5db199SXin Li        """
332*9c5db199SXin Li        if self.unjoinable:
333*9c5db199SXin Li            raise error.InvalidBgJobCall('Cannot call cleanup on '
334*9c5db199SXin Li                                         'a job with a unjoinable BgJob')
335*9c5db199SXin Li        if self._cleanup_called:
336*9c5db199SXin Li            logging.warning('BgJob [%s] received a duplicate call to '
337*9c5db199SXin Li                            'cleanup. Ignoring.', self.command)
338*9c5db199SXin Li            return
339*9c5db199SXin Li        try:
340*9c5db199SXin Li            if self.sp.stdout:
341*9c5db199SXin Li                self._stdout_tee.flush()
342*9c5db199SXin Li                self.sp.stdout.close()
343*9c5db199SXin Li                self.result.stdout = self._stdout_file.getvalue()
344*9c5db199SXin Li
345*9c5db199SXin Li            if self.sp.stderr:
346*9c5db199SXin Li                self._stderr_tee.flush()
347*9c5db199SXin Li                self.sp.stderr.close()
348*9c5db199SXin Li                self.result.stderr = self._stderr_file.getvalue()
349*9c5db199SXin Li        finally:
350*9c5db199SXin Li            self._cleanup_called = True
351*9c5db199SXin Li
352*9c5db199SXin Li    def _reset_sigpipe(self):
353*9c5db199SXin Li        if not env.IN_MOD_WSGI:
354*9c5db199SXin Li            signal.signal(signal.SIGPIPE, signal.SIG_DFL)
355*9c5db199SXin Li
356*9c5db199SXin Li
357*9c5db199SXin Lidef ip_to_long(ip):
358*9c5db199SXin Li    # !L is a long in network byte order
359*9c5db199SXin Li    return struct.unpack('!L', socket.inet_aton(ip))[0]
360*9c5db199SXin Li
361*9c5db199SXin Li
362*9c5db199SXin Lidef long_to_ip(number):
363*9c5db199SXin Li    # See above comment.
364*9c5db199SXin Li    return socket.inet_ntoa(struct.pack('!L', number))
365*9c5db199SXin Li
366*9c5db199SXin Li
367*9c5db199SXin Lidef create_subnet_mask(bits):
368*9c5db199SXin Li    return (1 << 32) - (1 << 32-bits)
369*9c5db199SXin Li
370*9c5db199SXin Li
371*9c5db199SXin Lidef format_ip_with_mask(ip, mask_bits):
372*9c5db199SXin Li    masked_ip = ip_to_long(ip) & create_subnet_mask(mask_bits)
373*9c5db199SXin Li    return "%s/%s" % (long_to_ip(masked_ip), mask_bits)
374*9c5db199SXin Li
375*9c5db199SXin Li
376*9c5db199SXin Lidef normalize_hostname(alias):
377*9c5db199SXin Li    ip = socket.gethostbyname(alias)
378*9c5db199SXin Li    return socket.gethostbyaddr(ip)[0]
379*9c5db199SXin Li
380*9c5db199SXin Li
381*9c5db199SXin Lidef get_ip_local_port_range():
382*9c5db199SXin Li    match = re.match(r'\s*(\d+)\s*(\d+)\s*$',
383*9c5db199SXin Li                     read_one_line('/proc/sys/net/ipv4/ip_local_port_range'))
384*9c5db199SXin Li    return (int(match.group(1)), int(match.group(2)))
385*9c5db199SXin Li
386*9c5db199SXin Li
387*9c5db199SXin Lidef set_ip_local_port_range(lower, upper):
388*9c5db199SXin Li    write_one_line('/proc/sys/net/ipv4/ip_local_port_range',
389*9c5db199SXin Li                   '%d %d\n' % (lower, upper))
390*9c5db199SXin Li
391*9c5db199SXin Li
392*9c5db199SXin Lidef read_one_line(filename):
393*9c5db199SXin Li    f = open(filename, 'r')
394*9c5db199SXin Li    try:
395*9c5db199SXin Li        return f.readline().rstrip('\n')
396*9c5db199SXin Li    finally:
397*9c5db199SXin Li        f.close()
398*9c5db199SXin Li
399*9c5db199SXin Li
400*9c5db199SXin Lidef read_file(filename):
401*9c5db199SXin Li    f = open(filename)
402*9c5db199SXin Li    try:
403*9c5db199SXin Li        return f.read()
404*9c5db199SXin Li    finally:
405*9c5db199SXin Li        f.close()
406*9c5db199SXin Li
407*9c5db199SXin Li
408*9c5db199SXin Lidef get_field(data, param, linestart="", sep=" "):
409*9c5db199SXin Li    """
410*9c5db199SXin Li    Parse data from string.
411*9c5db199SXin Li    @param data: Data to parse.
412*9c5db199SXin Li        example:
413*9c5db199SXin Li          data:
414*9c5db199SXin Li             cpu   324 345 34  5 345
415*9c5db199SXin Li             cpu0  34  11  34 34  33
416*9c5db199SXin Li             ^^^^
417*9c5db199SXin Li             start of line
418*9c5db199SXin Li             params 0   1   2  3   4
419*9c5db199SXin Li    @param param: Position of parameter after linestart marker.
420*9c5db199SXin Li    @param linestart: String to which start line with parameters.
421*9c5db199SXin Li    @param sep: Separator between parameters regular expression.
422*9c5db199SXin Li    """
423*9c5db199SXin Li    search = re.compile(r"(?<=^%s)\s*(.*)" % linestart, re.MULTILINE)
424*9c5db199SXin Li    find = search.search(data)
425*9c5db199SXin Li    if find != None:
426*9c5db199SXin Li        return re.split("%s" % sep, find.group(1))[param]
427*9c5db199SXin Li    else:
428*9c5db199SXin Li        print("There is no line which starts with %s in data." % linestart)
429*9c5db199SXin Li        return None
430*9c5db199SXin Li
431*9c5db199SXin Li
432*9c5db199SXin Lidef write_one_line(filename, line):
433*9c5db199SXin Li    open_write_close(filename, str(line).rstrip('\n') + '\n')
434*9c5db199SXin Li
435*9c5db199SXin Li
436*9c5db199SXin Lidef open_write_close(filename, data, is_binary=False):
437*9c5db199SXin Li    open_mode = 'w'
438*9c5db199SXin Li    if is_binary:
439*9c5db199SXin Li        open_mode = 'wb'
440*9c5db199SXin Li
441*9c5db199SXin Li    with open(filename, open_mode) as f:
442*9c5db199SXin Li        f.write(data)
443*9c5db199SXin Li
444*9c5db199SXin Li
445*9c5db199SXin Lidef locate_file(path, base_dir=None):
446*9c5db199SXin Li    """Locates a file.
447*9c5db199SXin Li
448*9c5db199SXin Li    @param path: The path of the file being located. Could be absolute or
449*9c5db199SXin Li        relative path. For relative path, it tries to locate the file from
450*9c5db199SXin Li        base_dir.
451*9c5db199SXin Li
452*9c5db199SXin Li    @param base_dir (optional): Base directory of the relative path.
453*9c5db199SXin Li
454*9c5db199SXin Li    @returns Absolute path of the file if found. None if path is None.
455*9c5db199SXin Li    @raises error.TestFail if the file is not found.
456*9c5db199SXin Li    """
457*9c5db199SXin Li    if path is None:
458*9c5db199SXin Li        return None
459*9c5db199SXin Li
460*9c5db199SXin Li    if not os.path.isabs(path) and base_dir is not None:
461*9c5db199SXin Li        # Assume the relative path is based in autotest directory.
462*9c5db199SXin Li        path = os.path.join(base_dir, path)
463*9c5db199SXin Li    if not os.path.isfile(path):
464*9c5db199SXin Li        raise error.TestFail('ERROR: Unable to find %s' % path)
465*9c5db199SXin Li    return path
466*9c5db199SXin Li
467*9c5db199SXin Li
468*9c5db199SXin Lidef read_keyval(path, type_tag=None):
469*9c5db199SXin Li    """
470*9c5db199SXin Li    Read a key-value pair format file into a dictionary, and return it.
471*9c5db199SXin Li    Takes either a filename or directory name as input. If it's a
472*9c5db199SXin Li    directory name, we assume you want the file to be called keyval.
473*9c5db199SXin Li
474*9c5db199SXin Li    @param path: Full path of the file to read from.
475*9c5db199SXin Li    @param type_tag: If not None, only keyvals with key ending
476*9c5db199SXin Li                     in a suffix {type_tag} will be collected.
477*9c5db199SXin Li    """
478*9c5db199SXin Li    if os.path.isdir(path):
479*9c5db199SXin Li        path = os.path.join(path, 'keyval')
480*9c5db199SXin Li    if not os.path.exists(path):
481*9c5db199SXin Li        return {}
482*9c5db199SXin Li
483*9c5db199SXin Li    if type_tag:
484*9c5db199SXin Li        pattern = r'^([-\.\w]+)\{%s\}=(.*)$' % type_tag
485*9c5db199SXin Li    else:
486*9c5db199SXin Li        pattern = r'^([-\.\w]+)=(.*)$'
487*9c5db199SXin Li
488*9c5db199SXin Li    keyval = {}
489*9c5db199SXin Li    f = open(path)
490*9c5db199SXin Li    for line in f:
491*9c5db199SXin Li        line = re.sub('#.*', '', line).rstrip()
492*9c5db199SXin Li        if not line:
493*9c5db199SXin Li            continue
494*9c5db199SXin Li        match = re.match(pattern, line)
495*9c5db199SXin Li        if match:
496*9c5db199SXin Li            key = match.group(1)
497*9c5db199SXin Li            value = match.group(2)
498*9c5db199SXin Li            if re.search('^\d+$', value):
499*9c5db199SXin Li                value = int(value)
500*9c5db199SXin Li            elif re.search('^(\d+\.)?\d+$', value):
501*9c5db199SXin Li                value = float(value)
502*9c5db199SXin Li            keyval[key] = value
503*9c5db199SXin Li        else:
504*9c5db199SXin Li            raise ValueError('Invalid format line: %s' % line)
505*9c5db199SXin Li    f.close()
506*9c5db199SXin Li    return keyval
507*9c5db199SXin Li
508*9c5db199SXin Li
509*9c5db199SXin Lidef write_keyval(path, dictionary, type_tag=None):
510*9c5db199SXin Li    """
511*9c5db199SXin Li    Write a key-value pair format file out to a file. This uses append
512*9c5db199SXin Li    mode to open the file, so existing text will not be overwritten or
513*9c5db199SXin Li    reparsed.
514*9c5db199SXin Li
515*9c5db199SXin Li    If type_tag is None, then the key must be composed of alphanumeric
516*9c5db199SXin Li    characters (or dashes+underscores). However, if type-tag is not
517*9c5db199SXin Li    null then the keys must also have "{type_tag}" as a suffix. At
518*9c5db199SXin Li    the moment the only valid values of type_tag are "attr" and "perf".
519*9c5db199SXin Li
520*9c5db199SXin Li    @param path: full path of the file to be written
521*9c5db199SXin Li    @param dictionary: the items to write
522*9c5db199SXin Li    @param type_tag: see text above
523*9c5db199SXin Li    """
524*9c5db199SXin Li    if os.path.isdir(path):
525*9c5db199SXin Li        path = os.path.join(path, 'keyval')
526*9c5db199SXin Li    keyval = open(path, 'a')
527*9c5db199SXin Li
528*9c5db199SXin Li    if type_tag is None:
529*9c5db199SXin Li        key_regex = re.compile(r'^[-\.\w]+$')
530*9c5db199SXin Li    else:
531*9c5db199SXin Li        if type_tag not in ('attr', 'perf'):
532*9c5db199SXin Li            raise ValueError('Invalid type tag: %s' % type_tag)
533*9c5db199SXin Li        escaped_tag = re.escape(type_tag)
534*9c5db199SXin Li        key_regex = re.compile(r'^[-\.\w]+\{%s\}$' % escaped_tag)
535*9c5db199SXin Li    try:
536*9c5db199SXin Li        for key in sorted(dictionary.keys()):
537*9c5db199SXin Li            if not key_regex.search(key):
538*9c5db199SXin Li                raise ValueError('Invalid key: %s' % key)
539*9c5db199SXin Li            keyval.write('%s=%s\n' % (key, dictionary[key]))
540*9c5db199SXin Li    finally:
541*9c5db199SXin Li        keyval.close()
542*9c5db199SXin Li
543*9c5db199SXin Li
544*9c5db199SXin Lidef is_url(path):
545*9c5db199SXin Li    """Return true if path looks like a URL"""
546*9c5db199SXin Li    # for now, just handle http and ftp
547*9c5db199SXin Li    url_parts = six.moves.urllib.parse.urlparse(path)
548*9c5db199SXin Li    return (url_parts[0] in ('http', 'ftp'))
549*9c5db199SXin Li
550*9c5db199SXin Li
551*9c5db199SXin Lidef urlopen(url, data=None, timeout=5):
552*9c5db199SXin Li    """Wrapper to urllib2.urlopen with timeout addition."""
553*9c5db199SXin Li
554*9c5db199SXin Li    # Save old timeout
555*9c5db199SXin Li    old_timeout = socket.getdefaulttimeout()
556*9c5db199SXin Li    socket.setdefaulttimeout(timeout)
557*9c5db199SXin Li    try:
558*9c5db199SXin Li        return urllib.request.urlopen(url, data=data)
559*9c5db199SXin Li    finally:
560*9c5db199SXin Li        socket.setdefaulttimeout(old_timeout)
561*9c5db199SXin Li
562*9c5db199SXin Li
563*9c5db199SXin Lidef urlretrieve(url, filename, data=None, timeout=300):
564*9c5db199SXin Li    """Retrieve a file from given url."""
565*9c5db199SXin Li    logging.debug('Fetching %s -> %s', url, filename)
566*9c5db199SXin Li
567*9c5db199SXin Li    src_file = urlopen(url, data=data, timeout=timeout)
568*9c5db199SXin Li    try:
569*9c5db199SXin Li        dest_file = open(filename, 'wb')
570*9c5db199SXin Li        try:
571*9c5db199SXin Li            shutil.copyfileobj(src_file, dest_file)
572*9c5db199SXin Li        finally:
573*9c5db199SXin Li            dest_file.close()
574*9c5db199SXin Li    finally:
575*9c5db199SXin Li        src_file.close()
576*9c5db199SXin Li
577*9c5db199SXin Li
578*9c5db199SXin Lidef hash(hashtype, input=None):
579*9c5db199SXin Li    """
580*9c5db199SXin Li    Returns an hash object of type md5 or sha1. This function is implemented in
581*9c5db199SXin Li    order to encapsulate hash objects in a way that is compatible with python
582*9c5db199SXin Li    2.4 and python 2.6 without warnings.
583*9c5db199SXin Li
584*9c5db199SXin Li    Note that even though python 2.6 hashlib supports hash types other than
585*9c5db199SXin Li    md5 and sha1, we are artificially limiting the input values in order to
586*9c5db199SXin Li    make the function to behave exactly the same among both python
587*9c5db199SXin Li    implementations.
588*9c5db199SXin Li
589*9c5db199SXin Li    @param input: Optional input string that will be used to update the hash.
590*9c5db199SXin Li    """
591*9c5db199SXin Li    # pylint: disable=redefined-builtin
592*9c5db199SXin Li    if hashtype not in ['md5', 'sha1']:
593*9c5db199SXin Li        raise ValueError("Unsupported hash type: %s" % hashtype)
594*9c5db199SXin Li
595*9c5db199SXin Li    try:
596*9c5db199SXin Li        computed_hash = hashlib.new(hashtype)
597*9c5db199SXin Li    except NameError:
598*9c5db199SXin Li        if hashtype == 'md5':
599*9c5db199SXin Li            computed_hash = md5.new()
600*9c5db199SXin Li        elif hashtype == 'sha1':
601*9c5db199SXin Li            computed_hash = sha.new()
602*9c5db199SXin Li
603*9c5db199SXin Li    if input:
604*9c5db199SXin Li        try:
605*9c5db199SXin Li            computed_hash.update(input.encode())
606*9c5db199SXin Li        except UnicodeError:
607*9c5db199SXin Li            computed_hash.update(input)
608*9c5db199SXin Li
609*9c5db199SXin Li
610*9c5db199SXin Li    return computed_hash
611*9c5db199SXin Li
612*9c5db199SXin Li
613*9c5db199SXin Lidef get_file(src, dest, permissions=None):
614*9c5db199SXin Li    """Get a file from src, which can be local or a remote URL"""
615*9c5db199SXin Li    if src == dest:
616*9c5db199SXin Li        return
617*9c5db199SXin Li
618*9c5db199SXin Li    if is_url(src):
619*9c5db199SXin Li        urlretrieve(src, dest)
620*9c5db199SXin Li    else:
621*9c5db199SXin Li        shutil.copyfile(src, dest)
622*9c5db199SXin Li
623*9c5db199SXin Li    if permissions:
624*9c5db199SXin Li        os.chmod(dest, permissions)
625*9c5db199SXin Li    return dest
626*9c5db199SXin Li
627*9c5db199SXin Li
628*9c5db199SXin Lidef unmap_url(srcdir, src, destdir='.'):
629*9c5db199SXin Li    """
630*9c5db199SXin Li    Receives either a path to a local file or a URL.
631*9c5db199SXin Li    returns either the path to the local file, or the fetched URL
632*9c5db199SXin Li
633*9c5db199SXin Li    unmap_url('/usr/src', 'foo.tar', '/tmp')
634*9c5db199SXin Li                            = '/usr/src/foo.tar'
635*9c5db199SXin Li    unmap_url('/usr/src', 'http://site/file', '/tmp')
636*9c5db199SXin Li                            = '/tmp/file'
637*9c5db199SXin Li                            (after retrieving it)
638*9c5db199SXin Li    """
639*9c5db199SXin Li    if is_url(src):
640*9c5db199SXin Li        url_parts = six.moves.urllib.parse.urlparse(src)
641*9c5db199SXin Li        filename = os.path.basename(url_parts[2])
642*9c5db199SXin Li        dest = os.path.join(destdir, filename)
643*9c5db199SXin Li        return get_file(src, dest)
644*9c5db199SXin Li    else:
645*9c5db199SXin Li        return os.path.join(srcdir, src)
646*9c5db199SXin Li
647*9c5db199SXin Li
648*9c5db199SXin Lidef update_version(srcdir, preserve_srcdir, new_version, install,
649*9c5db199SXin Li                   *args, **dargs):
650*9c5db199SXin Li    """
651*9c5db199SXin Li    Make sure srcdir is version new_version
652*9c5db199SXin Li
653*9c5db199SXin Li    If not, delete it and install() the new version.
654*9c5db199SXin Li
655*9c5db199SXin Li    In the preserve_srcdir case, we just check it's up to date,
656*9c5db199SXin Li    and if not, we rerun install, without removing srcdir
657*9c5db199SXin Li    """
658*9c5db199SXin Li    versionfile = os.path.join(srcdir, '.version')
659*9c5db199SXin Li    install_needed = True
660*9c5db199SXin Li
661*9c5db199SXin Li    if os.path.exists(versionfile) and os.path.getsize(versionfile) > 0:
662*9c5db199SXin Li        old_version = pickle.load(open(versionfile, 'rb'))
663*9c5db199SXin Li        if old_version == new_version:
664*9c5db199SXin Li            install_needed = False
665*9c5db199SXin Li
666*9c5db199SXin Li    if install_needed:
667*9c5db199SXin Li        if not preserve_srcdir and os.path.exists(srcdir):
668*9c5db199SXin Li            shutil.rmtree(srcdir)
669*9c5db199SXin Li        install(*args, **dargs)
670*9c5db199SXin Li        if os.path.exists(srcdir):
671*9c5db199SXin Li            pickle.dump(new_version, open(versionfile, 'wb'))
672*9c5db199SXin Li
673*9c5db199SXin Li
674*9c5db199SXin Lidef get_stderr_level(stderr_is_expected, stdout_level=DEFAULT_STDOUT_LEVEL):
675*9c5db199SXin Li    if stderr_is_expected:
676*9c5db199SXin Li        return stdout_level
677*9c5db199SXin Li    return DEFAULT_STDERR_LEVEL
678*9c5db199SXin Li
679*9c5db199SXin Li
680*9c5db199SXin Lidef run(command, timeout=None, ignore_status=False, stdout_tee=None,
681*9c5db199SXin Li        stderr_tee=None, verbose=True, stdin=None, stderr_is_expected=None,
682*9c5db199SXin Li        stdout_level=None, stderr_level=None, args=(), nickname=None,
683*9c5db199SXin Li        ignore_timeout=False, env=None, extra_paths=None):
684*9c5db199SXin Li    """
685*9c5db199SXin Li    Run a command on the host.
686*9c5db199SXin Li
687*9c5db199SXin Li    @param command: the command line string.
688*9c5db199SXin Li    @param timeout: time limit in seconds before attempting to kill the
689*9c5db199SXin Li            running process. The run() function will take a few seconds
690*9c5db199SXin Li            longer than 'timeout' to complete if it has to kill the process.
691*9c5db199SXin Li    @param ignore_status: do not raise an exception, no matter what the exit
692*9c5db199SXin Li            code of the command is.
693*9c5db199SXin Li    @param stdout_tee: optional file-like object to which stdout data
694*9c5db199SXin Li            will be written as it is generated (data will still be stored
695*9c5db199SXin Li            in result.stdout unless this is DEVNULL).
696*9c5db199SXin Li    @param stderr_tee: likewise for stderr.
697*9c5db199SXin Li    @param verbose: if True, log the command being run.
698*9c5db199SXin Li    @param stdin: stdin to pass to the executed process (can be a file
699*9c5db199SXin Li            descriptor, a file object of a real file or a string).
700*9c5db199SXin Li    @param stderr_is_expected: if True, stderr will be logged at the same level
701*9c5db199SXin Li            as stdout
702*9c5db199SXin Li    @param stdout_level: logging level used if stdout_tee is TEE_TO_LOGS;
703*9c5db199SXin Li            if None, a default is used.
704*9c5db199SXin Li    @param stderr_level: like stdout_level but for stderr.
705*9c5db199SXin Li    @param args: sequence of strings of arguments to be given to the command
706*9c5db199SXin Li            inside " quotes after they have been escaped for that; each
707*9c5db199SXin Li            element in the sequence will be given as a separate command
708*9c5db199SXin Li            argument
709*9c5db199SXin Li    @param nickname: Short string that will appear in logging messages
710*9c5db199SXin Li                     associated with this command.
711*9c5db199SXin Li    @param ignore_timeout: If True, timeouts are ignored otherwise if a
712*9c5db199SXin Li            timeout occurs it will raise CmdTimeoutError.
713*9c5db199SXin Li    @param env: Dict containing environment variables used in a subprocess.
714*9c5db199SXin Li    @param extra_paths: Optional string list, to be prepended to the PATH
715*9c5db199SXin Li                        env variable in env (or os.environ dict if env is
716*9c5db199SXin Li                        not specified).
717*9c5db199SXin Li
718*9c5db199SXin Li    @return a CmdResult object or None if the command timed out and
719*9c5db199SXin Li            ignore_timeout is True
720*9c5db199SXin Li    @rtype: CmdResult
721*9c5db199SXin Li
722*9c5db199SXin Li    @raise CmdError: the exit code of the command execution was not 0
723*9c5db199SXin Li    @raise CmdTimeoutError: the command timed out and ignore_timeout is False.
724*9c5db199SXin Li    """
725*9c5db199SXin Li    if isinstance(args, six.string_types):
726*9c5db199SXin Li        raise TypeError('Got a string for the "args" keyword argument, '
727*9c5db199SXin Li                        'need a sequence.')
728*9c5db199SXin Li
729*9c5db199SXin Li    # In some cases, command will actually be a list
730*9c5db199SXin Li    # (For example, see get_user_hash in client/cros/cryptohome.py.)
731*9c5db199SXin Li    # So, to cover that case, detect if it's a string or not and convert it
732*9c5db199SXin Li    # into one if necessary.
733*9c5db199SXin Li    if not isinstance(command, six.string_types):
734*9c5db199SXin Li        command = ' '.join([sh_quote_word(arg) for arg in command])
735*9c5db199SXin Li
736*9c5db199SXin Li    command = ' '.join([command] + [sh_quote_word(arg) for arg in args])
737*9c5db199SXin Li
738*9c5db199SXin Li    if stderr_is_expected is None:
739*9c5db199SXin Li        stderr_is_expected = ignore_status
740*9c5db199SXin Li    if stdout_level is None:
741*9c5db199SXin Li        stdout_level = DEFAULT_STDOUT_LEVEL
742*9c5db199SXin Li    if stderr_level is None:
743*9c5db199SXin Li        stderr_level = get_stderr_level(stderr_is_expected, stdout_level)
744*9c5db199SXin Li
745*9c5db199SXin Li    try:
746*9c5db199SXin Li        bg_job = join_bg_jobs(
747*9c5db199SXin Li            (BgJob(command, stdout_tee, stderr_tee, verbose, stdin=stdin,
748*9c5db199SXin Li                   stdout_level=stdout_level, stderr_level=stderr_level,
749*9c5db199SXin Li                   nickname=nickname, env=env, extra_paths=extra_paths),),
750*9c5db199SXin Li            timeout)[0]
751*9c5db199SXin Li    except error.CmdTimeoutError:
752*9c5db199SXin Li        if not ignore_timeout:
753*9c5db199SXin Li            raise
754*9c5db199SXin Li        return None
755*9c5db199SXin Li
756*9c5db199SXin Li    if not ignore_status and bg_job.result.exit_status:
757*9c5db199SXin Li        raise error.CmdError(command, bg_job.result,
758*9c5db199SXin Li                             "Command returned non-zero exit status")
759*9c5db199SXin Li
760*9c5db199SXin Li    return bg_job.result
761*9c5db199SXin Li
762*9c5db199SXin Li
763*9c5db199SXin Lidef run_parallel(commands, timeout=None, ignore_status=False,
764*9c5db199SXin Li                 stdout_tee=None, stderr_tee=None,
765*9c5db199SXin Li                 nicknames=None):
766*9c5db199SXin Li    """
767*9c5db199SXin Li    Behaves the same as run() with the following exceptions:
768*9c5db199SXin Li
769*9c5db199SXin Li    - commands is a list of commands to run in parallel.
770*9c5db199SXin Li    - ignore_status toggles whether or not an exception should be raised
771*9c5db199SXin Li      on any error.
772*9c5db199SXin Li
773*9c5db199SXin Li    @return: a list of CmdResult objects
774*9c5db199SXin Li    """
775*9c5db199SXin Li    bg_jobs = []
776*9c5db199SXin Li    if nicknames is None:
777*9c5db199SXin Li        nicknames = []
778*9c5db199SXin Li    for (command, nickname) in zip_longest(commands, nicknames):
779*9c5db199SXin Li        bg_jobs.append(BgJob(command, stdout_tee, stderr_tee,
780*9c5db199SXin Li                             stderr_level=get_stderr_level(ignore_status),
781*9c5db199SXin Li                             nickname=nickname))
782*9c5db199SXin Li
783*9c5db199SXin Li    # Updates objects in bg_jobs list with their process information
784*9c5db199SXin Li    join_bg_jobs(bg_jobs, timeout)
785*9c5db199SXin Li
786*9c5db199SXin Li    for bg_job in bg_jobs:
787*9c5db199SXin Li        if not ignore_status and bg_job.result.exit_status:
788*9c5db199SXin Li            raise error.CmdError(command, bg_job.result,
789*9c5db199SXin Li                                 "Command returned non-zero exit status")
790*9c5db199SXin Li
791*9c5db199SXin Li    return [bg_job.result for bg_job in bg_jobs]
792*9c5db199SXin Li
793*9c5db199SXin Li
794*9c5db199SXin Li@deprecated
795*9c5db199SXin Lidef run_bg(command):
796*9c5db199SXin Li    """Function deprecated. Please use BgJob class instead."""
797*9c5db199SXin Li    bg_job = BgJob(command)
798*9c5db199SXin Li    return bg_job.sp, bg_job.result
799*9c5db199SXin Li
800*9c5db199SXin Li
801*9c5db199SXin Lidef join_bg_jobs(bg_jobs, timeout=None):
802*9c5db199SXin Li    """Joins the bg_jobs with the current thread.
803*9c5db199SXin Li
804*9c5db199SXin Li    Returns the same list of bg_jobs objects that was passed in.
805*9c5db199SXin Li    """
806*9c5db199SXin Li    if any(bg_job.unjoinable for bg_job in bg_jobs):
807*9c5db199SXin Li        raise error.InvalidBgJobCall(
808*9c5db199SXin Li                'join_bg_jobs cannot be called for unjoinable bg_job')
809*9c5db199SXin Li
810*9c5db199SXin Li    timeout_error = False
811*9c5db199SXin Li    try:
812*9c5db199SXin Li        # We are holding ends to stdin, stdout pipes
813*9c5db199SXin Li        # hence we need to be sure to close those fds no mater what
814*9c5db199SXin Li        start_time = time.time()
815*9c5db199SXin Li        timeout_error = _wait_for_commands(bg_jobs, start_time, timeout)
816*9c5db199SXin Li
817*9c5db199SXin Li        for bg_job in bg_jobs:
818*9c5db199SXin Li            # Process stdout and stderr
819*9c5db199SXin Li            bg_job.process_output(stdout=True,final_read=True)
820*9c5db199SXin Li            bg_job.process_output(stdout=False,final_read=True)
821*9c5db199SXin Li    finally:
822*9c5db199SXin Li        # close our ends of the pipes to the sp no matter what
823*9c5db199SXin Li        for bg_job in bg_jobs:
824*9c5db199SXin Li            bg_job.cleanup()
825*9c5db199SXin Li
826*9c5db199SXin Li    if timeout_error:
827*9c5db199SXin Li        # TODO: This needs to be fixed to better represent what happens when
828*9c5db199SXin Li        # running in parallel. However this is backwards compatable, so it will
829*9c5db199SXin Li        # do for the time being.
830*9c5db199SXin Li        raise error.CmdTimeoutError(
831*9c5db199SXin Li                bg_jobs[0].command, bg_jobs[0].result,
832*9c5db199SXin Li                "Command(s) did not complete within %d seconds" % timeout)
833*9c5db199SXin Li
834*9c5db199SXin Li
835*9c5db199SXin Li    return bg_jobs
836*9c5db199SXin Li
837*9c5db199SXin Li
838*9c5db199SXin Lidef _wait_for_commands(bg_jobs, start_time, timeout):
839*9c5db199SXin Li    """Waits for background jobs by select polling their stdout/stderr.
840*9c5db199SXin Li
841*9c5db199SXin Li    @param bg_jobs: A list of background jobs to wait on.
842*9c5db199SXin Li    @param start_time: Time used to calculate the timeout lifetime of a job.
843*9c5db199SXin Li    @param timeout: The timeout of the list of bg_jobs.
844*9c5db199SXin Li
845*9c5db199SXin Li    @return: True if the return was due to a timeout, False otherwise.
846*9c5db199SXin Li    """
847*9c5db199SXin Li
848*9c5db199SXin Li    # To check for processes which terminate without producing any output
849*9c5db199SXin Li    # a 1 second timeout is used in select.
850*9c5db199SXin Li    SELECT_TIMEOUT = 1
851*9c5db199SXin Li
852*9c5db199SXin Li    read_list = []
853*9c5db199SXin Li    write_list = []
854*9c5db199SXin Li    reverse_dict = {}
855*9c5db199SXin Li
856*9c5db199SXin Li    for bg_job in bg_jobs:
857*9c5db199SXin Li        if bg_job.sp.stdout:
858*9c5db199SXin Li            read_list.append(bg_job.sp.stdout)
859*9c5db199SXin Li            reverse_dict[bg_job.sp.stdout] = (bg_job, True)
860*9c5db199SXin Li        if bg_job.sp.stderr:
861*9c5db199SXin Li            read_list.append(bg_job.sp.stderr)
862*9c5db199SXin Li            reverse_dict[bg_job.sp.stderr] = (bg_job, False)
863*9c5db199SXin Li        if bg_job.string_stdin is not None:
864*9c5db199SXin Li            write_list.append(bg_job.sp.stdin)
865*9c5db199SXin Li            reverse_dict[bg_job.sp.stdin] = bg_job
866*9c5db199SXin Li
867*9c5db199SXin Li    if timeout:
868*9c5db199SXin Li        stop_time = start_time + timeout
869*9c5db199SXin Li        time_left = stop_time - time.time()
870*9c5db199SXin Li    else:
871*9c5db199SXin Li        time_left = None # so that select never times out
872*9c5db199SXin Li
873*9c5db199SXin Li    while not timeout or time_left > 0:
874*9c5db199SXin Li        # select will return when we may write to stdin, when there is
875*9c5db199SXin Li        # stdout/stderr output we can read (including when it is
876*9c5db199SXin Li        # EOF, that is the process has terminated) or when a non-fatal
877*9c5db199SXin Li        # signal was sent to the process. In the last case the select returns
878*9c5db199SXin Li        # EINTR, and we continue waiting for the job if the signal handler for
879*9c5db199SXin Li        # the signal that interrupted the call allows us to.
880*9c5db199SXin Li        try:
881*9c5db199SXin Li            read_ready, write_ready, _ = select.select(read_list, write_list,
882*9c5db199SXin Li                                                       [], SELECT_TIMEOUT)
883*9c5db199SXin Li        except select.error as v:
884*9c5db199SXin Li            if v.args[0] == errno.EINTR:
885*9c5db199SXin Li                logging.warning(v)
886*9c5db199SXin Li                continue
887*9c5db199SXin Li            else:
888*9c5db199SXin Li                raise
889*9c5db199SXin Li        # os.read() has to be used instead of
890*9c5db199SXin Li        # subproc.stdout.read() which will otherwise block
891*9c5db199SXin Li        for file_obj in read_ready:
892*9c5db199SXin Li            bg_job, is_stdout = reverse_dict[file_obj]
893*9c5db199SXin Li            bg_job.process_output(is_stdout)
894*9c5db199SXin Li
895*9c5db199SXin Li        for file_obj in write_ready:
896*9c5db199SXin Li            # we can write PIPE_BUF bytes without blocking
897*9c5db199SXin Li            # POSIX requires PIPE_BUF is >= 512
898*9c5db199SXin Li            bg_job = reverse_dict[file_obj]
899*9c5db199SXin Li            string_stdin = bg_job.string_stdin[:512]
900*9c5db199SXin Li            if isinstance(string_stdin, six.text_type):
901*9c5db199SXin Li                string_stdin = string_stdin.encode('utf-8', 'strict')
902*9c5db199SXin Li            file_obj.write(string_stdin)
903*9c5db199SXin Li            bg_job.string_stdin = bg_job.string_stdin[512:]
904*9c5db199SXin Li            # no more input data, close stdin, remove it from the select set
905*9c5db199SXin Li            if not bg_job.string_stdin:
906*9c5db199SXin Li                file_obj.close()
907*9c5db199SXin Li                write_list.remove(file_obj)
908*9c5db199SXin Li                del reverse_dict[file_obj]
909*9c5db199SXin Li
910*9c5db199SXin Li        all_jobs_finished = True
911*9c5db199SXin Li        for bg_job in bg_jobs:
912*9c5db199SXin Li            if bg_job.result.exit_status is not None:
913*9c5db199SXin Li                continue
914*9c5db199SXin Li
915*9c5db199SXin Li            bg_job.result.exit_status = bg_job.sp.poll()
916*9c5db199SXin Li            if bg_job.result.exit_status is not None:
917*9c5db199SXin Li                # process exited, remove its stdout/stdin from the select set
918*9c5db199SXin Li                bg_job.result.duration = time.time() - start_time
919*9c5db199SXin Li                if bg_job.sp.stdout:
920*9c5db199SXin Li                    read_list.remove(bg_job.sp.stdout)
921*9c5db199SXin Li                    del reverse_dict[bg_job.sp.stdout]
922*9c5db199SXin Li                if bg_job.sp.stderr:
923*9c5db199SXin Li                    read_list.remove(bg_job.sp.stderr)
924*9c5db199SXin Li                    del reverse_dict[bg_job.sp.stderr]
925*9c5db199SXin Li            else:
926*9c5db199SXin Li                all_jobs_finished = False
927*9c5db199SXin Li
928*9c5db199SXin Li        if all_jobs_finished:
929*9c5db199SXin Li            return False
930*9c5db199SXin Li
931*9c5db199SXin Li        if timeout:
932*9c5db199SXin Li            time_left = stop_time - time.time()
933*9c5db199SXin Li
934*9c5db199SXin Li    # Kill all processes which did not complete prior to timeout
935*9c5db199SXin Li    for bg_job in bg_jobs:
936*9c5db199SXin Li        if bg_job.result.exit_status is not None:
937*9c5db199SXin Li            continue
938*9c5db199SXin Li
939*9c5db199SXin Li        logging.warning('run process timeout (%s) fired on: %s', timeout,
940*9c5db199SXin Li                        bg_job.command)
941*9c5db199SXin Li        if nuke_subprocess(bg_job.sp) is None:
942*9c5db199SXin Li            # If process could not be SIGKILL'd, log kernel stack.
943*9c5db199SXin Li            logging.warning(read_file('/proc/%d/stack' % bg_job.sp.pid))
944*9c5db199SXin Li        bg_job.result.exit_status = bg_job.sp.poll()
945*9c5db199SXin Li        bg_job.result.duration = time.time() - start_time
946*9c5db199SXin Li
947*9c5db199SXin Li    return True
948*9c5db199SXin Li
949*9c5db199SXin Li
950*9c5db199SXin Lidef pid_is_alive(pid):
951*9c5db199SXin Li    """
952*9c5db199SXin Li    True if process pid exists and is not yet stuck in Zombie state.
953*9c5db199SXin Li    Zombies are impossible to move between cgroups, etc.
954*9c5db199SXin Li    pid can be integer, or text of integer.
955*9c5db199SXin Li    """
956*9c5db199SXin Li    path = '/proc/%s/stat' % pid
957*9c5db199SXin Li
958*9c5db199SXin Li    try:
959*9c5db199SXin Li        stat = read_one_line(path)
960*9c5db199SXin Li    except IOError:
961*9c5db199SXin Li        if not os.path.exists(path):
962*9c5db199SXin Li            # file went away
963*9c5db199SXin Li            return False
964*9c5db199SXin Li        raise
965*9c5db199SXin Li
966*9c5db199SXin Li    return stat.split()[2] != 'Z'
967*9c5db199SXin Li
968*9c5db199SXin Li
969*9c5db199SXin Lidef signal_pid(pid, sig):
970*9c5db199SXin Li    """
971*9c5db199SXin Li    Sends a signal to a process id. Returns True if the process terminated
972*9c5db199SXin Li    successfully, False otherwise.
973*9c5db199SXin Li    """
974*9c5db199SXin Li    try:
975*9c5db199SXin Li        os.kill(pid, sig)
976*9c5db199SXin Li    except OSError:
977*9c5db199SXin Li        # The process may have died before we could kill it.
978*9c5db199SXin Li        pass
979*9c5db199SXin Li
980*9c5db199SXin Li    for _ in range(5):
981*9c5db199SXin Li        if not pid_is_alive(pid):
982*9c5db199SXin Li            return True
983*9c5db199SXin Li        time.sleep(1)
984*9c5db199SXin Li
985*9c5db199SXin Li    # The process is still alive
986*9c5db199SXin Li    return False
987*9c5db199SXin Li
988*9c5db199SXin Li
989*9c5db199SXin Lidef nuke_subprocess(subproc):
990*9c5db199SXin Li    # check if the subprocess is still alive, first
991*9c5db199SXin Li    if subproc.poll() is not None:
992*9c5db199SXin Li        return subproc.poll()
993*9c5db199SXin Li
994*9c5db199SXin Li    # the process has not terminated within timeout,
995*9c5db199SXin Li    # kill it via an escalating series of signals.
996*9c5db199SXin Li    signal_queue = [signal.SIGTERM, signal.SIGKILL]
997*9c5db199SXin Li    for sig in signal_queue:
998*9c5db199SXin Li        signal_pid(subproc.pid, sig)
999*9c5db199SXin Li        if subproc.poll() is not None:
1000*9c5db199SXin Li            return subproc.poll()
1001*9c5db199SXin Li
1002*9c5db199SXin Li
1003*9c5db199SXin Lidef nuke_pid(pid, signal_queue=(signal.SIGTERM, signal.SIGKILL)):
1004*9c5db199SXin Li    # the process has not terminated within timeout,
1005*9c5db199SXin Li    # kill it via an escalating series of signals.
1006*9c5db199SXin Li    pid_path = '/proc/%d/'
1007*9c5db199SXin Li    if not os.path.exists(pid_path % pid):
1008*9c5db199SXin Li        # Assume that if the pid does not exist in proc it is already dead.
1009*9c5db199SXin Li        logging.error('No listing in /proc for pid:%d.', pid)
1010*9c5db199SXin Li        raise error.AutoservPidAlreadyDeadError('Could not kill nonexistant '
1011*9c5db199SXin Li                                                'pid: %s.', pid)
1012*9c5db199SXin Li    for sig in signal_queue:
1013*9c5db199SXin Li        if signal_pid(pid, sig):
1014*9c5db199SXin Li            return
1015*9c5db199SXin Li
1016*9c5db199SXin Li    # no signal successfully terminated the process
1017*9c5db199SXin Li    raise error.AutoservRunError('Could not kill %d for process name: %s' % (
1018*9c5db199SXin Li            pid, get_process_name(pid)), None)
1019*9c5db199SXin Li
1020*9c5db199SXin Li
1021*9c5db199SXin Lidef system(command, timeout=None, ignore_status=False):
1022*9c5db199SXin Li    """
1023*9c5db199SXin Li    Run a command
1024*9c5db199SXin Li
1025*9c5db199SXin Li    @param timeout: timeout in seconds
1026*9c5db199SXin Li    @param ignore_status: if ignore_status=False, throw an exception if the
1027*9c5db199SXin Li            command's exit code is non-zero
1028*9c5db199SXin Li            if ignore_stauts=True, return the exit code.
1029*9c5db199SXin Li
1030*9c5db199SXin Li    @return exit status of command
1031*9c5db199SXin Li            (note, this will always be zero unless ignore_status=True)
1032*9c5db199SXin Li    """
1033*9c5db199SXin Li    return run(command, timeout=timeout, ignore_status=ignore_status,
1034*9c5db199SXin Li               stdout_tee=TEE_TO_LOGS, stderr_tee=TEE_TO_LOGS).exit_status
1035*9c5db199SXin Li
1036*9c5db199SXin Li
1037*9c5db199SXin Lidef system_parallel(commands, timeout=None, ignore_status=False):
1038*9c5db199SXin Li    """This function returns a list of exit statuses for the respective
1039*9c5db199SXin Li    list of commands."""
1040*9c5db199SXin Li    return [bg_jobs.exit_status for bg_jobs in
1041*9c5db199SXin Li            run_parallel(commands, timeout=timeout, ignore_status=ignore_status,
1042*9c5db199SXin Li                         stdout_tee=TEE_TO_LOGS, stderr_tee=TEE_TO_LOGS)]
1043*9c5db199SXin Li
1044*9c5db199SXin Li
1045*9c5db199SXin Lidef system_output(command, timeout=None, ignore_status=False,
1046*9c5db199SXin Li                  retain_output=False, args=()):
1047*9c5db199SXin Li    """
1048*9c5db199SXin Li    Run a command and return the stdout output.
1049*9c5db199SXin Li
1050*9c5db199SXin Li    @param command: command string to execute.
1051*9c5db199SXin Li    @param timeout: time limit in seconds before attempting to kill the
1052*9c5db199SXin Li            running process. The function will take a few seconds longer
1053*9c5db199SXin Li            than 'timeout' to complete if it has to kill the process.
1054*9c5db199SXin Li    @param ignore_status: do not raise an exception, no matter what the exit
1055*9c5db199SXin Li            code of the command is.
1056*9c5db199SXin Li    @param retain_output: set to True to make stdout/stderr of the command
1057*9c5db199SXin Li            output to be also sent to the logging system
1058*9c5db199SXin Li    @param args: sequence of strings of arguments to be given to the command
1059*9c5db199SXin Li            inside " quotes after they have been escaped for that; each
1060*9c5db199SXin Li            element in the sequence will be given as a separate command
1061*9c5db199SXin Li            argument
1062*9c5db199SXin Li
1063*9c5db199SXin Li    @return a string with the stdout output of the command.
1064*9c5db199SXin Li    """
1065*9c5db199SXin Li    if retain_output:
1066*9c5db199SXin Li        out = run(command, timeout=timeout, ignore_status=ignore_status,
1067*9c5db199SXin Li                  stdout_tee=TEE_TO_LOGS, stderr_tee=TEE_TO_LOGS,
1068*9c5db199SXin Li                  args=args).stdout
1069*9c5db199SXin Li    else:
1070*9c5db199SXin Li        out = run(command, timeout=timeout, ignore_status=ignore_status,
1071*9c5db199SXin Li                  args=args).stdout
1072*9c5db199SXin Li    if out[-1:] == '\n':
1073*9c5db199SXin Li        out = out[:-1]
1074*9c5db199SXin Li    return out
1075*9c5db199SXin Li
1076*9c5db199SXin Li
1077*9c5db199SXin Lidef system_output_parallel(commands, timeout=None, ignore_status=False,
1078*9c5db199SXin Li                           retain_output=False):
1079*9c5db199SXin Li    if retain_output:
1080*9c5db199SXin Li        out = [bg_job.stdout for bg_job
1081*9c5db199SXin Li               in run_parallel(commands, timeout=timeout,
1082*9c5db199SXin Li                               ignore_status=ignore_status,
1083*9c5db199SXin Li                               stdout_tee=TEE_TO_LOGS, stderr_tee=TEE_TO_LOGS)]
1084*9c5db199SXin Li    else:
1085*9c5db199SXin Li        out = [bg_job.stdout for bg_job in run_parallel(commands,
1086*9c5db199SXin Li                                  timeout=timeout, ignore_status=ignore_status)]
1087*9c5db199SXin Li    for _ in out:
1088*9c5db199SXin Li        if out[-1:] == '\n':
1089*9c5db199SXin Li            out = out[:-1]
1090*9c5db199SXin Li    return out
1091*9c5db199SXin Li
1092*9c5db199SXin Li
1093*9c5db199SXin Lidef strip_unicode(input_obj):
1094*9c5db199SXin Li    if type(input_obj) == list:
1095*9c5db199SXin Li        return [strip_unicode(i) for i in input_obj]
1096*9c5db199SXin Li    elif type(input_obj) == dict:
1097*9c5db199SXin Li        output = {}
1098*9c5db199SXin Li        for key in input_obj.keys():
1099*9c5db199SXin Li            output[str(key)] = strip_unicode(input_obj[key])
1100*9c5db199SXin Li        return output
1101*9c5db199SXin Li    elif type(input_obj) == six.text_type:
1102*9c5db199SXin Li        return str(input_obj)
1103*9c5db199SXin Li    else:
1104*9c5db199SXin Li        return input_obj
1105*9c5db199SXin Li
1106*9c5db199SXin Li
1107*9c5db199SXin Lidef get_cpu_percentage(function, *args, **dargs):
1108*9c5db199SXin Li    """Returns a tuple containing the CPU% and return value from function call.
1109*9c5db199SXin Li
1110*9c5db199SXin Li    This function calculates the usage time by taking the difference of
1111*9c5db199SXin Li    the user and system times both before and after the function call.
1112*9c5db199SXin Li    """
1113*9c5db199SXin Li    child_pre = resource.getrusage(resource.RUSAGE_CHILDREN)
1114*9c5db199SXin Li    self_pre = resource.getrusage(resource.RUSAGE_SELF)
1115*9c5db199SXin Li    start = time.time()
1116*9c5db199SXin Li    to_return = function(*args, **dargs)
1117*9c5db199SXin Li    elapsed = time.time() - start
1118*9c5db199SXin Li    self_post = resource.getrusage(resource.RUSAGE_SELF)
1119*9c5db199SXin Li    child_post = resource.getrusage(resource.RUSAGE_CHILDREN)
1120*9c5db199SXin Li
1121*9c5db199SXin Li    # Calculate CPU Percentage
1122*9c5db199SXin Li    s_user, s_system = [a - b for a, b in zip(self_post, self_pre)[:2]]
1123*9c5db199SXin Li    c_user, c_system = [a - b for a, b in zip(child_post, child_pre)[:2]]
1124*9c5db199SXin Li    cpu_percent = (s_user + c_user + s_system + c_system) / elapsed
1125*9c5db199SXin Li
1126*9c5db199SXin Li    return cpu_percent, to_return
1127*9c5db199SXin Li
1128*9c5db199SXin Li
1129*9c5db199SXin Lidef get_arch(run_function=run):
1130*9c5db199SXin Li    """
1131*9c5db199SXin Li    Get the hardware architecture of the machine.
1132*9c5db199SXin Li    If specified, run_function should return a CmdResult object and throw a
1133*9c5db199SXin Li    CmdError exception.
1134*9c5db199SXin Li    If run_function is anything other than utils.run(), it is used to
1135*9c5db199SXin Li    execute the commands. By default (when set to utils.run()) this will
1136*9c5db199SXin Li    just examine os.uname()[4].
1137*9c5db199SXin Li    """
1138*9c5db199SXin Li
1139*9c5db199SXin Li    # Short circuit from the common case.
1140*9c5db199SXin Li    if run_function == run:
1141*9c5db199SXin Li        return re.sub(r'i\d86$', 'i386', os.uname()[4])
1142*9c5db199SXin Li
1143*9c5db199SXin Li    # Otherwise, use the run_function in case it hits a remote machine.
1144*9c5db199SXin Li    arch = run_function('/bin/uname -m').stdout.rstrip()
1145*9c5db199SXin Li    if re.match(r'i\d86$', arch):
1146*9c5db199SXin Li        arch = 'i386'
1147*9c5db199SXin Li    return arch
1148*9c5db199SXin Li
1149*9c5db199SXin Lidef get_arch_userspace(run_function=run):
1150*9c5db199SXin Li    """
1151*9c5db199SXin Li    Get the architecture by userspace (possibly different from kernel).
1152*9c5db199SXin Li    """
1153*9c5db199SXin Li    archs = {
1154*9c5db199SXin Li        'arm': 'ELF 32-bit.*, ARM,',
1155*9c5db199SXin Li        'arm64': 'ELF 64-bit.*, ARM aarch64,',
1156*9c5db199SXin Li        'i386': 'ELF 32-bit.*, Intel 80386,',
1157*9c5db199SXin Li        'x86_64': 'ELF 64-bit.*, x86-64,',
1158*9c5db199SXin Li    }
1159*9c5db199SXin Li
1160*9c5db199SXin Li    cmd = 'file --brief --dereference /bin/sh'
1161*9c5db199SXin Li    filestr = run_function(cmd).stdout.rstrip()
1162*9c5db199SXin Li    for a, regex in six.iteritems(archs):
1163*9c5db199SXin Li        if re.match(regex, filestr):
1164*9c5db199SXin Li            return a
1165*9c5db199SXin Li
1166*9c5db199SXin Li    return get_arch()
1167*9c5db199SXin Li
1168*9c5db199SXin Li
1169*9c5db199SXin Lidef get_num_logical_cpus_per_socket(run_function=run):
1170*9c5db199SXin Li    """
1171*9c5db199SXin Li    Get the number of cores (including hyperthreading) per cpu.
1172*9c5db199SXin Li    run_function is used to execute the commands. It defaults to
1173*9c5db199SXin Li    utils.run() but a custom method (if provided) should be of the
1174*9c5db199SXin Li    same schema as utils.run. It should return a CmdResult object and
1175*9c5db199SXin Li    throw a CmdError exception.
1176*9c5db199SXin Li    """
1177*9c5db199SXin Li    siblings = run_function('grep "^siblings" /proc/cpuinfo').stdout.rstrip()
1178*9c5db199SXin Li    num_siblings = [int(x) for x in
1179*9c5db199SXin Li                    re.findall(r'^siblings\s*:\s*(\d+)\s*$', siblings, re.M)]
1180*9c5db199SXin Li    if len(num_siblings) == 0:
1181*9c5db199SXin Li        raise error.TestError('Unable to find siblings info in /proc/cpuinfo')
1182*9c5db199SXin Li    if min(num_siblings) != max(num_siblings):
1183*9c5db199SXin Li        raise error.TestError('Number of siblings differ %r' %
1184*9c5db199SXin Li                              num_siblings)
1185*9c5db199SXin Li    return num_siblings[0]
1186*9c5db199SXin Li
1187*9c5db199SXin Li
1188*9c5db199SXin Lidef set_high_performance_mode(host=None):
1189*9c5db199SXin Li    """
1190*9c5db199SXin Li    Sets the kernel governor mode to the highest setting.
1191*9c5db199SXin Li    Returns previous governor state.
1192*9c5db199SXin Li    """
1193*9c5db199SXin Li    original_governors = get_scaling_governor_states(host)
1194*9c5db199SXin Li    set_scaling_governors('performance', host)
1195*9c5db199SXin Li    return original_governors
1196*9c5db199SXin Li
1197*9c5db199SXin Li
1198*9c5db199SXin Lidef set_scaling_governors(value, host=None):
1199*9c5db199SXin Li    """
1200*9c5db199SXin Li    Sets all scaling governor to string value.
1201*9c5db199SXin Li    Sample values: 'performance', 'interactive', 'ondemand', 'powersave'.
1202*9c5db199SXin Li    """
1203*9c5db199SXin Li    paths = _get_cpufreq_paths('scaling_governor', host)
1204*9c5db199SXin Li    if not paths:
1205*9c5db199SXin Li        logging.info("Could not set governor states, as no files of the form "
1206*9c5db199SXin Li                     "'/sys/devices/system/cpu/cpu*/cpufreq/scaling_governor' "
1207*9c5db199SXin Li                     "were found.")
1208*9c5db199SXin Li    run_func = host.run if host else system
1209*9c5db199SXin Li    for path in paths:
1210*9c5db199SXin Li        cmd = 'echo %s > %s' % (value, path)
1211*9c5db199SXin Li        logging.info('Writing scaling governor mode \'%s\' -> %s', value, path)
1212*9c5db199SXin Li        # On Tegra CPUs can be dynamically enabled/disabled. Ignore failures.
1213*9c5db199SXin Li        run_func(cmd, ignore_status=True)
1214*9c5db199SXin Li
1215*9c5db199SXin Li
1216*9c5db199SXin Lidef _get_cpufreq_paths(filename, host=None):
1217*9c5db199SXin Li    """
1218*9c5db199SXin Li    Returns a list of paths to the governors.
1219*9c5db199SXin Li    """
1220*9c5db199SXin Li    run_func = host.run if host else run
1221*9c5db199SXin Li    glob = '/sys/devices/system/cpu/cpu*/cpufreq/' + filename
1222*9c5db199SXin Li    # Simple glob expansion; note that CPUs may come and go, causing these
1223*9c5db199SXin Li    # paths to change at any time.
1224*9c5db199SXin Li    cmd = 'echo ' + glob
1225*9c5db199SXin Li    try:
1226*9c5db199SXin Li        paths = run_func(cmd, verbose=False).stdout.split()
1227*9c5db199SXin Li    except error.CmdError:
1228*9c5db199SXin Li        return []
1229*9c5db199SXin Li    # If the glob result equals itself, then we likely didn't match any real
1230*9c5db199SXin Li    # paths (assuming 'cpu*' is not a real path).
1231*9c5db199SXin Li    if paths == [glob]:
1232*9c5db199SXin Li        return []
1233*9c5db199SXin Li    return paths
1234*9c5db199SXin Li
1235*9c5db199SXin Li
1236*9c5db199SXin Lidef get_scaling_governor_states(host=None):
1237*9c5db199SXin Li    """
1238*9c5db199SXin Li    Returns a list of (performance governor path, current state) tuples.
1239*9c5db199SXin Li    """
1240*9c5db199SXin Li    paths = _get_cpufreq_paths('scaling_governor', host)
1241*9c5db199SXin Li    path_value_list = []
1242*9c5db199SXin Li    run_func = host.run if host else run
1243*9c5db199SXin Li    for path in paths:
1244*9c5db199SXin Li        value = run_func('head -n 1 %s' % path, verbose=False).stdout
1245*9c5db199SXin Li        path_value_list.append((path, value))
1246*9c5db199SXin Li    return path_value_list
1247*9c5db199SXin Li
1248*9c5db199SXin Li
1249*9c5db199SXin Lidef restore_scaling_governor_states(path_value_list, host=None):
1250*9c5db199SXin Li    """
1251*9c5db199SXin Li    Restores governor states. Inverse operation to get_scaling_governor_states.
1252*9c5db199SXin Li    """
1253*9c5db199SXin Li    run_func = host.run if host else system
1254*9c5db199SXin Li    for (path, value) in path_value_list:
1255*9c5db199SXin Li        cmd = 'echo %s > %s' % (value.rstrip('\n'), path)
1256*9c5db199SXin Li        # On Tegra CPUs can be dynamically enabled/disabled. Ignore failures.
1257*9c5db199SXin Li        run_func(cmd, ignore_status=True)
1258*9c5db199SXin Li
1259*9c5db199SXin Li
1260*9c5db199SXin Lidef merge_trees(src, dest):
1261*9c5db199SXin Li    """
1262*9c5db199SXin Li    Merges a source directory tree at 'src' into a destination tree at
1263*9c5db199SXin Li    'dest'. If a path is a file in both trees than the file in the source
1264*9c5db199SXin Li    tree is APPENDED to the one in the destination tree. If a path is
1265*9c5db199SXin Li    a directory in both trees then the directories are recursively merged
1266*9c5db199SXin Li    with this function. In any other case, the function will skip the
1267*9c5db199SXin Li    paths that cannot be merged (instead of failing).
1268*9c5db199SXin Li    """
1269*9c5db199SXin Li    if not os.path.exists(src):
1270*9c5db199SXin Li        return # exists only in dest
1271*9c5db199SXin Li    elif not os.path.exists(dest):
1272*9c5db199SXin Li        if os.path.isfile(src):
1273*9c5db199SXin Li            shutil.copy2(src, dest) # file only in src
1274*9c5db199SXin Li        else:
1275*9c5db199SXin Li            shutil.copytree(src, dest, symlinks=True) # dir only in src
1276*9c5db199SXin Li        return
1277*9c5db199SXin Li    elif os.path.isfile(src) and os.path.isfile(dest):
1278*9c5db199SXin Li        # src & dest are files in both trees, append src to dest
1279*9c5db199SXin Li        destfile = open(dest, "a")
1280*9c5db199SXin Li        try:
1281*9c5db199SXin Li            srcfile = open(src)
1282*9c5db199SXin Li            try:
1283*9c5db199SXin Li                destfile.write(srcfile.read())
1284*9c5db199SXin Li            finally:
1285*9c5db199SXin Li                srcfile.close()
1286*9c5db199SXin Li        finally:
1287*9c5db199SXin Li            destfile.close()
1288*9c5db199SXin Li    elif os.path.isdir(src) and os.path.isdir(dest):
1289*9c5db199SXin Li        # src & dest are directories in both trees, so recursively merge
1290*9c5db199SXin Li        for name in os.listdir(src):
1291*9c5db199SXin Li            merge_trees(os.path.join(src, name), os.path.join(dest, name))
1292*9c5db199SXin Li    else:
1293*9c5db199SXin Li        # src & dest both exist, but are incompatible
1294*9c5db199SXin Li        return
1295*9c5db199SXin Li
1296*9c5db199SXin Li
1297*9c5db199SXin Liclass CmdResult(object):
1298*9c5db199SXin Li    """
1299*9c5db199SXin Li    Command execution result.
1300*9c5db199SXin Li
1301*9c5db199SXin Li    command:     String containing the command line itself
1302*9c5db199SXin Li    exit_status: Integer exit code of the process
1303*9c5db199SXin Li    stdout:      String containing stdout of the process
1304*9c5db199SXin Li    stderr:      String containing stderr of the process
1305*9c5db199SXin Li    duration:    Elapsed wall clock time running the process
1306*9c5db199SXin Li    """
1307*9c5db199SXin Li
1308*9c5db199SXin Li
1309*9c5db199SXin Li    def __init__(self, command="", stdout="", stderr="",
1310*9c5db199SXin Li                 exit_status=None, duration=0):
1311*9c5db199SXin Li        self.command = command
1312*9c5db199SXin Li        self.exit_status = exit_status
1313*9c5db199SXin Li        self.stdout = stdout
1314*9c5db199SXin Li        self.stderr = stderr
1315*9c5db199SXin Li        self.duration = duration
1316*9c5db199SXin Li
1317*9c5db199SXin Li
1318*9c5db199SXin Li    def __eq__(self, other):
1319*9c5db199SXin Li        if type(self) == type(other):
1320*9c5db199SXin Li            return (self.command == other.command
1321*9c5db199SXin Li                    and self.exit_status == other.exit_status
1322*9c5db199SXin Li                    and self.stdout == other.stdout
1323*9c5db199SXin Li                    and self.stderr == other.stderr
1324*9c5db199SXin Li                    and self.duration == other.duration)
1325*9c5db199SXin Li        else:
1326*9c5db199SXin Li            return NotImplemented
1327*9c5db199SXin Li
1328*9c5db199SXin Li
1329*9c5db199SXin Li    def __repr__(self):
1330*9c5db199SXin Li        wrapper = textwrap.TextWrapper(width = 78,
1331*9c5db199SXin Li                                       initial_indent="\n    ",
1332*9c5db199SXin Li                                       subsequent_indent="    ")
1333*9c5db199SXin Li
1334*9c5db199SXin Li        stdout = self.stdout.rstrip()
1335*9c5db199SXin Li        if stdout:
1336*9c5db199SXin Li            stdout = "\nstdout:\n%s" % stdout
1337*9c5db199SXin Li
1338*9c5db199SXin Li        stderr = self.stderr.rstrip()
1339*9c5db199SXin Li        if stderr:
1340*9c5db199SXin Li            stderr = "\nstderr:\n%s" % stderr
1341*9c5db199SXin Li
1342*9c5db199SXin Li        return ("* Command: %s\n"
1343*9c5db199SXin Li                "Exit status: %s\n"
1344*9c5db199SXin Li                "Duration: %s\n"
1345*9c5db199SXin Li                "%s"
1346*9c5db199SXin Li                "%s"
1347*9c5db199SXin Li                % (wrapper.fill(str(self.command)), self.exit_status,
1348*9c5db199SXin Li                self.duration, stdout, stderr))
1349*9c5db199SXin Li
1350*9c5db199SXin Li
1351*9c5db199SXin Liclass run_randomly:
1352*9c5db199SXin Li    def __init__(self, run_sequentially=False):
1353*9c5db199SXin Li        # Run sequentially is for debugging control files
1354*9c5db199SXin Li        self.test_list = []
1355*9c5db199SXin Li        self.run_sequentially = run_sequentially
1356*9c5db199SXin Li
1357*9c5db199SXin Li
1358*9c5db199SXin Li    def add(self, *args, **dargs):
1359*9c5db199SXin Li        test = (args, dargs)
1360*9c5db199SXin Li        self.test_list.append(test)
1361*9c5db199SXin Li
1362*9c5db199SXin Li
1363*9c5db199SXin Li    def run(self, fn):
1364*9c5db199SXin Li        while self.test_list:
1365*9c5db199SXin Li            test_index = random.randint(0, len(self.test_list)-1)
1366*9c5db199SXin Li            if self.run_sequentially:
1367*9c5db199SXin Li                test_index = 0
1368*9c5db199SXin Li            (args, dargs) = self.test_list.pop(test_index)
1369*9c5db199SXin Li            fn(*args, **dargs)
1370*9c5db199SXin Li
1371*9c5db199SXin Li
1372*9c5db199SXin Lidef import_site_module(path, module, placeholder=None, modulefile=None):
1373*9c5db199SXin Li    """
1374*9c5db199SXin Li    Try to import the site specific module if it exists.
1375*9c5db199SXin Li
1376*9c5db199SXin Li    @param path full filename of the source file calling this (ie __file__)
1377*9c5db199SXin Li    @param module full module name
1378*9c5db199SXin Li    @param placeholder value to return in case there is no symbol to import
1379*9c5db199SXin Li    @param modulefile module filename
1380*9c5db199SXin Li
1381*9c5db199SXin Li    @return site specific module or placeholder
1382*9c5db199SXin Li
1383*9c5db199SXin Li    @raises ImportError if the site file exists but imports fails
1384*9c5db199SXin Li    """
1385*9c5db199SXin Li    short_module = module[module.rfind(".") + 1:]
1386*9c5db199SXin Li
1387*9c5db199SXin Li    if not modulefile:
1388*9c5db199SXin Li        modulefile = short_module + ".py"
1389*9c5db199SXin Li
1390*9c5db199SXin Li    if os.path.exists(os.path.join(os.path.dirname(path), modulefile)):
1391*9c5db199SXin Li        return __import__(module, {}, {}, [short_module])
1392*9c5db199SXin Li    return placeholder
1393*9c5db199SXin Li
1394*9c5db199SXin Li
1395*9c5db199SXin Lidef import_site_symbol(path, module, name, placeholder=None, modulefile=None):
1396*9c5db199SXin Li    """
1397*9c5db199SXin Li    Try to import site specific symbol from site specific file if it exists
1398*9c5db199SXin Li
1399*9c5db199SXin Li    @param path full filename of the source file calling this (ie __file__)
1400*9c5db199SXin Li    @param module full module name
1401*9c5db199SXin Li    @param name symbol name to be imported from the site file
1402*9c5db199SXin Li    @param placeholder value to return in case there is no symbol to import
1403*9c5db199SXin Li    @param modulefile module filename
1404*9c5db199SXin Li
1405*9c5db199SXin Li    @return site specific symbol or placeholder
1406*9c5db199SXin Li
1407*9c5db199SXin Li    @raises ImportError if the site file exists but imports fails
1408*9c5db199SXin Li    """
1409*9c5db199SXin Li    module = import_site_module(path, module, modulefile=modulefile)
1410*9c5db199SXin Li    if not module:
1411*9c5db199SXin Li        return placeholder
1412*9c5db199SXin Li
1413*9c5db199SXin Li    # special unique value to tell us if the symbol can't be imported
1414*9c5db199SXin Li    cant_import = object()
1415*9c5db199SXin Li
1416*9c5db199SXin Li    obj = getattr(module, name, cant_import)
1417*9c5db199SXin Li    if obj is cant_import:
1418*9c5db199SXin Li        return placeholder
1419*9c5db199SXin Li
1420*9c5db199SXin Li    return obj
1421*9c5db199SXin Li
1422*9c5db199SXin Li
1423*9c5db199SXin Lidef import_site_class(path, module, classname, baseclass, modulefile=None):
1424*9c5db199SXin Li    """
1425*9c5db199SXin Li    Try to import site specific class from site specific file if it exists
1426*9c5db199SXin Li
1427*9c5db199SXin Li    Args:
1428*9c5db199SXin Li        path: full filename of the source file calling this (ie __file__)
1429*9c5db199SXin Li        module: full module name
1430*9c5db199SXin Li        classname: class name to be loaded from site file
1431*9c5db199SXin Li        baseclass: base class object to return when no site file present or
1432*9c5db199SXin Li            to mixin when site class exists but is not inherited from baseclass
1433*9c5db199SXin Li        modulefile: module filename
1434*9c5db199SXin Li
1435*9c5db199SXin Li    Returns: baseclass if site specific class does not exist, the site specific
1436*9c5db199SXin Li        class if it exists and is inherited from baseclass or a mixin of the
1437*9c5db199SXin Li        site specific class and baseclass when the site specific class exists
1438*9c5db199SXin Li        and is not inherited from baseclass
1439*9c5db199SXin Li
1440*9c5db199SXin Li    Raises: ImportError if the site file exists but imports fails
1441*9c5db199SXin Li    """
1442*9c5db199SXin Li
1443*9c5db199SXin Li    res = import_site_symbol(path, module, classname, None, modulefile)
1444*9c5db199SXin Li    if res:
1445*9c5db199SXin Li        if not issubclass(res, baseclass):
1446*9c5db199SXin Li            # if not a subclass of baseclass then mix in baseclass with the
1447*9c5db199SXin Li            # site specific class object and return the result
1448*9c5db199SXin Li            res = type(classname, (res, baseclass), {})
1449*9c5db199SXin Li    else:
1450*9c5db199SXin Li        res = baseclass
1451*9c5db199SXin Li
1452*9c5db199SXin Li    return res
1453*9c5db199SXin Li
1454*9c5db199SXin Li
1455*9c5db199SXin Lidef import_site_function(path, module, funcname, placeholder, modulefile=None):
1456*9c5db199SXin Li    """
1457*9c5db199SXin Li    Try to import site specific function from site specific file if it exists
1458*9c5db199SXin Li
1459*9c5db199SXin Li    Args:
1460*9c5db199SXin Li        path: full filename of the source file calling this (ie __file__)
1461*9c5db199SXin Li        module: full module name
1462*9c5db199SXin Li        funcname: function name to be imported from site file
1463*9c5db199SXin Li        placeholder: function to return in case there is no function to import
1464*9c5db199SXin Li        modulefile: module filename
1465*9c5db199SXin Li
1466*9c5db199SXin Li    Returns: site specific function object or placeholder
1467*9c5db199SXin Li
1468*9c5db199SXin Li    Raises: ImportError if the site file exists but imports fails
1469*9c5db199SXin Li    """
1470*9c5db199SXin Li
1471*9c5db199SXin Li    return import_site_symbol(path, module, funcname, placeholder, modulefile)
1472*9c5db199SXin Li
1473*9c5db199SXin Li
1474*9c5db199SXin Lidef _get_pid_path(program_name):
1475*9c5db199SXin Li    my_path = os.path.dirname(__file__)
1476*9c5db199SXin Li    return os.path.abspath(os.path.join(my_path, "..", "..",
1477*9c5db199SXin Li                                        "%s.pid" % program_name))
1478*9c5db199SXin Li
1479*9c5db199SXin Li
1480*9c5db199SXin Lidef write_pid(program_name):
1481*9c5db199SXin Li    """
1482*9c5db199SXin Li    Try to drop <program_name>.pid in the main autotest directory.
1483*9c5db199SXin Li
1484*9c5db199SXin Li    Args:
1485*9c5db199SXin Li      program_name: prefix for file name
1486*9c5db199SXin Li    """
1487*9c5db199SXin Li    pidfile = open(_get_pid_path(program_name), "w")
1488*9c5db199SXin Li    try:
1489*9c5db199SXin Li        pidfile.write("%s\n" % os.getpid())
1490*9c5db199SXin Li    finally:
1491*9c5db199SXin Li        pidfile.close()
1492*9c5db199SXin Li
1493*9c5db199SXin Li
1494*9c5db199SXin Lidef delete_pid_file_if_exists(program_name):
1495*9c5db199SXin Li    """
1496*9c5db199SXin Li    Tries to remove <program_name>.pid from the main autotest directory.
1497*9c5db199SXin Li    """
1498*9c5db199SXin Li    pidfile_path = _get_pid_path(program_name)
1499*9c5db199SXin Li
1500*9c5db199SXin Li    try:
1501*9c5db199SXin Li        os.remove(pidfile_path)
1502*9c5db199SXin Li    except OSError:
1503*9c5db199SXin Li        if not os.path.exists(pidfile_path):
1504*9c5db199SXin Li            return
1505*9c5db199SXin Li        raise
1506*9c5db199SXin Li
1507*9c5db199SXin Li
1508*9c5db199SXin Lidef get_pid_from_file(program_name):
1509*9c5db199SXin Li    """
1510*9c5db199SXin Li    Reads the pid from <program_name>.pid in the autotest directory.
1511*9c5db199SXin Li
1512*9c5db199SXin Li    @param program_name the name of the program
1513*9c5db199SXin Li    @return the pid if the file exists, None otherwise.
1514*9c5db199SXin Li    """
1515*9c5db199SXin Li    pidfile_path = _get_pid_path(program_name)
1516*9c5db199SXin Li    if not os.path.exists(pidfile_path):
1517*9c5db199SXin Li        return None
1518*9c5db199SXin Li
1519*9c5db199SXin Li    pidfile = open(_get_pid_path(program_name), 'r')
1520*9c5db199SXin Li
1521*9c5db199SXin Li    try:
1522*9c5db199SXin Li        try:
1523*9c5db199SXin Li            pid = int(pidfile.readline())
1524*9c5db199SXin Li        except IOError:
1525*9c5db199SXin Li            if not os.path.exists(pidfile_path):
1526*9c5db199SXin Li                return None
1527*9c5db199SXin Li            raise
1528*9c5db199SXin Li    finally:
1529*9c5db199SXin Li        pidfile.close()
1530*9c5db199SXin Li
1531*9c5db199SXin Li    return pid
1532*9c5db199SXin Li
1533*9c5db199SXin Li
1534*9c5db199SXin Lidef get_process_name(pid):
1535*9c5db199SXin Li    """
1536*9c5db199SXin Li    Get process name from PID.
1537*9c5db199SXin Li    @param pid: PID of process.
1538*9c5db199SXin Li    @return: Process name if PID stat file exists or 'Dead PID' if it does not.
1539*9c5db199SXin Li    """
1540*9c5db199SXin Li    pid_stat_path = "/proc/%d/stat"
1541*9c5db199SXin Li    if not os.path.exists(pid_stat_path % pid):
1542*9c5db199SXin Li        return "Dead Pid"
1543*9c5db199SXin Li    return get_field(read_file(pid_stat_path % pid), 1)[1:-1]
1544*9c5db199SXin Li
1545*9c5db199SXin Li
1546*9c5db199SXin Lidef program_is_alive(program_name):
1547*9c5db199SXin Li    """
1548*9c5db199SXin Li    Checks if the process is alive and not in Zombie state.
1549*9c5db199SXin Li
1550*9c5db199SXin Li    @param program_name the name of the program
1551*9c5db199SXin Li    @return True if still alive, False otherwise
1552*9c5db199SXin Li    """
1553*9c5db199SXin Li    pid = get_pid_from_file(program_name)
1554*9c5db199SXin Li    if pid is None:
1555*9c5db199SXin Li        return False
1556*9c5db199SXin Li    return pid_is_alive(pid)
1557*9c5db199SXin Li
1558*9c5db199SXin Li
1559*9c5db199SXin Lidef signal_program(program_name, sig=signal.SIGTERM):
1560*9c5db199SXin Li    """
1561*9c5db199SXin Li    Sends a signal to the process listed in <program_name>.pid
1562*9c5db199SXin Li
1563*9c5db199SXin Li    @param program_name the name of the program
1564*9c5db199SXin Li    @param sig signal to send
1565*9c5db199SXin Li    """
1566*9c5db199SXin Li    pid = get_pid_from_file(program_name)
1567*9c5db199SXin Li    if pid:
1568*9c5db199SXin Li        signal_pid(pid, sig)
1569*9c5db199SXin Li
1570*9c5db199SXin Li
1571*9c5db199SXin Lidef get_relative_path(path, reference):
1572*9c5db199SXin Li    """Given 2 absolute paths "path" and "reference", compute the path of
1573*9c5db199SXin Li    "path" as relative to the directory "reference".
1574*9c5db199SXin Li
1575*9c5db199SXin Li    @param path the absolute path to convert to a relative path
1576*9c5db199SXin Li    @param reference an absolute directory path to which the relative
1577*9c5db199SXin Li        path will be computed
1578*9c5db199SXin Li    """
1579*9c5db199SXin Li    # normalize the paths (remove double slashes, etc)
1580*9c5db199SXin Li    assert(os.path.isabs(path))
1581*9c5db199SXin Li    assert(os.path.isabs(reference))
1582*9c5db199SXin Li
1583*9c5db199SXin Li    path = os.path.normpath(path)
1584*9c5db199SXin Li    reference = os.path.normpath(reference)
1585*9c5db199SXin Li
1586*9c5db199SXin Li    # we could use os.path.split() but it splits from the end
1587*9c5db199SXin Li    path_list = path.split(os.path.sep)[1:]
1588*9c5db199SXin Li    ref_list = reference.split(os.path.sep)[1:]
1589*9c5db199SXin Li
1590*9c5db199SXin Li    # find the longest leading common path
1591*9c5db199SXin Li    for i in range(min(len(path_list), len(ref_list))):
1592*9c5db199SXin Li        if path_list[i] != ref_list[i]:
1593*9c5db199SXin Li            # decrement i so when exiting this loop either by no match or by
1594*9c5db199SXin Li            # end of range we are one step behind
1595*9c5db199SXin Li            i -= 1
1596*9c5db199SXin Li            break
1597*9c5db199SXin Li    i += 1
1598*9c5db199SXin Li    # drop the common part of the paths, not interested in that anymore
1599*9c5db199SXin Li    del path_list[:i]
1600*9c5db199SXin Li
1601*9c5db199SXin Li    # for each uncommon component in the reference prepend a ".."
1602*9c5db199SXin Li    path_list[:0] = ['..'] * (len(ref_list) - i)
1603*9c5db199SXin Li
1604*9c5db199SXin Li    return os.path.join(*path_list)
1605*9c5db199SXin Li
1606*9c5db199SXin Li
1607*9c5db199SXin Lidef sh_escape(command):
1608*9c5db199SXin Li    """
1609*9c5db199SXin Li    Escape special characters from a command so that it can be passed
1610*9c5db199SXin Li    as a double quoted (" ") string in a (ba)sh command.
1611*9c5db199SXin Li
1612*9c5db199SXin Li    Args:
1613*9c5db199SXin Li            command: the command string to escape.
1614*9c5db199SXin Li
1615*9c5db199SXin Li    Returns:
1616*9c5db199SXin Li            The escaped command string. The required englobing double
1617*9c5db199SXin Li            quotes are NOT added and so should be added at some point by
1618*9c5db199SXin Li            the caller.
1619*9c5db199SXin Li
1620*9c5db199SXin Li    See also: http://www.tldp.org/LDP/abs/html/escapingsection.html
1621*9c5db199SXin Li    """
1622*9c5db199SXin Li    command = command.replace("\\", "\\\\")
1623*9c5db199SXin Li    command = command.replace("$", r'\$')
1624*9c5db199SXin Li    command = command.replace('"', r'\"')
1625*9c5db199SXin Li    command = command.replace('`', r'\`')
1626*9c5db199SXin Li    return command
1627*9c5db199SXin Li
1628*9c5db199SXin Li
1629*9c5db199SXin Lidef sh_quote_word(text, allowlist=_SHELL_QUOTING_ALLOWLIST):
1630*9c5db199SXin Li    r"""Quote a string to make it safe as a single word in a shell command.
1631*9c5db199SXin Li
1632*9c5db199SXin Li    POSIX shell syntax recognizes no escape characters inside a single-quoted
1633*9c5db199SXin Li    string.  So, single quotes can safely quote any string of characters except
1634*9c5db199SXin Li    a string with a single quote character.  A single quote character must be
1635*9c5db199SXin Li    quoted with the sequence '\'' which translates to:
1636*9c5db199SXin Li        '  -> close current quote
1637*9c5db199SXin Li        \' -> insert a literal single quote
1638*9c5db199SXin Li        '  -> reopen quoting again.
1639*9c5db199SXin Li
1640*9c5db199SXin Li    This is safe for all combinations of characters, including embedded and
1641*9c5db199SXin Li    trailing backslashes in odd or even numbers.
1642*9c5db199SXin Li
1643*9c5db199SXin Li    This is also safe for nesting, e.g. the following is a valid use:
1644*9c5db199SXin Li
1645*9c5db199SXin Li        adb_command = 'adb shell %s' % (
1646*9c5db199SXin Li                sh_quote_word('echo %s' % sh_quote_word('hello world')))
1647*9c5db199SXin Li
1648*9c5db199SXin Li    @param text: The string to be quoted into a single word for the shell.
1649*9c5db199SXin Li    @param allowlist: Optional list of characters that do not need quoting.
1650*9c5db199SXin Li                      Defaults to a known good list of characters.
1651*9c5db199SXin Li
1652*9c5db199SXin Li    @return A string, possibly quoted, safe as a single word for a shell.
1653*9c5db199SXin Li    """
1654*9c5db199SXin Li    if all(c in allowlist for c in text):
1655*9c5db199SXin Li        return text
1656*9c5db199SXin Li    return "'" + text.replace("'", r"'\''") + "'"
1657*9c5db199SXin Li
1658*9c5db199SXin Li
1659*9c5db199SXin Lidef configure(extra=None, configure='./configure'):
1660*9c5db199SXin Li    """
1661*9c5db199SXin Li    Run configure passing in the correct host, build, and target options.
1662*9c5db199SXin Li
1663*9c5db199SXin Li    @param extra: extra command line arguments to pass to configure
1664*9c5db199SXin Li    @param configure: which configure script to use
1665*9c5db199SXin Li    """
1666*9c5db199SXin Li    args = []
1667*9c5db199SXin Li    if 'CHOST' in os.environ:
1668*9c5db199SXin Li        args.append('--host=' + os.environ['CHOST'])
1669*9c5db199SXin Li    if 'CBUILD' in os.environ:
1670*9c5db199SXin Li        args.append('--build=' + os.environ['CBUILD'])
1671*9c5db199SXin Li    if 'CTARGET' in os.environ:
1672*9c5db199SXin Li        args.append('--target=' + os.environ['CTARGET'])
1673*9c5db199SXin Li    if extra:
1674*9c5db199SXin Li        args.append(extra)
1675*9c5db199SXin Li
1676*9c5db199SXin Li    system('%s %s' % (configure, ' '.join(args)))
1677*9c5db199SXin Li
1678*9c5db199SXin Li
1679*9c5db199SXin Lidef make(extra='', make='make', timeout=None, ignore_status=False):
1680*9c5db199SXin Li    """
1681*9c5db199SXin Li    Run make, adding MAKEOPTS to the list of options.
1682*9c5db199SXin Li
1683*9c5db199SXin Li    @param extra: extra command line arguments to pass to make.
1684*9c5db199SXin Li    """
1685*9c5db199SXin Li    cmd = '%s %s %s' % (make, os.environ.get('MAKEOPTS', ''), extra)
1686*9c5db199SXin Li    return system(cmd, timeout=timeout, ignore_status=ignore_status)
1687*9c5db199SXin Li
1688*9c5db199SXin Li
1689*9c5db199SXin Lidef _cmp(x, y):
1690*9c5db199SXin Li    """
1691*9c5db199SXin Li    Replacement for built-in function cmp that was removed in Python 3
1692*9c5db199SXin Li
1693*9c5db199SXin Li    Compare the two objects x and y and return an integer according to
1694*9c5db199SXin Li    the outcome. The return value is negative if x < y, zero if x == y
1695*9c5db199SXin Li    and strictly positive if x > y.
1696*9c5db199SXin Li    """
1697*9c5db199SXin Li
1698*9c5db199SXin Li    return (x > y) - (x < y)
1699*9c5db199SXin Li
1700*9c5db199SXin Li
1701*9c5db199SXin Lidef compare_versions(ver1, ver2):
1702*9c5db199SXin Li    """Version number comparison between ver1 and ver2 strings.
1703*9c5db199SXin Li
1704*9c5db199SXin Li    >>> compare_tuple("1", "2")
1705*9c5db199SXin Li    -1
1706*9c5db199SXin Li    >>> compare_tuple("foo-1.1", "foo-1.2")
1707*9c5db199SXin Li    -1
1708*9c5db199SXin Li    >>> compare_tuple("1.2", "1.2a")
1709*9c5db199SXin Li    -1
1710*9c5db199SXin Li    >>> compare_tuple("1.2b", "1.2a")
1711*9c5db199SXin Li    1
1712*9c5db199SXin Li    >>> compare_tuple("1.3.5.3a", "1.3.5.3b")
1713*9c5db199SXin Li    -1
1714*9c5db199SXin Li
1715*9c5db199SXin Li    Args:
1716*9c5db199SXin Li        ver1: version string
1717*9c5db199SXin Li        ver2: version string
1718*9c5db199SXin Li
1719*9c5db199SXin Li    Returns:
1720*9c5db199SXin Li        int:  1 if ver1 >  ver2
1721*9c5db199SXin Li              0 if ver1 == ver2
1722*9c5db199SXin Li             -1 if ver1 <  ver2
1723*9c5db199SXin Li    """
1724*9c5db199SXin Li    ax = re.split('[.-]', ver1)
1725*9c5db199SXin Li    ay = re.split('[.-]', ver2)
1726*9c5db199SXin Li    while len(ax) > 0 and len(ay) > 0:
1727*9c5db199SXin Li        cx = ax.pop(0)
1728*9c5db199SXin Li        cy = ay.pop(0)
1729*9c5db199SXin Li        maxlen = max(len(cx), len(cy))
1730*9c5db199SXin Li        c = _cmp(cx.zfill(maxlen), cy.zfill(maxlen))
1731*9c5db199SXin Li        if c != 0:
1732*9c5db199SXin Li            return c
1733*9c5db199SXin Li    return _cmp(len(ax), len(ay))
1734*9c5db199SXin Li
1735*9c5db199SXin Li
1736*9c5db199SXin Lidef args_to_dict(args):
1737*9c5db199SXin Li    """Convert autoserv extra arguments in the form of key=val or key:val to a
1738*9c5db199SXin Li    dictionary.  Each argument key is converted to lowercase dictionary key.
1739*9c5db199SXin Li
1740*9c5db199SXin Li    Args:
1741*9c5db199SXin Li        args - list of autoserv extra arguments.
1742*9c5db199SXin Li
1743*9c5db199SXin Li    Returns:
1744*9c5db199SXin Li        dictionary
1745*9c5db199SXin Li    """
1746*9c5db199SXin Li    arg_re = re.compile(r'(\w+)[:=](.*)$')
1747*9c5db199SXin Li    args_dict = {}
1748*9c5db199SXin Li    for arg in args:
1749*9c5db199SXin Li        match = arg_re.match(arg)
1750*9c5db199SXin Li        if match:
1751*9c5db199SXin Li            args_dict[match.group(1).lower()] = match.group(2)
1752*9c5db199SXin Li        else:
1753*9c5db199SXin Li            logging.warning("args_to_dict: argument '%s' doesn't match "
1754*9c5db199SXin Li                            "'%s' pattern. Ignored.", arg, arg_re.pattern)
1755*9c5db199SXin Li    return args_dict
1756*9c5db199SXin Li
1757*9c5db199SXin Li
1758*9c5db199SXin Lidef get_unused_port():
1759*9c5db199SXin Li    """
1760*9c5db199SXin Li    Finds a semi-random available port. A race condition is still
1761*9c5db199SXin Li    possible after the port number is returned, if another process
1762*9c5db199SXin Li    happens to bind it.
1763*9c5db199SXin Li
1764*9c5db199SXin Li    Returns:
1765*9c5db199SXin Li        A port number that is unused on both TCP and UDP.
1766*9c5db199SXin Li    """
1767*9c5db199SXin Li
1768*9c5db199SXin Li    def try_bind(port, socket_type, socket_proto):
1769*9c5db199SXin Li        s = socket.socket(socket.AF_INET, socket_type, socket_proto)
1770*9c5db199SXin Li        try:
1771*9c5db199SXin Li            try:
1772*9c5db199SXin Li                s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
1773*9c5db199SXin Li                s.bind(('', port))
1774*9c5db199SXin Li                return s.getsockname()[1]
1775*9c5db199SXin Li            except socket.error:
1776*9c5db199SXin Li                return None
1777*9c5db199SXin Li        finally:
1778*9c5db199SXin Li            s.close()
1779*9c5db199SXin Li
1780*9c5db199SXin Li    # On the 2.6 kernel, calling try_bind() on UDP socket returns the
1781*9c5db199SXin Li    # same port over and over. So always try TCP first.
1782*9c5db199SXin Li    while True:
1783*9c5db199SXin Li        # Ask the OS for an unused port.
1784*9c5db199SXin Li        port = try_bind(0, socket.SOCK_STREAM, socket.IPPROTO_TCP)
1785*9c5db199SXin Li        # Check if this port is unused on the other protocol.
1786*9c5db199SXin Li        if port and try_bind(port, socket.SOCK_DGRAM, socket.IPPROTO_UDP):
1787*9c5db199SXin Li            return port
1788*9c5db199SXin Li
1789*9c5db199SXin Li
1790*9c5db199SXin Lidef ask(question, auto=False):
1791*9c5db199SXin Li    """
1792*9c5db199SXin Li    Raw input with a prompt that emulates logging.
1793*9c5db199SXin Li
1794*9c5db199SXin Li    @param question: Question to be asked
1795*9c5db199SXin Li    @param auto: Whether to return "y" instead of asking the question
1796*9c5db199SXin Li    """
1797*9c5db199SXin Li    if auto:
1798*9c5db199SXin Li        logging.info("%s (y/n) y", question)
1799*9c5db199SXin Li        return "y"
1800*9c5db199SXin Li    return input("%s INFO | %s (y/n) " %
1801*9c5db199SXin Li                     (time.strftime("%H:%M:%S", time.localtime()), question))
1802*9c5db199SXin Li
1803*9c5db199SXin Li
1804*9c5db199SXin Lidef rdmsr(address, cpu=0):
1805*9c5db199SXin Li    """
1806*9c5db199SXin Li    Reads an x86 MSR from the specified CPU, returns as long integer.
1807*9c5db199SXin Li    """
1808*9c5db199SXin Li    with open('/dev/cpu/%s/msr' % cpu, 'rb', 0) as fd:
1809*9c5db199SXin Li        fd.seek(address)
1810*9c5db199SXin Li        return struct.unpack('=Q', fd.read(8))[0]
1811*9c5db199SXin Li
1812*9c5db199SXin Li
1813*9c5db199SXin Lidef wait_for_value(func,
1814*9c5db199SXin Li                   expected_value=None,
1815*9c5db199SXin Li                   min_threshold=None,
1816*9c5db199SXin Li                   max_threshold=None,
1817*9c5db199SXin Li                   timeout_sec=10):
1818*9c5db199SXin Li    """
1819*9c5db199SXin Li    Returns the value of func().  If |expected_value|, |min_threshold|, and
1820*9c5db199SXin Li    |max_threshold| are not set, returns immediately.
1821*9c5db199SXin Li
1822*9c5db199SXin Li    If |expected_value| is set, polls the return value until |expected_value| is
1823*9c5db199SXin Li    reached, and returns that value.
1824*9c5db199SXin Li
1825*9c5db199SXin Li    If either |max_threshold| or |min_threshold| is set, this function will
1826*9c5db199SXin Li    will repeatedly call func() until the return value reaches or exceeds one of
1827*9c5db199SXin Li    these thresholds.
1828*9c5db199SXin Li
1829*9c5db199SXin Li    Polling will stop after |timeout_sec| regardless of these thresholds.
1830*9c5db199SXin Li
1831*9c5db199SXin Li    @param func: function whose return value is to be waited on.
1832*9c5db199SXin Li    @param expected_value: wait for func to return this value.
1833*9c5db199SXin Li    @param min_threshold: wait for func value to reach or fall below this value.
1834*9c5db199SXin Li    @param max_threshold: wait for func value to reach or rise above this value.
1835*9c5db199SXin Li    @param timeout_sec: Number of seconds to wait before giving up and
1836*9c5db199SXin Li                        returning whatever value func() last returned.
1837*9c5db199SXin Li
1838*9c5db199SXin Li    Return value:
1839*9c5db199SXin Li        The most recent return value of func().
1840*9c5db199SXin Li    """
1841*9c5db199SXin Li    value = None
1842*9c5db199SXin Li    start_time_sec = time.time()
1843*9c5db199SXin Li    while True:
1844*9c5db199SXin Li        value = func()
1845*9c5db199SXin Li        if (expected_value is None and \
1846*9c5db199SXin Li            min_threshold is None and \
1847*9c5db199SXin Li            max_threshold is None) or \
1848*9c5db199SXin Li           (expected_value is not None and value == expected_value) or \
1849*9c5db199SXin Li           (min_threshold is not None and value <= min_threshold) or \
1850*9c5db199SXin Li           (max_threshold is not None and value >= max_threshold):
1851*9c5db199SXin Li            break
1852*9c5db199SXin Li
1853*9c5db199SXin Li        if time.time() - start_time_sec >= timeout_sec:
1854*9c5db199SXin Li            break
1855*9c5db199SXin Li        time.sleep(0.1)
1856*9c5db199SXin Li
1857*9c5db199SXin Li    return value
1858*9c5db199SXin Li
1859*9c5db199SXin Li
1860*9c5db199SXin Lidef wait_for_value_changed(func,
1861*9c5db199SXin Li                           old_value=None,
1862*9c5db199SXin Li                           timeout_sec=10):
1863*9c5db199SXin Li    """
1864*9c5db199SXin Li    Returns the value of func().
1865*9c5db199SXin Li
1866*9c5db199SXin Li    The function polls the return value until it is different from |old_value|,
1867*9c5db199SXin Li    and returns that value.
1868*9c5db199SXin Li
1869*9c5db199SXin Li    Polling will stop after |timeout_sec|.
1870*9c5db199SXin Li
1871*9c5db199SXin Li    @param func: function whose return value is to be waited on.
1872*9c5db199SXin Li    @param old_value: wait for func to return a value different from this.
1873*9c5db199SXin Li    @param timeout_sec: Number of seconds to wait before giving up and
1874*9c5db199SXin Li                        returning whatever value func() last returned.
1875*9c5db199SXin Li
1876*9c5db199SXin Li    @returns The most recent return value of func().
1877*9c5db199SXin Li    """
1878*9c5db199SXin Li    value = None
1879*9c5db199SXin Li    start_time_sec = time.time()
1880*9c5db199SXin Li    while True:
1881*9c5db199SXin Li        value = func()
1882*9c5db199SXin Li        if value != old_value:
1883*9c5db199SXin Li            break
1884*9c5db199SXin Li
1885*9c5db199SXin Li        if time.time() - start_time_sec >= timeout_sec:
1886*9c5db199SXin Li            break
1887*9c5db199SXin Li        time.sleep(0.1)
1888*9c5db199SXin Li
1889*9c5db199SXin Li    return value
1890*9c5db199SXin Li
1891*9c5db199SXin Li
1892*9c5db199SXin LiCONFIG = global_config.global_config
1893*9c5db199SXin Li
1894*9c5db199SXin Li# Keep checking if the pid is alive every second until the timeout (in seconds)
1895*9c5db199SXin LiCHECK_PID_IS_ALIVE_TIMEOUT = 6
1896*9c5db199SXin Li
1897*9c5db199SXin Li_LOCAL_HOST_LIST = ('localhost', '127.0.0.1')
1898*9c5db199SXin Li
1899*9c5db199SXin Li# The default address of a vm gateway.
1900*9c5db199SXin LiDEFAULT_VM_GATEWAY = '10.0.2.2'
1901*9c5db199SXin Li
1902*9c5db199SXin Li# Google Storage bucket URI to store results in.
1903*9c5db199SXin LiDEFAULT_OFFLOAD_GSURI = CONFIG.get_config_value(
1904*9c5db199SXin Li        'CROS', 'results_storage_server', default=None)
1905*9c5db199SXin Li
1906*9c5db199SXin Li# Default Moblab Ethernet Interface.
1907*9c5db199SXin Li_MOBLAB_ETH_0 = 'eth0'
1908*9c5db199SXin Li_MOBLAB_ETH_1 = 'eth1'
1909*9c5db199SXin Li
1910*9c5db199SXin Li
1911*9c5db199SXin Lidef _parse_subnet(subnet_str):
1912*9c5db199SXin Li    """Parse a subnet string to a (ip, mask) tuple."""
1913*9c5db199SXin Li    ip, mask = subnet_str.split('/')
1914*9c5db199SXin Li    return ip, int(mask)
1915*9c5db199SXin Li
1916*9c5db199SXin Li
1917*9c5db199SXin Li# A list of subnets that requires dedicated devserver and drone in the same
1918*9c5db199SXin Li# subnet. Each item is a tuple of (subnet_ip, mask_bits), e.g.,
1919*9c5db199SXin Li# ('192.168.0.0', 24))
1920*9c5db199SXin LiRESTRICTED_SUBNETS = []
1921*9c5db199SXin Li
1922*9c5db199SXin Li
1923*9c5db199SXin Lidef _setup_restricted_subnets():
1924*9c5db199SXin Li    restricted_subnets_list = CONFIG.get_config_value(
1925*9c5db199SXin Li            'CROS', 'restricted_subnets', type=list, default=[])
1926*9c5db199SXin Li    global RESTRICTED_SUBNETS
1927*9c5db199SXin Li    RESTRICTED_SUBNETS = [_parse_subnet(s) for s in restricted_subnets_list]
1928*9c5db199SXin Li
1929*9c5db199SXin Li
1930*9c5db199SXin Li_setup_restricted_subnets()
1931*9c5db199SXin Li
1932*9c5db199SXin Li
1933*9c5db199SXin Li# A two level list of subnets, e.g. '[["1.1.1.0/24","1.1.2.0/24"],
1934*9c5db199SXin Li# ["1.2.1.0/24", "1.2.2.0/24"]]'. Each element of it is either a singleton list
1935*9c5db199SXin Li# of a restricted subnet, or a list of subnets which can communicate with each
1936*9c5db199SXin Li# other (i.e. p2p subnets).
1937*9c5db199SXin LiALL_SUBNETS = []
1938*9c5db199SXin Li
1939*9c5db199SXin Li
1940*9c5db199SXin Lidef _setup_all_subnets():
1941*9c5db199SXin Li    all_subnets_raw = CONFIG.get_config_value('CROS',
1942*9c5db199SXin Li                                              'p2p_subnets',
1943*9c5db199SXin Li                                              default='[]')
1944*9c5db199SXin Li    all_subnets = json.loads(all_subnets_raw)
1945*9c5db199SXin Li    for subnet_group in all_subnets:
1946*9c5db199SXin Li        ALL_SUBNETS.append([_parse_subnet(s) for s in subnet_group])
1947*9c5db199SXin Li
1948*9c5db199SXin Li    if not RESTRICTED_SUBNETS:
1949*9c5db199SXin Li        _setup_restricted_subnets()
1950*9c5db199SXin Li    for subnet in RESTRICTED_SUBNETS:
1951*9c5db199SXin Li        ALL_SUBNETS.append([subnet])
1952*9c5db199SXin Li
1953*9c5db199SXin Li
1954*9c5db199SXin Li_setup_all_subnets()
1955*9c5db199SXin Li
1956*9c5db199SXin Li
1957*9c5db199SXin Lidef get_all_restricted_subnets():
1958*9c5db199SXin Li    """Returns all restricted subnets in a flat list, including subnets that
1959*9c5db199SXin Li    are part of a p2p group.
1960*9c5db199SXin Li
1961*9c5db199SXin Li    This helps us to check if a host is in a restricted subnet."""
1962*9c5db199SXin Li    result = []
1963*9c5db199SXin Li    for s in ALL_SUBNETS:
1964*9c5db199SXin Li        result.extend(s)
1965*9c5db199SXin Li
1966*9c5db199SXin Li    return result
1967*9c5db199SXin Li
1968*9c5db199SXin Li
1969*9c5db199SXin Li# regex pattern for CLIENT/wireless_ssid_ config. For example, global config
1970*9c5db199SXin Li# can have following config in CLIENT section to indicate that hosts in subnet
1971*9c5db199SXin Li# 192.168.0.1/24 should use wireless ssid of `ssid_1`
1972*9c5db199SXin Li# wireless_ssid_192.168.0.1/24: ssid_1
1973*9c5db199SXin LiWIRELESS_SSID_PATTERN = 'wireless_ssid_(.*)/(\d+)'
1974*9c5db199SXin Li
1975*9c5db199SXin Li
1976*9c5db199SXin Lidef get_moblab_serial_number():
1977*9c5db199SXin Li    """Gets a unique identifier for the moblab.
1978*9c5db199SXin Li
1979*9c5db199SXin Li    Serial number is the prefered identifier, use it if
1980*9c5db199SXin Li    present, however fallback is the ethernet mac address.
1981*9c5db199SXin Li    """
1982*9c5db199SXin Li    for vpd_key in ['serial_number', 'ethernet_mac']:
1983*9c5db199SXin Li        try:
1984*9c5db199SXin Li            cmd_result = run('sudo vpd -g %s' % vpd_key)
1985*9c5db199SXin Li            if cmd_result and cmd_result.stdout:
1986*9c5db199SXin Li                return cmd_result.stdout
1987*9c5db199SXin Li        except error.CmdError as e:
1988*9c5db199SXin Li            logging.error(str(e))
1989*9c5db199SXin Li            logging.info(vpd_key)
1990*9c5db199SXin Li    return 'NoSerialNumber'
1991*9c5db199SXin Li
1992*9c5db199SXin Li
1993*9c5db199SXin Lidef ping(host,
1994*9c5db199SXin Li         deadline=None,
1995*9c5db199SXin Li         tries=None,
1996*9c5db199SXin Li         timeout=60,
1997*9c5db199SXin Li         ignore_timeout=False,
1998*9c5db199SXin Li         user=None,
1999*9c5db199SXin Li         interface=None):
2000*9c5db199SXin Li    """Attempt to ping |host|.
2001*9c5db199SXin Li
2002*9c5db199SXin Li    Shell out to 'ping' if host is an IPv4 addres or 'ping6' if host is an
2003*9c5db199SXin Li    IPv6 address to try to reach |host| for |timeout| seconds.
2004*9c5db199SXin Li    Returns exit code of ping.
2005*9c5db199SXin Li
2006*9c5db199SXin Li    Per 'man ping', if you specify BOTH |deadline| and |tries|, ping only
2007*9c5db199SXin Li    returns 0 if we get responses to |tries| pings within |deadline| seconds.
2008*9c5db199SXin Li
2009*9c5db199SXin Li    Specifying |deadline| or |count| alone should return 0 as long as
2010*9c5db199SXin Li    some packets receive responses.
2011*9c5db199SXin Li
2012*9c5db199SXin Li    Note that while this works with literal IPv6 addresses it will not work
2013*9c5db199SXin Li    with hostnames that resolve to IPv6 only.
2014*9c5db199SXin Li
2015*9c5db199SXin Li    @param host: the host to ping.
2016*9c5db199SXin Li    @param deadline: seconds within which |tries| pings must succeed.
2017*9c5db199SXin Li    @param tries: number of pings to send.
2018*9c5db199SXin Li    @param timeout: number of seconds after which to kill 'ping' command.
2019*9c5db199SXin Li    @param ignore_timeout: If true, timeouts won't raise CmdTimeoutError.
2020*9c5db199SXin Li    @param user: Run as a specific user
2021*9c5db199SXin Li    @param interface: Run on a specific network interface
2022*9c5db199SXin Li    @return exit code of ping command.
2023*9c5db199SXin Li    """
2024*9c5db199SXin Li    args = [host]
2025*9c5db199SXin Li    cmd = 'ping6' if re.search(r':.*:', host) else 'ping'
2026*9c5db199SXin Li
2027*9c5db199SXin Li    if deadline:
2028*9c5db199SXin Li        args.append('-w%d' % deadline)
2029*9c5db199SXin Li    if tries:
2030*9c5db199SXin Li        args.append('-c%d' % tries)
2031*9c5db199SXin Li    if interface:
2032*9c5db199SXin Li        args.append('-I%s' % interface)
2033*9c5db199SXin Li
2034*9c5db199SXin Li    if user != None:
2035*9c5db199SXin Li        args = [user, '-c', ' '.join([cmd] + args)]
2036*9c5db199SXin Li        cmd = 'su'
2037*9c5db199SXin Li
2038*9c5db199SXin Li    result = run(cmd,
2039*9c5db199SXin Li                 args=args,
2040*9c5db199SXin Li                 verbose=True,
2041*9c5db199SXin Li                 ignore_status=True,
2042*9c5db199SXin Li                 timeout=timeout,
2043*9c5db199SXin Li                 ignore_timeout=ignore_timeout,
2044*9c5db199SXin Li                 stderr_tee=TEE_TO_LOGS)
2045*9c5db199SXin Li
2046*9c5db199SXin Li    # Sometimes the ping process times out even though a deadline is set. If
2047*9c5db199SXin Li    # ignore_timeout is set, it will fall through to here instead of raising.
2048*9c5db199SXin Li    if result is None:
2049*9c5db199SXin Li        logging.debug('Unusual ping result (timeout)')
2050*9c5db199SXin Li        # From man ping: If a packet count and deadline are both specified, and
2051*9c5db199SXin Li        # fewer than count packets are received by the time the deadline has
2052*9c5db199SXin Li        # arrived, it will also exit with code 1. On other error it exits with
2053*9c5db199SXin Li        # code 2.
2054*9c5db199SXin Li        return 1 if deadline and tries else 2
2055*9c5db199SXin Li
2056*9c5db199SXin Li    rc = result.exit_status
2057*9c5db199SXin Li    lines = result.stdout.splitlines()
2058*9c5db199SXin Li
2059*9c5db199SXin Li    # rc=0: host reachable
2060*9c5db199SXin Li    # rc=1: host unreachable
2061*9c5db199SXin Li    # other: an error (do not abbreviate)
2062*9c5db199SXin Li    if rc in (0, 1):
2063*9c5db199SXin Li        # Report the two stats lines, as a single line.
2064*9c5db199SXin Li        # [-2]: packets transmitted, 1 received, 0% packet loss, time 0ms
2065*9c5db199SXin Li        # [-1]: rtt min/avg/max/mdev = 0.497/0.497/0.497/0.000 ms
2066*9c5db199SXin Li        stats = lines[-2:]
2067*9c5db199SXin Li        while '' in stats:
2068*9c5db199SXin Li            stats.remove('')
2069*9c5db199SXin Li
2070*9c5db199SXin Li        if stats or len(lines) < 2:
2071*9c5db199SXin Li            logging.debug('[rc=%s] %s', rc, '; '.join(stats))
2072*9c5db199SXin Li        else:
2073*9c5db199SXin Li            logging.debug('[rc=%s] Ping output:\n%s',
2074*9c5db199SXin Li                          rc, result.stdout)
2075*9c5db199SXin Li    else:
2076*9c5db199SXin Li        output = result.stdout.rstrip()
2077*9c5db199SXin Li        if output:
2078*9c5db199SXin Li            logging.debug('Unusual ping result (rc=%s):\n%s', rc, output)
2079*9c5db199SXin Li        else:
2080*9c5db199SXin Li            logging.debug('Unusual ping result (rc=%s).', rc)
2081*9c5db199SXin Li    return rc
2082*9c5db199SXin Li
2083*9c5db199SXin Li
2084*9c5db199SXin Lidef host_is_in_lab_zone(hostname):
2085*9c5db199SXin Li    """Check if the host is in the CLIENT.dns_zone.
2086*9c5db199SXin Li
2087*9c5db199SXin Li    @param hostname: The hostname to check.
2088*9c5db199SXin Li    @returns True if hostname.dns_zone resolves, otherwise False.
2089*9c5db199SXin Li    """
2090*9c5db199SXin Li    host_parts = hostname.split('.')
2091*9c5db199SXin Li    dns_zone = CONFIG.get_config_value('CLIENT', 'dns_zone', default=None)
2092*9c5db199SXin Li    fqdn = '%s.%s' % (host_parts[0], dns_zone)
2093*9c5db199SXin Li    logging.debug('Checking if host %s is in lab zone.', fqdn)
2094*9c5db199SXin Li    try:
2095*9c5db199SXin Li        socket.gethostbyname(fqdn)
2096*9c5db199SXin Li        return True
2097*9c5db199SXin Li    except socket.gaierror:
2098*9c5db199SXin Li        return False
2099*9c5db199SXin Li
2100*9c5db199SXin Li
2101*9c5db199SXin Lidef host_is_in_power_lab(hostname):
2102*9c5db199SXin Li    """Check if the hostname is in power lab.
2103*9c5db199SXin Li
2104*9c5db199SXin Li    Example: chromeos1-power-host2.cros
2105*9c5db199SXin Li
2106*9c5db199SXin Li    @param hostname: The hostname to check.
2107*9c5db199SXin Li    @returns True if hostname match power lab hostname, otherwise False.
2108*9c5db199SXin Li    """
2109*9c5db199SXin Li    pattern = r'chromeos\d.*power.*(\.cros(\.corp(\.google\.com)?)?)?$'
2110*9c5db199SXin Li    return re.match(pattern, hostname) is not None
2111*9c5db199SXin Li
2112*9c5db199SXin Li
2113*9c5db199SXin Lidef get_power_lab_wlan_hostname(hostname):
2114*9c5db199SXin Li    """Return wlan hostname for host in power lab.
2115*9c5db199SXin Li
2116*9c5db199SXin Li    Example: chromeos1-power-host2.cros -> chromeos1-power-host2-wlan.cros
2117*9c5db199SXin Li
2118*9c5db199SXin Li    @param hostname: The hostname in power lab.
2119*9c5db199SXin Li    @returns wlan hostname.
2120*9c5db199SXin Li    """
2121*9c5db199SXin Li    split_host = hostname.split('.')
2122*9c5db199SXin Li    split_host[0] += '-wlan'
2123*9c5db199SXin Li    return '.'.join(split_host)
2124*9c5db199SXin Li
2125*9c5db199SXin Li
2126*9c5db199SXin Lidef in_moblab_ssp():
2127*9c5db199SXin Li    """Detects if this execution is inside an SSP container on moblab."""
2128*9c5db199SXin Li    config_is_moblab = CONFIG.get_config_value('SSP', 'is_moblab', type=bool,
2129*9c5db199SXin Li                                               default=False)
2130*9c5db199SXin Li    return is_in_container() and config_is_moblab
2131*9c5db199SXin Li
2132*9c5db199SXin Li
2133*9c5db199SXin Lidef get_chrome_version(job_views):
2134*9c5db199SXin Li    """
2135*9c5db199SXin Li    Retrieves the version of the chrome binary associated with a job.
2136*9c5db199SXin Li
2137*9c5db199SXin Li    When a test runs we query the chrome binary for it's version and drop
2138*9c5db199SXin Li    that value into a client keyval. To retrieve the chrome version we get all
2139*9c5db199SXin Li    the views associated with a test from the db, including those of the
2140*9c5db199SXin Li    server and client jobs, and parse the version out of the first test view
2141*9c5db199SXin Li    that has it. If we never ran a single test in the suite the job_views
2142*9c5db199SXin Li    dictionary will not contain a chrome version.
2143*9c5db199SXin Li
2144*9c5db199SXin Li    This method cannot retrieve the chrome version from a dictionary that
2145*9c5db199SXin Li    does not conform to the structure of an autotest tko view.
2146*9c5db199SXin Li
2147*9c5db199SXin Li    @param job_views: a list of a job's result views, as returned by
2148*9c5db199SXin Li                      the get_detailed_test_views method in rpc_interface.
2149*9c5db199SXin Li    @return: The chrome version string, or None if one can't be found.
2150*9c5db199SXin Li    """
2151*9c5db199SXin Li
2152*9c5db199SXin Li    # Aborted jobs have no views.
2153*9c5db199SXin Li    if not job_views:
2154*9c5db199SXin Li        return None
2155*9c5db199SXin Li
2156*9c5db199SXin Li    for view in job_views:
2157*9c5db199SXin Li        if (view.get('attributes')
2158*9c5db199SXin Li            and constants.CHROME_VERSION in list(view['attributes'].keys())):
2159*9c5db199SXin Li
2160*9c5db199SXin Li            return view['attributes'].get(constants.CHROME_VERSION)
2161*9c5db199SXin Li
2162*9c5db199SXin Li    logging.warning('Could not find chrome version for failure.')
2163*9c5db199SXin Li    return None
2164*9c5db199SXin Li
2165*9c5db199SXin Li
2166*9c5db199SXin Lidef get_moblab_id():
2167*9c5db199SXin Li    """Gets the moblab random id.
2168*9c5db199SXin Li
2169*9c5db199SXin Li    The random id file is cached on disk. If it does not exist, a new file is
2170*9c5db199SXin Li    created the first time.
2171*9c5db199SXin Li
2172*9c5db199SXin Li    @returns the moblab random id.
2173*9c5db199SXin Li    """
2174*9c5db199SXin Li    moblab_id_filepath = '/home/moblab/.moblab_id'
2175*9c5db199SXin Li    try:
2176*9c5db199SXin Li        if os.path.exists(moblab_id_filepath):
2177*9c5db199SXin Li            with open(moblab_id_filepath, 'r') as moblab_id_file:
2178*9c5db199SXin Li                random_id = moblab_id_file.read()
2179*9c5db199SXin Li        else:
2180*9c5db199SXin Li            random_id = uuid.uuid1().hex
2181*9c5db199SXin Li            with open(moblab_id_filepath, 'w') as moblab_id_file:
2182*9c5db199SXin Li                moblab_id_file.write('%s' % random_id)
2183*9c5db199SXin Li    except IOError as e:
2184*9c5db199SXin Li        # Possible race condition, another process has created the file.
2185*9c5db199SXin Li        # Sleep a second to make sure the file gets closed.
2186*9c5db199SXin Li        logging.info(e)
2187*9c5db199SXin Li        time.sleep(1)
2188*9c5db199SXin Li        with open(moblab_id_filepath, 'r') as moblab_id_file:
2189*9c5db199SXin Li            random_id = moblab_id_file.read()
2190*9c5db199SXin Li    return random_id
2191*9c5db199SXin Li
2192*9c5db199SXin Li
2193*9c5db199SXin Lidef get_offload_gsuri():
2194*9c5db199SXin Li    """Return the GSURI to offload test results to.
2195*9c5db199SXin Li
2196*9c5db199SXin Li    For the normal use case this is the results_storage_server in the
2197*9c5db199SXin Li    global_config.
2198*9c5db199SXin Li
2199*9c5db199SXin Li    However partners using Moblab will be offloading their results to a
2200*9c5db199SXin Li    subdirectory of their image storage buckets. The subdirectory is
2201*9c5db199SXin Li    determined by the MAC Address of the Moblab device.
2202*9c5db199SXin Li
2203*9c5db199SXin Li    @returns gsuri to offload test results to.
2204*9c5db199SXin Li    """
2205*9c5db199SXin Li    # For non-moblab, use results_storage_server or default.
2206*9c5db199SXin Li    if not is_moblab():  # pylint: disable=undefined-variable
2207*9c5db199SXin Li        return DEFAULT_OFFLOAD_GSURI
2208*9c5db199SXin Li
2209*9c5db199SXin Li    # For moblab, use results_storage_server or image_storage_server as bucket
2210*9c5db199SXin Li    # name and mac-address/moblab_id as path.
2211*9c5db199SXin Li    gsuri = DEFAULT_OFFLOAD_GSURI
2212*9c5db199SXin Li    if not gsuri:
2213*9c5db199SXin Li        gsuri = "%sresults/" % CONFIG.get_config_value('CROS',
2214*9c5db199SXin Li                                                       'image_storage_server')
2215*9c5db199SXin Li
2216*9c5db199SXin Li    return '%s%s/%s/' % (gsuri, get_moblab_serial_number(), get_moblab_id())
2217*9c5db199SXin Li
2218*9c5db199SXin Li
2219*9c5db199SXin Li# TODO(petermayo): crosbug.com/31826 Share this with _GsUpload in
2220*9c5db199SXin Li# //chromite.git/buildbot/prebuilt.py somewhere/somehow
2221*9c5db199SXin Lidef gs_upload(local_file, remote_file, acl, result_dir=None,
2222*9c5db199SXin Li              transfer_timeout=300, acl_timeout=300):
2223*9c5db199SXin Li    """Upload to GS bucket.
2224*9c5db199SXin Li
2225*9c5db199SXin Li    @param local_file: Local file to upload
2226*9c5db199SXin Li    @param remote_file: Remote location to upload the local_file to.
2227*9c5db199SXin Li    @param acl: name or file used for controlling access to the uploaded
2228*9c5db199SXin Li                file.
2229*9c5db199SXin Li    @param result_dir: Result directory if you want to add tracing to the
2230*9c5db199SXin Li                       upload.
2231*9c5db199SXin Li    @param transfer_timeout: Timeout for this upload call.
2232*9c5db199SXin Li    @param acl_timeout: Timeout for the acl call needed to confirm that
2233*9c5db199SXin Li                        the uploader has permissions to execute the upload.
2234*9c5db199SXin Li
2235*9c5db199SXin Li    @raise CmdError: the exit code of the gsutil call was not 0.
2236*9c5db199SXin Li
2237*9c5db199SXin Li    @returns True/False - depending on if the upload succeeded or failed.
2238*9c5db199SXin Li    """
2239*9c5db199SXin Li    # https://developers.google.com/storage/docs/accesscontrol#extension
2240*9c5db199SXin Li    CANNED_ACLS = ['project-private', 'private', 'public-read',
2241*9c5db199SXin Li                   'public-read-write', 'authenticated-read',
2242*9c5db199SXin Li                   'bucket-owner-read', 'bucket-owner-full-control']
2243*9c5db199SXin Li    _GSUTIL_BIN = 'gsutil'
2244*9c5db199SXin Li    acl_cmd = None
2245*9c5db199SXin Li    if acl in CANNED_ACLS:
2246*9c5db199SXin Li        cmd = '%s cp -a %s %s %s' % (_GSUTIL_BIN, acl, local_file, remote_file)
2247*9c5db199SXin Li    else:
2248*9c5db199SXin Li        # For private uploads we assume that the overlay board is set up
2249*9c5db199SXin Li        # properly and a googlestore_acl.xml is present, if not this script
2250*9c5db199SXin Li        # errors
2251*9c5db199SXin Li        cmd = '%s cp -a private %s %s' % (_GSUTIL_BIN, local_file, remote_file)
2252*9c5db199SXin Li        if not os.path.exists(acl):
2253*9c5db199SXin Li            logging.error('Unable to find ACL File %s.', acl)
2254*9c5db199SXin Li            return False
2255*9c5db199SXin Li        acl_cmd = '%s setacl %s %s' % (_GSUTIL_BIN, acl, remote_file)
2256*9c5db199SXin Li    if not result_dir:
2257*9c5db199SXin Li        run(cmd, timeout=transfer_timeout, verbose=True)
2258*9c5db199SXin Li        if acl_cmd:
2259*9c5db199SXin Li            run(acl_cmd, timeout=acl_timeout, verbose=True)
2260*9c5db199SXin Li        return True
2261*9c5db199SXin Li    with open(os.path.join(result_dir, 'tracing'), 'w') as ftrace:
2262*9c5db199SXin Li        ftrace.write('Preamble\n')
2263*9c5db199SXin Li        run(cmd, timeout=transfer_timeout, verbose=True,
2264*9c5db199SXin Li                       stdout_tee=ftrace, stderr_tee=ftrace)
2265*9c5db199SXin Li        if acl_cmd:
2266*9c5db199SXin Li            ftrace.write('\nACL setting\n')
2267*9c5db199SXin Li            # Apply the passed in ACL xml file to the uploaded object.
2268*9c5db199SXin Li            run(acl_cmd, timeout=acl_timeout, verbose=True,
2269*9c5db199SXin Li                           stdout_tee=ftrace, stderr_tee=ftrace)
2270*9c5db199SXin Li        ftrace.write('Postamble\n')
2271*9c5db199SXin Li        return True
2272*9c5db199SXin Li
2273*9c5db199SXin Li
2274*9c5db199SXin Lidef gs_ls(uri_pattern):
2275*9c5db199SXin Li    """Returns a list of URIs that match a given pattern.
2276*9c5db199SXin Li
2277*9c5db199SXin Li    @param uri_pattern: a GS URI pattern, may contain wildcards
2278*9c5db199SXin Li
2279*9c5db199SXin Li    @return A list of URIs matching the given pattern.
2280*9c5db199SXin Li
2281*9c5db199SXin Li    @raise CmdError: the gsutil command failed.
2282*9c5db199SXin Li
2283*9c5db199SXin Li    """
2284*9c5db199SXin Li    gs_cmd = ' '.join(['gsutil', 'ls', uri_pattern])
2285*9c5db199SXin Li    result = system_output(gs_cmd).splitlines()
2286*9c5db199SXin Li    return [path.rstrip() for path in result if path]
2287*9c5db199SXin Li
2288*9c5db199SXin Li
2289*9c5db199SXin Lidef nuke_pids(pid_list, signal_queue=None):
2290*9c5db199SXin Li    """
2291*9c5db199SXin Li    Given a list of pid's, kill them via an esclating series of signals.
2292*9c5db199SXin Li
2293*9c5db199SXin Li    @param pid_list: List of PID's to kill.
2294*9c5db199SXin Li    @param signal_queue: Queue of signals to send the PID's to terminate them.
2295*9c5db199SXin Li
2296*9c5db199SXin Li    @return: A mapping of the signal name to the number of processes it
2297*9c5db199SXin Li        was sent to.
2298*9c5db199SXin Li    """
2299*9c5db199SXin Li    if signal_queue is None:
2300*9c5db199SXin Li        signal_queue = [signal.SIGTERM, signal.SIGKILL]
2301*9c5db199SXin Li    sig_count = {}
2302*9c5db199SXin Li    # Though this is slightly hacky it beats hardcoding names anyday.
2303*9c5db199SXin Li    sig_names = dict((k, v) for v, k in six.iteritems(signal.__dict__)
2304*9c5db199SXin Li                     if v.startswith('SIG'))
2305*9c5db199SXin Li    for sig in signal_queue:
2306*9c5db199SXin Li        logging.debug('Sending signal %s to the following pids:', sig)
2307*9c5db199SXin Li        sig_count[sig_names.get(sig, 'unknown_signal')] = len(pid_list)
2308*9c5db199SXin Li        for pid in pid_list:
2309*9c5db199SXin Li            logging.debug('Pid %d', pid)
2310*9c5db199SXin Li            try:
2311*9c5db199SXin Li                os.kill(pid, sig)
2312*9c5db199SXin Li            except OSError:
2313*9c5db199SXin Li                # The process may have died from a previous signal before we
2314*9c5db199SXin Li                # could kill it.
2315*9c5db199SXin Li                pass
2316*9c5db199SXin Li        if sig == signal.SIGKILL:
2317*9c5db199SXin Li            return sig_count
2318*9c5db199SXin Li        pid_list = [pid for pid in pid_list if pid_is_alive(pid)]
2319*9c5db199SXin Li        if not pid_list:
2320*9c5db199SXin Li            break
2321*9c5db199SXin Li        time.sleep(CHECK_PID_IS_ALIVE_TIMEOUT)
2322*9c5db199SXin Li    failed_list = []
2323*9c5db199SXin Li    for pid in pid_list:
2324*9c5db199SXin Li        if pid_is_alive(pid):
2325*9c5db199SXin Li            failed_list.append('Could not kill %d for process name: %s.' % pid,
2326*9c5db199SXin Li                               get_process_name(pid))
2327*9c5db199SXin Li    if failed_list:
2328*9c5db199SXin Li        raise error.AutoservRunError('Following errors occured: %s' %
2329*9c5db199SXin Li                                     failed_list, None)
2330*9c5db199SXin Li    return sig_count
2331*9c5db199SXin Li
2332*9c5db199SXin Li
2333*9c5db199SXin Lidef externalize_host(host):
2334*9c5db199SXin Li    """Returns an externally accessible host name.
2335*9c5db199SXin Li
2336*9c5db199SXin Li    @param host: a host name or address (string)
2337*9c5db199SXin Li
2338*9c5db199SXin Li    @return An externally visible host name or address
2339*9c5db199SXin Li
2340*9c5db199SXin Li    """
2341*9c5db199SXin Li    return socket.gethostname() if host in _LOCAL_HOST_LIST else host
2342*9c5db199SXin Li
2343*9c5db199SXin Li
2344*9c5db199SXin Lidef urlopen_socket_timeout(url, data=None, timeout=5):
2345*9c5db199SXin Li    """
2346*9c5db199SXin Li    Wrapper to urllib2.urlopen with a socket timeout.
2347*9c5db199SXin Li
2348*9c5db199SXin Li    This method will convert all socket timeouts to
2349*9c5db199SXin Li    TimeoutExceptions, so we can use it in conjunction
2350*9c5db199SXin Li    with the rpc retry decorator and continue to handle
2351*9c5db199SXin Li    other URLErrors as we see fit.
2352*9c5db199SXin Li
2353*9c5db199SXin Li    @param url: The url to open.
2354*9c5db199SXin Li    @param data: The data to send to the url (eg: the urlencoded dictionary
2355*9c5db199SXin Li                 used with a POST call).
2356*9c5db199SXin Li    @param timeout: The timeout for this urlopen call.
2357*9c5db199SXin Li
2358*9c5db199SXin Li    @return: The response of the urlopen call.
2359*9c5db199SXin Li
2360*9c5db199SXin Li    @raises: error.TimeoutException when a socket timeout occurs.
2361*9c5db199SXin Li             urllib2.URLError for errors that not caused by timeout.
2362*9c5db199SXin Li             urllib2.HTTPError for errors like 404 url not found.
2363*9c5db199SXin Li    """
2364*9c5db199SXin Li    old_timeout = socket.getdefaulttimeout()
2365*9c5db199SXin Li    socket.setdefaulttimeout(timeout)
2366*9c5db199SXin Li    try:
2367*9c5db199SXin Li        return urllib.request.urlopen(url, data=data)
2368*9c5db199SXin Li    except urllib.error.URLError as e:
2369*9c5db199SXin Li        if type(e.reason) is socket.timeout:
2370*9c5db199SXin Li            raise error.TimeoutException(str(e))
2371*9c5db199SXin Li        raise
2372*9c5db199SXin Li    finally:
2373*9c5db199SXin Li        socket.setdefaulttimeout(old_timeout)
2374*9c5db199SXin Li
2375*9c5db199SXin Li
2376*9c5db199SXin Lidef parse_chrome_version(version_string):
2377*9c5db199SXin Li    """
2378*9c5db199SXin Li    Parse a chrome version string and return version and milestone.
2379*9c5db199SXin Li
2380*9c5db199SXin Li    Given a chrome version of the form "W.X.Y.Z", return "W.X.Y.Z" as
2381*9c5db199SXin Li    the version and "W" as the milestone.
2382*9c5db199SXin Li
2383*9c5db199SXin Li    @param version_string: Chrome version string.
2384*9c5db199SXin Li    @return: a tuple (chrome_version, milestone). If the incoming version
2385*9c5db199SXin Li             string is not of the form "W.X.Y.Z", chrome_version will
2386*9c5db199SXin Li             be set to the incoming "version_string" argument and the
2387*9c5db199SXin Li             milestone will be set to the empty string.
2388*9c5db199SXin Li    """
2389*9c5db199SXin Li    match = re.search('(\d+)\.\d+\.\d+\.\d+', version_string)
2390*9c5db199SXin Li    ver = match.group(0) if match else version_string
2391*9c5db199SXin Li    milestone = match.group(1) if match else ''
2392*9c5db199SXin Li    return ver, milestone
2393*9c5db199SXin Li
2394*9c5db199SXin Li
2395*9c5db199SXin Lidef parse_gs_uri_version(uri):
2396*9c5db199SXin Li    """Pull out major.minor.sub from image URI
2397*9c5db199SXin Li
2398*9c5db199SXin Li    @param uri: A GS URI for a bucket containing ChromeOS build artifacts
2399*9c5db199SXin Li    @return: The build version as a string in the form 'major.minor.sub'
2400*9c5db199SXin Li
2401*9c5db199SXin Li    """
2402*9c5db199SXin Li    return re.sub('.*(R[0-9]+|LATEST)-', '', uri).strip('/')
2403*9c5db199SXin Li
2404*9c5db199SXin Li
2405*9c5db199SXin Lidef compare_gs_uri_build_versions(x, y):
2406*9c5db199SXin Li    """Compares two bucket URIs by their version string
2407*9c5db199SXin Li
2408*9c5db199SXin Li    @param x: A GS URI for a bucket containing ChromeOS build artifacts
2409*9c5db199SXin Li    @param y: Another GS URI for a bucket containing ChromeOS build artifacts
2410*9c5db199SXin Li    @return: 1 if x > y, -1 if x < y, and 0 if x == y
2411*9c5db199SXin Li
2412*9c5db199SXin Li    """
2413*9c5db199SXin Li    # Converts a gs uri 'gs://.../R75-<major>.<minor>.<sub>' to
2414*9c5db199SXin Li    # [major, minor, sub]
2415*9c5db199SXin Li    split_version = lambda v: [int(x) for x in
2416*9c5db199SXin Li                               parse_gs_uri_version(v).split('.')]
2417*9c5db199SXin Li
2418*9c5db199SXin Li    x_version = split_version(x)
2419*9c5db199SXin Li    y_version = split_version(y)
2420*9c5db199SXin Li
2421*9c5db199SXin Li    for a, b in zip(x_version, y_version):
2422*9c5db199SXin Li        if a > b:
2423*9c5db199SXin Li            return 1
2424*9c5db199SXin Li        elif b > a:
2425*9c5db199SXin Li            return -1
2426*9c5db199SXin Li
2427*9c5db199SXin Li    return 0
2428*9c5db199SXin Li
2429*9c5db199SXin Li
2430*9c5db199SXin Lidef is_localhost(server):
2431*9c5db199SXin Li    """Check if server is equivalent to localhost.
2432*9c5db199SXin Li
2433*9c5db199SXin Li    @param server: Name of the server to check.
2434*9c5db199SXin Li
2435*9c5db199SXin Li    @return: True if given server is equivalent to localhost.
2436*9c5db199SXin Li
2437*9c5db199SXin Li    @raise socket.gaierror: If server name failed to be resolved.
2438*9c5db199SXin Li    """
2439*9c5db199SXin Li    if server in _LOCAL_HOST_LIST:
2440*9c5db199SXin Li        return True
2441*9c5db199SXin Li    try:
2442*9c5db199SXin Li        return (socket.gethostbyname(socket.gethostname()) ==
2443*9c5db199SXin Li                socket.gethostbyname(server))
2444*9c5db199SXin Li    except socket.gaierror:
2445*9c5db199SXin Li        logging.error('Failed to resolve server name %s.', server)
2446*9c5db199SXin Li        return False
2447*9c5db199SXin Li
2448*9c5db199SXin Li
2449*9c5db199SXin Lidef get_function_arg_value(func, arg_name, args, kwargs):
2450*9c5db199SXin Li    """Get the value of the given argument for the function.
2451*9c5db199SXin Li
2452*9c5db199SXin Li    @param func: Function being called with given arguments.
2453*9c5db199SXin Li    @param arg_name: Name of the argument to look for value.
2454*9c5db199SXin Li    @param args: arguments for function to be called.
2455*9c5db199SXin Li    @param kwargs: keyword arguments for function to be called.
2456*9c5db199SXin Li
2457*9c5db199SXin Li    @return: The value of the given argument for the function.
2458*9c5db199SXin Li
2459*9c5db199SXin Li    @raise ValueError: If the argument is not listed function arguemnts.
2460*9c5db199SXin Li    @raise KeyError: If no value is found for the given argument.
2461*9c5db199SXin Li    """
2462*9c5db199SXin Li    if arg_name in kwargs:
2463*9c5db199SXin Li        return kwargs[arg_name]
2464*9c5db199SXin Li
2465*9c5db199SXin Li    argspec = inspect.getargspec(func)
2466*9c5db199SXin Li    index = argspec.args.index(arg_name)
2467*9c5db199SXin Li    try:
2468*9c5db199SXin Li        return args[index]
2469*9c5db199SXin Li    except IndexError:
2470*9c5db199SXin Li        try:
2471*9c5db199SXin Li            # The argument can use a default value. Reverse the default value
2472*9c5db199SXin Li            # so argument with default value can be counted from the last to
2473*9c5db199SXin Li            # the first.
2474*9c5db199SXin Li            return argspec.defaults[::-1][len(argspec.args) - index - 1]
2475*9c5db199SXin Li        except IndexError:
2476*9c5db199SXin Li            raise KeyError('Argument %s is not given a value. argspec: %s, '
2477*9c5db199SXin Li                           'args:%s, kwargs:%s' %
2478*9c5db199SXin Li                           (arg_name, argspec, args, kwargs))
2479*9c5db199SXin Li
2480*9c5db199SXin Li
2481*9c5db199SXin Lidef has_systemd():
2482*9c5db199SXin Li    """Check if the host is running systemd.
2483*9c5db199SXin Li
2484*9c5db199SXin Li    @return: True if the host uses systemd, otherwise returns False.
2485*9c5db199SXin Li    """
2486*9c5db199SXin Li    return os.path.basename(os.readlink('/proc/1/exe')) == 'systemd'
2487*9c5db199SXin Li
2488*9c5db199SXin Li
2489*9c5db199SXin Lidef get_real_user():
2490*9c5db199SXin Li    """Get the real user that runs the script.
2491*9c5db199SXin Li
2492*9c5db199SXin Li    The function check environment variable SUDO_USER for the user if the
2493*9c5db199SXin Li    script is run with sudo. Otherwise, it returns the value of environment
2494*9c5db199SXin Li    variable USER.
2495*9c5db199SXin Li
2496*9c5db199SXin Li    @return: The user name that runs the script.
2497*9c5db199SXin Li
2498*9c5db199SXin Li    """
2499*9c5db199SXin Li    user = os.environ.get('SUDO_USER')
2500*9c5db199SXin Li    if not user:
2501*9c5db199SXin Li        user = os.environ.get('USER')
2502*9c5db199SXin Li    return user
2503*9c5db199SXin Li
2504*9c5db199SXin Li
2505*9c5db199SXin Lidef get_service_pid(service_name):
2506*9c5db199SXin Li    """Return pid of service.
2507*9c5db199SXin Li
2508*9c5db199SXin Li    @param service_name: string name of service.
2509*9c5db199SXin Li
2510*9c5db199SXin Li    @return: pid or 0 if service is not running.
2511*9c5db199SXin Li    """
2512*9c5db199SXin Li    if has_systemd():
2513*9c5db199SXin Li        # systemctl show prints 'MainPID=0' if the service is not running.
2514*9c5db199SXin Li        cmd_result = run('systemctl show -p MainPID %s' %
2515*9c5db199SXin Li                                    service_name, ignore_status=True)
2516*9c5db199SXin Li        return int(cmd_result.stdout.split('=')[1])
2517*9c5db199SXin Li    else:
2518*9c5db199SXin Li        cmd_result = run('status %s' % service_name,
2519*9c5db199SXin Li                                        ignore_status=True)
2520*9c5db199SXin Li        if 'start/running' in cmd_result.stdout:
2521*9c5db199SXin Li            return int(cmd_result.stdout.split()[3])
2522*9c5db199SXin Li        return 0
2523*9c5db199SXin Li
2524*9c5db199SXin Li
2525*9c5db199SXin Lidef control_service(service_name, action='start', ignore_status=True):
2526*9c5db199SXin Li    """Controls a service. It can be used to start, stop or restart
2527*9c5db199SXin Li    a service.
2528*9c5db199SXin Li
2529*9c5db199SXin Li    @param service_name: string service to be restarted.
2530*9c5db199SXin Li
2531*9c5db199SXin Li    @param action: string choice of action to control command.
2532*9c5db199SXin Li
2533*9c5db199SXin Li    @param ignore_status: boolean ignore if system command fails.
2534*9c5db199SXin Li
2535*9c5db199SXin Li    @return: status code of the executed command.
2536*9c5db199SXin Li    """
2537*9c5db199SXin Li    if action not in ('start', 'stop', 'restart'):
2538*9c5db199SXin Li        raise ValueError('Unknown action supplied as parameter.')
2539*9c5db199SXin Li
2540*9c5db199SXin Li    control_cmd = action + ' ' + service_name
2541*9c5db199SXin Li    if has_systemd():
2542*9c5db199SXin Li        control_cmd = 'systemctl ' + control_cmd
2543*9c5db199SXin Li    return system(control_cmd, ignore_status=ignore_status)
2544*9c5db199SXin Li
2545*9c5db199SXin Li
2546*9c5db199SXin Lidef restart_service(service_name, ignore_status=True):
2547*9c5db199SXin Li    """Restarts a service
2548*9c5db199SXin Li
2549*9c5db199SXin Li    @param service_name: string service to be restarted.
2550*9c5db199SXin Li
2551*9c5db199SXin Li    @param ignore_status: boolean ignore if system command fails.
2552*9c5db199SXin Li
2553*9c5db199SXin Li    @return: status code of the executed command.
2554*9c5db199SXin Li    """
2555*9c5db199SXin Li    return control_service(service_name, action='restart',
2556*9c5db199SXin Li                           ignore_status=ignore_status)
2557*9c5db199SXin Li
2558*9c5db199SXin Li
2559*9c5db199SXin Lidef start_service(service_name, ignore_status=True):
2560*9c5db199SXin Li    """Starts a service
2561*9c5db199SXin Li
2562*9c5db199SXin Li    @param service_name: string service to be started.
2563*9c5db199SXin Li
2564*9c5db199SXin Li    @param ignore_status: boolean ignore if system command fails.
2565*9c5db199SXin Li
2566*9c5db199SXin Li    @return: status code of the executed command.
2567*9c5db199SXin Li    """
2568*9c5db199SXin Li    return control_service(service_name, action='start',
2569*9c5db199SXin Li                           ignore_status=ignore_status)
2570*9c5db199SXin Li
2571*9c5db199SXin Li
2572*9c5db199SXin Lidef stop_service(service_name, ignore_status=True):
2573*9c5db199SXin Li    """Stops a service
2574*9c5db199SXin Li
2575*9c5db199SXin Li    @param service_name: string service to be stopped.
2576*9c5db199SXin Li
2577*9c5db199SXin Li    @param ignore_status: boolean ignore if system command fails.
2578*9c5db199SXin Li
2579*9c5db199SXin Li    @return: status code of the executed command.
2580*9c5db199SXin Li    """
2581*9c5db199SXin Li    return control_service(service_name, action='stop',
2582*9c5db199SXin Li                           ignore_status=ignore_status)
2583*9c5db199SXin Li
2584*9c5db199SXin Li
2585*9c5db199SXin Lidef sudo_require_password():
2586*9c5db199SXin Li    """Test if the process can run sudo command without using password.
2587*9c5db199SXin Li
2588*9c5db199SXin Li    @return: True if the process needs password to run sudo command.
2589*9c5db199SXin Li
2590*9c5db199SXin Li    """
2591*9c5db199SXin Li    try:
2592*9c5db199SXin Li        run('sudo -n true')
2593*9c5db199SXin Li        return False
2594*9c5db199SXin Li    except error.CmdError:
2595*9c5db199SXin Li        logging.warning('sudo command requires password.')
2596*9c5db199SXin Li        return True
2597*9c5db199SXin Li
2598*9c5db199SXin Li
2599*9c5db199SXin Lidef is_in_container():
2600*9c5db199SXin Li    """Check if the process is running inside a container.
2601*9c5db199SXin Li
2602*9c5db199SXin Li    @return: True if the process is running inside a container, otherwise False.
2603*9c5db199SXin Li    """
2604*9c5db199SXin Li    result = run('grep -q "/lxc/" /proc/1/cgroup',
2605*9c5db199SXin Li                            verbose=False, ignore_status=True)
2606*9c5db199SXin Li    if result.exit_status == 0:
2607*9c5db199SXin Li        return True
2608*9c5db199SXin Li
2609*9c5db199SXin Li    # Check "container" environment variable for lxd/lxc containers.
2610*9c5db199SXin Li    if os.environ.get('container') == 'lxc':
2611*9c5db199SXin Li        return True
2612*9c5db199SXin Li
2613*9c5db199SXin Li    return False
2614*9c5db199SXin Li
2615*9c5db199SXin Li
2616*9c5db199SXin Lidef is_flash_installed():
2617*9c5db199SXin Li    """
2618*9c5db199SXin Li    The Adobe Flash binary is only distributed with internal builds.
2619*9c5db199SXin Li    """
2620*9c5db199SXin Li    return (os.path.exists('/opt/google/chrome/pepper/libpepflashplayer.so')
2621*9c5db199SXin Li        and os.path.exists('/opt/google/chrome/pepper/pepper-flash.info'))
2622*9c5db199SXin Li
2623*9c5db199SXin Li
2624*9c5db199SXin Lidef verify_flash_installed():
2625*9c5db199SXin Li    """
2626*9c5db199SXin Li    The Adobe Flash binary is only distributed with internal builds.
2627*9c5db199SXin Li    Warn users of public builds of the extra dependency.
2628*9c5db199SXin Li    """
2629*9c5db199SXin Li    if not is_flash_installed():
2630*9c5db199SXin Li        raise error.TestNAError('No Adobe Flash binary installed.')
2631*9c5db199SXin Li
2632*9c5db199SXin Li
2633*9c5db199SXin Lidef is_in_same_subnet(ip_1, ip_2, mask_bits=24):
2634*9c5db199SXin Li    """Check if two IP addresses are in the same subnet with given mask bits.
2635*9c5db199SXin Li
2636*9c5db199SXin Li    The two IP addresses are string of IPv4, e.g., '192.168.0.3'.
2637*9c5db199SXin Li
2638*9c5db199SXin Li    @param ip_1: First IP address to compare.
2639*9c5db199SXin Li    @param ip_2: Second IP address to compare.
2640*9c5db199SXin Li    @param mask_bits: Number of mask bits for subnet comparison. Default to 24.
2641*9c5db199SXin Li
2642*9c5db199SXin Li    @return: True if the two IP addresses are in the same subnet.
2643*9c5db199SXin Li
2644*9c5db199SXin Li    """
2645*9c5db199SXin Li    mask = ((2<<mask_bits-1) -1)<<(32-mask_bits)
2646*9c5db199SXin Li    ip_1_num = struct.unpack('!I', socket.inet_aton(ip_1))[0]
2647*9c5db199SXin Li    ip_2_num = struct.unpack('!I', socket.inet_aton(ip_2))[0]
2648*9c5db199SXin Li    return ip_1_num & mask == ip_2_num & mask
2649*9c5db199SXin Li
2650*9c5db199SXin Li
2651*9c5db199SXin Lidef get_ip_address(hostname=None):
2652*9c5db199SXin Li    """Get the IP address of given hostname or current machine.
2653*9c5db199SXin Li
2654*9c5db199SXin Li    @param hostname: Hostname of a DUT, default value is None.
2655*9c5db199SXin Li
2656*9c5db199SXin Li    @return: The IP address of given hostname. If hostname is not given then
2657*9c5db199SXin Li             we'll try to query the IP address of the current machine and
2658*9c5db199SXin Li             return.
2659*9c5db199SXin Li    """
2660*9c5db199SXin Li    if hostname:
2661*9c5db199SXin Li        try:
2662*9c5db199SXin Li            return socket.gethostbyname(hostname)
2663*9c5db199SXin Li        except socket.gaierror as e:
2664*9c5db199SXin Li            logging.error(
2665*9c5db199SXin Li                'Failed to get IP address of %s, error: %s.', hostname, e)
2666*9c5db199SXin Li    else:
2667*9c5db199SXin Li        s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
2668*9c5db199SXin Li        s.connect(("8.8.8.8", 80))
2669*9c5db199SXin Li        ip = s.getsockname()[0]
2670*9c5db199SXin Li        s.close()
2671*9c5db199SXin Li        return ip
2672*9c5db199SXin Li
2673*9c5db199SXin Li
2674*9c5db199SXin Lidef get_servers_in_same_subnet(host_ip, mask_bits, servers=None,
2675*9c5db199SXin Li                               server_ip_map=None):
2676*9c5db199SXin Li    """Get the servers in the same subnet of the given host ip.
2677*9c5db199SXin Li
2678*9c5db199SXin Li    @param host_ip: The IP address of a dut to look for devserver.
2679*9c5db199SXin Li    @param mask_bits: Number of mask bits.
2680*9c5db199SXin Li    @param servers: A list of servers to be filtered by subnet specified by
2681*9c5db199SXin Li                    host_ip and mask_bits.
2682*9c5db199SXin Li    @param server_ip_map: A map between the server name and its IP address.
2683*9c5db199SXin Li            The map can be pre-built for better performance, e.g., when
2684*9c5db199SXin Li            allocating a drone for an agent task.
2685*9c5db199SXin Li
2686*9c5db199SXin Li    @return: A list of servers in the same subnet of the given host ip.
2687*9c5db199SXin Li
2688*9c5db199SXin Li    """
2689*9c5db199SXin Li    matched_servers = []
2690*9c5db199SXin Li    if not servers and not server_ip_map:
2691*9c5db199SXin Li        raise ValueError('Either `servers` or `server_ip_map` must be given.')
2692*9c5db199SXin Li    if not servers:
2693*9c5db199SXin Li        servers = list(server_ip_map.keys())
2694*9c5db199SXin Li    # Make sure server_ip_map is an empty dict if it's not set.
2695*9c5db199SXin Li    if not server_ip_map:
2696*9c5db199SXin Li        server_ip_map = {}
2697*9c5db199SXin Li    for server in servers:
2698*9c5db199SXin Li        server_ip = server_ip_map.get(server, get_ip_address(server))
2699*9c5db199SXin Li        if server_ip and is_in_same_subnet(server_ip, host_ip, mask_bits):
2700*9c5db199SXin Li            matched_servers.append(server)
2701*9c5db199SXin Li    return matched_servers
2702*9c5db199SXin Li
2703*9c5db199SXin Li
2704*9c5db199SXin Lidef get_restricted_subnet(hostname, restricted_subnets=None):
2705*9c5db199SXin Li    """Get the restricted subnet of given hostname.
2706*9c5db199SXin Li
2707*9c5db199SXin Li    @param hostname: Name of the host to look for matched restricted subnet.
2708*9c5db199SXin Li    @param restricted_subnets: A list of restricted subnets, default is set to
2709*9c5db199SXin Li            RESTRICTED_SUBNETS.
2710*9c5db199SXin Li
2711*9c5db199SXin Li    @return: A tuple of (subnet_ip, mask_bits), which defines a restricted
2712*9c5db199SXin Li             subnet.
2713*9c5db199SXin Li    """
2714*9c5db199SXin Li    if restricted_subnets is None:
2715*9c5db199SXin Li        restricted_subnets=RESTRICTED_SUBNETS
2716*9c5db199SXin Li    host_ip = get_ip_address(hostname)
2717*9c5db199SXin Li    if not host_ip:
2718*9c5db199SXin Li        return
2719*9c5db199SXin Li    for subnet_ip, mask_bits in restricted_subnets:
2720*9c5db199SXin Li        if is_in_same_subnet(subnet_ip, host_ip, mask_bits):
2721*9c5db199SXin Li            return subnet_ip, mask_bits
2722*9c5db199SXin Li
2723*9c5db199SXin Li
2724*9c5db199SXin Lidef get_wireless_ssid(hostname):
2725*9c5db199SXin Li    """Get the wireless ssid based on given hostname.
2726*9c5db199SXin Li
2727*9c5db199SXin Li    The method tries to locate the wireless ssid in the same subnet of given
2728*9c5db199SXin Li    hostname first. If none is found, it returns the default setting in
2729*9c5db199SXin Li    CLIENT/wireless_ssid.
2730*9c5db199SXin Li
2731*9c5db199SXin Li    @param hostname: Hostname of the test device.
2732*9c5db199SXin Li
2733*9c5db199SXin Li    @return: wireless ssid for the test device.
2734*9c5db199SXin Li    """
2735*9c5db199SXin Li    default_ssid = CONFIG.get_config_value('CLIENT', 'wireless_ssid',
2736*9c5db199SXin Li                                           default=None)
2737*9c5db199SXin Li    host_ip = get_ip_address(hostname)
2738*9c5db199SXin Li    if not host_ip:
2739*9c5db199SXin Li        return default_ssid
2740*9c5db199SXin Li
2741*9c5db199SXin Li    # Get all wireless ssid in the global config.
2742*9c5db199SXin Li    ssids = CONFIG.get_config_value_regex('CLIENT', WIRELESS_SSID_PATTERN)
2743*9c5db199SXin Li
2744*9c5db199SXin Li    # There could be multiple subnet matches, pick the one with most strict
2745*9c5db199SXin Li    # match, i.e., the one with highest maskbit.
2746*9c5db199SXin Li    matched_ssid = default_ssid
2747*9c5db199SXin Li    matched_maskbit = -1
2748*9c5db199SXin Li    for key, value in ssids.items():
2749*9c5db199SXin Li        # The config key filtered by regex WIRELESS_SSID_PATTERN has a format of
2750*9c5db199SXin Li        # wireless_ssid_[subnet_ip]/[maskbit], for example:
2751*9c5db199SXin Li        # wireless_ssid_192.168.0.1/24
2752*9c5db199SXin Li        # Following line extract the subnet ip and mask bit from the key name.
2753*9c5db199SXin Li        match = re.match(WIRELESS_SSID_PATTERN, key)
2754*9c5db199SXin Li        subnet_ip, maskbit = match.groups()
2755*9c5db199SXin Li        maskbit = int(maskbit)
2756*9c5db199SXin Li        if (is_in_same_subnet(subnet_ip, host_ip, maskbit) and
2757*9c5db199SXin Li            maskbit > matched_maskbit):
2758*9c5db199SXin Li            matched_ssid = value
2759*9c5db199SXin Li            matched_maskbit = maskbit
2760*9c5db199SXin Li    return matched_ssid
2761*9c5db199SXin Li
2762*9c5db199SXin Li
2763*9c5db199SXin Lidef parse_launch_control_build(build_name):
2764*9c5db199SXin Li    """Get branch, target, build_id from the given Launch Control build_name.
2765*9c5db199SXin Li
2766*9c5db199SXin Li    @param build_name: Name of a Launch Control build, should be formated as
2767*9c5db199SXin Li                       branch/target/build_id
2768*9c5db199SXin Li
2769*9c5db199SXin Li    @return: Tuple of branch, target, build_id
2770*9c5db199SXin Li    @raise ValueError: If the build_name is not correctly formated.
2771*9c5db199SXin Li    """
2772*9c5db199SXin Li    branch, target, build_id = build_name.split('/')
2773*9c5db199SXin Li    return branch, target, build_id
2774*9c5db199SXin Li
2775*9c5db199SXin Li
2776*9c5db199SXin Lidef parse_android_target(target):
2777*9c5db199SXin Li    """Get board and build type from the given target.
2778*9c5db199SXin Li
2779*9c5db199SXin Li    @param target: Name of an Android build target, e.g., shamu-eng.
2780*9c5db199SXin Li
2781*9c5db199SXin Li    @return: Tuple of board, build_type
2782*9c5db199SXin Li    @raise ValueError: If the target is not correctly formated.
2783*9c5db199SXin Li    """
2784*9c5db199SXin Li    board, build_type = target.split('-')
2785*9c5db199SXin Li    return board, build_type
2786*9c5db199SXin Li
2787*9c5db199SXin Li
2788*9c5db199SXin Lidef parse_launch_control_target(target):
2789*9c5db199SXin Li    """Parse the build target and type from a Launch Control target.
2790*9c5db199SXin Li
2791*9c5db199SXin Li    The Launch Control target has the format of build_target-build_type, e.g.,
2792*9c5db199SXin Li    shamu-eng or dragonboard-userdebug. This method extracts the build target
2793*9c5db199SXin Li    and type from the target name.
2794*9c5db199SXin Li
2795*9c5db199SXin Li    @param target: Name of a Launch Control target, e.g., shamu-eng.
2796*9c5db199SXin Li
2797*9c5db199SXin Li    @return: (build_target, build_type), e.g., ('shamu', 'userdebug')
2798*9c5db199SXin Li    """
2799*9c5db199SXin Li    match = re.match('(?P<build_target>.+)-(?P<build_type>[^-]+)', target)
2800*9c5db199SXin Li    if match:
2801*9c5db199SXin Li        return match.group('build_target'), match.group('build_type')
2802*9c5db199SXin Li    else:
2803*9c5db199SXin Li        return None, None
2804*9c5db199SXin Li
2805*9c5db199SXin Li
2806*9c5db199SXin Lidef is_launch_control_build(build):
2807*9c5db199SXin Li    """Check if a given build is a Launch Control build.
2808*9c5db199SXin Li
2809*9c5db199SXin Li    @param build: Name of a build, e.g.,
2810*9c5db199SXin Li                  ChromeOS build: daisy-release/R50-1234.0.0
2811*9c5db199SXin Li                  Launch Control build: git_mnc_release/shamu-eng
2812*9c5db199SXin Li
2813*9c5db199SXin Li    @return: True if the build name matches the pattern of a Launch Control
2814*9c5db199SXin Li             build, False otherwise.
2815*9c5db199SXin Li    """
2816*9c5db199SXin Li    try:
2817*9c5db199SXin Li        _, target, _ = parse_launch_control_build(build)
2818*9c5db199SXin Li        build_target, _ = parse_launch_control_target(target)
2819*9c5db199SXin Li        if build_target:
2820*9c5db199SXin Li            return True
2821*9c5db199SXin Li    except ValueError:
2822*9c5db199SXin Li        # parse_launch_control_build or parse_launch_control_target failed.
2823*9c5db199SXin Li        pass
2824*9c5db199SXin Li    return False
2825*9c5db199SXin Li
2826*9c5db199SXin Li
2827*9c5db199SXin Lidef which(exec_file):
2828*9c5db199SXin Li    """Finds an executable file.
2829*9c5db199SXin Li
2830*9c5db199SXin Li    If the file name contains a path component, it is checked as-is.
2831*9c5db199SXin Li    Otherwise, we check with each of the path components found in the system
2832*9c5db199SXin Li    PATH prepended. This behavior is similar to the 'which' command-line tool.
2833*9c5db199SXin Li
2834*9c5db199SXin Li    @param exec_file: Name or path to desired executable.
2835*9c5db199SXin Li
2836*9c5db199SXin Li    @return: An actual path to the executable, or None if not found.
2837*9c5db199SXin Li    """
2838*9c5db199SXin Li    if os.path.dirname(exec_file):
2839*9c5db199SXin Li        return exec_file if os.access(exec_file, os.X_OK) else None
2840*9c5db199SXin Li    sys_path = os.environ.get('PATH')
2841*9c5db199SXin Li    prefix_list = sys_path.split(os.pathsep) if sys_path else []
2842*9c5db199SXin Li    for prefix in prefix_list:
2843*9c5db199SXin Li        path = os.path.join(prefix, exec_file)
2844*9c5db199SXin Li        if os.access(path, os.X_OK):
2845*9c5db199SXin Li            return path
2846*9c5db199SXin Li
2847*9c5db199SXin Li
2848*9c5db199SXin Liclass TimeoutError(error.TestError):
2849*9c5db199SXin Li    """Error raised when poll_for_condition() failed to poll within time.
2850*9c5db199SXin Li
2851*9c5db199SXin Li    It may embed a reason (either a string or an exception object) so that
2852*9c5db199SXin Li    the caller of poll_for_condition() can handle failure better.
2853*9c5db199SXin Li    """
2854*9c5db199SXin Li
2855*9c5db199SXin Li    def __init__(self, message=None, reason=None):
2856*9c5db199SXin Li        """Constructor.
2857*9c5db199SXin Li
2858*9c5db199SXin Li        It supports three invocations:
2859*9c5db199SXin Li        1) TimeoutError()
2860*9c5db199SXin Li        2) TimeoutError(message): with customized message.
2861*9c5db199SXin Li        3) TimeoutError(message, reason): with message and reason for timeout.
2862*9c5db199SXin Li        """
2863*9c5db199SXin Li        self.reason = reason
2864*9c5db199SXin Li        if self.reason:
2865*9c5db199SXin Li            reason_str = 'Reason: ' + repr(self.reason)
2866*9c5db199SXin Li            if message:
2867*9c5db199SXin Li                message += '. ' + reason_str
2868*9c5db199SXin Li            else:
2869*9c5db199SXin Li                message = reason_str
2870*9c5db199SXin Li
2871*9c5db199SXin Li        if message:
2872*9c5db199SXin Li            super(TimeoutError, self).__init__(message)
2873*9c5db199SXin Li        else:
2874*9c5db199SXin Li            super(TimeoutError, self).__init__()
2875*9c5db199SXin Li
2876*9c5db199SXin Li
2877*9c5db199SXin Liclass Timer(object):
2878*9c5db199SXin Li    """A synchronous timer to evaluate if timout is reached.
2879*9c5db199SXin Li
2880*9c5db199SXin Li    Usage:
2881*9c5db199SXin Li      timer = Timer(timeout_sec)
2882*9c5db199SXin Li      while timer.sleep(sleep_interval):
2883*9c5db199SXin Li        # do something...
2884*9c5db199SXin Li    """
2885*9c5db199SXin Li    def __init__(self, timeout):
2886*9c5db199SXin Li        """Constructor.
2887*9c5db199SXin Li
2888*9c5db199SXin Li        Note that timer won't start until next() is called.
2889*9c5db199SXin Li
2890*9c5db199SXin Li        @param timeout: timer timeout in seconds.
2891*9c5db199SXin Li        """
2892*9c5db199SXin Li        self.timeout = timeout
2893*9c5db199SXin Li        self.deadline = 0
2894*9c5db199SXin Li
2895*9c5db199SXin Li    def sleep(self, interval):
2896*9c5db199SXin Li        """Checks if it has sufficient time to sleep; sleeps if so.
2897*9c5db199SXin Li
2898*9c5db199SXin Li        It blocks for |interval| seconds if it has time to sleep.
2899*9c5db199SXin Li        If timer is not ticked yet, kicks it off and returns True without
2900*9c5db199SXin Li        sleep.
2901*9c5db199SXin Li
2902*9c5db199SXin Li        @param interval: sleep interval in seconds.
2903*9c5db199SXin Li        @return True if it has sleeped or just kicked off the timer. False
2904*9c5db199SXin Li                otherwise.
2905*9c5db199SXin Li        """
2906*9c5db199SXin Li        now = time.time()
2907*9c5db199SXin Li        if not self.deadline:
2908*9c5db199SXin Li            self.deadline = now + self.timeout
2909*9c5db199SXin Li            return True
2910*9c5db199SXin Li        if now + interval < self.deadline:
2911*9c5db199SXin Li            time.sleep(interval)
2912*9c5db199SXin Li            return True
2913*9c5db199SXin Li        return False
2914*9c5db199SXin Li
2915*9c5db199SXin Li
2916*9c5db199SXin Lidef poll_for_condition(condition,
2917*9c5db199SXin Li                       exception=None,
2918*9c5db199SXin Li                       timeout=10,
2919*9c5db199SXin Li                       sleep_interval=0.1,
2920*9c5db199SXin Li                       desc=None):
2921*9c5db199SXin Li    """Polls until a condition is evaluated to true.
2922*9c5db199SXin Li
2923*9c5db199SXin Li    @param condition: function taking no args and returning anything that will
2924*9c5db199SXin Li                      evaluate to True in a conditional check
2925*9c5db199SXin Li    @param exception: exception to throw if condition doesn't evaluate to true
2926*9c5db199SXin Li    @param timeout: maximum number of seconds to wait
2927*9c5db199SXin Li    @param sleep_interval: time to sleep between polls
2928*9c5db199SXin Li    @param desc: description of default TimeoutError used if 'exception' is
2929*9c5db199SXin Li                 None
2930*9c5db199SXin Li
2931*9c5db199SXin Li    @return The evaluated value that caused the poll loop to terminate.
2932*9c5db199SXin Li
2933*9c5db199SXin Li    @raise 'exception' arg if supplied; TimeoutError otherwise
2934*9c5db199SXin Li    """
2935*9c5db199SXin Li    start_time = time.time()
2936*9c5db199SXin Li    while True:
2937*9c5db199SXin Li        value = condition()
2938*9c5db199SXin Li        if value:
2939*9c5db199SXin Li            return value
2940*9c5db199SXin Li        if time.time() + sleep_interval - start_time > timeout:
2941*9c5db199SXin Li            if exception:
2942*9c5db199SXin Li                logging.error('Will raise error %r due to unexpected return: '
2943*9c5db199SXin Li                              '%r', exception, value)
2944*9c5db199SXin Li                raise exception # pylint: disable=raising-bad-type
2945*9c5db199SXin Li
2946*9c5db199SXin Li            if desc:
2947*9c5db199SXin Li                desc = 'Timed out waiting for condition: ' + desc
2948*9c5db199SXin Li            else:
2949*9c5db199SXin Li                desc = 'Timed out waiting for unnamed condition'
2950*9c5db199SXin Li            logging.error(desc)
2951*9c5db199SXin Li            raise TimeoutError(message=desc)
2952*9c5db199SXin Li
2953*9c5db199SXin Li        time.sleep(sleep_interval)
2954*9c5db199SXin Li
2955*9c5db199SXin Li
2956*9c5db199SXin Lidef poll_for_condition_ex(condition, timeout=10, sleep_interval=0.1, desc=None):
2957*9c5db199SXin Li    """Polls until a condition is evaluated to true or until timeout.
2958*9c5db199SXin Li
2959*9c5db199SXin Li    Similiar to poll_for_condition, except that it handles exceptions
2960*9c5db199SXin Li    condition() raises. If timeout is not reached, the exception is dropped and
2961*9c5db199SXin Li    poll for condition after a sleep; otherwise, the exception is embedded into
2962*9c5db199SXin Li    TimeoutError to raise.
2963*9c5db199SXin Li
2964*9c5db199SXin Li    @param condition: function taking no args and returning anything that will
2965*9c5db199SXin Li                      evaluate to True in a conditional check
2966*9c5db199SXin Li    @param timeout: maximum number of seconds to wait
2967*9c5db199SXin Li    @param sleep_interval: time to sleep between polls
2968*9c5db199SXin Li    @param desc: description of the condition
2969*9c5db199SXin Li
2970*9c5db199SXin Li    @return The evaluated value that caused the poll loop to terminate.
2971*9c5db199SXin Li
2972*9c5db199SXin Li    @raise TimeoutError. If condition() raised exception, it is embedded in
2973*9c5db199SXin Li           raised TimeoutError.
2974*9c5db199SXin Li    """
2975*9c5db199SXin Li    timer = Timer(timeout)
2976*9c5db199SXin Li    while timer.sleep(sleep_interval):
2977*9c5db199SXin Li        reason = None
2978*9c5db199SXin Li        try:
2979*9c5db199SXin Li            value = condition()
2980*9c5db199SXin Li            if value:
2981*9c5db199SXin Li                return value
2982*9c5db199SXin Li        except BaseException as e:
2983*9c5db199SXin Li            reason = e
2984*9c5db199SXin Li
2985*9c5db199SXin Li    if desc is None:
2986*9c5db199SXin Li        desc = 'unamed condition'
2987*9c5db199SXin Li    if reason is None:
2988*9c5db199SXin Li        reason = 'condition evaluted as false'
2989*9c5db199SXin Li    to_raise = TimeoutError(message='Timed out waiting for ' + desc,
2990*9c5db199SXin Li                            reason=reason)
2991*9c5db199SXin Li    logging.error(str(to_raise))
2992*9c5db199SXin Li    raise to_raise
2993*9c5db199SXin Li
2994*9c5db199SXin Li
2995*9c5db199SXin Lidef poll_till_condition_holds(condition,
2996*9c5db199SXin Li                              exception=None,
2997*9c5db199SXin Li                              timeout=10,
2998*9c5db199SXin Li                              sleep_interval=0.1,
2999*9c5db199SXin Li                              hold_interval=5,
3000*9c5db199SXin Li                              desc=None):
3001*9c5db199SXin Li    """Polls until a condition is evaluated to true for a period of time
3002*9c5db199SXin Li
3003*9c5db199SXin Li    This function checks that a condition remains true for the 'hold_interval'
3004*9c5db199SXin Li    seconds after it first becomes true. If the condition becomes false
3005*9c5db199SXin Li    subsequently, the timer is reset. This function will not detect if
3006*9c5db199SXin Li    condition becomes false for any period of time less than the sleep_interval.
3007*9c5db199SXin Li
3008*9c5db199SXin Li    @param condition: function taking no args and returning anything that will
3009*9c5db199SXin Li                      evaluate to True in a conditional check
3010*9c5db199SXin Li    @param exception: exception to throw if condition doesn't evaluate to true
3011*9c5db199SXin Li    @param timeout: maximum number of seconds to wait
3012*9c5db199SXin Li    @param sleep_interval: time to sleep between polls
3013*9c5db199SXin Li    @param hold_interval: time period for which the condition should hold true
3014*9c5db199SXin Li    @param desc: description of default TimeoutError used if 'exception' is
3015*9c5db199SXin Li                 None
3016*9c5db199SXin Li
3017*9c5db199SXin Li    @return The evaluated value that caused the poll loop to terminate.
3018*9c5db199SXin Li
3019*9c5db199SXin Li    @raise 'exception' arg if supplied; TimeoutError otherwise
3020*9c5db199SXin Li    """
3021*9c5db199SXin Li    start_time = time.time()
3022*9c5db199SXin Li    cond_is_held = False
3023*9c5db199SXin Li    cond_hold_start_time = None
3024*9c5db199SXin Li
3025*9c5db199SXin Li    while True:
3026*9c5db199SXin Li        value = condition()
3027*9c5db199SXin Li        if value:
3028*9c5db199SXin Li            if cond_is_held:
3029*9c5db199SXin Li                if time.time() - cond_hold_start_time > hold_interval:
3030*9c5db199SXin Li                    return value
3031*9c5db199SXin Li            else:
3032*9c5db199SXin Li                cond_is_held = True
3033*9c5db199SXin Li                cond_hold_start_time = time.time()
3034*9c5db199SXin Li        else:
3035*9c5db199SXin Li            cond_is_held = False
3036*9c5db199SXin Li
3037*9c5db199SXin Li        time_remaining = timeout - (time.time() - start_time)
3038*9c5db199SXin Li        if time_remaining < hold_interval:
3039*9c5db199SXin Li            if exception:
3040*9c5db199SXin Li                logging.error('Will raise error %r due to unexpected return: '
3041*9c5db199SXin Li                              '%r', exception, value)
3042*9c5db199SXin Li                raise exception # pylint: disable=raising-bad-type
3043*9c5db199SXin Li
3044*9c5db199SXin Li            if desc:
3045*9c5db199SXin Li                desc = 'Timed out waiting for condition: ' + desc
3046*9c5db199SXin Li            else:
3047*9c5db199SXin Li                desc = 'Timed out waiting for unnamed condition'
3048*9c5db199SXin Li            logging.error(desc)
3049*9c5db199SXin Li            raise TimeoutError(message=desc)
3050*9c5db199SXin Li
3051*9c5db199SXin Li        time.sleep(sleep_interval)
3052*9c5db199SXin Li
3053*9c5db199SXin Li
3054*9c5db199SXin Lidef shadowroot_query(element, action):
3055*9c5db199SXin Li    """Recursively queries shadowRoot.
3056*9c5db199SXin Li
3057*9c5db199SXin Li    @param element: element to query for.
3058*9c5db199SXin Li    @param action: action to be performed on the element.
3059*9c5db199SXin Li
3060*9c5db199SXin Li    @return JS functions to execute.
3061*9c5db199SXin Li
3062*9c5db199SXin Li    """
3063*9c5db199SXin Li    # /deep/ CSS query has been removed from ShadowDOM. The only way to access
3064*9c5db199SXin Li    # elements now is to recursively query in each shadowRoot.
3065*9c5db199SXin Li    shadowroot_script = """
3066*9c5db199SXin Li    function deepQuerySelectorAll(root, targetQuery) {
3067*9c5db199SXin Li        const elems = Array.prototype.slice.call(
3068*9c5db199SXin Li            root.querySelectorAll(targetQuery[0]));
3069*9c5db199SXin Li        const remaining = targetQuery.slice(1);
3070*9c5db199SXin Li        if (remaining.length === 0) {
3071*9c5db199SXin Li            return elems;
3072*9c5db199SXin Li        }
3073*9c5db199SXin Li
3074*9c5db199SXin Li        let res = [];
3075*9c5db199SXin Li        for (let i = 0; i < elems.length; i++) {
3076*9c5db199SXin Li            if (elems[i].shadowRoot) {
3077*9c5db199SXin Li                res = res.concat(
3078*9c5db199SXin Li                    deepQuerySelectorAll(elems[i].shadowRoot, remaining));
3079*9c5db199SXin Li            }
3080*9c5db199SXin Li        }
3081*9c5db199SXin Li        return res;
3082*9c5db199SXin Li    };
3083*9c5db199SXin Li    var testing_element = deepQuerySelectorAll(document, %s);
3084*9c5db199SXin Li    testing_element[0].%s;
3085*9c5db199SXin Li    """
3086*9c5db199SXin Li    script_to_execute = shadowroot_script % (element, action)
3087*9c5db199SXin Li    return script_to_execute
3088*9c5db199SXin Li
3089*9c5db199SXin Li
3090*9c5db199SXin Lidef threaded_return(function):
3091*9c5db199SXin Li    """
3092*9c5db199SXin Li    Decorator to add to a function to get that function to return a thread
3093*9c5db199SXin Li    object, but with the added benefit of storing its return value.
3094*9c5db199SXin Li
3095*9c5db199SXin Li    @param function: function object to be run in the thread
3096*9c5db199SXin Li
3097*9c5db199SXin Li    @return a threading.Thread object, that has already been started, is
3098*9c5db199SXin Li            recording its result, and can be completed and its result
3099*9c5db199SXin Li            fetched by calling .finish()
3100*9c5db199SXin Li    """
3101*9c5db199SXin Li    def wrapped_t(queue, *args, **kwargs):
3102*9c5db199SXin Li        """
3103*9c5db199SXin Li        Calls the decorated function as normal, but appends the output into
3104*9c5db199SXin Li        the passed-in threadsafe queue.
3105*9c5db199SXin Li        """
3106*9c5db199SXin Li        ret = function(*args, **kwargs)
3107*9c5db199SXin Li        queue.put(ret)
3108*9c5db199SXin Li
3109*9c5db199SXin Li    def wrapped_finish(threaded_object):
3110*9c5db199SXin Li        """
3111*9c5db199SXin Li        Provides a utility to this thread object, getting its result while
3112*9c5db199SXin Li        simultaneously joining the thread.
3113*9c5db199SXin Li        """
3114*9c5db199SXin Li        ret = threaded_object.get()
3115*9c5db199SXin Li        threaded_object.join()
3116*9c5db199SXin Li        return ret
3117*9c5db199SXin Li
3118*9c5db199SXin Li    def wrapper(*args, **kwargs):
3119*9c5db199SXin Li        """
3120*9c5db199SXin Li        Creates the queue and starts the thread, then assigns extra attributes
3121*9c5db199SXin Li        to the thread to give it result-storing capability.
3122*9c5db199SXin Li        """
3123*9c5db199SXin Li        q = six.moves.queue.Queue()
3124*9c5db199SXin Li        t = threading.Thread(target=wrapped_t, args=(q,) + args, kwargs=kwargs)
3125*9c5db199SXin Li        t.start()
3126*9c5db199SXin Li        t.result_queue = q
3127*9c5db199SXin Li        t.get = t.result_queue.get
3128*9c5db199SXin Li        t.finish = lambda: wrapped_finish(t)
3129*9c5db199SXin Li        return t
3130*9c5db199SXin Li
3131*9c5db199SXin Li    # for the decorator
3132*9c5db199SXin Li    return wrapper
3133*9c5db199SXin Li
3134*9c5db199SXin Li
3135*9c5db199SXin Li@threaded_return
3136*9c5db199SXin Lidef background_sample_until_condition(
3137*9c5db199SXin Li        function,
3138*9c5db199SXin Li        condition=lambda: True,
3139*9c5db199SXin Li        timeout=10,
3140*9c5db199SXin Li        sleep_interval=1):
3141*9c5db199SXin Li    """
3142*9c5db199SXin Li    Records the value of the function until the condition is False or the
3143*9c5db199SXin Li    timeout is reached. Runs as a background thread, so it's nonblocking.
3144*9c5db199SXin Li    Usage might look something like:
3145*9c5db199SXin Li
3146*9c5db199SXin Li    def function():
3147*9c5db199SXin Li        return get_value()
3148*9c5db199SXin Li    def condition():
3149*9c5db199SXin Li        return self._keep_sampling
3150*9c5db199SXin Li
3151*9c5db199SXin Li    # main thread
3152*9c5db199SXin Li    sample_thread = utils.background_sample_until_condition(
3153*9c5db199SXin Li        function=function,condition=condition)
3154*9c5db199SXin Li    # do other work
3155*9c5db199SXin Li    # ...
3156*9c5db199SXin Li    self._keep_sampling = False
3157*9c5db199SXin Li    # blocking call to get result and join the thread
3158*9c5db199SXin Li    result = sample_thread.finish()
3159*9c5db199SXin Li
3160*9c5db199SXin Li    @param function: function object, 0 args, to be continually polled
3161*9c5db199SXin Li    @param condition: function object, 0 args, to say when to stop polling
3162*9c5db199SXin Li    @param timeout: maximum number of seconds to wait
3163*9c5db199SXin Li    @param number of seconds to wait in between polls
3164*9c5db199SXin Li
3165*9c5db199SXin Li    @return a thread object that has already been started and is running in
3166*9c5db199SXin Li            the background, whose run must be stopped with .finish(), which
3167*9c5db199SXin Li            also returns a list of the results from the sample function
3168*9c5db199SXin Li    """
3169*9c5db199SXin Li    log = []
3170*9c5db199SXin Li
3171*9c5db199SXin Li    end_time = datetime.datetime.now() + datetime.timedelta(
3172*9c5db199SXin Li            seconds = timeout + sleep_interval)
3173*9c5db199SXin Li
3174*9c5db199SXin Li    while condition() and datetime.datetime.now() < end_time:
3175*9c5db199SXin Li        log.append(function())
3176*9c5db199SXin Li        time.sleep(sleep_interval)
3177*9c5db199SXin Li    return log
3178*9c5db199SXin Li
3179*9c5db199SXin Li
3180*9c5db199SXin Liclass metrics_mock(metrics_mock_class.mock_class_base):
3181*9c5db199SXin Li    """mock class for metrics in case chromite is not installed."""
3182*9c5db199SXin Li    pass
3183*9c5db199SXin Li
3184*9c5db199SXin Li
3185*9c5db199SXin LiMountInfo = collections.namedtuple('MountInfo', ['root', 'mount_point', 'tags'])
3186*9c5db199SXin Li
3187*9c5db199SXin Li
3188*9c5db199SXin Lidef get_mount_info(process='self', mount_point=None):
3189*9c5db199SXin Li    """Retrieves information about currently mounted file systems.
3190*9c5db199SXin Li
3191*9c5db199SXin Li    @param mount_point: (optional) The mount point (a path).  If this is
3192*9c5db199SXin Li                        provided, only information about the given mount point
3193*9c5db199SXin Li                        is returned.  If this is omitted, info about all mount
3194*9c5db199SXin Li                        points is returned.
3195*9c5db199SXin Li    @param process: (optional) The process id (or the string 'self') of the
3196*9c5db199SXin Li                    process whose mountinfo will be obtained.  If this is
3197*9c5db199SXin Li                    omitted, info about the current process is returned.
3198*9c5db199SXin Li
3199*9c5db199SXin Li    @return A generator yielding one MountInfo object for each relevant mount
3200*9c5db199SXin Li            found in /proc/PID/mountinfo.
3201*9c5db199SXin Li    """
3202*9c5db199SXin Li    with open('/proc/{}/mountinfo'.format(process)) as f:
3203*9c5db199SXin Li        for line in f.readlines():
3204*9c5db199SXin Li            # TODO b:169251326 terms below are set outside of this codebase
3205*9c5db199SXin Li            # and should be updated when possible. ("master" -> "main")
3206*9c5db199SXin Li            # These lines are formatted according to the proc(5) manpage.
3207*9c5db199SXin Li            # Sample line:
3208*9c5db199SXin Li            # 36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root \
3209*9c5db199SXin Li            #     rw,errors=continue
3210*9c5db199SXin Li            # Fields (descriptions omitted for fields we don't care about)
3211*9c5db199SXin Li            # 3: the root of the mount.
3212*9c5db199SXin Li            # 4: the mount point.
3213*9c5db199SXin Li            # 5: mount options.
3214*9c5db199SXin Li            # 6: tags.  There can be more than one of these.  This is where
3215*9c5db199SXin Li            #    shared mounts are indicated.
3216*9c5db199SXin Li            # 7: a dash separator marking the end of the tags.
3217*9c5db199SXin Li            mountinfo = line.split()
3218*9c5db199SXin Li            if mount_point is None or mountinfo[4] == mount_point:
3219*9c5db199SXin Li                tags = []
3220*9c5db199SXin Li                for field in mountinfo[6:]:
3221*9c5db199SXin Li                    if field == '-':
3222*9c5db199SXin Li                        break
3223*9c5db199SXin Li                    tags.append(field.split(':')[0])
3224*9c5db199SXin Li                yield MountInfo(root = mountinfo[3],
3225*9c5db199SXin Li                                mount_point = mountinfo[4],
3226*9c5db199SXin Li                                tags = tags)
3227*9c5db199SXin Li
3228*9c5db199SXin Li
3229*9c5db199SXin Li# Appended suffix for chart tablet naming convention in test lab
3230*9c5db199SXin LiCHART_ADDRESS_SUFFIX = '-tablet'
3231*9c5db199SXin Li
3232*9c5db199SXin Li
3233*9c5db199SXin Lidef get_lab_chart_address(hostname):
3234*9c5db199SXin Li    """Convert lab DUT hostname to address of camera box chart tablet"""
3235*9c5db199SXin Li    return hostname + CHART_ADDRESS_SUFFIX if is_in_container() else None
3236*9c5db199SXin Li
3237*9c5db199SXin Li
3238*9c5db199SXin Lidef cherry_pick_args(func, args, dargs):
3239*9c5db199SXin Li    """Sanitize positional and keyword arguments before calling a function.
3240*9c5db199SXin Li
3241*9c5db199SXin Li    Given a callable (func), an argument tuple and a dictionary of keyword
3242*9c5db199SXin Li    arguments, pick only those arguments which the function is prepared to
3243*9c5db199SXin Li    accept and return a new argument tuple and keyword argument dictionary.
3244*9c5db199SXin Li
3245*9c5db199SXin Li    Args:
3246*9c5db199SXin Li      func: A callable that we want to choose arguments for.
3247*9c5db199SXin Li      args: A tuple of positional arguments to consider passing to func.
3248*9c5db199SXin Li      dargs: A dictionary of keyword arguments to consider passing to func.
3249*9c5db199SXin Li    Returns:
3250*9c5db199SXin Li      A tuple of: (args tuple, keyword arguments dictionary)
3251*9c5db199SXin Li    """
3252*9c5db199SXin Li    # Cherry pick args:
3253*9c5db199SXin Li    if hasattr(func, "func_code"):
3254*9c5db199SXin Li        # Moock doesn't have __code__ in either py2 or 3 :(
3255*9c5db199SXin Li        flags = func.func_code.co_flags
3256*9c5db199SXin Li    else:
3257*9c5db199SXin Li        flags = func.__code__.co_flags
3258*9c5db199SXin Li
3259*9c5db199SXin Li    if flags & 0x04:
3260*9c5db199SXin Li        # func accepts *args, so return the entire args.
3261*9c5db199SXin Li        p_args = args
3262*9c5db199SXin Li    else:
3263*9c5db199SXin Li        p_args = ()
3264*9c5db199SXin Li
3265*9c5db199SXin Li    # Cherry pick dargs:
3266*9c5db199SXin Li    if flags & 0x08:
3267*9c5db199SXin Li        # func accepts **dargs, so return the entire dargs.
3268*9c5db199SXin Li        p_dargs = dargs
3269*9c5db199SXin Li    else:
3270*9c5db199SXin Li        # Only return the keyword arguments that func accepts.
3271*9c5db199SXin Li        p_dargs = {}
3272*9c5db199SXin Li        for param in get_nonstar_args(func):
3273*9c5db199SXin Li            if param in dargs:
3274*9c5db199SXin Li                p_dargs[param] = dargs[param]
3275*9c5db199SXin Li
3276*9c5db199SXin Li    return p_args, p_dargs
3277*9c5db199SXin Li
3278*9c5db199SXin Li
3279*9c5db199SXin Lidef cherry_pick_call(func, *args, **dargs):
3280*9c5db199SXin Li    """Cherry picks arguments from args/dargs based on what "func" accepts
3281*9c5db199SXin Li    and calls the function with the picked arguments."""
3282*9c5db199SXin Li    p_args, p_dargs = cherry_pick_args(func, args, dargs)
3283*9c5db199SXin Li    return func(*p_args, **p_dargs)
3284*9c5db199SXin Li
3285*9c5db199SXin Li
3286*9c5db199SXin Lidef get_nonstar_args(func):
3287*9c5db199SXin Li    """Extract all the (normal) function parameter names.
3288*9c5db199SXin Li
3289*9c5db199SXin Li    Given a function, returns a tuple of parameter names, specifically
3290*9c5db199SXin Li    excluding the * and ** parameters, if the function accepts them.
3291*9c5db199SXin Li
3292*9c5db199SXin Li    @param func: A callable that we want to chose arguments for.
3293*9c5db199SXin Li
3294*9c5db199SXin Li    @return: A tuple of parameters accepted by the function.
3295*9c5db199SXin Li    """
3296*9c5db199SXin Li    return func.__code__.co_varnames[:func.__code__.co_argcount]
3297*9c5db199SXin Li
3298*9c5db199SXin Lidef crc8(buf):
3299*9c5db199SXin Li    """Calculate CRC8 for a given int list.
3300*9c5db199SXin Li
3301*9c5db199SXin Li    This is a simple version of CRC8.
3302*9c5db199SXin Li
3303*9c5db199SXin Li    Args:
3304*9c5db199SXin Li      buf: A list of byte integer
3305*9c5db199SXin Li    Returns:
3306*9c5db199SXin Li      A crc value in integer
3307*9c5db199SXin Li    """
3308*9c5db199SXin Li
3309*9c5db199SXin Li    _table_crc8 = [ 0x00, 0x07, 0x0e, 0x09, 0x1c, 0x1b, 0x12, 0x15,
3310*9c5db199SXin Li                    0x38, 0x3f, 0x36, 0x31, 0x24, 0x23, 0x2a, 0x2d,
3311*9c5db199SXin Li                    0x70, 0x77, 0x7e, 0x79, 0x6c, 0x6b, 0x62, 0x65,
3312*9c5db199SXin Li                    0x48, 0x4f, 0x46, 0x41, 0x54, 0x53, 0x5a, 0x5d,
3313*9c5db199SXin Li                    0xe0, 0xe7, 0xee, 0xe9, 0xfc, 0xfb, 0xf2, 0xf5,
3314*9c5db199SXin Li                    0xd8, 0xdf, 0xd6, 0xd1, 0xc4, 0xc3, 0xca, 0xcd,
3315*9c5db199SXin Li                    0x90, 0x97, 0x9e, 0x99, 0x8c, 0x8b, 0x82, 0x85,
3316*9c5db199SXin Li                    0xa8, 0xaf, 0xa6, 0xa1, 0xb4, 0xb3, 0xba, 0xbd,
3317*9c5db199SXin Li                    0xc7, 0xc0, 0xc9, 0xce, 0xdb, 0xdc, 0xd5, 0xd2,
3318*9c5db199SXin Li                    0xff, 0xf8, 0xf1, 0xf6, 0xe3, 0xe4, 0xed, 0xea,
3319*9c5db199SXin Li                    0xb7, 0xb0, 0xb9, 0xbe, 0xab, 0xac, 0xa5, 0xa2,
3320*9c5db199SXin Li                    0x8f, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9d, 0x9a,
3321*9c5db199SXin Li                    0x27, 0x20, 0x29, 0x2e, 0x3b, 0x3c, 0x35, 0x32,
3322*9c5db199SXin Li                    0x1f, 0x18, 0x11, 0x16, 0x03, 0x04, 0x0d, 0x0a,
3323*9c5db199SXin Li                    0x57, 0x50, 0x59, 0x5e, 0x4b, 0x4c, 0x45, 0x42,
3324*9c5db199SXin Li                    0x6f, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7d, 0x7a,
3325*9c5db199SXin Li                    0x89, 0x8e, 0x87, 0x80, 0x95, 0x92, 0x9b, 0x9c,
3326*9c5db199SXin Li                    0xb1, 0xb6, 0xbf, 0xb8, 0xad, 0xaa, 0xa3, 0xa4,
3327*9c5db199SXin Li                    0xf9, 0xfe, 0xf7, 0xf0, 0xe5, 0xe2, 0xeb, 0xec,
3328*9c5db199SXin Li                    0xc1, 0xc6, 0xcf, 0xc8, 0xdd, 0xda, 0xd3, 0xd4,
3329*9c5db199SXin Li                    0x69, 0x6e, 0x67, 0x60, 0x75, 0x72, 0x7b, 0x7c,
3330*9c5db199SXin Li                    0x51, 0x56, 0x5f, 0x58, 0x4d, 0x4a, 0x43, 0x44,
3331*9c5db199SXin Li                    0x19, 0x1e, 0x17, 0x10, 0x05, 0x02, 0x0b, 0x0c,
3332*9c5db199SXin Li                    0x21, 0x26, 0x2f, 0x28, 0x3d, 0x3a, 0x33, 0x34,
3333*9c5db199SXin Li                    0x4e, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5c, 0x5b,
3334*9c5db199SXin Li                    0x76, 0x71, 0x78, 0x7f, 0x6a, 0x6d, 0x64, 0x63,
3335*9c5db199SXin Li                    0x3e, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2c, 0x2b,
3336*9c5db199SXin Li                    0x06, 0x01, 0x08, 0x0f, 0x1a, 0x1d, 0x14, 0x13,
3337*9c5db199SXin Li                    0xae, 0xa9, 0xa0, 0xa7, 0xb2, 0xb5, 0xbc, 0xbb,
3338*9c5db199SXin Li                    0x96, 0x91, 0x98, 0x9f, 0x8a, 0x8d, 0x84, 0x83,
3339*9c5db199SXin Li                    0xde, 0xd9, 0xd0, 0xd7, 0xc2, 0xc5, 0xcc, 0xcb,
3340*9c5db199SXin Li                    0xe6, 0xe1, 0xe8, 0xef, 0xfa, 0xfd, 0xf4, 0xf3,
3341*9c5db199SXin Li                  ]
3342*9c5db199SXin Li    if not isinstance(buf, list):
3343*9c5db199SXin Li        raise error.TestError('buf should be an integer list.')
3344*9c5db199SXin Li    if not all(isinstance(i, int) for i in buf):
3345*9c5db199SXin Li        raise error.TestError('buf should contain integers only.')
3346*9c5db199SXin Li
3347*9c5db199SXin Li    rv = 0
3348*9c5db199SXin Li    for i in buf:
3349*9c5db199SXin Li        rv = _table_crc8[ (rv ^ i) & 0xff ]
3350*9c5db199SXin Li    return rv
3351*9c5db199SXin Li
3352*9c5db199SXin Li
3353*9c5db199SXin Lidef send_msg_to_terminal(job, msg):
3354*9c5db199SXin Li    """Send from the client side to the terminal.
3355*9c5db199SXin Li
3356*9c5db199SXin Li    ONLY to be used on non-scheduled tests (aka local runs).
3357*9c5db199SXin Li    Do not send anything which could be confused for a status.
3358*9c5db199SXin Li    See server/autotest.py client_logger for examples of status's NOT to use
3359*9c5db199SXin Li
3360*9c5db199SXin Li    @param job: The client job obj. Can be accessed from anything built off
3361*9c5db199SXin Li        test.test via self.job
3362*9c5db199SXin Li    @param msg: the msg to send.
3363*9c5db199SXin Li    """
3364*9c5db199SXin Li    status = os.fdopen(3, 'w', 2)
3365*9c5db199SXin Li    try:
3366*9c5db199SXin Li        status.write(msg + '\n')
3367*9c5db199SXin Li    finally:
3368*9c5db199SXin Li        status.flush()
3369