blob: 72794760e9271e0cea326f43041a43905e859b79 [file] [log] [blame]
mblighdcd57a82007-07-11 23:06:47 +00001#!/usr/bin/python
2#
3# Copyright 2007 Google Inc. Released under the GPL v2
4
mblighdc735a22007-08-02 16:54:37 +00005"""
6This module defines the Autotest class
mblighdcd57a82007-07-11 23:06:47 +00007
8 Autotest: software to run tests automatically
9"""
10
mblighdc735a22007-08-02 16:54:37 +000011__author__ = """
12mbligh@google.com (Martin J. Bligh),
mblighdcd57a82007-07-11 23:06:47 +000013poirier@google.com (Benjamin Poirier),
mblighdc735a22007-08-02 16:54:37 +000014stutsman@google.com (Ryan Stutsman)
15"""
mblighdcd57a82007-07-11 23:06:47 +000016
17import re
18import os
19import sys
20import subprocess
21import urllib
22import tempfile
23import shutil
mbligh0e4613b2007-10-29 16:55:07 +000024import time
mblighdcd57a82007-07-11 23:06:47 +000025
26import installable_object
27import errors
28import utils
mbligh119c12a2007-11-12 22:13:44 +000029from common import logging
mblighdcd57a82007-07-11 23:06:47 +000030
31
32AUTOTEST_SVN = 'svn://test.kernel.org/autotest/trunk/client'
33AUTOTEST_HTTP = 'http://test.kernel.org/svn/autotest/trunk/client'
34
35# Timeouts for powering down and up respectively
36HALT_TIME = 300
mbligh07c1eac2007-11-05 18:39:29 +000037BOOT_TIME = 1800
mblighdcd57a82007-07-11 23:06:47 +000038
39
40class AutotestRunError(errors.AutoservRunError):
41 pass
42
mbligh0e4613b2007-10-29 16:55:07 +000043class AutotestTimeoutError(errors.AutoservRunError):
44 """This exception is raised when an autotest test exceeds the timeout
45 parameter passed to run_timed_test and is killed.
46 """
47
mblighdcd57a82007-07-11 23:06:47 +000048
49class Autotest(installable_object.InstallableObject):
mblighdc735a22007-08-02 16:54:37 +000050 """
51 This class represents the Autotest program.
mblighdcd57a82007-07-11 23:06:47 +000052
53 Autotest is used to run tests automatically and collect the results.
54 It also supports profilers.
55
56 Implementation details:
57 This is a leaf class in an abstract class hierarchy, it must
58 implement the unimplemented methods in parent classes.
59 """
mbligh119c12a2007-11-12 22:13:44 +000060 job = None
61
62
mblighdd81ef02007-08-10 01:18:40 +000063 def __init__(self, host = None):
64 self.host = host
mbligh91334902007-09-28 01:47:59 +000065 self.got = False
66 self.installed = False
mbligh9708f732007-10-18 03:18:54 +000067 self.serverdir = utils.get_server_dir()
mblighdcd57a82007-07-11 23:06:47 +000068 super(Autotest, self).__init__()
mblighc8949b82007-07-23 16:33:58 +000069
mblighdc735a22007-08-02 16:54:37 +000070
mbligh119c12a2007-11-12 22:13:44 +000071 @logging.record
mblighdd81ef02007-08-10 01:18:40 +000072 def install(self, host = None):
mblighdc735a22007-08-02 16:54:37 +000073 """
74 Install autotest. If get() was not called previously, an
mblighc8949b82007-07-23 16:33:58 +000075 attempt will be made to install from the autotest svn
76 repository.
mblighdcd57a82007-07-11 23:06:47 +000077
78 Args:
79 host: a Host instance on which autotest will be
80 installed
81
82 Raises:
83 AutoservError: if a tarball was not specified and
84 the target host does not have svn installed in its path
mblighc8949b82007-07-23 16:33:58 +000085
86 TODO(poirier): check dependencies
87 autotest needs:
88 bzcat
89 liboptdev (oprofile)
90 binutils-dev (oprofile)
91 make
mbligh629e39e2007-08-10 19:32:00 +000092 psutils (netperf)
mblighdcd57a82007-07-11 23:06:47 +000093 """
mblighdd81ef02007-08-10 01:18:40 +000094 if not host:
95 host = self.host
mbligh91334902007-09-28 01:47:59 +000096 if not self.got:
97 self.get()
mbligh7386abc2007-08-31 08:57:56 +000098 host.ensure_up()
mbligh91334902007-09-28 01:47:59 +000099 host.setup()
mblighb84a1cf2007-08-09 23:08:13 +0000100 print "Installing autotest on %s" % host.hostname
mbligh40f122a2007-11-03 23:08:46 +0000101
102 autodir = _get_autodir(host)
103 host.run('mkdir -p "%s"' % utils.sh_escape(autodir))
104
105 if getattr(host, 'site_install_autotest', None):
106 if host.site_install_autotest():
107 self.installed = True
108 return
109
mblighdcd57a82007-07-11 23:06:47 +0000110 # try to install from file or directory
mblighc8949b82007-07-23 16:33:58 +0000111 if self.source_material:
112 if os.path.isdir(self.source_material):
mblighdcd57a82007-07-11 23:06:47 +0000113 # Copy autotest recursively
mbligh40f122a2007-11-03 23:08:46 +0000114 host.send_file(self.source_material, autodir)
mblighdcd57a82007-07-11 23:06:47 +0000115 else:
116 # Copy autotest via tarball
117 raise "Not yet implemented!"
mbligh91334902007-09-28 01:47:59 +0000118 print "Installation of autotest completed"
119 self.installed = True
mblighdcd57a82007-07-11 23:06:47 +0000120 return
mbligh91334902007-09-28 01:47:59 +0000121
mblighdcd57a82007-07-11 23:06:47 +0000122 # if that fails try to install using svn
123 if utils.run('which svn').exit_status:
124 raise AutoservError('svn not found in path on \
125 target machine: %s' % host.name)
126 try:
127 host.run('svn checkout %s %s' %
mbligh3850f892007-11-05 22:49:24 +0000128 (AUTOTEST_SVN, autodir))
mblighdcd57a82007-07-11 23:06:47 +0000129 except errors.AutoservRunError, e:
130 host.run('svn checkout %s %s' %
mbligh3850f892007-11-05 22:49:24 +0000131 (AUTOTEST_HTTP, autodir))
mbligh91334902007-09-28 01:47:59 +0000132 print "Installation of autotest completed"
133 self.installed = True
134
135
136 def get(self, location = None):
137 if not location:
138 location = os.path.join(self.serverdir, '../client')
139 location = os.path.abspath(location)
mbligh8fc0e5a2007-10-11 18:39:03 +0000140 # If there's stuff run on our client directory already, it
141 # can cause problems. Try giving it a quick clean first.
142 cwd = os.getcwd()
143 os.chdir(location)
144 os.system('tools/make_clean')
145 os.chdir(cwd)
mbligh91334902007-09-28 01:47:59 +0000146 super(Autotest, self).get(location)
147 self.got = True
mblighdcd57a82007-07-11 23:06:47 +0000148
149
mbligh0e4613b2007-10-29 16:55:07 +0000150 def run(self, control_file, results_dir = '.', host = None,
151 timeout=None):
mblighdcd57a82007-07-11 23:06:47 +0000152 """
153 Run an autotest job on the remote machine.
mblighc8949b82007-07-23 16:33:58 +0000154
mblighdcd57a82007-07-11 23:06:47 +0000155 Args:
156 control_file: an open file-like-obj of the control file
157 results_dir: a str path where the results should be stored
158 on the local filesystem
159 host: a Host instance on which the control file should
160 be run
161
162 Raises:
163 AutotestRunError: if there is a problem executing
164 the control file
165 """
mbligh55a2a3b2007-09-30 01:27:55 +0000166 results_dir = os.path.abspath(results_dir)
mblighdd81ef02007-08-10 01:18:40 +0000167 if not host:
168 host = self.host
mbligh91334902007-09-28 01:47:59 +0000169 if not self.installed:
mbligh7fdd1692007-10-02 19:16:53 +0000170 self.install(host)
mbligh91334902007-09-28 01:47:59 +0000171
mblighdbe4a382007-07-26 19:41:28 +0000172 host.ensure_up()
173
mblighdcd57a82007-07-11 23:06:47 +0000174 atrun = _Run(host, results_dir)
mblighe4f01512007-08-10 01:42:50 +0000175 try:
176 atrun.verify_machine()
177 except:
178 print "Verify machine failed on %s. Reinstalling" % \
179 host.hostname
180 self.install(host)
mblighdcd57a82007-07-11 23:06:47 +0000181 atrun.verify_machine()
182 debug = os.path.join(results_dir, 'debug')
mbligh55a2a3b2007-09-30 01:27:55 +0000183 try:
184 os.makedirs(debug)
185 except:
186 pass
mblighc8949b82007-07-23 16:33:58 +0000187
mblighdcd57a82007-07-11 23:06:47 +0000188 # Ready .... Aim ....
mbligha4bece12007-11-24 19:37:14 +0000189 for control in [atrun.remote_control_file,
190 atrun.remote_control_file + '.state',
191 atrun.manual_control_file,
192 atrun.manual_control_file + '.state']:
193 host.run('rm -f ' + control)
mblighc8949b82007-07-23 16:33:58 +0000194
mblighdcd57a82007-07-11 23:06:47 +0000195 # Copy control_file to remote_control_file on the host
196 tmppath = utils.get(control_file)
197 host.send_file(tmppath, atrun.remote_control_file)
198 os.remove(tmppath)
mbligh0e4613b2007-10-29 16:55:07 +0000199
200 timeout_exc = None
201 try:
202 atrun.execute_control(timeout=timeout)
203 except AutotestTimeoutError, exc:
204 # on timeout, fall though and try to grab results,
205 # and then reraise
206 timeout_exc = exc
207
208 # try to retrive results, even if a timeout occured
209 results = os.path.join(atrun.autodir, 'results',
210 'default')
mblighdcd57a82007-07-11 23:06:47 +0000211 # Copy all dirs in default to results_dir
212 host.get_file(results + '/', results_dir)
213
mbligh0e4613b2007-10-29 16:55:07 +0000214 if timeout_exc:
215 raise timeout_exc
mblighdcd57a82007-07-11 23:06:47 +0000216
mbligh0e4613b2007-10-29 16:55:07 +0000217
218 def run_timed_test(self, test_name, results_dir = '.', host = None,
219 timeout=None, *args, **dargs):
mblighd54832b2007-07-25 16:46:56 +0000220 """
221 Assemble a tiny little control file to just run one test,
222 and run it as an autotest client-side test
223 """
mblighdd81ef02007-08-10 01:18:40 +0000224 if not host:
225 host = self.host
mbligh91334902007-09-28 01:47:59 +0000226 if not self.installed:
mbligh7fdd1692007-10-02 19:16:53 +0000227 self.install(host)
mbligh271d5af2007-08-10 19:54:01 +0000228 opts = ["%s=%s" % (o[0], repr(o[1])) for o in dargs.items()]
229 cmd = ", ".join([repr(test_name)] + map(repr, args) + opts)
mblighf1c52842007-10-16 15:21:38 +0000230 control = "job.run_test(%s)\n" % cmd
mbligh0e4613b2007-10-29 16:55:07 +0000231 self.run(control, results_dir, host, timeout=timeout)
232
233
234 def run_test(self, test_name, results_dir = '.', host = None,
235 *args, **dargs):
236 self.run_timed_test(test_name, results_dir, host, None,
237 *args, **dargs)
mblighd54832b2007-07-25 16:46:56 +0000238
239
mblighdcd57a82007-07-11 23:06:47 +0000240class _Run(object):
241 """
242 Represents a run of autotest control file. This class maintains
243 all the state necessary as an autotest control file is executed.
244
245 It is not intended to be used directly, rather control files
246 should be run using the run method in Autotest.
247 """
248 def __init__(self, host, results_dir):
249 self.host = host
250 self.results_dir = results_dir
mblighcc53b352007-10-24 21:15:30 +0000251 self.env = host.env
mblighc8949b82007-07-23 16:33:58 +0000252
mblighdcd57a82007-07-11 23:06:47 +0000253 self.autodir = _get_autodir(self.host)
mbligha4bece12007-11-24 19:37:14 +0000254 self.manual_control_file = os.path.join(self.autodir, 'control')
255 self.remote_control_file = os.path.join(self.autodir,
256 'control.autoserv')
mblighdc735a22007-08-02 16:54:37 +0000257
258
mblighdcd57a82007-07-11 23:06:47 +0000259 def verify_machine(self):
260 binary = os.path.join(self.autodir, 'bin/autotest')
mblighdd81ef02007-08-10 01:18:40 +0000261 try:
262 self.host.run('ls ' + binary)
263 except:
mbligh8d7e3472007-08-10 01:35:18 +0000264 raise "Autotest does not appear to be installed"
265 tmpdir = os.path.join(self.autodir, 'tmp')
266 self.host.run('umount %s' % tmpdir, ignore_status=True)
mblighdc735a22007-08-02 16:54:37 +0000267
268
mbligh0e4613b2007-10-29 16:55:07 +0000269 def __execute_section(self, section, timeout):
mblighdcd57a82007-07-11 23:06:47 +0000270 print "Executing %s/bin/autotest %s/control phase %d" % \
271 (self.autodir, self.autodir,
272 section)
mbligh0e4613b2007-10-29 16:55:07 +0000273
mblighadf2aab2007-11-29 18:16:43 +0000274 # open up the files we need for our logging
275 client_log_file = os.path.join(self.results_dir, 'debug',
276 'client.log.%d' % section)
277 client_log = open(client_log_file, 'w', 0)
mblighd2fc50f2007-10-23 22:38:00 +0000278 status_log_file = os.path.join(self.results_dir, 'status.log')
279 status_log = open(status_log_file, 'a', 0)
mbligh0e4613b2007-10-29 16:55:07 +0000280
mblighadf2aab2007-11-29 18:16:43 +0000281 # create a file-like object for catching the stderr text
282 # from the autotest client and extracting status logs from it
mbligh0e4613b2007-10-29 16:55:07 +0000283 class StdErrRedirector(object):
284 """Partial file object to write to both stdout and
285 the status log file. We only implement those methods
286 utils.run() actually calls.
287 """
mbligh2bf2db62007-11-27 00:53:18 +0000288 def __init__(self):
289 self.leftover = ""
290 self.last_line = ""
mbligh0e4613b2007-10-29 16:55:07 +0000291
mbligh2bf2db62007-11-27 00:53:18 +0000292 def _process_line(self, line):
293 """Write out a line of data to the appropriate
294 stream. Status lines sent by autotest will be
295 prepended with "AUTOTEST_STATUS", and all other
296 lines are ssh error messages.
297 """
298 if line.startswith("AUTOTEST_STATUS:"):
299 line = line[16:] + "\n"
300 sys.stdout.write(line)
301 status_log.write(line)
302 self.last_line = line
303 else:
304 sys.stderr.write(line + "\n")
305
306 def write(self, data):
307 data = self.leftover + data
308 lines = data.split("\n")
309 # process every line but the last one
310 for line in lines[:-1]:
311 self._process_line(line)
312 # save the last line for later processing
313 # since we may not have the whole line yet
314 self.leftover = lines[-1]
mbligha4bece12007-11-24 19:37:14 +0000315
mbligh0e4613b2007-10-29 16:55:07 +0000316 def flush(self):
317 sys.stdout.flush()
mbligh2bf2db62007-11-27 00:53:18 +0000318 sys.stderr.flush()
mbligh0e4613b2007-10-29 16:55:07 +0000319 status_log.flush()
320
mbligh2bf2db62007-11-27 00:53:18 +0000321 def close(self):
322 if self.leftover:
323 self._process_line(self.leftover)
324 self.flush()
mbligh2bf2db62007-11-27 00:53:18 +0000325 redirector = StdErrRedirector()
mblighadf2aab2007-11-29 18:16:43 +0000326
327 # build up the full command we want to run over the host
328 cmd = [os.path.join(self.autodir, 'bin/autotest_client')]
329 if section > 0:
330 cmd.append('-c')
331 cmd.append(self.remote_control_file)
332 full_cmd = ' '.join(cmd)
333
334 result = self.host.run(full_cmd, ignore_status=True,
335 timeout=timeout,
336 stdout_tee=client_log,
337 stderr_tee=redirector)
mbligh2bf2db62007-11-27 00:53:18 +0000338 redirector.close()
339
mblighcf732d12007-11-21 18:15:03 +0000340 if result.exit_status == 1:
mblighfaf0cd42007-11-19 16:00:24 +0000341 self.host.job.aborted = True
mbligh0e4613b2007-10-29 16:55:07 +0000342 if not result.stderr:
343 raise AutotestRunError(
344 "execute_section: %s failed to return anything\n"
345 "stdout:%s\n" % (full_cmd, result.stdout))
346
mbligh2bf2db62007-11-27 00:53:18 +0000347 return redirector.last_line
mblighdc735a22007-08-02 16:54:37 +0000348
349
mbligh0e4613b2007-10-29 16:55:07 +0000350 def execute_control(self, timeout=None):
mblighdcd57a82007-07-11 23:06:47 +0000351 section = 0
mbligh0e4613b2007-10-29 16:55:07 +0000352 time_left = None
353 if timeout:
354 end_time = time.time() + timeout
355 time_left = end_time - time.time()
356 while not timeout or time_left > 0:
357 last = self.__execute_section(section, time_left)
358 if timeout:
359 time_left = end_time - time.time()
360 if time_left <= 0:
361 break
mblighdcd57a82007-07-11 23:06:47 +0000362 section += 1
mblighc3430162007-11-14 23:57:19 +0000363 if re.match(r'^END .*\t----\t----\t.*$', last):
mblighdcd57a82007-07-11 23:06:47 +0000364 print "Client complete"
365 return
mblighc3430162007-11-14 23:57:19 +0000366 elif re.match('^\t*GOOD\t----\treboot\.start.*$', last):
mblighdcd57a82007-07-11 23:06:47 +0000367 print "Client is rebooting"
368 print "Waiting for client to halt"
369 if not self.host.wait_down(HALT_TIME):
370 raise AutotestRunError("%s \
371 failed to shutdown after %ds" %
mblighc8949b82007-07-23 16:33:58 +0000372 (self.host.hostname,
mblighdcd57a82007-07-11 23:06:47 +0000373 HALT_TIME))
374 print "Client down, waiting for restart"
375 if not self.host.wait_up(BOOT_TIME):
376 # since reboot failed
377 # hardreset the machine once if possible
378 # before failing this control file
mblighba81c682007-10-25 15:35:59 +0000379 print "Hardresetting %s" % (
380 self.host.hostname,)
381 try:
382 self.host.hardreset(wait=False)
383 except errors.AutoservUnsupportedError:
384 print "Hardreset unsupported on %s" % (
385 self.host.hostname,)
mblighc8949b82007-07-23 16:33:58 +0000386 raise AutotestRunError("%s failed to "
387 "boot after %ds" % (
388 self.host.hostname,
389 BOOT_TIME,))
mblighdcd57a82007-07-11 23:06:47 +0000390 continue
mblighc8949b82007-07-23 16:33:58 +0000391 raise AutotestRunError("Aborting - unknown "
392 "return code: %s\n" % last)
mblighdcd57a82007-07-11 23:06:47 +0000393
mbligh0e4613b2007-10-29 16:55:07 +0000394 # should only get here if we timed out
395 assert timeout
396 raise AutotestTimeoutError()
397
mblighdcd57a82007-07-11 23:06:47 +0000398
399def _get_autodir(host):
400 try:
mblighe988aa52007-11-24 19:40:41 +0000401 # There's no clean way to do this. readlink may not exist
402 cmd = "python -c 'import os,sys; print os.readlink(sys.argv[1])' /etc/autotest.conf"
403 dir = os.path.dirname(host.run(cmd).stdout)
404 if dir:
405 return dir
mblighdcd57a82007-07-11 23:06:47 +0000406 except errors.AutoservRunError:
407 pass
408 for path in ['/usr/local/autotest', '/home/autotest']:
409 try:
mblighe988aa52007-11-24 19:40:41 +0000410 host.run('ls ' + os.path.join(path, 'bin/autotest'))
mblighdcd57a82007-07-11 23:06:47 +0000411 return path
412 except errors.AutoservRunError:
413 pass
414 raise AutotestRunError("Cannot figure out autotest directory")