autotest.py revision 6e37245a18a23cf3093b66be065370de486fa1f4
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 """ 79 if not host: 80 host = self.host 81 print "Installing autotest on %s" % host.hostname 82 # try to install from file or directory 83 if self.source_material: 84 if os.path.isdir(self.source_material): 85 # Copy autotest recursively 86 autodir = _get_autodir(host) 87 host.run('mkdir -p "%s"' % 88 utils.sh_escape(autodir)) 89 host.send_file(self.source_material, 90 autodir) 91 else: 92 # Copy autotest via tarball 93 raise "Not yet implemented!" 94 return 95 96 # if that fails try to install using svn 97 if utils.run('which svn').exit_status: 98 raise AutoservError('svn not found in path on \ 99 target machine: %s' % host.name) 100 try: 101 host.run('svn checkout %s %s' % 102 (AUTOTEST_SVN, _get_autodir(host))) 103 except errors.AutoservRunError, e: 104 host.run('svn checkout %s %s' % 105 (AUTOTEST_HTTP, _get_autodir(host))) 106 107 108 def run(self, control_file, results_dir, host = None): 109 """ 110 Run an autotest job on the remote machine. 111 112 Args: 113 control_file: an open file-like-obj of the control file 114 results_dir: a str path where the results should be stored 115 on the local filesystem 116 host: a Host instance on which the control file should 117 be run 118 119 Raises: 120 AutotestRunError: if there is a problem executing 121 the control file 122 """ 123 if not host: 124 host = self.host 125 host.ensure_up() 126 127 atrun = _Run(host, results_dir) 128 try: 129 atrun.verify_machine() 130 except: 131 print "Verify machine failed on %s. Reinstalling" % \ 132 host.hostname 133 self.install(host) 134 atrun.verify_machine() 135 if os.path.isdir(results_dir): 136 shutil.rmtree(results_dir) 137 debug = os.path.join(results_dir, 'debug') 138 os.makedirs(debug) 139 140 # Ready .... Aim .... 141 host.run('rm -f ' + atrun.remote_control_file) 142 host.run('rm -f ' + atrun.remote_control_file + '.state') 143 144 # Copy control_file to remote_control_file on the host 145 tmppath = utils.get(control_file) 146 host.send_file(tmppath, atrun.remote_control_file) 147 os.remove(tmppath) 148 149 atrun.execute_control() 150 151 # retrive results 152 results = os.path.join(atrun.autodir, 'results', 'default') 153 # Copy all dirs in default to results_dir 154 host.get_file(results + '/', results_dir) 155 156 157 def run_test(self, test_name, results_dir, host = None, **options): 158 """ 159 Assemble a tiny little control file to just run one test, 160 and run it as an autotest client-side test 161 """ 162 if not host: 163 host = self.host 164 args = ["%s=%s" % (o[0], repr(o[1])) for o in options.items()] 165 args = ", ".join([repr(test_name)] + args) 166 control = "job.run_test(%s)\n" % args 167 self.run(control, results_dir, host) 168 169 170class _Run(object): 171 """ 172 Represents a run of autotest control file. This class maintains 173 all the state necessary as an autotest control file is executed. 174 175 It is not intended to be used directly, rather control files 176 should be run using the run method in Autotest. 177 """ 178 def __init__(self, host, results_dir): 179 self.host = host 180 self.results_dir = results_dir 181 self.env = '' 182 if hasattr(host, 'env'): 183 self.env = host.env 184 185 self.autodir = _get_autodir(self.host) 186 self.remote_control_file = os.path.join(self.autodir, 'control') 187 188 189 def verify_machine(self): 190 binary = os.path.join(self.autodir, 'bin/autotest') 191 try: 192 self.host.run('ls ' + binary) 193 except: 194 raise "Autotest does not appear to be installed" 195 tmpdir = os.path.join(self.autodir, 'tmp') 196 self.host.run('umount %s' % tmpdir, ignore_status=True) 197 198 199 def __execute_section(self, section): 200 print "Executing %s/bin/autotest %s/control phase %d" % \ 201 (self.autodir, self.autodir, 202 section) 203 logfile = "%s/debug/client.log.%d" % (self.results_dir, 204 section) 205 client_log = open(logfile, 'w') 206 if section > 0: 207 cont = '-c' 208 else: 209 cont = '' 210 client = os.path.join(self.autodir, 'bin/autotest_client') 211 ssh = "ssh -q %s@%s" % (self.host.user, self.host.hostname) 212 cmd = "%s %s %s" % (client, cont, self.remote_control_file) 213 print "%s '%s %s'" % (ssh, self.env, cmd) 214 # Use Popen here, not m.ssh, as we want it in the background 215 p = subprocess.Popen("%s '%s %s'" % (ssh, self.env, cmd), 216 shell=True, 217 stdout=client_log, 218 stderr=subprocess.PIPE) 219 line = None 220 for line in iter(p.stderr.readline, ''): 221 print line, 222 sys.stdout.flush() 223 if not line: 224 raise AutotestRunError("execute_section: %s '%s' \ 225 failed to return anything" % (ssh, cmd)) 226 return line 227 228 229 def execute_control(self): 230 section = 0 231 while True: 232 last = self.__execute_section(section) 233 section += 1 234 if re.match('DONE', last): 235 print "Client complete" 236 return 237 elif re.match('REBOOT', last): 238 print "Client is rebooting" 239 print "Waiting for client to halt" 240 if not self.host.wait_down(HALT_TIME): 241 raise AutotestRunError("%s \ 242 failed to shutdown after %ds" % 243 (self.host.hostname, 244 HALT_TIME)) 245 print "Client down, waiting for restart" 246 if not self.host.wait_up(BOOT_TIME): 247 # since reboot failed 248 # hardreset the machine once if possible 249 # before failing this control file 250 if hasattr(self.host, 'hardreset'): 251 print "Hardresetting %s" % ( 252 self.host.hostname,) 253 self.host.hardreset() 254 raise AutotestRunError("%s failed to " 255 "boot after %ds" % ( 256 self.host.hostname, 257 BOOT_TIME,)) 258 continue 259 raise AutotestRunError("Aborting - unknown " 260 "return code: %s\n" % last) 261 262 263def _get_autodir(host): 264 try: 265 atdir = host.run( 266 'grep "autodir *=" /etc/autotest.conf').stdout.strip() 267 if atdir: 268 m = re.search(r'autodir *= *[\'"]?([^\'"]*)[\'"]?', 269 atdir) 270 return m.group(1) 271 except errors.AutoservRunError: 272 pass 273 for path in ['/usr/local/autotest', '/home/autotest']: 274 try: 275 host.run('ls ' + path) 276 return path 277 except errors.AutoservRunError: 278 pass 279 raise AutotestRunError("Cannot figure out autotest directory") 280