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