blob: f4c6c585b1d971941758912abccbd01b294e3422 [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
mblighdcd57a82007-07-11 23:06:47 +000027import utils
mbligh119c12a2007-11-12 22:13:44 +000028from common import logging
mbligh03f4fc72007-11-29 20:56:14 +000029from common.error import *
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
mbligh03f4fc72007-11-29 20:56:14 +000040class AutotestRunError(AutoservRunError):
mblighdcd57a82007-07-11 23:06:47 +000041 pass
42
mbligh03f4fc72007-11-29 20:56:14 +000043class AutotestTimeoutError(AutoservRunError):
mbligh0e4613b2007-10-29 16:55:07 +000044 """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))
mbligh03f4fc72007-11-29 20:56:14 +0000129 except AutoservRunError, e:
mblighdcd57a82007-07-11 23:06:47 +0000130 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)
mbligh4918bdb2007-12-13 16:06:09 +0000198 if os.path.abspath(tmppath) != os.path.abspath(control_file):
199 os.remove(tmppath)
mbligh0e4613b2007-10-29 16:55:07 +0000200
mbligh0e4613b2007-10-29 16:55:07 +0000201 try:
202 atrun.execute_control(timeout=timeout)
mbligh35b225c2007-12-10 17:17:27 +0000203 finally:
mbligh1f194902007-12-20 01:31:43 +0000204 # make an effort to wait for the machine to come up
205 try:
206 host.ensure_up()
207 except AutoservError:
208 # don't worry about any errors, we'll try and
209 # get the results anyway
210 pass
211
mbligh35b225c2007-12-10 17:17:27 +0000212 # get the results
213 results = os.path.join(atrun.autodir, 'results',
214 'default')
215 # Copy all dirs in default to results_dir
216 host.get_file(results + '/', results_dir)
mblighdcd57a82007-07-11 23:06:47 +0000217
mbligh0e4613b2007-10-29 16:55:07 +0000218
219 def run_timed_test(self, test_name, results_dir = '.', host = None,
220 timeout=None, *args, **dargs):
mblighd54832b2007-07-25 16:46:56 +0000221 """
222 Assemble a tiny little control file to just run one test,
223 and run it as an autotest client-side test
224 """
mblighdd81ef02007-08-10 01:18:40 +0000225 if not host:
226 host = self.host
mbligh91334902007-09-28 01:47:59 +0000227 if not self.installed:
mbligh7fdd1692007-10-02 19:16:53 +0000228 self.install(host)
mbligh271d5af2007-08-10 19:54:01 +0000229 opts = ["%s=%s" % (o[0], repr(o[1])) for o in dargs.items()]
230 cmd = ", ".join([repr(test_name)] + map(repr, args) + opts)
mblighf1c52842007-10-16 15:21:38 +0000231 control = "job.run_test(%s)\n" % cmd
mbligh0e4613b2007-10-29 16:55:07 +0000232 self.run(control, results_dir, host, timeout=timeout)
233
234
235 def run_test(self, test_name, results_dir = '.', host = None,
236 *args, **dargs):
237 self.run_timed_test(test_name, results_dir, host, None,
238 *args, **dargs)
mblighd54832b2007-07-25 16:46:56 +0000239
240
mblighd528d302007-12-19 16:19:05 +0000241# a file-like object for catching stderr from the autotest client and
242# extracting status logs from it
243class StdErrRedirector(object):
244 """Partial file object to write to both stdout and
245 the status log file. We only implement those methods
246 utils.run() actually calls.
247 """
248 parser = re.compile(r"^AUTOTEST_STATUS:([^:]*):(.*)$")
249
250 def __init__(self, status_log):
251 self.status_log = status_log
252 self.leftover = ""
253 self.last_line = ""
254 self.logs = {}
255
256
257 def _process_log_dict(self, log_dict):
258 log_list = log_dict.pop("logs", [])
259 for key in sorted(log_dict.iterkeys()):
260 log_list += self._process_log_dict(log_dict.pop(key))
261 return log_list
262
263
264 def _process_logs(self):
265 """Go through the accumulated logs in self.log and print them
266 out to stdout and the status log. Note that this processes
267 logs in an ordering where:
268
269 1) logs to different tags are never interleaved
270 2) logs to x.y come before logs to x.y.z for all z
271 3) logs to x.y come before x.z whenever y < z
272
273 Note that this will in general not be the same as the
274 chronological ordering of the logs. However, if a chronological
275 ordering is desired that one can be reconstructed from the
276 status log by looking at timestamp lines."""
277 log_list = self._process_log_dict(self.logs)
278 for line in log_list:
279 print >> self.status_log, line
280 if log_list:
281 self.last_line = log_list[-1]
282
283
284 def _process_quoted_line(self, tag, line):
285 """Process a line quoted with an AUTOTEST_STATUS flag. If the
286 tag is blank then we want to push out all the data we've been
287 building up in self.logs, and then the newest line. If the
288 tag is not blank, then push the line into the logs for handling
289 later."""
290 print line
291 if tag == "":
292 self._process_logs()
293 print >> self.status_log, line
294 self.last_line = line
295 else:
296 tag_parts = [int(x) for x in tag.split(".")]
297 log_dict = self.logs
298 for part in tag_parts:
299 log_dict = log_dict.setdefault(part, {})
300 log_list = log_dict.setdefault("logs", [])
301 log_list.append(line)
302
303
304 def _process_line(self, line):
305 """Write out a line of data to the appropriate stream. Status
306 lines sent by autotest will be prepended with
307 "AUTOTEST_STATUS", and all other lines are ssh error
308 messages."""
309 match = self.parser.search(line)
310 if match:
311 tag, line = match.groups()
312 self._process_quoted_line(tag, line)
313 else:
314 print >> sys.stderr, line
315
316
317 def write(self, data):
318 data = self.leftover + data
319 lines = data.split("\n")
320 # process every line but the last one
321 for line in lines[:-1]:
322 self._process_line(line)
323 # save the last line for later processing
324 # since we may not have the whole line yet
325 self.leftover = lines[-1]
326
327
328 def flush(self):
329 sys.stdout.flush()
330 sys.stderr.flush()
331 self.status_log.flush()
332
333
334 def close(self):
335 if self.leftover:
336 self._process_line(self.leftover)
337 self._process_logs()
338 self.flush()
339
340
mblighdcd57a82007-07-11 23:06:47 +0000341class _Run(object):
342 """
343 Represents a run of autotest control file. This class maintains
344 all the state necessary as an autotest control file is executed.
345
346 It is not intended to be used directly, rather control files
347 should be run using the run method in Autotest.
348 """
349 def __init__(self, host, results_dir):
350 self.host = host
351 self.results_dir = results_dir
mblighcc53b352007-10-24 21:15:30 +0000352 self.env = host.env
mblighd528d302007-12-19 16:19:05 +0000353
mblighdcd57a82007-07-11 23:06:47 +0000354 self.autodir = _get_autodir(self.host)
mbligha4bece12007-11-24 19:37:14 +0000355 self.manual_control_file = os.path.join(self.autodir, 'control')
356 self.remote_control_file = os.path.join(self.autodir,
357 'control.autoserv')
mblighdc735a22007-08-02 16:54:37 +0000358
359
mblighdcd57a82007-07-11 23:06:47 +0000360 def verify_machine(self):
361 binary = os.path.join(self.autodir, 'bin/autotest')
mblighdd81ef02007-08-10 01:18:40 +0000362 try:
mbligh548197f2007-12-12 15:36:22 +0000363 self.host.run('ls %s > /dev/null' % binary)
mblighdd81ef02007-08-10 01:18:40 +0000364 except:
mbligh8d7e3472007-08-10 01:35:18 +0000365 raise "Autotest does not appear to be installed"
366 tmpdir = os.path.join(self.autodir, 'tmp')
367 self.host.run('umount %s' % tmpdir, ignore_status=True)
mblighdc735a22007-08-02 16:54:37 +0000368
369
mbligh0e4613b2007-10-29 16:55:07 +0000370 def __execute_section(self, section, timeout):
mblighdcd57a82007-07-11 23:06:47 +0000371 print "Executing %s/bin/autotest %s/control phase %d" % \
372 (self.autodir, self.autodir,
373 section)
mbligh0e4613b2007-10-29 16:55:07 +0000374
mblighadf2aab2007-11-29 18:16:43 +0000375 # build up the full command we want to run over the host
376 cmd = [os.path.join(self.autodir, 'bin/autotest_client')]
377 if section > 0:
378 cmd.append('-c')
379 cmd.append(self.remote_control_file)
380 full_cmd = ' '.join(cmd)
381
mblighd528d302007-12-19 16:19:05 +0000382 # open up the files we need for our logging
383 client_log_file = os.path.join(self.results_dir, 'debug',
384 'client.log.%d' % section)
385 client_log = open(client_log_file, 'w', 0)
386 status_log_file = os.path.join(self.results_dir, 'status.log')
387 status_log = open(status_log_file, 'a', 0)
388
389 try:
390 redirector = StdErrRedirector(status_log)
391 result = self.host.run(full_cmd, ignore_status=True,
392 timeout=timeout,
393 stdout_tee=client_log,
394 stderr_tee=redirector)
395 finally:
396 redirector.close()
mbligh2bf2db62007-11-27 00:53:18 +0000397
mblighcf732d12007-11-21 18:15:03 +0000398 if result.exit_status == 1:
mblighfaf0cd42007-11-19 16:00:24 +0000399 self.host.job.aborted = True
mbligh0e4613b2007-10-29 16:55:07 +0000400 if not result.stderr:
401 raise AutotestRunError(
402 "execute_section: %s failed to return anything\n"
403 "stdout:%s\n" % (full_cmd, result.stdout))
404
mbligh2bf2db62007-11-27 00:53:18 +0000405 return redirector.last_line
mblighdc735a22007-08-02 16:54:37 +0000406
407
mbligh0e4613b2007-10-29 16:55:07 +0000408 def execute_control(self, timeout=None):
mblighdcd57a82007-07-11 23:06:47 +0000409 section = 0
mbligh0e4613b2007-10-29 16:55:07 +0000410 time_left = None
411 if timeout:
412 end_time = time.time() + timeout
413 time_left = end_time - time.time()
414 while not timeout or time_left > 0:
415 last = self.__execute_section(section, time_left)
416 if timeout:
417 time_left = end_time - time.time()
418 if time_left <= 0:
419 break
mblighdcd57a82007-07-11 23:06:47 +0000420 section += 1
mblighc3430162007-11-14 23:57:19 +0000421 if re.match(r'^END .*\t----\t----\t.*$', last):
mblighdcd57a82007-07-11 23:06:47 +0000422 print "Client complete"
423 return
mblighc3430162007-11-14 23:57:19 +0000424 elif re.match('^\t*GOOD\t----\treboot\.start.*$', last):
mblighdcd57a82007-07-11 23:06:47 +0000425 print "Client is rebooting"
426 print "Waiting for client to halt"
427 if not self.host.wait_down(HALT_TIME):
428 raise AutotestRunError("%s \
429 failed to shutdown after %ds" %
mblighc8949b82007-07-23 16:33:58 +0000430 (self.host.hostname,
mblighdcd57a82007-07-11 23:06:47 +0000431 HALT_TIME))
432 print "Client down, waiting for restart"
433 if not self.host.wait_up(BOOT_TIME):
434 # since reboot failed
435 # hardreset the machine once if possible
436 # before failing this control file
mblighba81c682007-10-25 15:35:59 +0000437 print "Hardresetting %s" % (
438 self.host.hostname,)
439 try:
440 self.host.hardreset(wait=False)
mbligh03f4fc72007-11-29 20:56:14 +0000441 except AutoservUnsupportedError:
mblighba81c682007-10-25 15:35:59 +0000442 print "Hardreset unsupported on %s" % (
443 self.host.hostname,)
mblighc8949b82007-07-23 16:33:58 +0000444 raise AutotestRunError("%s failed to "
445 "boot after %ds" % (
446 self.host.hostname,
447 BOOT_TIME,))
mblighdcd57a82007-07-11 23:06:47 +0000448 continue
mblighc8949b82007-07-23 16:33:58 +0000449 raise AutotestRunError("Aborting - unknown "
450 "return code: %s\n" % last)
mblighdcd57a82007-07-11 23:06:47 +0000451
mbligh0e4613b2007-10-29 16:55:07 +0000452 # should only get here if we timed out
453 assert timeout
454 raise AutotestTimeoutError()
455
mblighdcd57a82007-07-11 23:06:47 +0000456
457def _get_autodir(host):
mblighda13d542008-01-03 16:28:34 +0000458 dir = host.get_autodir()
459 if dir:
460 return dir
mblighdcd57a82007-07-11 23:06:47 +0000461 try:
mblighe988aa52007-11-24 19:40:41 +0000462 # There's no clean way to do this. readlink may not exist
463 cmd = "python -c 'import os,sys; print os.readlink(sys.argv[1])' /etc/autotest.conf"
464 dir = os.path.dirname(host.run(cmd).stdout)
465 if dir:
466 return dir
mbligh03f4fc72007-11-29 20:56:14 +0000467 except AutoservRunError:
mblighdcd57a82007-07-11 23:06:47 +0000468 pass
469 for path in ['/usr/local/autotest', '/home/autotest']:
470 try:
mbligh548197f2007-12-12 15:36:22 +0000471 host.run('ls %s > /dev/null' % \
472 os.path.join(path, 'bin/autotest'))
mblighdcd57a82007-07-11 23:06:47 +0000473 return path
mbligh03f4fc72007-11-29 20:56:14 +0000474 except AutoservRunError:
mblighdcd57a82007-07-11 23:06:47 +0000475 pass
476 raise AutotestRunError("Cannot figure out autotest directory")