autotest.py revision 271d5af3050de888b5d63de7fa799a1d0459082f
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 18import os 19import sys 20import subprocess 21import urllib 22import tempfile 23import shutil 24 25import installable_object 26import errors 27import utils 28 29 30AUTOTEST_SVN = 'svn://test.kernel.org/autotest/trunk/client' 31AUTOTEST_HTTP = 'http://test.kernel.org/svn/autotest/trunk/client' 32 33# Timeouts for powering down and up respectively 34HALT_TIME = 300 35BOOT_TIME = 300 36 37 38class AutotestRunError(errors.AutoservRunError): 39 pass 40 41 42class Autotest(installable_object.InstallableObject): 43 """ 44 This class represents the Autotest program. 45 46 Autotest is used to run tests automatically and collect the results. 47 It also supports profilers. 48 49 Implementation details: 50 This is a leaf class in an abstract class hierarchy, it must 51 implement the unimplemented methods in parent classes. 52 """ 53 def __init__(self, host = None): 54 self.host = host 55 super(Autotest, self).__init__() 56 57 58 def install(self, host = None): 59 """ 60 Install autotest. If get() was not called previously, an 61 attempt will be made to install from the autotest svn 62 repository. 63 64 Args: 65 host: a Host instance on which autotest will be 66 installed 67 68 Raises: 69 AutoservError: if a tarball was not specified and 70 the target host does not have svn installed in its path 71 72 TODO(poirier): check dependencies 73 autotest needs: 74 bzcat 75 liboptdev (oprofile) 76 binutils-dev (oprofile) 77 make 78 psutils (netperf) 79 """ 80 if not host: 81 host = self.host 82 print "Installing autotest on %s" % host.hostname 83 # try to install from file or directory 84 if self.source_material: 85 if os.path.isdir(self.source_material): 86 # Copy autotest recursively 87 autodir = _get_autodir(host) 88 host.run('mkdir -p "%s"' % 89 utils.sh_escape(autodir)) 90 host.send_file(self.source_material, 91 autodir) 92 else: 93 # Copy autotest via tarball 94 raise "Not yet implemented!" 95 return 96 97 # if that fails try to install using svn 98 if utils.run('which svn').exit_status: 99 raise AutoservError('svn not found in path on \ 100 target machine: %s' % host.name) 101 try: 102 host.run('svn checkout %s %s' % 103 (AUTOTEST_SVN, _get_autodir(host))) 104 except errors.AutoservRunError, e: 105 host.run('svn checkout %s %s' % 106 (AUTOTEST_HTTP, _get_autodir(host))) 107 108 109 def run(self, control_file, results_dir, host = None): 110 """ 111 Run an autotest job on the remote machine. 112 113 Args: 114 control_file: an open file-like-obj of the control file 115 results_dir: a str path where the results should be stored 116 on the local filesystem 117 host: a Host instance on which the control file should 118 be run 119 120 Raises: 121 AutotestRunError: if there is a problem executing 122 the control file 123 """ 124 if not host: 125 host = self.host 126 host.ensure_up() 127 128 atrun = _Run(host, results_dir) 129 try: 130 atrun.verify_machine() 131 except: 132 print "Verify machine failed on %s. Reinstalling" % \ 133 host.hostname 134 self.install(host) 135 atrun.verify_machine() 136 if os.path.isdir(results_dir): 137 shutil.rmtree(results_dir) 138 debug = os.path.join(results_dir, 'debug') 139 os.makedirs(debug) 140 141 # Ready .... Aim .... 142 host.run('rm -f ' + atrun.remote_control_file) 143 host.run('rm -f ' + atrun.remote_control_file + '.state') 144 145 # Copy control_file to remote_control_file on the host 146 tmppath = utils.get(control_file) 147 host.send_file(tmppath, atrun.remote_control_file) 148 os.remove(tmppath) 149 150 atrun.execute_control() 151 152 # retrive results 153 results = os.path.join(atrun.autodir, 'results', 'default') 154 # Copy all dirs in default to results_dir 155 host.get_file(results + '/', results_dir) 156 157 158 def run_test(self, test_name, results_dir, host = None, *args, **dargs): 159 """ 160 Assemble a tiny little control file to just run one test, 161 and run it as an autotest client-side test 162 """ 163 if not host: 164 host = self.host 165 opts = ["%s=%s" % (o[0], repr(o[1])) for o in dargs.items()] 166 cmd = ", ".join([repr(test_name)] + map(repr, args) + opts) 167 control = "job.run_test(%s)" % cmd 168 self.run(control, results_dir, host) 169 170 171class _Run(object): 172 """ 173 Represents a run of autotest control file. This class maintains 174 all the state necessary as an autotest control file is executed. 175 176 It is not intended to be used directly, rather control files 177 should be run using the run method in Autotest. 178 """ 179 def __init__(self, host, results_dir): 180 self.host = host 181 self.results_dir = results_dir 182 self.env = '' 183 if hasattr(host, 'env'): 184 self.env = host.env 185 186 self.autodir = _get_autodir(self.host) 187 self.remote_control_file = os.path.join(self.autodir, 'control') 188 189 190 def verify_machine(self): 191 binary = os.path.join(self.autodir, 'bin/autotest') 192 try: 193 self.host.run('ls ' + binary) 194 except: 195 raise "Autotest does not appear to be installed" 196 tmpdir = os.path.join(self.autodir, 'tmp') 197 self.host.run('umount %s' % tmpdir, ignore_status=True) 198 199 200 def __execute_section(self, section): 201 print "Executing %s/bin/autotest %s/control phase %d" % \ 202 (self.autodir, self.autodir, 203 section) 204 logfile = "%s/debug/client.log.%d" % (self.results_dir, 205 section) 206 client_log = open(logfile, 'w') 207 if section > 0: 208 cont = '-c' 209 else: 210 cont = '' 211 client = os.path.join(self.autodir, 'bin/autotest_client') 212 ssh = "ssh -q %s@%s" % (self.host.user, self.host.hostname) 213 cmd = "%s %s %s" % (client, cont, self.remote_control_file) 214 print "%s '%s %s'" % (ssh, self.env, cmd) 215 # Use Popen here, not m.ssh, as we want it in the background 216 p = subprocess.Popen("%s '%s %s'" % (ssh, self.env, cmd), 217 shell=True, 218 stdout=client_log, 219 stderr=subprocess.PIPE) 220 line = None 221 for line in iter(p.stderr.readline, ''): 222 print line, 223 sys.stdout.flush() 224 if not line: 225 raise AutotestRunError("execute_section: %s '%s' \ 226 failed to return anything" % (ssh, cmd)) 227 return line 228 229 230 def execute_control(self): 231 section = 0 232 while True: 233 last = self.__execute_section(section) 234 section += 1 235 if re.match('DONE', last): 236 print "Client complete" 237 return 238 elif re.match('REBOOT', last): 239 print "Client is rebooting" 240 print "Waiting for client to halt" 241 if not self.host.wait_down(HALT_TIME): 242 raise AutotestRunError("%s \ 243 failed to shutdown after %ds" % 244 (self.host.hostname, 245 HALT_TIME)) 246 print "Client down, waiting for restart" 247 if not self.host.wait_up(BOOT_TIME): 248 # since reboot failed 249 # hardreset the machine once if possible 250 # before failing this control file 251 if hasattr(self.host, 'hardreset'): 252 print "Hardresetting %s" % ( 253 self.host.hostname,) 254 self.host.hardreset() 255 raise AutotestRunError("%s failed to " 256 "boot after %ds" % ( 257 self.host.hostname, 258 BOOT_TIME,)) 259 continue 260 raise AutotestRunError("Aborting - unknown " 261 "return code: %s\n" % last) 262 263 264def _get_autodir(host): 265 try: 266 atdir = host.run( 267 'grep "autodir *=" /etc/autotest.conf').stdout.strip() 268 if atdir: 269 m = re.search(r'autodir *= *[\'"]?([^\'"]*)[\'"]?', 270 atdir) 271 return m.group(1) 272 except errors.AutoservRunError: 273 pass 274 for path in ['/usr/local/autotest', '/home/autotest']: 275 try: 276 host.run('ls ' + path) 277 return path 278 except errors.AutoservRunError: 279 pass 280 raise AutotestRunError("Cannot figure out autotest directory") 281