blob: 1ce98221c412802203d72c2020ae0990e900e9d1 [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__,
44 "autotest_lib.server.site_job", "get_site_job_data",
45 _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()
118 self.ssh_user = ssh_user
119 self.ssh_port = ssh_port
120 self.ssh_pass = ssh_pass
jadmanski23afbec2008-09-17 18:12:07 +0000121 self.run_test_cleanup = True
mbligh09108442008-10-15 16:27:38 +0000122 self.last_boot_tag = None
jadmanski53aaf382008-11-17 16:22:31 +0000123 self.hosts = set()
mblighb5dac432008-11-27 00:38:44 +0000124 self.drop_caches_between_iterations = False
jadmanski10646442008-08-13 14:05:21 +0000125
126 self.stdout = fd_stack.fd_stack(1, sys.stdout)
127 self.stderr = fd_stack.fd_stack(2, sys.stderr)
128
mbligh80e1eba2008-11-19 00:26:18 +0000129 if resultdir:
130 self.sysinfo = sysinfo.sysinfo(self.resultdir)
jadmanski043e1132008-11-19 17:10:32 +0000131 self.profilers = profilers.profilers(self)
jadmanskic09fc152008-10-15 17:56:59 +0000132
jadmanski025099d2008-09-23 14:13:48 +0000133 if not os.access(self.tmpdir, os.W_OK):
134 try:
135 os.makedirs(self.tmpdir, 0700)
136 except os.error, e:
137 # Thrown if the directory already exists, which it may.
138 pass
139
mbligh2b92b862008-11-22 13:25:32 +0000140 if not (os.access(self.tmpdir, os.W_OK) and os.path.isdir(self.tmpdir)):
jadmanski025099d2008-09-23 14:13:48 +0000141 self.tmpdir = os.path.join(tempfile.gettempdir(),
142 'autotest-' + getpass.getuser())
143 try:
144 os.makedirs(self.tmpdir, 0700)
145 except os.error, e:
146 # Thrown if the directory already exists, which it may.
147 # If the problem was something other than the
148 # directory already existing, this chmod should throw as well
149 # exception.
150 os.chmod(self.tmpdir, stat.S_IRWXU)
151
mbligh80e1eba2008-11-19 00:26:18 +0000152 if self.status and os.path.exists(self.status):
jadmanski10646442008-08-13 14:05:21 +0000153 os.unlink(self.status)
154 job_data = {'label' : label, 'user' : user,
155 'hostname' : ','.join(machines),
showard170873e2009-01-07 00:22:26 +0000156 'status_version' : str(self.STATUS_VERSION),
157 'job_started' : str(int(time.time()))}
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
jadmanskie432dd22009-01-30 15:04:51 +0000178 @staticmethod
179 def _load_control_file(path):
180 f = open(path)
181 try:
182 control_file = f.read()
183 finally:
184 f.close()
185 return re.sub('\r', '', control_file)
186
187
jadmanski550fdc22008-11-20 16:32:08 +0000188 def _register_subcommand_hooks(self):
mbligh2b92b862008-11-22 13:25:32 +0000189 """
190 Register some hooks into the subcommand modules that allow us
191 to properly clean up self.hosts created in forked subprocesses.
192 """
jadmanski550fdc22008-11-20 16:32:08 +0000193 def on_fork(cmd):
194 self._existing_hosts_on_fork = set(self.hosts)
195 def on_join(cmd):
196 new_hosts = self.hosts - self._existing_hosts_on_fork
197 for host in new_hosts:
198 host.close()
199 subcommand.subcommand.register_fork_hook(on_fork)
200 subcommand.subcommand.register_join_hook(on_join)
201
jadmanski10646442008-08-13 14:05:21 +0000202
203 def init_parser(self, resultdir):
mbligh2b92b862008-11-22 13:25:32 +0000204 """
205 Start the continuous parsing of resultdir. This sets up
jadmanski10646442008-08-13 14:05:21 +0000206 the database connection and inserts the basic job object into
mbligh2b92b862008-11-22 13:25:32 +0000207 the database if necessary.
208 """
jadmanski10646442008-08-13 14:05:21 +0000209 # redirect parser debugging to .parse.log
210 parse_log = os.path.join(resultdir, '.parse.log')
211 parse_log = open(parse_log, 'w', 0)
212 tko_utils.redirect_parser_debugging(parse_log)
213 # create a job model object and set up the db
214 self.results_db = tko_db.db(autocommit=True)
215 self.parser = status_lib.parser(self.STATUS_VERSION)
216 self.job_model = self.parser.make_job(resultdir)
217 self.parser.start(self.job_model)
218 # check if a job already exists in the db and insert it if
219 # it does not
220 job_idx = self.results_db.find_job(self.parse_job)
221 if job_idx is None:
mbligh2b92b862008-11-22 13:25:32 +0000222 self.results_db.insert_job(self.parse_job, self.job_model)
jadmanski10646442008-08-13 14:05:21 +0000223 else:
mbligh2b92b862008-11-22 13:25:32 +0000224 machine_idx = self.results_db.lookup_machine(self.job_model.machine)
jadmanski10646442008-08-13 14:05:21 +0000225 self.job_model.index = job_idx
226 self.job_model.machine_idx = machine_idx
227
228
229 def cleanup_parser(self):
mbligh2b92b862008-11-22 13:25:32 +0000230 """
231 This should be called after the server job is finished
jadmanski10646442008-08-13 14:05:21 +0000232 to carry out any remaining cleanup (e.g. flushing any
mbligh2b92b862008-11-22 13:25:32 +0000233 remaining test results to the results db)
234 """
jadmanski10646442008-08-13 14:05:21 +0000235 if not self.using_parser:
236 return
237 final_tests = self.parser.end()
238 for test in final_tests:
239 self.__insert_test(test)
240 self.using_parser = False
241
242
243 def verify(self):
244 if not self.machines:
mbligh084bc172008-10-18 14:02:45 +0000245 raise error.AutoservError('No machines specified to verify')
mbligh0fce4112008-11-27 00:37:17 +0000246 if self.resultdir:
247 os.chdir(self.resultdir)
jadmanski10646442008-08-13 14:05:21 +0000248 try:
jadmanskicdd0c402008-09-19 21:21:31 +0000249 namespace = {'machines' : self.machines, 'job' : self,
250 'ssh_user' : self.ssh_user,
251 'ssh_port' : self.ssh_port,
252 'ssh_pass' : self.ssh_pass}
mbligh084bc172008-10-18 14:02:45 +0000253 self._execute_code(VERIFY_CONTROL_FILE, namespace, protect=False)
jadmanski10646442008-08-13 14:05:21 +0000254 except Exception, e:
mbligh2b92b862008-11-22 13:25:32 +0000255 msg = ('Verify failed\n' + str(e) + '\n' + traceback.format_exc())
jadmanski10646442008-08-13 14:05:21 +0000256 self.record('ABORT', None, None, msg)
257 raise
258
259
260 def repair(self, host_protection):
261 if not self.machines:
262 raise error.AutoservError('No machines specified to repair')
mbligh0fce4112008-11-27 00:37:17 +0000263 if self.resultdir:
264 os.chdir(self.resultdir)
jadmanski10646442008-08-13 14:05:21 +0000265 namespace = {'machines': self.machines, 'job': self,
266 'ssh_user': self.ssh_user, 'ssh_port': self.ssh_port,
267 'ssh_pass': self.ssh_pass,
268 'protection_level': host_protection}
mbligh25c0b8c2009-01-24 01:44:17 +0000269 # no matter what happens during repair (except if it succeeded in
270 # initiating hardware repair procedure), go on to try to reverify
jadmanski10646442008-08-13 14:05:21 +0000271 try:
mbligh2b92b862008-11-22 13:25:32 +0000272 self._execute_code(REPAIR_CONTROL_FILE, namespace, protect=False)
mbligh25c0b8c2009-01-24 01:44:17 +0000273 except error.AutoservHardwareRepairRequestedError:
274 raise
jadmanski10646442008-08-13 14:05:21 +0000275 except Exception, exc:
276 print 'Exception occured during repair'
277 traceback.print_exc()
mbligh25c0b8c2009-01-24 01:44:17 +0000278
jadmanski10646442008-08-13 14:05:21 +0000279 self.verify()
280
281
282 def precheck(self):
283 """
284 perform any additional checks in derived classes.
285 """
286 pass
287
288
289 def enable_external_logging(self):
mbligh2b92b862008-11-22 13:25:32 +0000290 """
291 Start or restart external logging mechanism.
jadmanski10646442008-08-13 14:05:21 +0000292 """
293 pass
294
295
296 def disable_external_logging(self):
mbligh2b92b862008-11-22 13:25:32 +0000297 """
298 Pause or stop external logging mechanism.
jadmanski10646442008-08-13 14:05:21 +0000299 """
300 pass
301
302
jadmanski23afbec2008-09-17 18:12:07 +0000303 def enable_test_cleanup(self):
mbligh2b92b862008-11-22 13:25:32 +0000304 """
305 By default tests run test.cleanup
306 """
jadmanski23afbec2008-09-17 18:12:07 +0000307 self.run_test_cleanup = True
308
309
310 def disable_test_cleanup(self):
mbligh2b92b862008-11-22 13:25:32 +0000311 """
312 By default tests do not run test.cleanup
313 """
jadmanski23afbec2008-09-17 18:12:07 +0000314 self.run_test_cleanup = False
315
316
jadmanski10646442008-08-13 14:05:21 +0000317 def use_external_logging(self):
mbligh2b92b862008-11-22 13:25:32 +0000318 """
319 Return True if external logging should be used.
jadmanski10646442008-08-13 14:05:21 +0000320 """
321 return False
322
323
324 def parallel_simple(self, function, machines, log=True, timeout=None):
mbligh2b92b862008-11-22 13:25:32 +0000325 """
326 Run 'function' using parallel_simple, with an extra wrapper to handle
327 the necessary setup for continuous parsing, if possible. If continuous
328 parsing is already properly initialized then this should just work.
329 """
330 is_forking = not (len(machines) == 1 and self.machines == machines)
jadmanski4dd1a002008-09-05 20:27:30 +0000331 if self.parse_job and is_forking and log:
jadmanski10646442008-08-13 14:05:21 +0000332 def wrapper(machine):
333 self.parse_job += "/" + machine
334 self.using_parser = True
335 self.machines = [machine]
mbligh2b92b862008-11-22 13:25:32 +0000336 self.resultdir = os.path.join(self.resultdir, machine)
jadmanski609a5f42008-08-26 20:52:42 +0000337 os.chdir(self.resultdir)
showard2bab8f42008-11-12 18:15:22 +0000338 utils.write_keyval(self.resultdir, {"hostname": machine})
jadmanski10646442008-08-13 14:05:21 +0000339 self.init_parser(self.resultdir)
340 result = function(machine)
341 self.cleanup_parser()
342 return result
jadmanski4dd1a002008-09-05 20:27:30 +0000343 elif len(machines) > 1 and log:
jadmanski10646442008-08-13 14:05:21 +0000344 def wrapper(machine):
345 self.resultdir = os.path.join(self.resultdir, machine)
jadmanski609a5f42008-08-26 20:52:42 +0000346 os.chdir(self.resultdir)
jadmanski10646442008-08-13 14:05:21 +0000347 result = function(machine)
348 return result
349 else:
350 wrapper = function
351 subcommand.parallel_simple(wrapper, machines, log, timeout)
352
353
jadmanskie432dd22009-01-30 15:04:51 +0000354 USE_TEMP_DIR = object()
mbligh2b92b862008-11-22 13:25:32 +0000355 def run(self, cleanup=False, install_before=False, install_after=False,
jadmanskie432dd22009-01-30 15:04:51 +0000356 collect_crashdumps=True, namespace={}, control=None,
357 control_file_dir=None):
jadmanski10646442008-08-13 14:05:21 +0000358 # use a copy so changes don't affect the original dictionary
359 namespace = namespace.copy()
360 machines = self.machines
jadmanskie432dd22009-01-30 15:04:51 +0000361 if control is None:
362 control = self.control
363 if control_file_dir is None:
364 control_file_dir = self.resultdir
jadmanski10646442008-08-13 14:05:21 +0000365
366 self.aborted = False
367 namespace['machines'] = machines
368 namespace['args'] = self.args
369 namespace['job'] = self
370 namespace['ssh_user'] = self.ssh_user
371 namespace['ssh_port'] = self.ssh_port
372 namespace['ssh_pass'] = self.ssh_pass
373 test_start_time = int(time.time())
374
mbligh80e1eba2008-11-19 00:26:18 +0000375 if self.resultdir:
376 os.chdir(self.resultdir)
mbligh80e1eba2008-11-19 00:26:18 +0000377 self.enable_external_logging()
jadmanskie432dd22009-01-30 15:04:51 +0000378
jadmanskicdd0c402008-09-19 21:21:31 +0000379 collect_crashinfo = True
mblighaebe3b62008-12-22 14:45:40 +0000380 temp_control_file_dir = None
jadmanski10646442008-08-13 14:05:21 +0000381 try:
382 if install_before and machines:
mbligh084bc172008-10-18 14:02:45 +0000383 self._execute_code(INSTALL_CONTROL_FILE, namespace)
jadmanskie432dd22009-01-30 15:04:51 +0000384
385 # determine the dir to write the control files to
386 if control_file_dir and control_file_dir is not self.USE_TEMP_DIR:
387 temp_control_file_dir = None
mblighaebe3b62008-12-22 14:45:40 +0000388 else:
jadmanskie432dd22009-01-30 15:04:51 +0000389 temp_control_file_dir = control_file_dir = tempfile.mkdtemp(
390 suffix='temp_control_file_dir')
391 server_control_file = os.path.join(control_file_dir,
392 SERVER_CONTROL_FILENAME)
393 client_control_file = os.path.join(control_file_dir,
394 CLIENT_CONTROL_FILENAME)
jadmanski10646442008-08-13 14:05:21 +0000395 if self.client:
jadmanskie432dd22009-01-30 15:04:51 +0000396 namespace['control'] = control
397 utils.open_write_close(client_control_file, control)
mblighaebe3b62008-12-22 14:45:40 +0000398 shutil.copy(CLIENT_WRAPPER_CONTROL_FILE, server_control_file)
jadmanski10646442008-08-13 14:05:21 +0000399 else:
mbligh181b7c22008-11-22 14:22:08 +0000400 namespace['utils'] = utils
jadmanskie432dd22009-01-30 15:04:51 +0000401 utils.open_write_close(server_control_file, control)
mblighaebe3b62008-12-22 14:45:40 +0000402 self._execute_code(server_control_file, namespace)
jadmanski10646442008-08-13 14:05:21 +0000403
jadmanskicdd0c402008-09-19 21:21:31 +0000404 # disable crashinfo collection if we get this far without error
405 collect_crashinfo = False
jadmanski10646442008-08-13 14:05:21 +0000406 finally:
mblighaebe3b62008-12-22 14:45:40 +0000407 if temp_control_file_dir:
jadmanskie432dd22009-01-30 15:04:51 +0000408 # Clean up temp directory used for copies of the control files
mblighaebe3b62008-12-22 14:45:40 +0000409 try:
410 shutil.rmtree(temp_control_file_dir)
411 except Exception, e:
jadmanskie432dd22009-01-30 15:04:51 +0000412 print 'Error %s removing dir %s' % (e,
413 temp_control_file_dir)
414
jadmanskicdd0c402008-09-19 21:21:31 +0000415 if machines and (collect_crashdumps or collect_crashinfo):
jadmanski10646442008-08-13 14:05:21 +0000416 namespace['test_start_time'] = test_start_time
jadmanskicdd0c402008-09-19 21:21:31 +0000417 if collect_crashinfo:
mbligh084bc172008-10-18 14:02:45 +0000418 # includes crashdumps
419 self._execute_code(CRASHINFO_CONTROL_FILE, namespace)
jadmanskicdd0c402008-09-19 21:21:31 +0000420 else:
mbligh084bc172008-10-18 14:02:45 +0000421 self._execute_code(CRASHDUMPS_CONTROL_FILE, namespace)
jadmanski10646442008-08-13 14:05:21 +0000422 self.disable_external_logging()
showard45ae8192008-11-05 19:32:53 +0000423 if cleanup and machines:
424 self._execute_code(CLEANUP_CONTROL_FILE, namespace)
jadmanski10646442008-08-13 14:05:21 +0000425 if install_after and machines:
mbligh084bc172008-10-18 14:02:45 +0000426 self._execute_code(INSTALL_CONTROL_FILE, namespace)
jadmanski10646442008-08-13 14:05:21 +0000427
428
429 def run_test(self, url, *args, **dargs):
mbligh2b92b862008-11-22 13:25:32 +0000430 """
431 Summon a test object and run it.
jadmanski10646442008-08-13 14:05:21 +0000432
433 tag
434 tag to add to testname
435 url
436 url of the test to run
437 """
438
439 (group, testname) = self.pkgmgr.get_package_name(url, 'test')
jadmanski10646442008-08-13 14:05:21 +0000440
441 tag = dargs.pop('tag', None)
442 if tag:
mbligh8ad24202009-01-07 16:49:36 +0000443 testname += '.' + str(tag)
jadmanskide292df2008-08-26 20:51:14 +0000444 subdir = testname
jadmanski10646442008-08-13 14:05:21 +0000445
446 outputdir = os.path.join(self.resultdir, subdir)
447 if os.path.exists(outputdir):
448 msg = ("%s already exists, test <%s> may have"
mbligh2b92b862008-11-22 13:25:32 +0000449 " already run with tag <%s>" % (outputdir, testname, tag))
jadmanski10646442008-08-13 14:05:21 +0000450 raise error.TestError(msg)
451 os.mkdir(outputdir)
452
453 def group_func():
454 try:
455 test.runtest(self, url, tag, args, dargs)
456 except error.TestBaseException, e:
457 self.record(e.exit_status, subdir, testname, str(e))
458 raise
459 except Exception, e:
460 info = str(e) + "\n" + traceback.format_exc()
461 self.record('FAIL', subdir, testname, info)
462 raise
463 else:
mbligh2b92b862008-11-22 13:25:32 +0000464 self.record('GOOD', subdir, testname, 'completed successfully')
jadmanskide292df2008-08-26 20:51:14 +0000465
466 result, exc_info = self._run_group(testname, subdir, group_func)
467 if exc_info and isinstance(exc_info[1], error.TestBaseException):
468 return False
469 elif exc_info:
470 raise exc_info[0], exc_info[1], exc_info[2]
471 else:
472 return True
jadmanski10646442008-08-13 14:05:21 +0000473
474
475 def _run_group(self, name, subdir, function, *args, **dargs):
476 """\
477 Underlying method for running something inside of a group.
478 """
jadmanskide292df2008-08-26 20:51:14 +0000479 result, exc_info = None, None
jadmanski10646442008-08-13 14:05:21 +0000480 old_record_prefix = self.record_prefix
481 try:
482 self.record('START', subdir, name)
483 self.record_prefix += '\t'
484 try:
485 result = function(*args, **dargs)
486 finally:
487 self.record_prefix = old_record_prefix
488 except error.TestBaseException, e:
jadmanskib88d6dc2009-01-10 00:33:18 +0000489 self.record("END %s" % e.exit_status, subdir, name)
jadmanskide292df2008-08-26 20:51:14 +0000490 exc_info = sys.exc_info()
jadmanski10646442008-08-13 14:05:21 +0000491 except Exception, e:
492 err_msg = str(e) + '\n'
493 err_msg += traceback.format_exc()
494 self.record('END ABORT', subdir, name, err_msg)
495 raise error.JobError(name + ' failed\n' + traceback.format_exc())
496 else:
497 self.record('END GOOD', subdir, name)
498
jadmanskide292df2008-08-26 20:51:14 +0000499 return result, exc_info
jadmanski10646442008-08-13 14:05:21 +0000500
501
502 def run_group(self, function, *args, **dargs):
503 """\
504 function:
505 subroutine to run
506 *args:
507 arguments for the function
508 """
509
510 name = function.__name__
511
512 # Allow the tag for the group to be specified.
513 tag = dargs.pop('tag', None)
514 if tag:
515 name = tag
516
jadmanskide292df2008-08-26 20:51:14 +0000517 return self._run_group(name, None, function, *args, **dargs)[0]
jadmanski10646442008-08-13 14:05:21 +0000518
519
520 def run_reboot(self, reboot_func, get_kernel_func):
521 """\
522 A specialization of run_group meant specifically for handling
523 a reboot. Includes support for capturing the kernel version
524 after the reboot.
525
526 reboot_func: a function that carries out the reboot
527
528 get_kernel_func: a function that returns a string
529 representing the kernel version.
530 """
531
532 old_record_prefix = self.record_prefix
533 try:
534 self.record('START', None, 'reboot')
535 self.record_prefix += '\t'
536 reboot_func()
537 except Exception, e:
538 self.record_prefix = old_record_prefix
539 err_msg = str(e) + '\n' + traceback.format_exc()
540 self.record('END FAIL', None, 'reboot', err_msg)
541 else:
542 kernel = get_kernel_func()
543 self.record_prefix = old_record_prefix
544 self.record('END GOOD', None, 'reboot',
545 optional_fields={"kernel": kernel})
546
547
jadmanskie432dd22009-01-30 15:04:51 +0000548 def run_control(self, path):
549 """Execute a control file found at path (relative to the autotest
550 path). Intended for executing a control file within a control file,
551 not for running the top-level job control file."""
552 path = os.path.join(self.autodir, path)
553 control_file = self._load_control_file(path)
554 self.run(control=control_file, control_file_dir=self.USE_TEMP_DIR)
555
556
jadmanskic09fc152008-10-15 17:56:59 +0000557 def add_sysinfo_command(self, command, logfile=None, on_every_test=False):
558 self._add_sysinfo_loggable(sysinfo.command(command, logfile),
559 on_every_test)
560
561
562 def add_sysinfo_logfile(self, file, on_every_test=False):
563 self._add_sysinfo_loggable(sysinfo.logfile(file), on_every_test)
564
565
566 def _add_sysinfo_loggable(self, loggable, on_every_test):
567 if on_every_test:
568 self.sysinfo.test_loggables.add(loggable)
569 else:
570 self.sysinfo.boot_loggables.add(loggable)
571
572
jadmanski10646442008-08-13 14:05:21 +0000573 def record(self, status_code, subdir, operation, status='',
574 optional_fields=None):
575 """
576 Record job-level status
577
578 The intent is to make this file both machine parseable and
579 human readable. That involves a little more complexity, but
580 really isn't all that bad ;-)
581
582 Format is <status code>\t<subdir>\t<operation>\t<status>
583
mbligh1b3b3762008-09-25 02:46:34 +0000584 status code: see common_lib.log.is_valid_status()
jadmanski10646442008-08-13 14:05:21 +0000585 for valid status definition
586
587 subdir: MUST be a relevant subdirectory in the results,
588 or None, which will be represented as '----'
589
590 operation: description of what you ran (e.g. "dbench", or
591 "mkfs -t foobar /dev/sda9")
592
593 status: error message or "completed sucessfully"
594
595 ------------------------------------------------------------
596
597 Initial tabs indicate indent levels for grouping, and is
598 governed by self.record_prefix
599
600 multiline messages have secondary lines prefaced by a double
601 space (' ')
602
603 Executing this method will trigger the logging of all new
604 warnings to date from the various console loggers.
605 """
606 # poll all our warning loggers for new warnings
607 warnings = self._read_warnings()
608 for timestamp, msg in warnings:
609 self._record("WARN", None, None, msg, timestamp)
610
611 # write out the actual status log line
612 self._record(status_code, subdir, operation, status,
613 optional_fields=optional_fields)
614
615
616 def _read_warnings(self):
617 warnings = []
618 while True:
619 # pull in a line of output from every logger that has
620 # output ready to be read
mbligh2b92b862008-11-22 13:25:32 +0000621 loggers, _, _ = select.select(self.warning_loggers, [], [], 0)
jadmanski10646442008-08-13 14:05:21 +0000622 closed_loggers = set()
623 for logger in loggers:
624 line = logger.readline()
625 # record any broken pipes (aka line == empty)
626 if len(line) == 0:
627 closed_loggers.add(logger)
628 continue
629 timestamp, msg = line.split('\t', 1)
630 warnings.append((int(timestamp), msg.strip()))
631
632 # stop listening to loggers that are closed
633 self.warning_loggers -= closed_loggers
634
635 # stop if none of the loggers have any output left
636 if not loggers:
637 break
638
639 # sort into timestamp order
640 warnings.sort()
641 return warnings
642
643
644 def _render_record(self, status_code, subdir, operation, status='',
645 epoch_time=None, record_prefix=None,
646 optional_fields=None):
647 """
648 Internal Function to generate a record to be written into a
649 status log. For use by server_job.* classes only.
650 """
651 if subdir:
652 if re.match(r'[\n\t]', subdir):
mbligh2b92b862008-11-22 13:25:32 +0000653 raise ValueError('Invalid character in subdir string')
jadmanski10646442008-08-13 14:05:21 +0000654 substr = subdir
655 else:
656 substr = '----'
657
mbligh1b3b3762008-09-25 02:46:34 +0000658 if not log.is_valid_status(status_code):
mbligh2b92b862008-11-22 13:25:32 +0000659 raise ValueError('Invalid status code supplied: %s' % status_code)
jadmanski10646442008-08-13 14:05:21 +0000660 if not operation:
661 operation = '----'
662 if re.match(r'[\n\t]', operation):
mbligh2b92b862008-11-22 13:25:32 +0000663 raise ValueError('Invalid character in operation string')
jadmanski10646442008-08-13 14:05:21 +0000664 operation = operation.rstrip()
665 status = status.rstrip()
666 status = re.sub(r"\t", " ", status)
667 # Ensure any continuation lines are marked so we can
668 # detect them in the status file to ensure it is parsable.
669 status = re.sub(r"\n", "\n" + self.record_prefix + " ", status)
670
671 if not optional_fields:
672 optional_fields = {}
673
674 # Generate timestamps for inclusion in the logs
675 if epoch_time is None:
676 epoch_time = int(time.time())
677 local_time = time.localtime(epoch_time)
678 optional_fields["timestamp"] = str(epoch_time)
679 optional_fields["localtime"] = time.strftime("%b %d %H:%M:%S",
680 local_time)
681
682 fields = [status_code, substr, operation]
683 fields += ["%s=%s" % x for x in optional_fields.iteritems()]
684 fields.append(status)
685
686 if record_prefix is None:
687 record_prefix = self.record_prefix
688
689 msg = '\t'.join(str(x) for x in fields)
jadmanski10646442008-08-13 14:05:21 +0000690 return record_prefix + msg + '\n'
691
692
693 def _record_prerendered(self, msg):
694 """
695 Record a pre-rendered msg into the status logs. The only
696 change this makes to the message is to add on the local
697 indentation. Should not be called outside of server_job.*
698 classes. Unlike _record, this does not write the message
699 to standard output.
700 """
701 lines = []
702 status_file = os.path.join(self.resultdir, 'status.log')
703 status_log = open(status_file, 'a')
704 for line in msg.splitlines():
705 line = self.record_prefix + line + '\n'
706 lines.append(line)
707 status_log.write(line)
708 status_log.close()
709 self.__parse_status(lines)
710
711
mbligh084bc172008-10-18 14:02:45 +0000712 def _fill_server_control_namespace(self, namespace, protect=True):
mbligh2b92b862008-11-22 13:25:32 +0000713 """
714 Prepare a namespace to be used when executing server control files.
mbligh084bc172008-10-18 14:02:45 +0000715
716 This sets up the control file API by importing modules and making them
717 available under the appropriate names within namespace.
718
719 For use by _execute_code().
720
721 Args:
722 namespace: The namespace dictionary to fill in.
723 protect: Boolean. If True (the default) any operation that would
724 clobber an existing entry in namespace will cause an error.
725 Raises:
726 error.AutoservError: When a name would be clobbered by import.
727 """
728 def _import_names(module_name, names=()):
mbligh2b92b862008-11-22 13:25:32 +0000729 """
730 Import a module and assign named attributes into namespace.
mbligh084bc172008-10-18 14:02:45 +0000731
732 Args:
733 module_name: The string module name.
734 names: A limiting list of names to import from module_name. If
735 empty (the default), all names are imported from the module
736 similar to a "from foo.bar import *" statement.
737 Raises:
738 error.AutoservError: When a name being imported would clobber
739 a name already in namespace.
740 """
741 module = __import__(module_name, {}, {}, names)
742
743 # No names supplied? Import * from the lowest level module.
744 # (Ugh, why do I have to implement this part myself?)
745 if not names:
746 for submodule_name in module_name.split('.')[1:]:
747 module = getattr(module, submodule_name)
748 if hasattr(module, '__all__'):
749 names = getattr(module, '__all__')
750 else:
751 names = dir(module)
752
753 # Install each name into namespace, checking to make sure it
754 # doesn't override anything that already exists.
755 for name in names:
756 # Check for conflicts to help prevent future problems.
757 if name in namespace and protect:
758 if namespace[name] is not getattr(module, name):
759 raise error.AutoservError('importing name '
760 '%s from %s %r would override %r' %
761 (name, module_name, getattr(module, name),
762 namespace[name]))
763 else:
764 # Encourage cleanliness and the use of __all__ for a
765 # more concrete API with less surprises on '*' imports.
766 warnings.warn('%s (%r) being imported from %s for use '
767 'in server control files is not the '
768 'first occurrance of that import.' %
769 (name, namespace[name], module_name))
770
771 namespace[name] = getattr(module, name)
772
773
774 # This is the equivalent of prepending a bunch of import statements to
775 # the front of the control script.
776 namespace.update(os=os, sys=sys)
777 _import_names('autotest_lib.server',
778 ('hosts', 'autotest', 'kvm', 'git', 'standalone_profiler',
779 'source_kernel', 'rpm_kernel', 'deb_kernel', 'git_kernel'))
780 _import_names('autotest_lib.server.subcommand',
781 ('parallel', 'parallel_simple', 'subcommand'))
782 _import_names('autotest_lib.server.utils',
783 ('run', 'get_tmp_dir', 'sh_escape', 'parse_machine'))
784 _import_names('autotest_lib.client.common_lib.error')
785 _import_names('autotest_lib.client.common_lib.barrier', ('barrier',))
786
787 # Inject ourself as the job object into other classes within the API.
788 # (Yuck, this injection is a gross thing be part of a public API. -gps)
789 #
790 # XXX Base & SiteAutotest do not appear to use .job. Who does?
791 namespace['autotest'].Autotest.job = self
792 # server.hosts.base_classes.Host uses .job.
793 namespace['hosts'].Host.job = self
794
795
796 def _execute_code(self, code_file, namespace, protect=True):
mbligh2b92b862008-11-22 13:25:32 +0000797 """
798 Execute code using a copy of namespace as a server control script.
mbligh084bc172008-10-18 14:02:45 +0000799
800 Unless protect_namespace is explicitly set to False, the dict will not
801 be modified.
802
803 Args:
804 code_file: The filename of the control file to execute.
805 namespace: A dict containing names to make available during execution.
806 protect: Boolean. If True (the default) a copy of the namespace dict
807 is used during execution to prevent the code from modifying its
808 contents outside of this function. If False the raw dict is
809 passed in and modifications will be allowed.
810 """
811 if protect:
812 namespace = namespace.copy()
813 self._fill_server_control_namespace(namespace, protect=protect)
814 # TODO: Simplify and get rid of the special cases for only 1 machine.
showard3e66e8c2008-10-27 19:20:51 +0000815 if len(self.machines) > 1:
mbligh084bc172008-10-18 14:02:45 +0000816 machines_text = '\n'.join(self.machines) + '\n'
817 # Only rewrite the file if it does not match our machine list.
818 try:
819 machines_f = open(MACHINES_FILENAME, 'r')
820 existing_machines_text = machines_f.read()
821 machines_f.close()
822 except EnvironmentError:
823 existing_machines_text = None
824 if machines_text != existing_machines_text:
825 utils.open_write_close(MACHINES_FILENAME, machines_text)
826 execfile(code_file, namespace, namespace)
jadmanski10646442008-08-13 14:05:21 +0000827
828
829 def _record(self, status_code, subdir, operation, status='',
830 epoch_time=None, optional_fields=None):
831 """
832 Actual function for recording a single line into the status
833 logs. Should never be called directly, only by job.record as
834 this would bypass the console monitor logging.
835 """
836
mbligh2b92b862008-11-22 13:25:32 +0000837 msg = self._render_record(status_code, subdir, operation, status,
838 epoch_time, optional_fields=optional_fields)
jadmanski10646442008-08-13 14:05:21 +0000839
840
841 status_file = os.path.join(self.resultdir, 'status.log')
842 sys.stdout.write(msg)
843 open(status_file, "a").write(msg)
844 if subdir:
845 test_dir = os.path.join(self.resultdir, subdir)
jadmanski5ff55352008-09-18 19:43:46 +0000846 status_file = os.path.join(test_dir, 'status.log')
jadmanski10646442008-08-13 14:05:21 +0000847 open(status_file, "a").write(msg)
848 self.__parse_status(msg.splitlines())
849
850
851 def __parse_status(self, new_lines):
852 if not self.using_parser:
853 return
854 new_tests = self.parser.process_lines(new_lines)
855 for test in new_tests:
856 self.__insert_test(test)
857
858
859 def __insert_test(self, test):
mbligh2b92b862008-11-22 13:25:32 +0000860 """
861 An internal method to insert a new test result into the
jadmanski10646442008-08-13 14:05:21 +0000862 database. This method will not raise an exception, even if an
863 error occurs during the insert, to avoid failing a test
864 simply because of unexpected database issues."""
showard21baa452008-10-21 00:08:39 +0000865 self.num_tests_run += 1
866 if status_lib.is_worse_than_or_equal_to(test.status, 'FAIL'):
867 self.num_tests_failed += 1
jadmanski10646442008-08-13 14:05:21 +0000868 try:
869 self.results_db.insert_test(self.job_model, test)
870 except Exception:
871 msg = ("WARNING: An unexpected error occured while "
872 "inserting test results into the database. "
873 "Ignoring error.\n" + traceback.format_exc())
874 print >> sys.stderr, msg
875
mblighcaa62c22008-04-07 21:51:17 +0000876
mbligha7007722009-01-13 00:37:11 +0000877site_server_job = utils.import_site_class(
878 __file__, "autotest_lib.server.site_server_job", "site_server_job",
879 base_server_job)
jadmanski0afbb632008-06-06 21:10:57 +0000880
jadmanski10646442008-08-13 14:05:21 +0000881class server_job(site_server_job, base_server_job):
jadmanski0afbb632008-06-06 21:10:57 +0000882 pass