autotest.py revision 1b3b376d94edee8f235f8669a047dc00751bf97a
1#!/usr/bin/python 2# 3# Copyright 2007 Google Inc. Released under the GPL v2 4 5""" 6This module defines the Autotest class 7 8 Autotest: software to run tests automatically 9""" 10 11__author__ = """ 12mbligh@google.com (Martin J. Bligh), 13poirier@google.com (Benjamin Poirier), 14stutsman@google.com (Ryan Stutsman) 15""" 16 17import re, os, sys, traceback, subprocess, tempfile, shutil, time, pickle 18 19from autotest_lib.server import installable_object, utils, server_job 20from autotest_lib.client.common_lib import log 21from autotest_lib.client.common_lib import error, global_config, packages 22 23 24 25AUTOTEST_SVN = 'svn://test.kernel.org/autotest/trunk/client' 26AUTOTEST_HTTP = 'http://test.kernel.org/svn/autotest/trunk/client' 27 28# Timeouts for powering down and up respectively 29HALT_TIME = 300 30BOOT_TIME = 1800 31CRASH_RECOVERY_TIME = 9000 32 33 34class BaseAutotest(installable_object.InstallableObject): 35 """ 36 This class represents the Autotest program. 37 38 Autotest is used to run tests automatically and collect the results. 39 It also supports profilers. 40 41 Implementation details: 42 This is a leaf class in an abstract class hierarchy, it must 43 implement the unimplemented methods in parent classes. 44 """ 45 46 def __init__(self, host = None): 47 self.host = host 48 self.got = False 49 self.installed = False 50 self.serverdir = utils.get_server_dir() 51 super(BaseAutotest, self).__init__() 52 53 54 @log.record 55 def install(self, host = None): 56 """ 57 Install autotest. If get() was not called previously, an 58 attempt will be made to install from the autotest svn 59 repository. 60 61 Args: 62 host: a Host instance on which autotest will be 63 installed 64 65 Raises: 66 AutoservError: if a tarball was not specified and 67 the target host does not have svn installed in its path 68 69 TODO(poirier): check dependencies 70 autotest needs: 71 bzcat 72 liboptdev (oprofile) 73 binutils-dev (oprofile) 74 make 75 psutils (netperf) 76 """ 77 if not host: 78 host = self.host 79 if not self.got: 80 self.get() 81 host.wait_up(timeout=30) 82 host.setup() 83 print "Installing autotest on %s" % host.hostname 84 85 # Let's try to figure out where autotest is installed. If we can't, 86 # (autotest not installed) just assume '/usr/local/autotest' and 87 # proceed. 88 try: 89 autodir = _get_autodir(host) 90 except error.AutotestRunError: 91 autodir = '/usr/local/autotest' 92 93 # Make the host object know as to where autotest is installed 94 host.set_autodir(autodir) 95 96 host.run('mkdir -p "%s"' % utils.sh_escape(autodir)) 97 98 # Fetch the autotest client from the nearest repository 99 try: 100 c = global_config.global_config 101 repos = c.get_config_value("PACKAGES", 'fetch_location', type=list) 102 pkgmgr = packages.PackageManager( 103 autodir, repo_urls=repos, do_locking=False, 104 run_function=host.run, 105 run_function_dargs=dict(timeout=600)) 106 # The packages dir is used to store all the packages that 107 # are fetched on that client. (for the tests,deps etc. 108 # too apart from the client) 109 pkg_dir = os.path.join(autodir, 'packages') 110 # clean up the autodir except for the packages directory 111 host.run('cd %s && ls | grep -v "^packages$"' 112 ' | xargs rm -rf && rm -rf .[^.]*' % autodir) 113 pkgmgr.install_pkg('autotest', 'client', pkg_dir, autodir, 114 preserve_install_dir=True) 115 self.installed = True 116 return 117 except global_config.ConfigError, e: 118 print ("Could not install autotest using the" 119 " packaging system %s" % e) 120 except (packages.PackageInstallError, error.AutoservRunError), e: 121 print "Could not install autotest from %s : %s " % (repos, e) 122 123 124 # try to install from file or directory 125 if self.source_material: 126 if os.path.isdir(self.source_material): 127 # Copy autotest recursively 128 host.send_file(self.source_material, autodir) 129 else: 130 # Copy autotest via tarball 131 e_msg = 'Installation method not yet implemented!' 132 raise NotImplementedError(e_msg) 133 print "Installation of autotest completed" 134 self.installed = True 135 return 136 137 # if that fails try to install using svn 138 if utils.run('which svn').exit_status: 139 raise error.AutoservError('svn not found on target machine: %s' 140 % host.name) 141 try: 142 host.run('svn checkout %s %s' % (AUTOTEST_SVN, autodir)) 143 except error.AutoservRunError, e: 144 host.run('svn checkout %s %s' % (AUTOTEST_HTTP, autodir)) 145 print "Installation of autotest completed" 146 self.installed = True 147 148 149 def get(self, location = None): 150 if not location: 151 location = os.path.join(self.serverdir, '../client') 152 location = os.path.abspath(location) 153 # If there's stuff run on our client directory already, it 154 # can cause problems. Try giving it a quick clean first. 155 cwd = os.getcwd() 156 os.chdir(location) 157 os.system('tools/make_clean') 158 os.chdir(cwd) 159 super(BaseAutotest, self).get(location) 160 self.got = True 161 162 163 def run(self, control_file, results_dir = '.', host = None, 164 timeout=None, tag=None, parallel_flag=False): 165 """ 166 Run an autotest job on the remote machine. 167 168 Args: 169 control_file: an open file-like-obj of the control file 170 results_dir: a str path where the results should be stored 171 on the local filesystem 172 host: a Host instance on which the control file should 173 be run 174 tag: tag name for the client side instance of autotest 175 parallel_flag: flag set when multiple jobs are run at the 176 same time 177 Raises: 178 AutotestRunError: if there is a problem executing 179 the control file 180 """ 181 host = self._get_host_and_setup(host) 182 results_dir = os.path.abspath(results_dir) 183 184 if tag: 185 results_dir = os.path.join(results_dir, tag) 186 187 atrun = _Run(host, results_dir, tag, parallel_flag) 188 self._do_run(control_file, results_dir, host, atrun, timeout) 189 190 191 def _get_host_and_setup(self, host): 192 if not host: 193 host = self.host 194 if not self.installed: 195 self.install(host) 196 197 host.wait_up(timeout=30) 198 return host 199 200 201 def _do_run(self, control_file, results_dir, host, atrun, timeout): 202 try: 203 atrun.verify_machine() 204 except: 205 print "Verify machine failed on %s. Reinstalling" % host.hostname 206 self.install(host) 207 atrun.verify_machine() 208 debug = os.path.join(results_dir, 'debug') 209 try: 210 os.makedirs(debug) 211 except: 212 pass 213 214 # Ready .... Aim .... 215 for control in [atrun.remote_control_file, 216 atrun.remote_control_file + '.state', 217 atrun.manual_control_file, 218 atrun.manual_control_file + '.state']: 219 host.run('rm -f ' + control) 220 221 tmppath = utils.get(control_file) 222 223 224 # Prepend the control file with a job.disable_test_cleanup call 225 # if necessary 226 if not host.job.run_test_cleanup: 227 cfile = open(tmppath).read() 228 cfile = "job.disable_test_cleanup()\n" + cfile 229 open(tmppath, "w").write(cfile) 230 231 # Insert the job.add_repository() lines in the control file 232 # if there are any repos defined in global_config.ini 233 try: 234 cfile = open(tmppath, 'r') 235 cfile_orig = cfile.read() 236 cfile.close() 237 c = global_config.global_config 238 repos = c.get_config_value("PACKAGES", 'fetch_location', type=list) 239 control_file_new = [] 240 control_file_new.append('job.add_repository(%s)\n' % repos) 241 control_file_new.append(cfile_orig) 242 243 # Overwrite the control file with the new one 244 cfile = open(tmppath, 'w') 245 cfile.write('\n'.join(control_file_new)) 246 cfile.close() 247 except global_config.ConfigError, e: 248 pass 249 250 251 # Copy control_file to remote_control_file on the host 252 host.send_file(tmppath, atrun.remote_control_file) 253 if os.path.abspath(tmppath) != os.path.abspath(control_file): 254 os.remove(tmppath) 255 256 try: 257 atrun.execute_control(timeout=timeout) 258 finally: 259 collector = server_job.log_collector(host, atrun.tag, results_dir) 260 collector.collect_client_job_results() 261 self._process_client_state_file(host, atrun, results_dir) 262 263 264 def _process_client_state_file(self, host, atrun, results_dir): 265 state_file = os.path.basename(atrun.remote_control_file) + ".state" 266 state_path = os.path.join(results_dir, state_file) 267 try: 268 state_dict = pickle.load(open(state_path)) 269 except Exception: 270 print >> sys.stderr, "Error while loading client job state file" 271 traceback.print_exc() 272 state_dict = {} 273 274 # clear out the state file 275 # TODO: stash the file away somewhere useful instead 276 try: 277 os.remove(state_path) 278 except Exception: 279 pass 280 281 msg = "Persistant state variables pulled back from %s: %s" 282 msg %= (host.hostname, state_dict) 283 print msg 284 285 if "__run_test_cleanup" in state_dict: 286 if state_dict["__run_test_cleanup"]: 287 host.job.enable_test_cleanup() 288 else: 289 host.job.disable_test_cleanup() 290 291 292 def run_timed_test(self, test_name, results_dir='.', host=None, 293 timeout=None, tag=None, *args, **dargs): 294 """ 295 Assemble a tiny little control file to just run one test, 296 and run it as an autotest client-side test 297 """ 298 if not host: 299 host = self.host 300 if not self.installed: 301 self.install(host) 302 opts = ["%s=%s" % (o[0], repr(o[1])) for o in dargs.items()] 303 cmd = ", ".join([repr(test_name)] + map(repr, args) + opts) 304 control = "job.run_test(%s)\n" % cmd 305 self.run(control, results_dir, host, timeout=timeout, tag=tag) 306 307 308 def run_test(self, test_name, results_dir='.', host=None, tag=None, 309 *args, **dargs): 310 self.run_timed_test(test_name, results_dir, host, timeout=None, 311 tag=tag, *args, **dargs) 312 313 314class _Run(object): 315 """ 316 Represents a run of autotest control file. This class maintains 317 all the state necessary as an autotest control file is executed. 318 319 It is not intended to be used directly, rather control files 320 should be run using the run method in Autotest. 321 """ 322 def __init__(self, host, results_dir, tag, parallel_flag): 323 self.host = host 324 self.results_dir = results_dir 325 self.env = host.env 326 self.tag = tag 327 self.parallel_flag = parallel_flag 328 self.autodir = _get_autodir(self.host) 329 control = os.path.join(self.autodir, 'control') 330 if tag: 331 control += '.' + tag 332 self.manual_control_file = control 333 self.remote_control_file = control + '.autoserv' 334 335 336 def verify_machine(self): 337 binary = os.path.join(self.autodir, 'bin/autotest') 338 try: 339 self.host.run('ls %s > /dev/null 2>&1' % binary) 340 except: 341 raise "Autotest does not appear to be installed" 342 343 if not self.parallel_flag: 344 tmpdir = os.path.join(self.autodir, 'tmp') 345 download = os.path.join(self.autodir, 'tests/download') 346 self.host.run('umount %s' % tmpdir, ignore_status=True) 347 self.host.run('umount %s' % download, ignore_status=True) 348 349 def get_full_cmd(self, section): 350 # build up the full command we want to run over the host 351 cmd = [os.path.join(self.autodir, 'bin/autotest_client'), 352 '-H autoserv'] 353 if section > 0: 354 cmd.append('-c') 355 if self.tag: 356 cmd.append('-t %s' % self.tag) 357 if self.host.job.use_external_logging(): 358 cmd.append('-l') 359 cmd.append(self.remote_control_file) 360 return ' '.join(cmd) 361 362 363 def get_client_log(self, section): 364 # open up the files we need for our logging 365 client_log_file = os.path.join(self.results_dir, 'debug', 366 'client.log.%d' % section) 367 return open(client_log_file, 'w', 0) 368 369 370 def execute_section(self, section, timeout): 371 print "Executing %s/bin/autotest %s/control phase %d" % \ 372 (self.autodir, self.autodir, section) 373 374 full_cmd = self.get_full_cmd(section) 375 client_log = self.get_client_log(section) 376 redirector = server_job.client_logger(self.host, self.tag, 377 self.results_dir) 378 379 try: 380 old_resultdir = self.host.job.resultdir 381 self.host.job.resultdir = self.results_dir 382 result = self.host.run(full_cmd, ignore_status=True, 383 timeout=timeout, 384 stdout_tee=client_log, 385 stderr_tee=redirector) 386 finally: 387 redirector.close() 388 self.host.job.resultdir = old_resultdir 389 390 if result.exit_status == 1: 391 self.host.job.aborted = True 392 raise error.AutotestRunError("client job was aborted") 393 if not result.stderr: 394 raise error.AutotestRunError( 395 "execute_section: %s failed to return anything\n" 396 "stdout:%s\n" % (full_cmd, result.stdout)) 397 398 return redirector.last_line 399 400 401 def execute_control(self, timeout=None): 402 section = 0 403 time_left = None 404 if timeout: 405 end_time = time.time() + timeout 406 time_left = end_time - time.time() 407 while not timeout or time_left > 0: 408 last = self.execute_section(section, time_left) 409 if timeout: 410 time_left = end_time - time.time() 411 if time_left <= 0: 412 break 413 section += 1 414 if re.match(r'^END .*\t----\t----\t.*$', last): 415 print "Client complete" 416 return 417 elif re.match('^\t*GOOD\t----\treboot\.start.*$', last): 418 print "Client is rebooting" 419 print "Waiting for client to halt" 420 if not self.host.wait_down(HALT_TIME): 421 err = "%s failed to shutdown after %d" 422 err %= (self.host.hostname, HALT_TIME) 423 raise error.AutotestRunError(err) 424 print "Client down, waiting for restart" 425 if not self.host.wait_up(BOOT_TIME): 426 # since reboot failed 427 # hardreset the machine once if possible 428 # before failing this control file 429 print "Hardresetting %s" % self.host.hostname 430 try: 431 self.host.hardreset(wait=False) 432 except error.AutoservUnsupportedError: 433 print "Hardreset unsupported on %s" % self.host.hostname 434 raise error.AutotestRunError("%s failed to boot after %ds" % 435 (self.host.hostname, BOOT_TIME)) 436 self.host.reboot_followup() 437 continue 438 self.host.job.record("END ABORT", None, None, 439 "Autotest client terminated unexpectedly") 440 # give the client machine a chance to recover from 441 # possible crash 442 self.host.wait_up(CRASH_RECOVERY_TIME) 443 raise error.AutotestRunError("Aborting - unexpected final status " 444 "message from client: %s\n" % last) 445 446 # should only get here if we timed out 447 assert timeout 448 raise error.AutotestTimeoutError() 449 450 451def _get_autodir(host): 452 autodir = host.get_autodir() 453 if autodir: 454 return autodir 455 try: 456 # There's no clean way to do this. readlink may not exist 457 cmd = "python -c 'import os,sys; print os.readlink(sys.argv[1])' /etc/autotest.conf 2> /dev/null" 458 autodir = os.path.dirname(host.run(cmd).stdout) 459 if autodir: 460 return autodir 461 except error.AutoservRunError: 462 pass 463 for path in ['/usr/local/autotest', '/home/autotest']: 464 try: 465 host.run('ls %s > /dev/null 2>&1' % 466 os.path.join(path, 'bin/autotest')) 467 return path 468 except error.AutoservRunError: 469 pass 470 raise error.AutotestRunError("Cannot figure out autotest directory") 471 472 473# site_autotest.py may be non-existant or empty, make sure that an appropriate 474# SiteAutotest class is created nevertheless 475try: 476 from site_autotest import SiteAutotest 477except ImportError: 478 class SiteAutotest(BaseAutotest): 479 pass 480 481 482class Autotest(SiteAutotest): 483 pass 484