blob: 374937ea295fd3f93585f14f01af1350d9ca5355 [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
mbligh062ed152009-01-13 00:57:14 +000037# by default provide a stub that generates no site data
38def _get_site_job_data_dummy(job):
39 return {}
40
41
jadmanski10646442008-08-13 14:05:21 +000042# load up site-specific code for generating site-specific job data
mbligh062ed152009-01-13 00:57:14 +000043get_site_job_data = utils.import_site_function(__file__,
jadmanskic0a623d2009-03-03 21:11:48 +000044 "autotest_lib.server.site_server_job", "get_site_job_data",
mbligh062ed152009-01-13 00:57:14 +000045 _get_site_job_data_dummy)
jadmanski10646442008-08-13 14:05:21 +000046
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:
jadmanskie432dd22009-01-30 15:04:51 +000098 self.control = self._load_control_file(control)
jadmanski10646442008-08-13 14:05:21 +000099 else:
showard45ae8192008-11-05 19:32:53 +0000100 self.control = ''
jadmanski10646442008-08-13 14:05:21 +0000101 self.resultdir = resultdir
mbligh80e1eba2008-11-19 00:26:18 +0000102 if resultdir:
103 if not os.path.exists(resultdir):
104 os.mkdir(resultdir)
105 self.debugdir = os.path.join(resultdir, 'debug')
106 if not os.path.exists(self.debugdir):
107 os.mkdir(self.debugdir)
108 self.status = os.path.join(resultdir, 'status')
109 else:
110 self.status = None
jadmanski10646442008-08-13 14:05:21 +0000111 self.label = label
112 self.user = user
113 self.args = args
114 self.machines = machines
115 self.client = client
116 self.record_prefix = ''
117 self.warning_loggers = set()
jadmanskif37df842009-02-11 00:03:26 +0000118 self.warning_manager = warning_manager()
jadmanski10646442008-08-13 14:05:21 +0000119 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),
showard170873e2009-01-07 00:22:26 +0000157 'status_version' : str(self.STATUS_VERSION),
158 'job_started' : str(int(time.time()))}
mbligh80e1eba2008-11-19 00:26:18 +0000159 if self.resultdir:
160 job_data.update(get_site_job_data(self))
161 utils.write_keyval(self.resultdir, job_data)
jadmanski10646442008-08-13 14:05:21 +0000162
163 self.parse_job = parse_job
164 if self.parse_job and len(machines) == 1:
165 self.using_parser = True
166 self.init_parser(resultdir)
167 else:
168 self.using_parser = False
mbligh2b92b862008-11-22 13:25:32 +0000169 self.pkgmgr = packages.PackageManager(self.autodir,
170 run_function_dargs={'timeout':600})
jadmanski10646442008-08-13 14:05:21 +0000171 self.pkgdir = os.path.join(self.autodir, 'packages')
172
showard21baa452008-10-21 00:08:39 +0000173 self.num_tests_run = 0
174 self.num_tests_failed = 0
175
jadmanski550fdc22008-11-20 16:32:08 +0000176 self._register_subcommand_hooks()
177
178
jadmanskie432dd22009-01-30 15:04:51 +0000179 @staticmethod
180 def _load_control_file(path):
181 f = open(path)
182 try:
183 control_file = f.read()
184 finally:
185 f.close()
186 return re.sub('\r', '', control_file)
187
188
jadmanski550fdc22008-11-20 16:32:08 +0000189 def _register_subcommand_hooks(self):
mbligh2b92b862008-11-22 13:25:32 +0000190 """
191 Register some hooks into the subcommand modules that allow us
192 to properly clean up self.hosts created in forked subprocesses.
193 """
jadmanski550fdc22008-11-20 16:32:08 +0000194 def on_fork(cmd):
195 self._existing_hosts_on_fork = set(self.hosts)
196 def on_join(cmd):
197 new_hosts = self.hosts - self._existing_hosts_on_fork
198 for host in new_hosts:
199 host.close()
200 subcommand.subcommand.register_fork_hook(on_fork)
201 subcommand.subcommand.register_join_hook(on_join)
202
jadmanski10646442008-08-13 14:05:21 +0000203
204 def init_parser(self, resultdir):
mbligh2b92b862008-11-22 13:25:32 +0000205 """
206 Start the continuous parsing of resultdir. This sets up
jadmanski10646442008-08-13 14:05:21 +0000207 the database connection and inserts the basic job object into
mbligh2b92b862008-11-22 13:25:32 +0000208 the database if necessary.
209 """
jadmanski10646442008-08-13 14:05:21 +0000210 # redirect parser debugging to .parse.log
211 parse_log = os.path.join(resultdir, '.parse.log')
212 parse_log = open(parse_log, 'w', 0)
213 tko_utils.redirect_parser_debugging(parse_log)
214 # create a job model object and set up the db
215 self.results_db = tko_db.db(autocommit=True)
216 self.parser = status_lib.parser(self.STATUS_VERSION)
217 self.job_model = self.parser.make_job(resultdir)
218 self.parser.start(self.job_model)
219 # check if a job already exists in the db and insert it if
220 # it does not
221 job_idx = self.results_db.find_job(self.parse_job)
222 if job_idx is None:
mbligh2b92b862008-11-22 13:25:32 +0000223 self.results_db.insert_job(self.parse_job, self.job_model)
jadmanski10646442008-08-13 14:05:21 +0000224 else:
mbligh2b92b862008-11-22 13:25:32 +0000225 machine_idx = self.results_db.lookup_machine(self.job_model.machine)
jadmanski10646442008-08-13 14:05:21 +0000226 self.job_model.index = job_idx
227 self.job_model.machine_idx = machine_idx
228
229
230 def cleanup_parser(self):
mbligh2b92b862008-11-22 13:25:32 +0000231 """
232 This should be called after the server job is finished
jadmanski10646442008-08-13 14:05:21 +0000233 to carry out any remaining cleanup (e.g. flushing any
mbligh2b92b862008-11-22 13:25:32 +0000234 remaining test results to the results db)
235 """
jadmanski10646442008-08-13 14:05:21 +0000236 if not self.using_parser:
237 return
238 final_tests = self.parser.end()
239 for test in final_tests:
240 self.__insert_test(test)
241 self.using_parser = False
242
243
244 def verify(self):
245 if not self.machines:
mbligh084bc172008-10-18 14:02:45 +0000246 raise error.AutoservError('No machines specified to verify')
mbligh0fce4112008-11-27 00:37:17 +0000247 if self.resultdir:
248 os.chdir(self.resultdir)
jadmanski10646442008-08-13 14:05:21 +0000249 try:
jadmanskicdd0c402008-09-19 21:21:31 +0000250 namespace = {'machines' : self.machines, 'job' : self,
251 'ssh_user' : self.ssh_user,
252 'ssh_port' : self.ssh_port,
253 'ssh_pass' : self.ssh_pass}
mbligh084bc172008-10-18 14:02:45 +0000254 self._execute_code(VERIFY_CONTROL_FILE, namespace, protect=False)
jadmanski10646442008-08-13 14:05:21 +0000255 except Exception, e:
mbligh2b92b862008-11-22 13:25:32 +0000256 msg = ('Verify failed\n' + str(e) + '\n' + traceback.format_exc())
jadmanski10646442008-08-13 14:05:21 +0000257 self.record('ABORT', None, None, msg)
258 raise
259
260
261 def repair(self, host_protection):
262 if not self.machines:
263 raise error.AutoservError('No machines specified to repair')
mbligh0fce4112008-11-27 00:37:17 +0000264 if self.resultdir:
265 os.chdir(self.resultdir)
jadmanski10646442008-08-13 14:05:21 +0000266 namespace = {'machines': self.machines, 'job': self,
267 'ssh_user': self.ssh_user, 'ssh_port': self.ssh_port,
268 'ssh_pass': self.ssh_pass,
269 'protection_level': host_protection}
mbligh25c0b8c2009-01-24 01:44:17 +0000270 # no matter what happens during repair (except if it succeeded in
271 # initiating hardware repair procedure), go on to try to reverify
jadmanski10646442008-08-13 14:05:21 +0000272 try:
mbligh2b92b862008-11-22 13:25:32 +0000273 self._execute_code(REPAIR_CONTROL_FILE, namespace, protect=False)
mbligh25c0b8c2009-01-24 01:44:17 +0000274 except error.AutoservHardwareRepairRequestedError:
275 raise
jadmanski10646442008-08-13 14:05:21 +0000276 except Exception, exc:
277 print 'Exception occured during repair'
278 traceback.print_exc()
mbligh25c0b8c2009-01-24 01:44:17 +0000279
jadmanski10646442008-08-13 14:05:21 +0000280 self.verify()
281
282
283 def precheck(self):
284 """
285 perform any additional checks in derived classes.
286 """
287 pass
288
289
290 def enable_external_logging(self):
mbligh2b92b862008-11-22 13:25:32 +0000291 """
292 Start or restart external logging mechanism.
jadmanski10646442008-08-13 14:05:21 +0000293 """
294 pass
295
296
297 def disable_external_logging(self):
mbligh2b92b862008-11-22 13:25:32 +0000298 """
299 Pause or stop external logging mechanism.
jadmanski10646442008-08-13 14:05:21 +0000300 """
301 pass
302
303
jadmanski23afbec2008-09-17 18:12:07 +0000304 def enable_test_cleanup(self):
mbligh2b92b862008-11-22 13:25:32 +0000305 """
306 By default tests run test.cleanup
307 """
jadmanski23afbec2008-09-17 18:12:07 +0000308 self.run_test_cleanup = True
309
310
311 def disable_test_cleanup(self):
mbligh2b92b862008-11-22 13:25:32 +0000312 """
313 By default tests do not run test.cleanup
314 """
jadmanski23afbec2008-09-17 18:12:07 +0000315 self.run_test_cleanup = False
316
317
jadmanski10646442008-08-13 14:05:21 +0000318 def use_external_logging(self):
mbligh2b92b862008-11-22 13:25:32 +0000319 """
320 Return True if external logging should be used.
jadmanski10646442008-08-13 14:05:21 +0000321 """
322 return False
323
324
325 def parallel_simple(self, function, machines, log=True, timeout=None):
mbligh2b92b862008-11-22 13:25:32 +0000326 """
327 Run 'function' using parallel_simple, with an extra wrapper to handle
328 the necessary setup for continuous parsing, if possible. If continuous
329 parsing is already properly initialized then this should just work.
330 """
331 is_forking = not (len(machines) == 1 and self.machines == machines)
jadmanski4dd1a002008-09-05 20:27:30 +0000332 if self.parse_job and is_forking and log:
jadmanski10646442008-08-13 14:05:21 +0000333 def wrapper(machine):
334 self.parse_job += "/" + machine
335 self.using_parser = True
336 self.machines = [machine]
mbligh2b92b862008-11-22 13:25:32 +0000337 self.resultdir = os.path.join(self.resultdir, machine)
jadmanski609a5f42008-08-26 20:52:42 +0000338 os.chdir(self.resultdir)
showard2bab8f42008-11-12 18:15:22 +0000339 utils.write_keyval(self.resultdir, {"hostname": machine})
jadmanski10646442008-08-13 14:05:21 +0000340 self.init_parser(self.resultdir)
341 result = function(machine)
342 self.cleanup_parser()
343 return result
jadmanski4dd1a002008-09-05 20:27:30 +0000344 elif len(machines) > 1 and log:
jadmanski10646442008-08-13 14:05:21 +0000345 def wrapper(machine):
346 self.resultdir = os.path.join(self.resultdir, machine)
jadmanski609a5f42008-08-26 20:52:42 +0000347 os.chdir(self.resultdir)
mbligh838d82d2009-03-11 17:14:31 +0000348 machine_data = {'hostname' : machine,
349 'status_version' : str(self.STATUS_VERSION)}
350 utils.write_keyval(self.resultdir, machine_data)
jadmanski10646442008-08-13 14:05:21 +0000351 result = function(machine)
352 return result
353 else:
354 wrapper = function
355 subcommand.parallel_simple(wrapper, machines, log, timeout)
356
357
jadmanskie432dd22009-01-30 15:04:51 +0000358 USE_TEMP_DIR = object()
mbligh2b92b862008-11-22 13:25:32 +0000359 def run(self, cleanup=False, install_before=False, install_after=False,
jadmanskie432dd22009-01-30 15:04:51 +0000360 collect_crashdumps=True, namespace={}, control=None,
361 control_file_dir=None):
jadmanski10646442008-08-13 14:05:21 +0000362 # use a copy so changes don't affect the original dictionary
363 namespace = namespace.copy()
364 machines = self.machines
jadmanskie432dd22009-01-30 15:04:51 +0000365 if control is None:
366 control = self.control
367 if control_file_dir is None:
368 control_file_dir = self.resultdir
jadmanski10646442008-08-13 14:05:21 +0000369
370 self.aborted = False
371 namespace['machines'] = machines
372 namespace['args'] = self.args
373 namespace['job'] = self
374 namespace['ssh_user'] = self.ssh_user
375 namespace['ssh_port'] = self.ssh_port
376 namespace['ssh_pass'] = self.ssh_pass
377 test_start_time = int(time.time())
378
mbligh80e1eba2008-11-19 00:26:18 +0000379 if self.resultdir:
380 os.chdir(self.resultdir)
mbligh80e1eba2008-11-19 00:26:18 +0000381 self.enable_external_logging()
jadmanskie432dd22009-01-30 15:04:51 +0000382
jadmanskicdd0c402008-09-19 21:21:31 +0000383 collect_crashinfo = True
mblighaebe3b62008-12-22 14:45:40 +0000384 temp_control_file_dir = None
jadmanski10646442008-08-13 14:05:21 +0000385 try:
386 if install_before and machines:
mbligh084bc172008-10-18 14:02:45 +0000387 self._execute_code(INSTALL_CONTROL_FILE, namespace)
jadmanskie432dd22009-01-30 15:04:51 +0000388
389 # determine the dir to write the control files to
390 if control_file_dir and control_file_dir is not self.USE_TEMP_DIR:
391 temp_control_file_dir = None
mblighaebe3b62008-12-22 14:45:40 +0000392 else:
jadmanskie432dd22009-01-30 15:04:51 +0000393 temp_control_file_dir = control_file_dir = tempfile.mkdtemp(
394 suffix='temp_control_file_dir')
395 server_control_file = os.path.join(control_file_dir,
396 SERVER_CONTROL_FILENAME)
397 client_control_file = os.path.join(control_file_dir,
398 CLIENT_CONTROL_FILENAME)
jadmanski10646442008-08-13 14:05:21 +0000399 if self.client:
jadmanskie432dd22009-01-30 15:04:51 +0000400 namespace['control'] = control
401 utils.open_write_close(client_control_file, control)
mblighaebe3b62008-12-22 14:45:40 +0000402 shutil.copy(CLIENT_WRAPPER_CONTROL_FILE, server_control_file)
jadmanski10646442008-08-13 14:05:21 +0000403 else:
mbligh181b7c22008-11-22 14:22:08 +0000404 namespace['utils'] = utils
jadmanskie432dd22009-01-30 15:04:51 +0000405 utils.open_write_close(server_control_file, control)
mblighaebe3b62008-12-22 14:45:40 +0000406 self._execute_code(server_control_file, namespace)
jadmanski10646442008-08-13 14:05:21 +0000407
jadmanskicdd0c402008-09-19 21:21:31 +0000408 # disable crashinfo collection if we get this far without error
409 collect_crashinfo = False
jadmanski10646442008-08-13 14:05:21 +0000410 finally:
mblighaebe3b62008-12-22 14:45:40 +0000411 if temp_control_file_dir:
jadmanskie432dd22009-01-30 15:04:51 +0000412 # Clean up temp directory used for copies of the control files
mblighaebe3b62008-12-22 14:45:40 +0000413 try:
414 shutil.rmtree(temp_control_file_dir)
415 except Exception, e:
jadmanskie432dd22009-01-30 15:04:51 +0000416 print 'Error %s removing dir %s' % (e,
417 temp_control_file_dir)
418
jadmanskicdd0c402008-09-19 21:21:31 +0000419 if machines and (collect_crashdumps or collect_crashinfo):
jadmanski10646442008-08-13 14:05:21 +0000420 namespace['test_start_time'] = test_start_time
jadmanskicdd0c402008-09-19 21:21:31 +0000421 if collect_crashinfo:
mbligh084bc172008-10-18 14:02:45 +0000422 # includes crashdumps
423 self._execute_code(CRASHINFO_CONTROL_FILE, namespace)
jadmanskicdd0c402008-09-19 21:21:31 +0000424 else:
mbligh084bc172008-10-18 14:02:45 +0000425 self._execute_code(CRASHDUMPS_CONTROL_FILE, namespace)
jadmanski10646442008-08-13 14:05:21 +0000426 self.disable_external_logging()
showard45ae8192008-11-05 19:32:53 +0000427 if cleanup and machines:
428 self._execute_code(CLEANUP_CONTROL_FILE, namespace)
jadmanski10646442008-08-13 14:05:21 +0000429 if install_after and machines:
mbligh084bc172008-10-18 14:02:45 +0000430 self._execute_code(INSTALL_CONTROL_FILE, namespace)
jadmanski10646442008-08-13 14:05:21 +0000431
432
433 def run_test(self, url, *args, **dargs):
mbligh2b92b862008-11-22 13:25:32 +0000434 """
435 Summon a test object and run it.
jadmanski10646442008-08-13 14:05:21 +0000436
437 tag
438 tag to add to testname
439 url
440 url of the test to run
441 """
442
443 (group, testname) = self.pkgmgr.get_package_name(url, 'test')
jadmanski10646442008-08-13 14:05:21 +0000444
445 tag = dargs.pop('tag', None)
446 if tag:
mbligh8ad24202009-01-07 16:49:36 +0000447 testname += '.' + str(tag)
jadmanskide292df2008-08-26 20:51:14 +0000448 subdir = testname
jadmanski10646442008-08-13 14:05:21 +0000449
450 outputdir = os.path.join(self.resultdir, subdir)
451 if os.path.exists(outputdir):
452 msg = ("%s already exists, test <%s> may have"
mbligh2b92b862008-11-22 13:25:32 +0000453 " already run with tag <%s>" % (outputdir, testname, tag))
jadmanski10646442008-08-13 14:05:21 +0000454 raise error.TestError(msg)
455 os.mkdir(outputdir)
456
457 def group_func():
458 try:
459 test.runtest(self, url, tag, args, dargs)
460 except error.TestBaseException, e:
461 self.record(e.exit_status, subdir, testname, str(e))
462 raise
463 except Exception, e:
464 info = str(e) + "\n" + traceback.format_exc()
465 self.record('FAIL', subdir, testname, info)
466 raise
467 else:
mbligh2b92b862008-11-22 13:25:32 +0000468 self.record('GOOD', subdir, testname, 'completed successfully')
jadmanskide292df2008-08-26 20:51:14 +0000469
470 result, exc_info = self._run_group(testname, subdir, group_func)
471 if exc_info and isinstance(exc_info[1], error.TestBaseException):
472 return False
473 elif exc_info:
474 raise exc_info[0], exc_info[1], exc_info[2]
475 else:
476 return True
jadmanski10646442008-08-13 14:05:21 +0000477
478
479 def _run_group(self, name, subdir, function, *args, **dargs):
480 """\
481 Underlying method for running something inside of a group.
482 """
jadmanskide292df2008-08-26 20:51:14 +0000483 result, exc_info = None, None
jadmanski10646442008-08-13 14:05:21 +0000484 old_record_prefix = self.record_prefix
485 try:
486 self.record('START', subdir, name)
487 self.record_prefix += '\t'
488 try:
489 result = function(*args, **dargs)
490 finally:
491 self.record_prefix = old_record_prefix
492 except error.TestBaseException, e:
jadmanskib88d6dc2009-01-10 00:33:18 +0000493 self.record("END %s" % e.exit_status, subdir, name)
jadmanskide292df2008-08-26 20:51:14 +0000494 exc_info = sys.exc_info()
jadmanski10646442008-08-13 14:05:21 +0000495 except Exception, e:
496 err_msg = str(e) + '\n'
497 err_msg += traceback.format_exc()
498 self.record('END ABORT', subdir, name, err_msg)
499 raise error.JobError(name + ' failed\n' + traceback.format_exc())
500 else:
501 self.record('END GOOD', subdir, name)
502
jadmanskide292df2008-08-26 20:51:14 +0000503 return result, exc_info
jadmanski10646442008-08-13 14:05:21 +0000504
505
506 def run_group(self, function, *args, **dargs):
507 """\
508 function:
509 subroutine to run
510 *args:
511 arguments for the function
512 """
513
514 name = function.__name__
515
516 # Allow the tag for the group to be specified.
517 tag = dargs.pop('tag', None)
518 if tag:
519 name = tag
520
jadmanskide292df2008-08-26 20:51:14 +0000521 return self._run_group(name, None, function, *args, **dargs)[0]
jadmanski10646442008-08-13 14:05:21 +0000522
523
524 def run_reboot(self, reboot_func, get_kernel_func):
525 """\
526 A specialization of run_group meant specifically for handling
527 a reboot. Includes support for capturing the kernel version
528 after the reboot.
529
530 reboot_func: a function that carries out the reboot
531
532 get_kernel_func: a function that returns a string
533 representing the kernel version.
534 """
535
536 old_record_prefix = self.record_prefix
537 try:
538 self.record('START', None, 'reboot')
539 self.record_prefix += '\t'
540 reboot_func()
541 except Exception, e:
542 self.record_prefix = old_record_prefix
543 err_msg = str(e) + '\n' + traceback.format_exc()
544 self.record('END FAIL', None, 'reboot', err_msg)
545 else:
546 kernel = get_kernel_func()
547 self.record_prefix = old_record_prefix
548 self.record('END GOOD', None, 'reboot',
549 optional_fields={"kernel": kernel})
550
551
jadmanskie432dd22009-01-30 15:04:51 +0000552 def run_control(self, path):
553 """Execute a control file found at path (relative to the autotest
554 path). Intended for executing a control file within a control file,
555 not for running the top-level job control file."""
556 path = os.path.join(self.autodir, path)
557 control_file = self._load_control_file(path)
558 self.run(control=control_file, control_file_dir=self.USE_TEMP_DIR)
559
560
jadmanskic09fc152008-10-15 17:56:59 +0000561 def add_sysinfo_command(self, command, logfile=None, on_every_test=False):
562 self._add_sysinfo_loggable(sysinfo.command(command, logfile),
563 on_every_test)
564
565
566 def add_sysinfo_logfile(self, file, on_every_test=False):
567 self._add_sysinfo_loggable(sysinfo.logfile(file), on_every_test)
568
569
570 def _add_sysinfo_loggable(self, loggable, on_every_test):
571 if on_every_test:
572 self.sysinfo.test_loggables.add(loggable)
573 else:
574 self.sysinfo.boot_loggables.add(loggable)
575
576
jadmanski10646442008-08-13 14:05:21 +0000577 def record(self, status_code, subdir, operation, status='',
578 optional_fields=None):
579 """
580 Record job-level status
581
582 The intent is to make this file both machine parseable and
583 human readable. That involves a little more complexity, but
584 really isn't all that bad ;-)
585
586 Format is <status code>\t<subdir>\t<operation>\t<status>
587
mbligh1b3b3762008-09-25 02:46:34 +0000588 status code: see common_lib.log.is_valid_status()
jadmanski10646442008-08-13 14:05:21 +0000589 for valid status definition
590
591 subdir: MUST be a relevant subdirectory in the results,
592 or None, which will be represented as '----'
593
594 operation: description of what you ran (e.g. "dbench", or
595 "mkfs -t foobar /dev/sda9")
596
597 status: error message or "completed sucessfully"
598
599 ------------------------------------------------------------
600
601 Initial tabs indicate indent levels for grouping, and is
602 governed by self.record_prefix
603
604 multiline messages have secondary lines prefaced by a double
605 space (' ')
606
607 Executing this method will trigger the logging of all new
608 warnings to date from the various console loggers.
609 """
610 # poll all our warning loggers for new warnings
611 warnings = self._read_warnings()
612 for timestamp, msg in warnings:
613 self._record("WARN", None, None, msg, timestamp)
614
615 # write out the actual status log line
616 self._record(status_code, subdir, operation, status,
617 optional_fields=optional_fields)
618
619
620 def _read_warnings(self):
jadmanskif37df842009-02-11 00:03:26 +0000621 """Poll all the warning loggers and extract any new warnings that have
622 been logged. If the warnings belong to a category that is currently
623 disabled, this method will discard them and they will no longer be
624 retrievable.
625
626 Returns a list of (timestamp, message) tuples, where timestamp is an
627 integer epoch timestamp."""
jadmanski10646442008-08-13 14:05:21 +0000628 warnings = []
629 while True:
630 # pull in a line of output from every logger that has
631 # output ready to be read
mbligh2b92b862008-11-22 13:25:32 +0000632 loggers, _, _ = select.select(self.warning_loggers, [], [], 0)
jadmanski10646442008-08-13 14:05:21 +0000633 closed_loggers = set()
634 for logger in loggers:
635 line = logger.readline()
636 # record any broken pipes (aka line == empty)
637 if len(line) == 0:
638 closed_loggers.add(logger)
639 continue
jadmanskif37df842009-02-11 00:03:26 +0000640 # parse out the warning
641 timestamp, msgtype, msg = line.split('\t', 2)
642 timestamp = int(timestamp)
643 # if the warning is valid, add it to the results
644 if self.warning_manager.is_valid(timestamp, msgtype):
645 warnings.append((timestamp, msg.strip()))
jadmanski10646442008-08-13 14:05:21 +0000646
647 # stop listening to loggers that are closed
648 self.warning_loggers -= closed_loggers
649
650 # stop if none of the loggers have any output left
651 if not loggers:
652 break
653
654 # sort into timestamp order
655 warnings.sort()
656 return warnings
657
658
jadmanskif37df842009-02-11 00:03:26 +0000659 def disable_warnings(self, warning_type, record=True):
660 self.warning_manager.disable_warnings(warning_type)
661 if record:
662 self.record("INFO", None, None,
663 "disabling %s warnings" % warning_type,
664 {"warnings.disable": warning_type})
665
666
667 def enable_warnings(self, warning_type, record=True):
668 self.warning_manager.enable_warnings(warning_type)
669 if record:
670 self.record("INFO", None, None,
671 "enabling %s warnings" % warning_type,
672 {"warnings.enable": warning_type})
673
674
jadmanski10646442008-08-13 14:05:21 +0000675 def _render_record(self, status_code, subdir, operation, status='',
676 epoch_time=None, record_prefix=None,
677 optional_fields=None):
678 """
679 Internal Function to generate a record to be written into a
680 status log. For use by server_job.* classes only.
681 """
682 if subdir:
683 if re.match(r'[\n\t]', subdir):
mbligh2b92b862008-11-22 13:25:32 +0000684 raise ValueError('Invalid character in subdir string')
jadmanski10646442008-08-13 14:05:21 +0000685 substr = subdir
686 else:
687 substr = '----'
688
mbligh1b3b3762008-09-25 02:46:34 +0000689 if not log.is_valid_status(status_code):
mbligh2b92b862008-11-22 13:25:32 +0000690 raise ValueError('Invalid status code supplied: %s' % status_code)
jadmanski10646442008-08-13 14:05:21 +0000691 if not operation:
692 operation = '----'
693 if re.match(r'[\n\t]', operation):
mbligh2b92b862008-11-22 13:25:32 +0000694 raise ValueError('Invalid character in operation string')
jadmanski10646442008-08-13 14:05:21 +0000695 operation = operation.rstrip()
696 status = status.rstrip()
697 status = re.sub(r"\t", " ", status)
698 # Ensure any continuation lines are marked so we can
699 # detect them in the status file to ensure it is parsable.
700 status = re.sub(r"\n", "\n" + self.record_prefix + " ", status)
701
702 if not optional_fields:
703 optional_fields = {}
704
705 # Generate timestamps for inclusion in the logs
706 if epoch_time is None:
707 epoch_time = int(time.time())
708 local_time = time.localtime(epoch_time)
709 optional_fields["timestamp"] = str(epoch_time)
710 optional_fields["localtime"] = time.strftime("%b %d %H:%M:%S",
711 local_time)
712
713 fields = [status_code, substr, operation]
714 fields += ["%s=%s" % x for x in optional_fields.iteritems()]
715 fields.append(status)
716
717 if record_prefix is None:
718 record_prefix = self.record_prefix
719
720 msg = '\t'.join(str(x) for x in fields)
jadmanski10646442008-08-13 14:05:21 +0000721 return record_prefix + msg + '\n'
722
723
724 def _record_prerendered(self, msg):
725 """
726 Record a pre-rendered msg into the status logs. The only
727 change this makes to the message is to add on the local
728 indentation. Should not be called outside of server_job.*
729 classes. Unlike _record, this does not write the message
730 to standard output.
731 """
732 lines = []
733 status_file = os.path.join(self.resultdir, 'status.log')
734 status_log = open(status_file, 'a')
735 for line in msg.splitlines():
736 line = self.record_prefix + line + '\n'
737 lines.append(line)
738 status_log.write(line)
739 status_log.close()
740 self.__parse_status(lines)
741
742
mbligh084bc172008-10-18 14:02:45 +0000743 def _fill_server_control_namespace(self, namespace, protect=True):
mbligh2b92b862008-11-22 13:25:32 +0000744 """
745 Prepare a namespace to be used when executing server control files.
mbligh084bc172008-10-18 14:02:45 +0000746
747 This sets up the control file API by importing modules and making them
748 available under the appropriate names within namespace.
749
750 For use by _execute_code().
751
752 Args:
753 namespace: The namespace dictionary to fill in.
754 protect: Boolean. If True (the default) any operation that would
755 clobber an existing entry in namespace will cause an error.
756 Raises:
757 error.AutoservError: When a name would be clobbered by import.
758 """
759 def _import_names(module_name, names=()):
mbligh2b92b862008-11-22 13:25:32 +0000760 """
761 Import a module and assign named attributes into namespace.
mbligh084bc172008-10-18 14:02:45 +0000762
763 Args:
764 module_name: The string module name.
765 names: A limiting list of names to import from module_name. If
766 empty (the default), all names are imported from the module
767 similar to a "from foo.bar import *" statement.
768 Raises:
769 error.AutoservError: When a name being imported would clobber
770 a name already in namespace.
771 """
772 module = __import__(module_name, {}, {}, names)
773
774 # No names supplied? Import * from the lowest level module.
775 # (Ugh, why do I have to implement this part myself?)
776 if not names:
777 for submodule_name in module_name.split('.')[1:]:
778 module = getattr(module, submodule_name)
779 if hasattr(module, '__all__'):
780 names = getattr(module, '__all__')
781 else:
782 names = dir(module)
783
784 # Install each name into namespace, checking to make sure it
785 # doesn't override anything that already exists.
786 for name in names:
787 # Check for conflicts to help prevent future problems.
788 if name in namespace and protect:
789 if namespace[name] is not getattr(module, name):
790 raise error.AutoservError('importing name '
791 '%s from %s %r would override %r' %
792 (name, module_name, getattr(module, name),
793 namespace[name]))
794 else:
795 # Encourage cleanliness and the use of __all__ for a
796 # more concrete API with less surprises on '*' imports.
797 warnings.warn('%s (%r) being imported from %s for use '
798 'in server control files is not the '
799 'first occurrance of that import.' %
800 (name, namespace[name], module_name))
801
802 namespace[name] = getattr(module, name)
803
804
805 # This is the equivalent of prepending a bunch of import statements to
806 # the front of the control script.
807 namespace.update(os=os, sys=sys)
808 _import_names('autotest_lib.server',
809 ('hosts', 'autotest', 'kvm', 'git', 'standalone_profiler',
810 'source_kernel', 'rpm_kernel', 'deb_kernel', 'git_kernel'))
811 _import_names('autotest_lib.server.subcommand',
812 ('parallel', 'parallel_simple', 'subcommand'))
813 _import_names('autotest_lib.server.utils',
814 ('run', 'get_tmp_dir', 'sh_escape', 'parse_machine'))
815 _import_names('autotest_lib.client.common_lib.error')
816 _import_names('autotest_lib.client.common_lib.barrier', ('barrier',))
817
818 # Inject ourself as the job object into other classes within the API.
819 # (Yuck, this injection is a gross thing be part of a public API. -gps)
820 #
821 # XXX Base & SiteAutotest do not appear to use .job. Who does?
822 namespace['autotest'].Autotest.job = self
823 # server.hosts.base_classes.Host uses .job.
824 namespace['hosts'].Host.job = self
825
826
827 def _execute_code(self, code_file, namespace, protect=True):
mbligh2b92b862008-11-22 13:25:32 +0000828 """
829 Execute code using a copy of namespace as a server control script.
mbligh084bc172008-10-18 14:02:45 +0000830
831 Unless protect_namespace is explicitly set to False, the dict will not
832 be modified.
833
834 Args:
835 code_file: The filename of the control file to execute.
836 namespace: A dict containing names to make available during execution.
837 protect: Boolean. If True (the default) a copy of the namespace dict
838 is used during execution to prevent the code from modifying its
839 contents outside of this function. If False the raw dict is
840 passed in and modifications will be allowed.
841 """
842 if protect:
843 namespace = namespace.copy()
844 self._fill_server_control_namespace(namespace, protect=protect)
845 # TODO: Simplify and get rid of the special cases for only 1 machine.
showard3e66e8c2008-10-27 19:20:51 +0000846 if len(self.machines) > 1:
mbligh084bc172008-10-18 14:02:45 +0000847 machines_text = '\n'.join(self.machines) + '\n'
848 # Only rewrite the file if it does not match our machine list.
849 try:
850 machines_f = open(MACHINES_FILENAME, 'r')
851 existing_machines_text = machines_f.read()
852 machines_f.close()
853 except EnvironmentError:
854 existing_machines_text = None
855 if machines_text != existing_machines_text:
856 utils.open_write_close(MACHINES_FILENAME, machines_text)
857 execfile(code_file, namespace, namespace)
jadmanski10646442008-08-13 14:05:21 +0000858
859
860 def _record(self, status_code, subdir, operation, status='',
861 epoch_time=None, optional_fields=None):
862 """
863 Actual function for recording a single line into the status
864 logs. Should never be called directly, only by job.record as
865 this would bypass the console monitor logging.
866 """
867
mbligh2b92b862008-11-22 13:25:32 +0000868 msg = self._render_record(status_code, subdir, operation, status,
869 epoch_time, optional_fields=optional_fields)
jadmanski10646442008-08-13 14:05:21 +0000870
871
872 status_file = os.path.join(self.resultdir, 'status.log')
873 sys.stdout.write(msg)
874 open(status_file, "a").write(msg)
875 if subdir:
876 test_dir = os.path.join(self.resultdir, subdir)
jadmanski5ff55352008-09-18 19:43:46 +0000877 status_file = os.path.join(test_dir, 'status.log')
jadmanski10646442008-08-13 14:05:21 +0000878 open(status_file, "a").write(msg)
879 self.__parse_status(msg.splitlines())
880
881
882 def __parse_status(self, new_lines):
883 if not self.using_parser:
884 return
885 new_tests = self.parser.process_lines(new_lines)
886 for test in new_tests:
887 self.__insert_test(test)
888
889
890 def __insert_test(self, test):
mbligh2b92b862008-11-22 13:25:32 +0000891 """
892 An internal method to insert a new test result into the
jadmanski10646442008-08-13 14:05:21 +0000893 database. This method will not raise an exception, even if an
894 error occurs during the insert, to avoid failing a test
895 simply because of unexpected database issues."""
showard21baa452008-10-21 00:08:39 +0000896 self.num_tests_run += 1
897 if status_lib.is_worse_than_or_equal_to(test.status, 'FAIL'):
898 self.num_tests_failed += 1
jadmanski10646442008-08-13 14:05:21 +0000899 try:
900 self.results_db.insert_test(self.job_model, test)
901 except Exception:
902 msg = ("WARNING: An unexpected error occured while "
903 "inserting test results into the database. "
904 "Ignoring error.\n" + traceback.format_exc())
905 print >> sys.stderr, msg
906
mblighcaa62c22008-04-07 21:51:17 +0000907
mbligha7007722009-01-13 00:37:11 +0000908site_server_job = utils.import_site_class(
909 __file__, "autotest_lib.server.site_server_job", "site_server_job",
910 base_server_job)
jadmanski0afbb632008-06-06 21:10:57 +0000911
jadmanski10646442008-08-13 14:05:21 +0000912class server_job(site_server_job, base_server_job):
jadmanski0afbb632008-06-06 21:10:57 +0000913 pass
jadmanskif37df842009-02-11 00:03:26 +0000914
915
916class warning_manager(object):
917 """Class for controlling warning logs. Manages the enabling and disabling
918 of warnings."""
919 def __init__(self):
920 # a map of warning types to a list of disabled time intervals
921 self.disabled_warnings = {}
922
923
924 def is_valid(self, timestamp, warning_type):
925 """Indicates if a warning (based on the time it occured and its type)
926 is a valid warning. A warning is considered "invalid" if this type of
927 warning was marked as "disabled" at the time the warning occured."""
928 disabled_intervals = self.disabled_warnings.get(warning_type, [])
929 for start, end in disabled_intervals:
930 if timestamp >= start and (end is None or timestamp < end):
931 return False
932 return True
933
934
935 def disable_warnings(self, warning_type, current_time_func=time.time):
936 """As of now, disables all further warnings of this type."""
937 intervals = self.disabled_warnings.setdefault(warning_type, [])
938 if not intervals or intervals[-1][1] is not None:
939 intervals.append((current_time_func(), None))
940
941
942 def enable_warnings(self, warning_type, current_time_func=time.time):
943 """As of now, enables all further warnings of this type."""
944 intervals = self.disabled_warnings.get(warning_type, [])
945 if intervals and intervals[-1][1] is None:
946 intervals[-1] = (intervals[-1][0], current_time_func())