Home | History | Annotate | Download | only in server
      1 # Copyright 2007 Google Inc. Released under the GPL v2
      2 #pylint: disable-msg=C0111
      3 
      4 import glob
      5 import logging
      6 import os
      7 import re
      8 import sys
      9 import tempfile
     10 import time
     11 import traceback
     12 
     13 import common
     14 from autotest_lib.client.bin.result_tools import runner as result_tools_runner
     15 from autotest_lib.client.common_lib import autotemp
     16 from autotest_lib.client.common_lib import base_job
     17 from autotest_lib.client.common_lib import error
     18 from autotest_lib.client.common_lib import global_config
     19 from autotest_lib.client.common_lib import packages
     20 from autotest_lib.client.common_lib import utils as client_utils
     21 from autotest_lib.server import installable_object
     22 from autotest_lib.server import utils
     23 from autotest_lib.server import utils as server_utils
     24 from autotest_lib.server.cros.dynamic_suite.constants import JOB_REPO_URL
     25 
     26 
     27 try:
     28     from chromite.lib import metrics
     29 except ImportError:
     30     metrics = client_utils.metrics_mock
     31 
     32 
     33 AUTOTEST_SVN = 'svn://test.kernel.org/autotest/trunk/client'
     34 AUTOTEST_HTTP = 'http://test.kernel.org/svn/autotest/trunk/client'
     35 
     36 _CONFIG = global_config.global_config
     37 AUTOSERV_PREBUILD = _CONFIG.get_config_value(
     38         'AUTOSERV', 'enable_server_prebuild', type=bool, default=False)
     39 
     40 # Match on a line like this:
     41 # FAIL test_name  test_name timestamp=1 localtime=Nov 15 12:43:10 <fail_msg>
     42 _FAIL_STATUS_RE = re.compile(
     43     r'\s*FAIL.*localtime=.*\s*.*\s*[0-9]+:[0-9]+:[0-9]+\s*(?P<fail_msg>.*)')
     44 
     45 
     46 class AutodirNotFoundError(Exception):
     47     """No Autotest installation could be found."""
     48 
     49 
     50 class AutotestFailure(Exception):
     51     """Gereric exception class for failures during a test run."""
     52 
     53 
     54 class AutotestAbort(AutotestFailure):
     55     """
     56     AutotestAborts are thrown when the DUT seems fine,
     57     and the test doesn't give us an explicit reason for
     58     failure; In this case we have no choice but to abort.
     59     """
     60 
     61 
     62 class AutotestDeviceError(AutotestFailure):
     63     """
     64     Exceptions that inherit from AutotestDeviceError
     65     are thrown when we can determine the current
     66     state of the DUT and conclude that it probably
     67     lead to the test failing; these exceptions lead
     68     to failures instead of aborts.
     69     """
     70 
     71 
     72 class AutotestDeviceNotPingable(AutotestDeviceError):
     73     """Error for when a DUT becomes unpingable."""
     74 
     75 
     76 class AutotestDeviceNotSSHable(AutotestDeviceError):
     77     """Error for when a DUT is pingable but not SSHable."""
     78 
     79 
     80 class AutotestDeviceRebooted(AutotestDeviceError):
     81     """Error for when a DUT rebooted unexpectedly."""
     82 
     83 
     84 class Autotest(installable_object.InstallableObject):
     85     """
     86     This class represents the Autotest program.
     87 
     88     Autotest is used to run tests automatically and collect the results.
     89     It also supports profilers.
     90 
     91     Implementation details:
     92     This is a leaf class in an abstract class hierarchy, it must
     93     implement the unimplemented methods in parent classes.
     94     """
     95 
     96     def __init__(self, host=None):
     97         self.host = host
     98         self.got = False
     99         self.installed = False
    100         self.serverdir = utils.get_server_dir()
    101         super(Autotest, self).__init__()
    102 
    103 
    104     install_in_tmpdir = False
    105     @classmethod
    106     def set_install_in_tmpdir(cls, flag):
    107         """ Sets a flag that controls whether or not Autotest should by
    108         default be installed in a "standard" directory (e.g.
    109         /home/autotest, /usr/local/autotest) or a temporary directory. """
    110         cls.install_in_tmpdir = flag
    111 
    112 
    113     @classmethod
    114     def get_client_autodir_paths(cls, host):
    115         return global_config.global_config.get_config_value(
    116                 'AUTOSERV', 'client_autodir_paths', type=list)
    117 
    118 
    119     @classmethod
    120     def get_installed_autodir(cls, host):
    121         """
    122         Find where the Autotest client is installed on the host.
    123         @returns an absolute path to an installed Autotest client root.
    124         @raises AutodirNotFoundError if no Autotest installation can be found.
    125         """
    126         autodir = host.get_autodir()
    127         if autodir:
    128             logging.debug('Using existing host autodir: %s', autodir)
    129             return autodir
    130 
    131         for path in Autotest.get_client_autodir_paths(host):
    132             try:
    133                 autotest_binary = os.path.join(path, 'bin', 'autotest')
    134                 host.run('test -x %s' % utils.sh_escape(autotest_binary))
    135                 host.run('test -w %s' % utils.sh_escape(path))
    136                 logging.debug('Found existing autodir at %s', path)
    137                 return path
    138             except error.GenericHostRunError:
    139                 logging.debug('%s does not exist on %s', autotest_binary,
    140                               host.hostname)
    141         raise AutodirNotFoundError
    142 
    143 
    144     @classmethod
    145     def get_install_dir(cls, host):
    146         """
    147         Determines the location where autotest should be installed on
    148         host. If self.install_in_tmpdir is set, it will return a unique
    149         temporary directory that autotest can be installed in. Otherwise, looks
    150         for an existing installation to use; if none is found, looks for a
    151         usable directory in the global config client_autodir_paths.
    152         """
    153         try:
    154             install_dir = cls.get_installed_autodir(host)
    155         except AutodirNotFoundError:
    156             install_dir = cls._find_installable_dir(host)
    157 
    158         if cls.install_in_tmpdir:
    159             return host.get_tmp_dir(parent=install_dir)
    160         return install_dir
    161 
    162 
    163     @classmethod
    164     def _find_installable_dir(cls, host):
    165         client_autodir_paths = cls.get_client_autodir_paths(host)
    166         for path in client_autodir_paths:
    167             try:
    168                 host.run('mkdir -p %s' % utils.sh_escape(path))
    169                 host.run('test -w %s' % utils.sh_escape(path))
    170                 return path
    171             except error.AutoservRunError:
    172                 logging.debug('Failed to create %s', path)
    173         metrics.Counter(
    174             'chromeos/autotest/errors/no_autotest_install_path').increment(
    175                 fields={'dut_host_name': host.hostname})
    176         raise error.AutoservInstallError(
    177                 'Unable to find a place to install Autotest; tried %s' %
    178                 ', '.join(client_autodir_paths))
    179 
    180 
    181     def get_fetch_location(self):
    182         """Generate list of locations where autotest can look for packages.
    183 
    184         Hosts are tagged with an attribute containing the URL from which
    185         to source packages when running a test on that host.
    186 
    187         @returns the list of candidate locations to check for packages.
    188         """
    189         c = global_config.global_config
    190         repos = c.get_config_value("PACKAGES", 'fetch_location', type=list,
    191                                    default=[])
    192         repos.reverse()
    193 
    194         if not server_utils.is_inside_chroot():
    195             # Only try to get fetch location from host attribute if the
    196             # test is not running inside chroot.
    197             #
    198             # Look for the repo url via the host attribute. If we are
    199             # not running with a full AFE autoserv will fall back to
    200             # serving packages itself from whatever source version it is
    201             # sync'd to rather than using the proper artifacts for the
    202             # build on the host.
    203             found_repo = self._get_fetch_location_from_host_attribute()
    204             if found_repo is not None:
    205                 # Add our new repo to the end, the package manager will
    206                 # later reverse the list of repositories resulting in ours
    207                 # being first
    208                 repos.append(found_repo)
    209 
    210         return repos
    211 
    212 
    213     def _get_fetch_location_from_host_attribute(self):
    214         """Get repo to use for packages from host attribute, if possible.
    215 
    216         Hosts are tagged with an attribute containing the URL
    217         from which to source packages when running a test on that host.
    218         If self.host is set, attempt to look this attribute in the host info.
    219 
    220         @returns value of the 'job_repo_url' host attribute, if present.
    221         """
    222         if not self.host:
    223             return None
    224 
    225         try:
    226             info = self.host.host_info_store.get()
    227         except Exception as e:
    228             # TODO(pprabhu): We really want to catch host_info.StoreError here,
    229             # but we can't import host_info from this module.
    230             #   - autotest_lib.hosts.host_info pulls in (naturally)
    231             #   autotest_lib.hosts.__init__
    232             #   - This pulls in all the host classes ever defined
    233             #   - That includes abstract_ssh, which depends on autotest
    234             logging.warning('Failed to obtain host info: %r', e)
    235             logging.warning('Skipping autotest fetch location based on %s',
    236                             JOB_REPO_URL)
    237             return None
    238 
    239         job_repo_url = info.attributes.get(JOB_REPO_URL, '')
    240         if not job_repo_url:
    241             logging.warning("No %s for %s", JOB_REPO_URL, self.host)
    242             return None
    243 
    244         logging.info('Got job repo url from host attributes: %s',
    245                         job_repo_url)
    246         return job_repo_url
    247 
    248 
    249     def install(self, host=None, autodir=None, use_packaging=True):
    250         """Install autotest.  If |host| is not None, stores it in |self.host|.
    251 
    252         @param host A Host instance on which autotest will be installed
    253         @param autodir Location on the remote host to install to
    254         @param use_packaging Enable install modes that use the packaging system.
    255 
    256         """
    257         if host:
    258             self.host = host
    259         self._install(host=host, autodir=autodir, use_packaging=use_packaging)
    260 
    261 
    262     def install_full_client(self, host=None, autodir=None):
    263         self._install(host=host, autodir=autodir, use_autoserv=False,
    264                       use_packaging=False)
    265 
    266 
    267     def install_no_autoserv(self, host=None, autodir=None):
    268         self._install(host=host, autodir=autodir, use_autoserv=False)
    269 
    270 
    271     def _install_using_packaging(self, host, autodir):
    272         repos = self.get_fetch_location()
    273         if not repos:
    274             raise error.PackageInstallError("No repos to install an "
    275                                             "autotest client from")
    276         # Make sure devserver has the autotest package staged
    277         host.verify_job_repo_url()
    278         pkgmgr = packages.PackageManager(autodir, hostname=host.hostname,
    279                                          repo_urls=repos,
    280                                          do_locking=False,
    281                                          run_function=host.run,
    282                                          run_function_dargs=dict(timeout=600))
    283         # The packages dir is used to store all the packages that
    284         # are fetched on that client. (for the tests,deps etc.
    285         # too apart from the client)
    286         pkg_dir = os.path.join(autodir, 'packages')
    287         # clean up the autodir except for the packages and result_tools
    288         # directory.
    289         host.run('cd %s && ls | grep -v "^packages$" | grep -v "^result_tools$"'
    290                  ' | xargs rm -rf && rm -rf .[!.]*' % autodir)
    291         pkgmgr.install_pkg('autotest', 'client', pkg_dir, autodir,
    292                            preserve_install_dir=True)
    293         self.installed = True
    294 
    295 
    296     def _install_using_send_file(self, host, autodir):
    297         dirs_to_exclude = set(["tests", "site_tests", "deps", "profilers",
    298                                "packages"])
    299         light_files = [os.path.join(self.source_material, f)
    300                        for f in os.listdir(self.source_material)
    301                        if f not in dirs_to_exclude]
    302         host.send_file(light_files, autodir, delete_dest=True)
    303 
    304         # create empty dirs for all the stuff we excluded
    305         commands = []
    306         for path in dirs_to_exclude:
    307             abs_path = os.path.join(autodir, path)
    308             abs_path = utils.sh_escape(abs_path)
    309             commands.append("mkdir -p '%s'" % abs_path)
    310             commands.append("touch '%s'/__init__.py" % abs_path)
    311         host.run(';'.join(commands))
    312 
    313 
    314     def _install(self, host=None, autodir=None, use_autoserv=True,
    315                  use_packaging=True):
    316         """
    317         Install autotest.  If get() was not called previously, an
    318         attempt will be made to install from the autotest svn
    319         repository.
    320 
    321         @param host A Host instance on which autotest will be installed
    322         @param autodir Location on the remote host to install to
    323         @param use_autoserv Enable install modes that depend on the client
    324             running with the autoserv harness
    325         @param use_packaging Enable install modes that use the packaging system
    326 
    327         @exception AutoservError if a tarball was not specified and
    328             the target host does not have svn installed in its path
    329         """
    330         if not host:
    331             host = self.host
    332         if not self.got:
    333             self.get()
    334         host.wait_up(timeout=30)
    335         host.setup()
    336         logging.info("Installing autotest on %s", host.hostname)
    337 
    338         # set up the autotest directory on the remote machine
    339         if not autodir:
    340             autodir = self.get_install_dir(host)
    341         logging.info('Using installation dir %s', autodir)
    342         host.set_autodir(autodir)
    343         host.run('mkdir -p %s' % utils.sh_escape(autodir))
    344 
    345         # make sure there are no files in $AUTODIR/results
    346         results_path = os.path.join(autodir, 'results')
    347         host.run('rm -rf %s/*' % utils.sh_escape(results_path),
    348                  ignore_status=True)
    349 
    350         # Fetch the autotest client from the nearest repository
    351         if use_packaging:
    352             try:
    353                 self._install_using_packaging(host, autodir)
    354                 logging.info("Installation of autotest completed using the "
    355                              "packaging system.")
    356                 return
    357             except (error.PackageInstallError, error.AutoservRunError,
    358                     global_config.ConfigError), e:
    359                 logging.info("Could not install autotest using the packaging "
    360                              "system: %s. Trying other methods", e)
    361         else:
    362             # Delete the package checksum file to force dut updating local
    363             # packages.
    364             command = ('rm -f "%s"' %
    365                        (os.path.join(autodir, packages.CHECKSUM_FILE)))
    366             host.run(command)
    367 
    368         # try to install from file or directory
    369         if self.source_material:
    370             c = global_config.global_config
    371             supports_autoserv_packaging = c.get_config_value(
    372                 "PACKAGES", "serve_packages_from_autoserv", type=bool)
    373             # Copy autotest recursively
    374             if supports_autoserv_packaging and use_autoserv:
    375                 self._install_using_send_file(host, autodir)
    376             else:
    377                 host.send_file(self.source_material, autodir, delete_dest=True)
    378             logging.info("Installation of autotest completed from %s",
    379                          self.source_material)
    380             self.installed = True
    381         else:
    382             # if that fails try to install using svn
    383             if utils.run('which svn').exit_status:
    384                 raise error.AutoservError(
    385                         'svn not found on target machine: %s' %
    386                         host.hostname)
    387             try:
    388                 host.run('svn checkout %s %s' % (AUTOTEST_SVN, autodir))
    389             except error.AutoservRunError, e:
    390                 host.run('svn checkout %s %s' % (AUTOTEST_HTTP, autodir))
    391             logging.info("Installation of autotest completed using SVN.")
    392             self.installed = True
    393 
    394         # TODO(milleral): http://crbug.com/258161
    395         # Send over the most recent global_config.ini after installation if one
    396         # is available.
    397         # This code is a bit duplicated from
    398         # _Run._create_client_config_file, but oh well.
    399         if self.installed and self.source_material:
    400             self._send_shadow_config()
    401 
    402     def _send_shadow_config(self):
    403         logging.info('Installing updated global_config.ini.')
    404         destination = os.path.join(self.host.get_autodir(),
    405                                    'global_config.ini')
    406         with tempfile.NamedTemporaryFile() as client_config:
    407             config = global_config.global_config
    408             client_section = config.get_section_values('CLIENT')
    409             client_section.write(client_config)
    410             client_config.flush()
    411             self.host.send_file(client_config.name, destination)
    412 
    413 
    414     def uninstall(self, host=None):
    415         """
    416         Uninstall (i.e. delete) autotest. Removes the autotest client install
    417         from the specified host.
    418 
    419         @params host a Host instance from which the client will be removed
    420         """
    421         if not self.installed:
    422             return
    423         if not host:
    424             host = self.host
    425         autodir = host.get_autodir()
    426         if not autodir:
    427             return
    428 
    429         # perform the actual uninstall
    430         host.run("rm -rf %s" % utils.sh_escape(autodir), ignore_status=True)
    431         host.set_autodir(None)
    432         self.installed = False
    433 
    434 
    435     def get(self, location=None):
    436         if not location:
    437             location = os.path.join(self.serverdir, '../client')
    438             location = os.path.abspath(location)
    439         installable_object.InstallableObject.get(self, location)
    440         self.got = True
    441 
    442 
    443     def run(self, control_file, results_dir='.', host=None, timeout=None,
    444             tag=None, parallel_flag=False, background=False,
    445             client_disconnect_timeout=None, use_packaging=True):
    446         """
    447         Run an autotest job on the remote machine.
    448 
    449         @param control_file: An open file-like-obj of the control file.
    450         @param results_dir: A str path where the results should be stored
    451                 on the local filesystem.
    452         @param host: A Host instance on which the control file should
    453                 be run.
    454         @param timeout: Maximum number of seconds to wait for the run or None.
    455         @param tag: Tag name for the client side instance of autotest.
    456         @param parallel_flag: Flag set when multiple jobs are run at the
    457                 same time.
    458         @param background: Indicates that the client should be launched as
    459                 a background job; the code calling run will be responsible
    460                 for monitoring the client and collecting the results.
    461         @param client_disconnect_timeout: Seconds to wait for the remote host
    462                 to come back after a reboot. Defaults to the host setting for
    463                 DEFAULT_REBOOT_TIMEOUT.
    464 
    465         @raises AutotestRunError: If there is a problem executing
    466                 the control file.
    467         """
    468         host = self._get_host_and_setup(host, use_packaging=use_packaging)
    469         logging.debug('Autotest job starts on remote host: %s',
    470                       host.hostname)
    471         results_dir = os.path.abspath(results_dir)
    472 
    473         if client_disconnect_timeout is None:
    474             client_disconnect_timeout = host.DEFAULT_REBOOT_TIMEOUT
    475 
    476         if tag:
    477             results_dir = os.path.join(results_dir, tag)
    478 
    479         atrun = _Run(host, results_dir, tag, parallel_flag, background)
    480         self._do_run(control_file, results_dir, host, atrun, timeout,
    481                      client_disconnect_timeout, use_packaging=use_packaging)
    482 
    483 
    484     def _get_host_and_setup(self, host, use_packaging=True):
    485         if not host:
    486             host = self.host
    487         if not self.installed:
    488             self.install(host, use_packaging=use_packaging)
    489 
    490         host.wait_up(timeout=30)
    491         return host
    492 
    493 
    494     def _do_run(self, control_file, results_dir, host, atrun, timeout,
    495                 client_disconnect_timeout, use_packaging=True):
    496         try:
    497             atrun.verify_machine()
    498         except:
    499             logging.error("Verify failed on %s. Reinstalling autotest",
    500                           host.hostname)
    501             self.install(host)
    502             atrun.verify_machine()
    503         debug = os.path.join(results_dir, 'debug')
    504         try:
    505             os.makedirs(debug)
    506         except Exception:
    507             pass
    508 
    509         delete_file_list = [atrun.remote_control_file,
    510                             atrun.remote_control_file + '.state',
    511                             atrun.manual_control_file,
    512                             atrun.manual_control_file + '.state']
    513         cmd = ';'.join('rm -f ' + control for control in delete_file_list)
    514         host.run(cmd, ignore_status=True)
    515 
    516         tmppath = utils.get(control_file, local_copy=True)
    517 
    518         # build up the initialization prologue for the control file
    519         prologue_lines = []
    520 
    521         # Add the additional user arguments
    522         prologue_lines.append("args = %r\n" % self.job.args)
    523 
    524         # If the packaging system is being used, add the repository list.
    525         repos = None
    526         try:
    527             if use_packaging:
    528                 repos = self.get_fetch_location()
    529                 prologue_lines.append('job.add_repository(%s)\n' % repos)
    530             else:
    531                 logging.debug('use_packaging is set to False, do not add any '
    532                               'repository.')
    533         except global_config.ConfigError, e:
    534             # If repos is defined packaging is enabled so log the error
    535             if repos:
    536                 logging.error(e)
    537 
    538         # on full-size installs, turn on any profilers the server is using
    539         if not atrun.background:
    540             running_profilers = host.job.profilers.add_log.iteritems()
    541             for profiler, (args, dargs) in running_profilers:
    542                 call_args = [repr(profiler)]
    543                 call_args += [repr(arg) for arg in args]
    544                 call_args += ["%s=%r" % item for item in dargs.iteritems()]
    545                 prologue_lines.append("job.profilers.add(%s)\n"
    546                                       % ", ".join(call_args))
    547         cfile = "".join(prologue_lines)
    548 
    549         cfile += open(tmppath).read()
    550         open(tmppath, "w").write(cfile)
    551 
    552         # Create and copy state file to remote_control_file + '.state'
    553         state_file = host.job.preprocess_client_state()
    554         host.send_file(state_file, atrun.remote_control_file + '.init.state')
    555         os.remove(state_file)
    556 
    557         # Copy control_file to remote_control_file on the host
    558         host.send_file(tmppath, atrun.remote_control_file)
    559         if os.path.abspath(tmppath) != os.path.abspath(control_file):
    560             os.remove(tmppath)
    561 
    562         atrun.execute_control(
    563                 timeout=timeout,
    564                 client_disconnect_timeout=client_disconnect_timeout)
    565 
    566 
    567     @staticmethod
    568     def extract_test_failure_msg(failure_status_line):
    569         """Extract the test failure message from the status line.
    570 
    571         @param failure_status_line:  String of test failure status line, it will
    572             look like:
    573           FAIL <test name>  <test name> timestamp=<ts> localtime=<lt> <reason>
    574 
    575         @returns String of the reason, return empty string if we can't regex out
    576             reason.
    577         """
    578         fail_msg = ''
    579         match = _FAIL_STATUS_RE.match(failure_status_line)
    580         if match:
    581             fail_msg = match.group('fail_msg')
    582         return fail_msg
    583 
    584 
    585     @classmethod
    586     def _check_client_test_result(cls, host, test_name):
    587         """
    588         Check result of client test.
    589         Autotest will store results in the file name status.
    590         We check that second to last line in that file begins with 'END GOOD'
    591 
    592         @raises TestFail: If client test does not pass.
    593         """
    594         client_result_dir = '%s/results/default' % host.autodir
    595         command = 'tail -2 %s/status | head -1' % client_result_dir
    596         status = host.run(command).stdout.strip()
    597         logging.info(status)
    598         if status[:8] != 'END GOOD':
    599             test_fail_status_line_cmd = (
    600                     'grep "^\s*FAIL\s*%s" %s/status | tail -n 1' %
    601                     (test_name, client_result_dir))
    602             test_fail_msg = cls.extract_test_failure_msg(
    603                     host.run(test_fail_status_line_cmd).stdout.strip())
    604             test_fail_msg_reason = ('' if not test_fail_msg
    605                                     else ' (reason: %s)' % test_fail_msg)
    606             test_fail_status = '%s client test did not pass%s.' % (
    607                     test_name, test_fail_msg_reason)
    608             raise error.TestFail(test_fail_status)
    609 
    610 
    611     def run_timed_test(self, test_name, results_dir='.', host=None,
    612                        timeout=None, parallel_flag=False, background=False,
    613                        client_disconnect_timeout=None, *args, **dargs):
    614         """
    615         Assemble a tiny little control file to just run one test,
    616         and run it as an autotest client-side test
    617         """
    618         if not host:
    619             host = self.host
    620         if not self.installed:
    621             self.install(host)
    622 
    623         opts = ["%s=%s" % (o[0], repr(o[1])) for o in dargs.items()]
    624         cmd = ", ".join([repr(test_name)] + map(repr, args) + opts)
    625         control = "job.run_test(%s)\n" % cmd
    626         self.run(control, results_dir, host, timeout=timeout,
    627                  parallel_flag=parallel_flag, background=background,
    628                  client_disconnect_timeout=client_disconnect_timeout)
    629 
    630         if dargs.get('check_client_result', False):
    631             self._check_client_test_result(host, test_name)
    632 
    633 
    634     def run_test(self, test_name, results_dir='.', host=None,
    635                  parallel_flag=False, background=False,
    636                  client_disconnect_timeout=None, *args, **dargs):
    637         self.run_timed_test(test_name, results_dir, host, timeout=None,
    638                             parallel_flag=parallel_flag, background=background,
    639                             client_disconnect_timeout=client_disconnect_timeout,
    640                             *args, **dargs)
    641 
    642 
    643     def run_static_method(self, module, method, results_dir='.', host=None,
    644                           *args):
    645         """Runs a non-instance method with |args| from |module| on the client.
    646 
    647         This method runs a static/class/module autotest method on the client.
    648         For example:
    649           run_static_method("autotest_lib.client.cros.cros_ui", "reboot")
    650 
    651         Will run autotest_lib.client.cros.cros_ui.reboot() on the client.
    652 
    653         @param module: module name as you would refer to it when importing in a
    654             control file. e.g. autotest_lib.client.common_lib.module_name.
    655         @param method: the method you want to call.
    656         @param results_dir: A str path where the results should be stored
    657             on the local filesystem.
    658         @param host: A Host instance on which the control file should
    659             be run.
    660         @param args: args to pass to the method.
    661         """
    662         control = "\n".join(["import %s" % module,
    663                              "%s.%s(%s)\n" % (module, method,
    664                                               ','.join(map(repr, args)))])
    665         self.run(control, results_dir=results_dir, host=host)
    666 
    667 
    668 class _Run(object):
    669     """
    670     Represents a run of autotest control file.  This class maintains
    671     all the state necessary as an autotest control file is executed.
    672 
    673     It is not intended to be used directly, rather control files
    674     should be run using the run method in Autotest.
    675     """
    676     def __init__(self, host, results_dir, tag, parallel_flag, background):
    677         self.host = host
    678         self.results_dir = results_dir
    679         self.env = host.env
    680         self.tag = tag
    681         self.parallel_flag = parallel_flag
    682         self.background = background
    683         self.autodir = Autotest.get_installed_autodir(self.host)
    684         control = os.path.join(self.autodir, 'control')
    685         if tag:
    686             control += '.' + tag
    687         self.manual_control_file = control
    688         self.remote_control_file = control + '.autoserv'
    689         self.config_file = os.path.join(self.autodir, 'global_config.ini')
    690 
    691 
    692     def verify_machine(self):
    693         binary = os.path.join(self.autodir, 'bin/autotest')
    694         try:
    695             self.host.run('ls %s > /dev/null 2>&1' % binary)
    696         except:
    697             raise error.AutoservInstallError(
    698                 "Autotest does not appear to be installed")
    699 
    700         if not self.parallel_flag:
    701             tmpdir = os.path.join(self.autodir, 'tmp')
    702             download = os.path.join(self.autodir, 'tests/download')
    703             self.host.run('umount %s' % tmpdir, ignore_status=True)
    704             self.host.run('umount %s' % download, ignore_status=True)
    705 
    706 
    707     def get_base_cmd_args(self, section):
    708         args = ['--verbose']
    709         if section > 0:
    710             args.append('-c')
    711         if self.tag:
    712             args.append('-t %s' % self.tag)
    713         if self.host.job.use_external_logging():
    714             args.append('-l')
    715         if self.host.hostname:
    716             args.append('--hostname=%s' % self.host.hostname)
    717         args.append('--user=%s' % self.host.job.user)
    718 
    719         args.append(self.remote_control_file)
    720         return args
    721 
    722 
    723     def get_background_cmd(self, section):
    724         cmd = ['nohup', os.path.join(self.autodir, 'bin/autotest_client')]
    725         cmd += self.get_base_cmd_args(section)
    726         cmd += ['>/dev/null', '2>/dev/null', '&']
    727         return ' '.join(cmd)
    728 
    729 
    730     def get_daemon_cmd(self, section, monitor_dir):
    731         cmd = ['nohup', os.path.join(self.autodir, 'bin/autotestd'),
    732                monitor_dir, '-H autoserv']
    733         cmd += self.get_base_cmd_args(section)
    734         cmd += ['>/dev/null', '2>/dev/null', '&']
    735         return ' '.join(cmd)
    736 
    737 
    738     def get_monitor_cmd(self, monitor_dir, stdout_read, stderr_read):
    739         cmd = [os.path.join(self.autodir, 'bin', 'autotestd_monitor'),
    740                monitor_dir, str(stdout_read), str(stderr_read)]
    741         return ' '.join(cmd)
    742 
    743 
    744     def get_client_log(self):
    745         """Find what the "next" client.* prefix should be
    746 
    747         @returns A string of the form client.INTEGER that should be prefixed
    748             to all client debug log files.
    749         """
    750         max_digit = -1
    751         debug_dir = os.path.join(self.results_dir, 'debug')
    752         client_logs = glob.glob(os.path.join(debug_dir, 'client.*.*'))
    753         for log in client_logs:
    754             _, number, _ = log.split('.', 2)
    755             if number.isdigit():
    756                 max_digit = max(max_digit, int(number))
    757         return 'client.%d' % (max_digit + 1)
    758 
    759 
    760     def copy_client_config_file(self, client_log_prefix=None):
    761         """
    762         Create and copy the client config file based on the server config.
    763 
    764         @param client_log_prefix: Optional prefix to prepend to log files.
    765         """
    766         client_config_file = self._create_client_config_file(client_log_prefix)
    767         self.host.send_file(client_config_file, self.config_file)
    768         os.remove(client_config_file)
    769 
    770 
    771     def _create_client_config_file(self, client_log_prefix=None):
    772         """
    773         Create a temporary file with the [CLIENT] section configuration values
    774         taken from the server global_config.ini.
    775 
    776         @param client_log_prefix: Optional prefix to prepend to log files.
    777 
    778         @return: Path of the temporary file generated.
    779         """
    780         config = global_config.global_config.get_section_values('CLIENT')
    781         if client_log_prefix:
    782             config.set('CLIENT', 'default_logging_name', client_log_prefix)
    783         return self._create_aux_file(config.write)
    784 
    785 
    786     def _create_aux_file(self, func, *args):
    787         """
    788         Creates a temporary file and writes content to it according to a
    789         content creation function. The file object is appended to *args, which
    790         is then passed to the content creation function
    791 
    792         @param func: Function that will be used to write content to the
    793                 temporary file.
    794         @param *args: List of parameters that func takes.
    795         @return: Path to the temporary file that was created.
    796         """
    797         fd, path = tempfile.mkstemp(dir=self.host.job.tmpdir)
    798         aux_file = os.fdopen(fd, "w")
    799         try:
    800             list_args = list(args)
    801             list_args.append(aux_file)
    802             func(*list_args)
    803         finally:
    804             aux_file.close()
    805         return path
    806 
    807 
    808     @staticmethod
    809     def is_client_job_finished(last_line):
    810         return bool(re.match(r'^\t*END .*\t[\w.-]+\t[\w.-]+\t.*$', last_line))
    811 
    812 
    813     @staticmethod
    814     def is_client_job_rebooting(last_line):
    815         return bool(re.match(r'^\t*GOOD\t[\w.-]+\treboot\.start.*$', last_line))
    816 
    817 
    818     def _diagnose_dut(self, old_boot_id=None):
    819         """
    820         Run diagnostic checks on a DUT.
    821 
    822         1. ping: A dead host will not respond to pings.
    823         2. ssh (happens with 3.): DUT hangs usually fail in authentication
    824             but respond to pings.
    825         3. Check if a reboot occured: A healthy but unexpected reboot leaves the
    826             host running with a new boot id.
    827 
    828         This method will always raise an exception from the AutotestFailure
    829         family and should only get called when the reason for a test failing
    830         is ambiguous.
    831 
    832         @raises AutotestDeviceNotPingable: If the DUT doesn't respond to ping.
    833         @raises AutotestDeviceNotSSHable: If we cannot SSH into the DUT.
    834         @raises AutotestDeviceRebooted: If the boot id changed.
    835         @raises AutotestAbort: If none of the above exceptions were raised.
    836             Since we have no recourse we must abort at this stage.
    837         """
    838         msg = 'Autotest client terminated unexpectedly: '
    839         if utils.ping(self.host.hostname, tries=1, deadline=1) != 0:
    840             msg += 'DUT is no longer pingable, it may have rebooted or hung.\n'
    841             raise AutotestDeviceNotPingable(msg)
    842 
    843         if old_boot_id:
    844             try:
    845                 new_boot_id = self.host.get_boot_id(timeout=60)
    846             except Exception as e:
    847                 msg += ('DUT is pingable but not SSHable, it most likely'
    848                         ' sporadically rebooted during testing. %s\n' % str(e))
    849                 raise AutotestDeviceNotSSHable(msg)
    850             else:
    851                 if new_boot_id != old_boot_id:
    852                     msg += 'DUT rebooted during the test run.\n'
    853                     raise AutotestDeviceRebooted(msg)
    854 
    855             msg += ('DUT is pingable, SSHable and did NOT restart '
    856                     'un-expectedly. We probably lost connectivity during the '
    857                     'test.')
    858         else:
    859             msg += ('DUT is pingable, could not determine if an un-expected '
    860                     'reboot occured during the test.')
    861 
    862         raise AutotestAbort(msg)
    863 
    864 
    865     def log_unexpected_abort(self, stderr_redirector, old_boot_id=None):
    866         """
    867         Logs that something unexpected happened, then tries to diagnose the
    868         failure. The purpose of this function is only to close out the status
    869         log with the appropriate error message, not to critically terminate
    870         the program.
    871 
    872         @param stderr_redirector: log stream.
    873         @param old_boot_id: boot id used to infer if a reboot occured.
    874         """
    875         stderr_redirector.flush_all_buffers()
    876         try:
    877             self._diagnose_dut(old_boot_id)
    878         except AutotestFailure as e:
    879             self.host.job.record('END ABORT', None, None, str(e))
    880 
    881 
    882     def _execute_in_background(self, section, timeout):
    883         full_cmd = self.get_background_cmd(section)
    884         devnull = open(os.devnull, "w")
    885 
    886         self.copy_client_config_file(self.get_client_log())
    887 
    888         self.host.job.push_execution_context(self.results_dir)
    889         try:
    890             result = self.host.run(full_cmd, ignore_status=True,
    891                                    timeout=timeout,
    892                                    stdout_tee=devnull,
    893                                    stderr_tee=devnull)
    894         finally:
    895             self.host.job.pop_execution_context()
    896 
    897         return result
    898 
    899 
    900     @staticmethod
    901     def _strip_stderr_prologue(stderr):
    902         """Strips the 'standard' prologue that get pre-pended to every
    903         remote command and returns the text that was actually written to
    904         stderr by the remote command."""
    905         stderr_lines = stderr.split("\n")[1:]
    906         if not stderr_lines:
    907             return ""
    908         elif stderr_lines[0].startswith("NOTE: autotestd_monitor"):
    909             del stderr_lines[0]
    910         return "\n".join(stderr_lines)
    911 
    912 
    913     def _execute_daemon(self, section, timeout, stderr_redirector,
    914                         client_disconnect_timeout):
    915         monitor_dir = self.host.get_tmp_dir()
    916         daemon_cmd = self.get_daemon_cmd(section, monitor_dir)
    917 
    918         # grab the location for the server-side client log file
    919         client_log_prefix = self.get_client_log()
    920         client_log_path = os.path.join(self.results_dir, 'debug',
    921                                        client_log_prefix + '.log')
    922         client_log = open(client_log_path, 'w', 0)
    923         self.copy_client_config_file(client_log_prefix)
    924 
    925         stdout_read = stderr_read = 0
    926         self.host.job.push_execution_context(self.results_dir)
    927         try:
    928             self.host.run(daemon_cmd, ignore_status=True, timeout=timeout)
    929             disconnect_warnings = []
    930             while True:
    931                 monitor_cmd = self.get_monitor_cmd(monitor_dir, stdout_read,
    932                                                    stderr_read)
    933                 try:
    934                     result = self.host.run(monitor_cmd, ignore_status=True,
    935                                            timeout=timeout,
    936                                            stdout_tee=client_log,
    937                                            stderr_tee=stderr_redirector)
    938                 except error.AutoservRunError, e:
    939                     result = e.result_obj
    940                     result.exit_status = None
    941                     disconnect_warnings.append(e.description)
    942 
    943                     stderr_redirector.log_warning(
    944                         "Autotest client was disconnected: %s" % e.description,
    945                         "NETWORK")
    946                 except error.AutoservSSHTimeout:
    947                     result = utils.CmdResult(monitor_cmd, "", "", None, 0)
    948                     stderr_redirector.log_warning(
    949                         "Attempt to connect to Autotest client timed out",
    950                         "NETWORK")
    951 
    952                 stdout_read += len(result.stdout)
    953                 stderr_read += len(self._strip_stderr_prologue(result.stderr))
    954 
    955                 if result.exit_status is not None:
    956                     # TODO (crosbug.com/38224)- sbasi: Remove extra logging.
    957                     logging.debug('Result exit status is %d.',
    958                                   result.exit_status)
    959                     return result
    960                 elif not self.host.wait_up(client_disconnect_timeout):
    961                     raise error.AutoservSSHTimeout(
    962                         "client was disconnected, reconnect timed out")
    963         finally:
    964             client_log.close()
    965             self.host.job.pop_execution_context()
    966 
    967 
    968     def execute_section(self, section, timeout, stderr_redirector,
    969                         client_disconnect_timeout):
    970         # TODO(crbug.com/684311) The claim is that section is never more than 0
    971         # in pratice. After validating for a week or so, delete all support of
    972         # multiple sections.
    973         metrics.Counter('chromeos/autotest/autotest/sections').increment(
    974                 fields={'is_first_section': (section == 0)})
    975         logging.info("Executing %s/bin/autotest %s/control phase %d",
    976                      self.autodir, self.autodir, section)
    977 
    978         if self.background:
    979             result = self._execute_in_background(section, timeout)
    980         else:
    981             result = self._execute_daemon(section, timeout, stderr_redirector,
    982                                           client_disconnect_timeout)
    983 
    984         last_line = stderr_redirector.last_line
    985 
    986         # check if we failed hard enough to warrant an exception
    987         if result.exit_status == 1:
    988             err = error.AutotestRunError("client job was aborted")
    989         elif not self.background and not result.stderr:
    990             err = error.AutotestRunError(
    991                 "execute_section %s failed to return anything\n"
    992                 "stdout:%s\n" % (section, result.stdout))
    993         else:
    994             err = None
    995 
    996         # log something if the client failed AND never finished logging
    997         if err and not self.is_client_job_finished(last_line):
    998             self.log_unexpected_abort(stderr_redirector)
    999 
   1000         if err:
   1001             raise err
   1002         else:
   1003             return stderr_redirector.last_line
   1004 
   1005 
   1006     def _wait_for_reboot(self, old_boot_id):
   1007         logging.info("Client is rebooting")
   1008         logging.info("Waiting for client to halt")
   1009         if not self.host.wait_down(self.host.WAIT_DOWN_REBOOT_TIMEOUT,
   1010                                    old_boot_id=old_boot_id):
   1011             err = "%s failed to shutdown after %d"
   1012             err %= (self.host.hostname, self.host.WAIT_DOWN_REBOOT_TIMEOUT)
   1013             raise error.AutotestRunError(err)
   1014         logging.info("Client down, waiting for restart")
   1015         if not self.host.wait_up(self.host.DEFAULT_REBOOT_TIMEOUT):
   1016             # since reboot failed
   1017             # hardreset the machine once if possible
   1018             # before failing this control file
   1019             warning = "%s did not come back up, hard resetting"
   1020             warning %= self.host.hostname
   1021             logging.warning(warning)
   1022             try:
   1023                 self.host.hardreset(wait=False)
   1024             except (AttributeError, error.AutoservUnsupportedError):
   1025                 warning = "Hard reset unsupported on %s"
   1026                 warning %= self.host.hostname
   1027                 logging.warning(warning)
   1028             raise error.AutotestRunError("%s failed to boot after %ds" %
   1029                                          (self.host.hostname,
   1030                                           self.host.DEFAULT_REBOOT_TIMEOUT))
   1031         self.host.reboot_followup()
   1032 
   1033 
   1034     def execute_control(self, timeout=None, client_disconnect_timeout=None):
   1035         if not self.background:
   1036             collector = log_collector(self.host, self.tag, self.results_dir)
   1037             hostname = self.host.hostname
   1038             remote_results = collector.client_results_dir
   1039             local_results = collector.server_results_dir
   1040             self.host.job.add_client_log(hostname, remote_results,
   1041                                          local_results)
   1042             job_record_context = self.host.job.get_record_context()
   1043 
   1044         section = 0
   1045         start_time = time.time()
   1046 
   1047         logger = client_logger(self.host, self.tag, self.results_dir)
   1048         try:
   1049             while not timeout or time.time() < start_time + timeout:
   1050                 if timeout:
   1051                     section_timeout = start_time + timeout - time.time()
   1052                 else:
   1053                     section_timeout = None
   1054                 boot_id = self.host.get_boot_id()
   1055                 last = self.execute_section(section, section_timeout,
   1056                                             logger, client_disconnect_timeout)
   1057                 if self.background:
   1058                     return
   1059                 section += 1
   1060                 if self.is_client_job_finished(last):
   1061                     logging.info("Client complete")
   1062                     return
   1063                 elif self.is_client_job_rebooting(last):
   1064                     try:
   1065                         self._wait_for_reboot(boot_id)
   1066                     except error.AutotestRunError, e:
   1067                         self.host.job.record("ABORT", None, "reboot", str(e))
   1068                         self.host.job.record("END ABORT", None, None, str(e))
   1069                         raise
   1070                     continue
   1071 
   1072                 # If a test fails without probable cause we try to bucket it's
   1073                 # failure into one of 2 categories. If we can determine the
   1074                 # current state of the device and it is suspicious, we close the
   1075                 # status lines indicating a failure. If we either cannot
   1076                 # determine the state of the device, or it appears totally
   1077                 # healthy, we give up and abort.
   1078                 try:
   1079                     self._diagnose_dut(boot_id)
   1080                 except AutotestDeviceError as e:
   1081                     # The status lines of the test are pretty much tailed to
   1082                     # our log, with indentation, from the client job on the DUT.
   1083                     # So if the DUT goes down unexpectedly we'll end up with a
   1084                     # malformed status log unless we manually unwind the status
   1085                     # stack. Ideally we would want to write a nice wrapper like
   1086                     # server_job methods run_reboot, run_group but they expect
   1087                     # reboots and we don't.
   1088                     self.host.job.record('FAIL', None, None, str(e))
   1089                     self.host.job.record('END FAIL', None, None)
   1090                     self.host.job.record('END GOOD', None, None)
   1091                     self.host.job.failed_with_device_error = True
   1092                     return
   1093                 except AutotestAbort as e:
   1094                     self.host.job.record('ABORT', None, None, str(e))
   1095                     self.host.job.record('END ABORT', None, None)
   1096 
   1097                     # give the client machine a chance to recover from a crash
   1098                     self.host.wait_up(
   1099                         self.host.HOURS_TO_WAIT_FOR_RECOVERY * 3600)
   1100                     logging.debug('Unexpected final status message from '
   1101                                   'client %s: %s', self.host.hostname, last)
   1102                     # The line 'last' may have sensitive phrases, like
   1103                     # 'END GOOD', which breaks the tko parser. So the error
   1104                     # message will exclude it, since it will be recorded to
   1105                     # status.log.
   1106                     msg = ("Aborting - unexpected final status message from "
   1107                            "client on %s\n") % self.host.hostname
   1108                     raise error.AutotestRunError(msg)
   1109         finally:
   1110             logging.debug('Autotest job finishes running. Below is the '
   1111                           'post-processing operations.')
   1112             logger.close()
   1113             if not self.background:
   1114                 collector.collect_client_job_results()
   1115                 collector.remove_redundant_client_logs()
   1116                 state_file = os.path.basename(self.remote_control_file
   1117                                               + '.state')
   1118                 state_path = os.path.join(self.results_dir, state_file)
   1119                 self.host.job.postprocess_client_state(state_path)
   1120                 self.host.job.remove_client_log(hostname, remote_results,
   1121                                                 local_results)
   1122                 job_record_context.restore()
   1123 
   1124             logging.debug('Autotest job finishes.')
   1125 
   1126         # should only get here if we timed out
   1127         assert timeout
   1128         raise error.AutotestTimeoutError()
   1129 
   1130 
   1131 class log_collector(object):
   1132     def __init__(self, host, client_tag, results_dir):
   1133         self.host = host
   1134         if not client_tag:
   1135             client_tag = "default"
   1136         self.client_results_dir = os.path.join(host.get_autodir(), "results",
   1137                                                client_tag)
   1138         self.server_results_dir = results_dir
   1139 
   1140 
   1141     def collect_client_job_results(self):
   1142         """ A method that collects all the current results of a running
   1143         client job into the results dir. By default does nothing as no
   1144         client job is running, but when running a client job you can override
   1145         this with something that will actually do something. """
   1146         # make an effort to wait for the machine to come up
   1147         try:
   1148             self.host.wait_up(timeout=30)
   1149         except error.AutoservError:
   1150             # don't worry about any errors, we'll try and
   1151             # get the results anyway
   1152             pass
   1153 
   1154         # Copy all dirs in default to results_dir
   1155         try:
   1156             # Build test result directory summary
   1157             result_tools_runner.run_on_client(
   1158                     self.host, self.client_results_dir)
   1159 
   1160             with metrics.SecondsTimer(
   1161                     'chromeos/autotest/job/log_collection_duration',
   1162                     fields={'dut_host_name': self.host.hostname}):
   1163                 self.host.get_file(
   1164                         self.client_results_dir + '/',
   1165                         self.server_results_dir,
   1166                         preserve_symlinks=True)
   1167         except Exception:
   1168             # well, don't stop running just because we couldn't get logs
   1169             e_msg = "Unexpected error copying test result logs, continuing ..."
   1170             logging.error(e_msg)
   1171             traceback.print_exc(file=sys.stdout)
   1172 
   1173 
   1174     def remove_redundant_client_logs(self):
   1175         """Remove client.*.log files in favour of client.*.DEBUG files."""
   1176         debug_dir = os.path.join(self.server_results_dir, 'debug')
   1177         debug_files = [f for f in os.listdir(debug_dir)
   1178                        if re.search(r'^client\.\d+\.DEBUG$', f)]
   1179         for debug_file in debug_files:
   1180             log_file = debug_file.replace('DEBUG', 'log')
   1181             log_file = os.path.join(debug_dir, log_file)
   1182             if os.path.exists(log_file):
   1183                 os.remove(log_file)
   1184 
   1185 
   1186 # a file-like object for catching stderr from an autotest client and
   1187 # extracting status logs from it
   1188 class client_logger(object):
   1189     """Partial file object to write to both stdout and
   1190     the status log file.  We only implement those methods
   1191     utils.run() actually calls.
   1192     """
   1193     status_parser = re.compile(r"^AUTOTEST_STATUS:([^:]*):(.*)$")
   1194     test_complete_parser = re.compile(r"^AUTOTEST_TEST_COMPLETE:(.*)$")
   1195     fetch_package_parser = re.compile(
   1196         r"^AUTOTEST_FETCH_PACKAGE:([^:]*):([^:]*):(.*)$")
   1197     extract_indent = re.compile(r"^(\t*).*$")
   1198     extract_timestamp = re.compile(r".*\ttimestamp=(\d+)\t.*$")
   1199 
   1200     def __init__(self, host, tag, server_results_dir):
   1201         self.host = host
   1202         self.job = host.job
   1203         self.log_collector = log_collector(host, tag, server_results_dir)
   1204         self.leftover = ""
   1205         self.last_line = ""
   1206         self.logs = {}
   1207 
   1208 
   1209     def _process_log_dict(self, log_dict):
   1210         log_list = log_dict.pop("logs", [])
   1211         for key in sorted(log_dict.iterkeys()):
   1212             log_list += self._process_log_dict(log_dict.pop(key))
   1213         return log_list
   1214 
   1215 
   1216     def _process_logs(self):
   1217         """Go through the accumulated logs in self.log and print them
   1218         out to stdout and the status log. Note that this processes
   1219         logs in an ordering where:
   1220 
   1221         1) logs to different tags are never interleaved
   1222         2) logs to x.y come before logs to x.y.z for all z
   1223         3) logs to x.y come before x.z whenever y < z
   1224 
   1225         Note that this will in general not be the same as the
   1226         chronological ordering of the logs. However, if a chronological
   1227         ordering is desired that one can be reconstructed from the
   1228         status log by looking at timestamp lines."""
   1229         log_list = self._process_log_dict(self.logs)
   1230         for entry in log_list:
   1231             self.job.record_entry(entry, log_in_subdir=False)
   1232         if log_list:
   1233             self.last_line = log_list[-1].render()
   1234 
   1235 
   1236     def _process_quoted_line(self, tag, line):
   1237         """Process a line quoted with an AUTOTEST_STATUS flag. If the
   1238         tag is blank then we want to push out all the data we've been
   1239         building up in self.logs, and then the newest line. If the
   1240         tag is not blank, then push the line into the logs for handling
   1241         later."""
   1242         entry = base_job.status_log_entry.parse(line)
   1243         if entry is None:
   1244             return  # the line contains no status lines
   1245         if tag == "":
   1246             self._process_logs()
   1247             self.job.record_entry(entry, log_in_subdir=False)
   1248             self.last_line = line
   1249         else:
   1250             tag_parts = [int(x) for x in tag.split(".")]
   1251             log_dict = self.logs
   1252             for part in tag_parts:
   1253                 log_dict = log_dict.setdefault(part, {})
   1254             log_list = log_dict.setdefault("logs", [])
   1255             log_list.append(entry)
   1256 
   1257 
   1258     def _process_info_line(self, line):
   1259         """Check if line is an INFO line, and if it is, interpret any control
   1260         messages (e.g. enabling/disabling warnings) that it may contain."""
   1261         match = re.search(r"^\t*INFO\t----\t----(.*)\t[^\t]*$", line)
   1262         if not match:
   1263             return   # not an INFO line
   1264         for field in match.group(1).split('\t'):
   1265             if field.startswith("warnings.enable="):
   1266                 func = self.job.warning_manager.enable_warnings
   1267             elif field.startswith("warnings.disable="):
   1268                 func = self.job.warning_manager.disable_warnings
   1269             else:
   1270                 continue
   1271             warning_type = field.split("=", 1)[1]
   1272             func(warning_type)
   1273 
   1274 
   1275     def _process_line(self, line):
   1276         """Write out a line of data to the appropriate stream.
   1277 
   1278         Returns the package checksum file if it exists.
   1279 
   1280         Status lines sent by autotest will be prepended with
   1281         "AUTOTEST_STATUS", and all other lines are ssh error messages.
   1282         """
   1283         logging.debug(line)
   1284         fetch_package_match = self.fetch_package_parser.search(line)
   1285         if fetch_package_match:
   1286             pkg_name, dest_path, fifo_path = fetch_package_match.groups()
   1287             serve_packages = _CONFIG.get_config_value(
   1288                 "PACKAGES", "serve_packages_from_autoserv", type=bool)
   1289             if serve_packages and pkg_name == 'packages.checksum':
   1290                 try:
   1291                     checksum_file = os.path.join(
   1292                         self.job.pkgmgr.pkgmgr_dir, 'packages', pkg_name)
   1293                     if os.path.exists(checksum_file):
   1294                         self.host.send_file(checksum_file, dest_path)
   1295                 except error.AutoservRunError:
   1296                     msg = "Package checksum file not found, continuing anyway"
   1297                     logging.exception(msg)
   1298 
   1299                 try:
   1300                     # When fetching a package, the client expects to be
   1301                     # notified when the fetching is complete. Autotest
   1302                     # does this pushing a B to a fifo queue to the client.
   1303                     self.host.run("echo B > %s" % fifo_path)
   1304                 except error.AutoservRunError:
   1305                     msg = "Checksum installation failed, continuing anyway"
   1306                     logging.exception(msg)
   1307                 finally:
   1308                     return
   1309 
   1310         status_match = self.status_parser.search(line)
   1311         test_complete_match = self.test_complete_parser.search(line)
   1312         fetch_package_match = self.fetch_package_parser.search(line)
   1313         if status_match:
   1314             tag, line = status_match.groups()
   1315             self._process_info_line(line)
   1316             self._process_quoted_line(tag, line)
   1317         elif test_complete_match:
   1318             self._process_logs()
   1319             fifo_path, = test_complete_match.groups()
   1320             try:
   1321                 self.log_collector.collect_client_job_results()
   1322                 self.host.run("echo A > %s" % fifo_path)
   1323             except Exception:
   1324                 msg = "Post-test log collection failed, continuing anyway"
   1325                 logging.exception(msg)
   1326         elif fetch_package_match:
   1327             pkg_name, dest_path, fifo_path = fetch_package_match.groups()
   1328             serve_packages = global_config.global_config.get_config_value(
   1329                 "PACKAGES", "serve_packages_from_autoserv", type=bool)
   1330             if serve_packages and pkg_name.endswith(".tar.bz2"):
   1331                 try:
   1332                     self._send_tarball(pkg_name, dest_path)
   1333                 except Exception:
   1334                     msg = "Package tarball creation failed, continuing anyway"
   1335                     logging.exception(msg)
   1336             try:
   1337                 self.host.run("echo B > %s" % fifo_path)
   1338             except Exception:
   1339                 msg = "Package tarball installation failed, continuing anyway"
   1340                 logging.exception(msg)
   1341         else:
   1342             logging.info(line)
   1343 
   1344 
   1345     def _send_tarball(self, pkg_name, remote_dest):
   1346         """Uses tarballs in package manager by default."""
   1347         try:
   1348             server_package = os.path.join(self.job.pkgmgr.pkgmgr_dir,
   1349                                           'packages', pkg_name)
   1350             if os.path.exists(server_package):
   1351               self.host.send_file(server_package, remote_dest)
   1352               return
   1353 
   1354         except error.AutoservRunError:
   1355             msg = ("Package %s could not be sent from the package cache." %
   1356                    pkg_name)
   1357             logging.exception(msg)
   1358 
   1359         name, pkg_type = self.job.pkgmgr.parse_tarball_name(pkg_name)
   1360         src_dirs = []
   1361         if pkg_type == 'test':
   1362             for test_dir in ['site_tests', 'tests']:
   1363                 src_dir = os.path.join(self.job.clientdir, test_dir, name)
   1364                 if os.path.exists(src_dir):
   1365                     src_dirs += [src_dir]
   1366                     break
   1367         elif pkg_type == 'profiler':
   1368             src_dirs += [os.path.join(self.job.clientdir, 'profilers', name)]
   1369         elif pkg_type == 'dep':
   1370             src_dirs += [os.path.join(self.job.clientdir, 'deps', name)]
   1371         elif pkg_type == 'client':
   1372             return  # you must already have a client to hit this anyway
   1373         else:
   1374             return  # no other types are supported
   1375 
   1376         # iterate over src_dirs until we find one that exists, then tar it
   1377         for src_dir in src_dirs:
   1378             if os.path.exists(src_dir):
   1379                 try:
   1380                     logging.info('Bundling %s into %s', src_dir, pkg_name)
   1381                     temp_dir = autotemp.tempdir(unique_id='autoserv-packager',
   1382                                                 dir=self.job.tmpdir)
   1383                     tarball_path = self.job.pkgmgr.tar_package(
   1384                         pkg_name, src_dir, temp_dir.name, " .")
   1385                     self.host.send_file(tarball_path, remote_dest)
   1386                 finally:
   1387                     temp_dir.clean()
   1388                 return
   1389 
   1390 
   1391     def log_warning(self, msg, warning_type):
   1392         """Injects a WARN message into the current status logging stream."""
   1393         timestamp = int(time.time())
   1394         if self.job.warning_manager.is_valid(timestamp, warning_type):
   1395             self.job.record('WARN', None, None, msg)
   1396 
   1397 
   1398     def write(self, data):
   1399         # now start processing the existing buffer and the new data
   1400         data = self.leftover + data
   1401         lines = data.split('\n')
   1402         processed_lines = 0
   1403         try:
   1404             # process all the buffered data except the last line
   1405             # ignore the last line since we may not have all of it yet
   1406             for line in lines[:-1]:
   1407                 self._process_line(line)
   1408                 processed_lines += 1
   1409         finally:
   1410             # save any unprocessed lines for future processing
   1411             self.leftover = '\n'.join(lines[processed_lines:])
   1412 
   1413 
   1414     def flush(self):
   1415         sys.stdout.flush()
   1416 
   1417 
   1418     def flush_all_buffers(self):
   1419         if self.leftover:
   1420             self._process_line(self.leftover)
   1421             self.leftover = ""
   1422         self._process_logs()
   1423         self.flush()
   1424 
   1425 
   1426     def close(self):
   1427         self.flush_all_buffers()
   1428