blob: 40489e46dcebdce439b1def9590af3ef6f8bed02 [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
mbligh0d0f67d2009-11-06 03:15:03 +000051class base_server_job(base_job.base_job):
52 """The server-side concrete implementation of base_job.
jadmanski10646442008-08-13 14:05:21 +000053
mbligh0d0f67d2009-11-06 03:15:03 +000054 Optional properties provided by this implementation:
55 serverdir
56 conmuxdir
57
58 num_tests_run
59 num_tests_failed
60
61 warning_manager
62 warning_loggers
jadmanski10646442008-08-13 14:05:21 +000063 """
64
mbligh0d0f67d2009-11-06 03:15:03 +000065 _STATUS_VERSION = 1
jadmanski10646442008-08-13 14:05:21 +000066
67 def __init__(self, control, args, resultdir, label, user, machines,
68 client=False, parse_job='',
mbligh374f3412009-05-13 21:29:45 +000069 ssh_user='root', ssh_port=22, ssh_pass='',
mblighe0cbc912010-03-11 18:03:07 +000070 group_name='', tag='',
71 control_filename=SERVER_CONTROL_FILENAME):
jadmanski10646442008-08-13 14:05:21 +000072 """
mbligh374f3412009-05-13 21:29:45 +000073 Create a server side job object.
mblighb5dac432008-11-27 00:38:44 +000074
mblighe7d9c602009-07-02 19:02:33 +000075 @param control: The pathname of the control file.
76 @param args: Passed to the control file.
77 @param resultdir: Where to throw the results.
78 @param label: Description of the job.
79 @param user: Username for the job (email address).
80 @param client: True if this is a client-side control file.
81 @param parse_job: string, if supplied it is the job execution tag that
82 the results will be passed through to the TKO parser with.
83 @param ssh_user: The SSH username. [root]
84 @param ssh_port: The SSH port number. [22]
85 @param ssh_pass: The SSH passphrase, if needed.
86 @param group_name: If supplied, this will be written out as
mbligh374f3412009-05-13 21:29:45 +000087 host_group_name in the keyvals file for the parser.
mblighe7d9c602009-07-02 19:02:33 +000088 @param tag: The job execution tag from the scheduler. [optional]
mblighe0cbc912010-03-11 18:03:07 +000089 @param control_filename: The filename where the server control file
90 should be written in the results directory.
jadmanski10646442008-08-13 14:05:21 +000091 """
mbligh0d0f67d2009-11-06 03:15:03 +000092 super(base_server_job, self).__init__(resultdir=resultdir)
mbligha788dc42009-03-26 21:10:16 +000093
mbligh0d0f67d2009-11-06 03:15:03 +000094 path = os.path.dirname(__file__)
95 self.control = control
96 self._uncollected_log_file = os.path.join(self.resultdir,
97 'uncollected_logs')
98 debugdir = os.path.join(self.resultdir, 'debug')
99 if not os.path.exists(debugdir):
100 os.mkdir(debugdir)
101
102 if user:
103 self.user = user
104 else:
105 self.user = getpass.getuser()
106
jadmanski808f4b12010-04-09 22:30:31 +0000107 self.args = args
jadmanski10646442008-08-13 14:05:21 +0000108 self.machines = machines
mbligh0d0f67d2009-11-06 03:15:03 +0000109 self._client = client
110 self._record_prefix = ''
jadmanski10646442008-08-13 14:05:21 +0000111 self.warning_loggers = set()
jadmanskif37df842009-02-11 00:03:26 +0000112 self.warning_manager = warning_manager()
mbligh0d0f67d2009-11-06 03:15:03 +0000113 self._ssh_user = ssh_user
114 self._ssh_port = ssh_port
115 self._ssh_pass = ssh_pass
mblighe7d9c602009-07-02 19:02:33 +0000116 self.tag = tag
mbligh09108442008-10-15 16:27:38 +0000117 self.last_boot_tag = None
jadmanski53aaf382008-11-17 16:22:31 +0000118 self.hosts = set()
mbligh0d0f67d2009-11-06 03:15:03 +0000119 self.drop_caches = False
mblighb5dac432008-11-27 00:38:44 +0000120 self.drop_caches_between_iterations = False
mblighe0cbc912010-03-11 18:03:07 +0000121 self._control_filename = control_filename
jadmanski10646442008-08-13 14:05:21 +0000122
showard75cdfee2009-06-10 17:40:41 +0000123 self.logging = logging_manager.get_logging_manager(
124 manage_stdout_and_stderr=True, redirect_fds=True)
125 subcommand.logging_manager_object = self.logging
jadmanski10646442008-08-13 14:05:21 +0000126
mbligh0d0f67d2009-11-06 03:15:03 +0000127 self.sysinfo = sysinfo.sysinfo(self.resultdir)
jadmanski043e1132008-11-19 17:10:32 +0000128 self.profilers = profilers.profilers(self)
jadmanskic09fc152008-10-15 17:56:59 +0000129
jadmanski10646442008-08-13 14:05:21 +0000130 job_data = {'label' : label, 'user' : user,
131 'hostname' : ','.join(machines),
mbligh0d0f67d2009-11-06 03:15:03 +0000132 'status_version' : str(self._STATUS_VERSION),
showard170873e2009-01-07 00:22:26 +0000133 'job_started' : str(int(time.time()))}
mbligh374f3412009-05-13 21:29:45 +0000134 if group_name:
135 job_data['host_group_name'] = group_name
jadmanski10646442008-08-13 14:05:21 +0000136
mbligh0d0f67d2009-11-06 03:15:03 +0000137 # only write these keyvals out on the first job in a resultdir
138 if 'job_started' not in utils.read_keyval(self.resultdir):
139 job_data.update(get_site_job_data(self))
140 utils.write_keyval(self.resultdir, job_data)
141
142 self._parse_job = parse_job
showardcc929362010-01-25 21:20:41 +0000143 self._using_parser = (self._parse_job and len(machines) <= 1)
mbligh0d0f67d2009-11-06 03:15:03 +0000144 self.pkgmgr = packages.PackageManager(
145 self.autodir, run_function_dargs={'timeout':600})
showard21baa452008-10-21 00:08:39 +0000146 self.num_tests_run = 0
147 self.num_tests_failed = 0
148
jadmanski550fdc22008-11-20 16:32:08 +0000149 self._register_subcommand_hooks()
150
mbligh0d0f67d2009-11-06 03:15:03 +0000151 # these components aren't usable on the server
152 self.bootloader = None
153 self.harness = None
154
155
156 @classmethod
157 def _find_base_directories(cls):
158 """
159 Determine locations of autodir, clientdir and serverdir. Assumes
160 that this file is located within serverdir and uses __file__ along
161 with relative paths to resolve the location.
162 """
163 serverdir = os.path.abspath(os.path.dirname(__file__))
164 autodir = os.path.normpath(os.path.join(serverdir, '..'))
165 clientdir = os.path.join(autodir, 'client')
166 return autodir, clientdir, serverdir
167
168
169 def _find_resultdir(self, resultdir):
170 """
171 Determine the location of resultdir. For server jobs we expect one to
172 always be explicitly passed in to __init__, so just return that.
173 """
174 if resultdir:
175 return os.path.normpath(resultdir)
176 else:
177 return None
178
jadmanski550fdc22008-11-20 16:32:08 +0000179
jadmanskie432dd22009-01-30 15:04:51 +0000180 @staticmethod
181 def _load_control_file(path):
182 f = open(path)
183 try:
184 control_file = f.read()
185 finally:
186 f.close()
187 return re.sub('\r', '', control_file)
188
189
jadmanski550fdc22008-11-20 16:32:08 +0000190 def _register_subcommand_hooks(self):
mbligh2b92b862008-11-22 13:25:32 +0000191 """
192 Register some hooks into the subcommand modules that allow us
193 to properly clean up self.hosts created in forked subprocesses.
194 """
jadmanski550fdc22008-11-20 16:32:08 +0000195 def on_fork(cmd):
196 self._existing_hosts_on_fork = set(self.hosts)
197 def on_join(cmd):
198 new_hosts = self.hosts - self._existing_hosts_on_fork
199 for host in new_hosts:
200 host.close()
201 subcommand.subcommand.register_fork_hook(on_fork)
202 subcommand.subcommand.register_join_hook(on_join)
203
jadmanski10646442008-08-13 14:05:21 +0000204
mbligh4608b002010-01-05 18:22:35 +0000205 def init_parser(self):
mbligh2b92b862008-11-22 13:25:32 +0000206 """
mbligh4608b002010-01-05 18:22:35 +0000207 Start the continuous parsing of self.resultdir. This sets up
jadmanski10646442008-08-13 14:05:21 +0000208 the database connection and inserts the basic job object into
mbligh2b92b862008-11-22 13:25:32 +0000209 the database if necessary.
210 """
mbligh4608b002010-01-05 18:22:35 +0000211 if not self._using_parser:
212 return
jadmanski10646442008-08-13 14:05:21 +0000213 # redirect parser debugging to .parse.log
mbligh4608b002010-01-05 18:22:35 +0000214 parse_log = os.path.join(self.resultdir, '.parse.log')
jadmanski10646442008-08-13 14:05:21 +0000215 parse_log = open(parse_log, 'w', 0)
216 tko_utils.redirect_parser_debugging(parse_log)
217 # create a job model object and set up the db
218 self.results_db = tko_db.db(autocommit=True)
mbligh0d0f67d2009-11-06 03:15:03 +0000219 self.parser = status_lib.parser(self._STATUS_VERSION)
mbligh4608b002010-01-05 18:22:35 +0000220 self.job_model = self.parser.make_job(self.resultdir)
jadmanski10646442008-08-13 14:05:21 +0000221 self.parser.start(self.job_model)
222 # check if a job already exists in the db and insert it if
223 # it does not
mbligh0d0f67d2009-11-06 03:15:03 +0000224 job_idx = self.results_db.find_job(self._parse_job)
jadmanski10646442008-08-13 14:05:21 +0000225 if job_idx is None:
mbligh0d0f67d2009-11-06 03:15:03 +0000226 self.results_db.insert_job(self._parse_job, self.job_model)
jadmanski10646442008-08-13 14:05:21 +0000227 else:
mbligh2b92b862008-11-22 13:25:32 +0000228 machine_idx = self.results_db.lookup_machine(self.job_model.machine)
jadmanski10646442008-08-13 14:05:21 +0000229 self.job_model.index = job_idx
230 self.job_model.machine_idx = machine_idx
231
232
233 def cleanup_parser(self):
mbligh2b92b862008-11-22 13:25:32 +0000234 """
235 This should be called after the server job is finished
jadmanski10646442008-08-13 14:05:21 +0000236 to carry out any remaining cleanup (e.g. flushing any
mbligh2b92b862008-11-22 13:25:32 +0000237 remaining test results to the results db)
238 """
mbligh0d0f67d2009-11-06 03:15:03 +0000239 if not self._using_parser:
jadmanski10646442008-08-13 14:05:21 +0000240 return
241 final_tests = self.parser.end()
242 for test in final_tests:
243 self.__insert_test(test)
mbligh0d0f67d2009-11-06 03:15:03 +0000244 self._using_parser = False
jadmanski10646442008-08-13 14:05:21 +0000245
246
247 def verify(self):
248 if not self.machines:
mbligh084bc172008-10-18 14:02:45 +0000249 raise error.AutoservError('No machines specified to verify')
mbligh0fce4112008-11-27 00:37:17 +0000250 if self.resultdir:
251 os.chdir(self.resultdir)
jadmanski10646442008-08-13 14:05:21 +0000252 try:
jadmanskicdd0c402008-09-19 21:21:31 +0000253 namespace = {'machines' : self.machines, 'job' : self,
mbligh0d0f67d2009-11-06 03:15:03 +0000254 'ssh_user' : self._ssh_user,
255 'ssh_port' : self._ssh_port,
256 'ssh_pass' : self._ssh_pass}
mbligh084bc172008-10-18 14:02:45 +0000257 self._execute_code(VERIFY_CONTROL_FILE, namespace, protect=False)
jadmanski10646442008-08-13 14:05:21 +0000258 except Exception, e:
mbligh2b92b862008-11-22 13:25:32 +0000259 msg = ('Verify failed\n' + str(e) + '\n' + traceback.format_exc())
jadmanski10646442008-08-13 14:05:21 +0000260 self.record('ABORT', None, None, msg)
261 raise
262
263
264 def repair(self, host_protection):
265 if not self.machines:
266 raise error.AutoservError('No machines specified to repair')
mbligh0fce4112008-11-27 00:37:17 +0000267 if self.resultdir:
268 os.chdir(self.resultdir)
jadmanski10646442008-08-13 14:05:21 +0000269 namespace = {'machines': self.machines, 'job': self,
mbligh0d0f67d2009-11-06 03:15:03 +0000270 'ssh_user': self._ssh_user, 'ssh_port': self._ssh_port,
271 'ssh_pass': self._ssh_pass,
jadmanski10646442008-08-13 14:05:21 +0000272 'protection_level': host_protection}
mbligh25c0b8c2009-01-24 01:44:17 +0000273
mbligh0931b0a2009-04-08 17:44:48 +0000274 self._execute_code(REPAIR_CONTROL_FILE, namespace, protect=False)
jadmanski10646442008-08-13 14:05:21 +0000275
276
277 def precheck(self):
278 """
279 perform any additional checks in derived classes.
280 """
281 pass
282
283
284 def enable_external_logging(self):
mbligh2b92b862008-11-22 13:25:32 +0000285 """
286 Start or restart external logging mechanism.
jadmanski10646442008-08-13 14:05:21 +0000287 """
288 pass
289
290
291 def disable_external_logging(self):
mbligh2b92b862008-11-22 13:25:32 +0000292 """
293 Pause or stop external logging mechanism.
jadmanski10646442008-08-13 14:05:21 +0000294 """
295 pass
296
297
298 def use_external_logging(self):
mbligh2b92b862008-11-22 13:25:32 +0000299 """
300 Return True if external logging should be used.
jadmanski10646442008-08-13 14:05:21 +0000301 """
302 return False
303
304
mbligh415dc212009-06-15 21:53:34 +0000305 def _make_parallel_wrapper(self, function, machines, log):
306 """Wrap function as appropriate for calling by parallel_simple."""
mbligh2b92b862008-11-22 13:25:32 +0000307 is_forking = not (len(machines) == 1 and self.machines == machines)
mbligh0d0f67d2009-11-06 03:15:03 +0000308 if self._parse_job and is_forking and log:
jadmanski10646442008-08-13 14:05:21 +0000309 def wrapper(machine):
mbligh0d0f67d2009-11-06 03:15:03 +0000310 self._parse_job += "/" + machine
311 self._using_parser = True
jadmanski10646442008-08-13 14:05:21 +0000312 self.machines = [machine]
mbligh0d0f67d2009-11-06 03:15:03 +0000313 self.push_execution_context(machine)
jadmanski609a5f42008-08-26 20:52:42 +0000314 os.chdir(self.resultdir)
showard2bab8f42008-11-12 18:15:22 +0000315 utils.write_keyval(self.resultdir, {"hostname": machine})
mbligh4608b002010-01-05 18:22:35 +0000316 self.init_parser()
jadmanski10646442008-08-13 14:05:21 +0000317 result = function(machine)
318 self.cleanup_parser()
319 return result
jadmanski4dd1a002008-09-05 20:27:30 +0000320 elif len(machines) > 1 and log:
jadmanski10646442008-08-13 14:05:21 +0000321 def wrapper(machine):
mbligh0d0f67d2009-11-06 03:15:03 +0000322 self.push_execution_context(machine)
jadmanski609a5f42008-08-26 20:52:42 +0000323 os.chdir(self.resultdir)
mbligh838d82d2009-03-11 17:14:31 +0000324 machine_data = {'hostname' : machine,
mbligh0d0f67d2009-11-06 03:15:03 +0000325 'status_version' : str(self._STATUS_VERSION)}
mbligh838d82d2009-03-11 17:14:31 +0000326 utils.write_keyval(self.resultdir, machine_data)
jadmanski10646442008-08-13 14:05:21 +0000327 result = function(machine)
328 return result
329 else:
330 wrapper = function
mbligh415dc212009-06-15 21:53:34 +0000331 return wrapper
332
333
334 def parallel_simple(self, function, machines, log=True, timeout=None,
335 return_results=False):
336 """
337 Run 'function' using parallel_simple, with an extra wrapper to handle
338 the necessary setup for continuous parsing, if possible. If continuous
339 parsing is already properly initialized then this should just work.
340
341 @param function: A callable to run in parallel given each machine.
342 @param machines: A list of machine names to be passed one per subcommand
343 invocation of function.
344 @param log: If True, output will be written to output in a subdirectory
345 named after each machine.
346 @param timeout: Seconds after which the function call should timeout.
347 @param return_results: If True instead of an AutoServError being raised
348 on any error a list of the results|exceptions from the function
349 called on each arg is returned. [default: False]
350
351 @raises error.AutotestError: If any of the functions failed.
352 """
353 wrapper = self._make_parallel_wrapper(function, machines, log)
354 return subcommand.parallel_simple(wrapper, machines,
355 log=log, timeout=timeout,
356 return_results=return_results)
357
358
359 def parallel_on_machines(self, function, machines, timeout=None):
360 """
showardcd5fac42009-07-06 20:19:43 +0000361 @param function: Called in parallel with one machine as its argument.
mbligh415dc212009-06-15 21:53:34 +0000362 @param machines: A list of machines to call function(machine) on.
363 @param timeout: Seconds after which the function call should timeout.
364
365 @returns A list of machines on which function(machine) returned
366 without raising an exception.
367 """
showardcd5fac42009-07-06 20:19:43 +0000368 results = self.parallel_simple(function, machines, timeout=timeout,
mbligh415dc212009-06-15 21:53:34 +0000369 return_results=True)
370 success_machines = []
371 for result, machine in itertools.izip(results, machines):
372 if not isinstance(result, Exception):
373 success_machines.append(machine)
374 return success_machines
jadmanski10646442008-08-13 14:05:21 +0000375
376
mbligh0d0f67d2009-11-06 03:15:03 +0000377 _USE_TEMP_DIR = object()
mbligh2b92b862008-11-22 13:25:32 +0000378 def run(self, cleanup=False, install_before=False, install_after=False,
jadmanskie432dd22009-01-30 15:04:51 +0000379 collect_crashdumps=True, namespace={}, control=None,
jadmanskidef0c3c2009-03-25 20:07:10 +0000380 control_file_dir=None, only_collect_crashinfo=False):
jadmanskifb9c0fa2009-04-29 17:39:16 +0000381 # for a normal job, make sure the uncollected logs file exists
382 # for a crashinfo-only run it should already exist, bail out otherwise
jadmanski648c39f2010-03-19 17:38:01 +0000383 created_uncollected_logs = False
mbligh0d0f67d2009-11-06 03:15:03 +0000384 if self.resultdir and not os.path.exists(self._uncollected_log_file):
jadmanskifb9c0fa2009-04-29 17:39:16 +0000385 if only_collect_crashinfo:
386 # if this is a crashinfo-only run, and there were no existing
387 # uncollected logs, just bail out early
388 logging.info("No existing uncollected logs, "
389 "skipping crashinfo collection")
390 return
391 else:
mbligh0d0f67d2009-11-06 03:15:03 +0000392 log_file = open(self._uncollected_log_file, "w")
jadmanskifb9c0fa2009-04-29 17:39:16 +0000393 pickle.dump([], log_file)
394 log_file.close()
jadmanski648c39f2010-03-19 17:38:01 +0000395 created_uncollected_logs = True
jadmanskifb9c0fa2009-04-29 17:39:16 +0000396
jadmanski10646442008-08-13 14:05:21 +0000397 # use a copy so changes don't affect the original dictionary
398 namespace = namespace.copy()
399 machines = self.machines
jadmanskie432dd22009-01-30 15:04:51 +0000400 if control is None:
jadmanski02a3ba22009-11-13 20:47:27 +0000401 if self.control is None:
402 control = ''
403 else:
404 control = self._load_control_file(self.control)
jadmanskie432dd22009-01-30 15:04:51 +0000405 if control_file_dir is None:
406 control_file_dir = self.resultdir
jadmanski10646442008-08-13 14:05:21 +0000407
408 self.aborted = False
409 namespace['machines'] = machines
jadmanski808f4b12010-04-09 22:30:31 +0000410 namespace['args'] = self.args
jadmanski10646442008-08-13 14:05:21 +0000411 namespace['job'] = self
mbligh0d0f67d2009-11-06 03:15:03 +0000412 namespace['ssh_user'] = self._ssh_user
413 namespace['ssh_port'] = self._ssh_port
414 namespace['ssh_pass'] = self._ssh_pass
jadmanski10646442008-08-13 14:05:21 +0000415 test_start_time = int(time.time())
416
mbligh80e1eba2008-11-19 00:26:18 +0000417 if self.resultdir:
418 os.chdir(self.resultdir)
jadmanski779bd292009-03-19 17:33:33 +0000419 # touch status.log so that the parser knows a job is running here
jadmanski382303a2009-04-21 19:53:39 +0000420 open(self.get_status_log_path(), 'a').close()
mbligh80e1eba2008-11-19 00:26:18 +0000421 self.enable_external_logging()
jadmanskie432dd22009-01-30 15:04:51 +0000422
jadmanskicdd0c402008-09-19 21:21:31 +0000423 collect_crashinfo = True
mblighaebe3b62008-12-22 14:45:40 +0000424 temp_control_file_dir = None
jadmanski10646442008-08-13 14:05:21 +0000425 try:
showardcf8d4922009-10-14 16:08:39 +0000426 try:
427 if install_before and machines:
428 self._execute_code(INSTALL_CONTROL_FILE, namespace)
jadmanskie432dd22009-01-30 15:04:51 +0000429
showardcf8d4922009-10-14 16:08:39 +0000430 if only_collect_crashinfo:
431 return
432
jadmanskidef0c3c2009-03-25 20:07:10 +0000433 # determine the dir to write the control files to
434 cfd_specified = (control_file_dir
mbligh0d0f67d2009-11-06 03:15:03 +0000435 and control_file_dir is not self._USE_TEMP_DIR)
jadmanskidef0c3c2009-03-25 20:07:10 +0000436 if cfd_specified:
437 temp_control_file_dir = None
438 else:
439 temp_control_file_dir = tempfile.mkdtemp(
440 suffix='temp_control_file_dir')
441 control_file_dir = temp_control_file_dir
442 server_control_file = os.path.join(control_file_dir,
mblighe0cbc912010-03-11 18:03:07 +0000443 self._control_filename)
jadmanskidef0c3c2009-03-25 20:07:10 +0000444 client_control_file = os.path.join(control_file_dir,
445 CLIENT_CONTROL_FILENAME)
mbligh0d0f67d2009-11-06 03:15:03 +0000446 if self._client:
jadmanskidef0c3c2009-03-25 20:07:10 +0000447 namespace['control'] = control
448 utils.open_write_close(client_control_file, control)
mblighfeac0102009-04-28 18:31:12 +0000449 shutil.copyfile(CLIENT_WRAPPER_CONTROL_FILE,
450 server_control_file)
jadmanskidef0c3c2009-03-25 20:07:10 +0000451 else:
452 utils.open_write_close(server_control_file, control)
mbligh26f0d882009-06-22 18:30:01 +0000453 logging.info("Processing control file")
jadmanskidef0c3c2009-03-25 20:07:10 +0000454 self._execute_code(server_control_file, namespace)
mbligh26f0d882009-06-22 18:30:01 +0000455 logging.info("Finished processing control file")
jadmanski10646442008-08-13 14:05:21 +0000456
jadmanskidef0c3c2009-03-25 20:07:10 +0000457 # no error occured, so we don't need to collect crashinfo
458 collect_crashinfo = False
showardcf8d4922009-10-14 16:08:39 +0000459 except:
460 try:
461 logging.exception(
462 'Exception escaped control file, job aborting:')
463 except:
464 pass # don't let logging exceptions here interfere
465 raise
jadmanski10646442008-08-13 14:05:21 +0000466 finally:
mblighaebe3b62008-12-22 14:45:40 +0000467 if temp_control_file_dir:
jadmanskie432dd22009-01-30 15:04:51 +0000468 # Clean up temp directory used for copies of the control files
mblighaebe3b62008-12-22 14:45:40 +0000469 try:
470 shutil.rmtree(temp_control_file_dir)
471 except Exception, e:
mblighe7d9c602009-07-02 19:02:33 +0000472 logging.warn('Could not remove temp directory %s: %s',
473 temp_control_file_dir, e)
jadmanskie432dd22009-01-30 15:04:51 +0000474
jadmanskicdd0c402008-09-19 21:21:31 +0000475 if machines and (collect_crashdumps or collect_crashinfo):
jadmanski10646442008-08-13 14:05:21 +0000476 namespace['test_start_time'] = test_start_time
jadmanskicdd0c402008-09-19 21:21:31 +0000477 if collect_crashinfo:
mbligh084bc172008-10-18 14:02:45 +0000478 # includes crashdumps
479 self._execute_code(CRASHINFO_CONTROL_FILE, namespace)
jadmanskicdd0c402008-09-19 21:21:31 +0000480 else:
mbligh084bc172008-10-18 14:02:45 +0000481 self._execute_code(CRASHDUMPS_CONTROL_FILE, namespace)
jadmanski648c39f2010-03-19 17:38:01 +0000482 if self._uncollected_log_file and created_uncollected_logs:
mbligh0d0f67d2009-11-06 03:15:03 +0000483 os.remove(self._uncollected_log_file)
jadmanski10646442008-08-13 14:05:21 +0000484 self.disable_external_logging()
showard45ae8192008-11-05 19:32:53 +0000485 if cleanup and machines:
486 self._execute_code(CLEANUP_CONTROL_FILE, namespace)
jadmanski10646442008-08-13 14:05:21 +0000487 if install_after and machines:
mbligh084bc172008-10-18 14:02:45 +0000488 self._execute_code(INSTALL_CONTROL_FILE, namespace)
jadmanski10646442008-08-13 14:05:21 +0000489
490
491 def run_test(self, url, *args, **dargs):
mbligh2b92b862008-11-22 13:25:32 +0000492 """
493 Summon a test object and run it.
jadmanski10646442008-08-13 14:05:21 +0000494
495 tag
496 tag to add to testname
497 url
498 url of the test to run
499 """
mblighfc3da5b2010-01-06 18:37:22 +0000500 group, testname = self.pkgmgr.get_package_name(url, 'test')
501 testname, subdir, tag = self._build_tagged_test_name(testname, dargs)
502 outputdir = self._make_test_outputdir(subdir)
jadmanski10646442008-08-13 14:05:21 +0000503
504 def group_func():
505 try:
506 test.runtest(self, url, tag, args, dargs)
507 except error.TestBaseException, e:
508 self.record(e.exit_status, subdir, testname, str(e))
509 raise
510 except Exception, e:
511 info = str(e) + "\n" + traceback.format_exc()
512 self.record('FAIL', subdir, testname, info)
513 raise
514 else:
mbligh2b92b862008-11-22 13:25:32 +0000515 self.record('GOOD', subdir, testname, 'completed successfully')
jadmanskide292df2008-08-26 20:51:14 +0000516
517 result, exc_info = self._run_group(testname, subdir, group_func)
518 if exc_info and isinstance(exc_info[1], error.TestBaseException):
519 return False
520 elif exc_info:
521 raise exc_info[0], exc_info[1], exc_info[2]
522 else:
523 return True
jadmanski10646442008-08-13 14:05:21 +0000524
525
526 def _run_group(self, name, subdir, function, *args, **dargs):
527 """\
528 Underlying method for running something inside of a group.
529 """
jadmanskide292df2008-08-26 20:51:14 +0000530 result, exc_info = None, None
mbligh0d0f67d2009-11-06 03:15:03 +0000531 old_record_prefix = self._record_prefix
jadmanski10646442008-08-13 14:05:21 +0000532 try:
533 self.record('START', subdir, name)
mbligh0d0f67d2009-11-06 03:15:03 +0000534 self._record_prefix += '\t'
jadmanski10646442008-08-13 14:05:21 +0000535 try:
536 result = function(*args, **dargs)
537 finally:
mbligh0d0f67d2009-11-06 03:15:03 +0000538 self._record_prefix = old_record_prefix
jadmanski10646442008-08-13 14:05:21 +0000539 except error.TestBaseException, e:
jadmanskib88d6dc2009-01-10 00:33:18 +0000540 self.record("END %s" % e.exit_status, subdir, name)
jadmanskide292df2008-08-26 20:51:14 +0000541 exc_info = sys.exc_info()
jadmanski10646442008-08-13 14:05:21 +0000542 except Exception, e:
543 err_msg = str(e) + '\n'
544 err_msg += traceback.format_exc()
545 self.record('END ABORT', subdir, name, err_msg)
546 raise error.JobError(name + ' failed\n' + traceback.format_exc())
547 else:
548 self.record('END GOOD', subdir, name)
549
jadmanskide292df2008-08-26 20:51:14 +0000550 return result, exc_info
jadmanski10646442008-08-13 14:05:21 +0000551
552
553 def run_group(self, function, *args, **dargs):
554 """\
555 function:
556 subroutine to run
557 *args:
558 arguments for the function
559 """
560
561 name = function.__name__
562
563 # Allow the tag for the group to be specified.
564 tag = dargs.pop('tag', None)
565 if tag:
566 name = tag
567
jadmanskide292df2008-08-26 20:51:14 +0000568 return self._run_group(name, None, function, *args, **dargs)[0]
jadmanski10646442008-08-13 14:05:21 +0000569
570
571 def run_reboot(self, reboot_func, get_kernel_func):
572 """\
573 A specialization of run_group meant specifically for handling
574 a reboot. Includes support for capturing the kernel version
575 after the reboot.
576
577 reboot_func: a function that carries out the reboot
578
579 get_kernel_func: a function that returns a string
580 representing the kernel version.
581 """
582
mbligh0d0f67d2009-11-06 03:15:03 +0000583 old_record_prefix = self._record_prefix
jadmanski10646442008-08-13 14:05:21 +0000584 try:
585 self.record('START', None, 'reboot')
mbligh0d0f67d2009-11-06 03:15:03 +0000586 self._record_prefix += '\t'
jadmanski10646442008-08-13 14:05:21 +0000587 reboot_func()
588 except Exception, e:
mbligh0d0f67d2009-11-06 03:15:03 +0000589 self._record_prefix = old_record_prefix
jadmanski10646442008-08-13 14:05:21 +0000590 err_msg = str(e) + '\n' + traceback.format_exc()
591 self.record('END FAIL', None, 'reboot', err_msg)
jadmanski4b51d542009-04-08 14:17:16 +0000592 raise
jadmanski10646442008-08-13 14:05:21 +0000593 else:
594 kernel = get_kernel_func()
mbligh0d0f67d2009-11-06 03:15:03 +0000595 self._record_prefix = old_record_prefix
jadmanski10646442008-08-13 14:05:21 +0000596 self.record('END GOOD', None, 'reboot',
597 optional_fields={"kernel": kernel})
598
599
jadmanskie432dd22009-01-30 15:04:51 +0000600 def run_control(self, path):
601 """Execute a control file found at path (relative to the autotest
602 path). Intended for executing a control file within a control file,
603 not for running the top-level job control file."""
604 path = os.path.join(self.autodir, path)
605 control_file = self._load_control_file(path)
mbligh0d0f67d2009-11-06 03:15:03 +0000606 self.run(control=control_file, control_file_dir=self._USE_TEMP_DIR)
jadmanskie432dd22009-01-30 15:04:51 +0000607
608
jadmanskic09fc152008-10-15 17:56:59 +0000609 def add_sysinfo_command(self, command, logfile=None, on_every_test=False):
mbligh4395bbd2009-03-25 19:34:17 +0000610 self._add_sysinfo_loggable(sysinfo.command(command, logf=logfile),
jadmanskic09fc152008-10-15 17:56:59 +0000611 on_every_test)
612
613
614 def add_sysinfo_logfile(self, file, on_every_test=False):
615 self._add_sysinfo_loggable(sysinfo.logfile(file), on_every_test)
616
617
618 def _add_sysinfo_loggable(self, loggable, on_every_test):
619 if on_every_test:
620 self.sysinfo.test_loggables.add(loggable)
621 else:
622 self.sysinfo.boot_loggables.add(loggable)
623
624
jadmanski10646442008-08-13 14:05:21 +0000625 def record(self, status_code, subdir, operation, status='',
626 optional_fields=None):
627 """
628 Record job-level status
629
630 The intent is to make this file both machine parseable and
631 human readable. That involves a little more complexity, but
632 really isn't all that bad ;-)
633
634 Format is <status code>\t<subdir>\t<operation>\t<status>
635
mbligh1b3b3762008-09-25 02:46:34 +0000636 status code: see common_lib.log.is_valid_status()
jadmanski10646442008-08-13 14:05:21 +0000637 for valid status definition
638
639 subdir: MUST be a relevant subdirectory in the results,
640 or None, which will be represented as '----'
641
642 operation: description of what you ran (e.g. "dbench", or
643 "mkfs -t foobar /dev/sda9")
644
645 status: error message or "completed sucessfully"
646
647 ------------------------------------------------------------
648
649 Initial tabs indicate indent levels for grouping, and is
mbligh0d0f67d2009-11-06 03:15:03 +0000650 governed by self._record_prefix
jadmanski10646442008-08-13 14:05:21 +0000651
652 multiline messages have secondary lines prefaced by a double
653 space (' ')
654
655 Executing this method will trigger the logging of all new
656 warnings to date from the various console loggers.
657 """
658 # poll all our warning loggers for new warnings
659 warnings = self._read_warnings()
mbligh0d0f67d2009-11-06 03:15:03 +0000660 old_record_prefix = self._record_prefix
jadmanski2de83112009-04-01 18:21:04 +0000661 try:
662 if status_code.startswith("END "):
mbligh0d0f67d2009-11-06 03:15:03 +0000663 self._record_prefix += "\t"
jadmanski2de83112009-04-01 18:21:04 +0000664 for timestamp, msg in warnings:
665 self._record("WARN", None, None, msg, timestamp)
666 finally:
mbligh0d0f67d2009-11-06 03:15:03 +0000667 self._record_prefix = old_record_prefix
jadmanski10646442008-08-13 14:05:21 +0000668
669 # write out the actual status log line
670 self._record(status_code, subdir, operation, status,
671 optional_fields=optional_fields)
672
673
674 def _read_warnings(self):
jadmanskif37df842009-02-11 00:03:26 +0000675 """Poll all the warning loggers and extract any new warnings that have
676 been logged. If the warnings belong to a category that is currently
677 disabled, this method will discard them and they will no longer be
678 retrievable.
679
680 Returns a list of (timestamp, message) tuples, where timestamp is an
681 integer epoch timestamp."""
jadmanski10646442008-08-13 14:05:21 +0000682 warnings = []
683 while True:
684 # pull in a line of output from every logger that has
685 # output ready to be read
mbligh2b92b862008-11-22 13:25:32 +0000686 loggers, _, _ = select.select(self.warning_loggers, [], [], 0)
jadmanski10646442008-08-13 14:05:21 +0000687 closed_loggers = set()
688 for logger in loggers:
689 line = logger.readline()
690 # record any broken pipes (aka line == empty)
691 if len(line) == 0:
692 closed_loggers.add(logger)
693 continue
jadmanskif37df842009-02-11 00:03:26 +0000694 # parse out the warning
695 timestamp, msgtype, msg = line.split('\t', 2)
696 timestamp = int(timestamp)
697 # if the warning is valid, add it to the results
698 if self.warning_manager.is_valid(timestamp, msgtype):
699 warnings.append((timestamp, msg.strip()))
jadmanski10646442008-08-13 14:05:21 +0000700
701 # stop listening to loggers that are closed
702 self.warning_loggers -= closed_loggers
703
704 # stop if none of the loggers have any output left
705 if not loggers:
706 break
707
708 # sort into timestamp order
709 warnings.sort()
710 return warnings
711
712
showardcc929362010-01-25 21:20:41 +0000713 def _unique_subdirectory(self, base_subdirectory_name):
714 """Compute a unique results subdirectory based on the given name.
715
716 Appends base_subdirectory_name with a number as necessary to find a
717 directory name that doesn't already exist.
718 """
719 subdirectory = base_subdirectory_name
720 counter = 1
721 while os.path.exists(os.path.join(self.resultdir, subdirectory)):
722 subdirectory = base_subdirectory_name + '.' + str(counter)
723 counter += 1
724 return subdirectory
725
726
727 def record_summary(self, status_code, test_name, reason='', attributes=None,
728 distinguishing_attributes=(), child_test_ids=None):
729 """Record a summary test result.
730
731 @param status_code: status code string, see
732 common_lib.log.is_valid_status()
733 @param test_name: name of the test
734 @param reason: (optional) string providing detailed reason for test
735 outcome
736 @param attributes: (optional) dict of string keyvals to associate with
737 this result
738 @param distinguishing_attributes: (optional) list of attribute names
739 that should be used to distinguish identically-named test
740 results. These attributes should be present in the attributes
741 parameter. This is used to generate user-friendly subdirectory
742 names.
743 @param child_test_ids: (optional) list of test indices for test results
744 used in generating this result.
745 """
746 subdirectory_name_parts = [test_name]
747 for attribute in distinguishing_attributes:
748 assert attributes
749 assert attribute in attributes, '%s not in %s' % (attribute,
750 attributes)
751 subdirectory_name_parts.append(attributes[attribute])
752 base_subdirectory_name = '.'.join(subdirectory_name_parts)
753
754 subdirectory = self._unique_subdirectory(base_subdirectory_name)
755 subdirectory_path = os.path.join(self.resultdir, subdirectory)
756 os.mkdir(subdirectory_path)
757
758 self.record(status_code, subdirectory, test_name,
759 status=reason, optional_fields={'is_summary': True})
760
761 if attributes:
762 utils.write_keyval(subdirectory_path, attributes)
763
764 if child_test_ids:
765 ids_string = ','.join(str(test_id) for test_id in child_test_ids)
766 summary_data = {'child_test_ids': ids_string}
767 utils.write_keyval(os.path.join(subdirectory_path, 'summary_data'),
768 summary_data)
769
770
jadmanski16a7ff72009-04-01 18:19:53 +0000771 def disable_warnings(self, warning_type):
jadmanskif37df842009-02-11 00:03:26 +0000772 self.warning_manager.disable_warnings(warning_type)
jadmanski16a7ff72009-04-01 18:19:53 +0000773 self.record("INFO", None, None,
774 "disabling %s warnings" % warning_type,
775 {"warnings.disable": warning_type})
jadmanskif37df842009-02-11 00:03:26 +0000776
777
jadmanski16a7ff72009-04-01 18:19:53 +0000778 def enable_warnings(self, warning_type):
jadmanskif37df842009-02-11 00:03:26 +0000779 self.warning_manager.enable_warnings(warning_type)
jadmanski16a7ff72009-04-01 18:19:53 +0000780 self.record("INFO", None, None,
781 "enabling %s warnings" % warning_type,
782 {"warnings.enable": warning_type})
jadmanskif37df842009-02-11 00:03:26 +0000783
784
jadmanski779bd292009-03-19 17:33:33 +0000785 def get_status_log_path(self, subdir=None):
786 """Return the path to the job status log.
787
788 @param subdir - Optional paramter indicating that you want the path
789 to a subdirectory status log.
790
791 @returns The path where the status log should be.
792 """
mbligh210bae62009-04-01 18:33:13 +0000793 if self.resultdir:
794 if subdir:
795 return os.path.join(self.resultdir, subdir, "status.log")
796 else:
797 return os.path.join(self.resultdir, "status.log")
jadmanski779bd292009-03-19 17:33:33 +0000798 else:
mbligh210bae62009-04-01 18:33:13 +0000799 return None
jadmanski779bd292009-03-19 17:33:33 +0000800
801
jadmanski6bb32d72009-03-19 20:25:24 +0000802 def _update_uncollected_logs_list(self, update_func):
803 """Updates the uncollected logs list in a multi-process safe manner.
804
805 @param update_func - a function that updates the list of uncollected
806 logs. Should take one parameter, the list to be updated.
807 """
mbligh0d0f67d2009-11-06 03:15:03 +0000808 if self._uncollected_log_file:
809 log_file = open(self._uncollected_log_file, "r+")
mbligha788dc42009-03-26 21:10:16 +0000810 fcntl.flock(log_file, fcntl.LOCK_EX)
jadmanski6bb32d72009-03-19 20:25:24 +0000811 try:
812 uncollected_logs = pickle.load(log_file)
813 update_func(uncollected_logs)
814 log_file.seek(0)
815 log_file.truncate()
816 pickle.dump(uncollected_logs, log_file)
jadmanski3bff9092009-04-22 18:09:47 +0000817 log_file.flush()
jadmanski6bb32d72009-03-19 20:25:24 +0000818 finally:
819 fcntl.flock(log_file, fcntl.LOCK_UN)
820 log_file.close()
821
822
823 def add_client_log(self, hostname, remote_path, local_path):
824 """Adds a new set of client logs to the list of uncollected logs,
825 to allow for future log recovery.
826
827 @param host - the hostname of the machine holding the logs
828 @param remote_path - the directory on the remote machine holding logs
829 @param local_path - the local directory to copy the logs into
830 """
831 def update_func(logs_list):
832 logs_list.append((hostname, remote_path, local_path))
833 self._update_uncollected_logs_list(update_func)
834
835
836 def remove_client_log(self, hostname, remote_path, local_path):
837 """Removes a set of client logs from the list of uncollected logs,
838 to allow for future log recovery.
839
840 @param host - the hostname of the machine holding the logs
841 @param remote_path - the directory on the remote machine holding logs
842 @param local_path - the local directory to copy the logs into
843 """
844 def update_func(logs_list):
845 logs_list.remove((hostname, remote_path, local_path))
846 self._update_uncollected_logs_list(update_func)
847
848
mbligh0d0f67d2009-11-06 03:15:03 +0000849 def get_client_logs(self):
850 """Retrieves the list of uncollected logs, if it exists.
851
852 @returns A list of (host, remote_path, local_path) tuples. Returns
853 an empty list if no uncollected logs file exists.
854 """
855 log_exists = (self._uncollected_log_file and
856 os.path.exists(self._uncollected_log_file))
857 if log_exists:
858 return pickle.load(open(self._uncollected_log_file))
859 else:
860 return []
861
862
jadmanski10646442008-08-13 14:05:21 +0000863 def _render_record(self, status_code, subdir, operation, status='',
864 epoch_time=None, record_prefix=None,
865 optional_fields=None):
866 """
867 Internal Function to generate a record to be written into a
868 status log. For use by server_job.* classes only.
869 """
870 if subdir:
871 if re.match(r'[\n\t]', subdir):
mbligh2b92b862008-11-22 13:25:32 +0000872 raise ValueError('Invalid character in subdir string')
jadmanski10646442008-08-13 14:05:21 +0000873 substr = subdir
874 else:
875 substr = '----'
876
mbligh1b3b3762008-09-25 02:46:34 +0000877 if not log.is_valid_status(status_code):
mbligh2b92b862008-11-22 13:25:32 +0000878 raise ValueError('Invalid status code supplied: %s' % status_code)
jadmanski10646442008-08-13 14:05:21 +0000879 if not operation:
880 operation = '----'
881 if re.match(r'[\n\t]', operation):
mbligh2b92b862008-11-22 13:25:32 +0000882 raise ValueError('Invalid character in operation string')
jadmanski10646442008-08-13 14:05:21 +0000883 operation = operation.rstrip()
884 status = status.rstrip()
885 status = re.sub(r"\t", " ", status)
886 # Ensure any continuation lines are marked so we can
887 # detect them in the status file to ensure it is parsable.
mbligh0d0f67d2009-11-06 03:15:03 +0000888 status = re.sub(r"\n", "\n" + self._record_prefix + " ", status)
jadmanski10646442008-08-13 14:05:21 +0000889
890 if not optional_fields:
891 optional_fields = {}
892
893 # Generate timestamps for inclusion in the logs
894 if epoch_time is None:
895 epoch_time = int(time.time())
896 local_time = time.localtime(epoch_time)
897 optional_fields["timestamp"] = str(epoch_time)
898 optional_fields["localtime"] = time.strftime("%b %d %H:%M:%S",
899 local_time)
900
901 fields = [status_code, substr, operation]
902 fields += ["%s=%s" % x for x in optional_fields.iteritems()]
903 fields.append(status)
904
905 if record_prefix is None:
mbligh0d0f67d2009-11-06 03:15:03 +0000906 record_prefix = self._record_prefix
jadmanski10646442008-08-13 14:05:21 +0000907
908 msg = '\t'.join(str(x) for x in fields)
jadmanski10646442008-08-13 14:05:21 +0000909 return record_prefix + msg + '\n'
910
911
912 def _record_prerendered(self, msg):
913 """
914 Record a pre-rendered msg into the status logs. The only
915 change this makes to the message is to add on the local
916 indentation. Should not be called outside of server_job.*
917 classes. Unlike _record, this does not write the message
918 to standard output.
919 """
920 lines = []
jadmanski779bd292009-03-19 17:33:33 +0000921 status_file = self.get_status_log_path()
jadmanski10646442008-08-13 14:05:21 +0000922 status_log = open(status_file, 'a')
923 for line in msg.splitlines():
mbligh0d0f67d2009-11-06 03:15:03 +0000924 line = self._record_prefix + line + '\n'
jadmanski10646442008-08-13 14:05:21 +0000925 lines.append(line)
926 status_log.write(line)
927 status_log.close()
928 self.__parse_status(lines)
929
930
mbligh084bc172008-10-18 14:02:45 +0000931 def _fill_server_control_namespace(self, namespace, protect=True):
mbligh2b92b862008-11-22 13:25:32 +0000932 """
933 Prepare a namespace to be used when executing server control files.
mbligh084bc172008-10-18 14:02:45 +0000934
935 This sets up the control file API by importing modules and making them
936 available under the appropriate names within namespace.
937
938 For use by _execute_code().
939
940 Args:
941 namespace: The namespace dictionary to fill in.
942 protect: Boolean. If True (the default) any operation that would
943 clobber an existing entry in namespace will cause an error.
944 Raises:
945 error.AutoservError: When a name would be clobbered by import.
946 """
947 def _import_names(module_name, names=()):
mbligh2b92b862008-11-22 13:25:32 +0000948 """
949 Import a module and assign named attributes into namespace.
mbligh084bc172008-10-18 14:02:45 +0000950
951 Args:
952 module_name: The string module name.
953 names: A limiting list of names to import from module_name. If
954 empty (the default), all names are imported from the module
955 similar to a "from foo.bar import *" statement.
956 Raises:
957 error.AutoservError: When a name being imported would clobber
958 a name already in namespace.
959 """
960 module = __import__(module_name, {}, {}, names)
961
962 # No names supplied? Import * from the lowest level module.
963 # (Ugh, why do I have to implement this part myself?)
964 if not names:
965 for submodule_name in module_name.split('.')[1:]:
966 module = getattr(module, submodule_name)
967 if hasattr(module, '__all__'):
968 names = getattr(module, '__all__')
969 else:
970 names = dir(module)
971
972 # Install each name into namespace, checking to make sure it
973 # doesn't override anything that already exists.
974 for name in names:
975 # Check for conflicts to help prevent future problems.
976 if name in namespace and protect:
977 if namespace[name] is not getattr(module, name):
978 raise error.AutoservError('importing name '
979 '%s from %s %r would override %r' %
980 (name, module_name, getattr(module, name),
981 namespace[name]))
982 else:
983 # Encourage cleanliness and the use of __all__ for a
984 # more concrete API with less surprises on '*' imports.
985 warnings.warn('%s (%r) being imported from %s for use '
986 'in server control files is not the '
987 'first occurrance of that import.' %
988 (name, namespace[name], module_name))
989
990 namespace[name] = getattr(module, name)
991
992
993 # This is the equivalent of prepending a bunch of import statements to
994 # the front of the control script.
mbligha2b07dd2009-06-22 18:26:13 +0000995 namespace.update(os=os, sys=sys, logging=logging)
mbligh084bc172008-10-18 14:02:45 +0000996 _import_names('autotest_lib.server',
997 ('hosts', 'autotest', 'kvm', 'git', 'standalone_profiler',
998 'source_kernel', 'rpm_kernel', 'deb_kernel', 'git_kernel'))
999 _import_names('autotest_lib.server.subcommand',
1000 ('parallel', 'parallel_simple', 'subcommand'))
1001 _import_names('autotest_lib.server.utils',
1002 ('run', 'get_tmp_dir', 'sh_escape', 'parse_machine'))
1003 _import_names('autotest_lib.client.common_lib.error')
1004 _import_names('autotest_lib.client.common_lib.barrier', ('barrier',))
1005
1006 # Inject ourself as the job object into other classes within the API.
1007 # (Yuck, this injection is a gross thing be part of a public API. -gps)
1008 #
1009 # XXX Base & SiteAutotest do not appear to use .job. Who does?
1010 namespace['autotest'].Autotest.job = self
1011 # server.hosts.base_classes.Host uses .job.
1012 namespace['hosts'].Host.job = self
1013
1014
1015 def _execute_code(self, code_file, namespace, protect=True):
mbligh2b92b862008-11-22 13:25:32 +00001016 """
1017 Execute code using a copy of namespace as a server control script.
mbligh084bc172008-10-18 14:02:45 +00001018
1019 Unless protect_namespace is explicitly set to False, the dict will not
1020 be modified.
1021
1022 Args:
1023 code_file: The filename of the control file to execute.
1024 namespace: A dict containing names to make available during execution.
1025 protect: Boolean. If True (the default) a copy of the namespace dict
1026 is used during execution to prevent the code from modifying its
1027 contents outside of this function. If False the raw dict is
1028 passed in and modifications will be allowed.
1029 """
1030 if protect:
1031 namespace = namespace.copy()
1032 self._fill_server_control_namespace(namespace, protect=protect)
1033 # TODO: Simplify and get rid of the special cases for only 1 machine.
showard3e66e8c2008-10-27 19:20:51 +00001034 if len(self.machines) > 1:
mbligh084bc172008-10-18 14:02:45 +00001035 machines_text = '\n'.join(self.machines) + '\n'
1036 # Only rewrite the file if it does not match our machine list.
1037 try:
1038 machines_f = open(MACHINES_FILENAME, 'r')
1039 existing_machines_text = machines_f.read()
1040 machines_f.close()
1041 except EnvironmentError:
1042 existing_machines_text = None
1043 if machines_text != existing_machines_text:
1044 utils.open_write_close(MACHINES_FILENAME, machines_text)
1045 execfile(code_file, namespace, namespace)
jadmanski10646442008-08-13 14:05:21 +00001046
1047
1048 def _record(self, status_code, subdir, operation, status='',
1049 epoch_time=None, optional_fields=None):
1050 """
1051 Actual function for recording a single line into the status
1052 logs. Should never be called directly, only by job.record as
1053 this would bypass the console monitor logging.
1054 """
1055
mbligh2b92b862008-11-22 13:25:32 +00001056 msg = self._render_record(status_code, subdir, operation, status,
1057 epoch_time, optional_fields=optional_fields)
jadmanski10646442008-08-13 14:05:21 +00001058
jadmanski779bd292009-03-19 17:33:33 +00001059 status_file = self.get_status_log_path()
jadmanski10646442008-08-13 14:05:21 +00001060 sys.stdout.write(msg)
mbligh210bae62009-04-01 18:33:13 +00001061 if status_file:
1062 open(status_file, "a").write(msg)
jadmanski10646442008-08-13 14:05:21 +00001063 if subdir:
jadmanski779bd292009-03-19 17:33:33 +00001064 sub_status_file = self.get_status_log_path(subdir)
1065 open(sub_status_file, "a").write(msg)
jadmanski10646442008-08-13 14:05:21 +00001066 self.__parse_status(msg.splitlines())
1067
1068
1069 def __parse_status(self, new_lines):
mbligh0d0f67d2009-11-06 03:15:03 +00001070 if not self._using_parser:
jadmanski10646442008-08-13 14:05:21 +00001071 return
1072 new_tests = self.parser.process_lines(new_lines)
1073 for test in new_tests:
1074 self.__insert_test(test)
1075
1076
1077 def __insert_test(self, test):
mbligh2b92b862008-11-22 13:25:32 +00001078 """
1079 An internal method to insert a new test result into the
jadmanski10646442008-08-13 14:05:21 +00001080 database. This method will not raise an exception, even if an
1081 error occurs during the insert, to avoid failing a test
1082 simply because of unexpected database issues."""
showard21baa452008-10-21 00:08:39 +00001083 self.num_tests_run += 1
1084 if status_lib.is_worse_than_or_equal_to(test.status, 'FAIL'):
1085 self.num_tests_failed += 1
jadmanski10646442008-08-13 14:05:21 +00001086 try:
1087 self.results_db.insert_test(self.job_model, test)
1088 except Exception:
1089 msg = ("WARNING: An unexpected error occured while "
1090 "inserting test results into the database. "
1091 "Ignoring error.\n" + traceback.format_exc())
1092 print >> sys.stderr, msg
1093
mblighcaa62c22008-04-07 21:51:17 +00001094
mblighfc3da5b2010-01-06 18:37:22 +00001095 def preprocess_client_state(self):
1096 """
1097 Produce a state file for initializing the state of a client job.
1098
1099 Creates a new client state file with all the current server state, as
1100 well as some pre-set client state.
1101
1102 @returns The path of the file the state was written into.
1103 """
1104 # initialize the sysinfo state
1105 self._state.set('client', 'sysinfo', self.sysinfo.serialize())
1106
1107 # dump the state out to a tempfile
1108 fd, file_path = tempfile.mkstemp(dir=self.tmpdir)
1109 os.close(fd)
mbligha2c99492010-01-27 22:59:50 +00001110
1111 # write_to_file doesn't need locking, we exclusively own file_path
mblighfc3da5b2010-01-06 18:37:22 +00001112 self._state.write_to_file(file_path)
1113 return file_path
1114
1115
1116 def postprocess_client_state(self, state_path):
1117 """
1118 Update the state of this job with the state from a client job.
1119
1120 Updates the state of the server side of a job with the final state
1121 of a client job that was run. Updates the non-client-specific state,
1122 pulls in some specific bits from the client-specific state, and then
1123 discards the rest. Removes the state file afterwards
1124
1125 @param state_file A path to the state file from the client.
1126 """
1127 # update the on-disk state
mblighfc3da5b2010-01-06 18:37:22 +00001128 try:
jadmanskib6e7bdb2010-04-13 16:00:39 +00001129 self._state.read_from_file(state_path)
mblighfc3da5b2010-01-06 18:37:22 +00001130 os.remove(state_path)
mbligha2c99492010-01-27 22:59:50 +00001131 except OSError, e:
mblighfc3da5b2010-01-06 18:37:22 +00001132 # ignore file-not-found errors
1133 if e.errno != errno.ENOENT:
1134 raise
jadmanskib6e7bdb2010-04-13 16:00:39 +00001135 else:
1136 logging.debug('Client state file %s not found', state_path)
mblighfc3da5b2010-01-06 18:37:22 +00001137
1138 # update the sysinfo state
1139 if self._state.has('client', 'sysinfo'):
1140 self.sysinfo.deserialize(self._state.get('client', 'sysinfo'))
1141
1142 # drop all the client-specific state
1143 self._state.discard_namespace('client')
1144
1145
mbligh0a883702010-04-21 01:58:34 +00001146 def clear_all_known_hosts(self):
1147 """Clears known hosts files for all AbstractSSHHosts."""
1148 for host in self.hosts:
1149 if isinstance(host, abstract_ssh.AbstractSSHHost):
1150 host.clear_known_hosts()
1151
1152
mbligha7007722009-01-13 00:37:11 +00001153site_server_job = utils.import_site_class(
1154 __file__, "autotest_lib.server.site_server_job", "site_server_job",
1155 base_server_job)
jadmanski0afbb632008-06-06 21:10:57 +00001156
mbligh0a8c3322009-04-28 18:32:19 +00001157class server_job(site_server_job):
jadmanski0afbb632008-06-06 21:10:57 +00001158 pass
jadmanskif37df842009-02-11 00:03:26 +00001159
1160
1161class warning_manager(object):
1162 """Class for controlling warning logs. Manages the enabling and disabling
1163 of warnings."""
1164 def __init__(self):
1165 # a map of warning types to a list of disabled time intervals
1166 self.disabled_warnings = {}
1167
1168
1169 def is_valid(self, timestamp, warning_type):
1170 """Indicates if a warning (based on the time it occured and its type)
1171 is a valid warning. A warning is considered "invalid" if this type of
1172 warning was marked as "disabled" at the time the warning occured."""
1173 disabled_intervals = self.disabled_warnings.get(warning_type, [])
1174 for start, end in disabled_intervals:
1175 if timestamp >= start and (end is None or timestamp < end):
1176 return False
1177 return True
1178
1179
1180 def disable_warnings(self, warning_type, current_time_func=time.time):
1181 """As of now, disables all further warnings of this type."""
1182 intervals = self.disabled_warnings.setdefault(warning_type, [])
1183 if not intervals or intervals[-1][1] is not None:
jadmanski16a7ff72009-04-01 18:19:53 +00001184 intervals.append((int(current_time_func()), None))
jadmanskif37df842009-02-11 00:03:26 +00001185
1186
1187 def enable_warnings(self, warning_type, current_time_func=time.time):
1188 """As of now, enables all further warnings of this type."""
1189 intervals = self.disabled_warnings.get(warning_type, [])
1190 if intervals and intervals[-1][1] is None:
jadmanski16a7ff72009-04-01 18:19:53 +00001191 intervals[-1] = (intervals[-1][0], int(current_time_func()))