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