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     AUTOTEST_PARENT_DIR = '/tmp/sysinfo'
     88     OUTPUT_PARENT_DIR = '/tmp'
     89 
     90     def __init__(self, job):
     91         self.job = job
     92         self.pickle = None
     93 
     94         # for now support a single host
     95         self.host = None
     96         self.autotest = None
     97         self.outputdir = None
     98 
     99         if len(job.machines) != 1:
    100             # disable logging on multi-machine tests
    101             self.before_hook = self.after_hook = None
    102             self.before_iteration_hook = self.after_iteration_hook = None
    103 
    104 
    105     def _install(self):
    106         if not self.host:
    107             from autotest_lib.server import hosts, autotest
    108             self.host = hosts.create_target_machine(
    109                     self.job.machine_dict_list[0])
    110             # TODO(kevcheng): remove when host client install is supported for
    111             # ADBHost. crbug.com/543702
    112             if not self.host.is_client_install_supported:
    113                 return self.host, None, None
    114             try:
    115                 # Remove existing autoserv-* directories before creating more
    116                 self.host.delete_all_tmp_dirs(self.AUTOTEST_PARENT_DIR)
    117                 self.host.delete_all_tmp_dirs(self.OUTPUT_PARENT_DIR)
    118 
    119                 tmp_dir = self.host.get_tmp_dir(self.AUTOTEST_PARENT_DIR)
    120                 self.autotest = autotest.Autotest(self.host)
    121                 self.autotest.install(autodir=tmp_dir)
    122                 self.outputdir = self.host.get_tmp_dir(self.OUTPUT_PARENT_DIR)
    123             except:
    124                 # if installation fails roll back the host
    125                 try:
    126                     self.host.close()
    127                 except:
    128                     logging.exception("Unable to close host %s",
    129                                       self.host.hostname)
    130                 self.host = None
    131                 self.autotest = None
    132                 raise
    133         else:
    134             # TODO(kevcheng): remove when host client install is supported for
    135             # ADBHost. crbug.com/543702
    136             if not self.host.is_client_install_supported:
    137                 return self.host, None, None
    138 
    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 % (outputdir, mytest.success),
    233                results_dir=self.job.resultdir)
    234 
    235         self._pull_sysinfo_keyval(host, outputdir, mytest)
    236 
    237 
    238     def cleanup(self, host_close=True):
    239         if self.host and self.autotest:
    240             try:
    241                 try:
    242                     self.autotest.uninstall()
    243                 finally:
    244                     if host_close:
    245                         self.host.close()
    246                     else:
    247                         self.host.erase_dir_contents(self.outputdir)
    248 
    249             except Exception:
    250                 # ignoring exceptions here so that we don't hide the true
    251                 # reason of failure from runtest
    252                 logging.exception('Error cleaning up the sysinfo autotest/host '
    253                                   'objects, ignoring it')
    254 
    255 
    256 def runtest(job, url, tag, args, dargs):
    257     """Server-side runtest.
    258 
    259     @param job: A server_job instance.
    260     @param url: URL to the test.
    261     @param tag: Test tag that will be appended to the test name.
    262                 See client/common_lib/test.py:runtest
    263     @param args: args to pass to the test.
    264     @param dargs: key-val based args to pass to the test.
    265     """
    266 
    267     disable_before_test_hook = dargs.pop('disable_before_test_sysinfo', False)
    268     disable_after_test_hook = dargs.pop('disable_after_test_sysinfo', False)
    269     disable_before_iteration_hook = dargs.pop(
    270             'disable_before_iteration_sysinfo', False)
    271     disable_after_iteration_hook = dargs.pop(
    272             'disable_after_iteration_sysinfo', False)
    273 
    274     disable_sysinfo = dargs.pop('disable_sysinfo', False)
    275     if job.fast and not disable_sysinfo:
    276         # Server job will be executed in fast mode, which means
    277         # 1) if job succeeds, no hook will be executed.
    278         # 2) if job failed, after_hook will be executed.
    279         logger = _sysinfo_logger(job)
    280         logging_args = [None, logger.after_hook, None,
    281                         logger.after_iteration_hook]
    282     elif not disable_sysinfo:
    283         logger = _sysinfo_logger(job)
    284         logging_args = [
    285             logger.before_hook if not disable_before_test_hook else None,
    286             logger.after_hook if not disable_after_test_hook else None,
    287             (logger.before_iteration_hook
    288                  if not disable_before_iteration_hook else None),
    289             (logger.after_iteration_hook
    290                  if not disable_after_iteration_hook else None),
    291         ]
    292     else:
    293         logger = None
    294         logging_args = [None, None, None, None]
    295 
    296     # add in a hook that calls host.log_kernel if we can
    297     def log_kernel_hook(mytest, existing_hook=logging_args[0]):
    298         if mytest.host_parameter:
    299             host = dargs[mytest.host_parameter]
    300             if host:
    301                 host.log_kernel()
    302         # chain this call with any existing hook
    303         if existing_hook:
    304             existing_hook(mytest)
    305     logging_args[0] = log_kernel_hook
    306 
    307     try:
    308         common_test.runtest(job, url, tag, args, dargs, locals(), globals(),
    309                             *logging_args)
    310     finally:
    311         if logger:
    312             logger.cleanup()
    313