blob: 986aa1703bc24891e98cb38ffdc4df67594d45a6 [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
jadmanski0afbb632008-06-06 21:10:57 +0000177 def get(self, location = None):
178 if not location:
179 location = os.path.join(self.serverdir, '../client')
180 location = os.path.abspath(location)
181 # If there's stuff run on our client directory already, it
182 # can cause problems. Try giving it a quick clean first.
183 cwd = os.getcwd()
184 os.chdir(location)
185 os.system('tools/make_clean')
186 os.chdir(cwd)
187 super(BaseAutotest, self).get(location)
188 self.got = True
mblighdcd57a82007-07-11 23:06:47 +0000189
190
jadmanski0afbb632008-06-06 21:10:57 +0000191 def run(self, control_file, results_dir = '.', host = None,
jadmanski6dadd832009-02-05 23:39:27 +0000192 timeout=None, tag=None, parallel_flag=False, background=False,
193 client_disconnect_timeout=1800):
jadmanski0afbb632008-06-06 21:10:57 +0000194 """
195 Run an autotest job on the remote machine.
mbligh9a3f5e52008-05-28 21:21:43 +0000196
jadmanski0afbb632008-06-06 21:10:57 +0000197 Args:
198 control_file: an open file-like-obj of the control file
199 results_dir: a str path where the results should be stored
200 on the local filesystem
201 host: a Host instance on which the control file should
202 be run
203 tag: tag name for the client side instance of autotest
204 parallel_flag: flag set when multiple jobs are run at the
205 same time
mblighb3c0c912008-11-27 00:32:45 +0000206 background: indicates that the client should be launched as
207 a background job; the code calling run will be
208 responsible for monitoring the client and
209 collecting the results
jadmanski0afbb632008-06-06 21:10:57 +0000210 Raises:
211 AutotestRunError: if there is a problem executing
212 the control file
213 """
214 host = self._get_host_and_setup(host)
215 results_dir = os.path.abspath(results_dir)
mblighc1cbc992008-05-27 20:01:45 +0000216
jadmanski0afbb632008-06-06 21:10:57 +0000217 if tag:
218 results_dir = os.path.join(results_dir, tag)
mblighc1cbc992008-05-27 20:01:45 +0000219
mblighb3c0c912008-11-27 00:32:45 +0000220 atrun = _Run(host, results_dir, tag, parallel_flag, background)
jadmanski6dadd832009-02-05 23:39:27 +0000221 self._do_run(control_file, results_dir, host, atrun, timeout,
222 client_disconnect_timeout)
mblighd8b39252008-03-20 21:15:03 +0000223
224
jadmanski0afbb632008-06-06 21:10:57 +0000225 def _get_host_and_setup(self, host):
226 if not host:
227 host = self.host
228 if not self.installed:
229 self.install(host)
mbligh91334902007-09-28 01:47:59 +0000230
jadmanski0afbb632008-06-06 21:10:57 +0000231 host.wait_up(timeout=30)
232 return host
mblighd8b39252008-03-20 21:15:03 +0000233
234
jadmanski6dadd832009-02-05 23:39:27 +0000235 def _do_run(self, control_file, results_dir, host, atrun, timeout,
236 client_disconnect_timeout):
jadmanski0afbb632008-06-06 21:10:57 +0000237 try:
238 atrun.verify_machine()
239 except:
showardb18134f2009-03-20 20:52:18 +0000240 logging.error("Verify failed on %s. Reinstalling autotest",
241 host.hostname)
jadmanski0afbb632008-06-06 21:10:57 +0000242 self.install(host)
243 atrun.verify_machine()
244 debug = os.path.join(results_dir, 'debug')
245 try:
246 os.makedirs(debug)
mbligh09108442008-10-15 16:27:38 +0000247 except Exception:
jadmanski0afbb632008-06-06 21:10:57 +0000248 pass
mbligh9a3f5e52008-05-28 21:21:43 +0000249
mbligh09108442008-10-15 16:27:38 +0000250 delete_file_list = [atrun.remote_control_file,
251 atrun.remote_control_file + '.state',
252 atrun.manual_control_file,
253 atrun.manual_control_file + '.state']
254 cmd = ';'.join('rm -f ' + control for control in delete_file_list)
255 host.run(cmd, ignore_status=True)
mbligh9a3f5e52008-05-28 21:21:43 +0000256
jadmanski0afbb632008-06-06 21:10:57 +0000257 tmppath = utils.get(control_file)
mblighc5ddfd12008-08-04 17:15:00 +0000258
jadmanskicb0e1612009-02-27 18:03:10 +0000259 # build up the initialization prologue for the control file
260 prologue_lines = []
261 prologue_lines.append("job.default_boot_tag(%r)\n"
262 % host.job.last_boot_tag)
263 prologue_lines.append("job.default_test_cleanup(%r)\n"
264 % host.job.run_test_cleanup)
jadmanski23afbec2008-09-17 18:12:07 +0000265
mbligh09108442008-10-15 16:27:38 +0000266 # If the packaging system is being used, add the repository list.
mblighc5ddfd12008-08-04 17:15:00 +0000267 try:
mblighc5ddfd12008-08-04 17:15:00 +0000268 c = global_config.global_config
269 repos = c.get_config_value("PACKAGES", 'fetch_location', type=list)
mbligh76d19f72008-10-15 16:24:43 +0000270 pkgmgr = packages.PackageManager('autotest', hostname=host.hostname,
271 repo_urls=repos)
jadmanskie2eef7b2009-03-03 23:55:13 +0000272 prologue_lines.append('job.add_repository(%s)\n'
273 % pkgmgr.repo_urls)
mblighc5ddfd12008-08-04 17:15:00 +0000274 except global_config.ConfigError, e:
275 pass
276
jadmanskie2eef7b2009-03-03 23:55:13 +0000277 # on full-size installs, turn on any profilers the server is using
278 if not self.lightweight:
279 running_profilers = host.job.profilers.add_log.iteritems()
280 for profiler, (args, dargs) in running_profilers:
281 call_args = [repr(profiler)]
282 call_args += [repr(arg) for arg in args]
283 call_args += ["%s=%r" % item for item in dargs.iteritems()]
284 prologue_lines.append("job.profilers.add(%s)\n"
285 % ", ".join(call_args))
286 cfile = "".join(prologue_lines)
287
mbligh09108442008-10-15 16:27:38 +0000288 cfile += open(tmppath).read()
289 open(tmppath, "w").write(cfile)
mblighc5ddfd12008-08-04 17:15:00 +0000290
jadmanskic09fc152008-10-15 17:56:59 +0000291 # Create and copy state file to remote_control_file + '.state'
292 sysinfo_state = {"__sysinfo": host.job.sysinfo.serialize()}
293 state_file = self._create_state_file(host.job, sysinfo_state)
294 host.send_file(state_file, atrun.remote_control_file + '.state')
295 os.remove(state_file)
296
mblighc5ddfd12008-08-04 17:15:00 +0000297 # Copy control_file to remote_control_file on the host
jadmanski0afbb632008-06-06 21:10:57 +0000298 host.send_file(tmppath, atrun.remote_control_file)
299 if os.path.abspath(tmppath) != os.path.abspath(control_file):
300 os.remove(tmppath)
mbligh0e4613b2007-10-29 16:55:07 +0000301
jadmanski6bb32d72009-03-19 20:25:24 +0000302 atrun.execute_control(
jadmanski6dadd832009-02-05 23:39:27 +0000303 timeout=timeout,
304 client_disconnect_timeout=client_disconnect_timeout)
jadmanski23afbec2008-09-17 18:12:07 +0000305
306
jadmanskic09fc152008-10-15 17:56:59 +0000307 def _create_state_file(self, job, state_dict):
308 """ Create a state file from a dictionary. Returns the path of the
309 state file. """
310 fd, path = tempfile.mkstemp(dir=job.tmpdir)
311 state_file = os.fdopen(fd, "w")
312 pickle.dump(state_dict, state_file)
313 state_file.close()
314 return path
315
316
jadmanski0afbb632008-06-06 21:10:57 +0000317 def run_timed_test(self, test_name, results_dir='.', host=None,
jadmanskic98c4702009-01-05 15:50:06 +0000318 timeout=None, *args, **dargs):
jadmanski0afbb632008-06-06 21:10:57 +0000319 """
320 Assemble a tiny little control file to just run one test,
321 and run it as an autotest client-side test
322 """
323 if not host:
324 host = self.host
325 if not self.installed:
326 self.install(host)
327 opts = ["%s=%s" % (o[0], repr(o[1])) for o in dargs.items()]
328 cmd = ", ".join([repr(test_name)] + map(repr, args) + opts)
329 control = "job.run_test(%s)\n" % cmd
jadmanskic98c4702009-01-05 15:50:06 +0000330 self.run(control, results_dir, host, timeout=timeout)
mbligh0e4613b2007-10-29 16:55:07 +0000331
332
jadmanskic98c4702009-01-05 15:50:06 +0000333 def run_test(self, test_name, results_dir='.', host=None, *args, **dargs):
jadmanski0afbb632008-06-06 21:10:57 +0000334 self.run_timed_test(test_name, results_dir, host, timeout=None,
jadmanskic98c4702009-01-05 15:50:06 +0000335 *args, **dargs)
mblighd54832b2007-07-25 16:46:56 +0000336
337
mblighdcd57a82007-07-11 23:06:47 +0000338class _Run(object):
jadmanski0afbb632008-06-06 21:10:57 +0000339 """
340 Represents a run of autotest control file. This class maintains
341 all the state necessary as an autotest control file is executed.
mblighdcd57a82007-07-11 23:06:47 +0000342
jadmanski0afbb632008-06-06 21:10:57 +0000343 It is not intended to be used directly, rather control files
344 should be run using the run method in Autotest.
345 """
mblighb3c0c912008-11-27 00:32:45 +0000346 def __init__(self, host, results_dir, tag, parallel_flag, background):
jadmanski0afbb632008-06-06 21:10:57 +0000347 self.host = host
348 self.results_dir = results_dir
349 self.env = host.env
350 self.tag = tag
351 self.parallel_flag = parallel_flag
mblighb3c0c912008-11-27 00:32:45 +0000352 self.background = background
jadmanski0afbb632008-06-06 21:10:57 +0000353 self.autodir = _get_autodir(self.host)
mbligh78bf5352008-07-11 20:27:36 +0000354 control = os.path.join(self.autodir, 'control')
jadmanski0afbb632008-06-06 21:10:57 +0000355 if tag:
mbligh78bf5352008-07-11 20:27:36 +0000356 control += '.' + tag
357 self.manual_control_file = control
358 self.remote_control_file = control + '.autoserv'
mblighdc735a22007-08-02 16:54:37 +0000359
360
jadmanski0afbb632008-06-06 21:10:57 +0000361 def verify_machine(self):
362 binary = os.path.join(self.autodir, 'bin/autotest')
363 try:
364 self.host.run('ls %s > /dev/null 2>&1' % binary)
365 except:
366 raise "Autotest does not appear to be installed"
mblighdc735a22007-08-02 16:54:37 +0000367
jadmanski0afbb632008-06-06 21:10:57 +0000368 if not self.parallel_flag:
369 tmpdir = os.path.join(self.autodir, 'tmp')
370 download = os.path.join(self.autodir, 'tests/download')
371 self.host.run('umount %s' % tmpdir, ignore_status=True)
372 self.host.run('umount %s' % download, ignore_status=True)
mblighdc735a22007-08-02 16:54:37 +0000373
jadmanski6dadd832009-02-05 23:39:27 +0000374
375 def get_base_cmd_args(self, section):
376 args = []
jadmanski0afbb632008-06-06 21:10:57 +0000377 if section > 0:
jadmanski6dadd832009-02-05 23:39:27 +0000378 args.append('-c')
jadmanski0afbb632008-06-06 21:10:57 +0000379 if self.tag:
jadmanski6dadd832009-02-05 23:39:27 +0000380 args.append('-t %s' % self.tag)
jadmanski0afbb632008-06-06 21:10:57 +0000381 if self.host.job.use_external_logging():
jadmanski6dadd832009-02-05 23:39:27 +0000382 args.append('-l')
383 args.append(self.remote_control_file)
384 return args
385
386
387 def get_background_cmd(self, section):
388 cmd = ['nohup', os.path.join(self.autodir, 'bin/autotest_client')]
389 cmd += self.get_base_cmd_args(section)
390 cmd.append('>/dev/null 2>/dev/null &')
391 return ' '.join(cmd)
392
393
394 def get_daemon_cmd(self, section, monitor_dir):
395 cmd = ['nohup', os.path.join(self.autodir, 'bin/autotestd'),
396 monitor_dir, '-H autoserv']
397 cmd += self.get_base_cmd_args(section)
398 cmd.append('>/dev/null 2>/dev/null </dev/null &')
399 return ' '.join(cmd)
400
401
402 def get_monitor_cmd(self, monitor_dir, stdout_read, stderr_read):
403 cmd = [os.path.join(self.autodir, 'bin', 'autotestd_monitor'),
404 monitor_dir, str(stdout_read), str(stderr_read)]
jadmanski0afbb632008-06-06 21:10:57 +0000405 return ' '.join(cmd)
mblighadf2aab2007-11-29 18:16:43 +0000406
mblighd8b39252008-03-20 21:15:03 +0000407
jadmanski0afbb632008-06-06 21:10:57 +0000408 def get_client_log(self, section):
jadmanskie0c7fb62008-12-16 20:51:16 +0000409 """ Find what the "next" client.log.* file should be and open it. """
410 debug_dir = os.path.join(self.results_dir, "debug")
411 client_logs = glob.glob(os.path.join(debug_dir, "client.log.*"))
412 next_log = os.path.join(debug_dir, "client.log.%d" % len(client_logs))
413 return open(next_log, "w", 0)
mblighd8b39252008-03-20 21:15:03 +0000414
415
jadmanskib264ed02009-01-12 23:54:27 +0000416 @staticmethod
417 def is_client_job_finished(last_line):
418 return bool(re.match(r'^END .*\t----\t----\t.*$', last_line))
419
420
421 @staticmethod
422 def is_client_job_rebooting(last_line):
423 return bool(re.match(r'^\t*GOOD\t----\treboot\.start.*$', last_line))
424
425
426 def log_unexpected_abort(self):
427 msg = "Autotest client terminated unexpectedly"
428 self.host.job.record("END ABORT", None, None, msg)
429
430
jadmanski6dadd832009-02-05 23:39:27 +0000431 def _execute_in_background(self, section, timeout):
432 full_cmd = self.get_background_cmd(section)
433 devnull = open(os.devnull, "w")
mblighd8b39252008-03-20 21:15:03 +0000434
jadmanski6dadd832009-02-05 23:39:27 +0000435 old_resultdir = self.host.job.resultdir
jadmanski0afbb632008-06-06 21:10:57 +0000436 try:
jadmanski0afbb632008-06-06 21:10:57 +0000437 self.host.job.resultdir = self.results_dir
438 result = self.host.run(full_cmd, ignore_status=True,
439 timeout=timeout,
jadmanski6dadd832009-02-05 23:39:27 +0000440 stdout_tee=devnull,
441 stderr_tee=devnull)
jadmanski0afbb632008-06-06 21:10:57 +0000442 finally:
jadmanski0afbb632008-06-06 21:10:57 +0000443 self.host.job.resultdir = old_resultdir
jadmanski6dadd832009-02-05 23:39:27 +0000444
445 return result
446
447
448 @staticmethod
449 def _strip_stderr_prologue(stderr):
450 """Strips the 'standard' prologue that get pre-pended to every
451 remote command and returns the text that was actually written to
452 stderr by the remote command."""
453 stderr_lines = stderr.split("\n")[1:]
454 if not stderr_lines:
455 return ""
456 elif stderr_lines[0].startswith("NOTE: autotestd_monitor"):
457 del stderr_lines[0]
458 return "\n".join(stderr_lines)
459
460
461 def _execute_daemon(self, section, timeout, stderr_redirector,
462 client_disconnect_timeout):
463 monitor_dir = self.host.get_tmp_dir()
464 daemon_cmd = self.get_daemon_cmd(section, monitor_dir)
465 client_log = self.get_client_log(section)
466
467 stdout_read = stderr_read = 0
468 old_resultdir = self.host.job.resultdir
469 try:
jadmanski29a4c702009-03-03 23:30:59 +0000470 self.host.job.resultdir = self.results_dir
jadmanski6dadd832009-02-05 23:39:27 +0000471 self.host.run(daemon_cmd, ignore_status=True, timeout=timeout)
472 while True:
473 monitor_cmd = self.get_monitor_cmd(monitor_dir, stdout_read,
474 stderr_read)
475 try:
476 result = self.host.run(monitor_cmd, ignore_status=True,
477 timeout=timeout,
478 stdout_tee=client_log,
479 stderr_tee=stderr_redirector)
480 except error.AutoservRunError, e:
481 result = e.result_obj
482 result.exit_status = None
483 stderr_redirector.log_warning(
484 "Autotest client was disconnected: %s" % e.description)
485 except error.AutoservSSHTimeout:
486 result = utils.CmdResult(monitor_cmd, "", "", None, 0)
487 stderr_redirector.log_warning(
488 "Attempt to connect to Autotest client timed out")
489
490 stdout_read += len(result.stdout)
491 stderr_read += len(self._strip_stderr_prologue(result.stderr))
492
493 if result.exit_status is not None:
494 return result
495 elif not self.host.wait_up(client_disconnect_timeout):
496 raise error.AutoservSSHTimeout(
497 "client was disconnected, reconnect timed out")
498 finally:
499 self.host.job.resultdir = old_resultdir
500
501
502 def execute_section(self, section, timeout, stderr_redirector,
503 client_disconnect_timeout):
showardb18134f2009-03-20 20:52:18 +0000504 logging.info("Executing %s/bin/autotest %s/control phase %d",
505 self.autodir, self.autodir, section)
jadmanski6dadd832009-02-05 23:39:27 +0000506
507 if self.background:
508 result = self._execute_in_background(section, timeout)
509 else:
510 result = self._execute_daemon(section, timeout, stderr_redirector,
511 client_disconnect_timeout)
512
513 last_line = stderr_redirector.last_line
mbligh2bf2db62007-11-27 00:53:18 +0000514
jadmanskib264ed02009-01-12 23:54:27 +0000515 # check if we failed hard enough to warrant an exception
jadmanski0afbb632008-06-06 21:10:57 +0000516 if result.exit_status == 1:
jadmanskib264ed02009-01-12 23:54:27 +0000517 err = error.AutotestRunError("client job was aborted")
518 elif not self.background and not result.stderr:
519 err = error.AutotestRunError(
jadmanskie4130532009-03-17 18:01:28 +0000520 "execute_section %s failed to return anything\n"
521 "stdout:%s\n" % (section, result.stdout))
jadmanskib264ed02009-01-12 23:54:27 +0000522 else:
523 err = None
mbligh0e4613b2007-10-29 16:55:07 +0000524
jadmanskib264ed02009-01-12 23:54:27 +0000525 # log something if the client failed AND never finished logging
526 if err and not self.is_client_job_finished(last_line):
527 self.log_unexpected_abort()
528
529 if err:
530 raise err
531 else:
532 return stderr_redirector.last_line
jadmanski4600e342008-10-29 22:54:00 +0000533
534
535 def _wait_for_reboot(self):
showardb18134f2009-03-20 20:52:18 +0000536 logging.info("Client is rebooting")
537 logging.info("Waiting for client to halt")
jadmanski4600e342008-10-29 22:54:00 +0000538 if not self.host.wait_down(HALT_TIME):
539 err = "%s failed to shutdown after %d"
540 err %= (self.host.hostname, HALT_TIME)
541 raise error.AutotestRunError(err)
showardb18134f2009-03-20 20:52:18 +0000542 logging.info("Client down, waiting for restart")
jadmanski4600e342008-10-29 22:54:00 +0000543 if not self.host.wait_up(BOOT_TIME):
544 # since reboot failed
545 # hardreset the machine once if possible
546 # before failing this control file
547 warning = "%s did not come back up, hard resetting"
548 warning %= self.host.hostname
showardb18134f2009-03-20 20:52:18 +0000549 logging.warning(warning)
jadmanski4600e342008-10-29 22:54:00 +0000550 try:
551 self.host.hardreset(wait=False)
mblighd99d3b272008-12-22 14:41:27 +0000552 except (AttributeError, error.AutoservUnsupportedError):
jadmanski4600e342008-10-29 22:54:00 +0000553 warning = "Hard reset unsupported on %s"
554 warning %= self.host.hostname
showardb18134f2009-03-20 20:52:18 +0000555 logging.warning(warning)
jadmanski4600e342008-10-29 22:54:00 +0000556 raise error.AutotestRunError("%s failed to boot after %ds" %
557 (self.host.hostname, BOOT_TIME))
558 self.host.reboot_followup()
mblighdc735a22007-08-02 16:54:37 +0000559
560
jadmanski6bb32d72009-03-19 20:25:24 +0000561 def _process_client_state_file(self):
562 state_file = os.path.basename(self.remote_control_file) + ".state"
563 state_path = os.path.join(self.results_dir, state_file)
564 try:
565 state_dict = pickle.load(open(state_path))
566 except Exception, e:
567 msg = "Ignoring error while loading client job state file: %s" % e
568 self.logger.warning(msg)
569 state_dict = {}
570
571 # clear out the state file
572 # TODO: stash the file away somewhere useful instead
573 try:
574 os.remove(state_path)
575 except Exception:
576 pass
577
578 msg = "Persistent state variables pulled back from %s: %s"
579 msg %= (self.host.hostname, state_dict)
580 print msg
581
582 if "__run_test_cleanup" in state_dict:
583 if state_dict["__run_test_cleanup"]:
584 self.host.job.enable_test_cleanup()
585 else:
586 self.host.job.disable_test_cleanup()
587
588 if "__last_boot_tag" in state_dict:
589 self.host.job.last_boot_tag = state_dict["__last_boot_tag"]
590
591 if "__sysinfo" in state_dict:
592 self.host.job.sysinfo.deserialize(state_dict["__sysinfo"])
593
594
jadmanski6dadd832009-02-05 23:39:27 +0000595 def execute_control(self, timeout=None, client_disconnect_timeout=None):
jadmanski6bb32d72009-03-19 20:25:24 +0000596 if not self.background:
597 collector = log_collector(self.host, self.tag, self.results_dir)
598 hostname = self.host.hostname
599 remote_results = collector.client_results_dir
600 local_results = collector.server_results_dir
601 self.host.job.add_client_log(hostname, remote_results,
602 local_results)
603
jadmanski0afbb632008-06-06 21:10:57 +0000604 section = 0
jadmanski4600e342008-10-29 22:54:00 +0000605 start_time = time.time()
606
jadmanski043e1132008-11-19 17:10:32 +0000607 logger = client_logger(self.host, self.tag, self.results_dir)
jadmanski4600e342008-10-29 22:54:00 +0000608 try:
609 while not timeout or time.time() < start_time + timeout:
610 if timeout:
611 section_timeout = start_time + timeout - time.time()
612 else:
613 section_timeout = None
614 last = self.execute_section(section, section_timeout,
jadmanski6dadd832009-02-05 23:39:27 +0000615 logger, client_disconnect_timeout)
mblighb3c0c912008-11-27 00:32:45 +0000616 if self.background:
617 return
jadmanski4600e342008-10-29 22:54:00 +0000618 section += 1
jadmanskib264ed02009-01-12 23:54:27 +0000619 if self.is_client_job_finished(last):
showardb18134f2009-03-20 20:52:18 +0000620 logging.info("Client complete")
jadmanski4600e342008-10-29 22:54:00 +0000621 return
jadmanskib264ed02009-01-12 23:54:27 +0000622 elif self.is_client_job_rebooting(last):
jadmanski79ab9282008-11-11 17:53:12 +0000623 try:
624 self._wait_for_reboot()
625 except error.AutotestRunError, e:
jadmanski043e1132008-11-19 17:10:32 +0000626 self.host.job.record("ABORT", None, "reboot", str(e))
627 self.host.job.record("END ABORT", None, None, str(e))
jadmanski79ab9282008-11-11 17:53:12 +0000628 raise
jadmanski4600e342008-10-29 22:54:00 +0000629 continue
630
631 # if we reach here, something unexpected happened
jadmanskib264ed02009-01-12 23:54:27 +0000632 self.log_unexpected_abort()
jadmanski4600e342008-10-29 22:54:00 +0000633
634 # give the client machine a chance to recover from a crash
635 self.host.wait_up(CRASH_RECOVERY_TIME)
636 msg = ("Aborting - unexpected final status message from "
637 "client: %s\n") % last
638 raise error.AutotestRunError(msg)
639 finally:
jadmanski043e1132008-11-19 17:10:32 +0000640 logger.close()
jadmanski6bb32d72009-03-19 20:25:24 +0000641 if not self.background:
642 collector.collect_client_job_results()
643 self._process_client_state_file()
644 self.host.job.remove_client_log(hostname, remote_results,
645 local_results)
mblighdcd57a82007-07-11 23:06:47 +0000646
jadmanski0afbb632008-06-06 21:10:57 +0000647 # should only get here if we timed out
648 assert timeout
649 raise error.AutotestTimeoutError()
mbligh0e4613b2007-10-29 16:55:07 +0000650
mblighdcd57a82007-07-11 23:06:47 +0000651
652def _get_autodir(host):
mbligh3c7a1502008-07-24 18:08:47 +0000653 autodir = host.get_autodir()
654 if autodir:
655 return autodir
jadmanski0afbb632008-06-06 21:10:57 +0000656 try:
657 # There's no clean way to do this. readlink may not exist
658 cmd = "python -c 'import os,sys; print os.readlink(sys.argv[1])' /etc/autotest.conf 2> /dev/null"
mbligh3c7a1502008-07-24 18:08:47 +0000659 autodir = os.path.dirname(host.run(cmd).stdout)
660 if autodir:
661 return autodir
jadmanski0afbb632008-06-06 21:10:57 +0000662 except error.AutoservRunError:
663 pass
664 for path in ['/usr/local/autotest', '/home/autotest']:
665 try:
jadmanski169ecad2008-09-12 15:49:44 +0000666 host.run('ls %s > /dev/null 2>&1' %
667 os.path.join(path, 'bin/autotest'))
jadmanski0afbb632008-06-06 21:10:57 +0000668 return path
669 except error.AutoservRunError:
670 pass
671 raise error.AutotestRunError("Cannot figure out autotest directory")
mblighd8b39252008-03-20 21:15:03 +0000672
673
jadmanski043e1132008-11-19 17:10:32 +0000674class log_collector(object):
675 def __init__(self, host, client_tag, results_dir):
676 self.host = host
677 if not client_tag:
678 client_tag = "default"
679 self.client_results_dir = os.path.join(host.get_autodir(), "results",
680 client_tag)
681 self.server_results_dir = results_dir
682
683
684 def collect_client_job_results(self):
685 """ A method that collects all the current results of a running
686 client job into the results dir. By default does nothing as no
687 client job is running, but when running a client job you can override
688 this with something that will actually do something. """
689
690 # make an effort to wait for the machine to come up
691 try:
692 self.host.wait_up(timeout=30)
693 except error.AutoservError:
694 # don't worry about any errors, we'll try and
695 # get the results anyway
696 pass
697
698
699 # Copy all dirs in default to results_dir
700 try:
701 keyval_path = self._prepare_for_copying_logs()
702 self.host.get_file(self.client_results_dir + '/',
703 self.server_results_dir)
704 self._process_copied_logs(keyval_path)
705 self._postprocess_copied_logs()
706 except Exception:
707 # well, don't stop running just because we couldn't get logs
showardb18134f2009-03-20 20:52:18 +0000708 e_msg = "Unexpected error copying test result logs, continuing ..."
709 logging.error(e_msg)
jadmanski043e1132008-11-19 17:10:32 +0000710 traceback.print_exc(file=sys.stdout)
711
712
713 def _prepare_for_copying_logs(self):
714 server_keyval = os.path.join(self.server_results_dir, 'keyval')
715 if not os.path.exists(server_keyval):
716 # Client-side keyval file can be copied directly
717 return
718
719 # Copy client-side keyval to temporary location
720 suffix = '.keyval_%s' % self.host.hostname
721 fd, keyval_path = tempfile.mkstemp(suffix)
722 os.close(fd)
723 try:
724 client_keyval = os.path.join(self.client_results_dir, 'keyval')
725 try:
726 self.host.get_file(client_keyval, keyval_path)
727 finally:
728 # We will squirrel away the client side keyval
729 # away and move it back when we are done
730 remote_temp_dir = self.host.get_tmp_dir()
731 self.temp_keyval_path = os.path.join(remote_temp_dir, "keyval")
732 self.host.run('mv %s %s' % (client_keyval,
733 self.temp_keyval_path))
734 except (error.AutoservRunError, error.AutoservSSHTimeout):
showardb18134f2009-03-20 20:52:18 +0000735 logging.error("Prepare for copying logs failed")
jadmanski043e1132008-11-19 17:10:32 +0000736 return keyval_path
737
738
739 def _process_copied_logs(self, keyval_path):
740 if not keyval_path:
741 # Client-side keyval file was copied directly
742 return
743
744 # Append contents of keyval_<host> file to keyval file
745 try:
746 # Read in new and old keyval files
747 new_keyval = utils.read_keyval(keyval_path)
748 old_keyval = utils.read_keyval(self.server_results_dir)
749 # 'Delete' from new keyval entries that are in both
750 tmp_keyval = {}
751 for key, val in new_keyval.iteritems():
752 if key not in old_keyval:
753 tmp_keyval[key] = val
754 # Append new info to keyval file
755 utils.write_keyval(self.server_results_dir, tmp_keyval)
756 # Delete keyval_<host> file
757 os.remove(keyval_path)
758 except IOError:
showardb18134f2009-03-20 20:52:18 +0000759 logging.error("Process copied logs failed")
jadmanski043e1132008-11-19 17:10:32 +0000760
761
762 def _postprocess_copied_logs(self):
763 # we can now put our keyval file back
764 client_keyval = os.path.join(self.client_results_dir, 'keyval')
765 try:
766 self.host.run('mv %s %s' % (self.temp_keyval_path, client_keyval))
767 except Exception:
768 pass
769
770
771
772# a file-like object for catching stderr from an autotest client and
773# extracting status logs from it
774class client_logger(object):
775 """Partial file object to write to both stdout and
776 the status log file. We only implement those methods
777 utils.run() actually calls.
778
779 Note that this class is fairly closely coupled with server_job, as it
780 uses special job._ methods to actually carry out the loggging.
781 """
782 status_parser = re.compile(r"^AUTOTEST_STATUS:([^:]*):(.*)$")
783 test_complete_parser = re.compile(r"^AUTOTEST_TEST_COMPLETE:(.*)$")
784 extract_indent = re.compile(r"^(\t*).*$")
785
786 def __init__(self, host, tag, server_results_dir):
787 self.host = host
788 self.job = host.job
789 self.log_collector = log_collector(host, tag, server_results_dir)
790 self.leftover = ""
791 self.last_line = ""
792 self.logs = {}
jadmanski6dadd832009-02-05 23:39:27 +0000793 self.server_warnings = []
jadmanski043e1132008-11-19 17:10:32 +0000794
795
796 def _process_log_dict(self, log_dict):
797 log_list = log_dict.pop("logs", [])
798 for key in sorted(log_dict.iterkeys()):
799 log_list += self._process_log_dict(log_dict.pop(key))
800 return log_list
801
802
803 def _process_logs(self):
804 """Go through the accumulated logs in self.log and print them
805 out to stdout and the status log. Note that this processes
806 logs in an ordering where:
807
808 1) logs to different tags are never interleaved
809 2) logs to x.y come before logs to x.y.z for all z
810 3) logs to x.y come before x.z whenever y < z
811
812 Note that this will in general not be the same as the
813 chronological ordering of the logs. However, if a chronological
814 ordering is desired that one can be reconstructed from the
815 status log by looking at timestamp lines."""
816 log_list = self._process_log_dict(self.logs)
817 for line in log_list:
818 self.job._record_prerendered(line + '\n')
819 if log_list:
820 self.last_line = log_list[-1]
821
822
823 def _process_quoted_line(self, tag, line):
824 """Process a line quoted with an AUTOTEST_STATUS flag. If the
825 tag is blank then we want to push out all the data we've been
826 building up in self.logs, and then the newest line. If the
827 tag is not blank, then push the line into the logs for handling
828 later."""
showardb18134f2009-03-20 20:52:18 +0000829 logging.info(line)
jadmanski043e1132008-11-19 17:10:32 +0000830 if tag == "":
831 self._process_logs()
832 self.job._record_prerendered(line + '\n')
833 self.last_line = line
834 else:
835 tag_parts = [int(x) for x in tag.split(".")]
836 log_dict = self.logs
837 for part in tag_parts:
838 log_dict = log_dict.setdefault(part, {})
839 log_list = log_dict.setdefault("logs", [])
840 log_list.append(line)
841
842
jadmanskif37df842009-02-11 00:03:26 +0000843 def _process_info_line(self, line):
844 """Check if line is an INFO line, and if it is, interpret any control
845 messages (e.g. enabling/disabling warnings) that it may contain."""
846 match = re.search(r"^\t*INFO\t----\t----(.*)\t[^\t]*$", line)
847 if not match:
848 return # not an INFO line
849 for field in match.group(1).split('\t'):
850 if field.startswith("warnings.enable="):
851 func = self.job.enable_warnings
852 elif field.startswith("warnings.disable="):
853 func = self.job.disable_warnings
854 else:
855 continue
856 warning_type = field.split("=", 1)[1]
857 func(warning_type, record=False)
858
859
jadmanski043e1132008-11-19 17:10:32 +0000860 def _process_line(self, line):
861 """Write out a line of data to the appropriate stream. Status
862 lines sent by autotest will be prepended with
863 "AUTOTEST_STATUS", and all other lines are ssh error
864 messages."""
865 status_match = self.status_parser.search(line)
866 test_complete_match = self.test_complete_parser.search(line)
867 if status_match:
868 tag, line = status_match.groups()
jadmanskif37df842009-02-11 00:03:26 +0000869 self._process_info_line(line)
jadmanski043e1132008-11-19 17:10:32 +0000870 self._process_quoted_line(tag, line)
871 elif test_complete_match:
jadmanskifcc0d5d2009-02-12 21:52:54 +0000872 self._process_logs()
jadmanski043e1132008-11-19 17:10:32 +0000873 fifo_path, = test_complete_match.groups()
874 self.log_collector.collect_client_job_results()
875 self.host.run("echo A > %s" % fifo_path)
876 else:
showardb18134f2009-03-20 20:52:18 +0000877 logging.info(line)
jadmanski043e1132008-11-19 17:10:32 +0000878
879
880 def _format_warnings(self, last_line, warnings):
881 # use the indentation of whatever the last log line was
882 indent = self.extract_indent.match(last_line).group(1)
883 # if the last line starts a new group, add an extra indent
884 if last_line.lstrip('\t').startswith("START\t"):
885 indent += '\t'
886 return [self.job._render_record("WARN", None, None, msg,
887 timestamp, indent).rstrip('\n')
888 for timestamp, msg in warnings]
889
890
891 def _process_warnings(self, last_line, log_dict, warnings):
892 if log_dict.keys() in ([], ["logs"]):
893 # there are no sub-jobs, just append the warnings here
894 warnings = self._format_warnings(last_line, warnings)
895 log_list = log_dict.setdefault("logs", [])
896 log_list += warnings
897 for warning in warnings:
898 sys.stdout.write(warning + '\n')
899 else:
900 # there are sub-jobs, so put the warnings in there
901 log_list = log_dict.get("logs", [])
902 if log_list:
903 last_line = log_list[-1]
904 for key in sorted(log_dict.iterkeys()):
905 if key != "logs":
906 self._process_warnings(last_line,
907 log_dict[key],
908 warnings)
909
jadmanskif37df842009-02-11 00:03:26 +0000910
jadmanski6dadd832009-02-05 23:39:27 +0000911 def log_warning(self, msg):
912 """Injects a WARN message into the current status logging stream."""
913 self.server_warnings.append((int(time.time()), msg))
914
jadmanski043e1132008-11-19 17:10:32 +0000915
916 def write(self, data):
917 # first check for any new console warnings
jadmanski6dadd832009-02-05 23:39:27 +0000918 warnings = self.job._read_warnings() + self.server_warnings
919 warnings.sort() # sort into timestamp order
920 self.server_warnings = []
jadmanski043e1132008-11-19 17:10:32 +0000921 self._process_warnings(self.last_line, self.logs, warnings)
922 # now process the newest data written out
923 data = self.leftover + data
924 lines = data.split("\n")
925 # process every line but the last one
926 for line in lines[:-1]:
927 self._process_line(line)
928 # save the last line for later processing
929 # since we may not have the whole line yet
930 self.leftover = lines[-1]
931
932
933 def flush(self):
934 sys.stdout.flush()
935
936
937 def close(self):
938 if self.leftover:
939 self._process_line(self.leftover)
940 self._process_logs()
941 self.flush()
942
943
mbligha7007722009-01-13 00:37:11 +0000944SiteAutotest = client_utils.import_site_class(
945 __file__, "autotest_lib.server.site_autotest", "SiteAutotest",
946 BaseAutotest)
mblighd8b39252008-03-20 21:15:03 +0000947
948class Autotest(SiteAutotest):
jadmanski0afbb632008-06-06 21:10:57 +0000949 pass