blob: d4c01e7f2abc1688ebb42ce0f1d9a469b25a59c6 [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
jadmanski10646442008-08-13 14:05:21 +000016from autotest_lib.tko import db as tko_db, status_lib, utils as tko_utils
jadmanski10646442008-08-13 14:05:21 +000017
18
mbligh084bc172008-10-18 14:02:45 +000019def _control_segment_path(name):
20 """Get the pathname of the named control segment file."""
jadmanski10646442008-08-13 14:05:21 +000021 server_dir = os.path.dirname(os.path.abspath(__file__))
mbligh084bc172008-10-18 14:02:45 +000022 return os.path.join(server_dir, "control_segments", name)
jadmanski10646442008-08-13 14:05:21 +000023
24
mbligh084bc172008-10-18 14:02:45 +000025CLIENT_CONTROL_FILENAME = 'control'
26SERVER_CONTROL_FILENAME = 'control.srv'
27MACHINES_FILENAME = '.machines'
jadmanski10646442008-08-13 14:05:21 +000028
mbligh084bc172008-10-18 14:02:45 +000029CLIENT_WRAPPER_CONTROL_FILE = _control_segment_path('client_wrapper')
30CRASHDUMPS_CONTROL_FILE = _control_segment_path('crashdumps')
31CRASHINFO_CONTROL_FILE = _control_segment_path('crashinfo')
mbligh084bc172008-10-18 14:02:45 +000032INSTALL_CONTROL_FILE = _control_segment_path('install')
showard45ae8192008-11-05 19:32:53 +000033CLEANUP_CONTROL_FILE = _control_segment_path('cleanup')
jadmanski10646442008-08-13 14:05:21 +000034
mbligh084bc172008-10-18 14:02:45 +000035VERIFY_CONTROL_FILE = _control_segment_path('verify')
mbligh084bc172008-10-18 14:02:45 +000036REPAIR_CONTROL_FILE = _control_segment_path('repair')
jadmanski10646442008-08-13 14:05:21 +000037
38
mbligh062ed152009-01-13 00:57:14 +000039# by default provide a stub that generates no site data
40def _get_site_job_data_dummy(job):
41 return {}
42
43
jadmanski10646442008-08-13 14:05:21 +000044# load up site-specific code for generating site-specific job data
mbligh062ed152009-01-13 00:57:14 +000045get_site_job_data = utils.import_site_function(__file__,
jadmanskic0a623d2009-03-03 21:11:48 +000046 "autotest_lib.server.site_server_job", "get_site_job_data",
mbligh062ed152009-01-13 00:57:14 +000047 _get_site_job_data_dummy)
jadmanski10646442008-08-13 14:05:21 +000048
49
mbligh0d0f67d2009-11-06 03:15:03 +000050class base_server_job(base_job.base_job):
51 """The server-side concrete implementation of base_job.
jadmanski10646442008-08-13 14:05:21 +000052
mbligh0d0f67d2009-11-06 03:15:03 +000053 Optional properties provided by this implementation:
54 serverdir
55 conmuxdir
56
57 num_tests_run
58 num_tests_failed
59
60 warning_manager
61 warning_loggers
jadmanski10646442008-08-13 14:05:21 +000062 """
63
mbligh0d0f67d2009-11-06 03:15:03 +000064 _STATUS_VERSION = 1
jadmanski10646442008-08-13 14:05:21 +000065
66 def __init__(self, control, args, resultdir, label, user, machines,
67 client=False, parse_job='',
mbligh374f3412009-05-13 21:29:45 +000068 ssh_user='root', ssh_port=22, ssh_pass='',
mblighe0cbc912010-03-11 18:03:07 +000069 group_name='', tag='',
70 control_filename=SERVER_CONTROL_FILENAME):
jadmanski10646442008-08-13 14:05:21 +000071 """
mbligh374f3412009-05-13 21:29:45 +000072 Create a server side job object.
mblighb5dac432008-11-27 00:38:44 +000073
mblighe7d9c602009-07-02 19:02:33 +000074 @param control: The pathname of the control file.
75 @param args: Passed to the control file.
76 @param resultdir: Where to throw the results.
77 @param label: Description of the job.
78 @param user: Username for the job (email address).
79 @param client: True if this is a client-side control file.
80 @param parse_job: string, if supplied it is the job execution tag that
81 the results will be passed through to the TKO parser with.
82 @param ssh_user: The SSH username. [root]
83 @param ssh_port: The SSH port number. [22]
84 @param ssh_pass: The SSH passphrase, if needed.
85 @param group_name: If supplied, this will be written out as
mbligh374f3412009-05-13 21:29:45 +000086 host_group_name in the keyvals file for the parser.
mblighe7d9c602009-07-02 19:02:33 +000087 @param tag: The job execution tag from the scheduler. [optional]
mblighe0cbc912010-03-11 18:03:07 +000088 @param control_filename: The filename where the server control file
89 should be written in the results directory.
jadmanski10646442008-08-13 14:05:21 +000090 """
mbligh0d0f67d2009-11-06 03:15:03 +000091 super(base_server_job, self).__init__(resultdir=resultdir)
mbligha788dc42009-03-26 21:10:16 +000092
mbligh0d0f67d2009-11-06 03:15:03 +000093 path = os.path.dirname(__file__)
94 self.control = control
95 self._uncollected_log_file = os.path.join(self.resultdir,
96 'uncollected_logs')
97 debugdir = os.path.join(self.resultdir, 'debug')
98 if not os.path.exists(debugdir):
99 os.mkdir(debugdir)
100
101 if user:
102 self.user = user
103 else:
104 self.user = getpass.getuser()
105
106 self._args = args
jadmanski10646442008-08-13 14:05:21 +0000107 self.machines = machines
mbligh0d0f67d2009-11-06 03:15:03 +0000108 self._client = client
109 self._record_prefix = ''
jadmanski10646442008-08-13 14:05:21 +0000110 self.warning_loggers = set()
jadmanskif37df842009-02-11 00:03:26 +0000111 self.warning_manager = warning_manager()
mbligh0d0f67d2009-11-06 03:15:03 +0000112 self._ssh_user = ssh_user
113 self._ssh_port = ssh_port
114 self._ssh_pass = ssh_pass
mblighe7d9c602009-07-02 19:02:33 +0000115 self.tag = tag
mbligh09108442008-10-15 16:27:38 +0000116 self.last_boot_tag = None
jadmanski53aaf382008-11-17 16:22:31 +0000117 self.hosts = set()
mbligh0d0f67d2009-11-06 03:15:03 +0000118 self.drop_caches = False
mblighb5dac432008-11-27 00:38:44 +0000119 self.drop_caches_between_iterations = False
mblighe0cbc912010-03-11 18:03:07 +0000120 self._control_filename = control_filename
jadmanski10646442008-08-13 14:05:21 +0000121
showard75cdfee2009-06-10 17:40:41 +0000122 self.logging = logging_manager.get_logging_manager(
123 manage_stdout_and_stderr=True, redirect_fds=True)
124 subcommand.logging_manager_object = self.logging
jadmanski10646442008-08-13 14:05:21 +0000125
mbligh0d0f67d2009-11-06 03:15:03 +0000126 self.sysinfo = sysinfo.sysinfo(self.resultdir)
jadmanski043e1132008-11-19 17:10:32 +0000127 self.profilers = profilers.profilers(self)
jadmanskic09fc152008-10-15 17:56:59 +0000128
jadmanski10646442008-08-13 14:05:21 +0000129 job_data = {'label' : label, 'user' : user,
130 'hostname' : ','.join(machines),
mbligh0d0f67d2009-11-06 03:15:03 +0000131 'status_version' : str(self._STATUS_VERSION),
showard170873e2009-01-07 00:22:26 +0000132 'job_started' : str(int(time.time()))}
mbligh374f3412009-05-13 21:29:45 +0000133 if group_name:
134 job_data['host_group_name'] = group_name
jadmanski10646442008-08-13 14:05:21 +0000135
mbligh0d0f67d2009-11-06 03:15:03 +0000136 # only write these keyvals out on the first job in a resultdir
137 if 'job_started' not in utils.read_keyval(self.resultdir):
138 job_data.update(get_site_job_data(self))
139 utils.write_keyval(self.resultdir, job_data)
140
141 self._parse_job = parse_job
showardcc929362010-01-25 21:20:41 +0000142 self._using_parser = (self._parse_job and len(machines) <= 1)
mbligh0d0f67d2009-11-06 03:15:03 +0000143 self.pkgmgr = packages.PackageManager(
144 self.autodir, run_function_dargs={'timeout':600})
showard21baa452008-10-21 00:08:39 +0000145 self.num_tests_run = 0
146 self.num_tests_failed = 0
147
jadmanski550fdc22008-11-20 16:32:08 +0000148 self._register_subcommand_hooks()
149
mbligh0d0f67d2009-11-06 03:15:03 +0000150 # these components aren't usable on the server
151 self.bootloader = None
152 self.harness = None
153
154
155 @classmethod
156 def _find_base_directories(cls):
157 """
158 Determine locations of autodir, clientdir and serverdir. Assumes
159 that this file is located within serverdir and uses __file__ along
160 with relative paths to resolve the location.
161 """
162 serverdir = os.path.abspath(os.path.dirname(__file__))
163 autodir = os.path.normpath(os.path.join(serverdir, '..'))
164 clientdir = os.path.join(autodir, 'client')
165 return autodir, clientdir, serverdir
166
167
168 def _find_resultdir(self, resultdir):
169 """
170 Determine the location of resultdir. For server jobs we expect one to
171 always be explicitly passed in to __init__, so just return that.
172 """
173 if resultdir:
174 return os.path.normpath(resultdir)
175 else:
176 return None
177
jadmanski550fdc22008-11-20 16:32:08 +0000178
jadmanskie432dd22009-01-30 15:04:51 +0000179 @staticmethod
180 def _load_control_file(path):
181 f = open(path)
182 try:
183 control_file = f.read()
184 finally:
185 f.close()
186 return re.sub('\r', '', control_file)
187
188
jadmanski550fdc22008-11-20 16:32:08 +0000189 def _register_subcommand_hooks(self):
mbligh2b92b862008-11-22 13:25:32 +0000190 """
191 Register some hooks into the subcommand modules that allow us
192 to properly clean up self.hosts created in forked subprocesses.
193 """
jadmanski550fdc22008-11-20 16:32:08 +0000194 def on_fork(cmd):
195 self._existing_hosts_on_fork = set(self.hosts)
196 def on_join(cmd):
197 new_hosts = self.hosts - self._existing_hosts_on_fork
198 for host in new_hosts:
199 host.close()
200 subcommand.subcommand.register_fork_hook(on_fork)
201 subcommand.subcommand.register_join_hook(on_join)
202
jadmanski10646442008-08-13 14:05:21 +0000203
mbligh4608b002010-01-05 18:22:35 +0000204 def init_parser(self):
mbligh2b92b862008-11-22 13:25:32 +0000205 """
mbligh4608b002010-01-05 18:22:35 +0000206 Start the continuous parsing of self.resultdir. This sets up
jadmanski10646442008-08-13 14:05:21 +0000207 the database connection and inserts the basic job object into
mbligh2b92b862008-11-22 13:25:32 +0000208 the database if necessary.
209 """
mbligh4608b002010-01-05 18:22:35 +0000210 if not self._using_parser:
211 return
jadmanski10646442008-08-13 14:05:21 +0000212 # redirect parser debugging to .parse.log
mbligh4608b002010-01-05 18:22:35 +0000213 parse_log = os.path.join(self.resultdir, '.parse.log')
jadmanski10646442008-08-13 14:05:21 +0000214 parse_log = open(parse_log, 'w', 0)
215 tko_utils.redirect_parser_debugging(parse_log)
216 # create a job model object and set up the db
217 self.results_db = tko_db.db(autocommit=True)
mbligh0d0f67d2009-11-06 03:15:03 +0000218 self.parser = status_lib.parser(self._STATUS_VERSION)
mbligh4608b002010-01-05 18:22:35 +0000219 self.job_model = self.parser.make_job(self.resultdir)
jadmanski10646442008-08-13 14:05:21 +0000220 self.parser.start(self.job_model)
221 # check if a job already exists in the db and insert it if
222 # it does not
mbligh0d0f67d2009-11-06 03:15:03 +0000223 job_idx = self.results_db.find_job(self._parse_job)
jadmanski10646442008-08-13 14:05:21 +0000224 if job_idx is None:
mbligh0d0f67d2009-11-06 03:15:03 +0000225 self.results_db.insert_job(self._parse_job, self.job_model)
jadmanski10646442008-08-13 14:05:21 +0000226 else:
mbligh2b92b862008-11-22 13:25:32 +0000227 machine_idx = self.results_db.lookup_machine(self.job_model.machine)
jadmanski10646442008-08-13 14:05:21 +0000228 self.job_model.index = job_idx
229 self.job_model.machine_idx = machine_idx
230
231
232 def cleanup_parser(self):
mbligh2b92b862008-11-22 13:25:32 +0000233 """
234 This should be called after the server job is finished
jadmanski10646442008-08-13 14:05:21 +0000235 to carry out any remaining cleanup (e.g. flushing any
mbligh2b92b862008-11-22 13:25:32 +0000236 remaining test results to the results db)
237 """
mbligh0d0f67d2009-11-06 03:15:03 +0000238 if not self._using_parser:
jadmanski10646442008-08-13 14:05:21 +0000239 return
240 final_tests = self.parser.end()
241 for test in final_tests:
242 self.__insert_test(test)
mbligh0d0f67d2009-11-06 03:15:03 +0000243 self._using_parser = False
jadmanski10646442008-08-13 14:05:21 +0000244
245
246 def verify(self):
247 if not self.machines:
mbligh084bc172008-10-18 14:02:45 +0000248 raise error.AutoservError('No machines specified to verify')
mbligh0fce4112008-11-27 00:37:17 +0000249 if self.resultdir:
250 os.chdir(self.resultdir)
jadmanski10646442008-08-13 14:05:21 +0000251 try:
jadmanskicdd0c402008-09-19 21:21:31 +0000252 namespace = {'machines' : self.machines, 'job' : self,
mbligh0d0f67d2009-11-06 03:15:03 +0000253 'ssh_user' : self._ssh_user,
254 'ssh_port' : self._ssh_port,
255 'ssh_pass' : self._ssh_pass}
mbligh084bc172008-10-18 14:02:45 +0000256 self._execute_code(VERIFY_CONTROL_FILE, namespace, protect=False)
jadmanski10646442008-08-13 14:05:21 +0000257 except Exception, e:
mbligh2b92b862008-11-22 13:25:32 +0000258 msg = ('Verify failed\n' + str(e) + '\n' + traceback.format_exc())
jadmanski10646442008-08-13 14:05:21 +0000259 self.record('ABORT', None, None, msg)
260 raise
261
262
263 def repair(self, host_protection):
264 if not self.machines:
265 raise error.AutoservError('No machines specified to repair')
mbligh0fce4112008-11-27 00:37:17 +0000266 if self.resultdir:
267 os.chdir(self.resultdir)
jadmanski10646442008-08-13 14:05:21 +0000268 namespace = {'machines': self.machines, 'job': self,
mbligh0d0f67d2009-11-06 03:15:03 +0000269 'ssh_user': self._ssh_user, 'ssh_port': self._ssh_port,
270 'ssh_pass': self._ssh_pass,
jadmanski10646442008-08-13 14:05:21 +0000271 'protection_level': host_protection}
mbligh25c0b8c2009-01-24 01:44:17 +0000272
mbligh0931b0a2009-04-08 17:44:48 +0000273 self._execute_code(REPAIR_CONTROL_FILE, namespace, protect=False)
jadmanski10646442008-08-13 14:05:21 +0000274
275
276 def precheck(self):
277 """
278 perform any additional checks in derived classes.
279 """
280 pass
281
282
283 def enable_external_logging(self):
mbligh2b92b862008-11-22 13:25:32 +0000284 """
285 Start or restart external logging mechanism.
jadmanski10646442008-08-13 14:05:21 +0000286 """
287 pass
288
289
290 def disable_external_logging(self):
mbligh2b92b862008-11-22 13:25:32 +0000291 """
292 Pause or stop external logging mechanism.
jadmanski10646442008-08-13 14:05:21 +0000293 """
294 pass
295
296
297 def use_external_logging(self):
mbligh2b92b862008-11-22 13:25:32 +0000298 """
299 Return True if external logging should be used.
jadmanski10646442008-08-13 14:05:21 +0000300 """
301 return False
302
303
mbligh415dc212009-06-15 21:53:34 +0000304 def _make_parallel_wrapper(self, function, machines, log):
305 """Wrap function as appropriate for calling by parallel_simple."""
mbligh2b92b862008-11-22 13:25:32 +0000306 is_forking = not (len(machines) == 1 and self.machines == machines)
mbligh0d0f67d2009-11-06 03:15:03 +0000307 if self._parse_job and is_forking and log:
jadmanski10646442008-08-13 14:05:21 +0000308 def wrapper(machine):
mbligh0d0f67d2009-11-06 03:15:03 +0000309 self._parse_job += "/" + machine
310 self._using_parser = True
jadmanski10646442008-08-13 14:05:21 +0000311 self.machines = [machine]
mbligh0d0f67d2009-11-06 03:15:03 +0000312 self.push_execution_context(machine)
jadmanski609a5f42008-08-26 20:52:42 +0000313 os.chdir(self.resultdir)
showard2bab8f42008-11-12 18:15:22 +0000314 utils.write_keyval(self.resultdir, {"hostname": machine})
mbligh4608b002010-01-05 18:22:35 +0000315 self.init_parser()
jadmanski10646442008-08-13 14:05:21 +0000316 result = function(machine)
317 self.cleanup_parser()
318 return result
jadmanski4dd1a002008-09-05 20:27:30 +0000319 elif len(machines) > 1 and log:
jadmanski10646442008-08-13 14:05:21 +0000320 def wrapper(machine):
mbligh0d0f67d2009-11-06 03:15:03 +0000321 self.push_execution_context(machine)
jadmanski609a5f42008-08-26 20:52:42 +0000322 os.chdir(self.resultdir)
mbligh838d82d2009-03-11 17:14:31 +0000323 machine_data = {'hostname' : machine,
mbligh0d0f67d2009-11-06 03:15:03 +0000324 'status_version' : str(self._STATUS_VERSION)}
mbligh838d82d2009-03-11 17:14:31 +0000325 utils.write_keyval(self.resultdir, machine_data)
jadmanski10646442008-08-13 14:05:21 +0000326 result = function(machine)
327 return result
328 else:
329 wrapper = function
mbligh415dc212009-06-15 21:53:34 +0000330 return wrapper
331
332
333 def parallel_simple(self, function, machines, log=True, timeout=None,
334 return_results=False):
335 """
336 Run 'function' using parallel_simple, with an extra wrapper to handle
337 the necessary setup for continuous parsing, if possible. If continuous
338 parsing is already properly initialized then this should just work.
339
340 @param function: A callable to run in parallel given each machine.
341 @param machines: A list of machine names to be passed one per subcommand
342 invocation of function.
343 @param log: If True, output will be written to output in a subdirectory
344 named after each machine.
345 @param timeout: Seconds after which the function call should timeout.
346 @param return_results: If True instead of an AutoServError being raised
347 on any error a list of the results|exceptions from the function
348 called on each arg is returned. [default: False]
349
350 @raises error.AutotestError: If any of the functions failed.
351 """
352 wrapper = self._make_parallel_wrapper(function, machines, log)
353 return subcommand.parallel_simple(wrapper, machines,
354 log=log, timeout=timeout,
355 return_results=return_results)
356
357
358 def parallel_on_machines(self, function, machines, timeout=None):
359 """
showardcd5fac42009-07-06 20:19:43 +0000360 @param function: Called in parallel with one machine as its argument.
mbligh415dc212009-06-15 21:53:34 +0000361 @param machines: A list of machines to call function(machine) on.
362 @param timeout: Seconds after which the function call should timeout.
363
364 @returns A list of machines on which function(machine) returned
365 without raising an exception.
366 """
showardcd5fac42009-07-06 20:19:43 +0000367 results = self.parallel_simple(function, machines, timeout=timeout,
mbligh415dc212009-06-15 21:53:34 +0000368 return_results=True)
369 success_machines = []
370 for result, machine in itertools.izip(results, machines):
371 if not isinstance(result, Exception):
372 success_machines.append(machine)
373 return success_machines
jadmanski10646442008-08-13 14:05:21 +0000374
375
mbligh0d0f67d2009-11-06 03:15:03 +0000376 _USE_TEMP_DIR = object()
mbligh2b92b862008-11-22 13:25:32 +0000377 def run(self, cleanup=False, install_before=False, install_after=False,
jadmanskie432dd22009-01-30 15:04:51 +0000378 collect_crashdumps=True, namespace={}, control=None,
jadmanskidef0c3c2009-03-25 20:07:10 +0000379 control_file_dir=None, only_collect_crashinfo=False):
jadmanskifb9c0fa2009-04-29 17:39:16 +0000380 # for a normal job, make sure the uncollected logs file exists
381 # for a crashinfo-only run it should already exist, bail out otherwise
mbligh0d0f67d2009-11-06 03:15:03 +0000382 if self.resultdir and not os.path.exists(self._uncollected_log_file):
jadmanskifb9c0fa2009-04-29 17:39:16 +0000383 if only_collect_crashinfo:
384 # if this is a crashinfo-only run, and there were no existing
385 # uncollected logs, just bail out early
386 logging.info("No existing uncollected logs, "
387 "skipping crashinfo collection")
388 return
389 else:
mbligh0d0f67d2009-11-06 03:15:03 +0000390 log_file = open(self._uncollected_log_file, "w")
jadmanskifb9c0fa2009-04-29 17:39:16 +0000391 pickle.dump([], log_file)
392 log_file.close()
393
jadmanski10646442008-08-13 14:05:21 +0000394 # use a copy so changes don't affect the original dictionary
395 namespace = namespace.copy()
396 machines = self.machines
jadmanskie432dd22009-01-30 15:04:51 +0000397 if control is None:
jadmanski02a3ba22009-11-13 20:47:27 +0000398 if self.control is None:
399 control = ''
400 else:
401 control = self._load_control_file(self.control)
jadmanskie432dd22009-01-30 15:04:51 +0000402 if control_file_dir is None:
403 control_file_dir = self.resultdir
jadmanski10646442008-08-13 14:05:21 +0000404
405 self.aborted = False
406 namespace['machines'] = machines
mbligh0d0f67d2009-11-06 03:15:03 +0000407 namespace['args'] = self._args
jadmanski10646442008-08-13 14:05:21 +0000408 namespace['job'] = self
mbligh0d0f67d2009-11-06 03:15:03 +0000409 namespace['ssh_user'] = self._ssh_user
410 namespace['ssh_port'] = self._ssh_port
411 namespace['ssh_pass'] = self._ssh_pass
jadmanski10646442008-08-13 14:05:21 +0000412 test_start_time = int(time.time())
413
mbligh80e1eba2008-11-19 00:26:18 +0000414 if self.resultdir:
415 os.chdir(self.resultdir)
jadmanski779bd292009-03-19 17:33:33 +0000416 # touch status.log so that the parser knows a job is running here
jadmanski382303a2009-04-21 19:53:39 +0000417 open(self.get_status_log_path(), 'a').close()
mbligh80e1eba2008-11-19 00:26:18 +0000418 self.enable_external_logging()
jadmanskie432dd22009-01-30 15:04:51 +0000419
jadmanskicdd0c402008-09-19 21:21:31 +0000420 collect_crashinfo = True
mblighaebe3b62008-12-22 14:45:40 +0000421 temp_control_file_dir = None
jadmanski10646442008-08-13 14:05:21 +0000422 try:
showardcf8d4922009-10-14 16:08:39 +0000423 try:
424 if install_before and machines:
425 self._execute_code(INSTALL_CONTROL_FILE, namespace)
jadmanskie432dd22009-01-30 15:04:51 +0000426
showardcf8d4922009-10-14 16:08:39 +0000427 if only_collect_crashinfo:
428 return
429
jadmanskidef0c3c2009-03-25 20:07:10 +0000430 # determine the dir to write the control files to
431 cfd_specified = (control_file_dir
mbligh0d0f67d2009-11-06 03:15:03 +0000432 and control_file_dir is not self._USE_TEMP_DIR)
jadmanskidef0c3c2009-03-25 20:07:10 +0000433 if cfd_specified:
434 temp_control_file_dir = None
435 else:
436 temp_control_file_dir = tempfile.mkdtemp(
437 suffix='temp_control_file_dir')
438 control_file_dir = temp_control_file_dir
439 server_control_file = os.path.join(control_file_dir,
mblighe0cbc912010-03-11 18:03:07 +0000440 self._control_filename)
jadmanskidef0c3c2009-03-25 20:07:10 +0000441 client_control_file = os.path.join(control_file_dir,
442 CLIENT_CONTROL_FILENAME)
mbligh0d0f67d2009-11-06 03:15:03 +0000443 if self._client:
jadmanskidef0c3c2009-03-25 20:07:10 +0000444 namespace['control'] = control
445 utils.open_write_close(client_control_file, control)
mblighfeac0102009-04-28 18:31:12 +0000446 shutil.copyfile(CLIENT_WRAPPER_CONTROL_FILE,
447 server_control_file)
jadmanskidef0c3c2009-03-25 20:07:10 +0000448 else:
449 utils.open_write_close(server_control_file, control)
mbligh26f0d882009-06-22 18:30:01 +0000450 logging.info("Processing control file")
jadmanskidef0c3c2009-03-25 20:07:10 +0000451 self._execute_code(server_control_file, namespace)
mbligh26f0d882009-06-22 18:30:01 +0000452 logging.info("Finished processing control file")
jadmanski10646442008-08-13 14:05:21 +0000453
jadmanskidef0c3c2009-03-25 20:07:10 +0000454 # no error occured, so we don't need to collect crashinfo
455 collect_crashinfo = False
showardcf8d4922009-10-14 16:08:39 +0000456 except:
457 try:
458 logging.exception(
459 'Exception escaped control file, job aborting:')
460 except:
461 pass # don't let logging exceptions here interfere
462 raise
jadmanski10646442008-08-13 14:05:21 +0000463 finally:
mblighaebe3b62008-12-22 14:45:40 +0000464 if temp_control_file_dir:
jadmanskie432dd22009-01-30 15:04:51 +0000465 # Clean up temp directory used for copies of the control files
mblighaebe3b62008-12-22 14:45:40 +0000466 try:
467 shutil.rmtree(temp_control_file_dir)
468 except Exception, e:
mblighe7d9c602009-07-02 19:02:33 +0000469 logging.warn('Could not remove temp directory %s: %s',
470 temp_control_file_dir, e)
jadmanskie432dd22009-01-30 15:04:51 +0000471
jadmanskicdd0c402008-09-19 21:21:31 +0000472 if machines and (collect_crashdumps or collect_crashinfo):
jadmanski10646442008-08-13 14:05:21 +0000473 namespace['test_start_time'] = test_start_time
jadmanskicdd0c402008-09-19 21:21:31 +0000474 if collect_crashinfo:
mbligh084bc172008-10-18 14:02:45 +0000475 # includes crashdumps
476 self._execute_code(CRASHINFO_CONTROL_FILE, namespace)
jadmanskicdd0c402008-09-19 21:21:31 +0000477 else:
mbligh084bc172008-10-18 14:02:45 +0000478 self._execute_code(CRASHDUMPS_CONTROL_FILE, namespace)
mbligh0d0f67d2009-11-06 03:15:03 +0000479 if self._uncollected_log_file:
480 os.remove(self._uncollected_log_file)
jadmanski10646442008-08-13 14:05:21 +0000481 self.disable_external_logging()
showard45ae8192008-11-05 19:32:53 +0000482 if cleanup and machines:
483 self._execute_code(CLEANUP_CONTROL_FILE, namespace)
jadmanski10646442008-08-13 14:05:21 +0000484 if install_after and machines:
mbligh084bc172008-10-18 14:02:45 +0000485 self._execute_code(INSTALL_CONTROL_FILE, namespace)
jadmanski10646442008-08-13 14:05:21 +0000486
487
488 def run_test(self, url, *args, **dargs):
mbligh2b92b862008-11-22 13:25:32 +0000489 """
490 Summon a test object and run it.
jadmanski10646442008-08-13 14:05:21 +0000491
492 tag
493 tag to add to testname
494 url
495 url of the test to run
496 """
mblighfc3da5b2010-01-06 18:37:22 +0000497 group, testname = self.pkgmgr.get_package_name(url, 'test')
498 testname, subdir, tag = self._build_tagged_test_name(testname, dargs)
499 outputdir = self._make_test_outputdir(subdir)
jadmanski10646442008-08-13 14:05:21 +0000500
501 def group_func():
502 try:
503 test.runtest(self, url, tag, args, dargs)
504 except error.TestBaseException, e:
505 self.record(e.exit_status, subdir, testname, str(e))
506 raise
507 except Exception, e:
508 info = str(e) + "\n" + traceback.format_exc()
509 self.record('FAIL', subdir, testname, info)
510 raise
511 else:
mbligh2b92b862008-11-22 13:25:32 +0000512 self.record('GOOD', subdir, testname, 'completed successfully')
jadmanskide292df2008-08-26 20:51:14 +0000513
514 result, exc_info = self._run_group(testname, subdir, group_func)
515 if exc_info and isinstance(exc_info[1], error.TestBaseException):
516 return False
517 elif exc_info:
518 raise exc_info[0], exc_info[1], exc_info[2]
519 else:
520 return True
jadmanski10646442008-08-13 14:05:21 +0000521
522
523 def _run_group(self, name, subdir, function, *args, **dargs):
524 """\
525 Underlying method for running something inside of a group.
526 """
jadmanskide292df2008-08-26 20:51:14 +0000527 result, exc_info = None, None
mbligh0d0f67d2009-11-06 03:15:03 +0000528 old_record_prefix = self._record_prefix
jadmanski10646442008-08-13 14:05:21 +0000529 try:
530 self.record('START', subdir, name)
mbligh0d0f67d2009-11-06 03:15:03 +0000531 self._record_prefix += '\t'
jadmanski10646442008-08-13 14:05:21 +0000532 try:
533 result = function(*args, **dargs)
534 finally:
mbligh0d0f67d2009-11-06 03:15:03 +0000535 self._record_prefix = old_record_prefix
jadmanski10646442008-08-13 14:05:21 +0000536 except error.TestBaseException, e:
jadmanskib88d6dc2009-01-10 00:33:18 +0000537 self.record("END %s" % e.exit_status, subdir, name)
jadmanskide292df2008-08-26 20:51:14 +0000538 exc_info = sys.exc_info()
jadmanski10646442008-08-13 14:05:21 +0000539 except Exception, e:
540 err_msg = str(e) + '\n'
541 err_msg += traceback.format_exc()
542 self.record('END ABORT', subdir, name, err_msg)
543 raise error.JobError(name + ' failed\n' + traceback.format_exc())
544 else:
545 self.record('END GOOD', subdir, name)
546
jadmanskide292df2008-08-26 20:51:14 +0000547 return result, exc_info
jadmanski10646442008-08-13 14:05:21 +0000548
549
550 def run_group(self, function, *args, **dargs):
551 """\
552 function:
553 subroutine to run
554 *args:
555 arguments for the function
556 """
557
558 name = function.__name__
559
560 # Allow the tag for the group to be specified.
561 tag = dargs.pop('tag', None)
562 if tag:
563 name = tag
564
jadmanskide292df2008-08-26 20:51:14 +0000565 return self._run_group(name, None, function, *args, **dargs)[0]
jadmanski10646442008-08-13 14:05:21 +0000566
567
568 def run_reboot(self, reboot_func, get_kernel_func):
569 """\
570 A specialization of run_group meant specifically for handling
571 a reboot. Includes support for capturing the kernel version
572 after the reboot.
573
574 reboot_func: a function that carries out the reboot
575
576 get_kernel_func: a function that returns a string
577 representing the kernel version.
578 """
579
mbligh0d0f67d2009-11-06 03:15:03 +0000580 old_record_prefix = self._record_prefix
jadmanski10646442008-08-13 14:05:21 +0000581 try:
582 self.record('START', None, 'reboot')
mbligh0d0f67d2009-11-06 03:15:03 +0000583 self._record_prefix += '\t'
jadmanski10646442008-08-13 14:05:21 +0000584 reboot_func()
585 except Exception, e:
mbligh0d0f67d2009-11-06 03:15:03 +0000586 self._record_prefix = old_record_prefix
jadmanski10646442008-08-13 14:05:21 +0000587 err_msg = str(e) + '\n' + traceback.format_exc()
588 self.record('END FAIL', None, 'reboot', err_msg)
jadmanski4b51d542009-04-08 14:17:16 +0000589 raise
jadmanski10646442008-08-13 14:05:21 +0000590 else:
591 kernel = get_kernel_func()
mbligh0d0f67d2009-11-06 03:15:03 +0000592 self._record_prefix = old_record_prefix
jadmanski10646442008-08-13 14:05:21 +0000593 self.record('END GOOD', None, 'reboot',
594 optional_fields={"kernel": kernel})
595
596
jadmanskie432dd22009-01-30 15:04:51 +0000597 def run_control(self, path):
598 """Execute a control file found at path (relative to the autotest
599 path). Intended for executing a control file within a control file,
600 not for running the top-level job control file."""
601 path = os.path.join(self.autodir, path)
602 control_file = self._load_control_file(path)
mbligh0d0f67d2009-11-06 03:15:03 +0000603 self.run(control=control_file, control_file_dir=self._USE_TEMP_DIR)
jadmanskie432dd22009-01-30 15:04:51 +0000604
605
jadmanskic09fc152008-10-15 17:56:59 +0000606 def add_sysinfo_command(self, command, logfile=None, on_every_test=False):
mbligh4395bbd2009-03-25 19:34:17 +0000607 self._add_sysinfo_loggable(sysinfo.command(command, logf=logfile),
jadmanskic09fc152008-10-15 17:56:59 +0000608 on_every_test)
609
610
611 def add_sysinfo_logfile(self, file, on_every_test=False):
612 self._add_sysinfo_loggable(sysinfo.logfile(file), on_every_test)
613
614
615 def _add_sysinfo_loggable(self, loggable, on_every_test):
616 if on_every_test:
617 self.sysinfo.test_loggables.add(loggable)
618 else:
619 self.sysinfo.boot_loggables.add(loggable)
620
621
jadmanski10646442008-08-13 14:05:21 +0000622 def record(self, status_code, subdir, operation, status='',
623 optional_fields=None):
624 """
625 Record job-level status
626
627 The intent is to make this file both machine parseable and
628 human readable. That involves a little more complexity, but
629 really isn't all that bad ;-)
630
631 Format is <status code>\t<subdir>\t<operation>\t<status>
632
mbligh1b3b3762008-09-25 02:46:34 +0000633 status code: see common_lib.log.is_valid_status()
jadmanski10646442008-08-13 14:05:21 +0000634 for valid status definition
635
636 subdir: MUST be a relevant subdirectory in the results,
637 or None, which will be represented as '----'
638
639 operation: description of what you ran (e.g. "dbench", or
640 "mkfs -t foobar /dev/sda9")
641
642 status: error message or "completed sucessfully"
643
644 ------------------------------------------------------------
645
646 Initial tabs indicate indent levels for grouping, and is
mbligh0d0f67d2009-11-06 03:15:03 +0000647 governed by self._record_prefix
jadmanski10646442008-08-13 14:05:21 +0000648
649 multiline messages have secondary lines prefaced by a double
650 space (' ')
651
652 Executing this method will trigger the logging of all new
653 warnings to date from the various console loggers.
654 """
655 # poll all our warning loggers for new warnings
656 warnings = self._read_warnings()
mbligh0d0f67d2009-11-06 03:15:03 +0000657 old_record_prefix = self._record_prefix
jadmanski2de83112009-04-01 18:21:04 +0000658 try:
659 if status_code.startswith("END "):
mbligh0d0f67d2009-11-06 03:15:03 +0000660 self._record_prefix += "\t"
jadmanski2de83112009-04-01 18:21:04 +0000661 for timestamp, msg in warnings:
662 self._record("WARN", None, None, msg, timestamp)
663 finally:
mbligh0d0f67d2009-11-06 03:15:03 +0000664 self._record_prefix = old_record_prefix
jadmanski10646442008-08-13 14:05:21 +0000665
666 # write out the actual status log line
667 self._record(status_code, subdir, operation, status,
668 optional_fields=optional_fields)
669
670
671 def _read_warnings(self):
jadmanskif37df842009-02-11 00:03:26 +0000672 """Poll all the warning loggers and extract any new warnings that have
673 been logged. If the warnings belong to a category that is currently
674 disabled, this method will discard them and they will no longer be
675 retrievable.
676
677 Returns a list of (timestamp, message) tuples, where timestamp is an
678 integer epoch timestamp."""
jadmanski10646442008-08-13 14:05:21 +0000679 warnings = []
680 while True:
681 # pull in a line of output from every logger that has
682 # output ready to be read
mbligh2b92b862008-11-22 13:25:32 +0000683 loggers, _, _ = select.select(self.warning_loggers, [], [], 0)
jadmanski10646442008-08-13 14:05:21 +0000684 closed_loggers = set()
685 for logger in loggers:
686 line = logger.readline()
687 # record any broken pipes (aka line == empty)
688 if len(line) == 0:
689 closed_loggers.add(logger)
690 continue
jadmanskif37df842009-02-11 00:03:26 +0000691 # parse out the warning
692 timestamp, msgtype, msg = line.split('\t', 2)
693 timestamp = int(timestamp)
694 # if the warning is valid, add it to the results
695 if self.warning_manager.is_valid(timestamp, msgtype):
696 warnings.append((timestamp, msg.strip()))
jadmanski10646442008-08-13 14:05:21 +0000697
698 # stop listening to loggers that are closed
699 self.warning_loggers -= closed_loggers
700
701 # stop if none of the loggers have any output left
702 if not loggers:
703 break
704
705 # sort into timestamp order
706 warnings.sort()
707 return warnings
708
709
showardcc929362010-01-25 21:20:41 +0000710 def _unique_subdirectory(self, base_subdirectory_name):
711 """Compute a unique results subdirectory based on the given name.
712
713 Appends base_subdirectory_name with a number as necessary to find a
714 directory name that doesn't already exist.
715 """
716 subdirectory = base_subdirectory_name
717 counter = 1
718 while os.path.exists(os.path.join(self.resultdir, subdirectory)):
719 subdirectory = base_subdirectory_name + '.' + str(counter)
720 counter += 1
721 return subdirectory
722
723
724 def record_summary(self, status_code, test_name, reason='', attributes=None,
725 distinguishing_attributes=(), child_test_ids=None):
726 """Record a summary test result.
727
728 @param status_code: status code string, see
729 common_lib.log.is_valid_status()
730 @param test_name: name of the test
731 @param reason: (optional) string providing detailed reason for test
732 outcome
733 @param attributes: (optional) dict of string keyvals to associate with
734 this result
735 @param distinguishing_attributes: (optional) list of attribute names
736 that should be used to distinguish identically-named test
737 results. These attributes should be present in the attributes
738 parameter. This is used to generate user-friendly subdirectory
739 names.
740 @param child_test_ids: (optional) list of test indices for test results
741 used in generating this result.
742 """
743 subdirectory_name_parts = [test_name]
744 for attribute in distinguishing_attributes:
745 assert attributes
746 assert attribute in attributes, '%s not in %s' % (attribute,
747 attributes)
748 subdirectory_name_parts.append(attributes[attribute])
749 base_subdirectory_name = '.'.join(subdirectory_name_parts)
750
751 subdirectory = self._unique_subdirectory(base_subdirectory_name)
752 subdirectory_path = os.path.join(self.resultdir, subdirectory)
753 os.mkdir(subdirectory_path)
754
755 self.record(status_code, subdirectory, test_name,
756 status=reason, optional_fields={'is_summary': True})
757
758 if attributes:
759 utils.write_keyval(subdirectory_path, attributes)
760
761 if child_test_ids:
762 ids_string = ','.join(str(test_id) for test_id in child_test_ids)
763 summary_data = {'child_test_ids': ids_string}
764 utils.write_keyval(os.path.join(subdirectory_path, 'summary_data'),
765 summary_data)
766
767
jadmanski16a7ff72009-04-01 18:19:53 +0000768 def disable_warnings(self, warning_type):
jadmanskif37df842009-02-11 00:03:26 +0000769 self.warning_manager.disable_warnings(warning_type)
jadmanski16a7ff72009-04-01 18:19:53 +0000770 self.record("INFO", None, None,
771 "disabling %s warnings" % warning_type,
772 {"warnings.disable": warning_type})
jadmanskif37df842009-02-11 00:03:26 +0000773
774
jadmanski16a7ff72009-04-01 18:19:53 +0000775 def enable_warnings(self, warning_type):
jadmanskif37df842009-02-11 00:03:26 +0000776 self.warning_manager.enable_warnings(warning_type)
jadmanski16a7ff72009-04-01 18:19:53 +0000777 self.record("INFO", None, None,
778 "enabling %s warnings" % warning_type,
779 {"warnings.enable": warning_type})
jadmanskif37df842009-02-11 00:03:26 +0000780
781
jadmanski779bd292009-03-19 17:33:33 +0000782 def get_status_log_path(self, subdir=None):
783 """Return the path to the job status log.
784
785 @param subdir - Optional paramter indicating that you want the path
786 to a subdirectory status log.
787
788 @returns The path where the status log should be.
789 """
mbligh210bae62009-04-01 18:33:13 +0000790 if self.resultdir:
791 if subdir:
792 return os.path.join(self.resultdir, subdir, "status.log")
793 else:
794 return os.path.join(self.resultdir, "status.log")
jadmanski779bd292009-03-19 17:33:33 +0000795 else:
mbligh210bae62009-04-01 18:33:13 +0000796 return None
jadmanski779bd292009-03-19 17:33:33 +0000797
798
jadmanski6bb32d72009-03-19 20:25:24 +0000799 def _update_uncollected_logs_list(self, update_func):
800 """Updates the uncollected logs list in a multi-process safe manner.
801
802 @param update_func - a function that updates the list of uncollected
803 logs. Should take one parameter, the list to be updated.
804 """
mbligh0d0f67d2009-11-06 03:15:03 +0000805 if self._uncollected_log_file:
806 log_file = open(self._uncollected_log_file, "r+")
mbligha788dc42009-03-26 21:10:16 +0000807 fcntl.flock(log_file, fcntl.LOCK_EX)
jadmanski6bb32d72009-03-19 20:25:24 +0000808 try:
809 uncollected_logs = pickle.load(log_file)
810 update_func(uncollected_logs)
811 log_file.seek(0)
812 log_file.truncate()
813 pickle.dump(uncollected_logs, log_file)
jadmanski3bff9092009-04-22 18:09:47 +0000814 log_file.flush()
jadmanski6bb32d72009-03-19 20:25:24 +0000815 finally:
816 fcntl.flock(log_file, fcntl.LOCK_UN)
817 log_file.close()
818
819
820 def add_client_log(self, hostname, remote_path, local_path):
821 """Adds a new set of client logs to the list of uncollected logs,
822 to allow for future log recovery.
823
824 @param host - the hostname of the machine holding the logs
825 @param remote_path - the directory on the remote machine holding logs
826 @param local_path - the local directory to copy the logs into
827 """
828 def update_func(logs_list):
829 logs_list.append((hostname, remote_path, local_path))
830 self._update_uncollected_logs_list(update_func)
831
832
833 def remove_client_log(self, hostname, remote_path, local_path):
834 """Removes a set of client logs from the list of uncollected logs,
835 to allow for future log recovery.
836
837 @param host - the hostname of the machine holding the logs
838 @param remote_path - the directory on the remote machine holding logs
839 @param local_path - the local directory to copy the logs into
840 """
841 def update_func(logs_list):
842 logs_list.remove((hostname, remote_path, local_path))
843 self._update_uncollected_logs_list(update_func)
844
845
mbligh0d0f67d2009-11-06 03:15:03 +0000846 def get_client_logs(self):
847 """Retrieves the list of uncollected logs, if it exists.
848
849 @returns A list of (host, remote_path, local_path) tuples. Returns
850 an empty list if no uncollected logs file exists.
851 """
852 log_exists = (self._uncollected_log_file and
853 os.path.exists(self._uncollected_log_file))
854 if log_exists:
855 return pickle.load(open(self._uncollected_log_file))
856 else:
857 return []
858
859
jadmanski10646442008-08-13 14:05:21 +0000860 def _render_record(self, status_code, subdir, operation, status='',
861 epoch_time=None, record_prefix=None,
862 optional_fields=None):
863 """
864 Internal Function to generate a record to be written into a
865 status log. For use by server_job.* classes only.
866 """
867 if subdir:
868 if re.match(r'[\n\t]', subdir):
mbligh2b92b862008-11-22 13:25:32 +0000869 raise ValueError('Invalid character in subdir string')
jadmanski10646442008-08-13 14:05:21 +0000870 substr = subdir
871 else:
872 substr = '----'
873
mbligh1b3b3762008-09-25 02:46:34 +0000874 if not log.is_valid_status(status_code):
mbligh2b92b862008-11-22 13:25:32 +0000875 raise ValueError('Invalid status code supplied: %s' % status_code)
jadmanski10646442008-08-13 14:05:21 +0000876 if not operation:
877 operation = '----'
878 if re.match(r'[\n\t]', operation):
mbligh2b92b862008-11-22 13:25:32 +0000879 raise ValueError('Invalid character in operation string')
jadmanski10646442008-08-13 14:05:21 +0000880 operation = operation.rstrip()
881 status = status.rstrip()
882 status = re.sub(r"\t", " ", status)
883 # Ensure any continuation lines are marked so we can
884 # detect them in the status file to ensure it is parsable.
mbligh0d0f67d2009-11-06 03:15:03 +0000885 status = re.sub(r"\n", "\n" + self._record_prefix + " ", status)
jadmanski10646442008-08-13 14:05:21 +0000886
887 if not optional_fields:
888 optional_fields = {}
889
890 # Generate timestamps for inclusion in the logs
891 if epoch_time is None:
892 epoch_time = int(time.time())
893 local_time = time.localtime(epoch_time)
894 optional_fields["timestamp"] = str(epoch_time)
895 optional_fields["localtime"] = time.strftime("%b %d %H:%M:%S",
896 local_time)
897
898 fields = [status_code, substr, operation]
899 fields += ["%s=%s" % x for x in optional_fields.iteritems()]
900 fields.append(status)
901
902 if record_prefix is None:
mbligh0d0f67d2009-11-06 03:15:03 +0000903 record_prefix = self._record_prefix
jadmanski10646442008-08-13 14:05:21 +0000904
905 msg = '\t'.join(str(x) for x in fields)
jadmanski10646442008-08-13 14:05:21 +0000906 return record_prefix + msg + '\n'
907
908
909 def _record_prerendered(self, msg):
910 """
911 Record a pre-rendered msg into the status logs. The only
912 change this makes to the message is to add on the local
913 indentation. Should not be called outside of server_job.*
914 classes. Unlike _record, this does not write the message
915 to standard output.
916 """
917 lines = []
jadmanski779bd292009-03-19 17:33:33 +0000918 status_file = self.get_status_log_path()
jadmanski10646442008-08-13 14:05:21 +0000919 status_log = open(status_file, 'a')
920 for line in msg.splitlines():
mbligh0d0f67d2009-11-06 03:15:03 +0000921 line = self._record_prefix + line + '\n'
jadmanski10646442008-08-13 14:05:21 +0000922 lines.append(line)
923 status_log.write(line)
924 status_log.close()
925 self.__parse_status(lines)
926
927
mbligh084bc172008-10-18 14:02:45 +0000928 def _fill_server_control_namespace(self, namespace, protect=True):
mbligh2b92b862008-11-22 13:25:32 +0000929 """
930 Prepare a namespace to be used when executing server control files.
mbligh084bc172008-10-18 14:02:45 +0000931
932 This sets up the control file API by importing modules and making them
933 available under the appropriate names within namespace.
934
935 For use by _execute_code().
936
937 Args:
938 namespace: The namespace dictionary to fill in.
939 protect: Boolean. If True (the default) any operation that would
940 clobber an existing entry in namespace will cause an error.
941 Raises:
942 error.AutoservError: When a name would be clobbered by import.
943 """
944 def _import_names(module_name, names=()):
mbligh2b92b862008-11-22 13:25:32 +0000945 """
946 Import a module and assign named attributes into namespace.
mbligh084bc172008-10-18 14:02:45 +0000947
948 Args:
949 module_name: The string module name.
950 names: A limiting list of names to import from module_name. If
951 empty (the default), all names are imported from the module
952 similar to a "from foo.bar import *" statement.
953 Raises:
954 error.AutoservError: When a name being imported would clobber
955 a name already in namespace.
956 """
957 module = __import__(module_name, {}, {}, names)
958
959 # No names supplied? Import * from the lowest level module.
960 # (Ugh, why do I have to implement this part myself?)
961 if not names:
962 for submodule_name in module_name.split('.')[1:]:
963 module = getattr(module, submodule_name)
964 if hasattr(module, '__all__'):
965 names = getattr(module, '__all__')
966 else:
967 names = dir(module)
968
969 # Install each name into namespace, checking to make sure it
970 # doesn't override anything that already exists.
971 for name in names:
972 # Check for conflicts to help prevent future problems.
973 if name in namespace and protect:
974 if namespace[name] is not getattr(module, name):
975 raise error.AutoservError('importing name '
976 '%s from %s %r would override %r' %
977 (name, module_name, getattr(module, name),
978 namespace[name]))
979 else:
980 # Encourage cleanliness and the use of __all__ for a
981 # more concrete API with less surprises on '*' imports.
982 warnings.warn('%s (%r) being imported from %s for use '
983 'in server control files is not the '
984 'first occurrance of that import.' %
985 (name, namespace[name], module_name))
986
987 namespace[name] = getattr(module, name)
988
989
990 # This is the equivalent of prepending a bunch of import statements to
991 # the front of the control script.
mbligha2b07dd2009-06-22 18:26:13 +0000992 namespace.update(os=os, sys=sys, logging=logging)
mbligh084bc172008-10-18 14:02:45 +0000993 _import_names('autotest_lib.server',
994 ('hosts', 'autotest', 'kvm', 'git', 'standalone_profiler',
995 'source_kernel', 'rpm_kernel', 'deb_kernel', 'git_kernel'))
996 _import_names('autotest_lib.server.subcommand',
997 ('parallel', 'parallel_simple', 'subcommand'))
998 _import_names('autotest_lib.server.utils',
999 ('run', 'get_tmp_dir', 'sh_escape', 'parse_machine'))
1000 _import_names('autotest_lib.client.common_lib.error')
1001 _import_names('autotest_lib.client.common_lib.barrier', ('barrier',))
1002
1003 # Inject ourself as the job object into other classes within the API.
1004 # (Yuck, this injection is a gross thing be part of a public API. -gps)
1005 #
1006 # XXX Base & SiteAutotest do not appear to use .job. Who does?
1007 namespace['autotest'].Autotest.job = self
1008 # server.hosts.base_classes.Host uses .job.
1009 namespace['hosts'].Host.job = self
1010
1011
1012 def _execute_code(self, code_file, namespace, protect=True):
mbligh2b92b862008-11-22 13:25:32 +00001013 """
1014 Execute code using a copy of namespace as a server control script.
mbligh084bc172008-10-18 14:02:45 +00001015
1016 Unless protect_namespace is explicitly set to False, the dict will not
1017 be modified.
1018
1019 Args:
1020 code_file: The filename of the control file to execute.
1021 namespace: A dict containing names to make available during execution.
1022 protect: Boolean. If True (the default) a copy of the namespace dict
1023 is used during execution to prevent the code from modifying its
1024 contents outside of this function. If False the raw dict is
1025 passed in and modifications will be allowed.
1026 """
1027 if protect:
1028 namespace = namespace.copy()
1029 self._fill_server_control_namespace(namespace, protect=protect)
1030 # TODO: Simplify and get rid of the special cases for only 1 machine.
showard3e66e8c2008-10-27 19:20:51 +00001031 if len(self.machines) > 1:
mbligh084bc172008-10-18 14:02:45 +00001032 machines_text = '\n'.join(self.machines) + '\n'
1033 # Only rewrite the file if it does not match our machine list.
1034 try:
1035 machines_f = open(MACHINES_FILENAME, 'r')
1036 existing_machines_text = machines_f.read()
1037 machines_f.close()
1038 except EnvironmentError:
1039 existing_machines_text = None
1040 if machines_text != existing_machines_text:
1041 utils.open_write_close(MACHINES_FILENAME, machines_text)
1042 execfile(code_file, namespace, namespace)
jadmanski10646442008-08-13 14:05:21 +00001043
1044
1045 def _record(self, status_code, subdir, operation, status='',
1046 epoch_time=None, optional_fields=None):
1047 """
1048 Actual function for recording a single line into the status
1049 logs. Should never be called directly, only by job.record as
1050 this would bypass the console monitor logging.
1051 """
1052
mbligh2b92b862008-11-22 13:25:32 +00001053 msg = self._render_record(status_code, subdir, operation, status,
1054 epoch_time, optional_fields=optional_fields)
jadmanski10646442008-08-13 14:05:21 +00001055
jadmanski779bd292009-03-19 17:33:33 +00001056 status_file = self.get_status_log_path()
jadmanski10646442008-08-13 14:05:21 +00001057 sys.stdout.write(msg)
mbligh210bae62009-04-01 18:33:13 +00001058 if status_file:
1059 open(status_file, "a").write(msg)
jadmanski10646442008-08-13 14:05:21 +00001060 if subdir:
jadmanski779bd292009-03-19 17:33:33 +00001061 sub_status_file = self.get_status_log_path(subdir)
1062 open(sub_status_file, "a").write(msg)
jadmanski10646442008-08-13 14:05:21 +00001063 self.__parse_status(msg.splitlines())
1064
1065
1066 def __parse_status(self, new_lines):
mbligh0d0f67d2009-11-06 03:15:03 +00001067 if not self._using_parser:
jadmanski10646442008-08-13 14:05:21 +00001068 return
1069 new_tests = self.parser.process_lines(new_lines)
1070 for test in new_tests:
1071 self.__insert_test(test)
1072
1073
1074 def __insert_test(self, test):
mbligh2b92b862008-11-22 13:25:32 +00001075 """
1076 An internal method to insert a new test result into the
jadmanski10646442008-08-13 14:05:21 +00001077 database. This method will not raise an exception, even if an
1078 error occurs during the insert, to avoid failing a test
1079 simply because of unexpected database issues."""
showard21baa452008-10-21 00:08:39 +00001080 self.num_tests_run += 1
1081 if status_lib.is_worse_than_or_equal_to(test.status, 'FAIL'):
1082 self.num_tests_failed += 1
jadmanski10646442008-08-13 14:05:21 +00001083 try:
1084 self.results_db.insert_test(self.job_model, test)
1085 except Exception:
1086 msg = ("WARNING: An unexpected error occured while "
1087 "inserting test results into the database. "
1088 "Ignoring error.\n" + traceback.format_exc())
1089 print >> sys.stderr, msg
1090
mblighcaa62c22008-04-07 21:51:17 +00001091
mblighfc3da5b2010-01-06 18:37:22 +00001092 def preprocess_client_state(self):
1093 """
1094 Produce a state file for initializing the state of a client job.
1095
1096 Creates a new client state file with all the current server state, as
1097 well as some pre-set client state.
1098
1099 @returns The path of the file the state was written into.
1100 """
1101 # initialize the sysinfo state
1102 self._state.set('client', 'sysinfo', self.sysinfo.serialize())
1103
1104 # dump the state out to a tempfile
1105 fd, file_path = tempfile.mkstemp(dir=self.tmpdir)
1106 os.close(fd)
mbligha2c99492010-01-27 22:59:50 +00001107
1108 # write_to_file doesn't need locking, we exclusively own file_path
mblighfc3da5b2010-01-06 18:37:22 +00001109 self._state.write_to_file(file_path)
1110 return file_path
1111
1112
1113 def postprocess_client_state(self, state_path):
1114 """
1115 Update the state of this job with the state from a client job.
1116
1117 Updates the state of the server side of a job with the final state
1118 of a client job that was run. Updates the non-client-specific state,
1119 pulls in some specific bits from the client-specific state, and then
1120 discards the rest. Removes the state file afterwards
1121
1122 @param state_file A path to the state file from the client.
1123 """
1124 # update the on-disk state
1125 self._state.read_from_file(state_path)
1126 try:
1127 os.remove(state_path)
mbligha2c99492010-01-27 22:59:50 +00001128 except OSError, e:
mblighfc3da5b2010-01-06 18:37:22 +00001129 # ignore file-not-found errors
1130 if e.errno != errno.ENOENT:
1131 raise
1132
1133 # update the sysinfo state
1134 if self._state.has('client', 'sysinfo'):
1135 self.sysinfo.deserialize(self._state.get('client', 'sysinfo'))
1136
1137 # drop all the client-specific state
1138 self._state.discard_namespace('client')
1139
1140
mbligha7007722009-01-13 00:37:11 +00001141site_server_job = utils.import_site_class(
1142 __file__, "autotest_lib.server.site_server_job", "site_server_job",
1143 base_server_job)
jadmanski0afbb632008-06-06 21:10:57 +00001144
mbligh0a8c3322009-04-28 18:32:19 +00001145class server_job(site_server_job):
jadmanski0afbb632008-06-06 21:10:57 +00001146 pass
jadmanskif37df842009-02-11 00:03:26 +00001147
1148
1149class warning_manager(object):
1150 """Class for controlling warning logs. Manages the enabling and disabling
1151 of warnings."""
1152 def __init__(self):
1153 # a map of warning types to a list of disabled time intervals
1154 self.disabled_warnings = {}
1155
1156
1157 def is_valid(self, timestamp, warning_type):
1158 """Indicates if a warning (based on the time it occured and its type)
1159 is a valid warning. A warning is considered "invalid" if this type of
1160 warning was marked as "disabled" at the time the warning occured."""
1161 disabled_intervals = self.disabled_warnings.get(warning_type, [])
1162 for start, end in disabled_intervals:
1163 if timestamp >= start and (end is None or timestamp < end):
1164 return False
1165 return True
1166
1167
1168 def disable_warnings(self, warning_type, current_time_func=time.time):
1169 """As of now, disables all further warnings of this type."""
1170 intervals = self.disabled_warnings.setdefault(warning_type, [])
1171 if not intervals or intervals[-1][1] is not None:
jadmanski16a7ff72009-04-01 18:19:53 +00001172 intervals.append((int(current_time_func()), None))
jadmanskif37df842009-02-11 00:03:26 +00001173
1174
1175 def enable_warnings(self, warning_type, current_time_func=time.time):
1176 """As of now, enables all further warnings of this type."""
1177 intervals = self.disabled_warnings.get(warning_type, [])
1178 if intervals and intervals[-1][1] is None:
jadmanski16a7ff72009-04-01 18:19:53 +00001179 intervals[-1] = (intervals[-1][0], int(current_time_func()))