1# Lint as: python2, python3 2# Copyright (c) 2007 The Chromium OS Authors. All rights reserved. 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5 6# Copyright Martin J. Bligh, Andy Whitcroft, 2007 7# 8# Define the server-side test class 9# 10# pylint: disable=missing-docstring 11 12import logging 13import os 14import tempfile 15 16from autotest_lib.client.common_lib import log 17from autotest_lib.client.common_lib import test as common_test 18from autotest_lib.client.common_lib import utils 19from autotest_lib.server import hosts, autotest 20 21 22class test(common_test.base_test): 23 disable_sysinfo_install_cache = False 24 host_parameter = None 25 26 27_sysinfo_before_test_script = """\ 28import pickle 29from autotest_lib.client.bin import test 30mytest = test.test(job, '', %r) 31job.sysinfo.log_before_each_test(mytest) 32sysinfo_pickle = os.path.join(mytest.outputdir, 'sysinfo.pickle') 33pickle.dump(job.sysinfo, open(sysinfo_pickle, 'wb')) 34job.record('GOOD', '', 'sysinfo.before') 35""" 36 37_sysinfo_after_test_script = """\ 38import pickle 39from autotest_lib.client.bin import test 40mytest = test.test(job, '', %r) 41# success is passed in so diffable_logdir can decide if or not to collect 42# full log content. 43mytest.success = %s 44mytest.collect_full_logs = %s 45sysinfo_pickle = os.path.join(mytest.outputdir, 'sysinfo.pickle') 46if os.path.exists(sysinfo_pickle): 47 try: 48 with open(sysinfo_pickle, 'r') as rf: 49 job.sysinfo = pickle.load(rf) 50 except UnicodeDecodeError: 51 with open(sysinfo_pickle, 'rb') as rf: 52 job.sysinfo = pickle.load(rf) 53 job.sysinfo.__init__(job.resultdir) 54job.sysinfo.log_after_each_test(mytest) 55job.record('GOOD', '', 'sysinfo.after') 56""" 57 58# this script is ran after _sysinfo_before_test_script and before 59# _sysinfo_after_test_script which means the pickle file exists 60# already and should be dumped with updated state for 61# _sysinfo_after_test_script to pick it up later 62_sysinfo_iteration_script = """\ 63import pickle 64from autotest_lib.client.bin import test 65mytest = test.test(job, '', %r) 66sysinfo_pickle = os.path.join(mytest.outputdir, 'sysinfo.pickle') 67if os.path.exists(sysinfo_pickle): 68 try: 69 with open(sysinfo_pickle, 'r') as rf: 70 job.sysinfo = pickle.load(rf) 71 except UnicodeDecodeError: 72 with open(sysinfo_pickle, 'rb') as rf: 73 job.sysinfo = pickle.load(rf) 74 job.sysinfo.__init__(job.resultdir) 75job.sysinfo.%s(mytest, iteration=%d) 76pickle.dump(job.sysinfo, open(sysinfo_pickle, 'wb')) 77job.record('GOOD', '', 'sysinfo.iteration.%s') 78""" 79 80 81def install_autotest_and_run(func): 82 def wrapper(self, mytest): 83 host, at, outputdir = self._install() 84 try: 85 host.erase_dir_contents(outputdir) 86 func(self, mytest, host, at, outputdir) 87 finally: 88 # the test class can define this flag to make us remove the 89 # sysinfo install files and outputdir contents after each run 90 if mytest.disable_sysinfo_install_cache: 91 self.cleanup(host_close=False) 92 93 return wrapper 94 95 96class _sysinfo_logger(object): 97 AUTOTEST_PARENT_DIR = '/tmp/sysinfo' 98 OUTPUT_PARENT_DIR = '/tmp' 99 100 def __init__(self, job): 101 self.job = job 102 self.pickle = None 103 104 # for now support a single host 105 self.host = None 106 self.autotest = None 107 self.outputdir = None 108 109 if len(job.machines) != 1: 110 # disable logging on multi-machine tests 111 self.before_hook = self.after_hook = None 112 self.before_iteration_hook = self.after_iteration_hook = None 113 114 115 def _install(self): 116 if not self.host: 117 self.host = hosts.create_target_machine( 118 self.job.machine_dict_list[0]) 119 try: 120 # Remove existing autoserv-* directories before creating more 121 self.host.delete_all_tmp_dirs([self.AUTOTEST_PARENT_DIR, 122 self.OUTPUT_PARENT_DIR]) 123 124 tmp_dir = self.host.get_tmp_dir(self.AUTOTEST_PARENT_DIR) 125 self.autotest = autotest.Autotest(self.host) 126 self.autotest.install(autodir=tmp_dir) 127 self.outputdir = self.host.get_tmp_dir(self.OUTPUT_PARENT_DIR) 128 except: 129 # if installation fails roll back the host 130 try: 131 self.host.close() 132 except: 133 logging.exception("Unable to close host %s", 134 self.host.hostname) 135 self.host = None 136 self.autotest = None 137 raise 138 else: 139 # if autotest client dir does not exist, reinstall (it may have 140 # been removed by the test code) 141 autodir = self.host.get_autodir() 142 if not autodir or not self.host.path_exists(autodir): 143 self.autotest.install(autodir=autodir) 144 145 # if the output dir does not exist, recreate it 146 if not self.host.path_exists(self.outputdir): 147 self.host.run('mkdir -p %s' % self.outputdir) 148 149 return self.host, self.autotest, self.outputdir 150 151 152 def _pull_pickle(self, host, outputdir): 153 """Pulls from the client the pickle file with the saved sysinfo state. 154 """ 155 fd, path = tempfile.mkstemp(dir=self.job.tmpdir) 156 os.close(fd) 157 host.get_file(os.path.join(outputdir, "sysinfo.pickle"), path) 158 self.pickle = path 159 160 161 def _push_pickle(self, host, outputdir): 162 """Pushes the server saved sysinfo pickle file to the client. 163 """ 164 if self.pickle: 165 host.send_file(self.pickle, 166 os.path.join(outputdir, "sysinfo.pickle")) 167 os.remove(self.pickle) 168 self.pickle = None 169 170 171 def _pull_sysinfo_keyval(self, host, outputdir, mytest): 172 """Pulls sysinfo and keyval data from the client. 173 """ 174 # pull the sysinfo data back on to the server 175 host.get_file(os.path.join(outputdir, "sysinfo"), mytest.outputdir) 176 177 # pull the keyval data back into the local one 178 fd, path = tempfile.mkstemp(dir=self.job.tmpdir) 179 os.close(fd) 180 host.get_file(os.path.join(outputdir, "keyval"), path) 181 keyval = utils.read_keyval(path) 182 os.remove(path) 183 mytest.write_test_keyval(keyval) 184 185 186 @log.log_and_ignore_errors("pre-test server sysinfo error:") 187 @install_autotest_and_run 188 def before_hook(self, mytest, host, at, outputdir): 189 # run the pre-test sysinfo script 190 at.run(_sysinfo_before_test_script % outputdir, 191 results_dir=self.job.resultdir) 192 193 self._pull_pickle(host, outputdir) 194 195 196 @log.log_and_ignore_errors("pre-test iteration server sysinfo error:") 197 @install_autotest_and_run 198 def before_iteration_hook(self, mytest, host, at, outputdir): 199 # this function is called after before_hook() se we have sysinfo state 200 # to push to the server 201 self._push_pickle(host, outputdir); 202 # run the pre-test iteration sysinfo script 203 at.run(_sysinfo_iteration_script % 204 (outputdir, 'log_before_each_iteration', mytest.iteration, 205 'before'), 206 results_dir=self.job.resultdir) 207 208 # get the new sysinfo state from the client 209 self._pull_pickle(host, outputdir) 210 211 212 @log.log_and_ignore_errors("post-test iteration server sysinfo error:") 213 @install_autotest_and_run 214 def after_iteration_hook(self, mytest, host, at, outputdir): 215 # push latest sysinfo state to the client 216 self._push_pickle(host, outputdir); 217 # run the post-test iteration sysinfo script 218 at.run(_sysinfo_iteration_script % 219 (outputdir, 'log_after_each_iteration', mytest.iteration, 220 'after'), 221 results_dir=self.job.resultdir) 222 223 # get the new sysinfo state from the client 224 self._pull_pickle(host, outputdir) 225 226 227 @log.log_and_ignore_errors("post-test server sysinfo error:") 228 @install_autotest_and_run 229 def after_hook(self, mytest, host, at, outputdir): 230 self._push_pickle(host, outputdir); 231 # run the post-test sysinfo script 232 at.run(_sysinfo_after_test_script % 233 (outputdir, mytest.success, mytest.force_full_log_collection), 234 results_dir=self.job.resultdir) 235 236 self._pull_sysinfo_keyval(host, outputdir, mytest) 237 238 239 @log.log_and_ignore_errors("post-test server crossystem error:") 240 def after_hook_crossystem_fast(self, mytest): 241 """Collects crossystem log file in fast mode 242 243 This is used in place of after_hook in fast mode. This function will 244 grab output of crossystem but not process other sysinfo logs. 245 """ 246 if not self.host: 247 self.host = hosts.create_target_machine( 248 self.job.machine_dict_list[0]) 249 output_path = '%s/sysinfo' % mytest.outputdir 250 utils.run('mkdir -p %s' % output_path) 251 crossystem_output = self.host.run_output('crossystem') 252 with open('%s/crossystem' % output_path, 'w') as f: 253 f.write(crossystem_output) 254 255 def cleanup(self, host_close=True): 256 if self.host and self.autotest: 257 try: 258 try: 259 self.autotest.uninstall() 260 finally: 261 if host_close: 262 self.host.close() 263 else: 264 self.host.erase_dir_contents(self.outputdir) 265 266 except Exception: 267 # ignoring exceptions here so that we don't hide the true 268 # reason of failure from runtest 269 logging.exception('Error cleaning up the sysinfo autotest/host ' 270 'objects, ignoring it') 271 272 273def runtest(job, url, tag, args, dargs): 274 """Server-side runtest. 275 276 @param job: A server_job instance. 277 @param url: URL to the test. 278 @param tag: Test tag that will be appended to the test name. 279 See client/common_lib/test.py:runtest 280 @param args: args to pass to the test. 281 @param dargs: key-val based args to pass to the test. 282 """ 283 284 disable_before_test_hook = dargs.pop('disable_before_test_sysinfo', False) 285 disable_after_test_hook = dargs.pop('disable_after_test_sysinfo', False) 286 disable_before_iteration_hook = dargs.pop( 287 'disable_before_iteration_sysinfo', False) 288 disable_after_iteration_hook = dargs.pop( 289 'disable_after_iteration_sysinfo', False) 290 291 disable_sysinfo = dargs.pop('disable_sysinfo', False) 292 logger = _sysinfo_logger(job) 293 if job.fast and not disable_sysinfo: 294 # Server job will be executed in fast mode, which means 295 # 1) if job succeeds, no hook will be executed. 296 # 2) if job failed, after_hook will be executed. 297 logging_args = [None, logger.after_hook, None, 298 logger.after_iteration_hook] 299 elif not disable_sysinfo: 300 logging_args = [ 301 logger.before_hook if not disable_before_test_hook else None, 302 logger.after_hook if not disable_after_test_hook else None, 303 (logger.before_iteration_hook 304 if not disable_before_iteration_hook else None), 305 (logger.after_iteration_hook 306 if not disable_after_iteration_hook else None), 307 ] 308 else: 309 logging_args = [None, logger.after_hook_crossystem_fast, None, None] 310 311 # add in a hook that calls host.log_kernel if we can 312 def log_kernel_hook(mytest, existing_hook=logging_args[0]): 313 if mytest.host_parameter: 314 host = dargs[mytest.host_parameter] 315 if host: 316 host.log_kernel() 317 # chain this call with any existing hook 318 if existing_hook: 319 existing_hook(mytest) 320 logging_args[0] = log_kernel_hook 321 322 try: 323 common_test.runtest(job, url, tag, args, dargs, locals(), globals(), 324 *logging_args) 325 finally: 326 if logger: 327 logger.cleanup() 328