blob: 222d11735e9ce35e89ab15f839a537a4f5d8eb46 [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
mblighb5dac432008-11-27 00:38:44 +000067 drop_caches_between_iterations
68 drop the pagecache between each iteration
jadmanski10646442008-08-13 14:05:21 +000069 """
70
71 STATUS_VERSION = 1
72
73
74 def __init__(self, control, args, resultdir, label, user, machines,
75 client=False, parse_job='',
76 ssh_user='root', ssh_port=22, ssh_pass=''):
77 """
mblighb5dac432008-11-27 00:38:44 +000078 Server side job object.
79
80 Parameters:
81 control: The control file (pathname of)
82 args: args to pass to the control file
83 resultdir: where to throw the results
84 label: label for the job
85 user: Username for the job (email address)
86 client: True if a client-side control file
jadmanski10646442008-08-13 14:05:21 +000087 """
88 path = os.path.dirname(__file__)
89 self.autodir = os.path.abspath(os.path.join(path, '..'))
90 self.serverdir = os.path.join(self.autodir, 'server')
91 self.testdir = os.path.join(self.serverdir, 'tests')
92 self.site_testdir = os.path.join(self.serverdir, 'site_tests')
93 self.tmpdir = os.path.join(self.serverdir, 'tmp')
94 self.conmuxdir = os.path.join(self.autodir, 'conmux')
95 self.clientdir = os.path.join(self.autodir, 'client')
96 self.toolsdir = os.path.join(self.autodir, 'client/tools')
97 if control:
98 self.control = open(control, 'r').read()
99 self.control = re.sub('\r', '', self.control)
100 else:
showard45ae8192008-11-05 19:32:53 +0000101 self.control = ''
jadmanski10646442008-08-13 14:05:21 +0000102 self.resultdir = resultdir
mbligh80e1eba2008-11-19 00:26:18 +0000103 if resultdir:
104 if not os.path.exists(resultdir):
105 os.mkdir(resultdir)
106 self.debugdir = os.path.join(resultdir, 'debug')
107 if not os.path.exists(self.debugdir):
108 os.mkdir(self.debugdir)
109 self.status = os.path.join(resultdir, 'status')
110 else:
111 self.status = None
jadmanski10646442008-08-13 14:05:21 +0000112 self.label = label
113 self.user = user
114 self.args = args
115 self.machines = machines
116 self.client = client
117 self.record_prefix = ''
118 self.warning_loggers = set()
119 self.ssh_user = ssh_user
120 self.ssh_port = ssh_port
121 self.ssh_pass = ssh_pass
jadmanski23afbec2008-09-17 18:12:07 +0000122 self.run_test_cleanup = True
mbligh09108442008-10-15 16:27:38 +0000123 self.last_boot_tag = None
jadmanski53aaf382008-11-17 16:22:31 +0000124 self.hosts = set()
mblighb5dac432008-11-27 00:38:44 +0000125 self.drop_caches_between_iterations = False
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
mblighaebe3b62008-12-22 14:45:40 +0000361 temp_control_file_dir = None
jadmanski10646442008-08-13 14:05:21 +0000362 try:
363 if install_before and machines:
mbligh084bc172008-10-18 14:02:45 +0000364 self._execute_code(INSTALL_CONTROL_FILE, namespace)
mblighaebe3b62008-12-22 14:45:40 +0000365 if self.resultdir:
366 server_control_file = SERVER_CONTROL_FILENAME
367 client_control_file = CLIENT_CONTROL_FILENAME
368 else:
369 temp_control_file_dir = tempfile.mkdtmp()
370 server_control_file = os.path.join(temp_control_file_dir,
371 SERVER_CONTROL_FILENAME)
372 client_control_file = os.path.join(temp_control_file_dir,
373 CLIENT_CONTROL_FILENAME)
jadmanski10646442008-08-13 14:05:21 +0000374 if self.client:
375 namespace['control'] = self.control
mblighaebe3b62008-12-22 14:45:40 +0000376 utils.open_write_close(client_control_file, self.control)
377 shutil.copy(CLIENT_WRAPPER_CONTROL_FILE, server_control_file)
jadmanski10646442008-08-13 14:05:21 +0000378 else:
mbligh181b7c22008-11-22 14:22:08 +0000379 namespace['utils'] = utils
mblighaebe3b62008-12-22 14:45:40 +0000380 utils.open_write_close(server_control_file, self.control)
381 self._execute_code(server_control_file, namespace)
jadmanski10646442008-08-13 14:05:21 +0000382
jadmanskicdd0c402008-09-19 21:21:31 +0000383 # disable crashinfo collection if we get this far without error
384 collect_crashinfo = False
jadmanski10646442008-08-13 14:05:21 +0000385 finally:
mblighaebe3b62008-12-22 14:45:40 +0000386 if temp_control_file_dir:
387 # Clean up temp. directory used for copies of the control files.
388 try:
389 shutil.rmtree(temp_control_file_dir)
390 except Exception, e:
391 print 'Error', e, 'removing dir', temp_control_file_dir
jadmanskicdd0c402008-09-19 21:21:31 +0000392 if machines and (collect_crashdumps or collect_crashinfo):
jadmanski10646442008-08-13 14:05:21 +0000393 namespace['test_start_time'] = test_start_time
jadmanskicdd0c402008-09-19 21:21:31 +0000394 if collect_crashinfo:
mbligh084bc172008-10-18 14:02:45 +0000395 # includes crashdumps
396 self._execute_code(CRASHINFO_CONTROL_FILE, namespace)
jadmanskicdd0c402008-09-19 21:21:31 +0000397 else:
mbligh084bc172008-10-18 14:02:45 +0000398 self._execute_code(CRASHDUMPS_CONTROL_FILE, namespace)
jadmanski10646442008-08-13 14:05:21 +0000399 self.disable_external_logging()
showard45ae8192008-11-05 19:32:53 +0000400 if cleanup and machines:
401 self._execute_code(CLEANUP_CONTROL_FILE, namespace)
jadmanski10646442008-08-13 14:05:21 +0000402 if install_after and machines:
mbligh084bc172008-10-18 14:02:45 +0000403 self._execute_code(INSTALL_CONTROL_FILE, namespace)
jadmanski10646442008-08-13 14:05:21 +0000404
405
406 def run_test(self, url, *args, **dargs):
mbligh2b92b862008-11-22 13:25:32 +0000407 """
408 Summon a test object and run it.
jadmanski10646442008-08-13 14:05:21 +0000409
410 tag
411 tag to add to testname
412 url
413 url of the test to run
414 """
415
416 (group, testname) = self.pkgmgr.get_package_name(url, 'test')
jadmanski10646442008-08-13 14:05:21 +0000417
418 tag = dargs.pop('tag', None)
419 if tag:
jadmanskide292df2008-08-26 20:51:14 +0000420 testname += '.' + tag
421 subdir = testname
jadmanski10646442008-08-13 14:05:21 +0000422
423 outputdir = os.path.join(self.resultdir, subdir)
424 if os.path.exists(outputdir):
425 msg = ("%s already exists, test <%s> may have"
mbligh2b92b862008-11-22 13:25:32 +0000426 " already run with tag <%s>" % (outputdir, testname, tag))
jadmanski10646442008-08-13 14:05:21 +0000427 raise error.TestError(msg)
428 os.mkdir(outputdir)
429
430 def group_func():
431 try:
432 test.runtest(self, url, tag, args, dargs)
433 except error.TestBaseException, e:
434 self.record(e.exit_status, subdir, testname, str(e))
435 raise
436 except Exception, e:
437 info = str(e) + "\n" + traceback.format_exc()
438 self.record('FAIL', subdir, testname, info)
439 raise
440 else:
mbligh2b92b862008-11-22 13:25:32 +0000441 self.record('GOOD', subdir, testname, 'completed successfully')
jadmanskide292df2008-08-26 20:51:14 +0000442
443 result, exc_info = self._run_group(testname, subdir, group_func)
444 if exc_info and isinstance(exc_info[1], error.TestBaseException):
445 return False
446 elif exc_info:
447 raise exc_info[0], exc_info[1], exc_info[2]
448 else:
449 return True
jadmanski10646442008-08-13 14:05:21 +0000450
451
452 def _run_group(self, name, subdir, function, *args, **dargs):
453 """\
454 Underlying method for running something inside of a group.
455 """
jadmanskide292df2008-08-26 20:51:14 +0000456 result, exc_info = None, None
jadmanski10646442008-08-13 14:05:21 +0000457 old_record_prefix = self.record_prefix
458 try:
459 self.record('START', subdir, name)
460 self.record_prefix += '\t'
461 try:
462 result = function(*args, **dargs)
463 finally:
464 self.record_prefix = old_record_prefix
465 except error.TestBaseException, e:
466 self.record("END %s" % e.exit_status, subdir, name, str(e))
jadmanskide292df2008-08-26 20:51:14 +0000467 exc_info = sys.exc_info()
jadmanski10646442008-08-13 14:05:21 +0000468 except Exception, e:
469 err_msg = str(e) + '\n'
470 err_msg += traceback.format_exc()
471 self.record('END ABORT', subdir, name, err_msg)
472 raise error.JobError(name + ' failed\n' + traceback.format_exc())
473 else:
474 self.record('END GOOD', subdir, name)
475
jadmanskide292df2008-08-26 20:51:14 +0000476 return result, exc_info
jadmanski10646442008-08-13 14:05:21 +0000477
478
479 def run_group(self, function, *args, **dargs):
480 """\
481 function:
482 subroutine to run
483 *args:
484 arguments for the function
485 """
486
487 name = function.__name__
488
489 # Allow the tag for the group to be specified.
490 tag = dargs.pop('tag', None)
491 if tag:
492 name = tag
493
jadmanskide292df2008-08-26 20:51:14 +0000494 return self._run_group(name, None, function, *args, **dargs)[0]
jadmanski10646442008-08-13 14:05:21 +0000495
496
497 def run_reboot(self, reboot_func, get_kernel_func):
498 """\
499 A specialization of run_group meant specifically for handling
500 a reboot. Includes support for capturing the kernel version
501 after the reboot.
502
503 reboot_func: a function that carries out the reboot
504
505 get_kernel_func: a function that returns a string
506 representing the kernel version.
507 """
508
509 old_record_prefix = self.record_prefix
510 try:
511 self.record('START', None, 'reboot')
512 self.record_prefix += '\t'
513 reboot_func()
514 except Exception, e:
515 self.record_prefix = old_record_prefix
516 err_msg = str(e) + '\n' + traceback.format_exc()
517 self.record('END FAIL', None, 'reboot', err_msg)
518 else:
519 kernel = get_kernel_func()
520 self.record_prefix = old_record_prefix
521 self.record('END GOOD', None, 'reboot',
522 optional_fields={"kernel": kernel})
523
524
jadmanskic09fc152008-10-15 17:56:59 +0000525 def add_sysinfo_command(self, command, logfile=None, on_every_test=False):
526 self._add_sysinfo_loggable(sysinfo.command(command, logfile),
527 on_every_test)
528
529
530 def add_sysinfo_logfile(self, file, on_every_test=False):
531 self._add_sysinfo_loggable(sysinfo.logfile(file), on_every_test)
532
533
534 def _add_sysinfo_loggable(self, loggable, on_every_test):
535 if on_every_test:
536 self.sysinfo.test_loggables.add(loggable)
537 else:
538 self.sysinfo.boot_loggables.add(loggable)
539
540
jadmanski10646442008-08-13 14:05:21 +0000541 def record(self, status_code, subdir, operation, status='',
542 optional_fields=None):
543 """
544 Record job-level status
545
546 The intent is to make this file both machine parseable and
547 human readable. That involves a little more complexity, but
548 really isn't all that bad ;-)
549
550 Format is <status code>\t<subdir>\t<operation>\t<status>
551
mbligh1b3b3762008-09-25 02:46:34 +0000552 status code: see common_lib.log.is_valid_status()
jadmanski10646442008-08-13 14:05:21 +0000553 for valid status definition
554
555 subdir: MUST be a relevant subdirectory in the results,
556 or None, which will be represented as '----'
557
558 operation: description of what you ran (e.g. "dbench", or
559 "mkfs -t foobar /dev/sda9")
560
561 status: error message or "completed sucessfully"
562
563 ------------------------------------------------------------
564
565 Initial tabs indicate indent levels for grouping, and is
566 governed by self.record_prefix
567
568 multiline messages have secondary lines prefaced by a double
569 space (' ')
570
571 Executing this method will trigger the logging of all new
572 warnings to date from the various console loggers.
573 """
574 # poll all our warning loggers for new warnings
575 warnings = self._read_warnings()
576 for timestamp, msg in warnings:
577 self._record("WARN", None, None, msg, timestamp)
578
579 # write out the actual status log line
580 self._record(status_code, subdir, operation, status,
581 optional_fields=optional_fields)
582
583
584 def _read_warnings(self):
585 warnings = []
586 while True:
587 # pull in a line of output from every logger that has
588 # output ready to be read
mbligh2b92b862008-11-22 13:25:32 +0000589 loggers, _, _ = select.select(self.warning_loggers, [], [], 0)
jadmanski10646442008-08-13 14:05:21 +0000590 closed_loggers = set()
591 for logger in loggers:
592 line = logger.readline()
593 # record any broken pipes (aka line == empty)
594 if len(line) == 0:
595 closed_loggers.add(logger)
596 continue
597 timestamp, msg = line.split('\t', 1)
598 warnings.append((int(timestamp), msg.strip()))
599
600 # stop listening to loggers that are closed
601 self.warning_loggers -= closed_loggers
602
603 # stop if none of the loggers have any output left
604 if not loggers:
605 break
606
607 # sort into timestamp order
608 warnings.sort()
609 return warnings
610
611
612 def _render_record(self, status_code, subdir, operation, status='',
613 epoch_time=None, record_prefix=None,
614 optional_fields=None):
615 """
616 Internal Function to generate a record to be written into a
617 status log. For use by server_job.* classes only.
618 """
619 if subdir:
620 if re.match(r'[\n\t]', subdir):
mbligh2b92b862008-11-22 13:25:32 +0000621 raise ValueError('Invalid character in subdir string')
jadmanski10646442008-08-13 14:05:21 +0000622 substr = subdir
623 else:
624 substr = '----'
625
mbligh1b3b3762008-09-25 02:46:34 +0000626 if not log.is_valid_status(status_code):
mbligh2b92b862008-11-22 13:25:32 +0000627 raise ValueError('Invalid status code supplied: %s' % status_code)
jadmanski10646442008-08-13 14:05:21 +0000628 if not operation:
629 operation = '----'
630 if re.match(r'[\n\t]', operation):
mbligh2b92b862008-11-22 13:25:32 +0000631 raise ValueError('Invalid character in operation string')
jadmanski10646442008-08-13 14:05:21 +0000632 operation = operation.rstrip()
633 status = status.rstrip()
634 status = re.sub(r"\t", " ", status)
635 # Ensure any continuation lines are marked so we can
636 # detect them in the status file to ensure it is parsable.
637 status = re.sub(r"\n", "\n" + self.record_prefix + " ", status)
638
639 if not optional_fields:
640 optional_fields = {}
641
642 # Generate timestamps for inclusion in the logs
643 if epoch_time is None:
644 epoch_time = int(time.time())
645 local_time = time.localtime(epoch_time)
646 optional_fields["timestamp"] = str(epoch_time)
647 optional_fields["localtime"] = time.strftime("%b %d %H:%M:%S",
648 local_time)
649
650 fields = [status_code, substr, operation]
651 fields += ["%s=%s" % x for x in optional_fields.iteritems()]
652 fields.append(status)
653
654 if record_prefix is None:
655 record_prefix = self.record_prefix
656
657 msg = '\t'.join(str(x) for x in fields)
jadmanski10646442008-08-13 14:05:21 +0000658 return record_prefix + msg + '\n'
659
660
661 def _record_prerendered(self, msg):
662 """
663 Record a pre-rendered msg into the status logs. The only
664 change this makes to the message is to add on the local
665 indentation. Should not be called outside of server_job.*
666 classes. Unlike _record, this does not write the message
667 to standard output.
668 """
669 lines = []
670 status_file = os.path.join(self.resultdir, 'status.log')
671 status_log = open(status_file, 'a')
672 for line in msg.splitlines():
673 line = self.record_prefix + line + '\n'
674 lines.append(line)
675 status_log.write(line)
676 status_log.close()
677 self.__parse_status(lines)
678
679
mbligh084bc172008-10-18 14:02:45 +0000680 def _fill_server_control_namespace(self, namespace, protect=True):
mbligh2b92b862008-11-22 13:25:32 +0000681 """
682 Prepare a namespace to be used when executing server control files.
mbligh084bc172008-10-18 14:02:45 +0000683
684 This sets up the control file API by importing modules and making them
685 available under the appropriate names within namespace.
686
687 For use by _execute_code().
688
689 Args:
690 namespace: The namespace dictionary to fill in.
691 protect: Boolean. If True (the default) any operation that would
692 clobber an existing entry in namespace will cause an error.
693 Raises:
694 error.AutoservError: When a name would be clobbered by import.
695 """
696 def _import_names(module_name, names=()):
mbligh2b92b862008-11-22 13:25:32 +0000697 """
698 Import a module and assign named attributes into namespace.
mbligh084bc172008-10-18 14:02:45 +0000699
700 Args:
701 module_name: The string module name.
702 names: A limiting list of names to import from module_name. If
703 empty (the default), all names are imported from the module
704 similar to a "from foo.bar import *" statement.
705 Raises:
706 error.AutoservError: When a name being imported would clobber
707 a name already in namespace.
708 """
709 module = __import__(module_name, {}, {}, names)
710
711 # No names supplied? Import * from the lowest level module.
712 # (Ugh, why do I have to implement this part myself?)
713 if not names:
714 for submodule_name in module_name.split('.')[1:]:
715 module = getattr(module, submodule_name)
716 if hasattr(module, '__all__'):
717 names = getattr(module, '__all__')
718 else:
719 names = dir(module)
720
721 # Install each name into namespace, checking to make sure it
722 # doesn't override anything that already exists.
723 for name in names:
724 # Check for conflicts to help prevent future problems.
725 if name in namespace and protect:
726 if namespace[name] is not getattr(module, name):
727 raise error.AutoservError('importing name '
728 '%s from %s %r would override %r' %
729 (name, module_name, getattr(module, name),
730 namespace[name]))
731 else:
732 # Encourage cleanliness and the use of __all__ for a
733 # more concrete API with less surprises on '*' imports.
734 warnings.warn('%s (%r) being imported from %s for use '
735 'in server control files is not the '
736 'first occurrance of that import.' %
737 (name, namespace[name], module_name))
738
739 namespace[name] = getattr(module, name)
740
741
742 # This is the equivalent of prepending a bunch of import statements to
743 # the front of the control script.
744 namespace.update(os=os, sys=sys)
745 _import_names('autotest_lib.server',
746 ('hosts', 'autotest', 'kvm', 'git', 'standalone_profiler',
747 'source_kernel', 'rpm_kernel', 'deb_kernel', 'git_kernel'))
748 _import_names('autotest_lib.server.subcommand',
749 ('parallel', 'parallel_simple', 'subcommand'))
750 _import_names('autotest_lib.server.utils',
751 ('run', 'get_tmp_dir', 'sh_escape', 'parse_machine'))
752 _import_names('autotest_lib.client.common_lib.error')
753 _import_names('autotest_lib.client.common_lib.barrier', ('barrier',))
754
755 # Inject ourself as the job object into other classes within the API.
756 # (Yuck, this injection is a gross thing be part of a public API. -gps)
757 #
758 # XXX Base & SiteAutotest do not appear to use .job. Who does?
759 namespace['autotest'].Autotest.job = self
760 # server.hosts.base_classes.Host uses .job.
761 namespace['hosts'].Host.job = self
762
763
764 def _execute_code(self, code_file, namespace, protect=True):
mbligh2b92b862008-11-22 13:25:32 +0000765 """
766 Execute code using a copy of namespace as a server control script.
mbligh084bc172008-10-18 14:02:45 +0000767
768 Unless protect_namespace is explicitly set to False, the dict will not
769 be modified.
770
771 Args:
772 code_file: The filename of the control file to execute.
773 namespace: A dict containing names to make available during execution.
774 protect: Boolean. If True (the default) a copy of the namespace dict
775 is used during execution to prevent the code from modifying its
776 contents outside of this function. If False the raw dict is
777 passed in and modifications will be allowed.
778 """
779 if protect:
780 namespace = namespace.copy()
781 self._fill_server_control_namespace(namespace, protect=protect)
782 # TODO: Simplify and get rid of the special cases for only 1 machine.
showard3e66e8c2008-10-27 19:20:51 +0000783 if len(self.machines) > 1:
mbligh084bc172008-10-18 14:02:45 +0000784 machines_text = '\n'.join(self.machines) + '\n'
785 # Only rewrite the file if it does not match our machine list.
786 try:
787 machines_f = open(MACHINES_FILENAME, 'r')
788 existing_machines_text = machines_f.read()
789 machines_f.close()
790 except EnvironmentError:
791 existing_machines_text = None
792 if machines_text != existing_machines_text:
793 utils.open_write_close(MACHINES_FILENAME, machines_text)
794 execfile(code_file, namespace, namespace)
jadmanski10646442008-08-13 14:05:21 +0000795
796
797 def _record(self, status_code, subdir, operation, status='',
798 epoch_time=None, optional_fields=None):
799 """
800 Actual function for recording a single line into the status
801 logs. Should never be called directly, only by job.record as
802 this would bypass the console monitor logging.
803 """
804
mbligh2b92b862008-11-22 13:25:32 +0000805 msg = self._render_record(status_code, subdir, operation, status,
806 epoch_time, optional_fields=optional_fields)
jadmanski10646442008-08-13 14:05:21 +0000807
808
809 status_file = os.path.join(self.resultdir, 'status.log')
810 sys.stdout.write(msg)
811 open(status_file, "a").write(msg)
812 if subdir:
813 test_dir = os.path.join(self.resultdir, subdir)
jadmanski5ff55352008-09-18 19:43:46 +0000814 status_file = os.path.join(test_dir, 'status.log')
jadmanski10646442008-08-13 14:05:21 +0000815 open(status_file, "a").write(msg)
816 self.__parse_status(msg.splitlines())
817
818
819 def __parse_status(self, new_lines):
820 if not self.using_parser:
821 return
822 new_tests = self.parser.process_lines(new_lines)
823 for test in new_tests:
824 self.__insert_test(test)
825
826
827 def __insert_test(self, test):
mbligh2b92b862008-11-22 13:25:32 +0000828 """
829 An internal method to insert a new test result into the
jadmanski10646442008-08-13 14:05:21 +0000830 database. This method will not raise an exception, even if an
831 error occurs during the insert, to avoid failing a test
832 simply because of unexpected database issues."""
showard21baa452008-10-21 00:08:39 +0000833 self.num_tests_run += 1
834 if status_lib.is_worse_than_or_equal_to(test.status, 'FAIL'):
835 self.num_tests_failed += 1
jadmanski10646442008-08-13 14:05:21 +0000836 try:
837 self.results_db.insert_test(self.job_model, test)
838 except Exception:
839 msg = ("WARNING: An unexpected error occured while "
840 "inserting test results into the database. "
841 "Ignoring error.\n" + traceback.format_exc())
842 print >> sys.stderr, msg
843
mblighcaa62c22008-04-07 21:51:17 +0000844
845# site_server_job.py may be non-existant or empty, make sure that an
846# appropriate site_server_job class is created nevertheless
847try:
jadmanski0afbb632008-06-06 21:10:57 +0000848 from autotest_lib.server.site_server_job import site_server_job
mblighcaa62c22008-04-07 21:51:17 +0000849except ImportError:
jadmanski10646442008-08-13 14:05:21 +0000850 class site_server_job(object):
jadmanski0afbb632008-06-06 21:10:57 +0000851 pass
852
jadmanski10646442008-08-13 14:05:21 +0000853class server_job(site_server_job, base_server_job):
jadmanski0afbb632008-06-06 21:10:57 +0000854 pass