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