Home | History | Annotate | Download | only in common_lib
      1 # Copyright (c) 2017 The Chromium Authors. All rights reserved.
      2 # Use of this source code is governed by a BSD-style license that can be
      3 # found in the LICENSE file.
      4 
      5 """
      6 Convenience functions for use by tests or whomever.
      7 
      8 There's no really good way to do this, as this isn't a class we can do
      9 inheritance with, just a collection of static methods.
     10 """
     11 
     12 # pylint: disable=missing-docstring
     13 
     14 import StringIO
     15 import errno
     16 import inspect
     17 import itertools
     18 import logging
     19 import os
     20 import pickle
     21 import random
     22 import re
     23 import resource
     24 import select
     25 import shutil
     26 import signal
     27 import socket
     28 import string
     29 import struct
     30 import subprocess
     31 import textwrap
     32 import time
     33 import urllib2
     34 import urlparse
     35 import uuid
     36 import warnings
     37 
     38 try:
     39     import hashlib
     40 except ImportError:
     41     import md5
     42     import sha
     43 
     44 import common
     45 
     46 from autotest_lib.client.common_lib import env
     47 from autotest_lib.client.common_lib import error
     48 from autotest_lib.client.common_lib import global_config
     49 from autotest_lib.client.common_lib import logging_manager
     50 from autotest_lib.client.common_lib import metrics_mock_class
     51 from autotest_lib.client.cros import constants
     52 
     53 from autotest_lib.client.common_lib.lsbrelease_utils import *
     54 
     55 
     56 def deprecated(func):
     57     """This is a decorator which can be used to mark functions as deprecated.
     58     It will result in a warning being emmitted when the function is used."""
     59     def new_func(*args, **dargs):
     60         warnings.warn("Call to deprecated function %s." % func.__name__,
     61                       category=DeprecationWarning)
     62         return func(*args, **dargs)
     63     new_func.__name__ = func.__name__
     64     new_func.__doc__ = func.__doc__
     65     new_func.__dict__.update(func.__dict__)
     66     return new_func
     67 
     68 
     69 class _NullStream(object):
     70     def write(self, data):
     71         pass
     72 
     73 
     74     def flush(self):
     75         pass
     76 
     77 
     78 TEE_TO_LOGS = object()
     79 _the_null_stream = _NullStream()
     80 
     81 DEVNULL = object()
     82 
     83 DEFAULT_STDOUT_LEVEL = logging.DEBUG
     84 DEFAULT_STDERR_LEVEL = logging.ERROR
     85 
     86 # prefixes for logging stdout/stderr of commands
     87 STDOUT_PREFIX = '[stdout] '
     88 STDERR_PREFIX = '[stderr] '
     89 
     90 # safe characters for the shell (do not need quoting)
     91 SHELL_QUOTING_WHITELIST = frozenset(string.ascii_letters +
     92                                     string.digits +
     93                                     '_-+=')
     94 
     95 def custom_warning_handler(message, category, filename, lineno, file=None,
     96                            line=None):
     97     """Custom handler to log at the WARNING error level. Ignores |file|."""
     98     logging.warning(warnings.formatwarning(message, category, filename, lineno,
     99                                            line))
    100 
    101 warnings.showwarning = custom_warning_handler
    102 
    103 def get_stream_tee_file(stream, level, prefix=''):
    104     if stream is None:
    105         return _the_null_stream
    106     if stream is DEVNULL:
    107         return None
    108     if stream is TEE_TO_LOGS:
    109         return logging_manager.LoggingFile(level=level, prefix=prefix)
    110     return stream
    111 
    112 
    113 def _join_with_nickname(base_string, nickname):
    114     if nickname:
    115         return '%s BgJob "%s" ' % (base_string, nickname)
    116     return base_string
    117 
    118 
    119 # TODO: Cleanup and possibly eliminate |unjoinable|, which is only used in our
    120 # master-ssh connection process, while fixing underlying
    121 # semantics problem in BgJob. See crbug.com/279312
    122 class BgJob(object):
    123     def __init__(self, command, stdout_tee=None, stderr_tee=None, verbose=True,
    124                  stdin=None, stdout_level=DEFAULT_STDOUT_LEVEL,
    125                  stderr_level=DEFAULT_STDERR_LEVEL, nickname=None,
    126                  unjoinable=False, env=None, extra_paths=None):
    127         """Create and start a new BgJob.
    128 
    129         This constructor creates a new BgJob, and uses Popen to start a new
    130         subprocess with given command. It returns without blocking on execution
    131         of the subprocess.
    132 
    133         After starting a new BgJob, use output_prepare to connect the process's
    134         stdout and stderr pipes to the stream of your choice.
    135 
    136         When the job is running, the jobs's output streams are only read from
    137         when process_output is called.
    138 
    139         @param command: command to be executed in new subprocess. May be either
    140                         a list, or a string (in which case Popen will be called
    141                         with shell=True)
    142         @param stdout_tee: (Optional) a file like object, TEE_TO_LOGS or
    143                            DEVNULL.
    144                            If not given, after finishing the process, the
    145                            stdout data from subprocess is available in
    146                            result.stdout.
    147                            If a file like object is given, in process_output(),
    148                            the stdout data from the subprocess will be handled
    149                            by the given file like object.
    150                            If TEE_TO_LOGS is given, in process_output(), the
    151                            stdout data from the subprocess will be handled by
    152                            the standard logging_manager.
    153                            If DEVNULL is given, the stdout of the subprocess
    154                            will be just discarded. In addition, even after
    155                            cleanup(), result.stdout will be just an empty
    156                            string (unlike the case where stdout_tee is not
    157                            given).
    158         @param stderr_tee: Same as stdout_tee, but for stderr.
    159         @param verbose: Boolean, make BgJob logging more verbose.
    160         @param stdin: Stream object, will be passed to Popen as the new
    161                       process's stdin.
    162         @param stdout_level: A logging level value. If stdout_tee was set to
    163                              TEE_TO_LOGS, sets the level that tee'd
    164                              stdout output will be logged at. Ignored
    165                              otherwise.
    166         @param stderr_level: Same as stdout_level, but for stderr.
    167         @param nickname: Optional string, to be included in logging messages
    168         @param unjoinable: Optional bool, default False.
    169                            This should be True for BgJobs running in background
    170                            and will never be joined with join_bg_jobs(), such
    171                            as the master-ssh connection. Instead, it is
    172                            caller's responsibility to terminate the subprocess
    173                            correctly, e.g. by calling nuke_subprocess().
    174                            This will lead that, calling join_bg_jobs(),
    175                            process_output() or cleanup() will result in an
    176                            InvalidBgJobCall exception.
    177                            Also, |stdout_tee| and |stderr_tee| must be set to
    178                            DEVNULL, otherwise InvalidBgJobCall is raised.
    179         @param env: Dict containing environment variables used in subprocess.
    180         @param extra_paths: Optional string list, to be prepended to the PATH
    181                             env variable in env (or os.environ dict if env is
    182                             not specified).
    183         """
    184         self.command = command
    185         self.unjoinable = unjoinable
    186         if (unjoinable and (stdout_tee != DEVNULL or stderr_tee != DEVNULL)):
    187             raise error.InvalidBgJobCall(
    188                 'stdout_tee and stderr_tee must be DEVNULL for '
    189                 'unjoinable BgJob')
    190         self._stdout_tee = get_stream_tee_file(
    191                 stdout_tee, stdout_level,
    192                 prefix=_join_with_nickname(STDOUT_PREFIX, nickname))
    193         self._stderr_tee = get_stream_tee_file(
    194                 stderr_tee, stderr_level,
    195                 prefix=_join_with_nickname(STDERR_PREFIX, nickname))
    196         self.result = CmdResult(command)
    197 
    198         # allow for easy stdin input by string, we'll let subprocess create
    199         # a pipe for stdin input and we'll write to it in the wait loop
    200         if isinstance(stdin, basestring):
    201             self.string_stdin = stdin
    202             stdin = subprocess.PIPE
    203         else:
    204             self.string_stdin = None
    205 
    206         # Prepend extra_paths to env['PATH'] if necessary.
    207         if extra_paths:
    208             env = (os.environ if env is None else env).copy()
    209             oldpath = env.get('PATH')
    210             env['PATH'] = os.pathsep.join(
    211                     extra_paths + ([oldpath] if oldpath else []))
    212 
    213         if verbose:
    214             logging.debug("Running '%s'", command)
    215 
    216         if type(command) == list:
    217             shell = False
    218             executable = None
    219         else:
    220             shell = True
    221             executable = '/bin/bash'
    222 
    223         with open('/dev/null', 'w') as devnull:
    224             self.sp = subprocess.Popen(
    225                 command,
    226                 stdin=stdin,
    227                 stdout=devnull if stdout_tee == DEVNULL else subprocess.PIPE,
    228                 stderr=devnull if stderr_tee == DEVNULL else subprocess.PIPE,
    229                 preexec_fn=self._reset_sigpipe,
    230                 shell=shell, executable=executable,
    231                 env=env, close_fds=True)
    232 
    233         self._cleanup_called = False
    234         self._stdout_file = (
    235             None if stdout_tee == DEVNULL else StringIO.StringIO())
    236         self._stderr_file = (
    237             None if stderr_tee == DEVNULL else StringIO.StringIO())
    238 
    239     def process_output(self, stdout=True, final_read=False):
    240         """Read from process's output stream, and write data to destinations.
    241 
    242         This function reads up to 1024 bytes from the background job's
    243         stdout or stderr stream, and writes the resulting data to the BgJob's
    244         output tee and to the stream set up in output_prepare.
    245 
    246         Warning: Calls to process_output will block on reads from the
    247         subprocess stream, and will block on writes to the configured
    248         destination stream.
    249 
    250         @param stdout: True = read and process data from job's stdout.
    251                        False = from stderr.
    252                        Default: True
    253         @param final_read: Do not read only 1024 bytes from stream. Instead,
    254                            read and process all data until end of the stream.
    255 
    256         """
    257         if self.unjoinable:
    258             raise error.InvalidBgJobCall('Cannot call process_output on '
    259                                          'a job with unjoinable BgJob')
    260         if stdout:
    261             pipe, buf, tee = (
    262                 self.sp.stdout, self._stdout_file, self._stdout_tee)
    263         else:
    264             pipe, buf, tee = (
    265                 self.sp.stderr, self._stderr_file, self._stderr_tee)
    266 
    267         if not pipe:
    268             return
    269 
    270         if final_read:
    271             # read in all the data we can from pipe and then stop
    272             data = []
    273             while select.select([pipe], [], [], 0)[0]:
    274                 data.append(os.read(pipe.fileno(), 1024))
    275                 if len(data[-1]) == 0:
    276                     break
    277             data = "".join(data)
    278         else:
    279             # perform a single read
    280             data = os.read(pipe.fileno(), 1024)
    281         buf.write(data)
    282         tee.write(data)
    283 
    284     def cleanup(self):
    285         """Clean up after BgJob.
    286 
    287         Flush the stdout_tee and stderr_tee buffers, close the
    288         subprocess stdout and stderr buffers, and saves data from
    289         the configured stdout and stderr destination streams to
    290         self.result. Duplicate calls ignored with a warning.
    291         """
    292         if self.unjoinable:
    293             raise error.InvalidBgJobCall('Cannot call cleanup on '
    294                                          'a job with a unjoinable BgJob')
    295         if self._cleanup_called:
    296             logging.warning('BgJob [%s] received a duplicate call to '
    297                             'cleanup. Ignoring.', self.command)
    298             return
    299         try:
    300             if self.sp.stdout:
    301                 self._stdout_tee.flush()
    302                 self.sp.stdout.close()
    303                 self.result.stdout = self._stdout_file.getvalue()
    304 
    305             if self.sp.stderr:
    306                 self._stderr_tee.flush()
    307                 self.sp.stderr.close()
    308                 self.result.stderr = self._stderr_file.getvalue()
    309         finally:
    310             self._cleanup_called = True
    311 
    312     def _reset_sigpipe(self):
    313         if not env.IN_MOD_WSGI:
    314             signal.signal(signal.SIGPIPE, signal.SIG_DFL)
    315 
    316 
    317 def ip_to_long(ip):
    318     # !L is a long in network byte order
    319     return struct.unpack('!L', socket.inet_aton(ip))[0]
    320 
    321 
    322 def long_to_ip(number):
    323     # See above comment.
    324     return socket.inet_ntoa(struct.pack('!L', number))
    325 
    326 
    327 def create_subnet_mask(bits):
    328     return (1 << 32) - (1 << 32-bits)
    329 
    330 
    331 def format_ip_with_mask(ip, mask_bits):
    332     masked_ip = ip_to_long(ip) & create_subnet_mask(mask_bits)
    333     return "%s/%s" % (long_to_ip(masked_ip), mask_bits)
    334 
    335 
    336 def normalize_hostname(alias):
    337     ip = socket.gethostbyname(alias)
    338     return socket.gethostbyaddr(ip)[0]
    339 
    340 
    341 def get_ip_local_port_range():
    342     match = re.match(r'\s*(\d+)\s*(\d+)\s*$',
    343                      read_one_line('/proc/sys/net/ipv4/ip_local_port_range'))
    344     return (int(match.group(1)), int(match.group(2)))
    345 
    346 
    347 def set_ip_local_port_range(lower, upper):
    348     write_one_line('/proc/sys/net/ipv4/ip_local_port_range',
    349                    '%d %d\n' % (lower, upper))
    350 
    351 
    352 def read_one_line(filename):
    353     return open(filename, 'r').readline().rstrip('\n')
    354 
    355 
    356 def read_file(filename):
    357     f = open(filename)
    358     try:
    359         return f.read()
    360     finally:
    361         f.close()
    362 
    363 
    364 def get_field(data, param, linestart="", sep=" "):
    365     """
    366     Parse data from string.
    367     @param data: Data to parse.
    368         example:
    369           data:
    370              cpu   324 345 34  5 345
    371              cpu0  34  11  34 34  33
    372              ^^^^
    373              start of line
    374              params 0   1   2  3   4
    375     @param param: Position of parameter after linestart marker.
    376     @param linestart: String to which start line with parameters.
    377     @param sep: Separator between parameters regular expression.
    378     """
    379     search = re.compile(r"(?<=^%s)\s*(.*)" % linestart, re.MULTILINE)
    380     find = search.search(data)
    381     if find != None:
    382         return re.split("%s" % sep, find.group(1))[param]
    383     else:
    384         print "There is no line which starts with %s in data." % linestart
    385         return None
    386 
    387 
    388 def write_one_line(filename, line):
    389     open_write_close(filename, str(line).rstrip('\n') + '\n')
    390 
    391 
    392 def open_write_close(filename, data):
    393     f = open(filename, 'w')
    394     try:
    395         f.write(data)
    396     finally:
    397         f.close()
    398 
    399 
    400 def locate_file(path, base_dir=None):
    401     """Locates a file.
    402 
    403     @param path: The path of the file being located. Could be absolute or relative
    404         path. For relative path, it tries to locate the file from base_dir.
    405     @param base_dir (optional): Base directory of the relative path.
    406 
    407     @returns Absolute path of the file if found. None if path is None.
    408     @raises error.TestFail if the file is not found.
    409     """
    410     if path is None:
    411         return None
    412 
    413     if not os.path.isabs(path) and base_dir is not None:
    414         # Assume the relative path is based in autotest directory.
    415         path = os.path.join(base_dir, path)
    416     if not os.path.isfile(path):
    417         raise error.TestFail('ERROR: Unable to find %s' % path)
    418     return path
    419 
    420 
    421 def matrix_to_string(matrix, header=None):
    422     """
    423     Return a pretty, aligned string representation of a nxm matrix.
    424 
    425     This representation can be used to print any tabular data, such as
    426     database results. It works by scanning the lengths of each element
    427     in each column, and determining the format string dynamically.
    428 
    429     @param matrix: Matrix representation (list with n rows of m elements).
    430     @param header: Optional tuple or list with header elements to be displayed.
    431     """
    432     if type(header) is list:
    433         header = tuple(header)
    434     lengths = []
    435     if header:
    436         for column in header:
    437             lengths.append(len(column))
    438     for row in matrix:
    439         for i, column in enumerate(row):
    440             column = unicode(column).encode("utf-8")
    441             cl = len(column)
    442             try:
    443                 ml = lengths[i]
    444                 if cl > ml:
    445                     lengths[i] = cl
    446             except IndexError:
    447                 lengths.append(cl)
    448 
    449     lengths = tuple(lengths)
    450     format_string = ""
    451     for length in lengths:
    452         format_string += "%-" + str(length) + "s "
    453     format_string += "\n"
    454 
    455     matrix_str = ""
    456     if header:
    457         matrix_str += format_string % header
    458     for row in matrix:
    459         matrix_str += format_string % tuple(row)
    460 
    461     return matrix_str
    462 
    463 
    464 def read_keyval(path, type_tag=None):
    465     """
    466     Read a key-value pair format file into a dictionary, and return it.
    467     Takes either a filename or directory name as input. If it's a
    468     directory name, we assume you want the file to be called keyval.
    469 
    470     @param path: Full path of the file to read from.
    471     @param type_tag: If not None, only keyvals with key ending
    472                      in a suffix {type_tag} will be collected.
    473     """
    474     if os.path.isdir(path):
    475         path = os.path.join(path, 'keyval')
    476     if not os.path.exists(path):
    477         return {}
    478 
    479     if type_tag:
    480         pattern = r'^([-\.\w]+)\{%s\}=(.*)$' % type_tag
    481     else:
    482         pattern = r'^([-\.\w]+)=(.*)$'
    483 
    484     keyval = {}
    485     f = open(path)
    486     for line in f:
    487         line = re.sub('#.*', '', line).rstrip()
    488         if not line:
    489             continue
    490         match = re.match(pattern, line)
    491         if match:
    492             key = match.group(1)
    493             value = match.group(2)
    494             if re.search('^\d+$', value):
    495                 value = int(value)
    496             elif re.search('^(\d+\.)?\d+$', value):
    497                 value = float(value)
    498             keyval[key] = value
    499         else:
    500             raise ValueError('Invalid format line: %s' % line)
    501     f.close()
    502     return keyval
    503 
    504 
    505 def write_keyval(path, dictionary, type_tag=None):
    506     """
    507     Write a key-value pair format file out to a file. This uses append
    508     mode to open the file, so existing text will not be overwritten or
    509     reparsed.
    510 
    511     If type_tag is None, then the key must be composed of alphanumeric
    512     characters (or dashes+underscores). However, if type-tag is not
    513     null then the keys must also have "{type_tag}" as a suffix. At
    514     the moment the only valid values of type_tag are "attr" and "perf".
    515 
    516     @param path: full path of the file to be written
    517     @param dictionary: the items to write
    518     @param type_tag: see text above
    519     """
    520     if os.path.isdir(path):
    521         path = os.path.join(path, 'keyval')
    522     keyval = open(path, 'a')
    523 
    524     if type_tag is None:
    525         key_regex = re.compile(r'^[-\.\w]+$')
    526     else:
    527         if type_tag not in ('attr', 'perf'):
    528             raise ValueError('Invalid type tag: %s' % type_tag)
    529         escaped_tag = re.escape(type_tag)
    530         key_regex = re.compile(r'^[-\.\w]+\{%s\}$' % escaped_tag)
    531     try:
    532         for key in sorted(dictionary.keys()):
    533             if not key_regex.search(key):
    534                 raise ValueError('Invalid key: %s' % key)
    535             keyval.write('%s=%s\n' % (key, dictionary[key]))
    536     finally:
    537         keyval.close()
    538 
    539 
    540 def is_url(path):
    541     """Return true if path looks like a URL"""
    542     # for now, just handle http and ftp
    543     url_parts = urlparse.urlparse(path)
    544     return (url_parts[0] in ('http', 'ftp'))
    545 
    546 
    547 def urlopen(url, data=None, timeout=5):
    548     """Wrapper to urllib2.urlopen with timeout addition."""
    549 
    550     # Save old timeout
    551     old_timeout = socket.getdefaulttimeout()
    552     socket.setdefaulttimeout(timeout)
    553     try:
    554         return urllib2.urlopen(url, data=data)
    555     finally:
    556         socket.setdefaulttimeout(old_timeout)
    557 
    558 
    559 def urlretrieve(url, filename, data=None, timeout=300):
    560     """Retrieve a file from given url."""
    561     logging.debug('Fetching %s -> %s', url, filename)
    562 
    563     src_file = urlopen(url, data=data, timeout=timeout)
    564     try:
    565         dest_file = open(filename, 'wb')
    566         try:
    567             shutil.copyfileobj(src_file, dest_file)
    568         finally:
    569             dest_file.close()
    570     finally:
    571         src_file.close()
    572 
    573 
    574 def hash(type, input=None):
    575     """
    576     Returns an hash object of type md5 or sha1. This function is implemented in
    577     order to encapsulate hash objects in a way that is compatible with python
    578     2.4 and python 2.6 without warnings.
    579 
    580     Note that even though python 2.6 hashlib supports hash types other than
    581     md5 and sha1, we are artificially limiting the input values in order to
    582     make the function to behave exactly the same among both python
    583     implementations.
    584 
    585     @param input: Optional input string that will be used to update the hash.
    586     """
    587     if type not in ['md5', 'sha1']:
    588         raise ValueError("Unsupported hash type: %s" % type)
    589 
    590     try:
    591         hash = hashlib.new(type)
    592     except NameError:
    593         if type == 'md5':
    594             hash = md5.new()
    595         elif type == 'sha1':
    596             hash = sha.new()
    597 
    598     if input:
    599         hash.update(input)
    600 
    601     return hash
    602 
    603 
    604 def get_file(src, dest, permissions=None):
    605     """Get a file from src, which can be local or a remote URL"""
    606     if src == dest:
    607         return
    608 
    609     if is_url(src):
    610         urlretrieve(src, dest)
    611     else:
    612         shutil.copyfile(src, dest)
    613 
    614     if permissions:
    615         os.chmod(dest, permissions)
    616     return dest
    617 
    618 
    619 def unmap_url(srcdir, src, destdir='.'):
    620     """
    621     Receives either a path to a local file or a URL.
    622     returns either the path to the local file, or the fetched URL
    623 
    624     unmap_url('/usr/src', 'foo.tar', '/tmp')
    625                             = '/usr/src/foo.tar'
    626     unmap_url('/usr/src', 'http://site/file', '/tmp')
    627                             = '/tmp/file'
    628                             (after retrieving it)
    629     """
    630     if is_url(src):
    631         url_parts = urlparse.urlparse(src)
    632         filename = os.path.basename(url_parts[2])
    633         dest = os.path.join(destdir, filename)
    634         return get_file(src, dest)
    635     else:
    636         return os.path.join(srcdir, src)
    637 
    638 
    639 def update_version(srcdir, preserve_srcdir, new_version, install,
    640                    *args, **dargs):
    641     """
    642     Make sure srcdir is version new_version
    643 
    644     If not, delete it and install() the new version.
    645 
    646     In the preserve_srcdir case, we just check it's up to date,
    647     and if not, we rerun install, without removing srcdir
    648     """
    649     versionfile = os.path.join(srcdir, '.version')
    650     install_needed = True
    651 
    652     if os.path.exists(versionfile):
    653         old_version = pickle.load(open(versionfile))
    654         if old_version == new_version:
    655             install_needed = False
    656 
    657     if install_needed:
    658         if not preserve_srcdir and os.path.exists(srcdir):
    659             shutil.rmtree(srcdir)
    660         install(*args, **dargs)
    661         if os.path.exists(srcdir):
    662             pickle.dump(new_version, open(versionfile, 'w'))
    663 
    664 
    665 def get_stderr_level(stderr_is_expected, stdout_level=DEFAULT_STDOUT_LEVEL):
    666     if stderr_is_expected:
    667         return stdout_level
    668     return DEFAULT_STDERR_LEVEL
    669 
    670 
    671 def run(command, timeout=None, ignore_status=False, stdout_tee=None,
    672         stderr_tee=None, verbose=True, stdin=None, stderr_is_expected=None,
    673         stdout_level=None, stderr_level=None, args=(), nickname=None,
    674         ignore_timeout=False, env=None, extra_paths=None):
    675     """
    676     Run a command on the host.
    677 
    678     @param command: the command line string.
    679     @param timeout: time limit in seconds before attempting to kill the
    680             running process. The run() function will take a few seconds
    681             longer than 'timeout' to complete if it has to kill the process.
    682     @param ignore_status: do not raise an exception, no matter what the exit
    683             code of the command is.
    684     @param stdout_tee: optional file-like object to which stdout data
    685             will be written as it is generated (data will still be stored
    686             in result.stdout).
    687     @param stderr_tee: likewise for stderr.
    688     @param verbose: if True, log the command being run.
    689     @param stdin: stdin to pass to the executed process (can be a file
    690             descriptor, a file object of a real file or a string).
    691     @param stderr_is_expected: if True, stderr will be logged at the same level
    692             as stdout
    693     @param stdout_level: logging level used if stdout_tee is TEE_TO_LOGS;
    694             if None, a default is used.
    695     @param stderr_level: like stdout_level but for stderr.
    696     @param args: sequence of strings of arguments to be given to the command
    697             inside " quotes after they have been escaped for that; each
    698             element in the sequence will be given as a separate command
    699             argument
    700     @param nickname: Short string that will appear in logging messages
    701                      associated with this command.
    702     @param ignore_timeout: If True, timeouts are ignored otherwise if a
    703             timeout occurs it will raise CmdTimeoutError.
    704     @param env: Dict containing environment variables used in a subprocess.
    705     @param extra_paths: Optional string list, to be prepended to the PATH
    706                         env variable in env (or os.environ dict if env is
    707                         not specified).
    708 
    709     @return a CmdResult object or None if the command timed out and
    710             ignore_timeout is True
    711 
    712     @raise CmdError: the exit code of the command execution was not 0
    713     @raise CmdTimeoutError: the command timed out and ignore_timeout is False.
    714     """
    715     if isinstance(args, basestring):
    716         raise TypeError('Got a string for the "args" keyword argument, '
    717                         'need a sequence.')
    718 
    719     # In some cases, command will actually be a list
    720     # (For example, see get_user_hash in client/cros/cryptohome.py.)
    721     # So, to cover that case, detect if it's a string or not and convert it
    722     # into one if necessary.
    723     if not isinstance(command, basestring):
    724         command = ' '.join([sh_quote_word(arg) for arg in command])
    725 
    726     command = ' '.join([command] + [sh_quote_word(arg) for arg in args])
    727 
    728     if stderr_is_expected is None:
    729         stderr_is_expected = ignore_status
    730     if stdout_level is None:
    731         stdout_level = DEFAULT_STDOUT_LEVEL
    732     if stderr_level is None:
    733         stderr_level = get_stderr_level(stderr_is_expected, stdout_level)
    734 
    735     try:
    736         bg_job = join_bg_jobs(
    737             (BgJob(command, stdout_tee, stderr_tee, verbose, stdin=stdin,
    738                    stdout_level=stdout_level, stderr_level=stderr_level,
    739                    nickname=nickname, env=env, extra_paths=extra_paths),),
    740             timeout)[0]
    741     except error.CmdTimeoutError:
    742         if not ignore_timeout:
    743             raise
    744         return None
    745 
    746     if not ignore_status and bg_job.result.exit_status:
    747         raise error.CmdError(command, bg_job.result,
    748                              "Command returned non-zero exit status")
    749 
    750     return bg_job.result
    751 
    752 
    753 def run_parallel(commands, timeout=None, ignore_status=False,
    754                  stdout_tee=None, stderr_tee=None,
    755                  nicknames=[]):
    756     """
    757     Behaves the same as run() with the following exceptions:
    758 
    759     - commands is a list of commands to run in parallel.
    760     - ignore_status toggles whether or not an exception should be raised
    761       on any error.
    762 
    763     @return: a list of CmdResult objects
    764     """
    765     bg_jobs = []
    766     for (command, nickname) in itertools.izip_longest(commands, nicknames):
    767         bg_jobs.append(BgJob(command, stdout_tee, stderr_tee,
    768                              stderr_level=get_stderr_level(ignore_status),
    769                              nickname=nickname))
    770 
    771     # Updates objects in bg_jobs list with their process information
    772     join_bg_jobs(bg_jobs, timeout)
    773 
    774     for bg_job in bg_jobs:
    775         if not ignore_status and bg_job.result.exit_status:
    776             raise error.CmdError(command, bg_job.result,
    777                                  "Command returned non-zero exit status")
    778 
    779     return [bg_job.result for bg_job in bg_jobs]
    780 
    781 
    782 @deprecated
    783 def run_bg(command):
    784     """Function deprecated. Please use BgJob class instead."""
    785     bg_job = BgJob(command)
    786     return bg_job.sp, bg_job.result
    787 
    788 
    789 def join_bg_jobs(bg_jobs, timeout=None):
    790     """Joins the bg_jobs with the current thread.
    791 
    792     Returns the same list of bg_jobs objects that was passed in.
    793     """
    794     if any(bg_job.unjoinable for bg_job in bg_jobs):
    795         raise error.InvalidBgJobCall(
    796                 'join_bg_jobs cannot be called for unjoinable bg_job')
    797 
    798     timeout_error = False
    799     try:
    800         # We are holding ends to stdin, stdout pipes
    801         # hence we need to be sure to close those fds no mater what
    802         start_time = time.time()
    803         timeout_error = _wait_for_commands(bg_jobs, start_time, timeout)
    804 
    805         for bg_job in bg_jobs:
    806             # Process stdout and stderr
    807             bg_job.process_output(stdout=True,final_read=True)
    808             bg_job.process_output(stdout=False,final_read=True)
    809     finally:
    810         # close our ends of the pipes to the sp no matter what
    811         for bg_job in bg_jobs:
    812             bg_job.cleanup()
    813 
    814     if timeout_error:
    815         # TODO: This needs to be fixed to better represent what happens when
    816         # running in parallel. However this is backwards compatable, so it will
    817         # do for the time being.
    818         raise error.CmdTimeoutError(
    819                 bg_jobs[0].command, bg_jobs[0].result,
    820                 "Command(s) did not complete within %d seconds" % timeout)
    821 
    822 
    823     return bg_jobs
    824 
    825 
    826 def _wait_for_commands(bg_jobs, start_time, timeout):
    827     """Waits for background jobs by select polling their stdout/stderr.
    828 
    829     @param bg_jobs: A list of background jobs to wait on.
    830     @param start_time: Time used to calculate the timeout lifetime of a job.
    831     @param timeout: The timeout of the list of bg_jobs.
    832 
    833     @return: True if the return was due to a timeout, False otherwise.
    834     """
    835 
    836     # To check for processes which terminate without producing any output
    837     # a 1 second timeout is used in select.
    838     SELECT_TIMEOUT = 1
    839 
    840     read_list = []
    841     write_list = []
    842     reverse_dict = {}
    843 
    844     for bg_job in bg_jobs:
    845         if bg_job.sp.stdout:
    846             read_list.append(bg_job.sp.stdout)
    847             reverse_dict[bg_job.sp.stdout] = (bg_job, True)
    848         if bg_job.sp.stderr:
    849             read_list.append(bg_job.sp.stderr)
    850             reverse_dict[bg_job.sp.stderr] = (bg_job, False)
    851         if bg_job.string_stdin is not None:
    852             write_list.append(bg_job.sp.stdin)
    853             reverse_dict[bg_job.sp.stdin] = bg_job
    854 
    855     if timeout:
    856         stop_time = start_time + timeout
    857         time_left = stop_time - time.time()
    858     else:
    859         time_left = None # so that select never times out
    860 
    861     while not timeout or time_left > 0:
    862         # select will return when we may write to stdin, when there is
    863         # stdout/stderr output we can read (including when it is
    864         # EOF, that is the process has terminated) or when a non-fatal
    865         # signal was sent to the process. In the last case the select returns
    866         # EINTR, and we continue waiting for the job if the signal handler for
    867         # the signal that interrupted the call allows us to.
    868         try:
    869             read_ready, write_ready, _ = select.select(read_list, write_list,
    870                                                        [], SELECT_TIMEOUT)
    871         except select.error as v:
    872             if v[0] == errno.EINTR:
    873                 logging.warning(v)
    874                 continue
    875             else:
    876                 raise
    877         # os.read() has to be used instead of
    878         # subproc.stdout.read() which will otherwise block
    879         for file_obj in read_ready:
    880             bg_job, is_stdout = reverse_dict[file_obj]
    881             bg_job.process_output(is_stdout)
    882 
    883         for file_obj in write_ready:
    884             # we can write PIPE_BUF bytes without blocking
    885             # POSIX requires PIPE_BUF is >= 512
    886             bg_job = reverse_dict[file_obj]
    887             file_obj.write(bg_job.string_stdin[:512])
    888             bg_job.string_stdin = bg_job.string_stdin[512:]
    889             # no more input data, close stdin, remove it from the select set
    890             if not bg_job.string_stdin:
    891                 file_obj.close()
    892                 write_list.remove(file_obj)
    893                 del reverse_dict[file_obj]
    894 
    895         all_jobs_finished = True
    896         for bg_job in bg_jobs:
    897             if bg_job.result.exit_status is not None:
    898                 continue
    899 
    900             bg_job.result.exit_status = bg_job.sp.poll()
    901             if bg_job.result.exit_status is not None:
    902                 # process exited, remove its stdout/stdin from the select set
    903                 bg_job.result.duration = time.time() - start_time
    904                 if bg_job.sp.stdout:
    905                     read_list.remove(bg_job.sp.stdout)
    906                     del reverse_dict[bg_job.sp.stdout]
    907                 if bg_job.sp.stderr:
    908                     read_list.remove(bg_job.sp.stderr)
    909                     del reverse_dict[bg_job.sp.stderr]
    910             else:
    911                 all_jobs_finished = False
    912 
    913         if all_jobs_finished:
    914             return False
    915 
    916         if timeout:
    917             time_left = stop_time - time.time()
    918 
    919     # Kill all processes which did not complete prior to timeout
    920     for bg_job in bg_jobs:
    921         if bg_job.result.exit_status is not None:
    922             continue
    923 
    924         logging.warning('run process timeout (%s) fired on: %s', timeout,
    925                         bg_job.command)
    926         if nuke_subprocess(bg_job.sp) is None:
    927             # If process could not be SIGKILL'd, log kernel stack.
    928             logging.warning(read_file('/proc/%d/stack' % bg_job.sp.pid))
    929         bg_job.result.exit_status = bg_job.sp.poll()
    930         bg_job.result.duration = time.time() - start_time
    931 
    932     return True
    933 
    934 
    935 def pid_is_alive(pid):
    936     """
    937     True if process pid exists and is not yet stuck in Zombie state.
    938     Zombies are impossible to move between cgroups, etc.
    939     pid can be integer, or text of integer.
    940     """
    941     path = '/proc/%s/stat' % pid
    942 
    943     try:
    944         stat = read_one_line(path)
    945     except IOError:
    946         if not os.path.exists(path):
    947             # file went away
    948             return False
    949         raise
    950 
    951     return stat.split()[2] != 'Z'
    952 
    953 
    954 def signal_pid(pid, sig):
    955     """
    956     Sends a signal to a process id. Returns True if the process terminated
    957     successfully, False otherwise.
    958     """
    959     try:
    960         os.kill(pid, sig)
    961     except OSError:
    962         # The process may have died before we could kill it.
    963         pass
    964 
    965     for i in range(5):
    966         if not pid_is_alive(pid):
    967             return True
    968         time.sleep(1)
    969 
    970     # The process is still alive
    971     return False
    972 
    973 
    974 def nuke_subprocess(subproc):
    975     # check if the subprocess is still alive, first
    976     if subproc.poll() is not None:
    977         return subproc.poll()
    978 
    979     # the process has not terminated within timeout,
    980     # kill it via an escalating series of signals.
    981     signal_queue = [signal.SIGTERM, signal.SIGKILL]
    982     for sig in signal_queue:
    983         signal_pid(subproc.pid, sig)
    984         if subproc.poll() is not None:
    985             return subproc.poll()
    986 
    987 
    988 def nuke_pid(pid, signal_queue=(signal.SIGTERM, signal.SIGKILL)):
    989     # the process has not terminated within timeout,
    990     # kill it via an escalating series of signals.
    991     pid_path = '/proc/%d/'
    992     if not os.path.exists(pid_path % pid):
    993         # Assume that if the pid does not exist in proc it is already dead.
    994         logging.error('No listing in /proc for pid:%d.', pid)
    995         raise error.AutoservPidAlreadyDeadError('Could not kill nonexistant '
    996                                                 'pid: %s.', pid)
    997     for sig in signal_queue:
    998         if signal_pid(pid, sig):
    999             return
   1000 
   1001     # no signal successfully terminated the process
   1002     raise error.AutoservRunError('Could not kill %d for process name: %s' % (
   1003             pid, get_process_name(pid)), None)
   1004 
   1005 
   1006 def system(command, timeout=None, ignore_status=False):
   1007     """
   1008     Run a command
   1009 
   1010     @param timeout: timeout in seconds
   1011     @param ignore_status: if ignore_status=False, throw an exception if the
   1012             command's exit code is non-zero
   1013             if ignore_stauts=True, return the exit code.
   1014 
   1015     @return exit status of command
   1016             (note, this will always be zero unless ignore_status=True)
   1017     """
   1018     return run(command, timeout=timeout, ignore_status=ignore_status,
   1019                stdout_tee=TEE_TO_LOGS, stderr_tee=TEE_TO_LOGS).exit_status
   1020 
   1021 
   1022 def system_parallel(commands, timeout=None, ignore_status=False):
   1023     """This function returns a list of exit statuses for the respective
   1024     list of commands."""
   1025     return [bg_jobs.exit_status for bg_jobs in
   1026             run_parallel(commands, timeout=timeout, ignore_status=ignore_status,
   1027                          stdout_tee=TEE_TO_LOGS, stderr_tee=TEE_TO_LOGS)]
   1028 
   1029 
   1030 def system_output(command, timeout=None, ignore_status=False,
   1031                   retain_output=False, args=()):
   1032     """
   1033     Run a command and return the stdout output.
   1034 
   1035     @param command: command string to execute.
   1036     @param timeout: time limit in seconds before attempting to kill the
   1037             running process. The function will take a few seconds longer
   1038             than 'timeout' to complete if it has to kill the process.
   1039     @param ignore_status: do not raise an exception, no matter what the exit
   1040             code of the command is.
   1041     @param retain_output: set to True to make stdout/stderr of the command
   1042             output to be also sent to the logging system
   1043     @param args: sequence of strings of arguments to be given to the command
   1044             inside " quotes after they have been escaped for that; each
   1045             element in the sequence will be given as a separate command
   1046             argument
   1047 
   1048     @return a string with the stdout output of the command.
   1049     """
   1050     if retain_output:
   1051         out = run(command, timeout=timeout, ignore_status=ignore_status,
   1052                   stdout_tee=TEE_TO_LOGS, stderr_tee=TEE_TO_LOGS,
   1053                   args=args).stdout
   1054     else:
   1055         out = run(command, timeout=timeout, ignore_status=ignore_status,
   1056                   args=args).stdout
   1057     if out[-1:] == '\n':
   1058         out = out[:-1]
   1059     return out
   1060 
   1061 
   1062 def system_output_parallel(commands, timeout=None, ignore_status=False,
   1063                            retain_output=False):
   1064     if retain_output:
   1065         out = [bg_job.stdout for bg_job
   1066                in run_parallel(commands, timeout=timeout,
   1067                                ignore_status=ignore_status,
   1068                                stdout_tee=TEE_TO_LOGS, stderr_tee=TEE_TO_LOGS)]
   1069     else:
   1070         out = [bg_job.stdout for bg_job in run_parallel(commands,
   1071                                   timeout=timeout, ignore_status=ignore_status)]
   1072     for x in out:
   1073         if out[-1:] == '\n': out = out[:-1]
   1074     return out
   1075 
   1076 
   1077 def strip_unicode(input):
   1078     if type(input) == list:
   1079         return [strip_unicode(i) for i in input]
   1080     elif type(input) == dict:
   1081         output = {}
   1082         for key in input.keys():
   1083             output[str(key)] = strip_unicode(input[key])
   1084         return output
   1085     elif type(input) == unicode:
   1086         return str(input)
   1087     else:
   1088         return input
   1089 
   1090 
   1091 def get_cpu_percentage(function, *args, **dargs):
   1092     """Returns a tuple containing the CPU% and return value from function call.
   1093 
   1094     This function calculates the usage time by taking the difference of
   1095     the user and system times both before and after the function call.
   1096     """
   1097     child_pre = resource.getrusage(resource.RUSAGE_CHILDREN)
   1098     self_pre = resource.getrusage(resource.RUSAGE_SELF)
   1099     start = time.time()
   1100     to_return = function(*args, **dargs)
   1101     elapsed = time.time() - start
   1102     self_post = resource.getrusage(resource.RUSAGE_SELF)
   1103     child_post = resource.getrusage(resource.RUSAGE_CHILDREN)
   1104 
   1105     # Calculate CPU Percentage
   1106     s_user, s_system = [a - b for a, b in zip(self_post, self_pre)[:2]]
   1107     c_user, c_system = [a - b for a, b in zip(child_post, child_pre)[:2]]
   1108     cpu_percent = (s_user + c_user + s_system + c_system) / elapsed
   1109 
   1110     return cpu_percent, to_return
   1111 
   1112 
   1113 def get_arch(run_function=run):
   1114     """
   1115     Get the hardware architecture of the machine.
   1116     If specified, run_function should return a CmdResult object and throw a
   1117     CmdError exception.
   1118     If run_function is anything other than utils.run(), it is used to
   1119     execute the commands. By default (when set to utils.run()) this will
   1120     just examine os.uname()[4].
   1121     """
   1122 
   1123     # Short circuit from the common case.
   1124     if run_function == run:
   1125         return re.sub(r'i\d86$', 'i386', os.uname()[4])
   1126 
   1127     # Otherwise, use the run_function in case it hits a remote machine.
   1128     arch = run_function('/bin/uname -m').stdout.rstrip()
   1129     if re.match(r'i\d86$', arch):
   1130         arch = 'i386'
   1131     return arch
   1132 
   1133 def get_arch_userspace(run_function=run):
   1134     """
   1135     Get the architecture by userspace (possibly different from kernel).
   1136     """
   1137     archs = {
   1138         'arm': 'ELF 32-bit.*, ARM,',
   1139         'i386': 'ELF 32-bit.*, Intel 80386,',
   1140         'x86_64': 'ELF 64-bit.*, x86-64,',
   1141     }
   1142 
   1143     cmd = 'file --brief --dereference /bin/sh'
   1144     filestr = run_function(cmd).stdout.rstrip()
   1145     for a, regex in archs.iteritems():
   1146         if re.match(regex, filestr):
   1147             return a
   1148 
   1149     return get_arch()
   1150 
   1151 
   1152 def get_num_logical_cpus_per_socket(run_function=run):
   1153     """
   1154     Get the number of cores (including hyperthreading) per cpu.
   1155     run_function is used to execute the commands. It defaults to
   1156     utils.run() but a custom method (if provided) should be of the
   1157     same schema as utils.run. It should return a CmdResult object and
   1158     throw a CmdError exception.
   1159     """
   1160     siblings = run_function('grep "^siblings" /proc/cpuinfo').stdout.rstrip()
   1161     num_siblings = map(int,
   1162                        re.findall(r'^siblings\s*:\s*(\d+)\s*$',
   1163                                   siblings, re.M))
   1164     if len(num_siblings) == 0:
   1165         raise error.TestError('Unable to find siblings info in /proc/cpuinfo')
   1166     if min(num_siblings) != max(num_siblings):
   1167         raise error.TestError('Number of siblings differ %r' %
   1168                               num_siblings)
   1169     return num_siblings[0]
   1170 
   1171 
   1172 def merge_trees(src, dest):
   1173     """
   1174     Merges a source directory tree at 'src' into a destination tree at
   1175     'dest'. If a path is a file in both trees than the file in the source
   1176     tree is APPENDED to the one in the destination tree. If a path is
   1177     a directory in both trees then the directories are recursively merged
   1178     with this function. In any other case, the function will skip the
   1179     paths that cannot be merged (instead of failing).
   1180     """
   1181     if not os.path.exists(src):
   1182         return # exists only in dest
   1183     elif not os.path.exists(dest):
   1184         if os.path.isfile(src):
   1185             shutil.copy2(src, dest) # file only in src
   1186         else:
   1187             shutil.copytree(src, dest, symlinks=True) # dir only in src
   1188         return
   1189     elif os.path.isfile(src) and os.path.isfile(dest):
   1190         # src & dest are files in both trees, append src to dest
   1191         destfile = open(dest, "a")
   1192         try:
   1193             srcfile = open(src)
   1194             try:
   1195                 destfile.write(srcfile.read())
   1196             finally:
   1197                 srcfile.close()
   1198         finally:
   1199             destfile.close()
   1200     elif os.path.isdir(src) and os.path.isdir(dest):
   1201         # src & dest are directories in both trees, so recursively merge
   1202         for name in os.listdir(src):
   1203             merge_trees(os.path.join(src, name), os.path.join(dest, name))
   1204     else:
   1205         # src & dest both exist, but are incompatible
   1206         return
   1207 
   1208 
   1209 class CmdResult(object):
   1210     """
   1211     Command execution result.
   1212 
   1213     command:     String containing the command line itself
   1214     exit_status: Integer exit code of the process
   1215     stdout:      String containing stdout of the process
   1216     stderr:      String containing stderr of the process
   1217     duration:    Elapsed wall clock time running the process
   1218     """
   1219 
   1220 
   1221     def __init__(self, command="", stdout="", stderr="",
   1222                  exit_status=None, duration=0):
   1223         self.command = command
   1224         self.exit_status = exit_status
   1225         self.stdout = stdout
   1226         self.stderr = stderr
   1227         self.duration = duration
   1228 
   1229 
   1230     def __eq__(self, other):
   1231         if type(self) == type(other):
   1232             return (self.command == other.command
   1233                     and self.exit_status == other.exit_status
   1234                     and self.stdout == other.stdout
   1235                     and self.stderr == other.stderr
   1236                     and self.duration == other.duration)
   1237         else:
   1238             return NotImplemented
   1239 
   1240 
   1241     def __repr__(self):
   1242         wrapper = textwrap.TextWrapper(width = 78,
   1243                                        initial_indent="\n    ",
   1244                                        subsequent_indent="    ")
   1245 
   1246         stdout = self.stdout.rstrip()
   1247         if stdout:
   1248             stdout = "\nstdout:\n%s" % stdout
   1249 
   1250         stderr = self.stderr.rstrip()
   1251         if stderr:
   1252             stderr = "\nstderr:\n%s" % stderr
   1253 
   1254         return ("* Command: %s\n"
   1255                 "Exit status: %s\n"
   1256                 "Duration: %s\n"
   1257                 "%s"
   1258                 "%s"
   1259                 % (wrapper.fill(str(self.command)), self.exit_status,
   1260                 self.duration, stdout, stderr))
   1261 
   1262 
   1263 class run_randomly:
   1264     def __init__(self, run_sequentially=False):
   1265         # Run sequentially is for debugging control files
   1266         self.test_list = []
   1267         self.run_sequentially = run_sequentially
   1268 
   1269 
   1270     def add(self, *args, **dargs):
   1271         test = (args, dargs)
   1272         self.test_list.append(test)
   1273 
   1274 
   1275     def run(self, fn):
   1276         while self.test_list:
   1277             test_index = random.randint(0, len(self.test_list)-1)
   1278             if self.run_sequentially:
   1279                 test_index = 0
   1280             (args, dargs) = self.test_list.pop(test_index)
   1281             fn(*args, **dargs)
   1282 
   1283 
   1284 def import_site_module(path, module, dummy=None, modulefile=None):
   1285     """
   1286     Try to import the site specific module if it exists.
   1287 
   1288     @param path full filename of the source file calling this (ie __file__)
   1289     @param module full module name
   1290     @param dummy dummy value to return in case there is no symbol to import
   1291     @param modulefile module filename
   1292 
   1293     @return site specific module or dummy
   1294 
   1295     @raises ImportError if the site file exists but imports fails
   1296     """
   1297     short_module = module[module.rfind(".") + 1:]
   1298 
   1299     if not modulefile:
   1300         modulefile = short_module + ".py"
   1301 
   1302     if os.path.exists(os.path.join(os.path.dirname(path), modulefile)):
   1303         return __import__(module, {}, {}, [short_module])
   1304     return dummy
   1305 
   1306 
   1307 def import_site_symbol(path, module, name, dummy=None, modulefile=None):
   1308     """
   1309     Try to import site specific symbol from site specific file if it exists
   1310 
   1311     @param path full filename of the source file calling this (ie __file__)
   1312     @param module full module name
   1313     @param name symbol name to be imported from the site file
   1314     @param dummy dummy value to return in case there is no symbol to import
   1315     @param modulefile module filename
   1316 
   1317     @return site specific symbol or dummy
   1318 
   1319     @raises ImportError if the site file exists but imports fails
   1320     """
   1321     module = import_site_module(path, module, modulefile=modulefile)
   1322     if not module:
   1323         return dummy
   1324 
   1325     # special unique value to tell us if the symbol can't be imported
   1326     cant_import = object()
   1327 
   1328     obj = getattr(module, name, cant_import)
   1329     if obj is cant_import:
   1330         return dummy
   1331 
   1332     return obj
   1333 
   1334 
   1335 def import_site_class(path, module, classname, baseclass, modulefile=None):
   1336     """
   1337     Try to import site specific class from site specific file if it exists
   1338 
   1339     Args:
   1340         path: full filename of the source file calling this (ie __file__)
   1341         module: full module name
   1342         classname: class name to be loaded from site file
   1343         baseclass: base class object to return when no site file present or
   1344             to mixin when site class exists but is not inherited from baseclass
   1345         modulefile: module filename
   1346 
   1347     Returns: baseclass if site specific class does not exist, the site specific
   1348         class if it exists and is inherited from baseclass or a mixin of the
   1349         site specific class and baseclass when the site specific class exists
   1350         and is not inherited from baseclass
   1351 
   1352     Raises: ImportError if the site file exists but imports fails
   1353     """
   1354 
   1355     res = import_site_symbol(path, module, classname, None, modulefile)
   1356     if res:
   1357         if not issubclass(res, baseclass):
   1358             # if not a subclass of baseclass then mix in baseclass with the
   1359             # site specific class object and return the result
   1360             res = type(classname, (res, baseclass), {})
   1361     else:
   1362         res = baseclass
   1363 
   1364     return res
   1365 
   1366 
   1367 def import_site_function(path, module, funcname, dummy, modulefile=None):
   1368     """
   1369     Try to import site specific function from site specific file if it exists
   1370 
   1371     Args:
   1372         path: full filename of the source file calling this (ie __file__)
   1373         module: full module name
   1374         funcname: function name to be imported from site file
   1375         dummy: dummy function to return in case there is no function to import
   1376         modulefile: module filename
   1377 
   1378     Returns: site specific function object or dummy
   1379 
   1380     Raises: ImportError if the site file exists but imports fails
   1381     """
   1382 
   1383     return import_site_symbol(path, module, funcname, dummy, modulefile)
   1384 
   1385 
   1386 def _get_pid_path(program_name):
   1387     my_path = os.path.dirname(__file__)
   1388     return os.path.abspath(os.path.join(my_path, "..", "..",
   1389                                         "%s.pid" % program_name))
   1390 
   1391 
   1392 def write_pid(program_name):
   1393     """
   1394     Try to drop <program_name>.pid in the main autotest directory.
   1395 
   1396     Args:
   1397       program_name: prefix for file name
   1398     """
   1399     pidfile = open(_get_pid_path(program_name), "w")
   1400     try:
   1401         pidfile.write("%s\n" % os.getpid())
   1402     finally:
   1403         pidfile.close()
   1404 
   1405 
   1406 def delete_pid_file_if_exists(program_name):
   1407     """
   1408     Tries to remove <program_name>.pid from the main autotest directory.
   1409     """
   1410     pidfile_path = _get_pid_path(program_name)
   1411 
   1412     try:
   1413         os.remove(pidfile_path)
   1414     except OSError:
   1415         if not os.path.exists(pidfile_path):
   1416             return
   1417         raise
   1418 
   1419 
   1420 def get_pid_from_file(program_name):
   1421     """
   1422     Reads the pid from <program_name>.pid in the autotest directory.
   1423 
   1424     @param program_name the name of the program
   1425     @return the pid if the file exists, None otherwise.
   1426     """
   1427     pidfile_path = _get_pid_path(program_name)
   1428     if not os.path.exists(pidfile_path):
   1429         return None
   1430 
   1431     pidfile = open(_get_pid_path(program_name), 'r')
   1432 
   1433     try:
   1434         try:
   1435             pid = int(pidfile.readline())
   1436         except IOError:
   1437             if not os.path.exists(pidfile_path):
   1438                 return None
   1439             raise
   1440     finally:
   1441         pidfile.close()
   1442 
   1443     return pid
   1444 
   1445 
   1446 def get_process_name(pid):
   1447     """
   1448     Get process name from PID.
   1449     @param pid: PID of process.
   1450     @return: Process name if PID stat file exists or 'Dead PID' if it does not.
   1451     """
   1452     pid_stat_path = "/proc/%d/stat"
   1453     if not os.path.exists(pid_stat_path % pid):
   1454         return "Dead Pid"
   1455     return get_field(read_file(pid_stat_path % pid), 1)[1:-1]
   1456 
   1457 
   1458 def program_is_alive(program_name):
   1459     """
   1460     Checks if the process is alive and not in Zombie state.
   1461 
   1462     @param program_name the name of the program
   1463     @return True if still alive, False otherwise
   1464     """
   1465     pid = get_pid_from_file(program_name)
   1466     if pid is None:
   1467         return False
   1468     return pid_is_alive(pid)
   1469 
   1470 
   1471 def signal_program(program_name, sig=signal.SIGTERM):
   1472     """
   1473     Sends a signal to the process listed in <program_name>.pid
   1474 
   1475     @param program_name the name of the program
   1476     @param sig signal to send
   1477     """
   1478     pid = get_pid_from_file(program_name)
   1479     if pid:
   1480         signal_pid(pid, sig)
   1481 
   1482 
   1483 def get_relative_path(path, reference):
   1484     """Given 2 absolute paths "path" and "reference", compute the path of
   1485     "path" as relative to the directory "reference".
   1486 
   1487     @param path the absolute path to convert to a relative path
   1488     @param reference an absolute directory path to which the relative
   1489         path will be computed
   1490     """
   1491     # normalize the paths (remove double slashes, etc)
   1492     assert(os.path.isabs(path))
   1493     assert(os.path.isabs(reference))
   1494 
   1495     path = os.path.normpath(path)
   1496     reference = os.path.normpath(reference)
   1497 
   1498     # we could use os.path.split() but it splits from the end
   1499     path_list = path.split(os.path.sep)[1:]
   1500     ref_list = reference.split(os.path.sep)[1:]
   1501 
   1502     # find the longest leading common path
   1503     for i in xrange(min(len(path_list), len(ref_list))):
   1504         if path_list[i] != ref_list[i]:
   1505             # decrement i so when exiting this loop either by no match or by
   1506             # end of range we are one step behind
   1507             i -= 1
   1508             break
   1509     i += 1
   1510     # drop the common part of the paths, not interested in that anymore
   1511     del path_list[:i]
   1512 
   1513     # for each uncommon component in the reference prepend a ".."
   1514     path_list[:0] = ['..'] * (len(ref_list) - i)
   1515 
   1516     return os.path.join(*path_list)
   1517 
   1518 
   1519 def sh_escape(command):
   1520     """
   1521     Escape special characters from a command so that it can be passed
   1522     as a double quoted (" ") string in a (ba)sh command.
   1523 
   1524     Args:
   1525             command: the command string to escape.
   1526 
   1527     Returns:
   1528             The escaped command string. The required englobing double
   1529             quotes are NOT added and so should be added at some point by
   1530             the caller.
   1531 
   1532     See also: http://www.tldp.org/LDP/abs/html/escapingsection.html
   1533     """
   1534     command = command.replace("\\", "\\\\")
   1535     command = command.replace("$", r'\$')
   1536     command = command.replace('"', r'\"')
   1537     command = command.replace('`', r'\`')
   1538     return command
   1539 
   1540 
   1541 def sh_quote_word(text, whitelist=SHELL_QUOTING_WHITELIST):
   1542     r"""Quote a string to make it safe as a single word in a shell command.
   1543 
   1544     POSIX shell syntax recognizes no escape characters inside a single-quoted
   1545     string.  So, single quotes can safely quote any string of characters except
   1546     a string with a single quote character.  A single quote character must be
   1547     quoted with the sequence '\'' which translates to:
   1548         '  -> close current quote
   1549         \' -> insert a literal single quote
   1550         '  -> reopen quoting again.
   1551 
   1552     This is safe for all combinations of characters, including embedded and
   1553     trailing backslashes in odd or even numbers.
   1554 
   1555     This is also safe for nesting, e.g. the following is a valid use:
   1556 
   1557         adb_command = 'adb shell %s' % (
   1558                 sh_quote_word('echo %s' % sh_quote_word('hello world')))
   1559 
   1560     @param text: The string to be quoted into a single word for the shell.
   1561     @param whitelist: Optional list of characters that do not need quoting.
   1562                       Defaults to a known good list of characters.
   1563 
   1564     @return A string, possibly quoted, safe as a single word for a shell.
   1565     """
   1566     if all(c in whitelist for c in text):
   1567         return text
   1568     return "'" + text.replace("'", r"'\''") + "'"
   1569 
   1570 
   1571 def configure(extra=None, configure='./configure'):
   1572     """
   1573     Run configure passing in the correct host, build, and target options.
   1574 
   1575     @param extra: extra command line arguments to pass to configure
   1576     @param configure: which configure script to use
   1577     """
   1578     args = []
   1579     if 'CHOST' in os.environ:
   1580         args.append('--host=' + os.environ['CHOST'])
   1581     if 'CBUILD' in os.environ:
   1582         args.append('--build=' + os.environ['CBUILD'])
   1583     if 'CTARGET' in os.environ:
   1584         args.append('--target=' + os.environ['CTARGET'])
   1585     if extra:
   1586         args.append(extra)
   1587 
   1588     system('%s %s' % (configure, ' '.join(args)))
   1589 
   1590 
   1591 def make(extra='', make='make', timeout=None, ignore_status=False):
   1592     """
   1593     Run make, adding MAKEOPTS to the list of options.
   1594 
   1595     @param extra: extra command line arguments to pass to make.
   1596     """
   1597     cmd = '%s %s %s' % (make, os.environ.get('MAKEOPTS', ''), extra)
   1598     return system(cmd, timeout=timeout, ignore_status=ignore_status)
   1599 
   1600 
   1601 def compare_versions(ver1, ver2):
   1602     """Version number comparison between ver1 and ver2 strings.
   1603 
   1604     >>> compare_tuple("1", "2")
   1605     -1
   1606     >>> compare_tuple("foo-1.1", "foo-1.2")
   1607     -1
   1608     >>> compare_tuple("1.2", "1.2a")
   1609     -1
   1610     >>> compare_tuple("1.2b", "1.2a")
   1611     1
   1612     >>> compare_tuple("1.3.5.3a", "1.3.5.3b")
   1613     -1
   1614 
   1615     Args:
   1616         ver1: version string
   1617         ver2: version string
   1618 
   1619     Returns:
   1620         int:  1 if ver1 >  ver2
   1621               0 if ver1 == ver2
   1622              -1 if ver1 <  ver2
   1623     """
   1624     ax = re.split('[.-]', ver1)
   1625     ay = re.split('[.-]', ver2)
   1626     while len(ax) > 0 and len(ay) > 0:
   1627         cx = ax.pop(0)
   1628         cy = ay.pop(0)
   1629         maxlen = max(len(cx), len(cy))
   1630         c = cmp(cx.zfill(maxlen), cy.zfill(maxlen))
   1631         if c != 0:
   1632             return c
   1633     return cmp(len(ax), len(ay))
   1634 
   1635 
   1636 def args_to_dict(args):
   1637     """Convert autoserv extra arguments in the form of key=val or key:val to a
   1638     dictionary.  Each argument key is converted to lowercase dictionary key.
   1639 
   1640     Args:
   1641         args - list of autoserv extra arguments.
   1642 
   1643     Returns:
   1644         dictionary
   1645     """
   1646     arg_re = re.compile(r'(\w+)[:=](.*)$')
   1647     dict = {}
   1648     for arg in args:
   1649         match = arg_re.match(arg)
   1650         if match:
   1651             dict[match.group(1).lower()] = match.group(2)
   1652         else:
   1653             logging.warning("args_to_dict: argument '%s' doesn't match "
   1654                             "'%s' pattern. Ignored.", arg, arg_re.pattern)
   1655     return dict
   1656 
   1657 
   1658 def get_unused_port():
   1659     """
   1660     Finds a semi-random available port. A race condition is still
   1661     possible after the port number is returned, if another process
   1662     happens to bind it.
   1663 
   1664     Returns:
   1665         A port number that is unused on both TCP and UDP.
   1666     """
   1667 
   1668     def try_bind(port, socket_type, socket_proto):
   1669         s = socket.socket(socket.AF_INET, socket_type, socket_proto)
   1670         try:
   1671             try:
   1672                 s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
   1673                 s.bind(('', port))
   1674                 return s.getsockname()[1]
   1675             except socket.error:
   1676                 return None
   1677         finally:
   1678             s.close()
   1679 
   1680     # On the 2.6 kernel, calling try_bind() on UDP socket returns the
   1681     # same port over and over. So always try TCP first.
   1682     while True:
   1683         # Ask the OS for an unused port.
   1684         port = try_bind(0, socket.SOCK_STREAM, socket.IPPROTO_TCP)
   1685         # Check if this port is unused on the other protocol.
   1686         if port and try_bind(port, socket.SOCK_DGRAM, socket.IPPROTO_UDP):
   1687             return port
   1688 
   1689 
   1690 def ask(question, auto=False):
   1691     """
   1692     Raw input with a prompt that emulates logging.
   1693 
   1694     @param question: Question to be asked
   1695     @param auto: Whether to return "y" instead of asking the question
   1696     """
   1697     if auto:
   1698         logging.info("%s (y/n) y", question)
   1699         return "y"
   1700     return raw_input("%s INFO | %s (y/n) " %
   1701                      (time.strftime("%H:%M:%S", time.localtime()), question))
   1702 
   1703 
   1704 def rdmsr(address, cpu=0):
   1705     """
   1706     Reads an x86 MSR from the specified CPU, returns as long integer.
   1707     """
   1708     with open('/dev/cpu/%s/msr' % cpu, 'r', 0) as fd:
   1709         fd.seek(address)
   1710         return struct.unpack('=Q', fd.read(8))[0]
   1711 
   1712 
   1713 def wait_for_value(func,
   1714                    expected_value=None,
   1715                    min_threshold=None,
   1716                    max_threshold=None,
   1717                    timeout_sec=10):
   1718     """
   1719     Returns the value of func().  If |expected_value|, |min_threshold|, and
   1720     |max_threshold| are not set, returns immediately.
   1721 
   1722     If |expected_value| is set, polls the return value until |expected_value| is
   1723     reached, and returns that value.
   1724 
   1725     If either |max_threshold| or |min_threshold| is set, this function will
   1726     will repeatedly call func() until the return value reaches or exceeds one of
   1727     these thresholds.
   1728 
   1729     Polling will stop after |timeout_sec| regardless of these thresholds.
   1730 
   1731     @param func: function whose return value is to be waited on.
   1732     @param expected_value: wait for func to return this value.
   1733     @param min_threshold: wait for func value to reach or fall below this value.
   1734     @param max_threshold: wait for func value to reach or rise above this value.
   1735     @param timeout_sec: Number of seconds to wait before giving up and
   1736                         returning whatever value func() last returned.
   1737 
   1738     Return value:
   1739         The most recent return value of func().
   1740     """
   1741     value = None
   1742     start_time_sec = time.time()
   1743     while True:
   1744         value = func()
   1745         if (expected_value is None and \
   1746             min_threshold is None and \
   1747             max_threshold is None) or \
   1748            (expected_value is not None and value == expected_value) or \
   1749            (min_threshold is not None and value <= min_threshold) or \
   1750            (max_threshold is not None and value >= max_threshold):
   1751             break
   1752 
   1753         if time.time() - start_time_sec >= timeout_sec:
   1754             break
   1755         time.sleep(0.1)
   1756 
   1757     return value
   1758 
   1759 
   1760 def wait_for_value_changed(func,
   1761                            old_value=None,
   1762                            timeout_sec=10):
   1763     """
   1764     Returns the value of func().
   1765 
   1766     The function polls the return value until it is different from |old_value|,
   1767     and returns that value.
   1768 
   1769     Polling will stop after |timeout_sec|.
   1770 
   1771     @param func: function whose return value is to be waited on.
   1772     @param old_value: wait for func to return a value different from this.
   1773     @param timeout_sec: Number of seconds to wait before giving up and
   1774                         returning whatever value func() last returned.
   1775 
   1776     @returns The most recent return value of func().
   1777     """
   1778     value = None
   1779     start_time_sec = time.time()
   1780     while True:
   1781         value = func()
   1782         if value != old_value:
   1783             break
   1784 
   1785         if time.time() - start_time_sec >= timeout_sec:
   1786             break
   1787         time.sleep(0.1)
   1788 
   1789     return value
   1790 
   1791 
   1792 CONFIG = global_config.global_config
   1793 
   1794 # Keep checking if the pid is alive every second until the timeout (in seconds)
   1795 CHECK_PID_IS_ALIVE_TIMEOUT = 6
   1796 
   1797 _LOCAL_HOST_LIST = ('localhost', '127.0.0.1')
   1798 
   1799 # The default address of a vm gateway.
   1800 DEFAULT_VM_GATEWAY = '10.0.2.2'
   1801 
   1802 # Google Storage bucket URI to store results in.
   1803 DEFAULT_OFFLOAD_GSURI = CONFIG.get_config_value(
   1804         'CROS', 'results_storage_server', default=None)
   1805 
   1806 # Default Moblab Ethernet Interface.
   1807 _MOBLAB_ETH_0 = 'eth0'
   1808 _MOBLAB_ETH_1 = 'eth1'
   1809 
   1810 # A list of subnets that requires dedicated devserver and drone in the same
   1811 # subnet. Each item is a tuple of (subnet_ip, mask_bits), e.g.,
   1812 # ('192.168.0.0', 24))
   1813 RESTRICTED_SUBNETS = []
   1814 
   1815 def _setup_restricted_subnets():
   1816     restricted_subnets_list = CONFIG.get_config_value(
   1817             'CROS', 'restricted_subnets', type=list, default=[])
   1818     # TODO(dshi): Remove the code to split subnet with `:` after R51 is
   1819     # off stable channel, and update shadow config to use `/` as
   1820     # delimiter for consistency.
   1821     for subnet in restricted_subnets_list:
   1822         ip, mask_bits = subnet.split('/') if '/' in subnet \
   1823                         else subnet.split(':')
   1824         RESTRICTED_SUBNETS.append((ip, int(mask_bits)))
   1825 
   1826 _setup_restricted_subnets()
   1827 
   1828 # regex pattern for CLIENT/wireless_ssid_ config. For example, global config
   1829 # can have following config in CLIENT section to indicate that hosts in subnet
   1830 # 192.168.0.1/24 should use wireless ssid of `ssid_1`
   1831 # wireless_ssid_192.168.0.1/24: ssid_1
   1832 WIRELESS_SSID_PATTERN = 'wireless_ssid_(.*)/(\d+)'
   1833 
   1834 
   1835 def get_moblab_serial_number():
   1836     """Gets the moblab public network interface.
   1837 
   1838     If the eth0 is an USB interface, try to use eth1 instead. Otherwise
   1839     use eth0 by default.
   1840     """
   1841     try:
   1842         cmd_result = run('sudo vpd -g serial_number')
   1843         if cmd_result.stdout:
   1844           return cmd_result.stdout
   1845     except error.CmdError as e:
   1846         logging.error(str(e))
   1847         logging.info('Serial number ')
   1848         pass
   1849     return 'NoSerialNumber'
   1850 
   1851 
   1852 def ping(host, deadline=None, tries=None, timeout=60, user=None):
   1853     """Attempt to ping |host|.
   1854 
   1855     Shell out to 'ping' if host is an IPv4 addres or 'ping6' if host is an
   1856     IPv6 address to try to reach |host| for |timeout| seconds.
   1857     Returns exit code of ping.
   1858 
   1859     Per 'man ping', if you specify BOTH |deadline| and |tries|, ping only
   1860     returns 0 if we get responses to |tries| pings within |deadline| seconds.
   1861 
   1862     Specifying |deadline| or |count| alone should return 0 as long as
   1863     some packets receive responses.
   1864 
   1865     Note that while this works with literal IPv6 addresses it will not work
   1866     with hostnames that resolve to IPv6 only.
   1867 
   1868     @param host: the host to ping.
   1869     @param deadline: seconds within which |tries| pings must succeed.
   1870     @param tries: number of pings to send.
   1871     @param timeout: number of seconds after which to kill 'ping' command.
   1872     @return exit code of ping command.
   1873     """
   1874     args = [host]
   1875     cmd = 'ping6' if re.search(r':.*:', host) else 'ping'
   1876 
   1877     if deadline:
   1878         args.append('-w%d' % deadline)
   1879     if tries:
   1880         args.append('-c%d' % tries)
   1881 
   1882     if user != None:
   1883         args = [user, '-c', ' '.join([cmd] + args)]
   1884         cmd = 'su'
   1885 
   1886     return run(cmd, args=args, verbose=True,
   1887                           ignore_status=True, timeout=timeout,
   1888                           stdout_tee=TEE_TO_LOGS,
   1889                           stderr_tee=TEE_TO_LOGS).exit_status
   1890 
   1891 
   1892 def host_is_in_lab_zone(hostname):
   1893     """Check if the host is in the CLIENT.dns_zone.
   1894 
   1895     @param hostname: The hostname to check.
   1896     @returns True if hostname.dns_zone resolves, otherwise False.
   1897     """
   1898     host_parts = hostname.split('.')
   1899     dns_zone = CONFIG.get_config_value('CLIENT', 'dns_zone', default=None)
   1900     fqdn = '%s.%s' % (host_parts[0], dns_zone)
   1901     try:
   1902         socket.gethostbyname(fqdn)
   1903         return True
   1904     except socket.gaierror:
   1905         return False
   1906 
   1907 
   1908 def host_could_be_in_afe(hostname):
   1909     """Check if the host could be in Autotest Front End.
   1910 
   1911     Report whether or not a host could be in AFE, without actually
   1912     consulting AFE. This method exists because some systems are in the
   1913     lab zone, but not actually managed by AFE.
   1914 
   1915     @param hostname: The hostname to check.
   1916     @returns True if hostname is in lab zone, and does not match *-dev-*
   1917     """
   1918     # Do the 'dev' check first, so that we skip DNS lookup if the
   1919     # hostname matches. This should give us greater resilience to lab
   1920     # failures.
   1921     return (hostname.find('-dev-') == -1) and host_is_in_lab_zone(hostname)
   1922 
   1923 
   1924 def get_chrome_version(job_views):
   1925     """
   1926     Retrieves the version of the chrome binary associated with a job.
   1927 
   1928     When a test runs we query the chrome binary for it's version and drop
   1929     that value into a client keyval. To retrieve the chrome version we get all
   1930     the views associated with a test from the db, including those of the
   1931     server and client jobs, and parse the version out of the first test view
   1932     that has it. If we never ran a single test in the suite the job_views
   1933     dictionary will not contain a chrome version.
   1934 
   1935     This method cannot retrieve the chrome version from a dictionary that
   1936     does not conform to the structure of an autotest tko view.
   1937 
   1938     @param job_views: a list of a job's result views, as returned by
   1939                       the get_detailed_test_views method in rpc_interface.
   1940     @return: The chrome version string, or None if one can't be found.
   1941     """
   1942 
   1943     # Aborted jobs have no views.
   1944     if not job_views:
   1945         return None
   1946 
   1947     for view in job_views:
   1948         if (view.get('attributes')
   1949             and constants.CHROME_VERSION in view['attributes'].keys()):
   1950 
   1951             return view['attributes'].get(constants.CHROME_VERSION)
   1952 
   1953     logging.warning('Could not find chrome version for failure.')
   1954     return None
   1955 
   1956 
   1957 def get_moblab_id():
   1958     """Gets the moblab random id.
   1959 
   1960     The random id file is cached on disk. If it does not exist, a new file is
   1961     created the first time.
   1962 
   1963     @returns the moblab random id.
   1964     """
   1965     moblab_id_filepath = '/home/moblab/.moblab_id'
   1966     try:
   1967         if os.path.exists(moblab_id_filepath):
   1968             with open(moblab_id_filepath, 'r') as moblab_id_file:
   1969                 random_id = moblab_id_file.read()
   1970         else:
   1971             random_id = uuid.uuid1().hex
   1972             with open(moblab_id_filepath, 'w') as moblab_id_file:
   1973                 moblab_id_file.write('%s' % random_id)
   1974     except IOError as e:
   1975         # Possible race condition, another process has created the file.
   1976         # Sleep a second to make sure the file gets closed.
   1977         logging.info(e)
   1978         time.sleep(1)
   1979         with open(moblab_id_filepath, 'r') as moblab_id_file:
   1980             random_id = moblab_id_file.read()
   1981     return random_id
   1982 
   1983 
   1984 def get_offload_gsuri():
   1985     """Return the GSURI to offload test results to.
   1986 
   1987     For the normal use case this is the results_storage_server in the
   1988     global_config.
   1989 
   1990     However partners using Moblab will be offloading their results to a
   1991     subdirectory of their image storage buckets. The subdirectory is
   1992     determined by the MAC Address of the Moblab device.
   1993 
   1994     @returns gsuri to offload test results to.
   1995     """
   1996     # For non-moblab, use results_storage_server or default.
   1997     if not is_moblab():
   1998         return DEFAULT_OFFLOAD_GSURI
   1999 
   2000     # For moblab, use results_storage_server or image_storage_server as bucket
   2001     # name and mac-address/moblab_id as path.
   2002     gsuri = DEFAULT_OFFLOAD_GSURI
   2003     if not gsuri:
   2004         gsuri = "%sresults/" % CONFIG.get_config_value('CROS', 'image_storage_server')
   2005 
   2006     return '%s%s/%s/' % (gsuri, get_moblab_serial_number(), get_moblab_id())
   2007 
   2008 
   2009 # TODO(petermayo): crosbug.com/31826 Share this with _GsUpload in
   2010 # //chromite.git/buildbot/prebuilt.py somewhere/somehow
   2011 def gs_upload(local_file, remote_file, acl, result_dir=None,
   2012               transfer_timeout=300, acl_timeout=300):
   2013     """Upload to GS bucket.
   2014 
   2015     @param local_file: Local file to upload
   2016     @param remote_file: Remote location to upload the local_file to.
   2017     @param acl: name or file used for controlling access to the uploaded
   2018                 file.
   2019     @param result_dir: Result directory if you want to add tracing to the
   2020                        upload.
   2021     @param transfer_timeout: Timeout for this upload call.
   2022     @param acl_timeout: Timeout for the acl call needed to confirm that
   2023                         the uploader has permissions to execute the upload.
   2024 
   2025     @raise CmdError: the exit code of the gsutil call was not 0.
   2026 
   2027     @returns True/False - depending on if the upload succeeded or failed.
   2028     """
   2029     # https://developers.google.com/storage/docs/accesscontrol#extension
   2030     CANNED_ACLS = ['project-private', 'private', 'public-read',
   2031                    'public-read-write', 'authenticated-read',
   2032                    'bucket-owner-read', 'bucket-owner-full-control']
   2033     _GSUTIL_BIN = 'gsutil'
   2034     acl_cmd = None
   2035     if acl in CANNED_ACLS:
   2036         cmd = '%s cp -a %s %s %s' % (_GSUTIL_BIN, acl, local_file, remote_file)
   2037     else:
   2038         # For private uploads we assume that the overlay board is set up
   2039         # properly and a googlestore_acl.xml is present, if not this script
   2040         # errors
   2041         cmd = '%s cp -a private %s %s' % (_GSUTIL_BIN, local_file, remote_file)
   2042         if not os.path.exists(acl):
   2043             logging.error('Unable to find ACL File %s.', acl)
   2044             return False
   2045         acl_cmd = '%s setacl %s %s' % (_GSUTIL_BIN, acl, remote_file)
   2046     if not result_dir:
   2047         run(cmd, timeout=transfer_timeout, verbose=True)
   2048         if acl_cmd:
   2049             run(acl_cmd, timeout=acl_timeout, verbose=True)
   2050         return True
   2051     with open(os.path.join(result_dir, 'tracing'), 'w') as ftrace:
   2052         ftrace.write('Preamble\n')
   2053         run(cmd, timeout=transfer_timeout, verbose=True,
   2054                        stdout_tee=ftrace, stderr_tee=ftrace)
   2055         if acl_cmd:
   2056             ftrace.write('\nACL setting\n')
   2057             # Apply the passed in ACL xml file to the uploaded object.
   2058             run(acl_cmd, timeout=acl_timeout, verbose=True,
   2059                            stdout_tee=ftrace, stderr_tee=ftrace)
   2060         ftrace.write('Postamble\n')
   2061         return True
   2062 
   2063 
   2064 def gs_ls(uri_pattern):
   2065     """Returns a list of URIs that match a given pattern.
   2066 
   2067     @param uri_pattern: a GS URI pattern, may contain wildcards
   2068 
   2069     @return A list of URIs matching the given pattern.
   2070 
   2071     @raise CmdError: the gsutil command failed.
   2072 
   2073     """
   2074     gs_cmd = ' '.join(['gsutil', 'ls', uri_pattern])
   2075     result = system_output(gs_cmd).splitlines()
   2076     return [path.rstrip() for path in result if path]
   2077 
   2078 
   2079 def nuke_pids(pid_list, signal_queue=[signal.SIGTERM, signal.SIGKILL]):
   2080     """
   2081     Given a list of pid's, kill them via an esclating series of signals.
   2082 
   2083     @param pid_list: List of PID's to kill.
   2084     @param signal_queue: Queue of signals to send the PID's to terminate them.
   2085 
   2086     @return: A mapping of the signal name to the number of processes it
   2087         was sent to.
   2088     """
   2089     sig_count = {}
   2090     # Though this is slightly hacky it beats hardcoding names anyday.
   2091     sig_names = dict((k, v) for v, k in signal.__dict__.iteritems()
   2092                      if v.startswith('SIG'))
   2093     for sig in signal_queue:
   2094         logging.debug('Sending signal %s to the following pids:', sig)
   2095         sig_count[sig_names.get(sig, 'unknown_signal')] = len(pid_list)
   2096         for pid in pid_list:
   2097             logging.debug('Pid %d', pid)
   2098             try:
   2099                 os.kill(pid, sig)
   2100             except OSError:
   2101                 # The process may have died from a previous signal before we
   2102                 # could kill it.
   2103                 pass
   2104         if sig == signal.SIGKILL:
   2105             return sig_count
   2106         pid_list = [pid for pid in pid_list if pid_is_alive(pid)]
   2107         if not pid_list:
   2108             break
   2109         time.sleep(CHECK_PID_IS_ALIVE_TIMEOUT)
   2110     failed_list = []
   2111     for pid in pid_list:
   2112         if pid_is_alive(pid):
   2113             failed_list.append('Could not kill %d for process name: %s.' % pid,
   2114                                get_process_name(pid))
   2115     if failed_list:
   2116         raise error.AutoservRunError('Following errors occured: %s' %
   2117                                      failed_list, None)
   2118     return sig_count
   2119 
   2120 
   2121 def externalize_host(host):
   2122     """Returns an externally accessible host name.
   2123 
   2124     @param host: a host name or address (string)
   2125 
   2126     @return An externally visible host name or address
   2127 
   2128     """
   2129     return socket.gethostname() if host in _LOCAL_HOST_LIST else host
   2130 
   2131 
   2132 def urlopen_socket_timeout(url, data=None, timeout=5):
   2133     """
   2134     Wrapper to urllib2.urlopen with a socket timeout.
   2135 
   2136     This method will convert all socket timeouts to
   2137     TimeoutExceptions, so we can use it in conjunction
   2138     with the rpc retry decorator and continue to handle
   2139     other URLErrors as we see fit.
   2140 
   2141     @param url: The url to open.
   2142     @param data: The data to send to the url (eg: the urlencoded dictionary
   2143                  used with a POST call).
   2144     @param timeout: The timeout for this urlopen call.
   2145 
   2146     @return: The response of the urlopen call.
   2147 
   2148     @raises: error.TimeoutException when a socket timeout occurs.
   2149              urllib2.URLError for errors that not caused by timeout.
   2150              urllib2.HTTPError for errors like 404 url not found.
   2151     """
   2152     old_timeout = socket.getdefaulttimeout()
   2153     socket.setdefaulttimeout(timeout)
   2154     try:
   2155         return urllib2.urlopen(url, data=data)
   2156     except urllib2.URLError as e:
   2157         if type(e.reason) is socket.timeout:
   2158             raise error.TimeoutException(str(e))
   2159         raise
   2160     finally:
   2161         socket.setdefaulttimeout(old_timeout)
   2162 
   2163 
   2164 def parse_chrome_version(version_string):
   2165     """
   2166     Parse a chrome version string and return version and milestone.
   2167 
   2168     Given a chrome version of the form "W.X.Y.Z", return "W.X.Y.Z" as
   2169     the version and "W" as the milestone.
   2170 
   2171     @param version_string: Chrome version string.
   2172     @return: a tuple (chrome_version, milestone). If the incoming version
   2173              string is not of the form "W.X.Y.Z", chrome_version will
   2174              be set to the incoming "version_string" argument and the
   2175              milestone will be set to the empty string.
   2176     """
   2177     match = re.search('(\d+)\.\d+\.\d+\.\d+', version_string)
   2178     ver = match.group(0) if match else version_string
   2179     milestone = match.group(1) if match else ''
   2180     return ver, milestone
   2181 
   2182 
   2183 def is_localhost(server):
   2184     """Check if server is equivalent to localhost.
   2185 
   2186     @param server: Name of the server to check.
   2187 
   2188     @return: True if given server is equivalent to localhost.
   2189 
   2190     @raise socket.gaierror: If server name failed to be resolved.
   2191     """
   2192     if server in _LOCAL_HOST_LIST:
   2193         return True
   2194     try:
   2195         return (socket.gethostbyname(socket.gethostname()) ==
   2196                 socket.gethostbyname(server))
   2197     except socket.gaierror:
   2198         logging.error('Failed to resolve server name %s.', server)
   2199         return False
   2200 
   2201 
   2202 def get_function_arg_value(func, arg_name, args, kwargs):
   2203     """Get the value of the given argument for the function.
   2204 
   2205     @param func: Function being called with given arguments.
   2206     @param arg_name: Name of the argument to look for value.
   2207     @param args: arguments for function to be called.
   2208     @param kwargs: keyword arguments for function to be called.
   2209 
   2210     @return: The value of the given argument for the function.
   2211 
   2212     @raise ValueError: If the argument is not listed function arguemnts.
   2213     @raise KeyError: If no value is found for the given argument.
   2214     """
   2215     if arg_name in kwargs:
   2216         return kwargs[arg_name]
   2217 
   2218     argspec = inspect.getargspec(func)
   2219     index = argspec.args.index(arg_name)
   2220     try:
   2221         return args[index]
   2222     except IndexError:
   2223         try:
   2224             # The argument can use a default value. Reverse the default value
   2225             # so argument with default value can be counted from the last to
   2226             # the first.
   2227             return argspec.defaults[::-1][len(argspec.args) - index - 1]
   2228         except IndexError:
   2229             raise KeyError('Argument %s is not given a value. argspec: %s, '
   2230                            'args:%s, kwargs:%s' %
   2231                            (arg_name, argspec, args, kwargs))
   2232 
   2233 
   2234 def has_systemd():
   2235     """Check if the host is running systemd.
   2236 
   2237     @return: True if the host uses systemd, otherwise returns False.
   2238     """
   2239     return os.path.basename(os.readlink('/proc/1/exe')) == 'systemd'
   2240 
   2241 
   2242 def version_match(build_version, release_version, update_url=''):
   2243     """Compare release version from lsb-release with cros-version label.
   2244 
   2245     build_version is a string based on build name. It is prefixed with builder
   2246     info and branch ID, e.g., lumpy-release/R43-6809.0.0. It may not include
   2247     builder info, e.g., lumpy-release, in which case, update_url shall be passed
   2248     in to determine if the build is a trybot or pgo-generate build.
   2249     release_version is retrieved from lsb-release.
   2250     These two values might not match exactly.
   2251 
   2252     The method is designed to compare version for following 6 scenarios with
   2253     samples of build version and expected release version:
   2254     1. trybot non-release build (paladin, pre-cq or test-ap build).
   2255     build version:   trybot-lumpy-paladin/R27-3837.0.0-b123
   2256     release version: 3837.0.2013_03_21_1340
   2257 
   2258     2. trybot release build.
   2259     build version:   trybot-lumpy-release/R27-3837.0.0-b456
   2260     release version: 3837.0.0
   2261 
   2262     3. buildbot official release build.
   2263     build version:   lumpy-release/R27-3837.0.0
   2264     release version: 3837.0.0
   2265 
   2266     4. non-official paladin rc build.
   2267     build version:   lumpy-paladin/R27-3878.0.0-rc7
   2268     release version: 3837.0.0-rc7
   2269 
   2270     5. chrome-perf build.
   2271     build version:   lumpy-chrome-perf/R28-3837.0.0-b2996
   2272     release version: 3837.0.0
   2273 
   2274     6. pgo-generate build.
   2275     build version:   lumpy-release-pgo-generate/R28-3837.0.0-b2996
   2276     release version: 3837.0.0-pgo-generate
   2277 
   2278     7. build version with --cheetsth suffix.
   2279     build version:   lumpy-release/R28-3837.0.0-cheetsth
   2280     release version: 3837.0.0
   2281 
   2282     TODO: This logic has a bug if a trybot paladin build failed to be
   2283     installed in a DUT running an older trybot paladin build with same
   2284     platform number, but different build number (-b###). So to conclusively
   2285     determine if a tryjob paladin build is imaged successfully, we may need
   2286     to find out the date string from update url.
   2287 
   2288     @param build_version: Build name for cros version, e.g.
   2289                           peppy-release/R43-6809.0.0 or R43-6809.0.0
   2290     @param release_version: Release version retrieved from lsb-release,
   2291                             e.g., 6809.0.0
   2292     @param update_url: Update url which include the full builder information.
   2293                        Default is set to empty string.
   2294 
   2295     @return: True if the values match, otherwise returns False.
   2296     """
   2297     # If the build is from release, CQ or PFQ builder, cros-version label must
   2298     # be ended with release version in lsb-release.
   2299     if (build_version.endswith(release_version) or
   2300             build_version.endswith(release_version + '-cheetsth')):
   2301         return True
   2302 
   2303     if build_version.endswith('-cheetsth'):
   2304         build_version = re.sub('-cheetsth' + '$', '', build_version)
   2305 
   2306     # Remove R#- and -b# at the end of build version
   2307     stripped_version = re.sub(r'(R\d+-|-b\d+)', '', build_version)
   2308     # Trim the builder info, e.g., trybot-lumpy-paladin/
   2309     stripped_version = stripped_version.split('/')[-1]
   2310 
   2311     is_trybot_non_release_build = (
   2312             re.match(r'.*trybot-.+-(paladin|pre-cq|test-ap|toolchain)',
   2313                      build_version) or
   2314             re.match(r'.*trybot-.+-(paladin|pre-cq|test-ap|toolchain)',
   2315                      update_url))
   2316 
   2317     # Replace date string with 0 in release_version
   2318     release_version_no_date = re.sub(r'\d{4}_\d{2}_\d{2}_\d+', '0',
   2319                                     release_version)
   2320     has_date_string = release_version != release_version_no_date
   2321 
   2322     is_pgo_generate_build = (
   2323             re.match(r'.+-pgo-generate', build_version) or
   2324             re.match(r'.+-pgo-generate', update_url))
   2325 
   2326     # Remove |-pgo-generate| in release_version
   2327     release_version_no_pgo = release_version.replace('-pgo-generate', '')
   2328     has_pgo_generate = release_version != release_version_no_pgo
   2329 
   2330     if is_trybot_non_release_build:
   2331         if not has_date_string:
   2332             logging.error('A trybot paladin or pre-cq build is expected. '
   2333                           'Version "%s" is not a paladin or pre-cq  build.',
   2334                           release_version)
   2335             return False
   2336         return stripped_version == release_version_no_date
   2337     elif is_pgo_generate_build:
   2338         if not has_pgo_generate:
   2339             logging.error('A pgo-generate build is expected. Version '
   2340                           '"%s" is not a pgo-generate build.',
   2341                           release_version)
   2342             return False
   2343         return stripped_version == release_version_no_pgo
   2344     else:
   2345         if has_date_string:
   2346             logging.error('Unexpected date found in a non trybot paladin or '
   2347                           'pre-cq build.')
   2348             return False
   2349         # Versioned build, i.e., rc or release build.
   2350         return stripped_version == release_version
   2351 
   2352 
   2353 def get_real_user():
   2354     """Get the real user that runs the script.
   2355 
   2356     The function check environment variable SUDO_USER for the user if the
   2357     script is run with sudo. Otherwise, it returns the value of environment
   2358     variable USER.
   2359 
   2360     @return: The user name that runs the script.
   2361 
   2362     """
   2363     user = os.environ.get('SUDO_USER')
   2364     if not user:
   2365         user = os.environ.get('USER')
   2366     return user
   2367 
   2368 
   2369 def get_service_pid(service_name):
   2370     """Return pid of service.
   2371 
   2372     @param service_name: string name of service.
   2373 
   2374     @return: pid or 0 if service is not running.
   2375     """
   2376     if has_systemd():
   2377         # systemctl show prints 'MainPID=0' if the service is not running.
   2378         cmd_result = run('systemctl show -p MainPID %s' %
   2379                                     service_name, ignore_status=True)
   2380         return int(cmd_result.stdout.split('=')[1])
   2381     else:
   2382         cmd_result = run('status %s' % service_name,
   2383                                         ignore_status=True)
   2384         if 'start/running' in cmd_result.stdout:
   2385             return int(cmd_result.stdout.split()[3])
   2386         return 0
   2387 
   2388 
   2389 def control_service(service_name, action='start', ignore_status=True):
   2390     """Controls a service. It can be used to start, stop or restart
   2391     a service.
   2392 
   2393     @param service_name: string service to be restarted.
   2394 
   2395     @param action: string choice of action to control command.
   2396 
   2397     @param ignore_status: boolean ignore if system command fails.
   2398 
   2399     @return: status code of the executed command.
   2400     """
   2401     if action not in ('start', 'stop', 'restart'):
   2402         raise ValueError('Unknown action supplied as parameter.')
   2403 
   2404     control_cmd = action + ' ' + service_name
   2405     if has_systemd():
   2406         control_cmd = 'systemctl ' + control_cmd
   2407     return system(control_cmd, ignore_status=ignore_status)
   2408 
   2409 
   2410 def restart_service(service_name, ignore_status=True):
   2411     """Restarts a service
   2412 
   2413     @param service_name: string service to be restarted.
   2414 
   2415     @param ignore_status: boolean ignore if system command fails.
   2416 
   2417     @return: status code of the executed command.
   2418     """
   2419     return control_service(service_name, action='restart', ignore_status=ignore_status)
   2420 
   2421 
   2422 def start_service(service_name, ignore_status=True):
   2423     """Starts a service
   2424 
   2425     @param service_name: string service to be started.
   2426 
   2427     @param ignore_status: boolean ignore if system command fails.
   2428 
   2429     @return: status code of the executed command.
   2430     """
   2431     return control_service(service_name, action='start', ignore_status=ignore_status)
   2432 
   2433 
   2434 def stop_service(service_name, ignore_status=True):
   2435     """Stops a service
   2436 
   2437     @param service_name: string service to be stopped.
   2438 
   2439     @param ignore_status: boolean ignore if system command fails.
   2440 
   2441     @return: status code of the executed command.
   2442     """
   2443     return control_service(service_name, action='stop', ignore_status=ignore_status)
   2444 
   2445 
   2446 def sudo_require_password():
   2447     """Test if the process can run sudo command without using password.
   2448 
   2449     @return: True if the process needs password to run sudo command.
   2450 
   2451     """
   2452     try:
   2453         run('sudo -n true')
   2454         return False
   2455     except error.CmdError:
   2456         logging.warn('sudo command requires password.')
   2457         return True
   2458 
   2459 
   2460 def is_in_container():
   2461     """Check if the process is running inside a container.
   2462 
   2463     @return: True if the process is running inside a container, otherwise False.
   2464     """
   2465     result = run('grep -q "/lxc/" /proc/1/cgroup',
   2466                             verbose=False, ignore_status=True)
   2467     if result.exit_status == 0:
   2468         return True
   2469 
   2470     # Check "container" environment variable for lxd/lxc containers.
   2471     if os.environ.get('container') == 'lxc':
   2472         return True
   2473 
   2474     return False
   2475 
   2476 
   2477 def is_flash_installed():
   2478     """
   2479     The Adobe Flash binary is only distributed with internal builds.
   2480     """
   2481     return (os.path.exists('/opt/google/chrome/pepper/libpepflashplayer.so')
   2482         and os.path.exists('/opt/google/chrome/pepper/pepper-flash.info'))
   2483 
   2484 
   2485 def verify_flash_installed():
   2486     """
   2487     The Adobe Flash binary is only distributed with internal builds.
   2488     Warn users of public builds of the extra dependency.
   2489     """
   2490     if not is_flash_installed():
   2491         raise error.TestNAError('No Adobe Flash binary installed.')
   2492 
   2493 
   2494 def is_in_same_subnet(ip_1, ip_2, mask_bits=24):
   2495     """Check if two IP addresses are in the same subnet with given mask bits.
   2496 
   2497     The two IP addresses are string of IPv4, e.g., '192.168.0.3'.
   2498 
   2499     @param ip_1: First IP address to compare.
   2500     @param ip_2: Second IP address to compare.
   2501     @param mask_bits: Number of mask bits for subnet comparison. Default to 24.
   2502 
   2503     @return: True if the two IP addresses are in the same subnet.
   2504 
   2505     """
   2506     mask = ((2L<<mask_bits-1) -1)<<(32-mask_bits)
   2507     ip_1_num = struct.unpack('!I', socket.inet_aton(ip_1))[0]
   2508     ip_2_num = struct.unpack('!I', socket.inet_aton(ip_2))[0]
   2509     return ip_1_num & mask == ip_2_num & mask
   2510 
   2511 
   2512 def get_ip_address(hostname):
   2513     """Get the IP address of given hostname.
   2514 
   2515     @param hostname: Hostname of a DUT.
   2516 
   2517     @return: The IP address of given hostname. None if failed to resolve
   2518              hostname.
   2519     """
   2520     try:
   2521         if hostname:
   2522             return socket.gethostbyname(hostname)
   2523     except socket.gaierror as e:
   2524         logging.error('Failed to get IP address of %s, error: %s.', hostname, e)
   2525 
   2526 
   2527 def get_servers_in_same_subnet(host_ip, mask_bits, servers=None,
   2528                                server_ip_map=None):
   2529     """Get the servers in the same subnet of the given host ip.
   2530 
   2531     @param host_ip: The IP address of a dut to look for devserver.
   2532     @param mask_bits: Number of mask bits.
   2533     @param servers: A list of servers to be filtered by subnet specified by
   2534                     host_ip and mask_bits.
   2535     @param server_ip_map: A map between the server name and its IP address.
   2536             The map can be pre-built for better performance, e.g., when
   2537             allocating a drone for an agent task.
   2538 
   2539     @return: A list of servers in the same subnet of the given host ip.
   2540 
   2541     """
   2542     matched_servers = []
   2543     if not servers and not server_ip_map:
   2544         raise ValueError('Either `servers` or `server_ip_map` must be given.')
   2545     if not servers:
   2546         servers = server_ip_map.keys()
   2547     # Make sure server_ip_map is an empty dict if it's not set.
   2548     if not server_ip_map:
   2549         server_ip_map = {}
   2550     for server in servers:
   2551         server_ip = server_ip_map.get(server, get_ip_address(server))
   2552         if server_ip and is_in_same_subnet(server_ip, host_ip, mask_bits):
   2553             matched_servers.append(server)
   2554     return matched_servers
   2555 
   2556 
   2557 def get_restricted_subnet(hostname, restricted_subnets=RESTRICTED_SUBNETS):
   2558     """Get the restricted subnet of given hostname.
   2559 
   2560     @param hostname: Name of the host to look for matched restricted subnet.
   2561     @param restricted_subnets: A list of restricted subnets, default is set to
   2562             RESTRICTED_SUBNETS.
   2563 
   2564     @return: A tuple of (subnet_ip, mask_bits), which defines a restricted
   2565              subnet.
   2566     """
   2567     host_ip = get_ip_address(hostname)
   2568     if not host_ip:
   2569         return
   2570     for subnet_ip, mask_bits in restricted_subnets:
   2571         if is_in_same_subnet(subnet_ip, host_ip, mask_bits):
   2572             return subnet_ip, mask_bits
   2573 
   2574 
   2575 def get_wireless_ssid(hostname):
   2576     """Get the wireless ssid based on given hostname.
   2577 
   2578     The method tries to locate the wireless ssid in the same subnet of given
   2579     hostname first. If none is found, it returns the default setting in
   2580     CLIENT/wireless_ssid.
   2581 
   2582     @param hostname: Hostname of the test device.
   2583 
   2584     @return: wireless ssid for the test device.
   2585     """
   2586     default_ssid = CONFIG.get_config_value('CLIENT', 'wireless_ssid',
   2587                                            default=None)
   2588     host_ip = get_ip_address(hostname)
   2589     if not host_ip:
   2590         return default_ssid
   2591 
   2592     # Get all wireless ssid in the global config.
   2593     ssids = CONFIG.get_config_value_regex('CLIENT', WIRELESS_SSID_PATTERN)
   2594 
   2595     # There could be multiple subnet matches, pick the one with most strict
   2596     # match, i.e., the one with highest maskbit.
   2597     matched_ssid = default_ssid
   2598     matched_maskbit = -1
   2599     for key, value in ssids.items():
   2600         # The config key filtered by regex WIRELESS_SSID_PATTERN has a format of
   2601         # wireless_ssid_[subnet_ip]/[maskbit], for example:
   2602         # wireless_ssid_192.168.0.1/24
   2603         # Following line extract the subnet ip and mask bit from the key name.
   2604         match = re.match(WIRELESS_SSID_PATTERN, key)
   2605         subnet_ip, maskbit = match.groups()
   2606         maskbit = int(maskbit)
   2607         if (is_in_same_subnet(subnet_ip, host_ip, maskbit) and
   2608             maskbit > matched_maskbit):
   2609             matched_ssid = value
   2610             matched_maskbit = maskbit
   2611     return matched_ssid
   2612 
   2613 
   2614 def parse_launch_control_build(build_name):
   2615     """Get branch, target, build_id from the given Launch Control build_name.
   2616 
   2617     @param build_name: Name of a Launch Control build, should be formated as
   2618                        branch/target/build_id
   2619 
   2620     @return: Tuple of branch, target, build_id
   2621     @raise ValueError: If the build_name is not correctly formated.
   2622     """
   2623     branch, target, build_id = build_name.split('/')
   2624     return branch, target, build_id
   2625 
   2626 
   2627 def parse_android_target(target):
   2628     """Get board and build type from the given target.
   2629 
   2630     @param target: Name of an Android build target, e.g., shamu-eng.
   2631 
   2632     @return: Tuple of board, build_type
   2633     @raise ValueError: If the target is not correctly formated.
   2634     """
   2635     board, build_type = target.split('-')
   2636     return board, build_type
   2637 
   2638 
   2639 def parse_launch_control_target(target):
   2640     """Parse the build target and type from a Launch Control target.
   2641 
   2642     The Launch Control target has the format of build_target-build_type, e.g.,
   2643     shamu-eng or dragonboard-userdebug. This method extracts the build target
   2644     and type from the target name.
   2645 
   2646     @param target: Name of a Launch Control target, e.g., shamu-eng.
   2647 
   2648     @return: (build_target, build_type), e.g., ('shamu', 'userdebug')
   2649     """
   2650     match = re.match('(?P<build_target>.+)-(?P<build_type>[^-]+)', target)
   2651     if match:
   2652         return match.group('build_target'), match.group('build_type')
   2653     else:
   2654         return None, None
   2655 
   2656 
   2657 def is_launch_control_build(build):
   2658     """Check if a given build is a Launch Control build.
   2659 
   2660     @param build: Name of a build, e.g.,
   2661                   ChromeOS build: daisy-release/R50-1234.0.0
   2662                   Launch Control build: git_mnc_release/shamu-eng
   2663 
   2664     @return: True if the build name matches the pattern of a Launch Control
   2665              build, False otherwise.
   2666     """
   2667     try:
   2668         _, target, _ = parse_launch_control_build(build)
   2669         build_target, _ = parse_launch_control_target(target)
   2670         if build_target:
   2671             return True
   2672     except ValueError:
   2673         # parse_launch_control_build or parse_launch_control_target failed.
   2674         pass
   2675     return False
   2676 
   2677 
   2678 def which(exec_file):
   2679     """Finds an executable file.
   2680 
   2681     If the file name contains a path component, it is checked as-is.
   2682     Otherwise, we check with each of the path components found in the system
   2683     PATH prepended. This behavior is similar to the 'which' command-line tool.
   2684 
   2685     @param exec_file: Name or path to desired executable.
   2686 
   2687     @return: An actual path to the executable, or None if not found.
   2688     """
   2689     if os.path.dirname(exec_file):
   2690         return exec_file if os.access(exec_file, os.X_OK) else None
   2691     sys_path = os.environ.get('PATH')
   2692     prefix_list = sys_path.split(os.pathsep) if sys_path else []
   2693     for prefix in prefix_list:
   2694         path = os.path.join(prefix, exec_file)
   2695         if os.access(path, os.X_OK):
   2696             return path
   2697 
   2698 
   2699 class TimeoutError(error.TestError):
   2700     """Error raised when we time out when waiting on a condition."""
   2701     pass
   2702 
   2703 
   2704 def poll_for_condition(condition,
   2705                        exception=None,
   2706                        timeout=10,
   2707                        sleep_interval=0.1,
   2708                        desc=None):
   2709     """Polls until a condition becomes true.
   2710 
   2711     @param condition: function taking no args and returning bool
   2712     @param exception: exception to throw if condition doesn't become true
   2713     @param timeout: maximum number of seconds to wait
   2714     @param sleep_interval: time to sleep between polls
   2715     @param desc: description of default TimeoutError used if 'exception' is
   2716                  None
   2717 
   2718     @return The true value that caused the poll loop to terminate.
   2719 
   2720     @raise 'exception' arg if supplied; TimeoutError otherwise
   2721     """
   2722     start_time = time.time()
   2723     while True:
   2724         value = condition()
   2725         if value:
   2726             return value
   2727         if time.time() + sleep_interval - start_time > timeout:
   2728             if exception:
   2729                 logging.error('Will raise error %r due to unexpected return: '
   2730                               '%r', exception, value)
   2731                 raise exception
   2732 
   2733             if desc:
   2734                 desc = 'Timed out waiting for condition: ' + desc
   2735             else:
   2736                 desc = 'Timed out waiting for unnamed condition'
   2737             logging.error(desc)
   2738             raise TimeoutError(desc)
   2739 
   2740         time.sleep(sleep_interval)
   2741 
   2742 
   2743 class metrics_mock(metrics_mock_class.mock_class_base):
   2744     """mock class for metrics in case chromite is not installed."""
   2745     pass
   2746