blob: 0e9a96df7261fea6670422c07123f88c339e33f9 [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)
124
125
mbligh0d0f67d2009-11-06 03:15:03 +0000126class base_server_job(base_job.base_job):
127 """The server-side concrete implementation of base_job.
jadmanski10646442008-08-13 14:05:21 +0000128
mbligh0d0f67d2009-11-06 03:15:03 +0000129 Optional properties provided by this implementation:
130 serverdir
131 conmuxdir
132
133 num_tests_run
134 num_tests_failed
135
136 warning_manager
137 warning_loggers
jadmanski10646442008-08-13 14:05:21 +0000138 """
139
mbligh0d0f67d2009-11-06 03:15:03 +0000140 _STATUS_VERSION = 1
jadmanski10646442008-08-13 14:05:21 +0000141
142 def __init__(self, control, args, resultdir, label, user, machines,
143 client=False, parse_job='',
mbligh374f3412009-05-13 21:29:45 +0000144 ssh_user='root', ssh_port=22, ssh_pass='',
mblighe0cbc912010-03-11 18:03:07 +0000145 group_name='', tag='',
146 control_filename=SERVER_CONTROL_FILENAME):
jadmanski10646442008-08-13 14:05:21 +0000147 """
mbligh374f3412009-05-13 21:29:45 +0000148 Create a server side job object.
mblighb5dac432008-11-27 00:38:44 +0000149
mblighe7d9c602009-07-02 19:02:33 +0000150 @param control: The pathname of the control file.
151 @param args: Passed to the control file.
152 @param resultdir: Where to throw the results.
153 @param label: Description of the job.
154 @param user: Username for the job (email address).
155 @param client: True if this is a client-side control file.
156 @param parse_job: string, if supplied it is the job execution tag that
157 the results will be passed through to the TKO parser with.
158 @param ssh_user: The SSH username. [root]
159 @param ssh_port: The SSH port number. [22]
160 @param ssh_pass: The SSH passphrase, if needed.
161 @param group_name: If supplied, this will be written out as
mbligh374f3412009-05-13 21:29:45 +0000162 host_group_name in the keyvals file for the parser.
mblighe7d9c602009-07-02 19:02:33 +0000163 @param tag: The job execution tag from the scheduler. [optional]
mblighe0cbc912010-03-11 18:03:07 +0000164 @param control_filename: The filename where the server control file
165 should be written in the results directory.
jadmanski10646442008-08-13 14:05:21 +0000166 """
mbligh0d0f67d2009-11-06 03:15:03 +0000167 super(base_server_job, self).__init__(resultdir=resultdir)
mbligha788dc42009-03-26 21:10:16 +0000168
mbligh0d0f67d2009-11-06 03:15:03 +0000169 path = os.path.dirname(__file__)
170 self.control = control
171 self._uncollected_log_file = os.path.join(self.resultdir,
172 'uncollected_logs')
173 debugdir = os.path.join(self.resultdir, 'debug')
174 if not os.path.exists(debugdir):
175 os.mkdir(debugdir)
176
177 if user:
178 self.user = user
179 else:
180 self.user = getpass.getuser()
181
jadmanski808f4b12010-04-09 22:30:31 +0000182 self.args = args
jadmanski10646442008-08-13 14:05:21 +0000183 self.machines = machines
mbligh0d0f67d2009-11-06 03:15:03 +0000184 self._client = client
jadmanski10646442008-08-13 14:05:21 +0000185 self.warning_loggers = set()
jadmanskif37df842009-02-11 00:03:26 +0000186 self.warning_manager = warning_manager()
mbligh0d0f67d2009-11-06 03:15:03 +0000187 self._ssh_user = ssh_user
188 self._ssh_port = ssh_port
189 self._ssh_pass = ssh_pass
mblighe7d9c602009-07-02 19:02:33 +0000190 self.tag = tag
mbligh09108442008-10-15 16:27:38 +0000191 self.last_boot_tag = None
jadmanski53aaf382008-11-17 16:22:31 +0000192 self.hosts = set()
mbligh0d0f67d2009-11-06 03:15:03 +0000193 self.drop_caches = False
mblighb5dac432008-11-27 00:38:44 +0000194 self.drop_caches_between_iterations = False
mblighe0cbc912010-03-11 18:03:07 +0000195 self._control_filename = control_filename
jadmanski10646442008-08-13 14:05:21 +0000196
showard75cdfee2009-06-10 17:40:41 +0000197 self.logging = logging_manager.get_logging_manager(
198 manage_stdout_and_stderr=True, redirect_fds=True)
199 subcommand.logging_manager_object = self.logging
jadmanski10646442008-08-13 14:05:21 +0000200
mbligh0d0f67d2009-11-06 03:15:03 +0000201 self.sysinfo = sysinfo.sysinfo(self.resultdir)
jadmanski043e1132008-11-19 17:10:32 +0000202 self.profilers = profilers.profilers(self)
jadmanskic09fc152008-10-15 17:56:59 +0000203
jadmanski10646442008-08-13 14:05:21 +0000204 job_data = {'label' : label, 'user' : user,
205 'hostname' : ','.join(machines),
mbligh0d0f67d2009-11-06 03:15:03 +0000206 'status_version' : str(self._STATUS_VERSION),
showard170873e2009-01-07 00:22:26 +0000207 'job_started' : str(int(time.time()))}
mbligh374f3412009-05-13 21:29:45 +0000208 if group_name:
209 job_data['host_group_name'] = group_name
jadmanski10646442008-08-13 14:05:21 +0000210
mbligh0d0f67d2009-11-06 03:15:03 +0000211 # only write these keyvals out on the first job in a resultdir
212 if 'job_started' not in utils.read_keyval(self.resultdir):
213 job_data.update(get_site_job_data(self))
214 utils.write_keyval(self.resultdir, job_data)
215
216 self._parse_job = parse_job
showardcc929362010-01-25 21:20:41 +0000217 self._using_parser = (self._parse_job and len(machines) <= 1)
mbligh0d0f67d2009-11-06 03:15:03 +0000218 self.pkgmgr = packages.PackageManager(
219 self.autodir, run_function_dargs={'timeout':600})
showard21baa452008-10-21 00:08:39 +0000220 self.num_tests_run = 0
221 self.num_tests_failed = 0
222
jadmanski550fdc22008-11-20 16:32:08 +0000223 self._register_subcommand_hooks()
224
mbligh0d0f67d2009-11-06 03:15:03 +0000225 # these components aren't usable on the server
226 self.bootloader = None
227 self.harness = None
228
jadmanski2a89dac2010-06-11 14:32:58 +0000229 # set up the status logger
jadmanski52053632010-06-11 21:08:10 +0000230 self._indenter = status_indenter()
jadmanski2a89dac2010-06-11 14:32:58 +0000231 self._logger = base_job.status_logger(
jadmanski52053632010-06-11 21:08:10 +0000232 self, self._indenter, 'status.log', 'status.log',
jadmanski2a89dac2010-06-11 14:32:58 +0000233 record_hook=server_job_record_hook(self))
234
mbligh0d0f67d2009-11-06 03:15:03 +0000235
236 @classmethod
237 def _find_base_directories(cls):
238 """
239 Determine locations of autodir, clientdir and serverdir. Assumes
240 that this file is located within serverdir and uses __file__ along
241 with relative paths to resolve the location.
242 """
243 serverdir = os.path.abspath(os.path.dirname(__file__))
244 autodir = os.path.normpath(os.path.join(serverdir, '..'))
245 clientdir = os.path.join(autodir, 'client')
246 return autodir, clientdir, serverdir
247
248
249 def _find_resultdir(self, resultdir):
250 """
251 Determine the location of resultdir. For server jobs we expect one to
252 always be explicitly passed in to __init__, so just return that.
253 """
254 if resultdir:
255 return os.path.normpath(resultdir)
256 else:
257 return None
258
jadmanski550fdc22008-11-20 16:32:08 +0000259
jadmanski2a89dac2010-06-11 14:32:58 +0000260 def _get_status_logger(self):
261 """Return a reference to the status logger."""
262 return self._logger
263
264
jadmanskie432dd22009-01-30 15:04:51 +0000265 @staticmethod
266 def _load_control_file(path):
267 f = open(path)
268 try:
269 control_file = f.read()
270 finally:
271 f.close()
272 return re.sub('\r', '', control_file)
273
274
jadmanski550fdc22008-11-20 16:32:08 +0000275 def _register_subcommand_hooks(self):
mbligh2b92b862008-11-22 13:25:32 +0000276 """
277 Register some hooks into the subcommand modules that allow us
278 to properly clean up self.hosts created in forked subprocesses.
279 """
jadmanski550fdc22008-11-20 16:32:08 +0000280 def on_fork(cmd):
281 self._existing_hosts_on_fork = set(self.hosts)
282 def on_join(cmd):
283 new_hosts = self.hosts - self._existing_hosts_on_fork
284 for host in new_hosts:
285 host.close()
286 subcommand.subcommand.register_fork_hook(on_fork)
287 subcommand.subcommand.register_join_hook(on_join)
288
jadmanski10646442008-08-13 14:05:21 +0000289
mbligh4608b002010-01-05 18:22:35 +0000290 def init_parser(self):
mbligh2b92b862008-11-22 13:25:32 +0000291 """
mbligh4608b002010-01-05 18:22:35 +0000292 Start the continuous parsing of self.resultdir. This sets up
jadmanski10646442008-08-13 14:05:21 +0000293 the database connection and inserts the basic job object into
mbligh2b92b862008-11-22 13:25:32 +0000294 the database if necessary.
295 """
mbligh4608b002010-01-05 18:22:35 +0000296 if not self._using_parser:
297 return
jadmanski10646442008-08-13 14:05:21 +0000298 # redirect parser debugging to .parse.log
mbligh4608b002010-01-05 18:22:35 +0000299 parse_log = os.path.join(self.resultdir, '.parse.log')
jadmanski10646442008-08-13 14:05:21 +0000300 parse_log = open(parse_log, 'w', 0)
301 tko_utils.redirect_parser_debugging(parse_log)
302 # create a job model object and set up the db
303 self.results_db = tko_db.db(autocommit=True)
mbligh0d0f67d2009-11-06 03:15:03 +0000304 self.parser = status_lib.parser(self._STATUS_VERSION)
mbligh4608b002010-01-05 18:22:35 +0000305 self.job_model = self.parser.make_job(self.resultdir)
jadmanski10646442008-08-13 14:05:21 +0000306 self.parser.start(self.job_model)
307 # check if a job already exists in the db and insert it if
308 # it does not
mbligh0d0f67d2009-11-06 03:15:03 +0000309 job_idx = self.results_db.find_job(self._parse_job)
jadmanski10646442008-08-13 14:05:21 +0000310 if job_idx is None:
mbligh0d0f67d2009-11-06 03:15:03 +0000311 self.results_db.insert_job(self._parse_job, self.job_model)
jadmanski10646442008-08-13 14:05:21 +0000312 else:
mbligh2b92b862008-11-22 13:25:32 +0000313 machine_idx = self.results_db.lookup_machine(self.job_model.machine)
jadmanski10646442008-08-13 14:05:21 +0000314 self.job_model.index = job_idx
315 self.job_model.machine_idx = machine_idx
316
317
318 def cleanup_parser(self):
mbligh2b92b862008-11-22 13:25:32 +0000319 """
320 This should be called after the server job is finished
jadmanski10646442008-08-13 14:05:21 +0000321 to carry out any remaining cleanup (e.g. flushing any
mbligh2b92b862008-11-22 13:25:32 +0000322 remaining test results to the results db)
323 """
mbligh0d0f67d2009-11-06 03:15:03 +0000324 if not self._using_parser:
jadmanski10646442008-08-13 14:05:21 +0000325 return
326 final_tests = self.parser.end()
327 for test in final_tests:
328 self.__insert_test(test)
mbligh0d0f67d2009-11-06 03:15:03 +0000329 self._using_parser = False
jadmanski10646442008-08-13 14:05:21 +0000330
331
332 def verify(self):
333 if not self.machines:
mbligh084bc172008-10-18 14:02:45 +0000334 raise error.AutoservError('No machines specified to verify')
mbligh0fce4112008-11-27 00:37:17 +0000335 if self.resultdir:
336 os.chdir(self.resultdir)
jadmanski10646442008-08-13 14:05:21 +0000337 try:
jadmanskicdd0c402008-09-19 21:21:31 +0000338 namespace = {'machines' : self.machines, 'job' : self,
mbligh0d0f67d2009-11-06 03:15:03 +0000339 'ssh_user' : self._ssh_user,
340 'ssh_port' : self._ssh_port,
341 'ssh_pass' : self._ssh_pass}
mbligh084bc172008-10-18 14:02:45 +0000342 self._execute_code(VERIFY_CONTROL_FILE, namespace, protect=False)
jadmanski10646442008-08-13 14:05:21 +0000343 except Exception, e:
mbligh2b92b862008-11-22 13:25:32 +0000344 msg = ('Verify failed\n' + str(e) + '\n' + traceback.format_exc())
jadmanski10646442008-08-13 14:05:21 +0000345 self.record('ABORT', None, None, msg)
346 raise
347
348
349 def repair(self, host_protection):
350 if not self.machines:
351 raise error.AutoservError('No machines specified to repair')
mbligh0fce4112008-11-27 00:37:17 +0000352 if self.resultdir:
353 os.chdir(self.resultdir)
jadmanski10646442008-08-13 14:05:21 +0000354 namespace = {'machines': self.machines, 'job': self,
mbligh0d0f67d2009-11-06 03:15:03 +0000355 'ssh_user': self._ssh_user, 'ssh_port': self._ssh_port,
356 'ssh_pass': self._ssh_pass,
jadmanski10646442008-08-13 14:05:21 +0000357 'protection_level': host_protection}
mbligh25c0b8c2009-01-24 01:44:17 +0000358
mbligh0931b0a2009-04-08 17:44:48 +0000359 self._execute_code(REPAIR_CONTROL_FILE, namespace, protect=False)
jadmanski10646442008-08-13 14:05:21 +0000360
361
362 def precheck(self):
363 """
364 perform any additional checks in derived classes.
365 """
366 pass
367
368
369 def enable_external_logging(self):
mbligh2b92b862008-11-22 13:25:32 +0000370 """
371 Start or restart external logging mechanism.
jadmanski10646442008-08-13 14:05:21 +0000372 """
373 pass
374
375
376 def disable_external_logging(self):
mbligh2b92b862008-11-22 13:25:32 +0000377 """
378 Pause or stop external logging mechanism.
jadmanski10646442008-08-13 14:05:21 +0000379 """
380 pass
381
382
383 def use_external_logging(self):
mbligh2b92b862008-11-22 13:25:32 +0000384 """
385 Return True if external logging should be used.
jadmanski10646442008-08-13 14:05:21 +0000386 """
387 return False
388
389
mbligh415dc212009-06-15 21:53:34 +0000390 def _make_parallel_wrapper(self, function, machines, log):
391 """Wrap function as appropriate for calling by parallel_simple."""
mbligh2b92b862008-11-22 13:25:32 +0000392 is_forking = not (len(machines) == 1 and self.machines == machines)
mbligh0d0f67d2009-11-06 03:15:03 +0000393 if self._parse_job and is_forking and log:
jadmanski10646442008-08-13 14:05:21 +0000394 def wrapper(machine):
mbligh0d0f67d2009-11-06 03:15:03 +0000395 self._parse_job += "/" + machine
396 self._using_parser = True
jadmanski10646442008-08-13 14:05:21 +0000397 self.machines = [machine]
mbligh0d0f67d2009-11-06 03:15:03 +0000398 self.push_execution_context(machine)
jadmanski609a5f42008-08-26 20:52:42 +0000399 os.chdir(self.resultdir)
showard2bab8f42008-11-12 18:15:22 +0000400 utils.write_keyval(self.resultdir, {"hostname": machine})
mbligh4608b002010-01-05 18:22:35 +0000401 self.init_parser()
jadmanski10646442008-08-13 14:05:21 +0000402 result = function(machine)
403 self.cleanup_parser()
404 return result
jadmanski4dd1a002008-09-05 20:27:30 +0000405 elif len(machines) > 1 and log:
jadmanski10646442008-08-13 14:05:21 +0000406 def wrapper(machine):
mbligh0d0f67d2009-11-06 03:15:03 +0000407 self.push_execution_context(machine)
jadmanski609a5f42008-08-26 20:52:42 +0000408 os.chdir(self.resultdir)
mbligh838d82d2009-03-11 17:14:31 +0000409 machine_data = {'hostname' : machine,
mbligh0d0f67d2009-11-06 03:15:03 +0000410 'status_version' : str(self._STATUS_VERSION)}
mbligh838d82d2009-03-11 17:14:31 +0000411 utils.write_keyval(self.resultdir, machine_data)
jadmanski10646442008-08-13 14:05:21 +0000412 result = function(machine)
413 return result
414 else:
415 wrapper = function
mbligh415dc212009-06-15 21:53:34 +0000416 return wrapper
417
418
419 def parallel_simple(self, function, machines, log=True, timeout=None,
420 return_results=False):
421 """
422 Run 'function' using parallel_simple, with an extra wrapper to handle
423 the necessary setup for continuous parsing, if possible. If continuous
424 parsing is already properly initialized then this should just work.
425
426 @param function: A callable to run in parallel given each machine.
427 @param machines: A list of machine names to be passed one per subcommand
428 invocation of function.
429 @param log: If True, output will be written to output in a subdirectory
430 named after each machine.
431 @param timeout: Seconds after which the function call should timeout.
432 @param return_results: If True instead of an AutoServError being raised
433 on any error a list of the results|exceptions from the function
434 called on each arg is returned. [default: False]
435
436 @raises error.AutotestError: If any of the functions failed.
437 """
438 wrapper = self._make_parallel_wrapper(function, machines, log)
439 return subcommand.parallel_simple(wrapper, machines,
440 log=log, timeout=timeout,
441 return_results=return_results)
442
443
444 def parallel_on_machines(self, function, machines, timeout=None):
445 """
showardcd5fac42009-07-06 20:19:43 +0000446 @param function: Called in parallel with one machine as its argument.
mbligh415dc212009-06-15 21:53:34 +0000447 @param machines: A list of machines to call function(machine) on.
448 @param timeout: Seconds after which the function call should timeout.
449
450 @returns A list of machines on which function(machine) returned
451 without raising an exception.
452 """
showardcd5fac42009-07-06 20:19:43 +0000453 results = self.parallel_simple(function, machines, timeout=timeout,
mbligh415dc212009-06-15 21:53:34 +0000454 return_results=True)
455 success_machines = []
456 for result, machine in itertools.izip(results, machines):
457 if not isinstance(result, Exception):
458 success_machines.append(machine)
459 return success_machines
jadmanski10646442008-08-13 14:05:21 +0000460
461
mbligh0d0f67d2009-11-06 03:15:03 +0000462 _USE_TEMP_DIR = object()
mbligh2b92b862008-11-22 13:25:32 +0000463 def run(self, cleanup=False, install_before=False, install_after=False,
jadmanskie432dd22009-01-30 15:04:51 +0000464 collect_crashdumps=True, namespace={}, control=None,
jadmanskidef0c3c2009-03-25 20:07:10 +0000465 control_file_dir=None, only_collect_crashinfo=False):
jadmanskifb9c0fa2009-04-29 17:39:16 +0000466 # for a normal job, make sure the uncollected logs file exists
467 # for a crashinfo-only run it should already exist, bail out otherwise
jadmanski648c39f2010-03-19 17:38:01 +0000468 created_uncollected_logs = False
mbligh0d0f67d2009-11-06 03:15:03 +0000469 if self.resultdir and not os.path.exists(self._uncollected_log_file):
jadmanskifb9c0fa2009-04-29 17:39:16 +0000470 if only_collect_crashinfo:
471 # if this is a crashinfo-only run, and there were no existing
472 # uncollected logs, just bail out early
473 logging.info("No existing uncollected logs, "
474 "skipping crashinfo collection")
475 return
476 else:
mbligh0d0f67d2009-11-06 03:15:03 +0000477 log_file = open(self._uncollected_log_file, "w")
jadmanskifb9c0fa2009-04-29 17:39:16 +0000478 pickle.dump([], log_file)
479 log_file.close()
jadmanski648c39f2010-03-19 17:38:01 +0000480 created_uncollected_logs = True
jadmanskifb9c0fa2009-04-29 17:39:16 +0000481
jadmanski10646442008-08-13 14:05:21 +0000482 # use a copy so changes don't affect the original dictionary
483 namespace = namespace.copy()
484 machines = self.machines
jadmanskie432dd22009-01-30 15:04:51 +0000485 if control is None:
jadmanski02a3ba22009-11-13 20:47:27 +0000486 if self.control is None:
487 control = ''
488 else:
489 control = self._load_control_file(self.control)
jadmanskie432dd22009-01-30 15:04:51 +0000490 if control_file_dir is None:
491 control_file_dir = self.resultdir
jadmanski10646442008-08-13 14:05:21 +0000492
493 self.aborted = False
494 namespace['machines'] = machines
jadmanski808f4b12010-04-09 22:30:31 +0000495 namespace['args'] = self.args
jadmanski10646442008-08-13 14:05:21 +0000496 namespace['job'] = self
mbligh0d0f67d2009-11-06 03:15:03 +0000497 namespace['ssh_user'] = self._ssh_user
498 namespace['ssh_port'] = self._ssh_port
499 namespace['ssh_pass'] = self._ssh_pass
jadmanski10646442008-08-13 14:05:21 +0000500 test_start_time = int(time.time())
501
mbligh80e1eba2008-11-19 00:26:18 +0000502 if self.resultdir:
503 os.chdir(self.resultdir)
jadmanski779bd292009-03-19 17:33:33 +0000504 # touch status.log so that the parser knows a job is running here
jadmanski382303a2009-04-21 19:53:39 +0000505 open(self.get_status_log_path(), 'a').close()
mbligh80e1eba2008-11-19 00:26:18 +0000506 self.enable_external_logging()
jadmanskie432dd22009-01-30 15:04:51 +0000507
jadmanskicdd0c402008-09-19 21:21:31 +0000508 collect_crashinfo = True
mblighaebe3b62008-12-22 14:45:40 +0000509 temp_control_file_dir = None
jadmanski10646442008-08-13 14:05:21 +0000510 try:
showardcf8d4922009-10-14 16:08:39 +0000511 try:
512 if install_before and machines:
513 self._execute_code(INSTALL_CONTROL_FILE, namespace)
jadmanskie432dd22009-01-30 15:04:51 +0000514
showardcf8d4922009-10-14 16:08:39 +0000515 if only_collect_crashinfo:
516 return
517
jadmanskidef0c3c2009-03-25 20:07:10 +0000518 # determine the dir to write the control files to
519 cfd_specified = (control_file_dir
mbligh0d0f67d2009-11-06 03:15:03 +0000520 and control_file_dir is not self._USE_TEMP_DIR)
jadmanskidef0c3c2009-03-25 20:07:10 +0000521 if cfd_specified:
522 temp_control_file_dir = None
523 else:
524 temp_control_file_dir = tempfile.mkdtemp(
525 suffix='temp_control_file_dir')
526 control_file_dir = temp_control_file_dir
527 server_control_file = os.path.join(control_file_dir,
mblighe0cbc912010-03-11 18:03:07 +0000528 self._control_filename)
jadmanskidef0c3c2009-03-25 20:07:10 +0000529 client_control_file = os.path.join(control_file_dir,
530 CLIENT_CONTROL_FILENAME)
mbligh0d0f67d2009-11-06 03:15:03 +0000531 if self._client:
jadmanskidef0c3c2009-03-25 20:07:10 +0000532 namespace['control'] = control
533 utils.open_write_close(client_control_file, control)
mblighfeac0102009-04-28 18:31:12 +0000534 shutil.copyfile(CLIENT_WRAPPER_CONTROL_FILE,
535 server_control_file)
jadmanskidef0c3c2009-03-25 20:07:10 +0000536 else:
537 utils.open_write_close(server_control_file, control)
mbligh26f0d882009-06-22 18:30:01 +0000538 logging.info("Processing control file")
jadmanskidef0c3c2009-03-25 20:07:10 +0000539 self._execute_code(server_control_file, namespace)
mbligh26f0d882009-06-22 18:30:01 +0000540 logging.info("Finished processing control file")
jadmanski10646442008-08-13 14:05:21 +0000541
jadmanskidef0c3c2009-03-25 20:07:10 +0000542 # no error occured, so we don't need to collect crashinfo
543 collect_crashinfo = False
showardcf8d4922009-10-14 16:08:39 +0000544 except:
545 try:
546 logging.exception(
547 'Exception escaped control file, job aborting:')
548 except:
549 pass # don't let logging exceptions here interfere
550 raise
jadmanski10646442008-08-13 14:05:21 +0000551 finally:
mblighaebe3b62008-12-22 14:45:40 +0000552 if temp_control_file_dir:
jadmanskie432dd22009-01-30 15:04:51 +0000553 # Clean up temp directory used for copies of the control files
mblighaebe3b62008-12-22 14:45:40 +0000554 try:
555 shutil.rmtree(temp_control_file_dir)
556 except Exception, e:
mblighe7d9c602009-07-02 19:02:33 +0000557 logging.warn('Could not remove temp directory %s: %s',
558 temp_control_file_dir, e)
jadmanskie432dd22009-01-30 15:04:51 +0000559
jadmanskicdd0c402008-09-19 21:21:31 +0000560 if machines and (collect_crashdumps or collect_crashinfo):
jadmanski10646442008-08-13 14:05:21 +0000561 namespace['test_start_time'] = test_start_time
jadmanskicdd0c402008-09-19 21:21:31 +0000562 if collect_crashinfo:
mbligh084bc172008-10-18 14:02:45 +0000563 # includes crashdumps
564 self._execute_code(CRASHINFO_CONTROL_FILE, namespace)
jadmanskicdd0c402008-09-19 21:21:31 +0000565 else:
mbligh084bc172008-10-18 14:02:45 +0000566 self._execute_code(CRASHDUMPS_CONTROL_FILE, namespace)
jadmanski648c39f2010-03-19 17:38:01 +0000567 if self._uncollected_log_file and created_uncollected_logs:
mbligh0d0f67d2009-11-06 03:15:03 +0000568 os.remove(self._uncollected_log_file)
jadmanski10646442008-08-13 14:05:21 +0000569 self.disable_external_logging()
showard45ae8192008-11-05 19:32:53 +0000570 if cleanup and machines:
571 self._execute_code(CLEANUP_CONTROL_FILE, namespace)
jadmanski10646442008-08-13 14:05:21 +0000572 if install_after and machines:
mbligh084bc172008-10-18 14:02:45 +0000573 self._execute_code(INSTALL_CONTROL_FILE, namespace)
jadmanski10646442008-08-13 14:05:21 +0000574
575
576 def run_test(self, url, *args, **dargs):
mbligh2b92b862008-11-22 13:25:32 +0000577 """
578 Summon a test object and run it.
jadmanski10646442008-08-13 14:05:21 +0000579
580 tag
581 tag to add to testname
582 url
583 url of the test to run
584 """
mblighfc3da5b2010-01-06 18:37:22 +0000585 group, testname = self.pkgmgr.get_package_name(url, 'test')
586 testname, subdir, tag = self._build_tagged_test_name(testname, dargs)
587 outputdir = self._make_test_outputdir(subdir)
jadmanski10646442008-08-13 14:05:21 +0000588
589 def group_func():
590 try:
591 test.runtest(self, url, tag, args, dargs)
592 except error.TestBaseException, e:
593 self.record(e.exit_status, subdir, testname, str(e))
594 raise
595 except Exception, e:
596 info = str(e) + "\n" + traceback.format_exc()
597 self.record('FAIL', subdir, testname, info)
598 raise
599 else:
mbligh2b92b862008-11-22 13:25:32 +0000600 self.record('GOOD', subdir, testname, 'completed successfully')
jadmanskide292df2008-08-26 20:51:14 +0000601
602 result, exc_info = self._run_group(testname, subdir, group_func)
603 if exc_info and isinstance(exc_info[1], error.TestBaseException):
604 return False
605 elif exc_info:
606 raise exc_info[0], exc_info[1], exc_info[2]
607 else:
608 return True
jadmanski10646442008-08-13 14:05:21 +0000609
610
611 def _run_group(self, name, subdir, function, *args, **dargs):
612 """\
613 Underlying method for running something inside of a group.
614 """
jadmanskide292df2008-08-26 20:51:14 +0000615 result, exc_info = None, None
jadmanski10646442008-08-13 14:05:21 +0000616 try:
617 self.record('START', subdir, name)
jadmanski52053632010-06-11 21:08:10 +0000618 result = function(*args, **dargs)
jadmanski10646442008-08-13 14:05:21 +0000619 except error.TestBaseException, e:
jadmanskib88d6dc2009-01-10 00:33:18 +0000620 self.record("END %s" % e.exit_status, subdir, name)
jadmanskide292df2008-08-26 20:51:14 +0000621 exc_info = sys.exc_info()
jadmanski10646442008-08-13 14:05:21 +0000622 except Exception, e:
623 err_msg = str(e) + '\n'
624 err_msg += traceback.format_exc()
625 self.record('END ABORT', subdir, name, err_msg)
626 raise error.JobError(name + ' failed\n' + traceback.format_exc())
627 else:
628 self.record('END GOOD', subdir, name)
629
jadmanskide292df2008-08-26 20:51:14 +0000630 return result, exc_info
jadmanski10646442008-08-13 14:05:21 +0000631
632
633 def run_group(self, function, *args, **dargs):
634 """\
635 function:
636 subroutine to run
637 *args:
638 arguments for the function
639 """
640
641 name = function.__name__
642
643 # Allow the tag for the group to be specified.
644 tag = dargs.pop('tag', None)
645 if tag:
646 name = tag
647
jadmanskide292df2008-08-26 20:51:14 +0000648 return self._run_group(name, None, function, *args, **dargs)[0]
jadmanski10646442008-08-13 14:05:21 +0000649
650
651 def run_reboot(self, reboot_func, get_kernel_func):
652 """\
653 A specialization of run_group meant specifically for handling
654 a reboot. Includes support for capturing the kernel version
655 after the reboot.
656
657 reboot_func: a function that carries out the reboot
658
659 get_kernel_func: a function that returns a string
660 representing the kernel version.
661 """
jadmanski10646442008-08-13 14:05:21 +0000662 try:
663 self.record('START', None, 'reboot')
jadmanski10646442008-08-13 14:05:21 +0000664 reboot_func()
665 except Exception, e:
jadmanski10646442008-08-13 14:05:21 +0000666 err_msg = str(e) + '\n' + traceback.format_exc()
667 self.record('END FAIL', None, 'reboot', err_msg)
jadmanski4b51d542009-04-08 14:17:16 +0000668 raise
jadmanski10646442008-08-13 14:05:21 +0000669 else:
670 kernel = get_kernel_func()
jadmanski10646442008-08-13 14:05:21 +0000671 self.record('END GOOD', None, 'reboot',
672 optional_fields={"kernel": kernel})
673
674
jadmanskie432dd22009-01-30 15:04:51 +0000675 def run_control(self, path):
676 """Execute a control file found at path (relative to the autotest
677 path). Intended for executing a control file within a control file,
678 not for running the top-level job control file."""
679 path = os.path.join(self.autodir, path)
680 control_file = self._load_control_file(path)
mbligh0d0f67d2009-11-06 03:15:03 +0000681 self.run(control=control_file, control_file_dir=self._USE_TEMP_DIR)
jadmanskie432dd22009-01-30 15:04:51 +0000682
683
jadmanskic09fc152008-10-15 17:56:59 +0000684 def add_sysinfo_command(self, command, logfile=None, on_every_test=False):
mbligh4395bbd2009-03-25 19:34:17 +0000685 self._add_sysinfo_loggable(sysinfo.command(command, logf=logfile),
jadmanskic09fc152008-10-15 17:56:59 +0000686 on_every_test)
687
688
689 def add_sysinfo_logfile(self, file, on_every_test=False):
690 self._add_sysinfo_loggable(sysinfo.logfile(file), on_every_test)
691
692
693 def _add_sysinfo_loggable(self, loggable, on_every_test):
694 if on_every_test:
695 self.sysinfo.test_loggables.add(loggable)
696 else:
697 self.sysinfo.boot_loggables.add(loggable)
698
699
jadmanski10646442008-08-13 14:05:21 +0000700 def _read_warnings(self):
jadmanskif37df842009-02-11 00:03:26 +0000701 """Poll all the warning loggers and extract any new warnings that have
702 been logged. If the warnings belong to a category that is currently
703 disabled, this method will discard them and they will no longer be
704 retrievable.
705
706 Returns a list of (timestamp, message) tuples, where timestamp is an
707 integer epoch timestamp."""
jadmanski10646442008-08-13 14:05:21 +0000708 warnings = []
709 while True:
710 # pull in a line of output from every logger that has
711 # output ready to be read
mbligh2b92b862008-11-22 13:25:32 +0000712 loggers, _, _ = select.select(self.warning_loggers, [], [], 0)
jadmanski10646442008-08-13 14:05:21 +0000713 closed_loggers = set()
714 for logger in loggers:
715 line = logger.readline()
716 # record any broken pipes (aka line == empty)
717 if len(line) == 0:
718 closed_loggers.add(logger)
719 continue
jadmanskif37df842009-02-11 00:03:26 +0000720 # parse out the warning
721 timestamp, msgtype, msg = line.split('\t', 2)
722 timestamp = int(timestamp)
723 # if the warning is valid, add it to the results
724 if self.warning_manager.is_valid(timestamp, msgtype):
725 warnings.append((timestamp, msg.strip()))
jadmanski10646442008-08-13 14:05:21 +0000726
727 # stop listening to loggers that are closed
728 self.warning_loggers -= closed_loggers
729
730 # stop if none of the loggers have any output left
731 if not loggers:
732 break
733
734 # sort into timestamp order
735 warnings.sort()
736 return warnings
737
738
showardcc929362010-01-25 21:20:41 +0000739 def _unique_subdirectory(self, base_subdirectory_name):
740 """Compute a unique results subdirectory based on the given name.
741
742 Appends base_subdirectory_name with a number as necessary to find a
743 directory name that doesn't already exist.
744 """
745 subdirectory = base_subdirectory_name
746 counter = 1
747 while os.path.exists(os.path.join(self.resultdir, subdirectory)):
748 subdirectory = base_subdirectory_name + '.' + str(counter)
749 counter += 1
750 return subdirectory
751
752
jadmanski52053632010-06-11 21:08:10 +0000753 def get_record_context(self):
754 """Returns an object representing the current job.record context.
755
756 The object returned is an opaque object with a 0-arg restore method
757 which can be called to restore the job.record context (i.e. indentation)
758 to the current level. The intention is that it should be used when
759 something external which generate job.record calls (e.g. an autotest
760 client) can fail catastrophically and the server job record state
761 needs to be reset to its original "known good" state.
762
763 @return: A context object with a 0-arg restore() method."""
764 return self._indenter.get_context()
765
766
showardcc929362010-01-25 21:20:41 +0000767 def record_summary(self, status_code, test_name, reason='', attributes=None,
768 distinguishing_attributes=(), child_test_ids=None):
769 """Record a summary test result.
770
771 @param status_code: status code string, see
772 common_lib.log.is_valid_status()
773 @param test_name: name of the test
774 @param reason: (optional) string providing detailed reason for test
775 outcome
776 @param attributes: (optional) dict of string keyvals to associate with
777 this result
778 @param distinguishing_attributes: (optional) list of attribute names
779 that should be used to distinguish identically-named test
780 results. These attributes should be present in the attributes
781 parameter. This is used to generate user-friendly subdirectory
782 names.
783 @param child_test_ids: (optional) list of test indices for test results
784 used in generating this result.
785 """
786 subdirectory_name_parts = [test_name]
787 for attribute in distinguishing_attributes:
788 assert attributes
789 assert attribute in attributes, '%s not in %s' % (attribute,
790 attributes)
791 subdirectory_name_parts.append(attributes[attribute])
792 base_subdirectory_name = '.'.join(subdirectory_name_parts)
793
794 subdirectory = self._unique_subdirectory(base_subdirectory_name)
795 subdirectory_path = os.path.join(self.resultdir, subdirectory)
796 os.mkdir(subdirectory_path)
797
798 self.record(status_code, subdirectory, test_name,
799 status=reason, optional_fields={'is_summary': True})
800
801 if attributes:
802 utils.write_keyval(subdirectory_path, attributes)
803
804 if child_test_ids:
805 ids_string = ','.join(str(test_id) for test_id in child_test_ids)
806 summary_data = {'child_test_ids': ids_string}
807 utils.write_keyval(os.path.join(subdirectory_path, 'summary_data'),
808 summary_data)
809
810
jadmanski16a7ff72009-04-01 18:19:53 +0000811 def disable_warnings(self, warning_type):
jadmanskif37df842009-02-11 00:03:26 +0000812 self.warning_manager.disable_warnings(warning_type)
jadmanski16a7ff72009-04-01 18:19:53 +0000813 self.record("INFO", None, None,
814 "disabling %s warnings" % warning_type,
815 {"warnings.disable": warning_type})
jadmanskif37df842009-02-11 00:03:26 +0000816
817
jadmanski16a7ff72009-04-01 18:19:53 +0000818 def enable_warnings(self, warning_type):
jadmanskif37df842009-02-11 00:03:26 +0000819 self.warning_manager.enable_warnings(warning_type)
jadmanski16a7ff72009-04-01 18:19:53 +0000820 self.record("INFO", None, None,
821 "enabling %s warnings" % warning_type,
822 {"warnings.enable": warning_type})
jadmanskif37df842009-02-11 00:03:26 +0000823
824
jadmanski779bd292009-03-19 17:33:33 +0000825 def get_status_log_path(self, subdir=None):
826 """Return the path to the job status log.
827
828 @param subdir - Optional paramter indicating that you want the path
829 to a subdirectory status log.
830
831 @returns The path where the status log should be.
832 """
mbligh210bae62009-04-01 18:33:13 +0000833 if self.resultdir:
834 if subdir:
835 return os.path.join(self.resultdir, subdir, "status.log")
836 else:
837 return os.path.join(self.resultdir, "status.log")
jadmanski779bd292009-03-19 17:33:33 +0000838 else:
mbligh210bae62009-04-01 18:33:13 +0000839 return None
jadmanski779bd292009-03-19 17:33:33 +0000840
841
jadmanski6bb32d72009-03-19 20:25:24 +0000842 def _update_uncollected_logs_list(self, update_func):
843 """Updates the uncollected logs list in a multi-process safe manner.
844
845 @param update_func - a function that updates the list of uncollected
846 logs. Should take one parameter, the list to be updated.
847 """
mbligh0d0f67d2009-11-06 03:15:03 +0000848 if self._uncollected_log_file:
849 log_file = open(self._uncollected_log_file, "r+")
mbligha788dc42009-03-26 21:10:16 +0000850 fcntl.flock(log_file, fcntl.LOCK_EX)
jadmanski6bb32d72009-03-19 20:25:24 +0000851 try:
852 uncollected_logs = pickle.load(log_file)
853 update_func(uncollected_logs)
854 log_file.seek(0)
855 log_file.truncate()
856 pickle.dump(uncollected_logs, log_file)
jadmanski3bff9092009-04-22 18:09:47 +0000857 log_file.flush()
jadmanski6bb32d72009-03-19 20:25:24 +0000858 finally:
859 fcntl.flock(log_file, fcntl.LOCK_UN)
860 log_file.close()
861
862
863 def add_client_log(self, hostname, remote_path, local_path):
864 """Adds a new set of client logs to the list of uncollected logs,
865 to allow for future log recovery.
866
867 @param host - the hostname of the machine holding the logs
868 @param remote_path - the directory on the remote machine holding logs
869 @param local_path - the local directory to copy the logs into
870 """
871 def update_func(logs_list):
872 logs_list.append((hostname, remote_path, local_path))
873 self._update_uncollected_logs_list(update_func)
874
875
876 def remove_client_log(self, hostname, remote_path, local_path):
877 """Removes a set of client logs from the list of uncollected logs,
878 to allow for future log recovery.
879
880 @param host - the hostname of the machine holding the logs
881 @param remote_path - the directory on the remote machine holding logs
882 @param local_path - the local directory to copy the logs into
883 """
884 def update_func(logs_list):
885 logs_list.remove((hostname, remote_path, local_path))
886 self._update_uncollected_logs_list(update_func)
887
888
mbligh0d0f67d2009-11-06 03:15:03 +0000889 def get_client_logs(self):
890 """Retrieves the list of uncollected logs, if it exists.
891
892 @returns A list of (host, remote_path, local_path) tuples. Returns
893 an empty list if no uncollected logs file exists.
894 """
895 log_exists = (self._uncollected_log_file and
896 os.path.exists(self._uncollected_log_file))
897 if log_exists:
898 return pickle.load(open(self._uncollected_log_file))
899 else:
900 return []
901
902
mbligh084bc172008-10-18 14:02:45 +0000903 def _fill_server_control_namespace(self, namespace, protect=True):
mbligh2b92b862008-11-22 13:25:32 +0000904 """
905 Prepare a namespace to be used when executing server control files.
mbligh084bc172008-10-18 14:02:45 +0000906
907 This sets up the control file API by importing modules and making them
908 available under the appropriate names within namespace.
909
910 For use by _execute_code().
911
912 Args:
913 namespace: The namespace dictionary to fill in.
914 protect: Boolean. If True (the default) any operation that would
915 clobber an existing entry in namespace will cause an error.
916 Raises:
917 error.AutoservError: When a name would be clobbered by import.
918 """
919 def _import_names(module_name, names=()):
mbligh2b92b862008-11-22 13:25:32 +0000920 """
921 Import a module and assign named attributes into namespace.
mbligh084bc172008-10-18 14:02:45 +0000922
923 Args:
924 module_name: The string module name.
925 names: A limiting list of names to import from module_name. If
926 empty (the default), all names are imported from the module
927 similar to a "from foo.bar import *" statement.
928 Raises:
929 error.AutoservError: When a name being imported would clobber
930 a name already in namespace.
931 """
932 module = __import__(module_name, {}, {}, names)
933
934 # No names supplied? Import * from the lowest level module.
935 # (Ugh, why do I have to implement this part myself?)
936 if not names:
937 for submodule_name in module_name.split('.')[1:]:
938 module = getattr(module, submodule_name)
939 if hasattr(module, '__all__'):
940 names = getattr(module, '__all__')
941 else:
942 names = dir(module)
943
944 # Install each name into namespace, checking to make sure it
945 # doesn't override anything that already exists.
946 for name in names:
947 # Check for conflicts to help prevent future problems.
948 if name in namespace and protect:
949 if namespace[name] is not getattr(module, name):
950 raise error.AutoservError('importing name '
951 '%s from %s %r would override %r' %
952 (name, module_name, getattr(module, name),
953 namespace[name]))
954 else:
955 # Encourage cleanliness and the use of __all__ for a
956 # more concrete API with less surprises on '*' imports.
957 warnings.warn('%s (%r) being imported from %s for use '
958 'in server control files is not the '
959 'first occurrance of that import.' %
960 (name, namespace[name], module_name))
961
962 namespace[name] = getattr(module, name)
963
964
965 # This is the equivalent of prepending a bunch of import statements to
966 # the front of the control script.
mbligha2b07dd2009-06-22 18:26:13 +0000967 namespace.update(os=os, sys=sys, logging=logging)
mbligh084bc172008-10-18 14:02:45 +0000968 _import_names('autotest_lib.server',
969 ('hosts', 'autotest', 'kvm', 'git', 'standalone_profiler',
970 'source_kernel', 'rpm_kernel', 'deb_kernel', 'git_kernel'))
971 _import_names('autotest_lib.server.subcommand',
972 ('parallel', 'parallel_simple', 'subcommand'))
973 _import_names('autotest_lib.server.utils',
974 ('run', 'get_tmp_dir', 'sh_escape', 'parse_machine'))
975 _import_names('autotest_lib.client.common_lib.error')
976 _import_names('autotest_lib.client.common_lib.barrier', ('barrier',))
977
978 # Inject ourself as the job object into other classes within the API.
979 # (Yuck, this injection is a gross thing be part of a public API. -gps)
980 #
981 # XXX Base & SiteAutotest do not appear to use .job. Who does?
982 namespace['autotest'].Autotest.job = self
983 # server.hosts.base_classes.Host uses .job.
984 namespace['hosts'].Host.job = self
985
986
987 def _execute_code(self, code_file, namespace, protect=True):
mbligh2b92b862008-11-22 13:25:32 +0000988 """
989 Execute code using a copy of namespace as a server control script.
mbligh084bc172008-10-18 14:02:45 +0000990
991 Unless protect_namespace is explicitly set to False, the dict will not
992 be modified.
993
994 Args:
995 code_file: The filename of the control file to execute.
996 namespace: A dict containing names to make available during execution.
997 protect: Boolean. If True (the default) a copy of the namespace dict
998 is used during execution to prevent the code from modifying its
999 contents outside of this function. If False the raw dict is
1000 passed in and modifications will be allowed.
1001 """
1002 if protect:
1003 namespace = namespace.copy()
1004 self._fill_server_control_namespace(namespace, protect=protect)
1005 # TODO: Simplify and get rid of the special cases for only 1 machine.
showard3e66e8c2008-10-27 19:20:51 +00001006 if len(self.machines) > 1:
mbligh084bc172008-10-18 14:02:45 +00001007 machines_text = '\n'.join(self.machines) + '\n'
1008 # Only rewrite the file if it does not match our machine list.
1009 try:
1010 machines_f = open(MACHINES_FILENAME, 'r')
1011 existing_machines_text = machines_f.read()
1012 machines_f.close()
1013 except EnvironmentError:
1014 existing_machines_text = None
1015 if machines_text != existing_machines_text:
1016 utils.open_write_close(MACHINES_FILENAME, machines_text)
1017 execfile(code_file, namespace, namespace)
jadmanski10646442008-08-13 14:05:21 +00001018
1019
jadmanski10646442008-08-13 14:05:21 +00001020 def __parse_status(self, new_lines):
mbligh0d0f67d2009-11-06 03:15:03 +00001021 if not self._using_parser:
jadmanski10646442008-08-13 14:05:21 +00001022 return
1023 new_tests = self.parser.process_lines(new_lines)
1024 for test in new_tests:
1025 self.__insert_test(test)
1026
1027
1028 def __insert_test(self, test):
mbligh2b92b862008-11-22 13:25:32 +00001029 """
1030 An internal method to insert a new test result into the
jadmanski10646442008-08-13 14:05:21 +00001031 database. This method will not raise an exception, even if an
1032 error occurs during the insert, to avoid failing a test
1033 simply because of unexpected database issues."""
showard21baa452008-10-21 00:08:39 +00001034 self.num_tests_run += 1
1035 if status_lib.is_worse_than_or_equal_to(test.status, 'FAIL'):
1036 self.num_tests_failed += 1
jadmanski10646442008-08-13 14:05:21 +00001037 try:
1038 self.results_db.insert_test(self.job_model, test)
1039 except Exception:
1040 msg = ("WARNING: An unexpected error occured while "
1041 "inserting test results into the database. "
1042 "Ignoring error.\n" + traceback.format_exc())
1043 print >> sys.stderr, msg
1044
mblighcaa62c22008-04-07 21:51:17 +00001045
mblighfc3da5b2010-01-06 18:37:22 +00001046 def preprocess_client_state(self):
1047 """
1048 Produce a state file for initializing the state of a client job.
1049
1050 Creates a new client state file with all the current server state, as
1051 well as some pre-set client state.
1052
1053 @returns The path of the file the state was written into.
1054 """
1055 # initialize the sysinfo state
1056 self._state.set('client', 'sysinfo', self.sysinfo.serialize())
1057
1058 # dump the state out to a tempfile
1059 fd, file_path = tempfile.mkstemp(dir=self.tmpdir)
1060 os.close(fd)
mbligha2c99492010-01-27 22:59:50 +00001061
1062 # write_to_file doesn't need locking, we exclusively own file_path
mblighfc3da5b2010-01-06 18:37:22 +00001063 self._state.write_to_file(file_path)
1064 return file_path
1065
1066
1067 def postprocess_client_state(self, state_path):
1068 """
1069 Update the state of this job with the state from a client job.
1070
1071 Updates the state of the server side of a job with the final state
1072 of a client job that was run. Updates the non-client-specific state,
1073 pulls in some specific bits from the client-specific state, and then
1074 discards the rest. Removes the state file afterwards
1075
1076 @param state_file A path to the state file from the client.
1077 """
1078 # update the on-disk state
mblighfc3da5b2010-01-06 18:37:22 +00001079 try:
jadmanskib6e7bdb2010-04-13 16:00:39 +00001080 self._state.read_from_file(state_path)
mblighfc3da5b2010-01-06 18:37:22 +00001081 os.remove(state_path)
mbligha2c99492010-01-27 22:59:50 +00001082 except OSError, e:
mblighfc3da5b2010-01-06 18:37:22 +00001083 # ignore file-not-found errors
1084 if e.errno != errno.ENOENT:
1085 raise
jadmanskib6e7bdb2010-04-13 16:00:39 +00001086 else:
1087 logging.debug('Client state file %s not found', state_path)
mblighfc3da5b2010-01-06 18:37:22 +00001088
1089 # update the sysinfo state
1090 if self._state.has('client', 'sysinfo'):
1091 self.sysinfo.deserialize(self._state.get('client', 'sysinfo'))
1092
1093 # drop all the client-specific state
1094 self._state.discard_namespace('client')
1095
1096
mbligh0a883702010-04-21 01:58:34 +00001097 def clear_all_known_hosts(self):
1098 """Clears known hosts files for all AbstractSSHHosts."""
1099 for host in self.hosts:
1100 if isinstance(host, abstract_ssh.AbstractSSHHost):
1101 host.clear_known_hosts()
1102
1103
mbligha7007722009-01-13 00:37:11 +00001104site_server_job = utils.import_site_class(
1105 __file__, "autotest_lib.server.site_server_job", "site_server_job",
1106 base_server_job)
jadmanski0afbb632008-06-06 21:10:57 +00001107
mbligh0a8c3322009-04-28 18:32:19 +00001108class server_job(site_server_job):
jadmanski0afbb632008-06-06 21:10:57 +00001109 pass
jadmanskif37df842009-02-11 00:03:26 +00001110
1111
1112class warning_manager(object):
1113 """Class for controlling warning logs. Manages the enabling and disabling
1114 of warnings."""
1115 def __init__(self):
1116 # a map of warning types to a list of disabled time intervals
1117 self.disabled_warnings = {}
1118
1119
1120 def is_valid(self, timestamp, warning_type):
1121 """Indicates if a warning (based on the time it occured and its type)
1122 is a valid warning. A warning is considered "invalid" if this type of
1123 warning was marked as "disabled" at the time the warning occured."""
1124 disabled_intervals = self.disabled_warnings.get(warning_type, [])
1125 for start, end in disabled_intervals:
1126 if timestamp >= start and (end is None or timestamp < end):
1127 return False
1128 return True
1129
1130
1131 def disable_warnings(self, warning_type, current_time_func=time.time):
1132 """As of now, disables all further warnings of this type."""
1133 intervals = self.disabled_warnings.setdefault(warning_type, [])
1134 if not intervals or intervals[-1][1] is not None:
jadmanski16a7ff72009-04-01 18:19:53 +00001135 intervals.append((int(current_time_func()), None))
jadmanskif37df842009-02-11 00:03:26 +00001136
1137
1138 def enable_warnings(self, warning_type, current_time_func=time.time):
1139 """As of now, enables all further warnings of this type."""
1140 intervals = self.disabled_warnings.get(warning_type, [])
1141 if intervals and intervals[-1][1] is None:
jadmanski16a7ff72009-04-01 18:19:53 +00001142 intervals[-1] = (intervals[-1][0], int(current_time_func()))