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 
      6 import os, tempfile, logging
      7 
      8 from autotest_lib.client.common_lib import log, utils, test as common_test
      9 
     10 
     11 class test(common_test.base_test):
     12     disable_sysinfo_install_cache = False
     13     host_parameter = None
     14 
     15 
     16 _sysinfo_before_test_script = """\
     17 import pickle
     18 from autotest_lib.client.bin import test
     19 mytest = test.test(job, '', %r)
     20 job.sysinfo.log_before_each_test(mytest)
     21 sysinfo_pickle = os.path.join(mytest.outputdir, 'sysinfo.pickle')
     22 pickle.dump(job.sysinfo, open(sysinfo_pickle, 'w'))
     23 job.record('GOOD', '', 'sysinfo.before')
     24 """
     25 
     26 _sysinfo_after_test_script = """\
     27 import pickle
     28 from autotest_lib.client.bin import test
     29 mytest = test.test(job, '', %r)
     30 # success is passed in so diffable_logdir can decide if or not to collect
     31 # full log content.
     32 mytest.success = %s
     33 sysinfo_pickle = os.path.join(mytest.outputdir, 'sysinfo.pickle')
     34 if os.path.exists(sysinfo_pickle):
     35     job.sysinfo = pickle.load(open(sysinfo_pickle))
     36     job.sysinfo.__init__(job.resultdir)
     37 job.sysinfo.log_after_each_test(mytest)
     38 job.record('GOOD', '', 'sysinfo.after')
     39 """
     40 
     41 # this script is ran after _sysinfo_before_test_script and before
     42 # _sysinfo_after_test_script which means the pickle file exists
     43 # already and should be dumped with updated state for
     44 # _sysinfo_after_test_script to pick it up later
     45 _sysinfo_iteration_script = """\
     46 import pickle
     47 from autotest_lib.client.bin import test
     48 mytest = test.test(job, '', %r)
     49 sysinfo_pickle = os.path.join(mytest.outputdir, 'sysinfo.pickle')
     50 if os.path.exists(sysinfo_pickle):
     51     job.sysinfo = pickle.load(open(sysinfo_pickle))
     52     job.sysinfo.__init__(job.resultdir)
     53 job.sysinfo.%s(mytest, iteration=%d)
     54 pickle.dump(job.sysinfo, open(sysinfo_pickle, 'w'))
     55 job.record('GOOD', '', 'sysinfo.iteration.%s')
     56 """
     57 
     58 
     59 def install_autotest_and_run(func):
     60     def wrapper(self, mytest):
     61         host, at, outputdir = self._install()
     62         # TODO(kevcheng): remove when host client install is supported for
     63         # ADBHost. crbug.com/543702
     64         if not host.is_client_install_supported:
     65             logging.debug('host client install not supported, skipping %s:',
     66                           func.__name__)
     67             return
     68 
     69         try:
     70             host.erase_dir_contents(outputdir)
     71             func(self, mytest, host, at, outputdir)
     72         finally:
     73             # the test class can define this flag to make us remove the
     74             # sysinfo install files and outputdir contents after each run
     75             if mytest.disable_sysinfo_install_cache:
     76                 self.cleanup(host_close=False)
     77 
     78     return wrapper
     79 
     80 
     81 class _sysinfo_logger(object):
     82     def __init__(self, job):
     83         self.job = job
     84         self.pickle = None
     85 
     86         # for now support a single host
     87         self.host = None
     88         self.autotest = None
     89         self.outputdir = None
     90 
     91         if len(job.machines) != 1:
     92             # disable logging on multi-machine tests
     93             self.before_hook = self.after_hook = None
     94             self.before_iteration_hook = self.after_iteration_hook = None
     95 
     96 
     97     def _install(self):
     98         if not self.host:
     99             from autotest_lib.server import hosts, autotest
    100             self.host = hosts.create_target_machine(
    101                     self.job.machine_dict_list[0])
    102             # TODO(kevcheng): remove when host client install is supported for
    103             # ADBHost. crbug.com/543702
    104             if not self.host.is_client_install_supported:
    105                 return self.host, None, None
    106             try:
    107                 tmp_dir = self.host.get_tmp_dir(parent="/tmp/sysinfo")
    108                 self.autotest = autotest.Autotest(self.host)
    109                 self.autotest.install(autodir=tmp_dir)
    110                 self.outputdir = self.host.get_tmp_dir()
    111             except:
    112                 # if installation fails roll back the host
    113                 try:
    114                     self.host.close()
    115                 except:
    116                     logging.exception("Unable to close host %s",
    117                                       self.host.hostname)
    118                 self.host = None
    119                 self.autotest = None
    120                 raise
    121         else:
    122             # TODO(kevcheng): remove when host client install is supported for
    123             # ADBHost. crbug.com/543702
    124             if not self.host.is_client_install_supported:
    125                 return self.host, None, None
    126 
    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 
    244 def 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     if not dargs.pop('disable_sysinfo', False):
    263         logger = _sysinfo_logger(job)
    264         logging_args = [
    265             logger.before_hook if not disable_before_test_hook else None,
    266             logger.after_hook if not disable_after_test_hook else None,
    267             (logger.before_iteration_hook
    268                  if not disable_before_iteration_hook else None),
    269             (logger.after_iteration_hook
    270                  if not disable_after_iteration_hook else None),
    271         ]
    272     else:
    273         logger = None
    274         logging_args = [None, None, None, None]
    275 
    276     # add in a hook that calls host.log_kernel if we can
    277     def log_kernel_hook(mytest, existing_hook=logging_args[0]):
    278         if mytest.host_parameter:
    279             host = dargs[mytest.host_parameter]
    280             if host:
    281                 host.log_kernel()
    282         # chain this call with any existing hook
    283         if existing_hook:
    284             existing_hook(mytest)
    285     logging_args[0] = log_kernel_hook
    286 
    287     try:
    288         common_test.runtest(job, url, tag, args, dargs, locals(), globals(),
    289                             *logging_args)
    290     finally:
    291         if logger:
    292             logger.cleanup()
    293