blob: 0f71deac78010c22232f7e3773e2379b55a44747 [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
mbligh4d6feff2008-01-14 16:48:56 +0000117 e_msg = 'Installation method not yet implemented!'
118 raise NotImplementedError(e_msg)
mbligh91334902007-09-28 01:47:59 +0000119 print "Installation of autotest completed"
120 self.installed = True
mblighdcd57a82007-07-11 23:06:47 +0000121 return
mbligh91334902007-09-28 01:47:59 +0000122
mblighdcd57a82007-07-11 23:06:47 +0000123 # if that fails try to install using svn
124 if utils.run('which svn').exit_status:
125 raise AutoservError('svn not found in path on \
126 target machine: %s' % host.name)
127 try:
128 host.run('svn checkout %s %s' %
mbligh3850f892007-11-05 22:49:24 +0000129 (AUTOTEST_SVN, autodir))
mbligh03f4fc72007-11-29 20:56:14 +0000130 except AutoservRunError, e:
mblighdcd57a82007-07-11 23:06:47 +0000131 host.run('svn checkout %s %s' %
mbligh3850f892007-11-05 22:49:24 +0000132 (AUTOTEST_HTTP, autodir))
mbligh91334902007-09-28 01:47:59 +0000133 print "Installation of autotest completed"
134 self.installed = True
135
136
137 def get(self, location = None):
138 if not location:
139 location = os.path.join(self.serverdir, '../client')
140 location = os.path.abspath(location)
mbligh8fc0e5a2007-10-11 18:39:03 +0000141 # If there's stuff run on our client directory already, it
142 # can cause problems. Try giving it a quick clean first.
143 cwd = os.getcwd()
144 os.chdir(location)
145 os.system('tools/make_clean')
146 os.chdir(cwd)
mbligh91334902007-09-28 01:47:59 +0000147 super(Autotest, self).get(location)
148 self.got = True
mblighdcd57a82007-07-11 23:06:47 +0000149
150
mbligh0e4613b2007-10-29 16:55:07 +0000151 def run(self, control_file, results_dir = '.', host = None,
152 timeout=None):
mblighdcd57a82007-07-11 23:06:47 +0000153 """
154 Run an autotest job on the remote machine.
mblighc8949b82007-07-23 16:33:58 +0000155
mblighdcd57a82007-07-11 23:06:47 +0000156 Args:
157 control_file: an open file-like-obj of the control file
158 results_dir: a str path where the results should be stored
159 on the local filesystem
160 host: a Host instance on which the control file should
161 be run
162
163 Raises:
164 AutotestRunError: if there is a problem executing
165 the control file
166 """
mbligh55a2a3b2007-09-30 01:27:55 +0000167 results_dir = os.path.abspath(results_dir)
mblighdd81ef02007-08-10 01:18:40 +0000168 if not host:
169 host = self.host
mbligh91334902007-09-28 01:47:59 +0000170 if not self.installed:
mbligh7fdd1692007-10-02 19:16:53 +0000171 self.install(host)
mbligh91334902007-09-28 01:47:59 +0000172
mblighdbe4a382007-07-26 19:41:28 +0000173 host.ensure_up()
174
mblighdcd57a82007-07-11 23:06:47 +0000175 atrun = _Run(host, results_dir)
mblighe4f01512007-08-10 01:42:50 +0000176 try:
177 atrun.verify_machine()
178 except:
179 print "Verify machine failed on %s. Reinstalling" % \
180 host.hostname
181 self.install(host)
mblighdcd57a82007-07-11 23:06:47 +0000182 atrun.verify_machine()
183 debug = os.path.join(results_dir, 'debug')
mbligh55a2a3b2007-09-30 01:27:55 +0000184 try:
185 os.makedirs(debug)
186 except:
187 pass
mblighc8949b82007-07-23 16:33:58 +0000188
mblighdcd57a82007-07-11 23:06:47 +0000189 # Ready .... Aim ....
mbligha4bece12007-11-24 19:37:14 +0000190 for control in [atrun.remote_control_file,
191 atrun.remote_control_file + '.state',
192 atrun.manual_control_file,
193 atrun.manual_control_file + '.state']:
194 host.run('rm -f ' + control)
mblighc8949b82007-07-23 16:33:58 +0000195
mblighdcd57a82007-07-11 23:06:47 +0000196 # Copy control_file to remote_control_file on the host
197 tmppath = utils.get(control_file)
198 host.send_file(tmppath, atrun.remote_control_file)
mbligh4918bdb2007-12-13 16:06:09 +0000199 if os.path.abspath(tmppath) != os.path.abspath(control_file):
200 os.remove(tmppath)
mbligh0e4613b2007-10-29 16:55:07 +0000201
mbligh0e4613b2007-10-29 16:55:07 +0000202 try:
203 atrun.execute_control(timeout=timeout)
mbligh35b225c2007-12-10 17:17:27 +0000204 finally:
mbligh1f194902007-12-20 01:31:43 +0000205 # make an effort to wait for the machine to come up
206 try:
207 host.ensure_up()
208 except AutoservError:
209 # don't worry about any errors, we'll try and
210 # get the results anyway
211 pass
212
mbligh35b225c2007-12-10 17:17:27 +0000213 # get the results
214 results = os.path.join(atrun.autodir, 'results',
215 'default')
216 # Copy all dirs in default to results_dir
217 host.get_file(results + '/', results_dir)
mblighdcd57a82007-07-11 23:06:47 +0000218
mbligh0e4613b2007-10-29 16:55:07 +0000219
220 def run_timed_test(self, test_name, results_dir = '.', host = None,
221 timeout=None, *args, **dargs):
mblighd54832b2007-07-25 16:46:56 +0000222 """
223 Assemble a tiny little control file to just run one test,
224 and run it as an autotest client-side test
225 """
mblighdd81ef02007-08-10 01:18:40 +0000226 if not host:
227 host = self.host
mbligh91334902007-09-28 01:47:59 +0000228 if not self.installed:
mbligh7fdd1692007-10-02 19:16:53 +0000229 self.install(host)
mbligh271d5af2007-08-10 19:54:01 +0000230 opts = ["%s=%s" % (o[0], repr(o[1])) for o in dargs.items()]
231 cmd = ", ".join([repr(test_name)] + map(repr, args) + opts)
mblighf1c52842007-10-16 15:21:38 +0000232 control = "job.run_test(%s)\n" % cmd
mbligh0e4613b2007-10-29 16:55:07 +0000233 self.run(control, results_dir, host, timeout=timeout)
234
235
236 def run_test(self, test_name, results_dir = '.', host = None,
237 *args, **dargs):
238 self.run_timed_test(test_name, results_dir, host, None,
239 *args, **dargs)
mblighd54832b2007-07-25 16:46:56 +0000240
241
mblighd528d302007-12-19 16:19:05 +0000242# a file-like object for catching stderr from the autotest client and
243# extracting status logs from it
244class StdErrRedirector(object):
245 """Partial file object to write to both stdout and
246 the status log file. We only implement those methods
247 utils.run() actually calls.
248 """
249 parser = re.compile(r"^AUTOTEST_STATUS:([^:]*):(.*)$")
250
251 def __init__(self, status_log):
252 self.status_log = status_log
253 self.leftover = ""
254 self.last_line = ""
255 self.logs = {}
256
257
258 def _process_log_dict(self, log_dict):
259 log_list = log_dict.pop("logs", [])
260 for key in sorted(log_dict.iterkeys()):
261 log_list += self._process_log_dict(log_dict.pop(key))
262 return log_list
263
264
265 def _process_logs(self):
266 """Go through the accumulated logs in self.log and print them
267 out to stdout and the status log. Note that this processes
268 logs in an ordering where:
269
270 1) logs to different tags are never interleaved
271 2) logs to x.y come before logs to x.y.z for all z
272 3) logs to x.y come before x.z whenever y < z
273
274 Note that this will in general not be the same as the
275 chronological ordering of the logs. However, if a chronological
276 ordering is desired that one can be reconstructed from the
277 status log by looking at timestamp lines."""
278 log_list = self._process_log_dict(self.logs)
279 for line in log_list:
280 print >> self.status_log, line
281 if log_list:
282 self.last_line = log_list[-1]
283
284
285 def _process_quoted_line(self, tag, line):
286 """Process a line quoted with an AUTOTEST_STATUS flag. If the
287 tag is blank then we want to push out all the data we've been
288 building up in self.logs, and then the newest line. If the
289 tag is not blank, then push the line into the logs for handling
290 later."""
291 print line
292 if tag == "":
293 self._process_logs()
294 print >> self.status_log, line
295 self.last_line = line
296 else:
297 tag_parts = [int(x) for x in tag.split(".")]
298 log_dict = self.logs
299 for part in tag_parts:
300 log_dict = log_dict.setdefault(part, {})
301 log_list = log_dict.setdefault("logs", [])
302 log_list.append(line)
303
304
305 def _process_line(self, line):
306 """Write out a line of data to the appropriate stream. Status
307 lines sent by autotest will be prepended with
308 "AUTOTEST_STATUS", and all other lines are ssh error
309 messages."""
310 match = self.parser.search(line)
311 if match:
312 tag, line = match.groups()
313 self._process_quoted_line(tag, line)
314 else:
315 print >> sys.stderr, line
316
317
318 def write(self, data):
319 data = self.leftover + data
320 lines = data.split("\n")
321 # process every line but the last one
322 for line in lines[:-1]:
323 self._process_line(line)
324 # save the last line for later processing
325 # since we may not have the whole line yet
326 self.leftover = lines[-1]
327
328
329 def flush(self):
330 sys.stdout.flush()
331 sys.stderr.flush()
332 self.status_log.flush()
333
334
335 def close(self):
336 if self.leftover:
337 self._process_line(self.leftover)
338 self._process_logs()
339 self.flush()
340
341
mblighdcd57a82007-07-11 23:06:47 +0000342class _Run(object):
343 """
344 Represents a run of autotest control file. This class maintains
345 all the state necessary as an autotest control file is executed.
346
347 It is not intended to be used directly, rather control files
348 should be run using the run method in Autotest.
349 """
350 def __init__(self, host, results_dir):
351 self.host = host
352 self.results_dir = results_dir
mblighcc53b352007-10-24 21:15:30 +0000353 self.env = host.env
mblighd528d302007-12-19 16:19:05 +0000354
mblighdcd57a82007-07-11 23:06:47 +0000355 self.autodir = _get_autodir(self.host)
mbligha4bece12007-11-24 19:37:14 +0000356 self.manual_control_file = os.path.join(self.autodir, 'control')
357 self.remote_control_file = os.path.join(self.autodir,
358 'control.autoserv')
mblighdc735a22007-08-02 16:54:37 +0000359
360
mblighdcd57a82007-07-11 23:06:47 +0000361 def verify_machine(self):
362 binary = os.path.join(self.autodir, 'bin/autotest')
mblighdd81ef02007-08-10 01:18:40 +0000363 try:
mbligh548197f2007-12-12 15:36:22 +0000364 self.host.run('ls %s > /dev/null' % binary)
mblighdd81ef02007-08-10 01:18:40 +0000365 except:
mbligh8d7e3472007-08-10 01:35:18 +0000366 raise "Autotest does not appear to be installed"
367 tmpdir = os.path.join(self.autodir, 'tmp')
368 self.host.run('umount %s' % tmpdir, ignore_status=True)
mblighdc735a22007-08-02 16:54:37 +0000369
370
mbligh0e4613b2007-10-29 16:55:07 +0000371 def __execute_section(self, section, timeout):
mblighdcd57a82007-07-11 23:06:47 +0000372 print "Executing %s/bin/autotest %s/control phase %d" % \
373 (self.autodir, self.autodir,
374 section)
mbligh0e4613b2007-10-29 16:55:07 +0000375
mblighadf2aab2007-11-29 18:16:43 +0000376 # build up the full command we want to run over the host
377 cmd = [os.path.join(self.autodir, 'bin/autotest_client')]
378 if section > 0:
379 cmd.append('-c')
380 cmd.append(self.remote_control_file)
381 full_cmd = ' '.join(cmd)
382
mblighd528d302007-12-19 16:19:05 +0000383 # open up the files we need for our logging
384 client_log_file = os.path.join(self.results_dir, 'debug',
385 'client.log.%d' % section)
386 client_log = open(client_log_file, 'w', 0)
387 status_log_file = os.path.join(self.results_dir, 'status.log')
388 status_log = open(status_log_file, 'a', 0)
389
390 try:
391 redirector = StdErrRedirector(status_log)
392 result = self.host.run(full_cmd, ignore_status=True,
393 timeout=timeout,
394 stdout_tee=client_log,
395 stderr_tee=redirector)
396 finally:
397 redirector.close()
mbligh2bf2db62007-11-27 00:53:18 +0000398
mblighcf732d12007-11-21 18:15:03 +0000399 if result.exit_status == 1:
mblighfaf0cd42007-11-19 16:00:24 +0000400 self.host.job.aborted = True
mbligh0e4613b2007-10-29 16:55:07 +0000401 if not result.stderr:
402 raise AutotestRunError(
403 "execute_section: %s failed to return anything\n"
404 "stdout:%s\n" % (full_cmd, result.stdout))
405
mbligh2bf2db62007-11-27 00:53:18 +0000406 return redirector.last_line
mblighdc735a22007-08-02 16:54:37 +0000407
408
mbligh0e4613b2007-10-29 16:55:07 +0000409 def execute_control(self, timeout=None):
mblighdcd57a82007-07-11 23:06:47 +0000410 section = 0
mbligh0e4613b2007-10-29 16:55:07 +0000411 time_left = None
412 if timeout:
413 end_time = time.time() + timeout
414 time_left = end_time - time.time()
415 while not timeout or time_left > 0:
416 last = self.__execute_section(section, time_left)
417 if timeout:
418 time_left = end_time - time.time()
419 if time_left <= 0:
420 break
mblighdcd57a82007-07-11 23:06:47 +0000421 section += 1
mblighc3430162007-11-14 23:57:19 +0000422 if re.match(r'^END .*\t----\t----\t.*$', last):
mblighdcd57a82007-07-11 23:06:47 +0000423 print "Client complete"
424 return
mblighc3430162007-11-14 23:57:19 +0000425 elif re.match('^\t*GOOD\t----\treboot\.start.*$', last):
mblighdcd57a82007-07-11 23:06:47 +0000426 print "Client is rebooting"
427 print "Waiting for client to halt"
428 if not self.host.wait_down(HALT_TIME):
429 raise AutotestRunError("%s \
430 failed to shutdown after %ds" %
mblighc8949b82007-07-23 16:33:58 +0000431 (self.host.hostname,
mblighdcd57a82007-07-11 23:06:47 +0000432 HALT_TIME))
433 print "Client down, waiting for restart"
434 if not self.host.wait_up(BOOT_TIME):
435 # since reboot failed
436 # hardreset the machine once if possible
437 # before failing this control file
mblighba81c682007-10-25 15:35:59 +0000438 print "Hardresetting %s" % (
439 self.host.hostname,)
440 try:
441 self.host.hardreset(wait=False)
mbligh03f4fc72007-11-29 20:56:14 +0000442 except AutoservUnsupportedError:
mblighba81c682007-10-25 15:35:59 +0000443 print "Hardreset unsupported on %s" % (
444 self.host.hostname,)
mblighc8949b82007-07-23 16:33:58 +0000445 raise AutotestRunError("%s failed to "
446 "boot after %ds" % (
447 self.host.hostname,
448 BOOT_TIME,))
mblighdcd57a82007-07-11 23:06:47 +0000449 continue
mblighc8949b82007-07-23 16:33:58 +0000450 raise AutotestRunError("Aborting - unknown "
451 "return code: %s\n" % last)
mblighdcd57a82007-07-11 23:06:47 +0000452
mbligh0e4613b2007-10-29 16:55:07 +0000453 # should only get here if we timed out
454 assert timeout
455 raise AutotestTimeoutError()
456
mblighdcd57a82007-07-11 23:06:47 +0000457
458def _get_autodir(host):
mblighda13d542008-01-03 16:28:34 +0000459 dir = host.get_autodir()
460 if dir:
461 return dir
mblighdcd57a82007-07-11 23:06:47 +0000462 try:
mblighe988aa52007-11-24 19:40:41 +0000463 # There's no clean way to do this. readlink may not exist
464 cmd = "python -c 'import os,sys; print os.readlink(sys.argv[1])' /etc/autotest.conf"
465 dir = os.path.dirname(host.run(cmd).stdout)
466 if dir:
467 return dir
mbligh03f4fc72007-11-29 20:56:14 +0000468 except AutoservRunError:
mblighdcd57a82007-07-11 23:06:47 +0000469 pass
470 for path in ['/usr/local/autotest', '/home/autotest']:
471 try:
mbligh548197f2007-12-12 15:36:22 +0000472 host.run('ls %s > /dev/null' % \
473 os.path.join(path, 'bin/autotest'))
mblighdcd57a82007-07-11 23:06:47 +0000474 return path
mbligh03f4fc72007-11-29 20:56:14 +0000475 except AutoservRunError:
mblighdcd57a82007-07-11 23:06:47 +0000476 pass
477 raise AutotestRunError("Cannot figure out autotest directory")