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