autotest.py revision 271d5af3050de888b5d63de7fa799a1d0459082f
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		psutils (netperf)
79		"""
80		if not host:
81			host = self.host
82		print "Installing autotest on %s" % host.hostname
83		# try to install from file or directory
84		if self.source_material:
85			if os.path.isdir(self.source_material):
86				# Copy autotest recursively
87				autodir = _get_autodir(host)
88				host.run('mkdir -p "%s"' %
89					 utils.sh_escape(autodir))
90				host.send_file(self.source_material,
91					       autodir)
92			else:
93				# Copy autotest via tarball
94				raise "Not yet implemented!"
95			return
96
97		# if that fails try to install using svn
98		if utils.run('which svn').exit_status:
99			raise AutoservError('svn not found in path on \
100			target machine: %s' % host.name)
101		try:
102			host.run('svn checkout %s %s' %
103				 (AUTOTEST_SVN, _get_autodir(host)))
104		except errors.AutoservRunError, e:
105			host.run('svn checkout %s %s' %
106				 (AUTOTEST_HTTP, _get_autodir(host)))
107
108
109	def run(self, control_file, results_dir, host = None):
110		"""
111		Run an autotest job on the remote machine.
112
113		Args:
114			control_file: an open file-like-obj of the control file
115			results_dir: a str path where the results should be stored
116				on the local filesystem
117			host: a Host instance on which the control file should
118				be run
119
120		Raises:
121			AutotestRunError: if there is a problem executing
122				the control file
123		"""
124		if not host:
125			host = self.host
126		host.ensure_up()
127
128		atrun = _Run(host, results_dir)
129		try:
130			atrun.verify_machine()
131		except:
132			print "Verify machine failed on %s. Reinstalling" % \
133								host.hostname
134			self.install(host)
135		atrun.verify_machine()
136		if os.path.isdir(results_dir):
137			shutil.rmtree(results_dir)
138		debug = os.path.join(results_dir, 'debug')
139		os.makedirs(debug)
140
141		# Ready .... Aim ....
142		host.run('rm -f ' + atrun.remote_control_file)
143		host.run('rm -f ' + atrun.remote_control_file + '.state')
144
145		# Copy control_file to remote_control_file on the host
146		tmppath = utils.get(control_file)
147		host.send_file(tmppath, atrun.remote_control_file)
148		os.remove(tmppath)
149
150		atrun.execute_control()
151
152		# retrive results
153		results = os.path.join(atrun.autodir, 'results', 'default')
154		# Copy all dirs in default to results_dir
155		host.get_file(results + '/', results_dir)
156
157
158	def run_test(self, test_name, results_dir, host = None, *args, **dargs):
159		"""
160		Assemble a tiny little control file to just run one test,
161		and run it as an autotest client-side test
162		"""
163		if not host:
164			host = self.host
165		opts = ["%s=%s" % (o[0], repr(o[1])) for o in dargs.items()]
166		cmd = ", ".join([repr(test_name)] + map(repr, args) + opts)
167		control = "job.run_test(%s)" % cmd
168		self.run(control, results_dir, host)
169
170
171class _Run(object):
172	"""
173	Represents a run of autotest control file.  This class maintains
174	all the state necessary as an autotest control file is executed.
175
176	It is not intended to be used directly, rather control files
177	should be run using the run method in Autotest.
178	"""
179	def __init__(self, host, results_dir):
180		self.host = host
181		self.results_dir = results_dir
182		self.env = ''
183		if hasattr(host, 'env'):
184			self.env = host.env
185
186		self.autodir = _get_autodir(self.host)
187		self.remote_control_file = os.path.join(self.autodir, 'control')
188
189
190	def verify_machine(self):
191		binary = os.path.join(self.autodir, 'bin/autotest')
192		try:
193			self.host.run('ls ' + binary)
194		except:
195			raise "Autotest does not appear to be installed"
196		tmpdir = os.path.join(self.autodir, 'tmp')
197		self.host.run('umount %s' % tmpdir, ignore_status=True)
198
199
200	def __execute_section(self, section):
201		print "Executing %s/bin/autotest %s/control phase %d" % \
202					(self.autodir, self.autodir,
203					 section)
204		logfile = "%s/debug/client.log.%d" % (self.results_dir,
205						      section)
206		client_log = open(logfile, 'w')
207		if section > 0:
208			cont = '-c'
209		else:
210			cont = ''
211		client = os.path.join(self.autodir, 'bin/autotest_client')
212		ssh = "ssh -q %s@%s" % (self.host.user, self.host.hostname)
213		cmd = "%s %s %s" % (client, cont, self.remote_control_file)
214		print "%s '%s %s'" % (ssh, self.env, cmd)
215		# Use Popen here, not m.ssh, as we want it in the background
216		p = subprocess.Popen("%s '%s %s'" % (ssh, self.env, cmd),
217				     shell=True,
218				     stdout=client_log,
219				     stderr=subprocess.PIPE)
220		line = None
221		for line in iter(p.stderr.readline, ''):
222			print line,
223			sys.stdout.flush()
224		if not line:
225			raise AutotestRunError("execute_section: %s '%s' \
226			failed to return anything" % (ssh, cmd))
227		return line
228
229
230	def execute_control(self):
231		section = 0
232		while True:
233			last = self.__execute_section(section)
234			section += 1
235			if re.match('DONE', last):
236				print "Client complete"
237				return
238			elif re.match('REBOOT', last):
239				print "Client is rebooting"
240				print "Waiting for client to halt"
241				if not self.host.wait_down(HALT_TIME):
242					raise AutotestRunError("%s \
243					failed to shutdown after %ds" %
244							(self.host.hostname,
245							HALT_TIME))
246				print "Client down, waiting for restart"
247				if not self.host.wait_up(BOOT_TIME):
248					# since reboot failed
249					# hardreset the machine once if possible
250					# before failing this control file
251					if hasattr(self.host, 'hardreset'):
252						print "Hardresetting %s" % (
253							self.host.hostname,)
254						self.host.hardreset()
255					raise AutotestRunError("%s failed to "
256						"boot after %ds" % (
257						self.host.hostname,
258						BOOT_TIME,))
259				continue
260			raise AutotestRunError("Aborting - unknown "
261				"return code: %s\n" % last)
262
263
264def _get_autodir(host):
265	try:
266		atdir = host.run(
267			'grep "autodir *=" /etc/autotest.conf').stdout.strip()
268		if atdir:
269			m = re.search(r'autodir *= *[\'"]?([^\'"]*)[\'"]?',
270				      atdir)
271			return m.group(1)
272	except errors.AutoservRunError:
273		pass
274	for path in ['/usr/local/autotest', '/home/autotest']:
275		try:
276			host.run('ls ' + path)
277			return path
278		except errors.AutoservRunError:
279			pass
280	raise AutotestRunError("Cannot figure out autotest directory")
281