blob: df944a09a4d6b552f362a7fd514304056aa0a2e2 [file] [log] [blame]
mbligh57e78662008-06-17 19:53:49 +00001"""
2The main job wrapper for the server side.
3
4This is the core infrastructure. Derived from the client side job.py
5
6Copyright Martin J. Bligh, Andy Whitcroft 2007
7"""
8
jadmanski025099d2008-09-23 14:13:48 +00009import getpass, os, sys, re, stat, tempfile, time, select, subprocess, traceback
mbligh084bc172008-10-18 14:02:45 +000010import shutil, warnings
jadmanskic09fc152008-10-15 17:56:59 +000011from autotest_lib.client.bin import fd_stack, sysinfo
mbligh09108442008-10-15 16:27:38 +000012from autotest_lib.client.common_lib import error, log, utils, packages
jadmanski043e1132008-11-19 17:10:32 +000013from autotest_lib.server import test, subcommand, profilers
jadmanski10646442008-08-13 14:05:21 +000014from autotest_lib.tko import db as tko_db, status_lib, utils as tko_utils
jadmanski10646442008-08-13 14:05:21 +000015
16
mbligh084bc172008-10-18 14:02:45 +000017def _control_segment_path(name):
18 """Get the pathname of the named control segment file."""
jadmanski10646442008-08-13 14:05:21 +000019 server_dir = os.path.dirname(os.path.abspath(__file__))
mbligh084bc172008-10-18 14:02:45 +000020 return os.path.join(server_dir, "control_segments", name)
jadmanski10646442008-08-13 14:05:21 +000021
22
mbligh084bc172008-10-18 14:02:45 +000023CLIENT_CONTROL_FILENAME = 'control'
24SERVER_CONTROL_FILENAME = 'control.srv'
25MACHINES_FILENAME = '.machines'
jadmanski10646442008-08-13 14:05:21 +000026
mbligh084bc172008-10-18 14:02:45 +000027CLIENT_WRAPPER_CONTROL_FILE = _control_segment_path('client_wrapper')
28CRASHDUMPS_CONTROL_FILE = _control_segment_path('crashdumps')
29CRASHINFO_CONTROL_FILE = _control_segment_path('crashinfo')
mbligh084bc172008-10-18 14:02:45 +000030INSTALL_CONTROL_FILE = _control_segment_path('install')
showard45ae8192008-11-05 19:32:53 +000031CLEANUP_CONTROL_FILE = _control_segment_path('cleanup')
jadmanski10646442008-08-13 14:05:21 +000032
mbligh084bc172008-10-18 14:02:45 +000033VERIFY_CONTROL_FILE = _control_segment_path('verify')
mbligh084bc172008-10-18 14:02:45 +000034REPAIR_CONTROL_FILE = _control_segment_path('repair')
jadmanski10646442008-08-13 14:05:21 +000035
36
37# load up site-specific code for generating site-specific job data
38try:
39 import site_job
40 get_site_job_data = site_job.get_site_job_data
41 del site_job
42except ImportError:
43 # by default provide a stub that generates no site data
44 def get_site_job_data(job):
45 return {}
46
47
48class base_server_job(object):
mbligh2b92b862008-11-22 13:25:32 +000049 """
50 The actual job against which we do everything.
jadmanski10646442008-08-13 14:05:21 +000051
52 Properties:
53 autodir
54 The top level autotest directory (/usr/local/autotest).
55 serverdir
56 <autodir>/server/
57 clientdir
58 <autodir>/client/
59 conmuxdir
60 <autodir>/conmux/
61 testdir
62 <autodir>/server/tests/
63 site_testdir
64 <autodir>/server/site_tests/
65 control
66 the control file for this job
67 """
68
69 STATUS_VERSION = 1
70
71
72 def __init__(self, control, args, resultdir, label, user, machines,
73 client=False, parse_job='',
74 ssh_user='root', ssh_port=22, ssh_pass=''):
75 """
76 control
77 The control file (pathname of)
78 args
79 args to pass to the control file
80 resultdir
81 where to throw the results
82 label
83 label for the job
84 user
85 Username for the job (email address)
86 client
87 True if a client-side control file
88 """
89 path = os.path.dirname(__file__)
90 self.autodir = os.path.abspath(os.path.join(path, '..'))
91 self.serverdir = os.path.join(self.autodir, 'server')
92 self.testdir = os.path.join(self.serverdir, 'tests')
93 self.site_testdir = os.path.join(self.serverdir, 'site_tests')
94 self.tmpdir = os.path.join(self.serverdir, 'tmp')
95 self.conmuxdir = os.path.join(self.autodir, 'conmux')
96 self.clientdir = os.path.join(self.autodir, 'client')
97 self.toolsdir = os.path.join(self.autodir, 'client/tools')
98 if control:
99 self.control = open(control, 'r').read()
100 self.control = re.sub('\r', '', self.control)
101 else:
showard45ae8192008-11-05 19:32:53 +0000102 self.control = ''
jadmanski10646442008-08-13 14:05:21 +0000103 self.resultdir = resultdir
mbligh80e1eba2008-11-19 00:26:18 +0000104 if resultdir:
105 if not os.path.exists(resultdir):
106 os.mkdir(resultdir)
107 self.debugdir = os.path.join(resultdir, 'debug')
108 if not os.path.exists(self.debugdir):
109 os.mkdir(self.debugdir)
110 self.status = os.path.join(resultdir, 'status')
111 else:
112 self.status = None
jadmanski10646442008-08-13 14:05:21 +0000113 self.label = label
114 self.user = user
115 self.args = args
116 self.machines = machines
117 self.client = client
118 self.record_prefix = ''
119 self.warning_loggers = set()
120 self.ssh_user = ssh_user
121 self.ssh_port = ssh_port
122 self.ssh_pass = ssh_pass
jadmanski23afbec2008-09-17 18:12:07 +0000123 self.run_test_cleanup = True
mbligh09108442008-10-15 16:27:38 +0000124 self.last_boot_tag = None
jadmanski53aaf382008-11-17 16:22:31 +0000125 self.hosts = set()
jadmanski10646442008-08-13 14:05:21 +0000126
127 self.stdout = fd_stack.fd_stack(1, sys.stdout)
128 self.stderr = fd_stack.fd_stack(2, sys.stderr)
129
mbligh80e1eba2008-11-19 00:26:18 +0000130 if resultdir:
131 self.sysinfo = sysinfo.sysinfo(self.resultdir)
jadmanski043e1132008-11-19 17:10:32 +0000132 self.profilers = profilers.profilers(self)
jadmanskic09fc152008-10-15 17:56:59 +0000133
jadmanski025099d2008-09-23 14:13:48 +0000134 if not os.access(self.tmpdir, os.W_OK):
135 try:
136 os.makedirs(self.tmpdir, 0700)
137 except os.error, e:
138 # Thrown if the directory already exists, which it may.
139 pass
140
mbligh2b92b862008-11-22 13:25:32 +0000141 if not (os.access(self.tmpdir, os.W_OK) and os.path.isdir(self.tmpdir)):
jadmanski025099d2008-09-23 14:13:48 +0000142 self.tmpdir = os.path.join(tempfile.gettempdir(),
143 'autotest-' + getpass.getuser())
144 try:
145 os.makedirs(self.tmpdir, 0700)
146 except os.error, e:
147 # Thrown if the directory already exists, which it may.
148 # If the problem was something other than the
149 # directory already existing, this chmod should throw as well
150 # exception.
151 os.chmod(self.tmpdir, stat.S_IRWXU)
152
mbligh80e1eba2008-11-19 00:26:18 +0000153 if self.status and os.path.exists(self.status):
jadmanski10646442008-08-13 14:05:21 +0000154 os.unlink(self.status)
155 job_data = {'label' : label, 'user' : user,
156 'hostname' : ','.join(machines),
157 'status_version' : str(self.STATUS_VERSION)}
mbligh80e1eba2008-11-19 00:26:18 +0000158 if self.resultdir:
159 job_data.update(get_site_job_data(self))
160 utils.write_keyval(self.resultdir, job_data)
jadmanski10646442008-08-13 14:05:21 +0000161
162 self.parse_job = parse_job
163 if self.parse_job and len(machines) == 1:
164 self.using_parser = True
165 self.init_parser(resultdir)
166 else:
167 self.using_parser = False
mbligh2b92b862008-11-22 13:25:32 +0000168 self.pkgmgr = packages.PackageManager(self.autodir,
169 run_function_dargs={'timeout':600})
jadmanski10646442008-08-13 14:05:21 +0000170 self.pkgdir = os.path.join(self.autodir, 'packages')
171
showard21baa452008-10-21 00:08:39 +0000172 self.num_tests_run = 0
173 self.num_tests_failed = 0
174
jadmanski550fdc22008-11-20 16:32:08 +0000175 self._register_subcommand_hooks()
176
177
178 def _register_subcommand_hooks(self):
mbligh2b92b862008-11-22 13:25:32 +0000179 """
180 Register some hooks into the subcommand modules that allow us
181 to properly clean up self.hosts created in forked subprocesses.
182 """
jadmanski550fdc22008-11-20 16:32:08 +0000183 def on_fork(cmd):
184 self._existing_hosts_on_fork = set(self.hosts)
185 def on_join(cmd):
186 new_hosts = self.hosts - self._existing_hosts_on_fork
187 for host in new_hosts:
188 host.close()
189 subcommand.subcommand.register_fork_hook(on_fork)
190 subcommand.subcommand.register_join_hook(on_join)
191
jadmanski10646442008-08-13 14:05:21 +0000192
193 def init_parser(self, resultdir):
mbligh2b92b862008-11-22 13:25:32 +0000194 """
195 Start the continuous parsing of resultdir. This sets up
jadmanski10646442008-08-13 14:05:21 +0000196 the database connection and inserts the basic job object into
mbligh2b92b862008-11-22 13:25:32 +0000197 the database if necessary.
198 """
jadmanski10646442008-08-13 14:05:21 +0000199 # redirect parser debugging to .parse.log
200 parse_log = os.path.join(resultdir, '.parse.log')
201 parse_log = open(parse_log, 'w', 0)
202 tko_utils.redirect_parser_debugging(parse_log)
203 # create a job model object and set up the db
204 self.results_db = tko_db.db(autocommit=True)
205 self.parser = status_lib.parser(self.STATUS_VERSION)
206 self.job_model = self.parser.make_job(resultdir)
207 self.parser.start(self.job_model)
208 # check if a job already exists in the db and insert it if
209 # it does not
210 job_idx = self.results_db.find_job(self.parse_job)
211 if job_idx is None:
mbligh2b92b862008-11-22 13:25:32 +0000212 self.results_db.insert_job(self.parse_job, self.job_model)
jadmanski10646442008-08-13 14:05:21 +0000213 else:
mbligh2b92b862008-11-22 13:25:32 +0000214 machine_idx = self.results_db.lookup_machine(self.job_model.machine)
jadmanski10646442008-08-13 14:05:21 +0000215 self.job_model.index = job_idx
216 self.job_model.machine_idx = machine_idx
217
218
219 def cleanup_parser(self):
mbligh2b92b862008-11-22 13:25:32 +0000220 """
221 This should be called after the server job is finished
jadmanski10646442008-08-13 14:05:21 +0000222 to carry out any remaining cleanup (e.g. flushing any
mbligh2b92b862008-11-22 13:25:32 +0000223 remaining test results to the results db)
224 """
jadmanski10646442008-08-13 14:05:21 +0000225 if not self.using_parser:
226 return
227 final_tests = self.parser.end()
228 for test in final_tests:
229 self.__insert_test(test)
230 self.using_parser = False
231
232
233 def verify(self):
234 if not self.machines:
mbligh084bc172008-10-18 14:02:45 +0000235 raise error.AutoservError('No machines specified to verify')
mbligh0fce4112008-11-27 00:37:17 +0000236 if self.resultdir:
237 os.chdir(self.resultdir)
jadmanski10646442008-08-13 14:05:21 +0000238 try:
jadmanskicdd0c402008-09-19 21:21:31 +0000239 namespace = {'machines' : self.machines, 'job' : self,
240 'ssh_user' : self.ssh_user,
241 'ssh_port' : self.ssh_port,
242 'ssh_pass' : self.ssh_pass}
mbligh084bc172008-10-18 14:02:45 +0000243 self._execute_code(VERIFY_CONTROL_FILE, namespace, protect=False)
jadmanski10646442008-08-13 14:05:21 +0000244 except Exception, e:
mbligh2b92b862008-11-22 13:25:32 +0000245 msg = ('Verify failed\n' + str(e) + '\n' + traceback.format_exc())
jadmanski10646442008-08-13 14:05:21 +0000246 self.record('ABORT', None, None, msg)
247 raise
248
249
250 def repair(self, host_protection):
251 if not self.machines:
252 raise error.AutoservError('No machines specified to repair')
mbligh0fce4112008-11-27 00:37:17 +0000253 if self.resultdir:
254 os.chdir(self.resultdir)
jadmanski10646442008-08-13 14:05:21 +0000255 namespace = {'machines': self.machines, 'job': self,
256 'ssh_user': self.ssh_user, 'ssh_port': self.ssh_port,
257 'ssh_pass': self.ssh_pass,
258 'protection_level': host_protection}
259 # no matter what happens during repair, go on to try to reverify
260 try:
mbligh2b92b862008-11-22 13:25:32 +0000261 self._execute_code(REPAIR_CONTROL_FILE, namespace, protect=False)
jadmanski10646442008-08-13 14:05:21 +0000262 except Exception, exc:
263 print 'Exception occured during repair'
264 traceback.print_exc()
265 self.verify()
266
267
268 def precheck(self):
269 """
270 perform any additional checks in derived classes.
271 """
272 pass
273
274
275 def enable_external_logging(self):
mbligh2b92b862008-11-22 13:25:32 +0000276 """
277 Start or restart external logging mechanism.
jadmanski10646442008-08-13 14:05:21 +0000278 """
279 pass
280
281
282 def disable_external_logging(self):
mbligh2b92b862008-11-22 13:25:32 +0000283 """
284 Pause or stop external logging mechanism.
jadmanski10646442008-08-13 14:05:21 +0000285 """
286 pass
287
288
jadmanski23afbec2008-09-17 18:12:07 +0000289 def enable_test_cleanup(self):
mbligh2b92b862008-11-22 13:25:32 +0000290 """
291 By default tests run test.cleanup
292 """
jadmanski23afbec2008-09-17 18:12:07 +0000293 self.run_test_cleanup = True
294
295
296 def disable_test_cleanup(self):
mbligh2b92b862008-11-22 13:25:32 +0000297 """
298 By default tests do not run test.cleanup
299 """
jadmanski23afbec2008-09-17 18:12:07 +0000300 self.run_test_cleanup = False
301
302
jadmanski10646442008-08-13 14:05:21 +0000303 def use_external_logging(self):
mbligh2b92b862008-11-22 13:25:32 +0000304 """
305 Return True if external logging should be used.
jadmanski10646442008-08-13 14:05:21 +0000306 """
307 return False
308
309
310 def parallel_simple(self, function, machines, log=True, timeout=None):
mbligh2b92b862008-11-22 13:25:32 +0000311 """
312 Run 'function' using parallel_simple, with an extra wrapper to handle
313 the necessary setup for continuous parsing, if possible. If continuous
314 parsing is already properly initialized then this should just work.
315 """
316 is_forking = not (len(machines) == 1 and self.machines == machines)
jadmanski4dd1a002008-09-05 20:27:30 +0000317 if self.parse_job and is_forking and log:
jadmanski10646442008-08-13 14:05:21 +0000318 def wrapper(machine):
319 self.parse_job += "/" + machine
320 self.using_parser = True
321 self.machines = [machine]
mbligh2b92b862008-11-22 13:25:32 +0000322 self.resultdir = os.path.join(self.resultdir, machine)
jadmanski609a5f42008-08-26 20:52:42 +0000323 os.chdir(self.resultdir)
showard2bab8f42008-11-12 18:15:22 +0000324 utils.write_keyval(self.resultdir, {"hostname": machine})
jadmanski10646442008-08-13 14:05:21 +0000325 self.init_parser(self.resultdir)
326 result = function(machine)
327 self.cleanup_parser()
328 return result
jadmanski4dd1a002008-09-05 20:27:30 +0000329 elif len(machines) > 1 and log:
jadmanski10646442008-08-13 14:05:21 +0000330 def wrapper(machine):
331 self.resultdir = os.path.join(self.resultdir, machine)
jadmanski609a5f42008-08-26 20:52:42 +0000332 os.chdir(self.resultdir)
jadmanski10646442008-08-13 14:05:21 +0000333 result = function(machine)
334 return result
335 else:
336 wrapper = function
337 subcommand.parallel_simple(wrapper, machines, log, timeout)
338
339
mbligh2b92b862008-11-22 13:25:32 +0000340 def run(self, cleanup=False, install_before=False, install_after=False,
341 collect_crashdumps=True, namespace={}):
jadmanski10646442008-08-13 14:05:21 +0000342 # use a copy so changes don't affect the original dictionary
343 namespace = namespace.copy()
344 machines = self.machines
345
346 self.aborted = False
347 namespace['machines'] = machines
348 namespace['args'] = self.args
349 namespace['job'] = self
350 namespace['ssh_user'] = self.ssh_user
351 namespace['ssh_port'] = self.ssh_port
352 namespace['ssh_pass'] = self.ssh_pass
353 test_start_time = int(time.time())
354
mbligh80e1eba2008-11-19 00:26:18 +0000355 if self.resultdir:
356 os.chdir(self.resultdir)
jadmanski10646442008-08-13 14:05:21 +0000357
mbligh80e1eba2008-11-19 00:26:18 +0000358 self.enable_external_logging()
359 status_log = os.path.join(self.resultdir, 'status.log')
jadmanskicdd0c402008-09-19 21:21:31 +0000360 collect_crashinfo = True
jadmanski10646442008-08-13 14:05:21 +0000361 try:
362 if install_before and machines:
mbligh084bc172008-10-18 14:02:45 +0000363 self._execute_code(INSTALL_CONTROL_FILE, namespace)
jadmanski10646442008-08-13 14:05:21 +0000364 if self.client:
365 namespace['control'] = self.control
mbligh084bc172008-10-18 14:02:45 +0000366 utils.open_write_close(CLIENT_CONTROL_FILENAME, self.control)
367 shutil.copy(CLIENT_WRAPPER_CONTROL_FILE,
368 SERVER_CONTROL_FILENAME)
jadmanski10646442008-08-13 14:05:21 +0000369 else:
mbligh181b7c22008-11-22 14:22:08 +0000370 namespace['utils'] = utils
mbligh084bc172008-10-18 14:02:45 +0000371 utils.open_write_close(SERVER_CONTROL_FILENAME, self.control)
372 self._execute_code(SERVER_CONTROL_FILENAME, namespace)
jadmanski10646442008-08-13 14:05:21 +0000373
jadmanskicdd0c402008-09-19 21:21:31 +0000374 # disable crashinfo collection if we get this far without error
375 collect_crashinfo = False
jadmanski10646442008-08-13 14:05:21 +0000376 finally:
jadmanskicdd0c402008-09-19 21:21:31 +0000377 if machines and (collect_crashdumps or collect_crashinfo):
jadmanski10646442008-08-13 14:05:21 +0000378 namespace['test_start_time'] = test_start_time
jadmanskicdd0c402008-09-19 21:21:31 +0000379 if collect_crashinfo:
mbligh084bc172008-10-18 14:02:45 +0000380 # includes crashdumps
381 self._execute_code(CRASHINFO_CONTROL_FILE, namespace)
jadmanskicdd0c402008-09-19 21:21:31 +0000382 else:
mbligh084bc172008-10-18 14:02:45 +0000383 self._execute_code(CRASHDUMPS_CONTROL_FILE, namespace)
jadmanski10646442008-08-13 14:05:21 +0000384 self.disable_external_logging()
showard45ae8192008-11-05 19:32:53 +0000385 if cleanup and machines:
386 self._execute_code(CLEANUP_CONTROL_FILE, namespace)
jadmanski10646442008-08-13 14:05:21 +0000387 if install_after and machines:
mbligh084bc172008-10-18 14:02:45 +0000388 self._execute_code(INSTALL_CONTROL_FILE, namespace)
jadmanski10646442008-08-13 14:05:21 +0000389
390
391 def run_test(self, url, *args, **dargs):
mbligh2b92b862008-11-22 13:25:32 +0000392 """
393 Summon a test object and run it.
jadmanski10646442008-08-13 14:05:21 +0000394
395 tag
396 tag to add to testname
397 url
398 url of the test to run
399 """
400
401 (group, testname) = self.pkgmgr.get_package_name(url, 'test')
jadmanski10646442008-08-13 14:05:21 +0000402
403 tag = dargs.pop('tag', None)
404 if tag:
jadmanskide292df2008-08-26 20:51:14 +0000405 testname += '.' + tag
406 subdir = testname
jadmanski10646442008-08-13 14:05:21 +0000407
408 outputdir = os.path.join(self.resultdir, subdir)
409 if os.path.exists(outputdir):
410 msg = ("%s already exists, test <%s> may have"
mbligh2b92b862008-11-22 13:25:32 +0000411 " already run with tag <%s>" % (outputdir, testname, tag))
jadmanski10646442008-08-13 14:05:21 +0000412 raise error.TestError(msg)
413 os.mkdir(outputdir)
414
415 def group_func():
416 try:
417 test.runtest(self, url, tag, args, dargs)
418 except error.TestBaseException, e:
419 self.record(e.exit_status, subdir, testname, str(e))
420 raise
421 except Exception, e:
422 info = str(e) + "\n" + traceback.format_exc()
423 self.record('FAIL', subdir, testname, info)
424 raise
425 else:
mbligh2b92b862008-11-22 13:25:32 +0000426 self.record('GOOD', subdir, testname, 'completed successfully')
jadmanskide292df2008-08-26 20:51:14 +0000427
428 result, exc_info = self._run_group(testname, subdir, group_func)
429 if exc_info and isinstance(exc_info[1], error.TestBaseException):
430 return False
431 elif exc_info:
432 raise exc_info[0], exc_info[1], exc_info[2]
433 else:
434 return True
jadmanski10646442008-08-13 14:05:21 +0000435
436
437 def _run_group(self, name, subdir, function, *args, **dargs):
438 """\
439 Underlying method for running something inside of a group.
440 """
jadmanskide292df2008-08-26 20:51:14 +0000441 result, exc_info = None, None
jadmanski10646442008-08-13 14:05:21 +0000442 old_record_prefix = self.record_prefix
443 try:
444 self.record('START', subdir, name)
445 self.record_prefix += '\t'
446 try:
447 result = function(*args, **dargs)
448 finally:
449 self.record_prefix = old_record_prefix
450 except error.TestBaseException, e:
451 self.record("END %s" % e.exit_status, subdir, name, str(e))
jadmanskide292df2008-08-26 20:51:14 +0000452 exc_info = sys.exc_info()
jadmanski10646442008-08-13 14:05:21 +0000453 except Exception, e:
454 err_msg = str(e) + '\n'
455 err_msg += traceback.format_exc()
456 self.record('END ABORT', subdir, name, err_msg)
457 raise error.JobError(name + ' failed\n' + traceback.format_exc())
458 else:
459 self.record('END GOOD', subdir, name)
460
jadmanskide292df2008-08-26 20:51:14 +0000461 return result, exc_info
jadmanski10646442008-08-13 14:05:21 +0000462
463
464 def run_group(self, function, *args, **dargs):
465 """\
466 function:
467 subroutine to run
468 *args:
469 arguments for the function
470 """
471
472 name = function.__name__
473
474 # Allow the tag for the group to be specified.
475 tag = dargs.pop('tag', None)
476 if tag:
477 name = tag
478
jadmanskide292df2008-08-26 20:51:14 +0000479 return self._run_group(name, None, function, *args, **dargs)[0]
jadmanski10646442008-08-13 14:05:21 +0000480
481
482 def run_reboot(self, reboot_func, get_kernel_func):
483 """\
484 A specialization of run_group meant specifically for handling
485 a reboot. Includes support for capturing the kernel version
486 after the reboot.
487
488 reboot_func: a function that carries out the reboot
489
490 get_kernel_func: a function that returns a string
491 representing the kernel version.
492 """
493
494 old_record_prefix = self.record_prefix
495 try:
496 self.record('START', None, 'reboot')
497 self.record_prefix += '\t'
498 reboot_func()
499 except Exception, e:
500 self.record_prefix = old_record_prefix
501 err_msg = str(e) + '\n' + traceback.format_exc()
502 self.record('END FAIL', None, 'reboot', err_msg)
503 else:
504 kernel = get_kernel_func()
505 self.record_prefix = old_record_prefix
506 self.record('END GOOD', None, 'reboot',
507 optional_fields={"kernel": kernel})
508
509
jadmanskic09fc152008-10-15 17:56:59 +0000510 def add_sysinfo_command(self, command, logfile=None, on_every_test=False):
511 self._add_sysinfo_loggable(sysinfo.command(command, logfile),
512 on_every_test)
513
514
515 def add_sysinfo_logfile(self, file, on_every_test=False):
516 self._add_sysinfo_loggable(sysinfo.logfile(file), on_every_test)
517
518
519 def _add_sysinfo_loggable(self, loggable, on_every_test):
520 if on_every_test:
521 self.sysinfo.test_loggables.add(loggable)
522 else:
523 self.sysinfo.boot_loggables.add(loggable)
524
525
jadmanski10646442008-08-13 14:05:21 +0000526 def record(self, status_code, subdir, operation, status='',
527 optional_fields=None):
528 """
529 Record job-level status
530
531 The intent is to make this file both machine parseable and
532 human readable. That involves a little more complexity, but
533 really isn't all that bad ;-)
534
535 Format is <status code>\t<subdir>\t<operation>\t<status>
536
mbligh1b3b3762008-09-25 02:46:34 +0000537 status code: see common_lib.log.is_valid_status()
jadmanski10646442008-08-13 14:05:21 +0000538 for valid status definition
539
540 subdir: MUST be a relevant subdirectory in the results,
541 or None, which will be represented as '----'
542
543 operation: description of what you ran (e.g. "dbench", or
544 "mkfs -t foobar /dev/sda9")
545
546 status: error message or "completed sucessfully"
547
548 ------------------------------------------------------------
549
550 Initial tabs indicate indent levels for grouping, and is
551 governed by self.record_prefix
552
553 multiline messages have secondary lines prefaced by a double
554 space (' ')
555
556 Executing this method will trigger the logging of all new
557 warnings to date from the various console loggers.
558 """
559 # poll all our warning loggers for new warnings
560 warnings = self._read_warnings()
561 for timestamp, msg in warnings:
562 self._record("WARN", None, None, msg, timestamp)
563
564 # write out the actual status log line
565 self._record(status_code, subdir, operation, status,
566 optional_fields=optional_fields)
567
568
569 def _read_warnings(self):
570 warnings = []
571 while True:
572 # pull in a line of output from every logger that has
573 # output ready to be read
mbligh2b92b862008-11-22 13:25:32 +0000574 loggers, _, _ = select.select(self.warning_loggers, [], [], 0)
jadmanski10646442008-08-13 14:05:21 +0000575 closed_loggers = set()
576 for logger in loggers:
577 line = logger.readline()
578 # record any broken pipes (aka line == empty)
579 if len(line) == 0:
580 closed_loggers.add(logger)
581 continue
582 timestamp, msg = line.split('\t', 1)
583 warnings.append((int(timestamp), msg.strip()))
584
585 # stop listening to loggers that are closed
586 self.warning_loggers -= closed_loggers
587
588 # stop if none of the loggers have any output left
589 if not loggers:
590 break
591
592 # sort into timestamp order
593 warnings.sort()
594 return warnings
595
596
597 def _render_record(self, status_code, subdir, operation, status='',
598 epoch_time=None, record_prefix=None,
599 optional_fields=None):
600 """
601 Internal Function to generate a record to be written into a
602 status log. For use by server_job.* classes only.
603 """
604 if subdir:
605 if re.match(r'[\n\t]', subdir):
mbligh2b92b862008-11-22 13:25:32 +0000606 raise ValueError('Invalid character in subdir string')
jadmanski10646442008-08-13 14:05:21 +0000607 substr = subdir
608 else:
609 substr = '----'
610
mbligh1b3b3762008-09-25 02:46:34 +0000611 if not log.is_valid_status(status_code):
mbligh2b92b862008-11-22 13:25:32 +0000612 raise ValueError('Invalid status code supplied: %s' % status_code)
jadmanski10646442008-08-13 14:05:21 +0000613 if not operation:
614 operation = '----'
615 if re.match(r'[\n\t]', operation):
mbligh2b92b862008-11-22 13:25:32 +0000616 raise ValueError('Invalid character in operation string')
jadmanski10646442008-08-13 14:05:21 +0000617 operation = operation.rstrip()
618 status = status.rstrip()
619 status = re.sub(r"\t", " ", status)
620 # Ensure any continuation lines are marked so we can
621 # detect them in the status file to ensure it is parsable.
622 status = re.sub(r"\n", "\n" + self.record_prefix + " ", status)
623
624 if not optional_fields:
625 optional_fields = {}
626
627 # Generate timestamps for inclusion in the logs
628 if epoch_time is None:
629 epoch_time = int(time.time())
630 local_time = time.localtime(epoch_time)
631 optional_fields["timestamp"] = str(epoch_time)
632 optional_fields["localtime"] = time.strftime("%b %d %H:%M:%S",
633 local_time)
634
635 fields = [status_code, substr, operation]
636 fields += ["%s=%s" % x for x in optional_fields.iteritems()]
637 fields.append(status)
638
639 if record_prefix is None:
640 record_prefix = self.record_prefix
641
642 msg = '\t'.join(str(x) for x in fields)
jadmanski10646442008-08-13 14:05:21 +0000643 return record_prefix + msg + '\n'
644
645
646 def _record_prerendered(self, msg):
647 """
648 Record a pre-rendered msg into the status logs. The only
649 change this makes to the message is to add on the local
650 indentation. Should not be called outside of server_job.*
651 classes. Unlike _record, this does not write the message
652 to standard output.
653 """
654 lines = []
655 status_file = os.path.join(self.resultdir, 'status.log')
656 status_log = open(status_file, 'a')
657 for line in msg.splitlines():
658 line = self.record_prefix + line + '\n'
659 lines.append(line)
660 status_log.write(line)
661 status_log.close()
662 self.__parse_status(lines)
663
664
mbligh084bc172008-10-18 14:02:45 +0000665 def _fill_server_control_namespace(self, namespace, protect=True):
mbligh2b92b862008-11-22 13:25:32 +0000666 """
667 Prepare a namespace to be used when executing server control files.
mbligh084bc172008-10-18 14:02:45 +0000668
669 This sets up the control file API by importing modules and making them
670 available under the appropriate names within namespace.
671
672 For use by _execute_code().
673
674 Args:
675 namespace: The namespace dictionary to fill in.
676 protect: Boolean. If True (the default) any operation that would
677 clobber an existing entry in namespace will cause an error.
678 Raises:
679 error.AutoservError: When a name would be clobbered by import.
680 """
681 def _import_names(module_name, names=()):
mbligh2b92b862008-11-22 13:25:32 +0000682 """
683 Import a module and assign named attributes into namespace.
mbligh084bc172008-10-18 14:02:45 +0000684
685 Args:
686 module_name: The string module name.
687 names: A limiting list of names to import from module_name. If
688 empty (the default), all names are imported from the module
689 similar to a "from foo.bar import *" statement.
690 Raises:
691 error.AutoservError: When a name being imported would clobber
692 a name already in namespace.
693 """
694 module = __import__(module_name, {}, {}, names)
695
696 # No names supplied? Import * from the lowest level module.
697 # (Ugh, why do I have to implement this part myself?)
698 if not names:
699 for submodule_name in module_name.split('.')[1:]:
700 module = getattr(module, submodule_name)
701 if hasattr(module, '__all__'):
702 names = getattr(module, '__all__')
703 else:
704 names = dir(module)
705
706 # Install each name into namespace, checking to make sure it
707 # doesn't override anything that already exists.
708 for name in names:
709 # Check for conflicts to help prevent future problems.
710 if name in namespace and protect:
711 if namespace[name] is not getattr(module, name):
712 raise error.AutoservError('importing name '
713 '%s from %s %r would override %r' %
714 (name, module_name, getattr(module, name),
715 namespace[name]))
716 else:
717 # Encourage cleanliness and the use of __all__ for a
718 # more concrete API with less surprises on '*' imports.
719 warnings.warn('%s (%r) being imported from %s for use '
720 'in server control files is not the '
721 'first occurrance of that import.' %
722 (name, namespace[name], module_name))
723
724 namespace[name] = getattr(module, name)
725
726
727 # This is the equivalent of prepending a bunch of import statements to
728 # the front of the control script.
729 namespace.update(os=os, sys=sys)
730 _import_names('autotest_lib.server',
731 ('hosts', 'autotest', 'kvm', 'git', 'standalone_profiler',
732 'source_kernel', 'rpm_kernel', 'deb_kernel', 'git_kernel'))
733 _import_names('autotest_lib.server.subcommand',
734 ('parallel', 'parallel_simple', 'subcommand'))
735 _import_names('autotest_lib.server.utils',
736 ('run', 'get_tmp_dir', 'sh_escape', 'parse_machine'))
737 _import_names('autotest_lib.client.common_lib.error')
738 _import_names('autotest_lib.client.common_lib.barrier', ('barrier',))
739
740 # Inject ourself as the job object into other classes within the API.
741 # (Yuck, this injection is a gross thing be part of a public API. -gps)
742 #
743 # XXX Base & SiteAutotest do not appear to use .job. Who does?
744 namespace['autotest'].Autotest.job = self
745 # server.hosts.base_classes.Host uses .job.
746 namespace['hosts'].Host.job = self
747
748
749 def _execute_code(self, code_file, namespace, protect=True):
mbligh2b92b862008-11-22 13:25:32 +0000750 """
751 Execute code using a copy of namespace as a server control script.
mbligh084bc172008-10-18 14:02:45 +0000752
753 Unless protect_namespace is explicitly set to False, the dict will not
754 be modified.
755
756 Args:
757 code_file: The filename of the control file to execute.
758 namespace: A dict containing names to make available during execution.
759 protect: Boolean. If True (the default) a copy of the namespace dict
760 is used during execution to prevent the code from modifying its
761 contents outside of this function. If False the raw dict is
762 passed in and modifications will be allowed.
763 """
764 if protect:
765 namespace = namespace.copy()
766 self._fill_server_control_namespace(namespace, protect=protect)
767 # TODO: Simplify and get rid of the special cases for only 1 machine.
showard3e66e8c2008-10-27 19:20:51 +0000768 if len(self.machines) > 1:
mbligh084bc172008-10-18 14:02:45 +0000769 machines_text = '\n'.join(self.machines) + '\n'
770 # Only rewrite the file if it does not match our machine list.
771 try:
772 machines_f = open(MACHINES_FILENAME, 'r')
773 existing_machines_text = machines_f.read()
774 machines_f.close()
775 except EnvironmentError:
776 existing_machines_text = None
777 if machines_text != existing_machines_text:
778 utils.open_write_close(MACHINES_FILENAME, machines_text)
779 execfile(code_file, namespace, namespace)
jadmanski10646442008-08-13 14:05:21 +0000780
781
782 def _record(self, status_code, subdir, operation, status='',
783 epoch_time=None, optional_fields=None):
784 """
785 Actual function for recording a single line into the status
786 logs. Should never be called directly, only by job.record as
787 this would bypass the console monitor logging.
788 """
789
mbligh2b92b862008-11-22 13:25:32 +0000790 msg = self._render_record(status_code, subdir, operation, status,
791 epoch_time, optional_fields=optional_fields)
jadmanski10646442008-08-13 14:05:21 +0000792
793
794 status_file = os.path.join(self.resultdir, 'status.log')
795 sys.stdout.write(msg)
796 open(status_file, "a").write(msg)
797 if subdir:
798 test_dir = os.path.join(self.resultdir, subdir)
jadmanski5ff55352008-09-18 19:43:46 +0000799 status_file = os.path.join(test_dir, 'status.log')
jadmanski10646442008-08-13 14:05:21 +0000800 open(status_file, "a").write(msg)
801 self.__parse_status(msg.splitlines())
802
803
804 def __parse_status(self, new_lines):
805 if not self.using_parser:
806 return
807 new_tests = self.parser.process_lines(new_lines)
808 for test in new_tests:
809 self.__insert_test(test)
810
811
812 def __insert_test(self, test):
mbligh2b92b862008-11-22 13:25:32 +0000813 """
814 An internal method to insert a new test result into the
jadmanski10646442008-08-13 14:05:21 +0000815 database. This method will not raise an exception, even if an
816 error occurs during the insert, to avoid failing a test
817 simply because of unexpected database issues."""
showard21baa452008-10-21 00:08:39 +0000818 self.num_tests_run += 1
819 if status_lib.is_worse_than_or_equal_to(test.status, 'FAIL'):
820 self.num_tests_failed += 1
jadmanski10646442008-08-13 14:05:21 +0000821 try:
822 self.results_db.insert_test(self.job_model, test)
823 except Exception:
824 msg = ("WARNING: An unexpected error occured while "
825 "inserting test results into the database. "
826 "Ignoring error.\n" + traceback.format_exc())
827 print >> sys.stderr, msg
828
mblighcaa62c22008-04-07 21:51:17 +0000829
830# site_server_job.py may be non-existant or empty, make sure that an
831# appropriate site_server_job class is created nevertheless
832try:
jadmanski0afbb632008-06-06 21:10:57 +0000833 from autotest_lib.server.site_server_job import site_server_job
mblighcaa62c22008-04-07 21:51:17 +0000834except ImportError:
jadmanski10646442008-08-13 14:05:21 +0000835 class site_server_job(object):
jadmanski0afbb632008-06-06 21:10:57 +0000836 pass
837
jadmanski10646442008-08-13 14:05:21 +0000838class server_job(site_server_job, base_server_job):
jadmanski0afbb632008-06-06 21:10:57 +0000839 pass