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 7This module is the one imported by other Autotest code and run result 8throttling. Other modules in result_tools are designed to be copied to DUT and 9executed with command line. That's why other modules (except view.py and 10unittests) don't import the common module. 11""" 12 13import logging 14import os 15 16import common 17from autotest_lib.client.common_lib import error 18from autotest_lib.client.common_lib import global_config 19from autotest_lib.client.common_lib import utils as client_utils 20 21try: 22 from chromite.lib import metrics 23except ImportError: 24 metrics = client_utils.metrics_mock 25 26 27CONFIG = global_config.global_config 28ENABLE_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 39DEFAULT_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 48def _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 70def 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 127def 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