blob: 757257fff1df866ca618dec268eea0838da12264 [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
jadmanski6bb32d72009-03-19 20:25:24 +00009import getpass, os, sys, re, stat, tempfile, time, select, subprocess
mblighfc3da5b2010-01-06 18:37:22 +000010import traceback, shutil, warnings, fcntl, pickle, logging, itertools, errno
showard75cdfee2009-06-10 17:40:41 +000011from autotest_lib.client.bin import sysinfo
mbligh0d0f67d2009-11-06 03:15:03 +000012from autotest_lib.client.common_lib import base_job
mbligh09108442008-10-15 16:27:38 +000013from autotest_lib.client.common_lib import error, log, utils, packages
showard75cdfee2009-06-10 17:40:41 +000014from autotest_lib.client.common_lib import logging_manager
jadmanski043e1132008-11-19 17:10:32 +000015from autotest_lib.server import test, subcommand, profilers
mbligh0a883702010-04-21 01:58:34 +000016from autotest_lib.server.hosts import abstract_ssh
jadmanski10646442008-08-13 14:05:21 +000017from autotest_lib.tko import db as tko_db, status_lib, utils as tko_utils
jadmanski10646442008-08-13 14:05:21 +000018
19
mbligh084bc172008-10-18 14:02:45 +000020def _control_segment_path(name):
21 """Get the pathname of the named control segment file."""
jadmanski10646442008-08-13 14:05:21 +000022 server_dir = os.path.dirname(os.path.abspath(__file__))
mbligh084bc172008-10-18 14:02:45 +000023 return os.path.join(server_dir, "control_segments", name)
jadmanski10646442008-08-13 14:05:21 +000024
25
mbligh084bc172008-10-18 14:02:45 +000026CLIENT_CONTROL_FILENAME = 'control'
27SERVER_CONTROL_FILENAME = 'control.srv'
28MACHINES_FILENAME = '.machines'
jadmanski10646442008-08-13 14:05:21 +000029
mbligh084bc172008-10-18 14:02:45 +000030CLIENT_WRAPPER_CONTROL_FILE = _control_segment_path('client_wrapper')
31CRASHDUMPS_CONTROL_FILE = _control_segment_path('crashdumps')
32CRASHINFO_CONTROL_FILE = _control_segment_path('crashinfo')
mbligh084bc172008-10-18 14:02:45 +000033INSTALL_CONTROL_FILE = _control_segment_path('install')
showard45ae8192008-11-05 19:32:53 +000034CLEANUP_CONTROL_FILE = _control_segment_path('cleanup')
jadmanski10646442008-08-13 14:05:21 +000035
mbligh084bc172008-10-18 14:02:45 +000036VERIFY_CONTROL_FILE = _control_segment_path('verify')
mbligh084bc172008-10-18 14:02:45 +000037REPAIR_CONTROL_FILE = _control_segment_path('repair')
jadmanski10646442008-08-13 14:05:21 +000038
39
mbligh062ed152009-01-13 00:57:14 +000040# by default provide a stub that generates no site data
41def _get_site_job_data_dummy(job):
42 return {}
43
44
jadmanski10646442008-08-13 14:05:21 +000045# load up site-specific code for generating site-specific job data
mbligh062ed152009-01-13 00:57:14 +000046get_site_job_data = utils.import_site_function(__file__,
jadmanskic0a623d2009-03-03 21:11:48 +000047 "autotest_lib.server.site_server_job", "get_site_job_data",
mbligh062ed152009-01-13 00:57:14 +000048 _get_site_job_data_dummy)
jadmanski10646442008-08-13 14:05:21 +000049
50
jadmanski2a89dac2010-06-11 14:32:58 +000051class status_indenter(base_job.status_indenter):
52 """Provide a simple integer-backed status indenter."""
53 def __init__(self):
54 self._indent = 0
55
56
57 @property
58 def indent(self):
59 return self._indent
60
61
62 def increment(self):
63 self._indent += 1
64
65
66 def decrement(self):
67 self._indent -= 1
68
69
jadmanski52053632010-06-11 21:08:10 +000070 def get_context(self):
71 """Returns a context object for use by job.get_record_context."""
72 class context(object):
73 def __init__(self, indenter, indent):
74 self._indenter = indenter
75 self._indent = indent
76 def restore(self):
77 self._indenter._indent = self._indent
78 return context(self, self._indent)
79
80
jadmanski2a89dac2010-06-11 14:32:58 +000081class server_job_record_hook(object):
82 """The job.record hook for server job. Used to inject WARN messages from
83 the console or vlm whenever new logs are written, and to echo any logs
84 to INFO level logging. Implemented as a class so that it can use state to
85 block recursive calls, so that the hook can call job.record itself to
86 log WARN messages.
87
88 Depends on job._read_warnings and job._logger.
89 """
90 def __init__(self, job):
91 self._job = job
92 self._being_called = False
93
94
95 def __call__(self, entry):
96 """A wrapper around the 'real' record hook, the _hook method, which
97 prevents recursion. This isn't making any effort to be threadsafe,
98 the intent is to outright block infinite recursion via a
99 job.record->_hook->job.record->_hook->job.record... chain."""
100 if self._being_called:
101 return
102 self._being_called = True
103 try:
104 self._hook(self._job, entry)
105 finally:
106 self._being_called = False
107
108
109 @staticmethod
110 def _hook(job, entry):
111 """The core hook, which can safely call job.record."""
112 entries = []
113 # poll all our warning loggers for new warnings
114 for timestamp, msg in job._read_warnings():
115 warning_entry = base_job.status_log_entry(
116 'WARN', None, None, msg, {}, timestamp=timestamp)
117 entries.append(warning_entry)
118 job.record_entry(warning_entry)
119 # echo rendered versions of all the status logs to info
120 entries.append(entry)
121 for entry in entries:
122 rendered_entry = job._logger.render_entry(entry)
123 logging.info(rendered_entry)
jadmanskie29d0e42010-06-17 16:06:52 +0000124 job._parse_status(rendered_entry)
jadmanski2a89dac2010-06-11 14:32:58 +0000125
126
mbligh0d0f67d2009-11-06 03:15:03 +0000127class base_server_job(base_job.base_job):
128 """The server-side concrete implementation of base_job.
jadmanski10646442008-08-13 14:05:21 +0000129
mbligh0d0f67d2009-11-06 03:15:03 +0000130 Optional properties provided by this implementation:
131 serverdir
132 conmuxdir
133
134 num_tests_run
135 num_tests_failed
136
137 warning_manager
138 warning_loggers
jadmanski10646442008-08-13 14:05:21 +0000139 """
140
mbligh0d0f67d2009-11-06 03:15:03 +0000141 _STATUS_VERSION = 1
jadmanski10646442008-08-13 14:05:21 +0000142
143 def __init__(self, control, args, resultdir, label, user, machines,
144 client=False, parse_job='',
mbligh374f3412009-05-13 21:29:45 +0000145 ssh_user='root', ssh_port=22, ssh_pass='',
mblighe0cbc912010-03-11 18:03:07 +0000146 group_name='', tag='',
147 control_filename=SERVER_CONTROL_FILENAME):
jadmanski10646442008-08-13 14:05:21 +0000148 """
mbligh374f3412009-05-13 21:29:45 +0000149 Create a server side job object.
mblighb5dac432008-11-27 00:38:44 +0000150
mblighe7d9c602009-07-02 19:02:33 +0000151 @param control: The pathname of the control file.
152 @param args: Passed to the control file.
153 @param resultdir: Where to throw the results.
154 @param label: Description of the job.
155 @param user: Username for the job (email address).
156 @param client: True if this is a client-side control file.
157 @param parse_job: string, if supplied it is the job execution tag that
158 the results will be passed through to the TKO parser with.
159 @param ssh_user: The SSH username. [root]
160 @param ssh_port: The SSH port number. [22]
161 @param ssh_pass: The SSH passphrase, if needed.
162 @param group_name: If supplied, this will be written out as
mbligh374f3412009-05-13 21:29:45 +0000163 host_group_name in the keyvals file for the parser.
mblighe7d9c602009-07-02 19:02:33 +0000164 @param tag: The job execution tag from the scheduler. [optional]
mblighe0cbc912010-03-11 18:03:07 +0000165 @param control_filename: The filename where the server control file
166 should be written in the results directory.
jadmanski10646442008-08-13 14:05:21 +0000167 """
mbligh0d0f67d2009-11-06 03:15:03 +0000168 super(base_server_job, self).__init__(resultdir=resultdir)
mbligha788dc42009-03-26 21:10:16 +0000169
mbligh0d0f67d2009-11-06 03:15:03 +0000170 path = os.path.dirname(__file__)
171 self.control = control
172 self._uncollected_log_file = os.path.join(self.resultdir,
173 'uncollected_logs')
174 debugdir = os.path.join(self.resultdir, 'debug')
175 if not os.path.exists(debugdir):
176 os.mkdir(debugdir)
177
178 if user:
179 self.user = user
180 else:
181 self.user = getpass.getuser()
182
jadmanski808f4b12010-04-09 22:30:31 +0000183 self.args = args
jadmanski10646442008-08-13 14:05:21 +0000184 self.machines = machines
mbligh0d0f67d2009-11-06 03:15:03 +0000185 self._client = client
jadmanski10646442008-08-13 14:05:21 +0000186 self.warning_loggers = set()
jadmanskif37df842009-02-11 00:03:26 +0000187 self.warning_manager = warning_manager()
mbligh0d0f67d2009-11-06 03:15:03 +0000188 self._ssh_user = ssh_user
189 self._ssh_port = ssh_port
190 self._ssh_pass = ssh_pass
mblighe7d9c602009-07-02 19:02:33 +0000191 self.tag = tag
mbligh09108442008-10-15 16:27:38 +0000192 self.last_boot_tag = None
jadmanski53aaf382008-11-17 16:22:31 +0000193 self.hosts = set()
mbligh0d0f67d2009-11-06 03:15:03 +0000194 self.drop_caches = False
mblighb5dac432008-11-27 00:38:44 +0000195 self.drop_caches_between_iterations = False
mblighe0cbc912010-03-11 18:03:07 +0000196 self._control_filename = control_filename
jadmanski10646442008-08-13 14:05:21 +0000197
showard75cdfee2009-06-10 17:40:41 +0000198 self.logging = logging_manager.get_logging_manager(
199 manage_stdout_and_stderr=True, redirect_fds=True)
200 subcommand.logging_manager_object = self.logging
jadmanski10646442008-08-13 14:05:21 +0000201
mbligh0d0f67d2009-11-06 03:15:03 +0000202 self.sysinfo = sysinfo.sysinfo(self.resultdir)
jadmanski043e1132008-11-19 17:10:32 +0000203 self.profilers = profilers.profilers(self)
jadmanskic09fc152008-10-15 17:56:59 +0000204
jadmanski10646442008-08-13 14:05:21 +0000205 job_data = {'label' : label, 'user' : user,
206 'hostname' : ','.join(machines),
mbligh0d0f67d2009-11-06 03:15:03 +0000207 'status_version' : str(self._STATUS_VERSION),
showard170873e2009-01-07 00:22:26 +0000208 'job_started' : str(int(time.time()))}
mbligh374f3412009-05-13 21:29:45 +0000209 if group_name:
210 job_data['host_group_name'] = group_name
jadmanski10646442008-08-13 14:05:21 +0000211
mbligh0d0f67d2009-11-06 03:15:03 +0000212 # only write these keyvals out on the first job in a resultdir
213 if 'job_started' not in utils.read_keyval(self.resultdir):
214 job_data.update(get_site_job_data(self))
215 utils.write_keyval(self.resultdir, job_data)
216
217 self._parse_job = parse_job
showardcc929362010-01-25 21:20:41 +0000218 self._using_parser = (self._parse_job and len(machines) <= 1)
mbligh0d0f67d2009-11-06 03:15:03 +0000219 self.pkgmgr = packages.PackageManager(
220 self.autodir, run_function_dargs={'timeout':600})
showard21baa452008-10-21 00:08:39 +0000221 self.num_tests_run = 0
222 self.num_tests_failed = 0
223
jadmanski550fdc22008-11-20 16:32:08 +0000224 self._register_subcommand_hooks()
225
mbligh0d0f67d2009-11-06 03:15:03 +0000226 # these components aren't usable on the server
227 self.bootloader = None
228 self.harness = None
229
jadmanski2a89dac2010-06-11 14:32:58 +0000230 # set up the status logger
jadmanski52053632010-06-11 21:08:10 +0000231 self._indenter = status_indenter()
jadmanski2a89dac2010-06-11 14:32:58 +0000232 self._logger = base_job.status_logger(
jadmanski52053632010-06-11 21:08:10 +0000233 self, self._indenter, 'status.log', 'status.log',
jadmanski2a89dac2010-06-11 14:32:58 +0000234 record_hook=server_job_record_hook(self))
235
mbligh0d0f67d2009-11-06 03:15:03 +0000236
237 @classmethod
238 def _find_base_directories(cls):
239 """
240 Determine locations of autodir, clientdir and serverdir. Assumes
241 that this file is located within serverdir and uses __file__ along
242 with relative paths to resolve the location.
243 """
244 serverdir = os.path.abspath(os.path.dirname(__file__))
245 autodir = os.path.normpath(os.path.join(serverdir, '..'))
246 clientdir = os.path.join(autodir, 'client')
247 return autodir, clientdir, serverdir
248
249
250 def _find_resultdir(self, resultdir):
251 """
252 Determine the location of resultdir. For server jobs we expect one to
253 always be explicitly passed in to __init__, so just return that.
254 """
255 if resultdir:
256 return os.path.normpath(resultdir)
257 else:
258 return None
259
jadmanski550fdc22008-11-20 16:32:08 +0000260
jadmanski2a89dac2010-06-11 14:32:58 +0000261 def _get_status_logger(self):
262 """Return a reference to the status logger."""
263 return self._logger
264
265
jadmanskie432dd22009-01-30 15:04:51 +0000266 @staticmethod
267 def _load_control_file(path):
268 f = open(path)
269 try:
270 control_file = f.read()
271 finally:
272 f.close()
273 return re.sub('\r', '', control_file)
274
275
jadmanski550fdc22008-11-20 16:32:08 +0000276 def _register_subcommand_hooks(self):
mbligh2b92b862008-11-22 13:25:32 +0000277 """
278 Register some hooks into the subcommand modules that allow us
279 to properly clean up self.hosts created in forked subprocesses.
280 """
jadmanski550fdc22008-11-20 16:32:08 +0000281 def on_fork(cmd):
282 self._existing_hosts_on_fork = set(self.hosts)
283 def on_join(cmd):
284 new_hosts = self.hosts - self._existing_hosts_on_fork
285 for host in new_hosts:
286 host.close()
287 subcommand.subcommand.register_fork_hook(on_fork)
288 subcommand.subcommand.register_join_hook(on_join)
289
jadmanski10646442008-08-13 14:05:21 +0000290
mbligh4608b002010-01-05 18:22:35 +0000291 def init_parser(self):
mbligh2b92b862008-11-22 13:25:32 +0000292 """
mbligh4608b002010-01-05 18:22:35 +0000293 Start the continuous parsing of self.resultdir. This sets up
jadmanski10646442008-08-13 14:05:21 +0000294 the database connection and inserts the basic job object into
mbligh2b92b862008-11-22 13:25:32 +0000295 the database if necessary.
296 """
mbligh4608b002010-01-05 18:22:35 +0000297 if not self._using_parser:
298 return
jadmanski10646442008-08-13 14:05:21 +0000299 # redirect parser debugging to .parse.log
mbligh4608b002010-01-05 18:22:35 +0000300 parse_log = os.path.join(self.resultdir, '.parse.log')
jadmanski10646442008-08-13 14:05:21 +0000301 parse_log = open(parse_log, 'w', 0)
302 tko_utils.redirect_parser_debugging(parse_log)
303 # create a job model object and set up the db
304 self.results_db = tko_db.db(autocommit=True)
mbligh0d0f67d2009-11-06 03:15:03 +0000305 self.parser = status_lib.parser(self._STATUS_VERSION)
mbligh4608b002010-01-05 18:22:35 +0000306 self.job_model = self.parser.make_job(self.resultdir)
jadmanski10646442008-08-13 14:05:21 +0000307 self.parser.start(self.job_model)
308 # check if a job already exists in the db and insert it if
309 # it does not
mbligh0d0f67d2009-11-06 03:15:03 +0000310 job_idx = self.results_db.find_job(self._parse_job)
jadmanski10646442008-08-13 14:05:21 +0000311 if job_idx is None:
mbligh0d0f67d2009-11-06 03:15:03 +0000312 self.results_db.insert_job(self._parse_job, self.job_model)
jadmanski10646442008-08-13 14:05:21 +0000313 else:
mbligh2b92b862008-11-22 13:25:32 +0000314 machine_idx = self.results_db.lookup_machine(self.job_model.machine)
jadmanski10646442008-08-13 14:05:21 +0000315 self.job_model.index = job_idx
316 self.job_model.machine_idx = machine_idx
317
318
319 def cleanup_parser(self):
mbligh2b92b862008-11-22 13:25:32 +0000320 """
321 This should be called after the server job is finished
jadmanski10646442008-08-13 14:05:21 +0000322 to carry out any remaining cleanup (e.g. flushing any
mbligh2b92b862008-11-22 13:25:32 +0000323 remaining test results to the results db)
324 """
mbligh0d0f67d2009-11-06 03:15:03 +0000325 if not self._using_parser:
jadmanski10646442008-08-13 14:05:21 +0000326 return
327 final_tests = self.parser.end()
328 for test in final_tests:
329 self.__insert_test(test)
mbligh0d0f67d2009-11-06 03:15:03 +0000330 self._using_parser = False
jadmanski10646442008-08-13 14:05:21 +0000331
332
333 def verify(self):
334 if not self.machines:
mbligh084bc172008-10-18 14:02:45 +0000335 raise error.AutoservError('No machines specified to verify')
mbligh0fce4112008-11-27 00:37:17 +0000336 if self.resultdir:
337 os.chdir(self.resultdir)
jadmanski10646442008-08-13 14:05:21 +0000338 try:
jadmanskicdd0c402008-09-19 21:21:31 +0000339 namespace = {'machines' : self.machines, 'job' : self,
mbligh0d0f67d2009-11-06 03:15:03 +0000340 'ssh_user' : self._ssh_user,
341 'ssh_port' : self._ssh_port,
342 'ssh_pass' : self._ssh_pass}
mbligh084bc172008-10-18 14:02:45 +0000343 self._execute_code(VERIFY_CONTROL_FILE, namespace, protect=False)
jadmanski10646442008-08-13 14:05:21 +0000344 except Exception, e:
mbligh2b92b862008-11-22 13:25:32 +0000345 msg = ('Verify failed\n' + str(e) + '\n' + traceback.format_exc())
jadmanski10646442008-08-13 14:05:21 +0000346 self.record('ABORT', None, None, msg)
347 raise
348
349
350 def repair(self, host_protection):
351 if not self.machines:
352 raise error.AutoservError('No machines specified to repair')
mbligh0fce4112008-11-27 00:37:17 +0000353 if self.resultdir:
354 os.chdir(self.resultdir)
jadmanski10646442008-08-13 14:05:21 +0000355 namespace = {'machines': self.machines, 'job': self,
mbligh0d0f67d2009-11-06 03:15:03 +0000356 'ssh_user': self._ssh_user, 'ssh_port': self._ssh_port,
357 'ssh_pass': self._ssh_pass,
jadmanski10646442008-08-13 14:05:21 +0000358 'protection_level': host_protection}
mbligh25c0b8c2009-01-24 01:44:17 +0000359
mbligh0931b0a2009-04-08 17:44:48 +0000360 self._execute_code(REPAIR_CONTROL_FILE, namespace, protect=False)
jadmanski10646442008-08-13 14:05:21 +0000361
362
363 def precheck(self):
364 """
365 perform any additional checks in derived classes.
366 """
367 pass
368
369
370 def enable_external_logging(self):
mbligh2b92b862008-11-22 13:25:32 +0000371 """
372 Start or restart external logging mechanism.
jadmanski10646442008-08-13 14:05:21 +0000373 """
374 pass
375
376
377 def disable_external_logging(self):
mbligh2b92b862008-11-22 13:25:32 +0000378 """
379 Pause or stop external logging mechanism.
jadmanski10646442008-08-13 14:05:21 +0000380 """
381 pass
382
383
384 def use_external_logging(self):
mbligh2b92b862008-11-22 13:25:32 +0000385 """
386 Return True if external logging should be used.
jadmanski10646442008-08-13 14:05:21 +0000387 """
388 return False
389
390
mbligh415dc212009-06-15 21:53:34 +0000391 def _make_parallel_wrapper(self, function, machines, log):
392 """Wrap function as appropriate for calling by parallel_simple."""
mbligh2b92b862008-11-22 13:25:32 +0000393 is_forking = not (len(machines) == 1 and self.machines == machines)
mbligh0d0f67d2009-11-06 03:15:03 +0000394 if self._parse_job and is_forking and log:
jadmanski10646442008-08-13 14:05:21 +0000395 def wrapper(machine):
mbligh0d0f67d2009-11-06 03:15:03 +0000396 self._parse_job += "/" + machine
397 self._using_parser = True
jadmanski10646442008-08-13 14:05:21 +0000398 self.machines = [machine]
mbligh0d0f67d2009-11-06 03:15:03 +0000399 self.push_execution_context(machine)
jadmanski609a5f42008-08-26 20:52:42 +0000400 os.chdir(self.resultdir)
showard2bab8f42008-11-12 18:15:22 +0000401 utils.write_keyval(self.resultdir, {"hostname": machine})
mbligh4608b002010-01-05 18:22:35 +0000402 self.init_parser()
jadmanski10646442008-08-13 14:05:21 +0000403 result = function(machine)
404 self.cleanup_parser()
405 return result
jadmanski4dd1a002008-09-05 20:27:30 +0000406 elif len(machines) > 1 and log:
jadmanski10646442008-08-13 14:05:21 +0000407 def wrapper(machine):
mbligh0d0f67d2009-11-06 03:15:03 +0000408 self.push_execution_context(machine)
jadmanski609a5f42008-08-26 20:52:42 +0000409 os.chdir(self.resultdir)
mbligh838d82d2009-03-11 17:14:31 +0000410 machine_data = {'hostname' : machine,
mbligh0d0f67d2009-11-06 03:15:03 +0000411 'status_version' : str(self._STATUS_VERSION)}
mbligh838d82d2009-03-11 17:14:31 +0000412 utils.write_keyval(self.resultdir, machine_data)
jadmanski10646442008-08-13 14:05:21 +0000413 result = function(machine)
414 return result
415 else:
416 wrapper = function
mbligh415dc212009-06-15 21:53:34 +0000417 return wrapper
418
419
420 def parallel_simple(self, function, machines, log=True, timeout=None,
421 return_results=False):
422 """
423 Run 'function' using parallel_simple, with an extra wrapper to handle
424 the necessary setup for continuous parsing, if possible. If continuous
425 parsing is already properly initialized then this should just work.
426
427 @param function: A callable to run in parallel given each machine.
428 @param machines: A list of machine names to be passed one per subcommand
429 invocation of function.
430 @param log: If True, output will be written to output in a subdirectory
431 named after each machine.
432 @param timeout: Seconds after which the function call should timeout.
433 @param return_results: If True instead of an AutoServError being raised
434 on any error a list of the results|exceptions from the function
435 called on each arg is returned. [default: False]
436
437 @raises error.AutotestError: If any of the functions failed.
438 """
439 wrapper = self._make_parallel_wrapper(function, machines, log)
440 return subcommand.parallel_simple(wrapper, machines,
441 log=log, timeout=timeout,
442 return_results=return_results)
443
444
445 def parallel_on_machines(self, function, machines, timeout=None):
446 """
showardcd5fac42009-07-06 20:19:43 +0000447 @param function: Called in parallel with one machine as its argument.
mbligh415dc212009-06-15 21:53:34 +0000448 @param machines: A list of machines to call function(machine) on.
449 @param timeout: Seconds after which the function call should timeout.
450
451 @returns A list of machines on which function(machine) returned
452 without raising an exception.
453 """
showardcd5fac42009-07-06 20:19:43 +0000454 results = self.parallel_simple(function, machines, timeout=timeout,
mbligh415dc212009-06-15 21:53:34 +0000455 return_results=True)
456 success_machines = []
457 for result, machine in itertools.izip(results, machines):
458 if not isinstance(result, Exception):
459 success_machines.append(machine)
460 return success_machines
jadmanski10646442008-08-13 14:05:21 +0000461
462
mbligh0d0f67d2009-11-06 03:15:03 +0000463 _USE_TEMP_DIR = object()
mbligh2b92b862008-11-22 13:25:32 +0000464 def run(self, cleanup=False, install_before=False, install_after=False,
jadmanskie432dd22009-01-30 15:04:51 +0000465 collect_crashdumps=True, namespace={}, control=None,
jadmanskidef0c3c2009-03-25 20:07:10 +0000466 control_file_dir=None, only_collect_crashinfo=False):
jadmanskifb9c0fa2009-04-29 17:39:16 +0000467 # for a normal job, make sure the uncollected logs file exists
468 # for a crashinfo-only run it should already exist, bail out otherwise
jadmanski648c39f2010-03-19 17:38:01 +0000469 created_uncollected_logs = False
mbligh0d0f67d2009-11-06 03:15:03 +0000470 if self.resultdir and not os.path.exists(self._uncollected_log_file):
jadmanskifb9c0fa2009-04-29 17:39:16 +0000471 if only_collect_crashinfo:
472 # if this is a crashinfo-only run, and there were no existing
473 # uncollected logs, just bail out early
474 logging.info("No existing uncollected logs, "
475 "skipping crashinfo collection")
476 return
477 else:
mbligh0d0f67d2009-11-06 03:15:03 +0000478 log_file = open(self._uncollected_log_file, "w")
jadmanskifb9c0fa2009-04-29 17:39:16 +0000479 pickle.dump([], log_file)
480 log_file.close()
jadmanski648c39f2010-03-19 17:38:01 +0000481 created_uncollected_logs = True
jadmanskifb9c0fa2009-04-29 17:39:16 +0000482
jadmanski10646442008-08-13 14:05:21 +0000483 # use a copy so changes don't affect the original dictionary
484 namespace = namespace.copy()
485 machines = self.machines
jadmanskie432dd22009-01-30 15:04:51 +0000486 if control is None:
jadmanski02a3ba22009-11-13 20:47:27 +0000487 if self.control is None:
488 control = ''
489 else:
490 control = self._load_control_file(self.control)
jadmanskie432dd22009-01-30 15:04:51 +0000491 if control_file_dir is None:
492 control_file_dir = self.resultdir
jadmanski10646442008-08-13 14:05:21 +0000493
494 self.aborted = False
495 namespace['machines'] = machines
jadmanski808f4b12010-04-09 22:30:31 +0000496 namespace['args'] = self.args
jadmanski10646442008-08-13 14:05:21 +0000497 namespace['job'] = self
mbligh0d0f67d2009-11-06 03:15:03 +0000498 namespace['ssh_user'] = self._ssh_user
499 namespace['ssh_port'] = self._ssh_port
500 namespace['ssh_pass'] = self._ssh_pass
jadmanski10646442008-08-13 14:05:21 +0000501 test_start_time = int(time.time())
502
mbligh80e1eba2008-11-19 00:26:18 +0000503 if self.resultdir:
504 os.chdir(self.resultdir)
jadmanski779bd292009-03-19 17:33:33 +0000505 # touch status.log so that the parser knows a job is running here
jadmanski382303a2009-04-21 19:53:39 +0000506 open(self.get_status_log_path(), 'a').close()
mbligh80e1eba2008-11-19 00:26:18 +0000507 self.enable_external_logging()
jadmanskie432dd22009-01-30 15:04:51 +0000508
jadmanskicdd0c402008-09-19 21:21:31 +0000509 collect_crashinfo = True
mblighaebe3b62008-12-22 14:45:40 +0000510 temp_control_file_dir = None
jadmanski10646442008-08-13 14:05:21 +0000511 try:
showardcf8d4922009-10-14 16:08:39 +0000512 try:
513 if install_before and machines:
514 self._execute_code(INSTALL_CONTROL_FILE, namespace)
jadmanskie432dd22009-01-30 15:04:51 +0000515
showardcf8d4922009-10-14 16:08:39 +0000516 if only_collect_crashinfo:
517 return
518
jadmanskidef0c3c2009-03-25 20:07:10 +0000519 # determine the dir to write the control files to
520 cfd_specified = (control_file_dir
mbligh0d0f67d2009-11-06 03:15:03 +0000521 and control_file_dir is not self._USE_TEMP_DIR)
jadmanskidef0c3c2009-03-25 20:07:10 +0000522 if cfd_specified:
523 temp_control_file_dir = None
524 else:
525 temp_control_file_dir = tempfile.mkdtemp(
526 suffix='temp_control_file_dir')
527 control_file_dir = temp_control_file_dir
528 server_control_file = os.path.join(control_file_dir,
mblighe0cbc912010-03-11 18:03:07 +0000529 self._control_filename)
jadmanskidef0c3c2009-03-25 20:07:10 +0000530 client_control_file = os.path.join(control_file_dir,
531 CLIENT_CONTROL_FILENAME)
mbligh0d0f67d2009-11-06 03:15:03 +0000532 if self._client:
jadmanskidef0c3c2009-03-25 20:07:10 +0000533 namespace['control'] = control
534 utils.open_write_close(client_control_file, control)
mblighfeac0102009-04-28 18:31:12 +0000535 shutil.copyfile(CLIENT_WRAPPER_CONTROL_FILE,
536 server_control_file)
jadmanskidef0c3c2009-03-25 20:07:10 +0000537 else:
538 utils.open_write_close(server_control_file, control)
mbligh26f0d882009-06-22 18:30:01 +0000539 logging.info("Processing control file")
jadmanskidef0c3c2009-03-25 20:07:10 +0000540 self._execute_code(server_control_file, namespace)
mbligh26f0d882009-06-22 18:30:01 +0000541 logging.info("Finished processing control file")
jadmanski10646442008-08-13 14:05:21 +0000542
jadmanskidef0c3c2009-03-25 20:07:10 +0000543 # no error occured, so we don't need to collect crashinfo
544 collect_crashinfo = False
showardcf8d4922009-10-14 16:08:39 +0000545 except:
546 try:
547 logging.exception(
548 'Exception escaped control file, job aborting:')
549 except:
550 pass # don't let logging exceptions here interfere
551 raise
jadmanski10646442008-08-13 14:05:21 +0000552 finally:
mblighaebe3b62008-12-22 14:45:40 +0000553 if temp_control_file_dir:
jadmanskie432dd22009-01-30 15:04:51 +0000554 # Clean up temp directory used for copies of the control files
mblighaebe3b62008-12-22 14:45:40 +0000555 try:
556 shutil.rmtree(temp_control_file_dir)
557 except Exception, e:
mblighe7d9c602009-07-02 19:02:33 +0000558 logging.warn('Could not remove temp directory %s: %s',
559 temp_control_file_dir, e)
jadmanskie432dd22009-01-30 15:04:51 +0000560
jadmanskicdd0c402008-09-19 21:21:31 +0000561 if machines and (collect_crashdumps or collect_crashinfo):
jadmanski10646442008-08-13 14:05:21 +0000562 namespace['test_start_time'] = test_start_time
jadmanskicdd0c402008-09-19 21:21:31 +0000563 if collect_crashinfo:
mbligh084bc172008-10-18 14:02:45 +0000564 # includes crashdumps
565 self._execute_code(CRASHINFO_CONTROL_FILE, namespace)
jadmanskicdd0c402008-09-19 21:21:31 +0000566 else:
mbligh084bc172008-10-18 14:02:45 +0000567 self._execute_code(CRASHDUMPS_CONTROL_FILE, namespace)
jadmanski648c39f2010-03-19 17:38:01 +0000568 if self._uncollected_log_file and created_uncollected_logs:
mbligh0d0f67d2009-11-06 03:15:03 +0000569 os.remove(self._uncollected_log_file)
jadmanski10646442008-08-13 14:05:21 +0000570 self.disable_external_logging()
showard45ae8192008-11-05 19:32:53 +0000571 if cleanup and machines:
572 self._execute_code(CLEANUP_CONTROL_FILE, namespace)
jadmanski10646442008-08-13 14:05:21 +0000573 if install_after and machines:
mbligh084bc172008-10-18 14:02:45 +0000574 self._execute_code(INSTALL_CONTROL_FILE, namespace)
jadmanski10646442008-08-13 14:05:21 +0000575
576
577 def run_test(self, url, *args, **dargs):
mbligh2b92b862008-11-22 13:25:32 +0000578 """
579 Summon a test object and run it.
jadmanski10646442008-08-13 14:05:21 +0000580
581 tag
582 tag to add to testname
583 url
584 url of the test to run
585 """
mblighfc3da5b2010-01-06 18:37:22 +0000586 group, testname = self.pkgmgr.get_package_name(url, 'test')
587 testname, subdir, tag = self._build_tagged_test_name(testname, dargs)
588 outputdir = self._make_test_outputdir(subdir)
jadmanski10646442008-08-13 14:05:21 +0000589
590 def group_func():
591 try:
592 test.runtest(self, url, tag, args, dargs)
593 except error.TestBaseException, e:
594 self.record(e.exit_status, subdir, testname, str(e))
595 raise
596 except Exception, e:
597 info = str(e) + "\n" + traceback.format_exc()
598 self.record('FAIL', subdir, testname, info)
599 raise
600 else:
mbligh2b92b862008-11-22 13:25:32 +0000601 self.record('GOOD', subdir, testname, 'completed successfully')
jadmanskide292df2008-08-26 20:51:14 +0000602
603 result, exc_info = self._run_group(testname, subdir, group_func)
604 if exc_info and isinstance(exc_info[1], error.TestBaseException):
605 return False
606 elif exc_info:
607 raise exc_info[0], exc_info[1], exc_info[2]
608 else:
609 return True
jadmanski10646442008-08-13 14:05:21 +0000610
611
612 def _run_group(self, name, subdir, function, *args, **dargs):
613 """\
614 Underlying method for running something inside of a group.
615 """
jadmanskide292df2008-08-26 20:51:14 +0000616 result, exc_info = None, None
jadmanski10646442008-08-13 14:05:21 +0000617 try:
618 self.record('START', subdir, name)
jadmanski52053632010-06-11 21:08:10 +0000619 result = function(*args, **dargs)
jadmanski10646442008-08-13 14:05:21 +0000620 except error.TestBaseException, e:
jadmanskib88d6dc2009-01-10 00:33:18 +0000621 self.record("END %s" % e.exit_status, subdir, name)
jadmanskide292df2008-08-26 20:51:14 +0000622 exc_info = sys.exc_info()
jadmanski10646442008-08-13 14:05:21 +0000623 except Exception, e:
624 err_msg = str(e) + '\n'
625 err_msg += traceback.format_exc()
626 self.record('END ABORT', subdir, name, err_msg)
627 raise error.JobError(name + ' failed\n' + traceback.format_exc())
628 else:
629 self.record('END GOOD', subdir, name)
630
jadmanskide292df2008-08-26 20:51:14 +0000631 return result, exc_info
jadmanski10646442008-08-13 14:05:21 +0000632
633
634 def run_group(self, function, *args, **dargs):
635 """\
636 function:
637 subroutine to run
638 *args:
639 arguments for the function
640 """
641
642 name = function.__name__
643
644 # Allow the tag for the group to be specified.
645 tag = dargs.pop('tag', None)
646 if tag:
647 name = tag
648
jadmanskide292df2008-08-26 20:51:14 +0000649 return self._run_group(name, None, function, *args, **dargs)[0]
jadmanski10646442008-08-13 14:05:21 +0000650
651
652 def run_reboot(self, reboot_func, get_kernel_func):
653 """\
654 A specialization of run_group meant specifically for handling
655 a reboot. Includes support for capturing the kernel version
656 after the reboot.
657
658 reboot_func: a function that carries out the reboot
659
660 get_kernel_func: a function that returns a string
661 representing the kernel version.
662 """
jadmanski10646442008-08-13 14:05:21 +0000663 try:
664 self.record('START', None, 'reboot')
jadmanski10646442008-08-13 14:05:21 +0000665 reboot_func()
666 except Exception, e:
jadmanski10646442008-08-13 14:05:21 +0000667 err_msg = str(e) + '\n' + traceback.format_exc()
668 self.record('END FAIL', None, 'reboot', err_msg)
jadmanski4b51d542009-04-08 14:17:16 +0000669 raise
jadmanski10646442008-08-13 14:05:21 +0000670 else:
671 kernel = get_kernel_func()
jadmanski10646442008-08-13 14:05:21 +0000672 self.record('END GOOD', None, 'reboot',
673 optional_fields={"kernel": kernel})
674
675
jadmanskie432dd22009-01-30 15:04:51 +0000676 def run_control(self, path):
677 """Execute a control file found at path (relative to the autotest
678 path). Intended for executing a control file within a control file,
679 not for running the top-level job control file."""
680 path = os.path.join(self.autodir, path)
681 control_file = self._load_control_file(path)
mbligh0d0f67d2009-11-06 03:15:03 +0000682 self.run(control=control_file, control_file_dir=self._USE_TEMP_DIR)
jadmanskie432dd22009-01-30 15:04:51 +0000683
684
jadmanskic09fc152008-10-15 17:56:59 +0000685 def add_sysinfo_command(self, command, logfile=None, on_every_test=False):
mbligh4395bbd2009-03-25 19:34:17 +0000686 self._add_sysinfo_loggable(sysinfo.command(command, logf=logfile),
jadmanskic09fc152008-10-15 17:56:59 +0000687 on_every_test)
688
689
690 def add_sysinfo_logfile(self, file, on_every_test=False):
691 self._add_sysinfo_loggable(sysinfo.logfile(file), on_every_test)
692
693
694 def _add_sysinfo_loggable(self, loggable, on_every_test):
695 if on_every_test:
696 self.sysinfo.test_loggables.add(loggable)
697 else:
698 self.sysinfo.boot_loggables.add(loggable)
699
700
jadmanski10646442008-08-13 14:05:21 +0000701 def _read_warnings(self):
jadmanskif37df842009-02-11 00:03:26 +0000702 """Poll all the warning loggers and extract any new warnings that have
703 been logged. If the warnings belong to a category that is currently
704 disabled, this method will discard them and they will no longer be
705 retrievable.
706
707 Returns a list of (timestamp, message) tuples, where timestamp is an
708 integer epoch timestamp."""
jadmanski10646442008-08-13 14:05:21 +0000709 warnings = []
710 while True:
711 # pull in a line of output from every logger that has
712 # output ready to be read
mbligh2b92b862008-11-22 13:25:32 +0000713 loggers, _, _ = select.select(self.warning_loggers, [], [], 0)
jadmanski10646442008-08-13 14:05:21 +0000714 closed_loggers = set()
715 for logger in loggers:
716 line = logger.readline()
717 # record any broken pipes (aka line == empty)
718 if len(line) == 0:
719 closed_loggers.add(logger)
720 continue
jadmanskif37df842009-02-11 00:03:26 +0000721 # parse out the warning
722 timestamp, msgtype, msg = line.split('\t', 2)
723 timestamp = int(timestamp)
724 # if the warning is valid, add it to the results
725 if self.warning_manager.is_valid(timestamp, msgtype):
726 warnings.append((timestamp, msg.strip()))
jadmanski10646442008-08-13 14:05:21 +0000727
728 # stop listening to loggers that are closed
729 self.warning_loggers -= closed_loggers
730
731 # stop if none of the loggers have any output left
732 if not loggers:
733 break
734
735 # sort into timestamp order
736 warnings.sort()
737 return warnings
738
739
showardcc929362010-01-25 21:20:41 +0000740 def _unique_subdirectory(self, base_subdirectory_name):
741 """Compute a unique results subdirectory based on the given name.
742
743 Appends base_subdirectory_name with a number as necessary to find a
744 directory name that doesn't already exist.
745 """
746 subdirectory = base_subdirectory_name
747 counter = 1
748 while os.path.exists(os.path.join(self.resultdir, subdirectory)):
749 subdirectory = base_subdirectory_name + '.' + str(counter)
750 counter += 1
751 return subdirectory
752
753
jadmanski52053632010-06-11 21:08:10 +0000754 def get_record_context(self):
755 """Returns an object representing the current job.record context.
756
757 The object returned is an opaque object with a 0-arg restore method
758 which can be called to restore the job.record context (i.e. indentation)
759 to the current level. The intention is that it should be used when
760 something external which generate job.record calls (e.g. an autotest
761 client) can fail catastrophically and the server job record state
762 needs to be reset to its original "known good" state.
763
764 @return: A context object with a 0-arg restore() method."""
765 return self._indenter.get_context()
766
767
showardcc929362010-01-25 21:20:41 +0000768 def record_summary(self, status_code, test_name, reason='', attributes=None,
769 distinguishing_attributes=(), child_test_ids=None):
770 """Record a summary test result.
771
772 @param status_code: status code string, see
773 common_lib.log.is_valid_status()
774 @param test_name: name of the test
775 @param reason: (optional) string providing detailed reason for test
776 outcome
777 @param attributes: (optional) dict of string keyvals to associate with
778 this result
779 @param distinguishing_attributes: (optional) list of attribute names
780 that should be used to distinguish identically-named test
781 results. These attributes should be present in the attributes
782 parameter. This is used to generate user-friendly subdirectory
783 names.
784 @param child_test_ids: (optional) list of test indices for test results
785 used in generating this result.
786 """
787 subdirectory_name_parts = [test_name]
788 for attribute in distinguishing_attributes:
789 assert attributes
790 assert attribute in attributes, '%s not in %s' % (attribute,
791 attributes)
792 subdirectory_name_parts.append(attributes[attribute])
793 base_subdirectory_name = '.'.join(subdirectory_name_parts)
794
795 subdirectory = self._unique_subdirectory(base_subdirectory_name)
796 subdirectory_path = os.path.join(self.resultdir, subdirectory)
797 os.mkdir(subdirectory_path)
798
799 self.record(status_code, subdirectory, test_name,
800 status=reason, optional_fields={'is_summary': True})
801
802 if attributes:
803 utils.write_keyval(subdirectory_path, attributes)
804
805 if child_test_ids:
806 ids_string = ','.join(str(test_id) for test_id in child_test_ids)
807 summary_data = {'child_test_ids': ids_string}
808 utils.write_keyval(os.path.join(subdirectory_path, 'summary_data'),
809 summary_data)
810
811
jadmanski16a7ff72009-04-01 18:19:53 +0000812 def disable_warnings(self, warning_type):
jadmanskif37df842009-02-11 00:03:26 +0000813 self.warning_manager.disable_warnings(warning_type)
jadmanski16a7ff72009-04-01 18:19:53 +0000814 self.record("INFO", None, None,
815 "disabling %s warnings" % warning_type,
816 {"warnings.disable": warning_type})
jadmanskif37df842009-02-11 00:03:26 +0000817
818
jadmanski16a7ff72009-04-01 18:19:53 +0000819 def enable_warnings(self, warning_type):
jadmanskif37df842009-02-11 00:03:26 +0000820 self.warning_manager.enable_warnings(warning_type)
jadmanski16a7ff72009-04-01 18:19:53 +0000821 self.record("INFO", None, None,
822 "enabling %s warnings" % warning_type,
823 {"warnings.enable": warning_type})
jadmanskif37df842009-02-11 00:03:26 +0000824
825
jadmanski779bd292009-03-19 17:33:33 +0000826 def get_status_log_path(self, subdir=None):
827 """Return the path to the job status log.
828
829 @param subdir - Optional paramter indicating that you want the path
830 to a subdirectory status log.
831
832 @returns The path where the status log should be.
833 """
mbligh210bae62009-04-01 18:33:13 +0000834 if self.resultdir:
835 if subdir:
836 return os.path.join(self.resultdir, subdir, "status.log")
837 else:
838 return os.path.join(self.resultdir, "status.log")
jadmanski779bd292009-03-19 17:33:33 +0000839 else:
mbligh210bae62009-04-01 18:33:13 +0000840 return None
jadmanski779bd292009-03-19 17:33:33 +0000841
842
jadmanski6bb32d72009-03-19 20:25:24 +0000843 def _update_uncollected_logs_list(self, update_func):
844 """Updates the uncollected logs list in a multi-process safe manner.
845
846 @param update_func - a function that updates the list of uncollected
847 logs. Should take one parameter, the list to be updated.
848 """
mbligh0d0f67d2009-11-06 03:15:03 +0000849 if self._uncollected_log_file:
850 log_file = open(self._uncollected_log_file, "r+")
mbligha788dc42009-03-26 21:10:16 +0000851 fcntl.flock(log_file, fcntl.LOCK_EX)
jadmanski6bb32d72009-03-19 20:25:24 +0000852 try:
853 uncollected_logs = pickle.load(log_file)
854 update_func(uncollected_logs)
855 log_file.seek(0)
856 log_file.truncate()
857 pickle.dump(uncollected_logs, log_file)
jadmanski3bff9092009-04-22 18:09:47 +0000858 log_file.flush()
jadmanski6bb32d72009-03-19 20:25:24 +0000859 finally:
860 fcntl.flock(log_file, fcntl.LOCK_UN)
861 log_file.close()
862
863
864 def add_client_log(self, hostname, remote_path, local_path):
865 """Adds a new set of client logs to the list of uncollected logs,
866 to allow for future log recovery.
867
868 @param host - the hostname of the machine holding the logs
869 @param remote_path - the directory on the remote machine holding logs
870 @param local_path - the local directory to copy the logs into
871 """
872 def update_func(logs_list):
873 logs_list.append((hostname, remote_path, local_path))
874 self._update_uncollected_logs_list(update_func)
875
876
877 def remove_client_log(self, hostname, remote_path, local_path):
878 """Removes a set of client logs from the list of uncollected logs,
879 to allow for future log recovery.
880
881 @param host - the hostname of the machine holding the logs
882 @param remote_path - the directory on the remote machine holding logs
883 @param local_path - the local directory to copy the logs into
884 """
885 def update_func(logs_list):
886 logs_list.remove((hostname, remote_path, local_path))
887 self._update_uncollected_logs_list(update_func)
888
889
mbligh0d0f67d2009-11-06 03:15:03 +0000890 def get_client_logs(self):
891 """Retrieves the list of uncollected logs, if it exists.
892
893 @returns A list of (host, remote_path, local_path) tuples. Returns
894 an empty list if no uncollected logs file exists.
895 """
896 log_exists = (self._uncollected_log_file and
897 os.path.exists(self._uncollected_log_file))
898 if log_exists:
899 return pickle.load(open(self._uncollected_log_file))
900 else:
901 return []
902
903
mbligh084bc172008-10-18 14:02:45 +0000904 def _fill_server_control_namespace(self, namespace, protect=True):
mbligh2b92b862008-11-22 13:25:32 +0000905 """
906 Prepare a namespace to be used when executing server control files.
mbligh084bc172008-10-18 14:02:45 +0000907
908 This sets up the control file API by importing modules and making them
909 available under the appropriate names within namespace.
910
911 For use by _execute_code().
912
913 Args:
914 namespace: The namespace dictionary to fill in.
915 protect: Boolean. If True (the default) any operation that would
916 clobber an existing entry in namespace will cause an error.
917 Raises:
918 error.AutoservError: When a name would be clobbered by import.
919 """
920 def _import_names(module_name, names=()):
mbligh2b92b862008-11-22 13:25:32 +0000921 """
922 Import a module and assign named attributes into namespace.
mbligh084bc172008-10-18 14:02:45 +0000923
924 Args:
925 module_name: The string module name.
926 names: A limiting list of names to import from module_name. If
927 empty (the default), all names are imported from the module
928 similar to a "from foo.bar import *" statement.
929 Raises:
930 error.AutoservError: When a name being imported would clobber
931 a name already in namespace.
932 """
933 module = __import__(module_name, {}, {}, names)
934
935 # No names supplied? Import * from the lowest level module.
936 # (Ugh, why do I have to implement this part myself?)
937 if not names:
938 for submodule_name in module_name.split('.')[1:]:
939 module = getattr(module, submodule_name)
940 if hasattr(module, '__all__'):
941 names = getattr(module, '__all__')
942 else:
943 names = dir(module)
944
945 # Install each name into namespace, checking to make sure it
946 # doesn't override anything that already exists.
947 for name in names:
948 # Check for conflicts to help prevent future problems.
949 if name in namespace and protect:
950 if namespace[name] is not getattr(module, name):
951 raise error.AutoservError('importing name '
952 '%s from %s %r would override %r' %
953 (name, module_name, getattr(module, name),
954 namespace[name]))
955 else:
956 # Encourage cleanliness and the use of __all__ for a
957 # more concrete API with less surprises on '*' imports.
958 warnings.warn('%s (%r) being imported from %s for use '
959 'in server control files is not the '
960 'first occurrance of that import.' %
961 (name, namespace[name], module_name))
962
963 namespace[name] = getattr(module, name)
964
965
966 # This is the equivalent of prepending a bunch of import statements to
967 # the front of the control script.
mbligha2b07dd2009-06-22 18:26:13 +0000968 namespace.update(os=os, sys=sys, logging=logging)
mbligh084bc172008-10-18 14:02:45 +0000969 _import_names('autotest_lib.server',
970 ('hosts', 'autotest', 'kvm', 'git', 'standalone_profiler',
971 'source_kernel', 'rpm_kernel', 'deb_kernel', 'git_kernel'))
972 _import_names('autotest_lib.server.subcommand',
973 ('parallel', 'parallel_simple', 'subcommand'))
974 _import_names('autotest_lib.server.utils',
975 ('run', 'get_tmp_dir', 'sh_escape', 'parse_machine'))
976 _import_names('autotest_lib.client.common_lib.error')
977 _import_names('autotest_lib.client.common_lib.barrier', ('barrier',))
978
979 # Inject ourself as the job object into other classes within the API.
980 # (Yuck, this injection is a gross thing be part of a public API. -gps)
981 #
982 # XXX Base & SiteAutotest do not appear to use .job. Who does?
983 namespace['autotest'].Autotest.job = self
984 # server.hosts.base_classes.Host uses .job.
985 namespace['hosts'].Host.job = self
986
987
988 def _execute_code(self, code_file, namespace, protect=True):
mbligh2b92b862008-11-22 13:25:32 +0000989 """
990 Execute code using a copy of namespace as a server control script.
mbligh084bc172008-10-18 14:02:45 +0000991
992 Unless protect_namespace is explicitly set to False, the dict will not
993 be modified.
994
995 Args:
996 code_file: The filename of the control file to execute.
997 namespace: A dict containing names to make available during execution.
998 protect: Boolean. If True (the default) a copy of the namespace dict
999 is used during execution to prevent the code from modifying its
1000 contents outside of this function. If False the raw dict is
1001 passed in and modifications will be allowed.
1002 """
1003 if protect:
1004 namespace = namespace.copy()
1005 self._fill_server_control_namespace(namespace, protect=protect)
1006 # TODO: Simplify and get rid of the special cases for only 1 machine.
showard3e66e8c2008-10-27 19:20:51 +00001007 if len(self.machines) > 1:
mbligh084bc172008-10-18 14:02:45 +00001008 machines_text = '\n'.join(self.machines) + '\n'
1009 # Only rewrite the file if it does not match our machine list.
1010 try:
1011 machines_f = open(MACHINES_FILENAME, 'r')
1012 existing_machines_text = machines_f.read()
1013 machines_f.close()
1014 except EnvironmentError:
1015 existing_machines_text = None
1016 if machines_text != existing_machines_text:
1017 utils.open_write_close(MACHINES_FILENAME, machines_text)
1018 execfile(code_file, namespace, namespace)
jadmanski10646442008-08-13 14:05:21 +00001019
1020
jadmanskie29d0e42010-06-17 16:06:52 +00001021 def _parse_status(self, new_line):
mbligh0d0f67d2009-11-06 03:15:03 +00001022 if not self._using_parser:
jadmanski10646442008-08-13 14:05:21 +00001023 return
jadmanskie29d0e42010-06-17 16:06:52 +00001024 new_tests = self.parser.process_lines([new_line])
jadmanski10646442008-08-13 14:05:21 +00001025 for test in new_tests:
1026 self.__insert_test(test)
1027
1028
1029 def __insert_test(self, test):
mbligh2b92b862008-11-22 13:25:32 +00001030 """
1031 An internal method to insert a new test result into the
jadmanski10646442008-08-13 14:05:21 +00001032 database. This method will not raise an exception, even if an
1033 error occurs during the insert, to avoid failing a test
1034 simply because of unexpected database issues."""
showard21baa452008-10-21 00:08:39 +00001035 self.num_tests_run += 1
1036 if status_lib.is_worse_than_or_equal_to(test.status, 'FAIL'):
1037 self.num_tests_failed += 1
jadmanski10646442008-08-13 14:05:21 +00001038 try:
1039 self.results_db.insert_test(self.job_model, test)
1040 except Exception:
1041 msg = ("WARNING: An unexpected error occured while "
1042 "inserting test results into the database. "
1043 "Ignoring error.\n" + traceback.format_exc())
1044 print >> sys.stderr, msg
1045
mblighcaa62c22008-04-07 21:51:17 +00001046
mblighfc3da5b2010-01-06 18:37:22 +00001047 def preprocess_client_state(self):
1048 """
1049 Produce a state file for initializing the state of a client job.
1050
1051 Creates a new client state file with all the current server state, as
1052 well as some pre-set client state.
1053
1054 @returns The path of the file the state was written into.
1055 """
1056 # initialize the sysinfo state
1057 self._state.set('client', 'sysinfo', self.sysinfo.serialize())
1058
1059 # dump the state out to a tempfile
1060 fd, file_path = tempfile.mkstemp(dir=self.tmpdir)
1061 os.close(fd)
mbligha2c99492010-01-27 22:59:50 +00001062
1063 # write_to_file doesn't need locking, we exclusively own file_path
mblighfc3da5b2010-01-06 18:37:22 +00001064 self._state.write_to_file(file_path)
1065 return file_path
1066
1067
1068 def postprocess_client_state(self, state_path):
1069 """
1070 Update the state of this job with the state from a client job.
1071
1072 Updates the state of the server side of a job with the final state
1073 of a client job that was run. Updates the non-client-specific state,
1074 pulls in some specific bits from the client-specific state, and then
1075 discards the rest. Removes the state file afterwards
1076
1077 @param state_file A path to the state file from the client.
1078 """
1079 # update the on-disk state
mblighfc3da5b2010-01-06 18:37:22 +00001080 try:
jadmanskib6e7bdb2010-04-13 16:00:39 +00001081 self._state.read_from_file(state_path)
mblighfc3da5b2010-01-06 18:37:22 +00001082 os.remove(state_path)
mbligha2c99492010-01-27 22:59:50 +00001083 except OSError, e:
mblighfc3da5b2010-01-06 18:37:22 +00001084 # ignore file-not-found errors
1085 if e.errno != errno.ENOENT:
1086 raise
jadmanskib6e7bdb2010-04-13 16:00:39 +00001087 else:
1088 logging.debug('Client state file %s not found', state_path)
mblighfc3da5b2010-01-06 18:37:22 +00001089
1090 # update the sysinfo state
1091 if self._state.has('client', 'sysinfo'):
1092 self.sysinfo.deserialize(self._state.get('client', 'sysinfo'))
1093
1094 # drop all the client-specific state
1095 self._state.discard_namespace('client')
1096
1097
mbligh0a883702010-04-21 01:58:34 +00001098 def clear_all_known_hosts(self):
1099 """Clears known hosts files for all AbstractSSHHosts."""
1100 for host in self.hosts:
1101 if isinstance(host, abstract_ssh.AbstractSSHHost):
1102 host.clear_known_hosts()
1103
1104
mbligha7007722009-01-13 00:37:11 +00001105site_server_job = utils.import_site_class(
1106 __file__, "autotest_lib.server.site_server_job", "site_server_job",
1107 base_server_job)
jadmanski0afbb632008-06-06 21:10:57 +00001108
mbligh0a8c3322009-04-28 18:32:19 +00001109class server_job(site_server_job):
jadmanski0afbb632008-06-06 21:10:57 +00001110 pass
jadmanskif37df842009-02-11 00:03:26 +00001111
1112
1113class warning_manager(object):
1114 """Class for controlling warning logs. Manages the enabling and disabling
1115 of warnings."""
1116 def __init__(self):
1117 # a map of warning types to a list of disabled time intervals
1118 self.disabled_warnings = {}
1119
1120
1121 def is_valid(self, timestamp, warning_type):
1122 """Indicates if a warning (based on the time it occured and its type)
1123 is a valid warning. A warning is considered "invalid" if this type of
1124 warning was marked as "disabled" at the time the warning occured."""
1125 disabled_intervals = self.disabled_warnings.get(warning_type, [])
1126 for start, end in disabled_intervals:
1127 if timestamp >= start and (end is None or timestamp < end):
1128 return False
1129 return True
1130
1131
1132 def disable_warnings(self, warning_type, current_time_func=time.time):
1133 """As of now, disables all further warnings of this type."""
1134 intervals = self.disabled_warnings.setdefault(warning_type, [])
1135 if not intervals or intervals[-1][1] is not None:
jadmanski16a7ff72009-04-01 18:19:53 +00001136 intervals.append((int(current_time_func()), None))
jadmanskif37df842009-02-11 00:03:26 +00001137
1138
1139 def enable_warnings(self, warning_type, current_time_func=time.time):
1140 """As of now, enables all further warnings of this type."""
1141 intervals = self.disabled_warnings.get(warning_type, [])
1142 if intervals and intervals[-1][1] is None:
jadmanski16a7ff72009-04-01 18:19:53 +00001143 intervals[-1] = (intervals[-1][0], int(current_time_func()))