blob: 87350823361e797c7e47b6e532d27f1cce3e6c01 [file] [log] [blame]
Paul Pendlebury7c1fdcf2011-05-04 12:39:15 -07001# Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
mbligh57e78662008-06-17 19:53:49 +00004"""
5The main job wrapper for the server side.
6
7This is the core infrastructure. Derived from the client side job.py
8
9Copyright Martin J. Bligh, Andy Whitcroft 2007
10"""
11
Scott Zawalski91493c82013-01-25 16:15:20 -050012import getpass, os, sys, re, tempfile, time, select, platform
mblighfc3da5b2010-01-06 18:37:22 +000013import traceback, shutil, warnings, fcntl, pickle, logging, itertools, errno
showard75cdfee2009-06-10 17:40:41 +000014from autotest_lib.client.bin import sysinfo
mbligh0d0f67d2009-11-06 03:15:03 +000015from autotest_lib.client.common_lib import base_job
Scott Zawalski91493c82013-01-25 16:15:20 -050016from autotest_lib.client.common_lib import error, utils, packages
showard75cdfee2009-06-10 17:40:41 +000017from autotest_lib.client.common_lib import logging_manager
Paul Pendlebury57593562011-06-15 10:45:49 -070018from autotest_lib.server import test, subcommand, profilers
mbligh0a883702010-04-21 01:58:34 +000019from autotest_lib.server.hosts import abstract_ssh
jadmanski10646442008-08-13 14:05:21 +000020from autotest_lib.tko import db as tko_db, status_lib, utils as tko_utils
jadmanski10646442008-08-13 14:05:21 +000021
22
mbligh084bc172008-10-18 14:02:45 +000023def _control_segment_path(name):
24 """Get the pathname of the named control segment file."""
jadmanski10646442008-08-13 14:05:21 +000025 server_dir = os.path.dirname(os.path.abspath(__file__))
mbligh084bc172008-10-18 14:02:45 +000026 return os.path.join(server_dir, "control_segments", name)
jadmanski10646442008-08-13 14:05:21 +000027
28
mbligh084bc172008-10-18 14:02:45 +000029CLIENT_CONTROL_FILENAME = 'control'
30SERVER_CONTROL_FILENAME = 'control.srv'
31MACHINES_FILENAME = '.machines'
jadmanski10646442008-08-13 14:05:21 +000032
mbligh084bc172008-10-18 14:02:45 +000033CLIENT_WRAPPER_CONTROL_FILE = _control_segment_path('client_wrapper')
34CRASHDUMPS_CONTROL_FILE = _control_segment_path('crashdumps')
35CRASHINFO_CONTROL_FILE = _control_segment_path('crashinfo')
mbligh084bc172008-10-18 14:02:45 +000036INSTALL_CONTROL_FILE = _control_segment_path('install')
showard45ae8192008-11-05 19:32:53 +000037CLEANUP_CONTROL_FILE = _control_segment_path('cleanup')
jadmanski10646442008-08-13 14:05:21 +000038
mbligh084bc172008-10-18 14:02:45 +000039VERIFY_CONTROL_FILE = _control_segment_path('verify')
mbligh084bc172008-10-18 14:02:45 +000040REPAIR_CONTROL_FILE = _control_segment_path('repair')
jadmanski10646442008-08-13 14:05:21 +000041
42
mbligh062ed152009-01-13 00:57:14 +000043# by default provide a stub that generates no site data
44def _get_site_job_data_dummy(job):
45 return {}
46
47
jadmanski2a89dac2010-06-11 14:32:58 +000048class status_indenter(base_job.status_indenter):
49 """Provide a simple integer-backed status indenter."""
50 def __init__(self):
51 self._indent = 0
52
53
54 @property
55 def indent(self):
56 return self._indent
57
58
59 def increment(self):
60 self._indent += 1
61
62
63 def decrement(self):
64 self._indent -= 1
65
66
jadmanski52053632010-06-11 21:08:10 +000067 def get_context(self):
68 """Returns a context object for use by job.get_record_context."""
69 class context(object):
70 def __init__(self, indenter, indent):
71 self._indenter = indenter
72 self._indent = indent
73 def restore(self):
74 self._indenter._indent = self._indent
75 return context(self, self._indent)
76
77
jadmanski2a89dac2010-06-11 14:32:58 +000078class server_job_record_hook(object):
79 """The job.record hook for server job. Used to inject WARN messages from
80 the console or vlm whenever new logs are written, and to echo any logs
81 to INFO level logging. Implemented as a class so that it can use state to
82 block recursive calls, so that the hook can call job.record itself to
83 log WARN messages.
84
85 Depends on job._read_warnings and job._logger.
86 """
87 def __init__(self, job):
88 self._job = job
89 self._being_called = False
90
91
92 def __call__(self, entry):
93 """A wrapper around the 'real' record hook, the _hook method, which
94 prevents recursion. This isn't making any effort to be threadsafe,
95 the intent is to outright block infinite recursion via a
96 job.record->_hook->job.record->_hook->job.record... chain."""
97 if self._being_called:
98 return
99 self._being_called = True
100 try:
101 self._hook(self._job, entry)
102 finally:
103 self._being_called = False
104
105
106 @staticmethod
107 def _hook(job, entry):
108 """The core hook, which can safely call job.record."""
109 entries = []
110 # poll all our warning loggers for new warnings
111 for timestamp, msg in job._read_warnings():
112 warning_entry = base_job.status_log_entry(
113 'WARN', None, None, msg, {}, timestamp=timestamp)
114 entries.append(warning_entry)
115 job.record_entry(warning_entry)
116 # echo rendered versions of all the status logs to info
117 entries.append(entry)
118 for entry in entries:
119 rendered_entry = job._logger.render_entry(entry)
120 logging.info(rendered_entry)
jadmanskie29d0e42010-06-17 16:06:52 +0000121 job._parse_status(rendered_entry)
jadmanski2a89dac2010-06-11 14:32:58 +0000122
123
mbligh0d0f67d2009-11-06 03:15:03 +0000124class base_server_job(base_job.base_job):
125 """The server-side concrete implementation of base_job.
jadmanski10646442008-08-13 14:05:21 +0000126
mbligh0d0f67d2009-11-06 03:15:03 +0000127 Optional properties provided by this implementation:
128 serverdir
129 conmuxdir
130
131 num_tests_run
132 num_tests_failed
133
134 warning_manager
135 warning_loggers
jadmanski10646442008-08-13 14:05:21 +0000136 """
137
mbligh0d0f67d2009-11-06 03:15:03 +0000138 _STATUS_VERSION = 1
jadmanski10646442008-08-13 14:05:21 +0000139
140 def __init__(self, control, args, resultdir, label, user, machines,
141 client=False, parse_job='',
Scott Zawalski91493c82013-01-25 16:15:20 -0500142 ssh_user='root', ssh_port=22, ssh_pass='', test_retry=0,
mblighe0cbc912010-03-11 18:03:07 +0000143 group_name='', tag='',
144 control_filename=SERVER_CONTROL_FILENAME):
jadmanski10646442008-08-13 14:05:21 +0000145 """
mbligh374f3412009-05-13 21:29:45 +0000146 Create a server side job object.
mblighb5dac432008-11-27 00:38:44 +0000147
mblighe7d9c602009-07-02 19:02:33 +0000148 @param control: The pathname of the control file.
149 @param args: Passed to the control file.
150 @param resultdir: Where to throw the results.
151 @param label: Description of the job.
152 @param user: Username for the job (email address).
153 @param client: True if this is a client-side control file.
154 @param parse_job: string, if supplied it is the job execution tag that
155 the results will be passed through to the TKO parser with.
156 @param ssh_user: The SSH username. [root]
157 @param ssh_port: The SSH port number. [22]
158 @param ssh_pass: The SSH passphrase, if needed.
Scott Zawalski91493c82013-01-25 16:15:20 -0500159 @param test_retry: The number of times to retry a test if the test did
160 not complete successfully.
mblighe7d9c602009-07-02 19:02:33 +0000161 @param group_name: If supplied, this will be written out as
mbligh374f3412009-05-13 21:29:45 +0000162 host_group_name in the keyvals file for the parser.
mblighe7d9c602009-07-02 19:02:33 +0000163 @param tag: The job execution tag from the scheduler. [optional]
mblighe0cbc912010-03-11 18:03:07 +0000164 @param control_filename: The filename where the server control file
165 should be written in the results directory.
jadmanski10646442008-08-13 14:05:21 +0000166 """
Scott Zawalski91493c82013-01-25 16:15:20 -0500167 super(base_server_job, self).__init__(resultdir=resultdir,
168 test_retry=test_retry)
mbligh0d0f67d2009-11-06 03:15:03 +0000169 path = os.path.dirname(__file__)
Scott Zawalski91493c82013-01-25 16:15:20 -0500170 self.test_retry = test_retry
mbligh0d0f67d2009-11-06 03:15:03 +0000171 self.control = control
172 self._uncollected_log_file = os.path.join(self.resultdir,
173 'uncollected_logs')
174 debugdir = os.path.join(self.resultdir, 'debug')
175 if not os.path.exists(debugdir):
176 os.mkdir(debugdir)
177
178 if user:
179 self.user = user
180 else:
181 self.user = getpass.getuser()
182
jadmanski808f4b12010-04-09 22:30:31 +0000183 self.args = args
Peter Mayo7a875762012-06-13 14:38:15 -0400184 self.label = label
jadmanski10646442008-08-13 14:05:21 +0000185 self.machines = machines
mbligh0d0f67d2009-11-06 03:15:03 +0000186 self._client = client
jadmanski10646442008-08-13 14:05:21 +0000187 self.warning_loggers = set()
jadmanskif37df842009-02-11 00:03:26 +0000188 self.warning_manager = warning_manager()
mbligh0d0f67d2009-11-06 03:15:03 +0000189 self._ssh_user = ssh_user
190 self._ssh_port = ssh_port
191 self._ssh_pass = ssh_pass
mblighe7d9c602009-07-02 19:02:33 +0000192 self.tag = tag
mbligh09108442008-10-15 16:27:38 +0000193 self.last_boot_tag = None
jadmanski53aaf382008-11-17 16:22:31 +0000194 self.hosts = set()
mbligh0d0f67d2009-11-06 03:15:03 +0000195 self.drop_caches = False
mblighb5dac432008-11-27 00:38:44 +0000196 self.drop_caches_between_iterations = False
mblighe0cbc912010-03-11 18:03:07 +0000197 self._control_filename = control_filename
jadmanski10646442008-08-13 14:05:21 +0000198
showard75cdfee2009-06-10 17:40:41 +0000199 self.logging = logging_manager.get_logging_manager(
200 manage_stdout_and_stderr=True, redirect_fds=True)
201 subcommand.logging_manager_object = self.logging
jadmanski10646442008-08-13 14:05:21 +0000202
mbligh0d0f67d2009-11-06 03:15:03 +0000203 self.sysinfo = sysinfo.sysinfo(self.resultdir)
jadmanski043e1132008-11-19 17:10:32 +0000204 self.profilers = profilers.profilers(self)
jadmanskic09fc152008-10-15 17:56:59 +0000205
jadmanski10646442008-08-13 14:05:21 +0000206 job_data = {'label' : label, 'user' : user,
207 'hostname' : ','.join(machines),
Eric Li861b2d52011-02-04 14:50:35 -0800208 'drone' : platform.node(),
mbligh0d0f67d2009-11-06 03:15:03 +0000209 'status_version' : str(self._STATUS_VERSION),
showard170873e2009-01-07 00:22:26 +0000210 'job_started' : str(int(time.time()))}
mbligh374f3412009-05-13 21:29:45 +0000211 if group_name:
212 job_data['host_group_name'] = group_name
jadmanski10646442008-08-13 14:05:21 +0000213
mbligh0d0f67d2009-11-06 03:15:03 +0000214 # only write these keyvals out on the first job in a resultdir
215 if 'job_started' not in utils.read_keyval(self.resultdir):
216 job_data.update(get_site_job_data(self))
217 utils.write_keyval(self.resultdir, job_data)
218
219 self._parse_job = parse_job
showardcc929362010-01-25 21:20:41 +0000220 self._using_parser = (self._parse_job and len(machines) <= 1)
mbligh0d0f67d2009-11-06 03:15:03 +0000221 self.pkgmgr = packages.PackageManager(
222 self.autodir, run_function_dargs={'timeout':600})
showard21baa452008-10-21 00:08:39 +0000223 self.num_tests_run = 0
224 self.num_tests_failed = 0
225
jadmanski550fdc22008-11-20 16:32:08 +0000226 self._register_subcommand_hooks()
227
mbligh0d0f67d2009-11-06 03:15:03 +0000228 # these components aren't usable on the server
229 self.bootloader = None
230 self.harness = None
231
jadmanski2a89dac2010-06-11 14:32:58 +0000232 # set up the status logger
jadmanski52053632010-06-11 21:08:10 +0000233 self._indenter = status_indenter()
jadmanski2a89dac2010-06-11 14:32:58 +0000234 self._logger = base_job.status_logger(
jadmanski52053632010-06-11 21:08:10 +0000235 self, self._indenter, 'status.log', 'status.log',
jadmanski2a89dac2010-06-11 14:32:58 +0000236 record_hook=server_job_record_hook(self))
237
mbligh0d0f67d2009-11-06 03:15:03 +0000238
239 @classmethod
240 def _find_base_directories(cls):
241 """
242 Determine locations of autodir, clientdir and serverdir. Assumes
243 that this file is located within serverdir and uses __file__ along
244 with relative paths to resolve the location.
245 """
246 serverdir = os.path.abspath(os.path.dirname(__file__))
247 autodir = os.path.normpath(os.path.join(serverdir, '..'))
248 clientdir = os.path.join(autodir, 'client')
249 return autodir, clientdir, serverdir
250
251
Scott Zawalski91493c82013-01-25 16:15:20 -0500252 def _find_resultdir(self, resultdir, *args, **dargs):
mbligh0d0f67d2009-11-06 03:15:03 +0000253 """
254 Determine the location of resultdir. For server jobs we expect one to
255 always be explicitly passed in to __init__, so just return that.
256 """
257 if resultdir:
258 return os.path.normpath(resultdir)
259 else:
260 return None
261
jadmanski550fdc22008-11-20 16:32:08 +0000262
jadmanski2a89dac2010-06-11 14:32:58 +0000263 def _get_status_logger(self):
264 """Return a reference to the status logger."""
265 return self._logger
266
267
jadmanskie432dd22009-01-30 15:04:51 +0000268 @staticmethod
269 def _load_control_file(path):
270 f = open(path)
271 try:
272 control_file = f.read()
273 finally:
274 f.close()
275 return re.sub('\r', '', control_file)
276
277
jadmanski550fdc22008-11-20 16:32:08 +0000278 def _register_subcommand_hooks(self):
mbligh2b92b862008-11-22 13:25:32 +0000279 """
280 Register some hooks into the subcommand modules that allow us
281 to properly clean up self.hosts created in forked subprocesses.
282 """
jadmanski550fdc22008-11-20 16:32:08 +0000283 def on_fork(cmd):
284 self._existing_hosts_on_fork = set(self.hosts)
285 def on_join(cmd):
286 new_hosts = self.hosts - self._existing_hosts_on_fork
287 for host in new_hosts:
288 host.close()
289 subcommand.subcommand.register_fork_hook(on_fork)
290 subcommand.subcommand.register_join_hook(on_join)
291
jadmanski10646442008-08-13 14:05:21 +0000292
mbligh4608b002010-01-05 18:22:35 +0000293 def init_parser(self):
mbligh2b92b862008-11-22 13:25:32 +0000294 """
mbligh4608b002010-01-05 18:22:35 +0000295 Start the continuous parsing of self.resultdir. This sets up
jadmanski10646442008-08-13 14:05:21 +0000296 the database connection and inserts the basic job object into
mbligh2b92b862008-11-22 13:25:32 +0000297 the database if necessary.
298 """
mbligh4608b002010-01-05 18:22:35 +0000299 if not self._using_parser:
300 return
jadmanski10646442008-08-13 14:05:21 +0000301 # redirect parser debugging to .parse.log
mbligh4608b002010-01-05 18:22:35 +0000302 parse_log = os.path.join(self.resultdir, '.parse.log')
jadmanski10646442008-08-13 14:05:21 +0000303 parse_log = open(parse_log, 'w', 0)
304 tko_utils.redirect_parser_debugging(parse_log)
305 # create a job model object and set up the db
306 self.results_db = tko_db.db(autocommit=True)
mbligh0d0f67d2009-11-06 03:15:03 +0000307 self.parser = status_lib.parser(self._STATUS_VERSION)
mbligh4608b002010-01-05 18:22:35 +0000308 self.job_model = self.parser.make_job(self.resultdir)
jadmanski10646442008-08-13 14:05:21 +0000309 self.parser.start(self.job_model)
310 # check if a job already exists in the db and insert it if
311 # it does not
mbligh0d0f67d2009-11-06 03:15:03 +0000312 job_idx = self.results_db.find_job(self._parse_job)
jadmanski10646442008-08-13 14:05:21 +0000313 if job_idx is None:
mbligh0d0f67d2009-11-06 03:15:03 +0000314 self.results_db.insert_job(self._parse_job, self.job_model)
jadmanski10646442008-08-13 14:05:21 +0000315 else:
mbligh2b92b862008-11-22 13:25:32 +0000316 machine_idx = self.results_db.lookup_machine(self.job_model.machine)
jadmanski10646442008-08-13 14:05:21 +0000317 self.job_model.index = job_idx
318 self.job_model.machine_idx = machine_idx
319
320
321 def cleanup_parser(self):
mbligh2b92b862008-11-22 13:25:32 +0000322 """
323 This should be called after the server job is finished
jadmanski10646442008-08-13 14:05:21 +0000324 to carry out any remaining cleanup (e.g. flushing any
mbligh2b92b862008-11-22 13:25:32 +0000325 remaining test results to the results db)
326 """
mbligh0d0f67d2009-11-06 03:15:03 +0000327 if not self._using_parser:
jadmanski10646442008-08-13 14:05:21 +0000328 return
329 final_tests = self.parser.end()
330 for test in final_tests:
331 self.__insert_test(test)
mbligh0d0f67d2009-11-06 03:15:03 +0000332 self._using_parser = False
jadmanski10646442008-08-13 14:05:21 +0000333
334
335 def verify(self):
336 if not self.machines:
mbligh084bc172008-10-18 14:02:45 +0000337 raise error.AutoservError('No machines specified to verify')
mbligh0fce4112008-11-27 00:37:17 +0000338 if self.resultdir:
339 os.chdir(self.resultdir)
jadmanski10646442008-08-13 14:05:21 +0000340 try:
jadmanskicdd0c402008-09-19 21:21:31 +0000341 namespace = {'machines' : self.machines, 'job' : self,
mbligh0d0f67d2009-11-06 03:15:03 +0000342 'ssh_user' : self._ssh_user,
343 'ssh_port' : self._ssh_port,
344 'ssh_pass' : self._ssh_pass}
mbligh084bc172008-10-18 14:02:45 +0000345 self._execute_code(VERIFY_CONTROL_FILE, namespace, protect=False)
jadmanski10646442008-08-13 14:05:21 +0000346 except Exception, e:
mbligh2b92b862008-11-22 13:25:32 +0000347 msg = ('Verify failed\n' + str(e) + '\n' + traceback.format_exc())
jadmanski10646442008-08-13 14:05:21 +0000348 self.record('ABORT', None, None, msg)
349 raise
350
351
352 def repair(self, host_protection):
353 if not self.machines:
354 raise error.AutoservError('No machines specified to repair')
mbligh0fce4112008-11-27 00:37:17 +0000355 if self.resultdir:
356 os.chdir(self.resultdir)
jadmanski10646442008-08-13 14:05:21 +0000357 namespace = {'machines': self.machines, 'job': self,
mbligh0d0f67d2009-11-06 03:15:03 +0000358 'ssh_user': self._ssh_user, 'ssh_port': self._ssh_port,
359 'ssh_pass': self._ssh_pass,
jadmanski10646442008-08-13 14:05:21 +0000360 'protection_level': host_protection}
mbligh25c0b8c2009-01-24 01:44:17 +0000361
mbligh0931b0a2009-04-08 17:44:48 +0000362 self._execute_code(REPAIR_CONTROL_FILE, namespace, protect=False)
jadmanski10646442008-08-13 14:05:21 +0000363
364
365 def precheck(self):
366 """
367 perform any additional checks in derived classes.
368 """
369 pass
370
371
372 def enable_external_logging(self):
mbligh2b92b862008-11-22 13:25:32 +0000373 """
374 Start or restart external logging mechanism.
jadmanski10646442008-08-13 14:05:21 +0000375 """
376 pass
377
378
379 def disable_external_logging(self):
mbligh2b92b862008-11-22 13:25:32 +0000380 """
381 Pause or stop external logging mechanism.
jadmanski10646442008-08-13 14:05:21 +0000382 """
383 pass
384
385
386 def use_external_logging(self):
mbligh2b92b862008-11-22 13:25:32 +0000387 """
388 Return True if external logging should be used.
jadmanski10646442008-08-13 14:05:21 +0000389 """
390 return False
391
392
mbligh415dc212009-06-15 21:53:34 +0000393 def _make_parallel_wrapper(self, function, machines, log):
394 """Wrap function as appropriate for calling by parallel_simple."""
mbligh2b92b862008-11-22 13:25:32 +0000395 is_forking = not (len(machines) == 1 and self.machines == machines)
mbligh0d0f67d2009-11-06 03:15:03 +0000396 if self._parse_job and is_forking and log:
jadmanski10646442008-08-13 14:05:21 +0000397 def wrapper(machine):
mbligh0d0f67d2009-11-06 03:15:03 +0000398 self._parse_job += "/" + machine
399 self._using_parser = True
jadmanski10646442008-08-13 14:05:21 +0000400 self.machines = [machine]
mbligh0d0f67d2009-11-06 03:15:03 +0000401 self.push_execution_context(machine)
jadmanski609a5f42008-08-26 20:52:42 +0000402 os.chdir(self.resultdir)
showard2bab8f42008-11-12 18:15:22 +0000403 utils.write_keyval(self.resultdir, {"hostname": machine})
mbligh4608b002010-01-05 18:22:35 +0000404 self.init_parser()
jadmanski10646442008-08-13 14:05:21 +0000405 result = function(machine)
406 self.cleanup_parser()
407 return result
jadmanski4dd1a002008-09-05 20:27:30 +0000408 elif len(machines) > 1 and log:
jadmanski10646442008-08-13 14:05:21 +0000409 def wrapper(machine):
mbligh0d0f67d2009-11-06 03:15:03 +0000410 self.push_execution_context(machine)
jadmanski609a5f42008-08-26 20:52:42 +0000411 os.chdir(self.resultdir)
mbligh838d82d2009-03-11 17:14:31 +0000412 machine_data = {'hostname' : machine,
mbligh0d0f67d2009-11-06 03:15:03 +0000413 'status_version' : str(self._STATUS_VERSION)}
mbligh838d82d2009-03-11 17:14:31 +0000414 utils.write_keyval(self.resultdir, machine_data)
jadmanski10646442008-08-13 14:05:21 +0000415 result = function(machine)
416 return result
417 else:
418 wrapper = function
mbligh415dc212009-06-15 21:53:34 +0000419 return wrapper
420
421
422 def parallel_simple(self, function, machines, log=True, timeout=None,
423 return_results=False):
424 """
425 Run 'function' using parallel_simple, with an extra wrapper to handle
426 the necessary setup for continuous parsing, if possible. If continuous
427 parsing is already properly initialized then this should just work.
428
429 @param function: A callable to run in parallel given each machine.
430 @param machines: A list of machine names to be passed one per subcommand
431 invocation of function.
432 @param log: If True, output will be written to output in a subdirectory
433 named after each machine.
434 @param timeout: Seconds after which the function call should timeout.
435 @param return_results: If True instead of an AutoServError being raised
436 on any error a list of the results|exceptions from the function
437 called on each arg is returned. [default: False]
438
439 @raises error.AutotestError: If any of the functions failed.
440 """
441 wrapper = self._make_parallel_wrapper(function, machines, log)
442 return subcommand.parallel_simple(wrapper, machines,
443 log=log, timeout=timeout,
444 return_results=return_results)
445
446
447 def parallel_on_machines(self, function, machines, timeout=None):
448 """
showardcd5fac42009-07-06 20:19:43 +0000449 @param function: Called in parallel with one machine as its argument.
mbligh415dc212009-06-15 21:53:34 +0000450 @param machines: A list of machines to call function(machine) on.
451 @param timeout: Seconds after which the function call should timeout.
452
453 @returns A list of machines on which function(machine) returned
454 without raising an exception.
455 """
showardcd5fac42009-07-06 20:19:43 +0000456 results = self.parallel_simple(function, machines, timeout=timeout,
mbligh415dc212009-06-15 21:53:34 +0000457 return_results=True)
458 success_machines = []
459 for result, machine in itertools.izip(results, machines):
460 if not isinstance(result, Exception):
461 success_machines.append(machine)
462 return success_machines
jadmanski10646442008-08-13 14:05:21 +0000463
464
mbligh0d0f67d2009-11-06 03:15:03 +0000465 _USE_TEMP_DIR = object()
mbligh2b92b862008-11-22 13:25:32 +0000466 def run(self, cleanup=False, install_before=False, install_after=False,
jadmanskie432dd22009-01-30 15:04:51 +0000467 collect_crashdumps=True, namespace={}, control=None,
jadmanskidef0c3c2009-03-25 20:07:10 +0000468 control_file_dir=None, only_collect_crashinfo=False):
jadmanskifb9c0fa2009-04-29 17:39:16 +0000469 # for a normal job, make sure the uncollected logs file exists
470 # for a crashinfo-only run it should already exist, bail out otherwise
jadmanski648c39f2010-03-19 17:38:01 +0000471 created_uncollected_logs = False
mbligh0d0f67d2009-11-06 03:15:03 +0000472 if self.resultdir and not os.path.exists(self._uncollected_log_file):
jadmanskifb9c0fa2009-04-29 17:39:16 +0000473 if only_collect_crashinfo:
474 # if this is a crashinfo-only run, and there were no existing
475 # uncollected logs, just bail out early
476 logging.info("No existing uncollected logs, "
477 "skipping crashinfo collection")
478 return
479 else:
mbligh0d0f67d2009-11-06 03:15:03 +0000480 log_file = open(self._uncollected_log_file, "w")
jadmanskifb9c0fa2009-04-29 17:39:16 +0000481 pickle.dump([], log_file)
482 log_file.close()
jadmanski648c39f2010-03-19 17:38:01 +0000483 created_uncollected_logs = True
jadmanskifb9c0fa2009-04-29 17:39:16 +0000484
jadmanski10646442008-08-13 14:05:21 +0000485 # use a copy so changes don't affect the original dictionary
486 namespace = namespace.copy()
487 machines = self.machines
jadmanskie432dd22009-01-30 15:04:51 +0000488 if control is None:
jadmanski02a3ba22009-11-13 20:47:27 +0000489 if self.control is None:
490 control = ''
491 else:
492 control = self._load_control_file(self.control)
jadmanskie432dd22009-01-30 15:04:51 +0000493 if control_file_dir is None:
494 control_file_dir = self.resultdir
jadmanski10646442008-08-13 14:05:21 +0000495
496 self.aborted = False
497 namespace['machines'] = machines
jadmanski808f4b12010-04-09 22:30:31 +0000498 namespace['args'] = self.args
jadmanski10646442008-08-13 14:05:21 +0000499 namespace['job'] = self
mbligh0d0f67d2009-11-06 03:15:03 +0000500 namespace['ssh_user'] = self._ssh_user
501 namespace['ssh_port'] = self._ssh_port
502 namespace['ssh_pass'] = self._ssh_pass
jadmanski10646442008-08-13 14:05:21 +0000503 test_start_time = int(time.time())
504
mbligh80e1eba2008-11-19 00:26:18 +0000505 if self.resultdir:
506 os.chdir(self.resultdir)
jadmanski779bd292009-03-19 17:33:33 +0000507 # touch status.log so that the parser knows a job is running here
jadmanski382303a2009-04-21 19:53:39 +0000508 open(self.get_status_log_path(), 'a').close()
mbligh80e1eba2008-11-19 00:26:18 +0000509 self.enable_external_logging()
jadmanskie432dd22009-01-30 15:04:51 +0000510
jadmanskicdd0c402008-09-19 21:21:31 +0000511 collect_crashinfo = True
mblighaebe3b62008-12-22 14:45:40 +0000512 temp_control_file_dir = None
jadmanski10646442008-08-13 14:05:21 +0000513 try:
showardcf8d4922009-10-14 16:08:39 +0000514 try:
515 if install_before and machines:
516 self._execute_code(INSTALL_CONTROL_FILE, namespace)
jadmanskie432dd22009-01-30 15:04:51 +0000517
showardcf8d4922009-10-14 16:08:39 +0000518 if only_collect_crashinfo:
519 return
520
jadmanskidef0c3c2009-03-25 20:07:10 +0000521 # determine the dir to write the control files to
522 cfd_specified = (control_file_dir
mbligh0d0f67d2009-11-06 03:15:03 +0000523 and control_file_dir is not self._USE_TEMP_DIR)
jadmanskidef0c3c2009-03-25 20:07:10 +0000524 if cfd_specified:
525 temp_control_file_dir = None
526 else:
527 temp_control_file_dir = tempfile.mkdtemp(
528 suffix='temp_control_file_dir')
529 control_file_dir = temp_control_file_dir
530 server_control_file = os.path.join(control_file_dir,
mblighe0cbc912010-03-11 18:03:07 +0000531 self._control_filename)
jadmanskidef0c3c2009-03-25 20:07:10 +0000532 client_control_file = os.path.join(control_file_dir,
533 CLIENT_CONTROL_FILENAME)
mbligh0d0f67d2009-11-06 03:15:03 +0000534 if self._client:
jadmanskidef0c3c2009-03-25 20:07:10 +0000535 namespace['control'] = control
536 utils.open_write_close(client_control_file, control)
mblighfeac0102009-04-28 18:31:12 +0000537 shutil.copyfile(CLIENT_WRAPPER_CONTROL_FILE,
538 server_control_file)
jadmanskidef0c3c2009-03-25 20:07:10 +0000539 else:
540 utils.open_write_close(server_control_file, control)
mbligh26f0d882009-06-22 18:30:01 +0000541 logging.info("Processing control file")
jadmanskidef0c3c2009-03-25 20:07:10 +0000542 self._execute_code(server_control_file, namespace)
mbligh26f0d882009-06-22 18:30:01 +0000543 logging.info("Finished processing control file")
jadmanski10646442008-08-13 14:05:21 +0000544
jadmanskidef0c3c2009-03-25 20:07:10 +0000545 # no error occured, so we don't need to collect crashinfo
546 collect_crashinfo = False
Eric Li6f27d4f2010-09-29 10:55:17 -0700547 except Exception, e:
showardcf8d4922009-10-14 16:08:39 +0000548 try:
549 logging.exception(
550 'Exception escaped control file, job aborting:')
Eric Li6f27d4f2010-09-29 10:55:17 -0700551 self.record('INFO', None, None, str(e),
552 {'job_abort_reason': str(e)})
showardcf8d4922009-10-14 16:08:39 +0000553 except:
554 pass # don't let logging exceptions here interfere
555 raise
jadmanski10646442008-08-13 14:05:21 +0000556 finally:
mblighaebe3b62008-12-22 14:45:40 +0000557 if temp_control_file_dir:
jadmanskie432dd22009-01-30 15:04:51 +0000558 # Clean up temp directory used for copies of the control files
mblighaebe3b62008-12-22 14:45:40 +0000559 try:
560 shutil.rmtree(temp_control_file_dir)
561 except Exception, e:
mblighe7d9c602009-07-02 19:02:33 +0000562 logging.warn('Could not remove temp directory %s: %s',
563 temp_control_file_dir, e)
jadmanskie432dd22009-01-30 15:04:51 +0000564
jadmanskicdd0c402008-09-19 21:21:31 +0000565 if machines and (collect_crashdumps or collect_crashinfo):
jadmanski10646442008-08-13 14:05:21 +0000566 namespace['test_start_time'] = test_start_time
jadmanskicdd0c402008-09-19 21:21:31 +0000567 if collect_crashinfo:
mbligh084bc172008-10-18 14:02:45 +0000568 # includes crashdumps
569 self._execute_code(CRASHINFO_CONTROL_FILE, namespace)
jadmanskicdd0c402008-09-19 21:21:31 +0000570 else:
mbligh084bc172008-10-18 14:02:45 +0000571 self._execute_code(CRASHDUMPS_CONTROL_FILE, namespace)
jadmanski10646442008-08-13 14:05:21 +0000572 self.disable_external_logging()
showard45ae8192008-11-05 19:32:53 +0000573 if cleanup and machines:
574 self._execute_code(CLEANUP_CONTROL_FILE, namespace)
Chris Sosaf4d43ff2012-10-30 11:21:05 -0700575 if self._uncollected_log_file and created_uncollected_logs:
576 os.remove(self._uncollected_log_file)
jadmanski10646442008-08-13 14:05:21 +0000577 if install_after and machines:
mbligh084bc172008-10-18 14:02:45 +0000578 self._execute_code(INSTALL_CONTROL_FILE, namespace)
jadmanski10646442008-08-13 14:05:21 +0000579
580
581 def run_test(self, url, *args, **dargs):
mbligh2b92b862008-11-22 13:25:32 +0000582 """
583 Summon a test object and run it.
jadmanski10646442008-08-13 14:05:21 +0000584
585 tag
586 tag to add to testname
587 url
588 url of the test to run
589 """
mblighfc3da5b2010-01-06 18:37:22 +0000590 group, testname = self.pkgmgr.get_package_name(url, 'test')
591 testname, subdir, tag = self._build_tagged_test_name(testname, dargs)
592 outputdir = self._make_test_outputdir(subdir)
jadmanski10646442008-08-13 14:05:21 +0000593
594 def group_func():
595 try:
596 test.runtest(self, url, tag, args, dargs)
597 except error.TestBaseException, e:
598 self.record(e.exit_status, subdir, testname, str(e))
599 raise
600 except Exception, e:
601 info = str(e) + "\n" + traceback.format_exc()
602 self.record('FAIL', subdir, testname, info)
603 raise
604 else:
mbligh2b92b862008-11-22 13:25:32 +0000605 self.record('GOOD', subdir, testname, 'completed successfully')
jadmanskide292df2008-08-26 20:51:14 +0000606
607 result, exc_info = self._run_group(testname, subdir, group_func)
608 if exc_info and isinstance(exc_info[1], error.TestBaseException):
609 return False
610 elif exc_info:
611 raise exc_info[0], exc_info[1], exc_info[2]
612 else:
613 return True
jadmanski10646442008-08-13 14:05:21 +0000614
615
616 def _run_group(self, name, subdir, function, *args, **dargs):
617 """\
618 Underlying method for running something inside of a group.
619 """
jadmanskide292df2008-08-26 20:51:14 +0000620 result, exc_info = None, None
jadmanski10646442008-08-13 14:05:21 +0000621 try:
622 self.record('START', subdir, name)
jadmanski52053632010-06-11 21:08:10 +0000623 result = function(*args, **dargs)
jadmanski10646442008-08-13 14:05:21 +0000624 except error.TestBaseException, e:
jadmanskib88d6dc2009-01-10 00:33:18 +0000625 self.record("END %s" % e.exit_status, subdir, name)
jadmanskide292df2008-08-26 20:51:14 +0000626 exc_info = sys.exc_info()
jadmanski10646442008-08-13 14:05:21 +0000627 except Exception, e:
628 err_msg = str(e) + '\n'
629 err_msg += traceback.format_exc()
630 self.record('END ABORT', subdir, name, err_msg)
631 raise error.JobError(name + ' failed\n' + traceback.format_exc())
632 else:
633 self.record('END GOOD', subdir, name)
634
jadmanskide292df2008-08-26 20:51:14 +0000635 return result, exc_info
jadmanski10646442008-08-13 14:05:21 +0000636
637
638 def run_group(self, function, *args, **dargs):
639 """\
640 function:
641 subroutine to run
642 *args:
643 arguments for the function
644 """
645
646 name = function.__name__
647
648 # Allow the tag for the group to be specified.
649 tag = dargs.pop('tag', None)
650 if tag:
651 name = tag
652
jadmanskide292df2008-08-26 20:51:14 +0000653 return self._run_group(name, None, function, *args, **dargs)[0]
jadmanski10646442008-08-13 14:05:21 +0000654
655
656 def run_reboot(self, reboot_func, get_kernel_func):
657 """\
658 A specialization of run_group meant specifically for handling
659 a reboot. Includes support for capturing the kernel version
660 after the reboot.
661
662 reboot_func: a function that carries out the reboot
663
664 get_kernel_func: a function that returns a string
665 representing the kernel version.
666 """
jadmanski10646442008-08-13 14:05:21 +0000667 try:
668 self.record('START', None, 'reboot')
jadmanski10646442008-08-13 14:05:21 +0000669 reboot_func()
670 except Exception, e:
jadmanski10646442008-08-13 14:05:21 +0000671 err_msg = str(e) + '\n' + traceback.format_exc()
672 self.record('END FAIL', None, 'reboot', err_msg)
jadmanski4b51d542009-04-08 14:17:16 +0000673 raise
jadmanski10646442008-08-13 14:05:21 +0000674 else:
675 kernel = get_kernel_func()
jadmanski10646442008-08-13 14:05:21 +0000676 self.record('END GOOD', None, 'reboot',
Dale Curtis74a314b2011-06-23 14:55:46 -0700677 optional_fields={"kernel": kernel})
jadmanski10646442008-08-13 14:05:21 +0000678
679
jadmanskie432dd22009-01-30 15:04:51 +0000680 def run_control(self, path):
681 """Execute a control file found at path (relative to the autotest
682 path). Intended for executing a control file within a control file,
683 not for running the top-level job control file."""
684 path = os.path.join(self.autodir, path)
685 control_file = self._load_control_file(path)
mbligh0d0f67d2009-11-06 03:15:03 +0000686 self.run(control=control_file, control_file_dir=self._USE_TEMP_DIR)
jadmanskie432dd22009-01-30 15:04:51 +0000687
688
jadmanskic09fc152008-10-15 17:56:59 +0000689 def add_sysinfo_command(self, command, logfile=None, on_every_test=False):
mbligh4395bbd2009-03-25 19:34:17 +0000690 self._add_sysinfo_loggable(sysinfo.command(command, logf=logfile),
jadmanskic09fc152008-10-15 17:56:59 +0000691 on_every_test)
692
693
694 def add_sysinfo_logfile(self, file, on_every_test=False):
695 self._add_sysinfo_loggable(sysinfo.logfile(file), on_every_test)
696
697
698 def _add_sysinfo_loggable(self, loggable, on_every_test):
699 if on_every_test:
700 self.sysinfo.test_loggables.add(loggable)
701 else:
702 self.sysinfo.boot_loggables.add(loggable)
703
704
jadmanski10646442008-08-13 14:05:21 +0000705 def _read_warnings(self):
jadmanskif37df842009-02-11 00:03:26 +0000706 """Poll all the warning loggers and extract any new warnings that have
707 been logged. If the warnings belong to a category that is currently
708 disabled, this method will discard them and they will no longer be
709 retrievable.
710
711 Returns a list of (timestamp, message) tuples, where timestamp is an
712 integer epoch timestamp."""
jadmanski10646442008-08-13 14:05:21 +0000713 warnings = []
714 while True:
715 # pull in a line of output from every logger that has
716 # output ready to be read
mbligh2b92b862008-11-22 13:25:32 +0000717 loggers, _, _ = select.select(self.warning_loggers, [], [], 0)
jadmanski10646442008-08-13 14:05:21 +0000718 closed_loggers = set()
719 for logger in loggers:
720 line = logger.readline()
721 # record any broken pipes (aka line == empty)
722 if len(line) == 0:
723 closed_loggers.add(logger)
724 continue
jadmanskif37df842009-02-11 00:03:26 +0000725 # parse out the warning
726 timestamp, msgtype, msg = line.split('\t', 2)
727 timestamp = int(timestamp)
728 # if the warning is valid, add it to the results
729 if self.warning_manager.is_valid(timestamp, msgtype):
730 warnings.append((timestamp, msg.strip()))
jadmanski10646442008-08-13 14:05:21 +0000731
732 # stop listening to loggers that are closed
733 self.warning_loggers -= closed_loggers
734
735 # stop if none of the loggers have any output left
736 if not loggers:
737 break
738
739 # sort into timestamp order
740 warnings.sort()
741 return warnings
742
743
showardcc929362010-01-25 21:20:41 +0000744 def _unique_subdirectory(self, base_subdirectory_name):
745 """Compute a unique results subdirectory based on the given name.
746
747 Appends base_subdirectory_name with a number as necessary to find a
748 directory name that doesn't already exist.
749 """
750 subdirectory = base_subdirectory_name
751 counter = 1
752 while os.path.exists(os.path.join(self.resultdir, subdirectory)):
753 subdirectory = base_subdirectory_name + '.' + str(counter)
754 counter += 1
755 return subdirectory
756
757
jadmanski52053632010-06-11 21:08:10 +0000758 def get_record_context(self):
759 """Returns an object representing the current job.record context.
760
761 The object returned is an opaque object with a 0-arg restore method
762 which can be called to restore the job.record context (i.e. indentation)
763 to the current level. The intention is that it should be used when
764 something external which generate job.record calls (e.g. an autotest
765 client) can fail catastrophically and the server job record state
766 needs to be reset to its original "known good" state.
767
768 @return: A context object with a 0-arg restore() method."""
769 return self._indenter.get_context()
770
771
showardcc929362010-01-25 21:20:41 +0000772 def record_summary(self, status_code, test_name, reason='', attributes=None,
773 distinguishing_attributes=(), child_test_ids=None):
774 """Record a summary test result.
775
776 @param status_code: status code string, see
777 common_lib.log.is_valid_status()
778 @param test_name: name of the test
779 @param reason: (optional) string providing detailed reason for test
780 outcome
781 @param attributes: (optional) dict of string keyvals to associate with
782 this result
783 @param distinguishing_attributes: (optional) list of attribute names
784 that should be used to distinguish identically-named test
785 results. These attributes should be present in the attributes
786 parameter. This is used to generate user-friendly subdirectory
787 names.
788 @param child_test_ids: (optional) list of test indices for test results
789 used in generating this result.
790 """
791 subdirectory_name_parts = [test_name]
792 for attribute in distinguishing_attributes:
793 assert attributes
794 assert attribute in attributes, '%s not in %s' % (attribute,
795 attributes)
796 subdirectory_name_parts.append(attributes[attribute])
797 base_subdirectory_name = '.'.join(subdirectory_name_parts)
798
799 subdirectory = self._unique_subdirectory(base_subdirectory_name)
800 subdirectory_path = os.path.join(self.resultdir, subdirectory)
801 os.mkdir(subdirectory_path)
802
803 self.record(status_code, subdirectory, test_name,
804 status=reason, optional_fields={'is_summary': True})
805
806 if attributes:
807 utils.write_keyval(subdirectory_path, attributes)
808
809 if child_test_ids:
810 ids_string = ','.join(str(test_id) for test_id in child_test_ids)
811 summary_data = {'child_test_ids': ids_string}
812 utils.write_keyval(os.path.join(subdirectory_path, 'summary_data'),
813 summary_data)
814
815
jadmanski16a7ff72009-04-01 18:19:53 +0000816 def disable_warnings(self, warning_type):
jadmanskif37df842009-02-11 00:03:26 +0000817 self.warning_manager.disable_warnings(warning_type)
jadmanski16a7ff72009-04-01 18:19:53 +0000818 self.record("INFO", None, None,
819 "disabling %s warnings" % warning_type,
820 {"warnings.disable": warning_type})
jadmanskif37df842009-02-11 00:03:26 +0000821
822
jadmanski16a7ff72009-04-01 18:19:53 +0000823 def enable_warnings(self, warning_type):
jadmanskif37df842009-02-11 00:03:26 +0000824 self.warning_manager.enable_warnings(warning_type)
jadmanski16a7ff72009-04-01 18:19:53 +0000825 self.record("INFO", None, None,
826 "enabling %s warnings" % warning_type,
827 {"warnings.enable": warning_type})
jadmanskif37df842009-02-11 00:03:26 +0000828
829
jadmanski779bd292009-03-19 17:33:33 +0000830 def get_status_log_path(self, subdir=None):
831 """Return the path to the job status log.
832
833 @param subdir - Optional paramter indicating that you want the path
834 to a subdirectory status log.
835
836 @returns The path where the status log should be.
837 """
mbligh210bae62009-04-01 18:33:13 +0000838 if self.resultdir:
839 if subdir:
840 return os.path.join(self.resultdir, subdir, "status.log")
841 else:
842 return os.path.join(self.resultdir, "status.log")
jadmanski779bd292009-03-19 17:33:33 +0000843 else:
mbligh210bae62009-04-01 18:33:13 +0000844 return None
jadmanski779bd292009-03-19 17:33:33 +0000845
846
jadmanski6bb32d72009-03-19 20:25:24 +0000847 def _update_uncollected_logs_list(self, update_func):
848 """Updates the uncollected logs list in a multi-process safe manner.
849
850 @param update_func - a function that updates the list of uncollected
851 logs. Should take one parameter, the list to be updated.
852 """
mbligh0d0f67d2009-11-06 03:15:03 +0000853 if self._uncollected_log_file:
854 log_file = open(self._uncollected_log_file, "r+")
mbligha788dc42009-03-26 21:10:16 +0000855 fcntl.flock(log_file, fcntl.LOCK_EX)
jadmanski6bb32d72009-03-19 20:25:24 +0000856 try:
857 uncollected_logs = pickle.load(log_file)
858 update_func(uncollected_logs)
859 log_file.seek(0)
860 log_file.truncate()
861 pickle.dump(uncollected_logs, log_file)
jadmanski3bff9092009-04-22 18:09:47 +0000862 log_file.flush()
jadmanski6bb32d72009-03-19 20:25:24 +0000863 finally:
864 fcntl.flock(log_file, fcntl.LOCK_UN)
865 log_file.close()
866
867
868 def add_client_log(self, hostname, remote_path, local_path):
869 """Adds a new set of client logs to the list of uncollected logs,
870 to allow for future log recovery.
871
872 @param host - the hostname of the machine holding the logs
873 @param remote_path - the directory on the remote machine holding logs
874 @param local_path - the local directory to copy the logs into
875 """
876 def update_func(logs_list):
877 logs_list.append((hostname, remote_path, local_path))
878 self._update_uncollected_logs_list(update_func)
879
880
881 def remove_client_log(self, hostname, remote_path, local_path):
882 """Removes a set of client logs from the list of uncollected logs,
883 to allow for future log recovery.
884
885 @param host - the hostname of the machine holding the logs
886 @param remote_path - the directory on the remote machine holding logs
887 @param local_path - the local directory to copy the logs into
888 """
889 def update_func(logs_list):
890 logs_list.remove((hostname, remote_path, local_path))
891 self._update_uncollected_logs_list(update_func)
892
893
mbligh0d0f67d2009-11-06 03:15:03 +0000894 def get_client_logs(self):
895 """Retrieves the list of uncollected logs, if it exists.
896
897 @returns A list of (host, remote_path, local_path) tuples. Returns
898 an empty list if no uncollected logs file exists.
899 """
900 log_exists = (self._uncollected_log_file and
901 os.path.exists(self._uncollected_log_file))
902 if log_exists:
903 return pickle.load(open(self._uncollected_log_file))
904 else:
905 return []
906
907
mbligh084bc172008-10-18 14:02:45 +0000908 def _fill_server_control_namespace(self, namespace, protect=True):
mbligh2b92b862008-11-22 13:25:32 +0000909 """
910 Prepare a namespace to be used when executing server control files.
mbligh084bc172008-10-18 14:02:45 +0000911
912 This sets up the control file API by importing modules and making them
913 available under the appropriate names within namespace.
914
915 For use by _execute_code().
916
917 Args:
918 namespace: The namespace dictionary to fill in.
919 protect: Boolean. If True (the default) any operation that would
920 clobber an existing entry in namespace will cause an error.
921 Raises:
922 error.AutoservError: When a name would be clobbered by import.
923 """
924 def _import_names(module_name, names=()):
mbligh2b92b862008-11-22 13:25:32 +0000925 """
926 Import a module and assign named attributes into namespace.
mbligh084bc172008-10-18 14:02:45 +0000927
928 Args:
929 module_name: The string module name.
930 names: A limiting list of names to import from module_name. If
931 empty (the default), all names are imported from the module
932 similar to a "from foo.bar import *" statement.
933 Raises:
934 error.AutoservError: When a name being imported would clobber
935 a name already in namespace.
936 """
937 module = __import__(module_name, {}, {}, names)
938
939 # No names supplied? Import * from the lowest level module.
940 # (Ugh, why do I have to implement this part myself?)
941 if not names:
942 for submodule_name in module_name.split('.')[1:]:
943 module = getattr(module, submodule_name)
944 if hasattr(module, '__all__'):
945 names = getattr(module, '__all__')
946 else:
947 names = dir(module)
948
949 # Install each name into namespace, checking to make sure it
950 # doesn't override anything that already exists.
951 for name in names:
952 # Check for conflicts to help prevent future problems.
953 if name in namespace and protect:
954 if namespace[name] is not getattr(module, name):
955 raise error.AutoservError('importing name '
956 '%s from %s %r would override %r' %
957 (name, module_name, getattr(module, name),
958 namespace[name]))
959 else:
960 # Encourage cleanliness and the use of __all__ for a
961 # more concrete API with less surprises on '*' imports.
962 warnings.warn('%s (%r) being imported from %s for use '
963 'in server control files is not the '
964 'first occurrance of that import.' %
965 (name, namespace[name], module_name))
966
967 namespace[name] = getattr(module, name)
968
969
970 # This is the equivalent of prepending a bunch of import statements to
971 # the front of the control script.
mbligha2b07dd2009-06-22 18:26:13 +0000972 namespace.update(os=os, sys=sys, logging=logging)
mbligh084bc172008-10-18 14:02:45 +0000973 _import_names('autotest_lib.server',
974 ('hosts', 'autotest', 'kvm', 'git', 'standalone_profiler',
975 'source_kernel', 'rpm_kernel', 'deb_kernel', 'git_kernel'))
976 _import_names('autotest_lib.server.subcommand',
977 ('parallel', 'parallel_simple', 'subcommand'))
978 _import_names('autotest_lib.server.utils',
979 ('run', 'get_tmp_dir', 'sh_escape', 'parse_machine'))
980 _import_names('autotest_lib.client.common_lib.error')
981 _import_names('autotest_lib.client.common_lib.barrier', ('barrier',))
982
983 # Inject ourself as the job object into other classes within the API.
984 # (Yuck, this injection is a gross thing be part of a public API. -gps)
985 #
986 # XXX Base & SiteAutotest do not appear to use .job. Who does?
987 namespace['autotest'].Autotest.job = self
988 # server.hosts.base_classes.Host uses .job.
989 namespace['hosts'].Host.job = self
Eric Li10222b82010-11-24 09:33:15 -0800990 namespace['hosts'].factory.ssh_user = self._ssh_user
991 namespace['hosts'].factory.ssh_port = self._ssh_port
992 namespace['hosts'].factory.ssh_pass = self._ssh_pass
mbligh084bc172008-10-18 14:02:45 +0000993
994
995 def _execute_code(self, code_file, namespace, protect=True):
mbligh2b92b862008-11-22 13:25:32 +0000996 """
997 Execute code using a copy of namespace as a server control script.
mbligh084bc172008-10-18 14:02:45 +0000998
999 Unless protect_namespace is explicitly set to False, the dict will not
1000 be modified.
1001
1002 Args:
1003 code_file: The filename of the control file to execute.
1004 namespace: A dict containing names to make available during execution.
1005 protect: Boolean. If True (the default) a copy of the namespace dict
1006 is used during execution to prevent the code from modifying its
1007 contents outside of this function. If False the raw dict is
1008 passed in and modifications will be allowed.
1009 """
1010 if protect:
1011 namespace = namespace.copy()
1012 self._fill_server_control_namespace(namespace, protect=protect)
1013 # TODO: Simplify and get rid of the special cases for only 1 machine.
showard3e66e8c2008-10-27 19:20:51 +00001014 if len(self.machines) > 1:
mbligh084bc172008-10-18 14:02:45 +00001015 machines_text = '\n'.join(self.machines) + '\n'
1016 # Only rewrite the file if it does not match our machine list.
1017 try:
1018 machines_f = open(MACHINES_FILENAME, 'r')
1019 existing_machines_text = machines_f.read()
1020 machines_f.close()
1021 except EnvironmentError:
1022 existing_machines_text = None
1023 if machines_text != existing_machines_text:
1024 utils.open_write_close(MACHINES_FILENAME, machines_text)
1025 execfile(code_file, namespace, namespace)
jadmanski10646442008-08-13 14:05:21 +00001026
1027
jadmanskie29d0e42010-06-17 16:06:52 +00001028 def _parse_status(self, new_line):
mbligh0d0f67d2009-11-06 03:15:03 +00001029 if not self._using_parser:
jadmanski10646442008-08-13 14:05:21 +00001030 return
jadmanskie29d0e42010-06-17 16:06:52 +00001031 new_tests = self.parser.process_lines([new_line])
jadmanski10646442008-08-13 14:05:21 +00001032 for test in new_tests:
1033 self.__insert_test(test)
1034
1035
1036 def __insert_test(self, test):
mbligh2b92b862008-11-22 13:25:32 +00001037 """
1038 An internal method to insert a new test result into the
jadmanski10646442008-08-13 14:05:21 +00001039 database. This method will not raise an exception, even if an
1040 error occurs during the insert, to avoid failing a test
1041 simply because of unexpected database issues."""
showard21baa452008-10-21 00:08:39 +00001042 self.num_tests_run += 1
1043 if status_lib.is_worse_than_or_equal_to(test.status, 'FAIL'):
1044 self.num_tests_failed += 1
jadmanski10646442008-08-13 14:05:21 +00001045 try:
1046 self.results_db.insert_test(self.job_model, test)
1047 except Exception:
1048 msg = ("WARNING: An unexpected error occured while "
1049 "inserting test results into the database. "
1050 "Ignoring error.\n" + traceback.format_exc())
1051 print >> sys.stderr, msg
1052
mblighcaa62c22008-04-07 21:51:17 +00001053
mblighfc3da5b2010-01-06 18:37:22 +00001054 def preprocess_client_state(self):
1055 """
1056 Produce a state file for initializing the state of a client job.
1057
1058 Creates a new client state file with all the current server state, as
1059 well as some pre-set client state.
1060
1061 @returns The path of the file the state was written into.
1062 """
1063 # initialize the sysinfo state
1064 self._state.set('client', 'sysinfo', self.sysinfo.serialize())
1065
1066 # dump the state out to a tempfile
1067 fd, file_path = tempfile.mkstemp(dir=self.tmpdir)
1068 os.close(fd)
mbligha2c99492010-01-27 22:59:50 +00001069
1070 # write_to_file doesn't need locking, we exclusively own file_path
mblighfc3da5b2010-01-06 18:37:22 +00001071 self._state.write_to_file(file_path)
1072 return file_path
1073
1074
1075 def postprocess_client_state(self, state_path):
1076 """
1077 Update the state of this job with the state from a client job.
1078
1079 Updates the state of the server side of a job with the final state
1080 of a client job that was run. Updates the non-client-specific state,
1081 pulls in some specific bits from the client-specific state, and then
1082 discards the rest. Removes the state file afterwards
1083
1084 @param state_file A path to the state file from the client.
1085 """
1086 # update the on-disk state
mblighfc3da5b2010-01-06 18:37:22 +00001087 try:
jadmanskib6e7bdb2010-04-13 16:00:39 +00001088 self._state.read_from_file(state_path)
mblighfc3da5b2010-01-06 18:37:22 +00001089 os.remove(state_path)
mbligha2c99492010-01-27 22:59:50 +00001090 except OSError, e:
mblighfc3da5b2010-01-06 18:37:22 +00001091 # ignore file-not-found errors
1092 if e.errno != errno.ENOENT:
1093 raise
jadmanskib6e7bdb2010-04-13 16:00:39 +00001094 else:
1095 logging.debug('Client state file %s not found', state_path)
mblighfc3da5b2010-01-06 18:37:22 +00001096
1097 # update the sysinfo state
1098 if self._state.has('client', 'sysinfo'):
1099 self.sysinfo.deserialize(self._state.get('client', 'sysinfo'))
1100
1101 # drop all the client-specific state
1102 self._state.discard_namespace('client')
1103
1104
mbligh0a883702010-04-21 01:58:34 +00001105 def clear_all_known_hosts(self):
1106 """Clears known hosts files for all AbstractSSHHosts."""
1107 for host in self.hosts:
1108 if isinstance(host, abstract_ssh.AbstractSSHHost):
1109 host.clear_known_hosts()
1110
1111
jadmanskif37df842009-02-11 00:03:26 +00001112class warning_manager(object):
1113 """Class for controlling warning logs. Manages the enabling and disabling
1114 of warnings."""
1115 def __init__(self):
1116 # a map of warning types to a list of disabled time intervals
1117 self.disabled_warnings = {}
1118
1119
1120 def is_valid(self, timestamp, warning_type):
1121 """Indicates if a warning (based on the time it occured and its type)
1122 is a valid warning. A warning is considered "invalid" if this type of
1123 warning was marked as "disabled" at the time the warning occured."""
1124 disabled_intervals = self.disabled_warnings.get(warning_type, [])
1125 for start, end in disabled_intervals:
1126 if timestamp >= start and (end is None or timestamp < end):
1127 return False
1128 return True
1129
1130
1131 def disable_warnings(self, warning_type, current_time_func=time.time):
1132 """As of now, disables all further warnings of this type."""
1133 intervals = self.disabled_warnings.setdefault(warning_type, [])
1134 if not intervals or intervals[-1][1] is not None:
jadmanski16a7ff72009-04-01 18:19:53 +00001135 intervals.append((int(current_time_func()), None))
jadmanskif37df842009-02-11 00:03:26 +00001136
1137
1138 def enable_warnings(self, warning_type, current_time_func=time.time):
1139 """As of now, enables all further warnings of this type."""
1140 intervals = self.disabled_warnings.get(warning_type, [])
1141 if intervals and intervals[-1][1] is None:
jadmanski16a7ff72009-04-01 18:19:53 +00001142 intervals[-1] = (intervals[-1][0], int(current_time_func()))
Paul Pendlebury57593562011-06-15 10:45:49 -07001143
1144
1145# load up site-specific code for generating site-specific job data
1146get_site_job_data = utils.import_site_function(__file__,
1147 "autotest_lib.server.site_server_job", "get_site_job_data",
1148 _get_site_job_data_dummy)
1149
1150
1151site_server_job = utils.import_site_class(
1152 __file__, "autotest_lib.server.site_server_job", "site_server_job",
1153 base_server_job)
1154
1155
1156class server_job(site_server_job):
Dale Curtis456d3c12011-07-19 11:42:51 -07001157 pass