blob: fabb75ea6f060b9e0218240e550270ee40cf910e [file] [log] [blame]
mblighdcd57a82007-07-11 23:06:47 +00001# Copyright 2007 Google Inc. Released under the GPL v2
2
showardb18134f2009-03-20 20:52:18 +00003import re, os, sys, traceback, subprocess, tempfile, time, pickle, glob, logging
jadmanski043e1132008-11-19 17:10:32 +00004from autotest_lib.server import installable_object, utils
showardb18134f2009-03-20 20:52:18 +00005from autotest_lib.client.common_lib import log, error
mbligh09108442008-10-15 16:27:38 +00006from autotest_lib.client.common_lib import global_config, packages
mbligha7007722009-01-13 00:37:11 +00007from autotest_lib.client.common_lib import utils as client_utils
mbligh3c7a1502008-07-24 18:08:47 +00008
mblighdcd57a82007-07-11 23:06:47 +00009AUTOTEST_SVN = 'svn://test.kernel.org/autotest/trunk/client'
10AUTOTEST_HTTP = 'http://test.kernel.org/svn/autotest/trunk/client'
11
12# Timeouts for powering down and up respectively
13HALT_TIME = 300
mbligh07c1eac2007-11-05 18:39:29 +000014BOOT_TIME = 1800
jadmanskiec859142008-05-29 21:33:39 +000015CRASH_RECOVERY_TIME = 9000
mbligh0e4613b2007-10-29 16:55:07 +000016
mblighdcd57a82007-07-11 23:06:47 +000017
mblighd8b39252008-03-20 21:15:03 +000018class BaseAutotest(installable_object.InstallableObject):
jadmanski0afbb632008-06-06 21:10:57 +000019 """
20 This class represents the Autotest program.
mblighdcd57a82007-07-11 23:06:47 +000021
jadmanski0afbb632008-06-06 21:10:57 +000022 Autotest is used to run tests automatically and collect the results.
23 It also supports profilers.
mblighdcd57a82007-07-11 23:06:47 +000024
jadmanski0afbb632008-06-06 21:10:57 +000025 Implementation details:
26 This is a leaf class in an abstract class hierarchy, it must
27 implement the unimplemented methods in parent classes.
28 """
mbligh119c12a2007-11-12 22:13:44 +000029
jadmanski0afbb632008-06-06 21:10:57 +000030 def __init__(self, host = None):
31 self.host = host
32 self.got = False
33 self.installed = False
jadmanskie2eef7b2009-03-03 23:55:13 +000034 self.lightweight = False
jadmanski0afbb632008-06-06 21:10:57 +000035 self.serverdir = utils.get_server_dir()
36 super(BaseAutotest, self).__init__()
mblighc8949b82007-07-23 16:33:58 +000037
mblighdc735a22007-08-02 16:54:37 +000038
jadmanskif22fea82008-11-26 20:57:07 +000039 install_in_tmpdir = False
40 @classmethod
41 def set_install_in_tmpdir(cls, flag):
42 """ Sets a flag that controls whether or not Autotest should by
43 default be installed in a "standard" directory (e.g.
44 /home/autotest, /usr/local/autotest) or a temporary directory. """
45 cls.install_in_tmpdir = flag
46
47
48 def _get_install_dir(self, host):
49 """ Determines the location where autotest should be installed on
50 host. If self.install_in_tmpdir is set, it will return a unique
51 temporary directory that autotest can be installed in. """
52 try:
53 autodir = _get_autodir(host)
54 except error.AutotestRunError:
55 autodir = '/usr/local/autotest'
56 if self.install_in_tmpdir:
57 autodir = host.get_tmp_dir(parent=autodir)
58 return autodir
59
60
mbligh1b3b3762008-09-25 02:46:34 +000061 @log.record
mblighb3c0c912008-11-27 00:32:45 +000062 def install(self, host=None, autodir=None):
63 self._install(host=host, autodir=autodir)
jadmanski54f90af2008-10-10 16:20:55 +000064
65
66 def install_base(self, host=None, autodir=None):
67 """ Performs a lightweight autotest install. Useful for when you
68 want to run some client-side code but don't want to pay the cost
69 of a full installation. """
70 self._install(host=host, autodir=autodir, lightweight=True)
71
72
73 def _install(self, host=None, autodir=None, lightweight=False):
jadmanski0afbb632008-06-06 21:10:57 +000074 """
75 Install autotest. If get() was not called previously, an
76 attempt will be made to install from the autotest svn
77 repository.
mbligh9a3f5e52008-05-28 21:21:43 +000078
jadmanski0afbb632008-06-06 21:10:57 +000079 Args:
jadmanski54f90af2008-10-10 16:20:55 +000080 host: a Host instance on which autotest will be installed
81 autodir: location on the remote host to install to
82 lightweight: exclude tests, deps and profilers, if possible
mbligh9a3f5e52008-05-28 21:21:43 +000083
jadmanski0afbb632008-06-06 21:10:57 +000084 Raises:
jadmanski54f90af2008-10-10 16:20:55 +000085 AutoservError: if a tarball was not specified and
86 the target host does not have svn installed in its path"""
jadmanski0afbb632008-06-06 21:10:57 +000087 if not host:
88 host = self.host
89 if not self.got:
90 self.get()
91 host.wait_up(timeout=30)
92 host.setup()
showardb18134f2009-03-20 20:52:18 +000093 logging.info("Installing autotest on %s", host.hostname)
mbligh40f122a2007-11-03 23:08:46 +000094
jadmanski54f90af2008-10-10 16:20:55 +000095 # set up the autotest directory on the remote machine
96 if not autodir:
jadmanskif22fea82008-11-26 20:57:07 +000097 autodir = self._get_install_dir(host)
mbligh0562e652008-08-20 20:11:45 +000098 host.set_autodir(autodir)
jadmanski3c236942009-03-04 17:51:26 +000099 host.run('mkdir -p %s' % utils.sh_escape(autodir))
mbligh40f122a2007-11-03 23:08:46 +0000100
jadmanski1c3c07b2009-03-03 23:29:36 +0000101 # make sure there are no files in $AUTODIR/results
102 results_path = os.path.join(autodir, 'results')
jadmanski3c236942009-03-04 17:51:26 +0000103 host.run('rm -rf %s/*' % utils.sh_escape(results_path),
jadmanski1c3c07b2009-03-03 23:29:36 +0000104 ignore_status=True)
105
mblighc5ddfd12008-08-04 17:15:00 +0000106 # Fetch the autotest client from the nearest repository
107 try:
108 c = global_config.global_config
109 repos = c.get_config_value("PACKAGES", 'fetch_location', type=list)
mbligh76d19f72008-10-15 16:24:43 +0000110 pkgmgr = packages.PackageManager(autodir, hostname=host.hostname,
111 repo_urls=repos,
mbligh1e3b0992008-10-14 16:29:54 +0000112 do_locking=False,
113 run_function=host.run,
114 run_function_dargs=dict(timeout=600))
mblighc5ddfd12008-08-04 17:15:00 +0000115 # The packages dir is used to store all the packages that
116 # are fetched on that client. (for the tests,deps etc.
117 # too apart from the client)
118 pkg_dir = os.path.join(autodir, 'packages')
119 # clean up the autodir except for the packages directory
120 host.run('cd %s && ls | grep -v "^packages$"'
121 ' | xargs rm -rf && rm -rf .[^.]*' % autodir)
122 pkgmgr.install_pkg('autotest', 'client', pkg_dir, autodir,
123 preserve_install_dir=True)
124 self.installed = True
jadmanskie2eef7b2009-03-03 23:55:13 +0000125 self.lightweight = lightweight
mblighc5ddfd12008-08-04 17:15:00 +0000126 return
127 except global_config.ConfigError, e:
showardb18134f2009-03-20 20:52:18 +0000128 logging.error("Could not install autotest using the packaging"
129 "system: %s", e)
mblighc5ddfd12008-08-04 17:15:00 +0000130 except (packages.PackageInstallError, error.AutoservRunError), e:
showardb18134f2009-03-20 20:52:18 +0000131 logging.error("Could not install autotest from %s", repos)
mblighc5ddfd12008-08-04 17:15:00 +0000132
jadmanski0afbb632008-06-06 21:10:57 +0000133 # try to install from file or directory
134 if self.source_material:
135 if os.path.isdir(self.source_material):
136 # Copy autotest recursively
jadmanski54f90af2008-10-10 16:20:55 +0000137 if lightweight:
138 dirs_to_exclude = set(["tests", "site_tests", "deps",
139 "tools", "profilers"])
140 light_files = [os.path.join(self.source_material, f)
141 for f in os.listdir(self.source_material)
142 if f not in dirs_to_exclude]
mbligh89e258d2008-10-24 13:58:08 +0000143 host.send_file(light_files, autodir, delete_dest=True)
jadmanski54f90af2008-10-10 16:20:55 +0000144
145 # create empty dirs for all the stuff we excluded
146 commands = []
147 for path in dirs_to_exclude:
148 abs_path = os.path.join(autodir, path)
149 abs_path = utils.sh_escape(abs_path)
150 commands.append("mkdir -p '%s'" % abs_path)
151 host.run(';'.join(commands))
152 else:
mbligh89e258d2008-10-24 13:58:08 +0000153 host.send_file(self.source_material, autodir,
154 delete_dest=True)
jadmanski0afbb632008-06-06 21:10:57 +0000155 else:
156 # Copy autotest via tarball
157 e_msg = 'Installation method not yet implemented!'
158 raise NotImplementedError(e_msg)
showardb18134f2009-03-20 20:52:18 +0000159 logging.info("Installation of autotest completed")
jadmanski0afbb632008-06-06 21:10:57 +0000160 self.installed = True
jadmanskie2eef7b2009-03-03 23:55:13 +0000161 self.lightweight = lightweight
jadmanski0afbb632008-06-06 21:10:57 +0000162 return
mbligh91334902007-09-28 01:47:59 +0000163
jadmanski0afbb632008-06-06 21:10:57 +0000164 # if that fails try to install using svn
165 if utils.run('which svn').exit_status:
mbligh78bf5352008-07-11 20:27:36 +0000166 raise error.AutoservError('svn not found on target machine: %s'
167 % host.name)
jadmanski0afbb632008-06-06 21:10:57 +0000168 try:
mbligh78bf5352008-07-11 20:27:36 +0000169 host.run('svn checkout %s %s' % (AUTOTEST_SVN, autodir))
jadmanski0afbb632008-06-06 21:10:57 +0000170 except error.AutoservRunError, e:
mbligh78bf5352008-07-11 20:27:36 +0000171 host.run('svn checkout %s %s' % (AUTOTEST_HTTP, autodir))
showardb18134f2009-03-20 20:52:18 +0000172 logging.info("Installation of autotest completed")
jadmanski0afbb632008-06-06 21:10:57 +0000173 self.installed = True
jadmanskie2eef7b2009-03-03 23:55:13 +0000174 self.lightweight = lightweight
mbligh91334902007-09-28 01:47:59 +0000175
176
jadmanski7c7aff32009-03-25 22:43:07 +0000177 def uninstall(self, host=None):
178 """
179 Uninstall (i.e. delete) autotest. Removes the autotest client install
180 from the specified host.
181
182 @params host a Host instance from which the client will be removed
183 """
184 if not self.installed:
185 return
186 if not host:
187 host = self.host
188 autodir = host.get_autodir()
189 if not autodir:
190 return
191
192 # perform the actual uninstall
193 host.run("rm -rf %s" % utils.sh_escape(autodir), ignore_status=True)
194 host.set_autodir(None)
195 self.installed = False
196
197
jadmanski0afbb632008-06-06 21:10:57 +0000198 def get(self, location = None):
199 if not location:
200 location = os.path.join(self.serverdir, '../client')
201 location = os.path.abspath(location)
202 # If there's stuff run on our client directory already, it
203 # can cause problems. Try giving it a quick clean first.
204 cwd = os.getcwd()
205 os.chdir(location)
206 os.system('tools/make_clean')
207 os.chdir(cwd)
208 super(BaseAutotest, self).get(location)
209 self.got = True
mblighdcd57a82007-07-11 23:06:47 +0000210
211
jadmanski0afbb632008-06-06 21:10:57 +0000212 def run(self, control_file, results_dir = '.', host = None,
jadmanski6dadd832009-02-05 23:39:27 +0000213 timeout=None, tag=None, parallel_flag=False, background=False,
214 client_disconnect_timeout=1800):
jadmanski0afbb632008-06-06 21:10:57 +0000215 """
216 Run an autotest job on the remote machine.
mbligh9a3f5e52008-05-28 21:21:43 +0000217
jadmanski0afbb632008-06-06 21:10:57 +0000218 Args:
219 control_file: an open file-like-obj of the control file
220 results_dir: a str path where the results should be stored
221 on the local filesystem
222 host: a Host instance on which the control file should
223 be run
224 tag: tag name for the client side instance of autotest
225 parallel_flag: flag set when multiple jobs are run at the
226 same time
mblighb3c0c912008-11-27 00:32:45 +0000227 background: indicates that the client should be launched as
228 a background job; the code calling run will be
229 responsible for monitoring the client and
230 collecting the results
jadmanski0afbb632008-06-06 21:10:57 +0000231 Raises:
232 AutotestRunError: if there is a problem executing
233 the control file
234 """
235 host = self._get_host_and_setup(host)
236 results_dir = os.path.abspath(results_dir)
mblighc1cbc992008-05-27 20:01:45 +0000237
jadmanski0afbb632008-06-06 21:10:57 +0000238 if tag:
239 results_dir = os.path.join(results_dir, tag)
mblighc1cbc992008-05-27 20:01:45 +0000240
mblighb3c0c912008-11-27 00:32:45 +0000241 atrun = _Run(host, results_dir, tag, parallel_flag, background)
jadmanski6dadd832009-02-05 23:39:27 +0000242 self._do_run(control_file, results_dir, host, atrun, timeout,
243 client_disconnect_timeout)
mblighd8b39252008-03-20 21:15:03 +0000244
245
jadmanski0afbb632008-06-06 21:10:57 +0000246 def _get_host_and_setup(self, host):
247 if not host:
248 host = self.host
249 if not self.installed:
250 self.install(host)
mbligh91334902007-09-28 01:47:59 +0000251
jadmanski0afbb632008-06-06 21:10:57 +0000252 host.wait_up(timeout=30)
253 return host
mblighd8b39252008-03-20 21:15:03 +0000254
255
jadmanski6dadd832009-02-05 23:39:27 +0000256 def _do_run(self, control_file, results_dir, host, atrun, timeout,
257 client_disconnect_timeout):
jadmanski0afbb632008-06-06 21:10:57 +0000258 try:
259 atrun.verify_machine()
260 except:
showardb18134f2009-03-20 20:52:18 +0000261 logging.error("Verify failed on %s. Reinstalling autotest",
262 host.hostname)
jadmanski0afbb632008-06-06 21:10:57 +0000263 self.install(host)
264 atrun.verify_machine()
265 debug = os.path.join(results_dir, 'debug')
266 try:
267 os.makedirs(debug)
mbligh09108442008-10-15 16:27:38 +0000268 except Exception:
jadmanski0afbb632008-06-06 21:10:57 +0000269 pass
mbligh9a3f5e52008-05-28 21:21:43 +0000270
mbligh09108442008-10-15 16:27:38 +0000271 delete_file_list = [atrun.remote_control_file,
272 atrun.remote_control_file + '.state',
273 atrun.manual_control_file,
274 atrun.manual_control_file + '.state']
275 cmd = ';'.join('rm -f ' + control for control in delete_file_list)
276 host.run(cmd, ignore_status=True)
mbligh9a3f5e52008-05-28 21:21:43 +0000277
jadmanski0afbb632008-06-06 21:10:57 +0000278 tmppath = utils.get(control_file)
mblighc5ddfd12008-08-04 17:15:00 +0000279
jadmanskicb0e1612009-02-27 18:03:10 +0000280 # build up the initialization prologue for the control file
281 prologue_lines = []
282 prologue_lines.append("job.default_boot_tag(%r)\n"
283 % host.job.last_boot_tag)
284 prologue_lines.append("job.default_test_cleanup(%r)\n"
285 % host.job.run_test_cleanup)
jadmanski23afbec2008-09-17 18:12:07 +0000286
mbligh09108442008-10-15 16:27:38 +0000287 # If the packaging system is being used, add the repository list.
mblighc5ddfd12008-08-04 17:15:00 +0000288 try:
mblighc5ddfd12008-08-04 17:15:00 +0000289 c = global_config.global_config
290 repos = c.get_config_value("PACKAGES", 'fetch_location', type=list)
mbligh76d19f72008-10-15 16:24:43 +0000291 pkgmgr = packages.PackageManager('autotest', hostname=host.hostname,
292 repo_urls=repos)
jadmanskie2eef7b2009-03-03 23:55:13 +0000293 prologue_lines.append('job.add_repository(%s)\n'
294 % pkgmgr.repo_urls)
mblighc5ddfd12008-08-04 17:15:00 +0000295 except global_config.ConfigError, e:
296 pass
297
jadmanskie2eef7b2009-03-03 23:55:13 +0000298 # on full-size installs, turn on any profilers the server is using
299 if not self.lightweight:
300 running_profilers = host.job.profilers.add_log.iteritems()
301 for profiler, (args, dargs) in running_profilers:
302 call_args = [repr(profiler)]
303 call_args += [repr(arg) for arg in args]
304 call_args += ["%s=%r" % item for item in dargs.iteritems()]
305 prologue_lines.append("job.profilers.add(%s)\n"
306 % ", ".join(call_args))
307 cfile = "".join(prologue_lines)
308
mbligh09108442008-10-15 16:27:38 +0000309 cfile += open(tmppath).read()
310 open(tmppath, "w").write(cfile)
mblighc5ddfd12008-08-04 17:15:00 +0000311
jadmanskic09fc152008-10-15 17:56:59 +0000312 # Create and copy state file to remote_control_file + '.state'
313 sysinfo_state = {"__sysinfo": host.job.sysinfo.serialize()}
314 state_file = self._create_state_file(host.job, sysinfo_state)
315 host.send_file(state_file, atrun.remote_control_file + '.state')
316 os.remove(state_file)
317
mblighc5ddfd12008-08-04 17:15:00 +0000318 # Copy control_file to remote_control_file on the host
jadmanski0afbb632008-06-06 21:10:57 +0000319 host.send_file(tmppath, atrun.remote_control_file)
320 if os.path.abspath(tmppath) != os.path.abspath(control_file):
321 os.remove(tmppath)
mbligh0e4613b2007-10-29 16:55:07 +0000322
jadmanski6bb32d72009-03-19 20:25:24 +0000323 atrun.execute_control(
jadmanski6dadd832009-02-05 23:39:27 +0000324 timeout=timeout,
325 client_disconnect_timeout=client_disconnect_timeout)
jadmanski23afbec2008-09-17 18:12:07 +0000326
327
jadmanskic09fc152008-10-15 17:56:59 +0000328 def _create_state_file(self, job, state_dict):
329 """ Create a state file from a dictionary. Returns the path of the
330 state file. """
331 fd, path = tempfile.mkstemp(dir=job.tmpdir)
332 state_file = os.fdopen(fd, "w")
333 pickle.dump(state_dict, state_file)
334 state_file.close()
335 return path
336
337
jadmanski0afbb632008-06-06 21:10:57 +0000338 def run_timed_test(self, test_name, results_dir='.', host=None,
jadmanskic98c4702009-01-05 15:50:06 +0000339 timeout=None, *args, **dargs):
jadmanski0afbb632008-06-06 21:10:57 +0000340 """
341 Assemble a tiny little control file to just run one test,
342 and run it as an autotest client-side test
343 """
344 if not host:
345 host = self.host
346 if not self.installed:
347 self.install(host)
348 opts = ["%s=%s" % (o[0], repr(o[1])) for o in dargs.items()]
349 cmd = ", ".join([repr(test_name)] + map(repr, args) + opts)
350 control = "job.run_test(%s)\n" % cmd
jadmanskic98c4702009-01-05 15:50:06 +0000351 self.run(control, results_dir, host, timeout=timeout)
mbligh0e4613b2007-10-29 16:55:07 +0000352
353
jadmanskic98c4702009-01-05 15:50:06 +0000354 def run_test(self, test_name, results_dir='.', host=None, *args, **dargs):
jadmanski0afbb632008-06-06 21:10:57 +0000355 self.run_timed_test(test_name, results_dir, host, timeout=None,
jadmanskic98c4702009-01-05 15:50:06 +0000356 *args, **dargs)
mblighd54832b2007-07-25 16:46:56 +0000357
358
mblighdcd57a82007-07-11 23:06:47 +0000359class _Run(object):
jadmanski0afbb632008-06-06 21:10:57 +0000360 """
361 Represents a run of autotest control file. This class maintains
362 all the state necessary as an autotest control file is executed.
mblighdcd57a82007-07-11 23:06:47 +0000363
jadmanski0afbb632008-06-06 21:10:57 +0000364 It is not intended to be used directly, rather control files
365 should be run using the run method in Autotest.
366 """
mblighb3c0c912008-11-27 00:32:45 +0000367 def __init__(self, host, results_dir, tag, parallel_flag, background):
jadmanski0afbb632008-06-06 21:10:57 +0000368 self.host = host
369 self.results_dir = results_dir
370 self.env = host.env
371 self.tag = tag
372 self.parallel_flag = parallel_flag
mblighb3c0c912008-11-27 00:32:45 +0000373 self.background = background
jadmanski0afbb632008-06-06 21:10:57 +0000374 self.autodir = _get_autodir(self.host)
mbligh78bf5352008-07-11 20:27:36 +0000375 control = os.path.join(self.autodir, 'control')
jadmanski0afbb632008-06-06 21:10:57 +0000376 if tag:
mbligh78bf5352008-07-11 20:27:36 +0000377 control += '.' + tag
378 self.manual_control_file = control
379 self.remote_control_file = control + '.autoserv'
mblighdc735a22007-08-02 16:54:37 +0000380
381
jadmanski0afbb632008-06-06 21:10:57 +0000382 def verify_machine(self):
383 binary = os.path.join(self.autodir, 'bin/autotest')
384 try:
385 self.host.run('ls %s > /dev/null 2>&1' % binary)
386 except:
387 raise "Autotest does not appear to be installed"
mblighdc735a22007-08-02 16:54:37 +0000388
jadmanski0afbb632008-06-06 21:10:57 +0000389 if not self.parallel_flag:
390 tmpdir = os.path.join(self.autodir, 'tmp')
391 download = os.path.join(self.autodir, 'tests/download')
392 self.host.run('umount %s' % tmpdir, ignore_status=True)
393 self.host.run('umount %s' % download, ignore_status=True)
mblighdc735a22007-08-02 16:54:37 +0000394
jadmanski6dadd832009-02-05 23:39:27 +0000395
396 def get_base_cmd_args(self, section):
397 args = []
jadmanski0afbb632008-06-06 21:10:57 +0000398 if section > 0:
jadmanski6dadd832009-02-05 23:39:27 +0000399 args.append('-c')
jadmanski0afbb632008-06-06 21:10:57 +0000400 if self.tag:
jadmanski6dadd832009-02-05 23:39:27 +0000401 args.append('-t %s' % self.tag)
jadmanski0afbb632008-06-06 21:10:57 +0000402 if self.host.job.use_external_logging():
jadmanski6dadd832009-02-05 23:39:27 +0000403 args.append('-l')
404 args.append(self.remote_control_file)
405 return args
406
407
408 def get_background_cmd(self, section):
409 cmd = ['nohup', os.path.join(self.autodir, 'bin/autotest_client')]
410 cmd += self.get_base_cmd_args(section)
411 cmd.append('>/dev/null 2>/dev/null &')
412 return ' '.join(cmd)
413
414
415 def get_daemon_cmd(self, section, monitor_dir):
416 cmd = ['nohup', os.path.join(self.autodir, 'bin/autotestd'),
417 monitor_dir, '-H autoserv']
418 cmd += self.get_base_cmd_args(section)
419 cmd.append('>/dev/null 2>/dev/null </dev/null &')
420 return ' '.join(cmd)
421
422
423 def get_monitor_cmd(self, monitor_dir, stdout_read, stderr_read):
424 cmd = [os.path.join(self.autodir, 'bin', 'autotestd_monitor'),
425 monitor_dir, str(stdout_read), str(stderr_read)]
jadmanski0afbb632008-06-06 21:10:57 +0000426 return ' '.join(cmd)
mblighadf2aab2007-11-29 18:16:43 +0000427
mblighd8b39252008-03-20 21:15:03 +0000428
jadmanski0afbb632008-06-06 21:10:57 +0000429 def get_client_log(self, section):
jadmanskie0c7fb62008-12-16 20:51:16 +0000430 """ Find what the "next" client.log.* file should be and open it. """
431 debug_dir = os.path.join(self.results_dir, "debug")
432 client_logs = glob.glob(os.path.join(debug_dir, "client.log.*"))
433 next_log = os.path.join(debug_dir, "client.log.%d" % len(client_logs))
434 return open(next_log, "w", 0)
mblighd8b39252008-03-20 21:15:03 +0000435
436
jadmanskib264ed02009-01-12 23:54:27 +0000437 @staticmethod
438 def is_client_job_finished(last_line):
439 return bool(re.match(r'^END .*\t----\t----\t.*$', last_line))
440
441
442 @staticmethod
443 def is_client_job_rebooting(last_line):
444 return bool(re.match(r'^\t*GOOD\t----\treboot\.start.*$', last_line))
445
446
447 def log_unexpected_abort(self):
448 msg = "Autotest client terminated unexpectedly"
449 self.host.job.record("END ABORT", None, None, msg)
450
451
jadmanski6dadd832009-02-05 23:39:27 +0000452 def _execute_in_background(self, section, timeout):
453 full_cmd = self.get_background_cmd(section)
454 devnull = open(os.devnull, "w")
mblighd8b39252008-03-20 21:15:03 +0000455
jadmanski6dadd832009-02-05 23:39:27 +0000456 old_resultdir = self.host.job.resultdir
jadmanski0afbb632008-06-06 21:10:57 +0000457 try:
jadmanski0afbb632008-06-06 21:10:57 +0000458 self.host.job.resultdir = self.results_dir
459 result = self.host.run(full_cmd, ignore_status=True,
460 timeout=timeout,
jadmanski6dadd832009-02-05 23:39:27 +0000461 stdout_tee=devnull,
462 stderr_tee=devnull)
jadmanski0afbb632008-06-06 21:10:57 +0000463 finally:
jadmanski0afbb632008-06-06 21:10:57 +0000464 self.host.job.resultdir = old_resultdir
jadmanski6dadd832009-02-05 23:39:27 +0000465
466 return result
467
468
469 @staticmethod
470 def _strip_stderr_prologue(stderr):
471 """Strips the 'standard' prologue that get pre-pended to every
472 remote command and returns the text that was actually written to
473 stderr by the remote command."""
474 stderr_lines = stderr.split("\n")[1:]
475 if not stderr_lines:
476 return ""
477 elif stderr_lines[0].startswith("NOTE: autotestd_monitor"):
478 del stderr_lines[0]
479 return "\n".join(stderr_lines)
480
481
482 def _execute_daemon(self, section, timeout, stderr_redirector,
483 client_disconnect_timeout):
484 monitor_dir = self.host.get_tmp_dir()
485 daemon_cmd = self.get_daemon_cmd(section, monitor_dir)
486 client_log = self.get_client_log(section)
487
488 stdout_read = stderr_read = 0
489 old_resultdir = self.host.job.resultdir
490 try:
jadmanski29a4c702009-03-03 23:30:59 +0000491 self.host.job.resultdir = self.results_dir
jadmanski6dadd832009-02-05 23:39:27 +0000492 self.host.run(daemon_cmd, ignore_status=True, timeout=timeout)
jadmanski91d56a92009-04-01 15:20:40 +0000493 disconnect_warnings = []
jadmanski6dadd832009-02-05 23:39:27 +0000494 while True:
495 monitor_cmd = self.get_monitor_cmd(monitor_dir, stdout_read,
496 stderr_read)
497 try:
498 result = self.host.run(monitor_cmd, ignore_status=True,
499 timeout=timeout,
500 stdout_tee=client_log,
501 stderr_tee=stderr_redirector)
502 except error.AutoservRunError, e:
503 result = e.result_obj
504 result.exit_status = None
jadmanski91d56a92009-04-01 15:20:40 +0000505 disconnect_warnings.append(e.description)
506
jadmanski6dadd832009-02-05 23:39:27 +0000507 stderr_redirector.log_warning(
jadmanski91d56a92009-04-01 15:20:40 +0000508 "Autotest client was disconnected: %s" % e.description,
509 "NETWORK")
jadmanski6dadd832009-02-05 23:39:27 +0000510 except error.AutoservSSHTimeout:
511 result = utils.CmdResult(monitor_cmd, "", "", None, 0)
512 stderr_redirector.log_warning(
jadmanski91d56a92009-04-01 15:20:40 +0000513 "Attempt to connect to Autotest client timed out",
514 "NETWORK")
jadmanski6dadd832009-02-05 23:39:27 +0000515
516 stdout_read += len(result.stdout)
517 stderr_read += len(self._strip_stderr_prologue(result.stderr))
518
519 if result.exit_status is not None:
520 return result
521 elif not self.host.wait_up(client_disconnect_timeout):
522 raise error.AutoservSSHTimeout(
523 "client was disconnected, reconnect timed out")
524 finally:
525 self.host.job.resultdir = old_resultdir
526
527
528 def execute_section(self, section, timeout, stderr_redirector,
529 client_disconnect_timeout):
showardb18134f2009-03-20 20:52:18 +0000530 logging.info("Executing %s/bin/autotest %s/control phase %d",
531 self.autodir, self.autodir, section)
jadmanski6dadd832009-02-05 23:39:27 +0000532
533 if self.background:
534 result = self._execute_in_background(section, timeout)
535 else:
536 result = self._execute_daemon(section, timeout, stderr_redirector,
537 client_disconnect_timeout)
538
539 last_line = stderr_redirector.last_line
mbligh2bf2db62007-11-27 00:53:18 +0000540
jadmanskib264ed02009-01-12 23:54:27 +0000541 # check if we failed hard enough to warrant an exception
jadmanski0afbb632008-06-06 21:10:57 +0000542 if result.exit_status == 1:
jadmanskib264ed02009-01-12 23:54:27 +0000543 err = error.AutotestRunError("client job was aborted")
544 elif not self.background and not result.stderr:
545 err = error.AutotestRunError(
jadmanskie4130532009-03-17 18:01:28 +0000546 "execute_section %s failed to return anything\n"
547 "stdout:%s\n" % (section, result.stdout))
jadmanskib264ed02009-01-12 23:54:27 +0000548 else:
549 err = None
mbligh0e4613b2007-10-29 16:55:07 +0000550
jadmanskib264ed02009-01-12 23:54:27 +0000551 # log something if the client failed AND never finished logging
552 if err and not self.is_client_job_finished(last_line):
553 self.log_unexpected_abort()
554
555 if err:
556 raise err
557 else:
558 return stderr_redirector.last_line
jadmanski4600e342008-10-29 22:54:00 +0000559
560
561 def _wait_for_reboot(self):
showardb18134f2009-03-20 20:52:18 +0000562 logging.info("Client is rebooting")
563 logging.info("Waiting for client to halt")
jadmanski4600e342008-10-29 22:54:00 +0000564 if not self.host.wait_down(HALT_TIME):
565 err = "%s failed to shutdown after %d"
566 err %= (self.host.hostname, HALT_TIME)
567 raise error.AutotestRunError(err)
showardb18134f2009-03-20 20:52:18 +0000568 logging.info("Client down, waiting for restart")
jadmanski4600e342008-10-29 22:54:00 +0000569 if not self.host.wait_up(BOOT_TIME):
570 # since reboot failed
571 # hardreset the machine once if possible
572 # before failing this control file
573 warning = "%s did not come back up, hard resetting"
574 warning %= self.host.hostname
showardb18134f2009-03-20 20:52:18 +0000575 logging.warning(warning)
jadmanski4600e342008-10-29 22:54:00 +0000576 try:
577 self.host.hardreset(wait=False)
mblighd99d3b272008-12-22 14:41:27 +0000578 except (AttributeError, error.AutoservUnsupportedError):
jadmanski4600e342008-10-29 22:54:00 +0000579 warning = "Hard reset unsupported on %s"
580 warning %= self.host.hostname
showardb18134f2009-03-20 20:52:18 +0000581 logging.warning(warning)
jadmanski4600e342008-10-29 22:54:00 +0000582 raise error.AutotestRunError("%s failed to boot after %ds" %
583 (self.host.hostname, BOOT_TIME))
584 self.host.reboot_followup()
mblighdc735a22007-08-02 16:54:37 +0000585
586
jadmanski6bb32d72009-03-19 20:25:24 +0000587 def _process_client_state_file(self):
588 state_file = os.path.basename(self.remote_control_file) + ".state"
589 state_path = os.path.join(self.results_dir, state_file)
590 try:
591 state_dict = pickle.load(open(state_path))
592 except Exception, e:
593 msg = "Ignoring error while loading client job state file: %s" % e
594 self.logger.warning(msg)
595 state_dict = {}
596
597 # clear out the state file
598 # TODO: stash the file away somewhere useful instead
599 try:
600 os.remove(state_path)
601 except Exception:
602 pass
603
604 msg = "Persistent state variables pulled back from %s: %s"
605 msg %= (self.host.hostname, state_dict)
606 print msg
607
608 if "__run_test_cleanup" in state_dict:
609 if state_dict["__run_test_cleanup"]:
610 self.host.job.enable_test_cleanup()
611 else:
612 self.host.job.disable_test_cleanup()
613
614 if "__last_boot_tag" in state_dict:
615 self.host.job.last_boot_tag = state_dict["__last_boot_tag"]
616
617 if "__sysinfo" in state_dict:
618 self.host.job.sysinfo.deserialize(state_dict["__sysinfo"])
619
620
jadmanski6dadd832009-02-05 23:39:27 +0000621 def execute_control(self, timeout=None, client_disconnect_timeout=None):
jadmanski6bb32d72009-03-19 20:25:24 +0000622 if not self.background:
623 collector = log_collector(self.host, self.tag, self.results_dir)
624 hostname = self.host.hostname
625 remote_results = collector.client_results_dir
626 local_results = collector.server_results_dir
627 self.host.job.add_client_log(hostname, remote_results,
628 local_results)
629
jadmanski0afbb632008-06-06 21:10:57 +0000630 section = 0
jadmanski4600e342008-10-29 22:54:00 +0000631 start_time = time.time()
632
jadmanski043e1132008-11-19 17:10:32 +0000633 logger = client_logger(self.host, self.tag, self.results_dir)
jadmanski4600e342008-10-29 22:54:00 +0000634 try:
635 while not timeout or time.time() < start_time + timeout:
636 if timeout:
637 section_timeout = start_time + timeout - time.time()
638 else:
639 section_timeout = None
640 last = self.execute_section(section, section_timeout,
jadmanski6dadd832009-02-05 23:39:27 +0000641 logger, client_disconnect_timeout)
mblighb3c0c912008-11-27 00:32:45 +0000642 if self.background:
643 return
jadmanski4600e342008-10-29 22:54:00 +0000644 section += 1
jadmanskib264ed02009-01-12 23:54:27 +0000645 if self.is_client_job_finished(last):
showardb18134f2009-03-20 20:52:18 +0000646 logging.info("Client complete")
jadmanski4600e342008-10-29 22:54:00 +0000647 return
jadmanskib264ed02009-01-12 23:54:27 +0000648 elif self.is_client_job_rebooting(last):
jadmanski79ab9282008-11-11 17:53:12 +0000649 try:
650 self._wait_for_reboot()
651 except error.AutotestRunError, e:
jadmanski043e1132008-11-19 17:10:32 +0000652 self.host.job.record("ABORT", None, "reboot", str(e))
653 self.host.job.record("END ABORT", None, None, str(e))
jadmanski79ab9282008-11-11 17:53:12 +0000654 raise
jadmanski4600e342008-10-29 22:54:00 +0000655 continue
656
657 # if we reach here, something unexpected happened
jadmanskib264ed02009-01-12 23:54:27 +0000658 self.log_unexpected_abort()
jadmanski4600e342008-10-29 22:54:00 +0000659
660 # give the client machine a chance to recover from a crash
661 self.host.wait_up(CRASH_RECOVERY_TIME)
662 msg = ("Aborting - unexpected final status message from "
663 "client: %s\n") % last
664 raise error.AutotestRunError(msg)
665 finally:
jadmanski043e1132008-11-19 17:10:32 +0000666 logger.close()
jadmanski6bb32d72009-03-19 20:25:24 +0000667 if not self.background:
668 collector.collect_client_job_results()
669 self._process_client_state_file()
670 self.host.job.remove_client_log(hostname, remote_results,
671 local_results)
mblighdcd57a82007-07-11 23:06:47 +0000672
jadmanski0afbb632008-06-06 21:10:57 +0000673 # should only get here if we timed out
674 assert timeout
675 raise error.AutotestTimeoutError()
mbligh0e4613b2007-10-29 16:55:07 +0000676
mblighdcd57a82007-07-11 23:06:47 +0000677
678def _get_autodir(host):
mbligh3c7a1502008-07-24 18:08:47 +0000679 autodir = host.get_autodir()
680 if autodir:
681 return autodir
jadmanski0afbb632008-06-06 21:10:57 +0000682 try:
683 # There's no clean way to do this. readlink may not exist
684 cmd = "python -c 'import os,sys; print os.readlink(sys.argv[1])' /etc/autotest.conf 2> /dev/null"
mbligh3c7a1502008-07-24 18:08:47 +0000685 autodir = os.path.dirname(host.run(cmd).stdout)
686 if autodir:
687 return autodir
jadmanski0afbb632008-06-06 21:10:57 +0000688 except error.AutoservRunError:
689 pass
690 for path in ['/usr/local/autotest', '/home/autotest']:
691 try:
jadmanski169ecad2008-09-12 15:49:44 +0000692 host.run('ls %s > /dev/null 2>&1' %
693 os.path.join(path, 'bin/autotest'))
jadmanski0afbb632008-06-06 21:10:57 +0000694 return path
695 except error.AutoservRunError:
696 pass
697 raise error.AutotestRunError("Cannot figure out autotest directory")
mblighd8b39252008-03-20 21:15:03 +0000698
699
jadmanski043e1132008-11-19 17:10:32 +0000700class log_collector(object):
701 def __init__(self, host, client_tag, results_dir):
702 self.host = host
703 if not client_tag:
704 client_tag = "default"
705 self.client_results_dir = os.path.join(host.get_autodir(), "results",
706 client_tag)
707 self.server_results_dir = results_dir
708
709
710 def collect_client_job_results(self):
711 """ A method that collects all the current results of a running
712 client job into the results dir. By default does nothing as no
713 client job is running, but when running a client job you can override
714 this with something that will actually do something. """
715
716 # make an effort to wait for the machine to come up
717 try:
718 self.host.wait_up(timeout=30)
719 except error.AutoservError:
720 # don't worry about any errors, we'll try and
721 # get the results anyway
722 pass
723
724
725 # Copy all dirs in default to results_dir
726 try:
727 keyval_path = self._prepare_for_copying_logs()
728 self.host.get_file(self.client_results_dir + '/',
729 self.server_results_dir)
730 self._process_copied_logs(keyval_path)
731 self._postprocess_copied_logs()
732 except Exception:
733 # well, don't stop running just because we couldn't get logs
showardb18134f2009-03-20 20:52:18 +0000734 e_msg = "Unexpected error copying test result logs, continuing ..."
735 logging.error(e_msg)
jadmanski043e1132008-11-19 17:10:32 +0000736 traceback.print_exc(file=sys.stdout)
737
738
739 def _prepare_for_copying_logs(self):
740 server_keyval = os.path.join(self.server_results_dir, 'keyval')
741 if not os.path.exists(server_keyval):
742 # Client-side keyval file can be copied directly
743 return
744
745 # Copy client-side keyval to temporary location
746 suffix = '.keyval_%s' % self.host.hostname
747 fd, keyval_path = tempfile.mkstemp(suffix)
748 os.close(fd)
749 try:
750 client_keyval = os.path.join(self.client_results_dir, 'keyval')
751 try:
752 self.host.get_file(client_keyval, keyval_path)
753 finally:
754 # We will squirrel away the client side keyval
755 # away and move it back when we are done
756 remote_temp_dir = self.host.get_tmp_dir()
757 self.temp_keyval_path = os.path.join(remote_temp_dir, "keyval")
758 self.host.run('mv %s %s' % (client_keyval,
759 self.temp_keyval_path))
760 except (error.AutoservRunError, error.AutoservSSHTimeout):
showardb18134f2009-03-20 20:52:18 +0000761 logging.error("Prepare for copying logs failed")
jadmanski043e1132008-11-19 17:10:32 +0000762 return keyval_path
763
764
765 def _process_copied_logs(self, keyval_path):
766 if not keyval_path:
767 # Client-side keyval file was copied directly
768 return
769
770 # Append contents of keyval_<host> file to keyval file
771 try:
772 # Read in new and old keyval files
773 new_keyval = utils.read_keyval(keyval_path)
774 old_keyval = utils.read_keyval(self.server_results_dir)
775 # 'Delete' from new keyval entries that are in both
776 tmp_keyval = {}
777 for key, val in new_keyval.iteritems():
778 if key not in old_keyval:
779 tmp_keyval[key] = val
780 # Append new info to keyval file
781 utils.write_keyval(self.server_results_dir, tmp_keyval)
782 # Delete keyval_<host> file
783 os.remove(keyval_path)
784 except IOError:
showardb18134f2009-03-20 20:52:18 +0000785 logging.error("Process copied logs failed")
jadmanski043e1132008-11-19 17:10:32 +0000786
787
788 def _postprocess_copied_logs(self):
789 # we can now put our keyval file back
790 client_keyval = os.path.join(self.client_results_dir, 'keyval')
791 try:
792 self.host.run('mv %s %s' % (self.temp_keyval_path, client_keyval))
793 except Exception:
794 pass
795
796
797
798# a file-like object for catching stderr from an autotest client and
799# extracting status logs from it
800class client_logger(object):
801 """Partial file object to write to both stdout and
802 the status log file. We only implement those methods
803 utils.run() actually calls.
804
805 Note that this class is fairly closely coupled with server_job, as it
806 uses special job._ methods to actually carry out the loggging.
807 """
808 status_parser = re.compile(r"^AUTOTEST_STATUS:([^:]*):(.*)$")
809 test_complete_parser = re.compile(r"^AUTOTEST_TEST_COMPLETE:(.*)$")
810 extract_indent = re.compile(r"^(\t*).*$")
811
812 def __init__(self, host, tag, server_results_dir):
813 self.host = host
814 self.job = host.job
815 self.log_collector = log_collector(host, tag, server_results_dir)
816 self.leftover = ""
817 self.last_line = ""
818 self.logs = {}
jadmanski6dadd832009-02-05 23:39:27 +0000819 self.server_warnings = []
jadmanski043e1132008-11-19 17:10:32 +0000820
821
822 def _process_log_dict(self, log_dict):
823 log_list = log_dict.pop("logs", [])
824 for key in sorted(log_dict.iterkeys()):
825 log_list += self._process_log_dict(log_dict.pop(key))
826 return log_list
827
828
829 def _process_logs(self):
830 """Go through the accumulated logs in self.log and print them
831 out to stdout and the status log. Note that this processes
832 logs in an ordering where:
833
834 1) logs to different tags are never interleaved
835 2) logs to x.y come before logs to x.y.z for all z
836 3) logs to x.y come before x.z whenever y < z
837
838 Note that this will in general not be the same as the
839 chronological ordering of the logs. However, if a chronological
840 ordering is desired that one can be reconstructed from the
841 status log by looking at timestamp lines."""
842 log_list = self._process_log_dict(self.logs)
843 for line in log_list:
844 self.job._record_prerendered(line + '\n')
845 if log_list:
846 self.last_line = log_list[-1]
847
848
849 def _process_quoted_line(self, tag, line):
850 """Process a line quoted with an AUTOTEST_STATUS flag. If the
851 tag is blank then we want to push out all the data we've been
852 building up in self.logs, and then the newest line. If the
853 tag is not blank, then push the line into the logs for handling
854 later."""
showardb18134f2009-03-20 20:52:18 +0000855 logging.info(line)
jadmanski043e1132008-11-19 17:10:32 +0000856 if tag == "":
857 self._process_logs()
858 self.job._record_prerendered(line + '\n')
859 self.last_line = line
860 else:
861 tag_parts = [int(x) for x in tag.split(".")]
862 log_dict = self.logs
863 for part in tag_parts:
864 log_dict = log_dict.setdefault(part, {})
865 log_list = log_dict.setdefault("logs", [])
866 log_list.append(line)
867
868
jadmanskif37df842009-02-11 00:03:26 +0000869 def _process_info_line(self, line):
870 """Check if line is an INFO line, and if it is, interpret any control
871 messages (e.g. enabling/disabling warnings) that it may contain."""
872 match = re.search(r"^\t*INFO\t----\t----(.*)\t[^\t]*$", line)
873 if not match:
874 return # not an INFO line
875 for field in match.group(1).split('\t'):
876 if field.startswith("warnings.enable="):
jadmanski16a7ff72009-04-01 18:19:53 +0000877 func = self.job.warning_manager.enable_warnings
jadmanskif37df842009-02-11 00:03:26 +0000878 elif field.startswith("warnings.disable="):
jadmanski16a7ff72009-04-01 18:19:53 +0000879 func = self.job.warning_manager.disable_warnings
jadmanskif37df842009-02-11 00:03:26 +0000880 else:
881 continue
882 warning_type = field.split("=", 1)[1]
jadmanski16a7ff72009-04-01 18:19:53 +0000883 func(warning_type)
jadmanskif37df842009-02-11 00:03:26 +0000884
885
jadmanski043e1132008-11-19 17:10:32 +0000886 def _process_line(self, line):
887 """Write out a line of data to the appropriate stream. Status
888 lines sent by autotest will be prepended with
889 "AUTOTEST_STATUS", and all other lines are ssh error
890 messages."""
891 status_match = self.status_parser.search(line)
892 test_complete_match = self.test_complete_parser.search(line)
893 if status_match:
894 tag, line = status_match.groups()
jadmanskif37df842009-02-11 00:03:26 +0000895 self._process_info_line(line)
jadmanski043e1132008-11-19 17:10:32 +0000896 self._process_quoted_line(tag, line)
897 elif test_complete_match:
jadmanskifcc0d5d2009-02-12 21:52:54 +0000898 self._process_logs()
jadmanski043e1132008-11-19 17:10:32 +0000899 fifo_path, = test_complete_match.groups()
900 self.log_collector.collect_client_job_results()
901 self.host.run("echo A > %s" % fifo_path)
902 else:
showardb18134f2009-03-20 20:52:18 +0000903 logging.info(line)
jadmanski043e1132008-11-19 17:10:32 +0000904
905
906 def _format_warnings(self, last_line, warnings):
907 # use the indentation of whatever the last log line was
908 indent = self.extract_indent.match(last_line).group(1)
909 # if the last line starts a new group, add an extra indent
910 if last_line.lstrip('\t').startswith("START\t"):
911 indent += '\t'
912 return [self.job._render_record("WARN", None, None, msg,
913 timestamp, indent).rstrip('\n')
914 for timestamp, msg in warnings]
915
916
917 def _process_warnings(self, last_line, log_dict, warnings):
918 if log_dict.keys() in ([], ["logs"]):
919 # there are no sub-jobs, just append the warnings here
920 warnings = self._format_warnings(last_line, warnings)
921 log_list = log_dict.setdefault("logs", [])
922 log_list += warnings
923 for warning in warnings:
924 sys.stdout.write(warning + '\n')
925 else:
926 # there are sub-jobs, so put the warnings in there
927 log_list = log_dict.get("logs", [])
928 if log_list:
929 last_line = log_list[-1]
930 for key in sorted(log_dict.iterkeys()):
931 if key != "logs":
932 self._process_warnings(last_line,
933 log_dict[key],
934 warnings)
935
jadmanskif37df842009-02-11 00:03:26 +0000936
jadmanski91d56a92009-04-01 15:20:40 +0000937 def log_warning(self, msg, warning_type):
jadmanski6dadd832009-02-05 23:39:27 +0000938 """Injects a WARN message into the current status logging stream."""
jadmanski91d56a92009-04-01 15:20:40 +0000939 timestamp = int(time.time())
940 if self.job.warning_manager.is_valid(timestamp, warning_type):
941 self.server_warnings.append((timestamp, msg))
jadmanski6dadd832009-02-05 23:39:27 +0000942
jadmanski043e1132008-11-19 17:10:32 +0000943
944 def write(self, data):
945 # first check for any new console warnings
jadmanski6dadd832009-02-05 23:39:27 +0000946 warnings = self.job._read_warnings() + self.server_warnings
947 warnings.sort() # sort into timestamp order
948 self.server_warnings = []
jadmanski043e1132008-11-19 17:10:32 +0000949 self._process_warnings(self.last_line, self.logs, warnings)
950 # now process the newest data written out
951 data = self.leftover + data
952 lines = data.split("\n")
953 # process every line but the last one
954 for line in lines[:-1]:
955 self._process_line(line)
956 # save the last line for later processing
957 # since we may not have the whole line yet
958 self.leftover = lines[-1]
959
960
961 def flush(self):
962 sys.stdout.flush()
963
964
965 def close(self):
966 if self.leftover:
967 self._process_line(self.leftover)
968 self._process_logs()
969 self.flush()
970
971
mbligha7007722009-01-13 00:37:11 +0000972SiteAutotest = client_utils.import_site_class(
973 __file__, "autotest_lib.server.site_autotest", "SiteAutotest",
974 BaseAutotest)
mblighd8b39252008-03-20 21:15:03 +0000975
976class Autotest(SiteAutotest):
jadmanski0afbb632008-06-06 21:10:57 +0000977 pass