blob: 6ef59debc57b8b2d9d2abc662c54711cc5b9fa38 [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
Eric Li6f27d4f2010-09-29 10:55:17 -0700545 except Exception, e:
showardcf8d4922009-10-14 16:08:39 +0000546 try:
547 logging.exception(
548 'Exception escaped control file, job aborting:')
Eric Li6f27d4f2010-09-29 10:55:17 -0700549 self.record('INFO', None, None, str(e),
550 {'job_abort_reason': str(e)})
showardcf8d4922009-10-14 16:08:39 +0000551 except:
552 pass # don't let logging exceptions here interfere
553 raise
jadmanski10646442008-08-13 14:05:21 +0000554 finally:
mblighaebe3b62008-12-22 14:45:40 +0000555 if temp_control_file_dir:
jadmanskie432dd22009-01-30 15:04:51 +0000556 # Clean up temp directory used for copies of the control files
mblighaebe3b62008-12-22 14:45:40 +0000557 try:
558 shutil.rmtree(temp_control_file_dir)
559 except Exception, e:
mblighe7d9c602009-07-02 19:02:33 +0000560 logging.warn('Could not remove temp directory %s: %s',
561 temp_control_file_dir, e)
jadmanskie432dd22009-01-30 15:04:51 +0000562
jadmanskicdd0c402008-09-19 21:21:31 +0000563 if machines and (collect_crashdumps or collect_crashinfo):
jadmanski10646442008-08-13 14:05:21 +0000564 namespace['test_start_time'] = test_start_time
jadmanskicdd0c402008-09-19 21:21:31 +0000565 if collect_crashinfo:
mbligh084bc172008-10-18 14:02:45 +0000566 # includes crashdumps
567 self._execute_code(CRASHINFO_CONTROL_FILE, namespace)
jadmanskicdd0c402008-09-19 21:21:31 +0000568 else:
mbligh084bc172008-10-18 14:02:45 +0000569 self._execute_code(CRASHDUMPS_CONTROL_FILE, namespace)
jadmanski648c39f2010-03-19 17:38:01 +0000570 if self._uncollected_log_file and created_uncollected_logs:
mbligh0d0f67d2009-11-06 03:15:03 +0000571 os.remove(self._uncollected_log_file)
jadmanski10646442008-08-13 14:05:21 +0000572 self.disable_external_logging()
showard45ae8192008-11-05 19:32:53 +0000573 if cleanup and machines:
574 self._execute_code(CLEANUP_CONTROL_FILE, namespace)
jadmanski10646442008-08-13 14:05:21 +0000575 if install_after and machines:
mbligh084bc172008-10-18 14:02:45 +0000576 self._execute_code(INSTALL_CONTROL_FILE, namespace)
jadmanski10646442008-08-13 14:05:21 +0000577
578
579 def run_test(self, url, *args, **dargs):
mbligh2b92b862008-11-22 13:25:32 +0000580 """
581 Summon a test object and run it.
jadmanski10646442008-08-13 14:05:21 +0000582
583 tag
584 tag to add to testname
585 url
586 url of the test to run
587 """
mblighfc3da5b2010-01-06 18:37:22 +0000588 group, testname = self.pkgmgr.get_package_name(url, 'test')
589 testname, subdir, tag = self._build_tagged_test_name(testname, dargs)
590 outputdir = self._make_test_outputdir(subdir)
jadmanski10646442008-08-13 14:05:21 +0000591
592 def group_func():
593 try:
594 test.runtest(self, url, tag, args, dargs)
595 except error.TestBaseException, e:
596 self.record(e.exit_status, subdir, testname, str(e))
597 raise
598 except Exception, e:
599 info = str(e) + "\n" + traceback.format_exc()
600 self.record('FAIL', subdir, testname, info)
601 raise
602 else:
mbligh2b92b862008-11-22 13:25:32 +0000603 self.record('GOOD', subdir, testname, 'completed successfully')
jadmanskide292df2008-08-26 20:51:14 +0000604
605 result, exc_info = self._run_group(testname, subdir, group_func)
606 if exc_info and isinstance(exc_info[1], error.TestBaseException):
607 return False
608 elif exc_info:
609 raise exc_info[0], exc_info[1], exc_info[2]
610 else:
611 return True
jadmanski10646442008-08-13 14:05:21 +0000612
613
614 def _run_group(self, name, subdir, function, *args, **dargs):
615 """\
616 Underlying method for running something inside of a group.
617 """
jadmanskide292df2008-08-26 20:51:14 +0000618 result, exc_info = None, None
jadmanski10646442008-08-13 14:05:21 +0000619 try:
620 self.record('START', subdir, name)
jadmanski52053632010-06-11 21:08:10 +0000621 result = function(*args, **dargs)
jadmanski10646442008-08-13 14:05:21 +0000622 except error.TestBaseException, e:
jadmanskib88d6dc2009-01-10 00:33:18 +0000623 self.record("END %s" % e.exit_status, subdir, name)
jadmanskide292df2008-08-26 20:51:14 +0000624 exc_info = sys.exc_info()
jadmanski10646442008-08-13 14:05:21 +0000625 except Exception, e:
626 err_msg = str(e) + '\n'
627 err_msg += traceback.format_exc()
628 self.record('END ABORT', subdir, name, err_msg)
629 raise error.JobError(name + ' failed\n' + traceback.format_exc())
630 else:
631 self.record('END GOOD', subdir, name)
632
jadmanskide292df2008-08-26 20:51:14 +0000633 return result, exc_info
jadmanski10646442008-08-13 14:05:21 +0000634
635
636 def run_group(self, function, *args, **dargs):
637 """\
638 function:
639 subroutine to run
640 *args:
641 arguments for the function
642 """
643
644 name = function.__name__
645
646 # Allow the tag for the group to be specified.
647 tag = dargs.pop('tag', None)
648 if tag:
649 name = tag
650
jadmanskide292df2008-08-26 20:51:14 +0000651 return self._run_group(name, None, function, *args, **dargs)[0]
jadmanski10646442008-08-13 14:05:21 +0000652
653
654 def run_reboot(self, reboot_func, get_kernel_func):
655 """\
656 A specialization of run_group meant specifically for handling
657 a reboot. Includes support for capturing the kernel version
658 after the reboot.
659
660 reboot_func: a function that carries out the reboot
661
662 get_kernel_func: a function that returns a string
663 representing the kernel version.
664 """
jadmanski10646442008-08-13 14:05:21 +0000665 try:
666 self.record('START', None, 'reboot')
jadmanski10646442008-08-13 14:05:21 +0000667 reboot_func()
668 except Exception, e:
jadmanski10646442008-08-13 14:05:21 +0000669 err_msg = str(e) + '\n' + traceback.format_exc()
670 self.record('END FAIL', None, 'reboot', err_msg)
jadmanski4b51d542009-04-08 14:17:16 +0000671 raise
jadmanski10646442008-08-13 14:05:21 +0000672 else:
673 kernel = get_kernel_func()
jadmanski10646442008-08-13 14:05:21 +0000674 self.record('END GOOD', None, 'reboot',
675 optional_fields={"kernel": kernel})
676
677
jadmanskie432dd22009-01-30 15:04:51 +0000678 def run_control(self, path):
679 """Execute a control file found at path (relative to the autotest
680 path). Intended for executing a control file within a control file,
681 not for running the top-level job control file."""
682 path = os.path.join(self.autodir, path)
683 control_file = self._load_control_file(path)
mbligh0d0f67d2009-11-06 03:15:03 +0000684 self.run(control=control_file, control_file_dir=self._USE_TEMP_DIR)
jadmanskie432dd22009-01-30 15:04:51 +0000685
686
jadmanskic09fc152008-10-15 17:56:59 +0000687 def add_sysinfo_command(self, command, logfile=None, on_every_test=False):
mbligh4395bbd2009-03-25 19:34:17 +0000688 self._add_sysinfo_loggable(sysinfo.command(command, logf=logfile),
jadmanskic09fc152008-10-15 17:56:59 +0000689 on_every_test)
690
691
692 def add_sysinfo_logfile(self, file, on_every_test=False):
693 self._add_sysinfo_loggable(sysinfo.logfile(file), on_every_test)
694
695
696 def _add_sysinfo_loggable(self, loggable, on_every_test):
697 if on_every_test:
698 self.sysinfo.test_loggables.add(loggable)
699 else:
700 self.sysinfo.boot_loggables.add(loggable)
701
702
jadmanski10646442008-08-13 14:05:21 +0000703 def _read_warnings(self):
jadmanskif37df842009-02-11 00:03:26 +0000704 """Poll all the warning loggers and extract any new warnings that have
705 been logged. If the warnings belong to a category that is currently
706 disabled, this method will discard them and they will no longer be
707 retrievable.
708
709 Returns a list of (timestamp, message) tuples, where timestamp is an
710 integer epoch timestamp."""
jadmanski10646442008-08-13 14:05:21 +0000711 warnings = []
712 while True:
713 # pull in a line of output from every logger that has
714 # output ready to be read
mbligh2b92b862008-11-22 13:25:32 +0000715 loggers, _, _ = select.select(self.warning_loggers, [], [], 0)
jadmanski10646442008-08-13 14:05:21 +0000716 closed_loggers = set()
717 for logger in loggers:
718 line = logger.readline()
719 # record any broken pipes (aka line == empty)
720 if len(line) == 0:
721 closed_loggers.add(logger)
722 continue
jadmanskif37df842009-02-11 00:03:26 +0000723 # parse out the warning
724 timestamp, msgtype, msg = line.split('\t', 2)
725 timestamp = int(timestamp)
726 # if the warning is valid, add it to the results
727 if self.warning_manager.is_valid(timestamp, msgtype):
728 warnings.append((timestamp, msg.strip()))
jadmanski10646442008-08-13 14:05:21 +0000729
730 # stop listening to loggers that are closed
731 self.warning_loggers -= closed_loggers
732
733 # stop if none of the loggers have any output left
734 if not loggers:
735 break
736
737 # sort into timestamp order
738 warnings.sort()
739 return warnings
740
741
showardcc929362010-01-25 21:20:41 +0000742 def _unique_subdirectory(self, base_subdirectory_name):
743 """Compute a unique results subdirectory based on the given name.
744
745 Appends base_subdirectory_name with a number as necessary to find a
746 directory name that doesn't already exist.
747 """
748 subdirectory = base_subdirectory_name
749 counter = 1
750 while os.path.exists(os.path.join(self.resultdir, subdirectory)):
751 subdirectory = base_subdirectory_name + '.' + str(counter)
752 counter += 1
753 return subdirectory
754
755
jadmanski52053632010-06-11 21:08:10 +0000756 def get_record_context(self):
757 """Returns an object representing the current job.record context.
758
759 The object returned is an opaque object with a 0-arg restore method
760 which can be called to restore the job.record context (i.e. indentation)
761 to the current level. The intention is that it should be used when
762 something external which generate job.record calls (e.g. an autotest
763 client) can fail catastrophically and the server job record state
764 needs to be reset to its original "known good" state.
765
766 @return: A context object with a 0-arg restore() method."""
767 return self._indenter.get_context()
768
769
showardcc929362010-01-25 21:20:41 +0000770 def record_summary(self, status_code, test_name, reason='', attributes=None,
771 distinguishing_attributes=(), child_test_ids=None):
772 """Record a summary test result.
773
774 @param status_code: status code string, see
775 common_lib.log.is_valid_status()
776 @param test_name: name of the test
777 @param reason: (optional) string providing detailed reason for test
778 outcome
779 @param attributes: (optional) dict of string keyvals to associate with
780 this result
781 @param distinguishing_attributes: (optional) list of attribute names
782 that should be used to distinguish identically-named test
783 results. These attributes should be present in the attributes
784 parameter. This is used to generate user-friendly subdirectory
785 names.
786 @param child_test_ids: (optional) list of test indices for test results
787 used in generating this result.
788 """
789 subdirectory_name_parts = [test_name]
790 for attribute in distinguishing_attributes:
791 assert attributes
792 assert attribute in attributes, '%s not in %s' % (attribute,
793 attributes)
794 subdirectory_name_parts.append(attributes[attribute])
795 base_subdirectory_name = '.'.join(subdirectory_name_parts)
796
797 subdirectory = self._unique_subdirectory(base_subdirectory_name)
798 subdirectory_path = os.path.join(self.resultdir, subdirectory)
799 os.mkdir(subdirectory_path)
800
801 self.record(status_code, subdirectory, test_name,
802 status=reason, optional_fields={'is_summary': True})
803
804 if attributes:
805 utils.write_keyval(subdirectory_path, attributes)
806
807 if child_test_ids:
808 ids_string = ','.join(str(test_id) for test_id in child_test_ids)
809 summary_data = {'child_test_ids': ids_string}
810 utils.write_keyval(os.path.join(subdirectory_path, 'summary_data'),
811 summary_data)
812
813
jadmanski16a7ff72009-04-01 18:19:53 +0000814 def disable_warnings(self, warning_type):
jadmanskif37df842009-02-11 00:03:26 +0000815 self.warning_manager.disable_warnings(warning_type)
jadmanski16a7ff72009-04-01 18:19:53 +0000816 self.record("INFO", None, None,
817 "disabling %s warnings" % warning_type,
818 {"warnings.disable": warning_type})
jadmanskif37df842009-02-11 00:03:26 +0000819
820
jadmanski16a7ff72009-04-01 18:19:53 +0000821 def enable_warnings(self, warning_type):
jadmanskif37df842009-02-11 00:03:26 +0000822 self.warning_manager.enable_warnings(warning_type)
jadmanski16a7ff72009-04-01 18:19:53 +0000823 self.record("INFO", None, None,
824 "enabling %s warnings" % warning_type,
825 {"warnings.enable": warning_type})
jadmanskif37df842009-02-11 00:03:26 +0000826
827
jadmanski779bd292009-03-19 17:33:33 +0000828 def get_status_log_path(self, subdir=None):
829 """Return the path to the job status log.
830
831 @param subdir - Optional paramter indicating that you want the path
832 to a subdirectory status log.
833
834 @returns The path where the status log should be.
835 """
mbligh210bae62009-04-01 18:33:13 +0000836 if self.resultdir:
837 if subdir:
838 return os.path.join(self.resultdir, subdir, "status.log")
839 else:
840 return os.path.join(self.resultdir, "status.log")
jadmanski779bd292009-03-19 17:33:33 +0000841 else:
mbligh210bae62009-04-01 18:33:13 +0000842 return None
jadmanski779bd292009-03-19 17:33:33 +0000843
844
jadmanski6bb32d72009-03-19 20:25:24 +0000845 def _update_uncollected_logs_list(self, update_func):
846 """Updates the uncollected logs list in a multi-process safe manner.
847
848 @param update_func - a function that updates the list of uncollected
849 logs. Should take one parameter, the list to be updated.
850 """
mbligh0d0f67d2009-11-06 03:15:03 +0000851 if self._uncollected_log_file:
852 log_file = open(self._uncollected_log_file, "r+")
mbligha788dc42009-03-26 21:10:16 +0000853 fcntl.flock(log_file, fcntl.LOCK_EX)
jadmanski6bb32d72009-03-19 20:25:24 +0000854 try:
855 uncollected_logs = pickle.load(log_file)
856 update_func(uncollected_logs)
857 log_file.seek(0)
858 log_file.truncate()
859 pickle.dump(uncollected_logs, log_file)
jadmanski3bff9092009-04-22 18:09:47 +0000860 log_file.flush()
jadmanski6bb32d72009-03-19 20:25:24 +0000861 finally:
862 fcntl.flock(log_file, fcntl.LOCK_UN)
863 log_file.close()
864
865
866 def add_client_log(self, hostname, remote_path, local_path):
867 """Adds a new set of client logs to the list of uncollected logs,
868 to allow for future log recovery.
869
870 @param host - the hostname of the machine holding the logs
871 @param remote_path - the directory on the remote machine holding logs
872 @param local_path - the local directory to copy the logs into
873 """
874 def update_func(logs_list):
875 logs_list.append((hostname, remote_path, local_path))
876 self._update_uncollected_logs_list(update_func)
877
878
879 def remove_client_log(self, hostname, remote_path, local_path):
880 """Removes a set of client logs from the list of uncollected logs,
881 to allow for future log recovery.
882
883 @param host - the hostname of the machine holding the logs
884 @param remote_path - the directory on the remote machine holding logs
885 @param local_path - the local directory to copy the logs into
886 """
887 def update_func(logs_list):
888 logs_list.remove((hostname, remote_path, local_path))
889 self._update_uncollected_logs_list(update_func)
890
891
mbligh0d0f67d2009-11-06 03:15:03 +0000892 def get_client_logs(self):
893 """Retrieves the list of uncollected logs, if it exists.
894
895 @returns A list of (host, remote_path, local_path) tuples. Returns
896 an empty list if no uncollected logs file exists.
897 """
898 log_exists = (self._uncollected_log_file and
899 os.path.exists(self._uncollected_log_file))
900 if log_exists:
901 return pickle.load(open(self._uncollected_log_file))
902 else:
903 return []
904
905
mbligh084bc172008-10-18 14:02:45 +0000906 def _fill_server_control_namespace(self, namespace, protect=True):
mbligh2b92b862008-11-22 13:25:32 +0000907 """
908 Prepare a namespace to be used when executing server control files.
mbligh084bc172008-10-18 14:02:45 +0000909
910 This sets up the control file API by importing modules and making them
911 available under the appropriate names within namespace.
912
913 For use by _execute_code().
914
915 Args:
916 namespace: The namespace dictionary to fill in.
917 protect: Boolean. If True (the default) any operation that would
918 clobber an existing entry in namespace will cause an error.
919 Raises:
920 error.AutoservError: When a name would be clobbered by import.
921 """
922 def _import_names(module_name, names=()):
mbligh2b92b862008-11-22 13:25:32 +0000923 """
924 Import a module and assign named attributes into namespace.
mbligh084bc172008-10-18 14:02:45 +0000925
926 Args:
927 module_name: The string module name.
928 names: A limiting list of names to import from module_name. If
929 empty (the default), all names are imported from the module
930 similar to a "from foo.bar import *" statement.
931 Raises:
932 error.AutoservError: When a name being imported would clobber
933 a name already in namespace.
934 """
935 module = __import__(module_name, {}, {}, names)
936
937 # No names supplied? Import * from the lowest level module.
938 # (Ugh, why do I have to implement this part myself?)
939 if not names:
940 for submodule_name in module_name.split('.')[1:]:
941 module = getattr(module, submodule_name)
942 if hasattr(module, '__all__'):
943 names = getattr(module, '__all__')
944 else:
945 names = dir(module)
946
947 # Install each name into namespace, checking to make sure it
948 # doesn't override anything that already exists.
949 for name in names:
950 # Check for conflicts to help prevent future problems.
951 if name in namespace and protect:
952 if namespace[name] is not getattr(module, name):
953 raise error.AutoservError('importing name '
954 '%s from %s %r would override %r' %
955 (name, module_name, getattr(module, name),
956 namespace[name]))
957 else:
958 # Encourage cleanliness and the use of __all__ for a
959 # more concrete API with less surprises on '*' imports.
960 warnings.warn('%s (%r) being imported from %s for use '
961 'in server control files is not the '
962 'first occurrance of that import.' %
963 (name, namespace[name], module_name))
964
965 namespace[name] = getattr(module, name)
966
967
968 # This is the equivalent of prepending a bunch of import statements to
969 # the front of the control script.
mbligha2b07dd2009-06-22 18:26:13 +0000970 namespace.update(os=os, sys=sys, logging=logging)
mbligh084bc172008-10-18 14:02:45 +0000971 _import_names('autotest_lib.server',
972 ('hosts', 'autotest', 'kvm', 'git', 'standalone_profiler',
973 'source_kernel', 'rpm_kernel', 'deb_kernel', 'git_kernel'))
974 _import_names('autotest_lib.server.subcommand',
975 ('parallel', 'parallel_simple', 'subcommand'))
976 _import_names('autotest_lib.server.utils',
977 ('run', 'get_tmp_dir', 'sh_escape', 'parse_machine'))
978 _import_names('autotest_lib.client.common_lib.error')
979 _import_names('autotest_lib.client.common_lib.barrier', ('barrier',))
980
981 # Inject ourself as the job object into other classes within the API.
982 # (Yuck, this injection is a gross thing be part of a public API. -gps)
983 #
984 # XXX Base & SiteAutotest do not appear to use .job. Who does?
985 namespace['autotest'].Autotest.job = self
986 # server.hosts.base_classes.Host uses .job.
987 namespace['hosts'].Host.job = self
988
989
990 def _execute_code(self, code_file, namespace, protect=True):
mbligh2b92b862008-11-22 13:25:32 +0000991 """
992 Execute code using a copy of namespace as a server control script.
mbligh084bc172008-10-18 14:02:45 +0000993
994 Unless protect_namespace is explicitly set to False, the dict will not
995 be modified.
996
997 Args:
998 code_file: The filename of the control file to execute.
999 namespace: A dict containing names to make available during execution.
1000 protect: Boolean. If True (the default) a copy of the namespace dict
1001 is used during execution to prevent the code from modifying its
1002 contents outside of this function. If False the raw dict is
1003 passed in and modifications will be allowed.
1004 """
1005 if protect:
1006 namespace = namespace.copy()
1007 self._fill_server_control_namespace(namespace, protect=protect)
1008 # TODO: Simplify and get rid of the special cases for only 1 machine.
showard3e66e8c2008-10-27 19:20:51 +00001009 if len(self.machines) > 1:
mbligh084bc172008-10-18 14:02:45 +00001010 machines_text = '\n'.join(self.machines) + '\n'
1011 # Only rewrite the file if it does not match our machine list.
1012 try:
1013 machines_f = open(MACHINES_FILENAME, 'r')
1014 existing_machines_text = machines_f.read()
1015 machines_f.close()
1016 except EnvironmentError:
1017 existing_machines_text = None
1018 if machines_text != existing_machines_text:
1019 utils.open_write_close(MACHINES_FILENAME, machines_text)
1020 execfile(code_file, namespace, namespace)
jadmanski10646442008-08-13 14:05:21 +00001021
1022
jadmanskie29d0e42010-06-17 16:06:52 +00001023 def _parse_status(self, new_line):
mbligh0d0f67d2009-11-06 03:15:03 +00001024 if not self._using_parser:
jadmanski10646442008-08-13 14:05:21 +00001025 return
jadmanskie29d0e42010-06-17 16:06:52 +00001026 new_tests = self.parser.process_lines([new_line])
jadmanski10646442008-08-13 14:05:21 +00001027 for test in new_tests:
1028 self.__insert_test(test)
1029
1030
1031 def __insert_test(self, test):
mbligh2b92b862008-11-22 13:25:32 +00001032 """
1033 An internal method to insert a new test result into the
jadmanski10646442008-08-13 14:05:21 +00001034 database. This method will not raise an exception, even if an
1035 error occurs during the insert, to avoid failing a test
1036 simply because of unexpected database issues."""
showard21baa452008-10-21 00:08:39 +00001037 self.num_tests_run += 1
1038 if status_lib.is_worse_than_or_equal_to(test.status, 'FAIL'):
1039 self.num_tests_failed += 1
jadmanski10646442008-08-13 14:05:21 +00001040 try:
1041 self.results_db.insert_test(self.job_model, test)
1042 except Exception:
1043 msg = ("WARNING: An unexpected error occured while "
1044 "inserting test results into the database. "
1045 "Ignoring error.\n" + traceback.format_exc())
1046 print >> sys.stderr, msg
1047
mblighcaa62c22008-04-07 21:51:17 +00001048
mblighfc3da5b2010-01-06 18:37:22 +00001049 def preprocess_client_state(self):
1050 """
1051 Produce a state file for initializing the state of a client job.
1052
1053 Creates a new client state file with all the current server state, as
1054 well as some pre-set client state.
1055
1056 @returns The path of the file the state was written into.
1057 """
1058 # initialize the sysinfo state
1059 self._state.set('client', 'sysinfo', self.sysinfo.serialize())
1060
1061 # dump the state out to a tempfile
1062 fd, file_path = tempfile.mkstemp(dir=self.tmpdir)
1063 os.close(fd)
mbligha2c99492010-01-27 22:59:50 +00001064
1065 # write_to_file doesn't need locking, we exclusively own file_path
mblighfc3da5b2010-01-06 18:37:22 +00001066 self._state.write_to_file(file_path)
1067 return file_path
1068
1069
1070 def postprocess_client_state(self, state_path):
1071 """
1072 Update the state of this job with the state from a client job.
1073
1074 Updates the state of the server side of a job with the final state
1075 of a client job that was run. Updates the non-client-specific state,
1076 pulls in some specific bits from the client-specific state, and then
1077 discards the rest. Removes the state file afterwards
1078
1079 @param state_file A path to the state file from the client.
1080 """
1081 # update the on-disk state
mblighfc3da5b2010-01-06 18:37:22 +00001082 try:
jadmanskib6e7bdb2010-04-13 16:00:39 +00001083 self._state.read_from_file(state_path)
mblighfc3da5b2010-01-06 18:37:22 +00001084 os.remove(state_path)
mbligha2c99492010-01-27 22:59:50 +00001085 except OSError, e:
mblighfc3da5b2010-01-06 18:37:22 +00001086 # ignore file-not-found errors
1087 if e.errno != errno.ENOENT:
1088 raise
jadmanskib6e7bdb2010-04-13 16:00:39 +00001089 else:
1090 logging.debug('Client state file %s not found', state_path)
mblighfc3da5b2010-01-06 18:37:22 +00001091
1092 # update the sysinfo state
1093 if self._state.has('client', 'sysinfo'):
1094 self.sysinfo.deserialize(self._state.get('client', 'sysinfo'))
1095
1096 # drop all the client-specific state
1097 self._state.discard_namespace('client')
1098
1099
mbligh0a883702010-04-21 01:58:34 +00001100 def clear_all_known_hosts(self):
1101 """Clears known hosts files for all AbstractSSHHosts."""
1102 for host in self.hosts:
1103 if isinstance(host, abstract_ssh.AbstractSSHHost):
1104 host.clear_known_hosts()
1105
1106
mbligha7007722009-01-13 00:37:11 +00001107site_server_job = utils.import_site_class(
1108 __file__, "autotest_lib.server.site_server_job", "site_server_job",
1109 base_server_job)
jadmanski0afbb632008-06-06 21:10:57 +00001110
mbligh0a8c3322009-04-28 18:32:19 +00001111class server_job(site_server_job):
jadmanski0afbb632008-06-06 21:10:57 +00001112 pass
jadmanskif37df842009-02-11 00:03:26 +00001113
1114
1115class warning_manager(object):
1116 """Class for controlling warning logs. Manages the enabling and disabling
1117 of warnings."""
1118 def __init__(self):
1119 # a map of warning types to a list of disabled time intervals
1120 self.disabled_warnings = {}
1121
1122
1123 def is_valid(self, timestamp, warning_type):
1124 """Indicates if a warning (based on the time it occured and its type)
1125 is a valid warning. A warning is considered "invalid" if this type of
1126 warning was marked as "disabled" at the time the warning occured."""
1127 disabled_intervals = self.disabled_warnings.get(warning_type, [])
1128 for start, end in disabled_intervals:
1129 if timestamp >= start and (end is None or timestamp < end):
1130 return False
1131 return True
1132
1133
1134 def disable_warnings(self, warning_type, current_time_func=time.time):
1135 """As of now, disables all further warnings of this type."""
1136 intervals = self.disabled_warnings.setdefault(warning_type, [])
1137 if not intervals or intervals[-1][1] is not None:
jadmanski16a7ff72009-04-01 18:19:53 +00001138 intervals.append((int(current_time_func()), None))
jadmanskif37df842009-02-11 00:03:26 +00001139
1140
1141 def enable_warnings(self, warning_type, current_time_func=time.time):
1142 """As of now, enables all further warnings of this type."""
1143 intervals = self.disabled_warnings.get(warning_type, [])
1144 if intervals and intervals[-1][1] is None:
jadmanski16a7ff72009-04-01 18:19:53 +00001145 intervals[-1] = (intervals[-1][0], int(current_time_func()))