Home | History | Annotate | Download | only in result_tools
      1 # Copyright 2017 The Chromium OS 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 """Utility to deploy and run result utils on a DUT.
      6 
      7 This module is the one imported by other Autotest code and run result
      8 throttling. Other modules in result_tools are designed to be copied to DUT and
      9 executed with command line. That's why other modules (except view.py and
     10 unittests) don't import the common module.
     11 """
     12 
     13 import logging
     14 import os
     15 
     16 import common
     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 utils as client_utils
     20 
     21 try:
     22     from chromite.lib import metrics
     23 except ImportError:
     24     metrics = client_utils.metrics_mock
     25 
     26 
     27 CONFIG = global_config.global_config
     28 ENABLE_RESULT_THROTTLING = CONFIG.get_config_value(
     29         'AUTOSERV', 'enable_result_throttling', type=bool, default=False)
     30 
     31 _THROTTLE_OPTION_FMT = '-m %s'
     32 _BUILD_DIR_SUMMARY_CMD = '%s/result_tools/utils.py -p %s %s'
     33 _BUILD_DIR_SUMMARY_TIMEOUT = 120
     34 _FIND_DIR_SUMMARY_TIMEOUT = 10
     35 _CLEANUP_DIR_SUMMARY_CMD = '%s/result_tools/utils.py -p %s -d'
     36 _CLEANUP_DIR_SUMMARY_TIMEOUT = 10
     37 
     38 # Default autotest directory on host
     39 DEFAULT_AUTOTEST_DIR = '/usr/local/autotest'
     40 
     41 # File patterns to be excluded from deploying to the dut.
     42 _EXCLUDES = ['*.pyc', '*unittest.py', 'common.py', '__init__.py', 'runner.py',
     43              'view.py']
     44 
     45 # A set of hostnames that have result tools already deployed.
     46 _deployed_duts = set()
     47 
     48 def _deploy_result_tools(host):
     49     """Send result tools to the dut.
     50 
     51     @param host: Host to run the result tools.
     52     """
     53     logging.debug('Deploy result utilities to %s', host.hostname)
     54     with metrics.SecondsTimer(
     55             'chromeos/autotest/job/send_result_tools_duration',
     56             fields={'dut_host_name': host.hostname}) as fields:
     57         try:
     58             result_tools_dir = os.path.dirname(__file__)
     59             host.send_file(result_tools_dir, DEFAULT_AUTOTEST_DIR,
     60                            excludes = _EXCLUDES)
     61             fields['success'] = True
     62         except error.AutotestHostRunError:
     63             logging.debug('Failed to deploy result tools using `excludes`. Try '
     64                           'again without `excludes`.')
     65             host.send_file(result_tools_dir, DEFAULT_AUTOTEST_DIR)
     66             fields['success'] = False
     67         _deployed_duts.add(host.hostname)
     68 
     69 
     70 def run_on_client(host, client_results_dir, cleanup_only=False):
     71     """Run result utils on the given host.
     72 
     73     @param host: Host to run the result utils.
     74     @param client_results_dir: Path to the results directory on the client.
     75     @param cleanup_only: True to delete all existing directory summary files in
     76             the given directory.
     77     @return: True: If the command runs on client without error.
     78              False: If the command failed with error in result throttling.
     79     """
     80     success = False
     81     with metrics.SecondsTimer(
     82             'chromeos/autotest/job/dir_summary_collection_duration',
     83             fields={'dut_host_name': host.hostname}) as fields:
     84         try:
     85             if host.hostname not in _deployed_duts:
     86                 _deploy_result_tools(host)
     87             else:
     88                 logging.debug('result tools are already deployed to %s.',
     89                               host.hostname)
     90 
     91             if cleanup_only:
     92                 logging.debug('Cleaning up directory summary in %s',
     93                               client_results_dir)
     94                 cmd = (_CLEANUP_DIR_SUMMARY_CMD %
     95                        (DEFAULT_AUTOTEST_DIR, client_results_dir))
     96                 host.run(cmd, ignore_status=False,
     97                          timeout=_CLEANUP_DIR_SUMMARY_TIMEOUT)
     98             else:
     99                 logging.debug('Getting directory summary for %s',
    100                               client_results_dir)
    101                 throttle_option = ''
    102                 if ENABLE_RESULT_THROTTLING:
    103                     try:
    104                         throttle_option = (_THROTTLE_OPTION_FMT %
    105                                            host.job.max_result_size_KB)
    106                     except AttributeError:
    107                         # In case host job is not set, skip throttling.
    108                         logging.warn('host object does not have job attribute, '
    109                                      'skipping result throttling.')
    110                 cmd = (_BUILD_DIR_SUMMARY_CMD %
    111                        (DEFAULT_AUTOTEST_DIR, client_results_dir,
    112                         throttle_option))
    113                 host.run(cmd, ignore_status=False,
    114                          timeout=_BUILD_DIR_SUMMARY_TIMEOUT)
    115                 success = True
    116             fields['success'] = True
    117         except error.AutoservRunError:
    118             action = 'cleanup' if cleanup_only else 'create'
    119             logging.exception(
    120                     'Non-critical failure: Failed to %s directory summary for '
    121                     '%s.', action, client_results_dir)
    122             fields['success'] = False
    123 
    124     return success
    125 
    126 
    127 def collect_last_summary(host, source_path, dest_path,
    128                          skip_summary_collection=False):
    129     """Collect the last directory summary next to the given file path.
    130 
    131     If the given source_path is a directory, return without collecting any
    132     result summary file, as the summary file should have been collected with the
    133     directory.
    134 
    135     @param host: The RemoteHost to collect logs from.
    136     @param source_path: The remote path to collect the directory summary file
    137             from. If the source_path is a file
    138     @param dest_path: A path to write the source_path into. The summary file
    139             will be saved to the same folder.
    140     @param skip_summary_collection: True to skip summary file collection, only
    141             to delete the last summary. This is used in case when result
    142             collection in the dut failed. Default is set to False.
    143     """
    144     if not os.path.exists(dest_path):
    145         logging.debug('Source path %s does not exist, no directory summary '
    146                       'will be collected', dest_path)
    147         return
    148 
    149     # Test if source_path is a file.
    150     try:
    151         host.run('test -f %s' % source_path, timeout=_FIND_DIR_SUMMARY_TIMEOUT)
    152         is_source_file = True
    153     except error.AutoservRunError:
    154         is_source_file = False
    155         # No need to collect summary files if the source path is a directory,
    156         # as the summary files should have been copied over with the directory.
    157         # However, the last summary should be cleaned up so it won't affect
    158         # later tests.
    159         skip_summary_collection = True
    160 
    161     source_dir = os.path.dirname(source_path) if is_source_file else source_path
    162     dest_dir = dest_path if os.path.isdir(dest_path) else dest_path
    163 
    164     # Get the latest directory summary file.
    165     try:
    166         summary_pattern = os.path.join(source_dir, 'dir_summary_*.json')
    167         summary_file = host.run(
    168                 'ls -t %s | head -1' % summary_pattern,
    169                 timeout=_FIND_DIR_SUMMARY_TIMEOUT).stdout.strip()
    170     except error.AutoservRunError:
    171         logging.exception(
    172                 'Non-critical failure: Failed to locate the latest directory '
    173                 'summary for %s', source_dir)
    174         return
    175 
    176     try:
    177         if not skip_summary_collection:
    178             host.get_file(
    179                     summary_file,
    180                     os.path.join(dest_dir, os.path.basename(summary_file)),
    181                     preserve_perm=False)
    182     finally:
    183         # Remove the collected summary file so it won't affect later tests.
    184         try:
    185             host.run('rm %s' % summary_file,
    186                      timeout=_FIND_DIR_SUMMARY_TIMEOUT).stdout.strip()
    187         except error.AutoservRunError:
    188             logging.exception(
    189                     'Non-critical failure: Failed to delete the latest '
    190                     'directory summary: %s', summary_file)
    191