blob: a97468b0d15928ea43fa570cb49ecd3f43f3d34f [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
mblighdcd57a82007-07-11 23:06:47 +000040
mbligh0e4613b2007-10-29 16:55:07 +000041
mblighdcd57a82007-07-11 23:06:47 +000042
43class Autotest(installable_object.InstallableObject):
mblighdc735a22007-08-02 16:54:37 +000044 """
45 This class represents the Autotest program.
mblighdcd57a82007-07-11 23:06:47 +000046
47 Autotest is used to run tests automatically and collect the results.
48 It also supports profilers.
49
50 Implementation details:
51 This is a leaf class in an abstract class hierarchy, it must
52 implement the unimplemented methods in parent classes.
53 """
mbligh119c12a2007-11-12 22:13:44 +000054 job = None
55
56
mblighdd81ef02007-08-10 01:18:40 +000057 def __init__(self, host = None):
58 self.host = host
mbligh91334902007-09-28 01:47:59 +000059 self.got = False
60 self.installed = False
mbligh9708f732007-10-18 03:18:54 +000061 self.serverdir = utils.get_server_dir()
mblighdcd57a82007-07-11 23:06:47 +000062 super(Autotest, self).__init__()
mblighc8949b82007-07-23 16:33:58 +000063
mblighdc735a22007-08-02 16:54:37 +000064
mbligh119c12a2007-11-12 22:13:44 +000065 @logging.record
mblighdd81ef02007-08-10 01:18:40 +000066 def install(self, host = None):
mblighdc735a22007-08-02 16:54:37 +000067 """
68 Install autotest. If get() was not called previously, an
mblighc8949b82007-07-23 16:33:58 +000069 attempt will be made to install from the autotest svn
70 repository.
mblighdcd57a82007-07-11 23:06:47 +000071
72 Args:
73 host: a Host instance on which autotest will be
74 installed
75
76 Raises:
77 AutoservError: if a tarball was not specified and
78 the target host does not have svn installed in its path
mblighc8949b82007-07-23 16:33:58 +000079
80 TODO(poirier): check dependencies
81 autotest needs:
82 bzcat
83 liboptdev (oprofile)
84 binutils-dev (oprofile)
85 make
mbligh629e39e2007-08-10 19:32:00 +000086 psutils (netperf)
mblighdcd57a82007-07-11 23:06:47 +000087 """
mblighdd81ef02007-08-10 01:18:40 +000088 if not host:
89 host = self.host
mbligh91334902007-09-28 01:47:59 +000090 if not self.got:
91 self.get()
mbligh9e787d22008-02-11 17:56:37 +000092 host.wait_up(timeout=30)
mbligh91334902007-09-28 01:47:59 +000093 host.setup()
mblighb84a1cf2007-08-09 23:08:13 +000094 print "Installing autotest on %s" % host.hostname
mbligh40f122a2007-11-03 23:08:46 +000095
96 autodir = _get_autodir(host)
97 host.run('mkdir -p "%s"' % utils.sh_escape(autodir))
98
99 if getattr(host, 'site_install_autotest', None):
100 if host.site_install_autotest():
101 self.installed = True
102 return
103
mblighdcd57a82007-07-11 23:06:47 +0000104 # try to install from file or directory
mblighc8949b82007-07-23 16:33:58 +0000105 if self.source_material:
106 if os.path.isdir(self.source_material):
mblighdcd57a82007-07-11 23:06:47 +0000107 # Copy autotest recursively
mbligh40f122a2007-11-03 23:08:46 +0000108 host.send_file(self.source_material, autodir)
mblighdcd57a82007-07-11 23:06:47 +0000109 else:
110 # Copy autotest via tarball
mbligh4d6feff2008-01-14 16:48:56 +0000111 e_msg = 'Installation method not yet implemented!'
112 raise NotImplementedError(e_msg)
mbligh91334902007-09-28 01:47:59 +0000113 print "Installation of autotest completed"
114 self.installed = True
mblighdcd57a82007-07-11 23:06:47 +0000115 return
mbligh91334902007-09-28 01:47:59 +0000116
mblighdcd57a82007-07-11 23:06:47 +0000117 # if that fails try to install using svn
118 if utils.run('which svn').exit_status:
119 raise AutoservError('svn not found in path on \
120 target machine: %s' % host.name)
121 try:
122 host.run('svn checkout %s %s' %
mbligh3850f892007-11-05 22:49:24 +0000123 (AUTOTEST_SVN, autodir))
mbligh03f4fc72007-11-29 20:56:14 +0000124 except AutoservRunError, e:
mblighdcd57a82007-07-11 23:06:47 +0000125 host.run('svn checkout %s %s' %
mbligh3850f892007-11-05 22:49:24 +0000126 (AUTOTEST_HTTP, autodir))
mbligh91334902007-09-28 01:47:59 +0000127 print "Installation of autotest completed"
128 self.installed = True
129
130
131 def get(self, location = None):
132 if not location:
133 location = os.path.join(self.serverdir, '../client')
134 location = os.path.abspath(location)
mbligh8fc0e5a2007-10-11 18:39:03 +0000135 # If there's stuff run on our client directory already, it
136 # can cause problems. Try giving it a quick clean first.
137 cwd = os.getcwd()
138 os.chdir(location)
139 os.system('tools/make_clean')
140 os.chdir(cwd)
mbligh91334902007-09-28 01:47:59 +0000141 super(Autotest, self).get(location)
142 self.got = True
mblighdcd57a82007-07-11 23:06:47 +0000143
144
mbligh0e4613b2007-10-29 16:55:07 +0000145 def run(self, control_file, results_dir = '.', host = None,
146 timeout=None):
mblighdcd57a82007-07-11 23:06:47 +0000147 """
148 Run an autotest job on the remote machine.
mblighc8949b82007-07-23 16:33:58 +0000149
mblighdcd57a82007-07-11 23:06:47 +0000150 Args:
151 control_file: an open file-like-obj of the control file
152 results_dir: a str path where the results should be stored
153 on the local filesystem
154 host: a Host instance on which the control file should
155 be run
156
157 Raises:
158 AutotestRunError: if there is a problem executing
159 the control file
160 """
mbligh55a2a3b2007-09-30 01:27:55 +0000161 results_dir = os.path.abspath(results_dir)
mblighdd81ef02007-08-10 01:18:40 +0000162 if not host:
163 host = self.host
mbligh91334902007-09-28 01:47:59 +0000164 if not self.installed:
mbligh7fdd1692007-10-02 19:16:53 +0000165 self.install(host)
mbligh91334902007-09-28 01:47:59 +0000166
mbligh9e787d22008-02-11 17:56:37 +0000167 host.wait_up(timeout=30)
mblighdbe4a382007-07-26 19:41:28 +0000168
mblighdcd57a82007-07-11 23:06:47 +0000169 atrun = _Run(host, results_dir)
mblighe4f01512007-08-10 01:42:50 +0000170 try:
171 atrun.verify_machine()
172 except:
173 print "Verify machine failed on %s. Reinstalling" % \
174 host.hostname
175 self.install(host)
mblighdcd57a82007-07-11 23:06:47 +0000176 atrun.verify_machine()
177 debug = os.path.join(results_dir, 'debug')
mbligh55a2a3b2007-09-30 01:27:55 +0000178 try:
179 os.makedirs(debug)
180 except:
181 pass
mblighc8949b82007-07-23 16:33:58 +0000182
mblighdcd57a82007-07-11 23:06:47 +0000183 # Ready .... Aim ....
mbligha4bece12007-11-24 19:37:14 +0000184 for control in [atrun.remote_control_file,
185 atrun.remote_control_file + '.state',
186 atrun.manual_control_file,
187 atrun.manual_control_file + '.state']:
188 host.run('rm -f ' + control)
mblighc8949b82007-07-23 16:33:58 +0000189
mblighdcd57a82007-07-11 23:06:47 +0000190 # Copy control_file to remote_control_file on the host
191 tmppath = utils.get(control_file)
192 host.send_file(tmppath, atrun.remote_control_file)
mbligh4918bdb2007-12-13 16:06:09 +0000193 if os.path.abspath(tmppath) != os.path.abspath(control_file):
194 os.remove(tmppath)
mbligh0e4613b2007-10-29 16:55:07 +0000195
mbligh0e4613b2007-10-29 16:55:07 +0000196 try:
197 atrun.execute_control(timeout=timeout)
mbligh35b225c2007-12-10 17:17:27 +0000198 finally:
mbligh1f194902007-12-20 01:31:43 +0000199 # make an effort to wait for the machine to come up
200 try:
mbligh9e787d22008-02-11 17:56:37 +0000201 host.wait_up(timeout=30)
mbligh1f194902007-12-20 01:31:43 +0000202 except AutoservError:
203 # don't worry about any errors, we'll try and
204 # get the results anyway
205 pass
206
mbligh35b225c2007-12-10 17:17:27 +0000207 # get the results
208 results = os.path.join(atrun.autodir, 'results',
209 'default')
210 # Copy all dirs in default to results_dir
211 host.get_file(results + '/', results_dir)
mblighdcd57a82007-07-11 23:06:47 +0000212
mbligh0e4613b2007-10-29 16:55:07 +0000213
214 def run_timed_test(self, test_name, results_dir = '.', host = None,
215 timeout=None, *args, **dargs):
mblighd54832b2007-07-25 16:46:56 +0000216 """
217 Assemble a tiny little control file to just run one test,
218 and run it as an autotest client-side test
219 """
mblighdd81ef02007-08-10 01:18:40 +0000220 if not host:
221 host = self.host
mbligh91334902007-09-28 01:47:59 +0000222 if not self.installed:
mbligh7fdd1692007-10-02 19:16:53 +0000223 self.install(host)
mbligh271d5af2007-08-10 19:54:01 +0000224 opts = ["%s=%s" % (o[0], repr(o[1])) for o in dargs.items()]
225 cmd = ", ".join([repr(test_name)] + map(repr, args) + opts)
mblighf1c52842007-10-16 15:21:38 +0000226 control = "job.run_test(%s)\n" % cmd
mbligh0e4613b2007-10-29 16:55:07 +0000227 self.run(control, results_dir, host, timeout=timeout)
228
229
230 def run_test(self, test_name, results_dir = '.', host = None,
231 *args, **dargs):
232 self.run_timed_test(test_name, results_dir, host, None,
233 *args, **dargs)
mblighd54832b2007-07-25 16:46:56 +0000234
235
mblighd528d302007-12-19 16:19:05 +0000236# a file-like object for catching stderr from the autotest client and
237# extracting status logs from it
238class StdErrRedirector(object):
239 """Partial file object to write to both stdout and
240 the status log file. We only implement those methods
241 utils.run() actually calls.
242 """
243 parser = re.compile(r"^AUTOTEST_STATUS:([^:]*):(.*)$")
244
245 def __init__(self, status_log):
246 self.status_log = status_log
247 self.leftover = ""
248 self.last_line = ""
249 self.logs = {}
250
251
252 def _process_log_dict(self, log_dict):
253 log_list = log_dict.pop("logs", [])
254 for key in sorted(log_dict.iterkeys()):
255 log_list += self._process_log_dict(log_dict.pop(key))
256 return log_list
257
258
259 def _process_logs(self):
260 """Go through the accumulated logs in self.log and print them
261 out to stdout and the status log. Note that this processes
262 logs in an ordering where:
263
264 1) logs to different tags are never interleaved
265 2) logs to x.y come before logs to x.y.z for all z
266 3) logs to x.y come before x.z whenever y < z
267
268 Note that this will in general not be the same as the
269 chronological ordering of the logs. However, if a chronological
270 ordering is desired that one can be reconstructed from the
271 status log by looking at timestamp lines."""
272 log_list = self._process_log_dict(self.logs)
273 for line in log_list:
274 print >> self.status_log, line
275 if log_list:
276 self.last_line = log_list[-1]
277
278
279 def _process_quoted_line(self, tag, line):
280 """Process a line quoted with an AUTOTEST_STATUS flag. If the
281 tag is blank then we want to push out all the data we've been
282 building up in self.logs, and then the newest line. If the
283 tag is not blank, then push the line into the logs for handling
284 later."""
285 print line
286 if tag == "":
287 self._process_logs()
288 print >> self.status_log, line
289 self.last_line = line
290 else:
291 tag_parts = [int(x) for x in tag.split(".")]
292 log_dict = self.logs
293 for part in tag_parts:
294 log_dict = log_dict.setdefault(part, {})
295 log_list = log_dict.setdefault("logs", [])
296 log_list.append(line)
297
298
299 def _process_line(self, line):
300 """Write out a line of data to the appropriate stream. Status
301 lines sent by autotest will be prepended with
302 "AUTOTEST_STATUS", and all other lines are ssh error
303 messages."""
304 match = self.parser.search(line)
305 if match:
306 tag, line = match.groups()
307 self._process_quoted_line(tag, line)
308 else:
309 print >> sys.stderr, line
310
311
312 def write(self, data):
313 data = self.leftover + data
314 lines = data.split("\n")
315 # process every line but the last one
316 for line in lines[:-1]:
317 self._process_line(line)
318 # save the last line for later processing
319 # since we may not have the whole line yet
320 self.leftover = lines[-1]
321
322
323 def flush(self):
324 sys.stdout.flush()
325 sys.stderr.flush()
326 self.status_log.flush()
327
328
329 def close(self):
330 if self.leftover:
331 self._process_line(self.leftover)
332 self._process_logs()
333 self.flush()
334
335
mblighdcd57a82007-07-11 23:06:47 +0000336class _Run(object):
337 """
338 Represents a run of autotest control file. This class maintains
339 all the state necessary as an autotest control file is executed.
340
341 It is not intended to be used directly, rather control files
342 should be run using the run method in Autotest.
343 """
344 def __init__(self, host, results_dir):
345 self.host = host
346 self.results_dir = results_dir
mblighcc53b352007-10-24 21:15:30 +0000347 self.env = host.env
mblighd528d302007-12-19 16:19:05 +0000348
mblighdcd57a82007-07-11 23:06:47 +0000349 self.autodir = _get_autodir(self.host)
mbligha4bece12007-11-24 19:37:14 +0000350 self.manual_control_file = os.path.join(self.autodir, 'control')
351 self.remote_control_file = os.path.join(self.autodir,
352 'control.autoserv')
mblighdc735a22007-08-02 16:54:37 +0000353
354
mblighdcd57a82007-07-11 23:06:47 +0000355 def verify_machine(self):
356 binary = os.path.join(self.autodir, 'bin/autotest')
mblighdd81ef02007-08-10 01:18:40 +0000357 try:
mbligh09f45692008-01-26 22:53:56 +0000358 self.host.run('ls %s > /dev/null 2>&1' % binary)
mblighdd81ef02007-08-10 01:18:40 +0000359 except:
mbligh8d7e3472007-08-10 01:35:18 +0000360 raise "Autotest does not appear to be installed"
361 tmpdir = os.path.join(self.autodir, 'tmp')
362 self.host.run('umount %s' % tmpdir, ignore_status=True)
mblighdc735a22007-08-02 16:54:37 +0000363
364
mbligh0e4613b2007-10-29 16:55:07 +0000365 def __execute_section(self, section, timeout):
mblighdcd57a82007-07-11 23:06:47 +0000366 print "Executing %s/bin/autotest %s/control phase %d" % \
367 (self.autodir, self.autodir,
368 section)
mbligh0e4613b2007-10-29 16:55:07 +0000369
mblighadf2aab2007-11-29 18:16:43 +0000370 # build up the full command we want to run over the host
371 cmd = [os.path.join(self.autodir, 'bin/autotest_client')]
372 if section > 0:
373 cmd.append('-c')
374 cmd.append(self.remote_control_file)
375 full_cmd = ' '.join(cmd)
376
mblighd528d302007-12-19 16:19:05 +0000377 # open up the files we need for our logging
378 client_log_file = os.path.join(self.results_dir, 'debug',
379 'client.log.%d' % section)
380 client_log = open(client_log_file, 'w', 0)
381 status_log_file = os.path.join(self.results_dir, 'status.log')
382 status_log = open(status_log_file, 'a', 0)
383
384 try:
385 redirector = StdErrRedirector(status_log)
386 result = self.host.run(full_cmd, ignore_status=True,
387 timeout=timeout,
388 stdout_tee=client_log,
389 stderr_tee=redirector)
390 finally:
391 redirector.close()
mbligh2bf2db62007-11-27 00:53:18 +0000392
mblighcf732d12007-11-21 18:15:03 +0000393 if result.exit_status == 1:
mblighfaf0cd42007-11-19 16:00:24 +0000394 self.host.job.aborted = True
mbligh0e4613b2007-10-29 16:55:07 +0000395 if not result.stderr:
396 raise AutotestRunError(
397 "execute_section: %s failed to return anything\n"
398 "stdout:%s\n" % (full_cmd, result.stdout))
399
mbligh2bf2db62007-11-27 00:53:18 +0000400 return redirector.last_line
mblighdc735a22007-08-02 16:54:37 +0000401
402
mbligh0e4613b2007-10-29 16:55:07 +0000403 def execute_control(self, timeout=None):
mblighdcd57a82007-07-11 23:06:47 +0000404 section = 0
mbligh0e4613b2007-10-29 16:55:07 +0000405 time_left = None
406 if timeout:
407 end_time = time.time() + timeout
408 time_left = end_time - time.time()
409 while not timeout or time_left > 0:
410 last = self.__execute_section(section, time_left)
411 if timeout:
412 time_left = end_time - time.time()
413 if time_left <= 0:
414 break
mblighdcd57a82007-07-11 23:06:47 +0000415 section += 1
mblighc3430162007-11-14 23:57:19 +0000416 if re.match(r'^END .*\t----\t----\t.*$', last):
mblighdcd57a82007-07-11 23:06:47 +0000417 print "Client complete"
418 return
mblighc3430162007-11-14 23:57:19 +0000419 elif re.match('^\t*GOOD\t----\treboot\.start.*$', last):
mblighdcd57a82007-07-11 23:06:47 +0000420 print "Client is rebooting"
421 print "Waiting for client to halt"
422 if not self.host.wait_down(HALT_TIME):
423 raise AutotestRunError("%s \
424 failed to shutdown after %ds" %
mblighc8949b82007-07-23 16:33:58 +0000425 (self.host.hostname,
mblighdcd57a82007-07-11 23:06:47 +0000426 HALT_TIME))
427 print "Client down, waiting for restart"
428 if not self.host.wait_up(BOOT_TIME):
429 # since reboot failed
430 # hardreset the machine once if possible
431 # before failing this control file
mblighba81c682007-10-25 15:35:59 +0000432 print "Hardresetting %s" % (
433 self.host.hostname,)
434 try:
435 self.host.hardreset(wait=False)
mbligh03f4fc72007-11-29 20:56:14 +0000436 except AutoservUnsupportedError:
mblighba81c682007-10-25 15:35:59 +0000437 print "Hardreset unsupported on %s" % (
438 self.host.hostname,)
mblighc8949b82007-07-23 16:33:58 +0000439 raise AutotestRunError("%s failed to "
440 "boot after %ds" % (
441 self.host.hostname,
442 BOOT_TIME,))
mblighdcd57a82007-07-11 23:06:47 +0000443 continue
mblighc8949b82007-07-23 16:33:58 +0000444 raise AutotestRunError("Aborting - unknown "
445 "return code: %s\n" % last)
mblighdcd57a82007-07-11 23:06:47 +0000446
mbligh0e4613b2007-10-29 16:55:07 +0000447 # should only get here if we timed out
448 assert timeout
449 raise AutotestTimeoutError()
450
mblighdcd57a82007-07-11 23:06:47 +0000451
452def _get_autodir(host):
mblighda13d542008-01-03 16:28:34 +0000453 dir = host.get_autodir()
454 if dir:
455 return dir
mblighdcd57a82007-07-11 23:06:47 +0000456 try:
mblighe988aa52007-11-24 19:40:41 +0000457 # There's no clean way to do this. readlink may not exist
mbligh09f45692008-01-26 22:53:56 +0000458 cmd = "python -c 'import os,sys; print os.readlink(sys.argv[1])' /etc/autotest.conf 2> /dev/null"
mblighe988aa52007-11-24 19:40:41 +0000459 dir = os.path.dirname(host.run(cmd).stdout)
460 if dir:
461 return dir
mbligh03f4fc72007-11-29 20:56:14 +0000462 except AutoservRunError:
mblighdcd57a82007-07-11 23:06:47 +0000463 pass
464 for path in ['/usr/local/autotest', '/home/autotest']:
465 try:
mbligh09f45692008-01-26 22:53:56 +0000466 host.run('ls %s > /dev/null 2>&1' % \
mbligh548197f2007-12-12 15:36:22 +0000467 os.path.join(path, 'bin/autotest'))
mblighdcd57a82007-07-11 23:06:47 +0000468 return path
mbligh03f4fc72007-11-29 20:56:14 +0000469 except AutoservRunError:
mblighdcd57a82007-07-11 23:06:47 +0000470 pass
471 raise AutotestRunError("Cannot figure out autotest directory")