blob: fcfd4019e0285a979521ef24165d014fc5b7315b [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')
mbligh084bc172008-10-18 14:02:45 +000038VERIFY_CONTROL_FILE = _control_segment_path('verify')
mbligh084bc172008-10-18 14:02:45 +000039REPAIR_CONTROL_FILE = _control_segment_path('repair')
Alex Millercb79ba72013-05-29 14:43:00 -070040PROVISION_CONTROL_FILE = _control_segment_path('provision')
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
Alex Millercb79ba72013-05-29 14:43:00 -0700365 def provision(self, labels):
366 """
367 Provision all hosts to match |labels|.
368
369 @param labels: A comma seperated string of labels to provision the
370 host to.
371
372 """
373 namespace = {'provision_labels': labels}
374 control = self._load_control_file(PROVISION_CONTROL_FILE)
375 self.run(control=control, namespace=namespace)
376
377
jadmanski10646442008-08-13 14:05:21 +0000378 def precheck(self):
379 """
380 perform any additional checks in derived classes.
381 """
382 pass
383
384
385 def enable_external_logging(self):
mbligh2b92b862008-11-22 13:25:32 +0000386 """
387 Start or restart external logging mechanism.
jadmanski10646442008-08-13 14:05:21 +0000388 """
389 pass
390
391
392 def disable_external_logging(self):
mbligh2b92b862008-11-22 13:25:32 +0000393 """
394 Pause or stop external logging mechanism.
jadmanski10646442008-08-13 14:05:21 +0000395 """
396 pass
397
398
399 def use_external_logging(self):
mbligh2b92b862008-11-22 13:25:32 +0000400 """
401 Return True if external logging should be used.
jadmanski10646442008-08-13 14:05:21 +0000402 """
403 return False
404
405
mbligh415dc212009-06-15 21:53:34 +0000406 def _make_parallel_wrapper(self, function, machines, log):
407 """Wrap function as appropriate for calling by parallel_simple."""
mbligh2b92b862008-11-22 13:25:32 +0000408 is_forking = not (len(machines) == 1 and self.machines == machines)
mbligh0d0f67d2009-11-06 03:15:03 +0000409 if self._parse_job and is_forking and log:
jadmanski10646442008-08-13 14:05:21 +0000410 def wrapper(machine):
mbligh0d0f67d2009-11-06 03:15:03 +0000411 self._parse_job += "/" + machine
412 self._using_parser = True
jadmanski10646442008-08-13 14:05:21 +0000413 self.machines = [machine]
mbligh0d0f67d2009-11-06 03:15:03 +0000414 self.push_execution_context(machine)
jadmanski609a5f42008-08-26 20:52:42 +0000415 os.chdir(self.resultdir)
showard2bab8f42008-11-12 18:15:22 +0000416 utils.write_keyval(self.resultdir, {"hostname": machine})
mbligh4608b002010-01-05 18:22:35 +0000417 self.init_parser()
jadmanski10646442008-08-13 14:05:21 +0000418 result = function(machine)
419 self.cleanup_parser()
420 return result
jadmanski4dd1a002008-09-05 20:27:30 +0000421 elif len(machines) > 1 and log:
jadmanski10646442008-08-13 14:05:21 +0000422 def wrapper(machine):
mbligh0d0f67d2009-11-06 03:15:03 +0000423 self.push_execution_context(machine)
jadmanski609a5f42008-08-26 20:52:42 +0000424 os.chdir(self.resultdir)
mbligh838d82d2009-03-11 17:14:31 +0000425 machine_data = {'hostname' : machine,
mbligh0d0f67d2009-11-06 03:15:03 +0000426 'status_version' : str(self._STATUS_VERSION)}
mbligh838d82d2009-03-11 17:14:31 +0000427 utils.write_keyval(self.resultdir, machine_data)
jadmanski10646442008-08-13 14:05:21 +0000428 result = function(machine)
429 return result
430 else:
431 wrapper = function
mbligh415dc212009-06-15 21:53:34 +0000432 return wrapper
433
434
435 def parallel_simple(self, function, machines, log=True, timeout=None,
436 return_results=False):
437 """
438 Run 'function' using parallel_simple, with an extra wrapper to handle
439 the necessary setup for continuous parsing, if possible. If continuous
440 parsing is already properly initialized then this should just work.
441
442 @param function: A callable to run in parallel given each machine.
443 @param machines: A list of machine names to be passed one per subcommand
444 invocation of function.
445 @param log: If True, output will be written to output in a subdirectory
446 named after each machine.
447 @param timeout: Seconds after which the function call should timeout.
448 @param return_results: If True instead of an AutoServError being raised
449 on any error a list of the results|exceptions from the function
450 called on each arg is returned. [default: False]
451
452 @raises error.AutotestError: If any of the functions failed.
453 """
454 wrapper = self._make_parallel_wrapper(function, machines, log)
455 return subcommand.parallel_simple(wrapper, machines,
456 log=log, timeout=timeout,
457 return_results=return_results)
458
459
460 def parallel_on_machines(self, function, machines, timeout=None):
461 """
showardcd5fac42009-07-06 20:19:43 +0000462 @param function: Called in parallel with one machine as its argument.
mbligh415dc212009-06-15 21:53:34 +0000463 @param machines: A list of machines to call function(machine) on.
464 @param timeout: Seconds after which the function call should timeout.
465
466 @returns A list of machines on which function(machine) returned
467 without raising an exception.
468 """
showardcd5fac42009-07-06 20:19:43 +0000469 results = self.parallel_simple(function, machines, timeout=timeout,
mbligh415dc212009-06-15 21:53:34 +0000470 return_results=True)
471 success_machines = []
472 for result, machine in itertools.izip(results, machines):
473 if not isinstance(result, Exception):
474 success_machines.append(machine)
475 return success_machines
jadmanski10646442008-08-13 14:05:21 +0000476
477
mbligh0d0f67d2009-11-06 03:15:03 +0000478 _USE_TEMP_DIR = object()
mbligh2b92b862008-11-22 13:25:32 +0000479 def run(self, cleanup=False, install_before=False, install_after=False,
jadmanskie432dd22009-01-30 15:04:51 +0000480 collect_crashdumps=True, namespace={}, control=None,
jadmanskidef0c3c2009-03-25 20:07:10 +0000481 control_file_dir=None, only_collect_crashinfo=False):
jadmanskifb9c0fa2009-04-29 17:39:16 +0000482 # for a normal job, make sure the uncollected logs file exists
483 # for a crashinfo-only run it should already exist, bail out otherwise
jadmanski648c39f2010-03-19 17:38:01 +0000484 created_uncollected_logs = False
mbligh0d0f67d2009-11-06 03:15:03 +0000485 if self.resultdir and not os.path.exists(self._uncollected_log_file):
jadmanskifb9c0fa2009-04-29 17:39:16 +0000486 if only_collect_crashinfo:
487 # if this is a crashinfo-only run, and there were no existing
488 # uncollected logs, just bail out early
489 logging.info("No existing uncollected logs, "
490 "skipping crashinfo collection")
491 return
492 else:
mbligh0d0f67d2009-11-06 03:15:03 +0000493 log_file = open(self._uncollected_log_file, "w")
jadmanskifb9c0fa2009-04-29 17:39:16 +0000494 pickle.dump([], log_file)
495 log_file.close()
jadmanski648c39f2010-03-19 17:38:01 +0000496 created_uncollected_logs = True
jadmanskifb9c0fa2009-04-29 17:39:16 +0000497
jadmanski10646442008-08-13 14:05:21 +0000498 # use a copy so changes don't affect the original dictionary
499 namespace = namespace.copy()
500 machines = self.machines
jadmanskie432dd22009-01-30 15:04:51 +0000501 if control is None:
jadmanski02a3ba22009-11-13 20:47:27 +0000502 if self.control is None:
503 control = ''
504 else:
505 control = self._load_control_file(self.control)
jadmanskie432dd22009-01-30 15:04:51 +0000506 if control_file_dir is None:
507 control_file_dir = self.resultdir
jadmanski10646442008-08-13 14:05:21 +0000508
509 self.aborted = False
510 namespace['machines'] = machines
jadmanski808f4b12010-04-09 22:30:31 +0000511 namespace['args'] = self.args
jadmanski10646442008-08-13 14:05:21 +0000512 namespace['job'] = self
mbligh0d0f67d2009-11-06 03:15:03 +0000513 namespace['ssh_user'] = self._ssh_user
514 namespace['ssh_port'] = self._ssh_port
515 namespace['ssh_pass'] = self._ssh_pass
jadmanski10646442008-08-13 14:05:21 +0000516 test_start_time = int(time.time())
517
mbligh80e1eba2008-11-19 00:26:18 +0000518 if self.resultdir:
519 os.chdir(self.resultdir)
jadmanski779bd292009-03-19 17:33:33 +0000520 # touch status.log so that the parser knows a job is running here
jadmanski382303a2009-04-21 19:53:39 +0000521 open(self.get_status_log_path(), 'a').close()
mbligh80e1eba2008-11-19 00:26:18 +0000522 self.enable_external_logging()
jadmanskie432dd22009-01-30 15:04:51 +0000523
jadmanskicdd0c402008-09-19 21:21:31 +0000524 collect_crashinfo = True
mblighaebe3b62008-12-22 14:45:40 +0000525 temp_control_file_dir = None
jadmanski10646442008-08-13 14:05:21 +0000526 try:
showardcf8d4922009-10-14 16:08:39 +0000527 try:
528 if install_before and machines:
529 self._execute_code(INSTALL_CONTROL_FILE, namespace)
jadmanskie432dd22009-01-30 15:04:51 +0000530
showardcf8d4922009-10-14 16:08:39 +0000531 if only_collect_crashinfo:
532 return
533
jadmanskidef0c3c2009-03-25 20:07:10 +0000534 # determine the dir to write the control files to
535 cfd_specified = (control_file_dir
mbligh0d0f67d2009-11-06 03:15:03 +0000536 and control_file_dir is not self._USE_TEMP_DIR)
jadmanskidef0c3c2009-03-25 20:07:10 +0000537 if cfd_specified:
538 temp_control_file_dir = None
539 else:
540 temp_control_file_dir = tempfile.mkdtemp(
541 suffix='temp_control_file_dir')
542 control_file_dir = temp_control_file_dir
543 server_control_file = os.path.join(control_file_dir,
mblighe0cbc912010-03-11 18:03:07 +0000544 self._control_filename)
jadmanskidef0c3c2009-03-25 20:07:10 +0000545 client_control_file = os.path.join(control_file_dir,
546 CLIENT_CONTROL_FILENAME)
mbligh0d0f67d2009-11-06 03:15:03 +0000547 if self._client:
jadmanskidef0c3c2009-03-25 20:07:10 +0000548 namespace['control'] = control
549 utils.open_write_close(client_control_file, control)
mblighfeac0102009-04-28 18:31:12 +0000550 shutil.copyfile(CLIENT_WRAPPER_CONTROL_FILE,
551 server_control_file)
jadmanskidef0c3c2009-03-25 20:07:10 +0000552 else:
553 utils.open_write_close(server_control_file, control)
mbligh26f0d882009-06-22 18:30:01 +0000554 logging.info("Processing control file")
jadmanskidef0c3c2009-03-25 20:07:10 +0000555 self._execute_code(server_control_file, namespace)
mbligh26f0d882009-06-22 18:30:01 +0000556 logging.info("Finished processing control file")
jadmanski10646442008-08-13 14:05:21 +0000557
jadmanskidef0c3c2009-03-25 20:07:10 +0000558 # no error occured, so we don't need to collect crashinfo
559 collect_crashinfo = False
Eric Li6f27d4f2010-09-29 10:55:17 -0700560 except Exception, e:
showardcf8d4922009-10-14 16:08:39 +0000561 try:
562 logging.exception(
563 'Exception escaped control file, job aborting:')
Eric Li6f27d4f2010-09-29 10:55:17 -0700564 self.record('INFO', None, None, str(e),
565 {'job_abort_reason': str(e)})
showardcf8d4922009-10-14 16:08:39 +0000566 except:
567 pass # don't let logging exceptions here interfere
568 raise
jadmanski10646442008-08-13 14:05:21 +0000569 finally:
mblighaebe3b62008-12-22 14:45:40 +0000570 if temp_control_file_dir:
jadmanskie432dd22009-01-30 15:04:51 +0000571 # Clean up temp directory used for copies of the control files
mblighaebe3b62008-12-22 14:45:40 +0000572 try:
573 shutil.rmtree(temp_control_file_dir)
574 except Exception, e:
mblighe7d9c602009-07-02 19:02:33 +0000575 logging.warn('Could not remove temp directory %s: %s',
576 temp_control_file_dir, e)
jadmanskie432dd22009-01-30 15:04:51 +0000577
jadmanskicdd0c402008-09-19 21:21:31 +0000578 if machines and (collect_crashdumps or collect_crashinfo):
jadmanski10646442008-08-13 14:05:21 +0000579 namespace['test_start_time'] = test_start_time
jadmanskicdd0c402008-09-19 21:21:31 +0000580 if collect_crashinfo:
mbligh084bc172008-10-18 14:02:45 +0000581 # includes crashdumps
582 self._execute_code(CRASHINFO_CONTROL_FILE, namespace)
jadmanskicdd0c402008-09-19 21:21:31 +0000583 else:
mbligh084bc172008-10-18 14:02:45 +0000584 self._execute_code(CRASHDUMPS_CONTROL_FILE, namespace)
jadmanski10646442008-08-13 14:05:21 +0000585 self.disable_external_logging()
showard45ae8192008-11-05 19:32:53 +0000586 if cleanup and machines:
587 self._execute_code(CLEANUP_CONTROL_FILE, namespace)
Chris Sosaf4d43ff2012-10-30 11:21:05 -0700588 if self._uncollected_log_file and created_uncollected_logs:
589 os.remove(self._uncollected_log_file)
jadmanski10646442008-08-13 14:05:21 +0000590 if install_after and machines:
mbligh084bc172008-10-18 14:02:45 +0000591 self._execute_code(INSTALL_CONTROL_FILE, namespace)
jadmanski10646442008-08-13 14:05:21 +0000592
593
594 def run_test(self, url, *args, **dargs):
mbligh2b92b862008-11-22 13:25:32 +0000595 """
596 Summon a test object and run it.
jadmanski10646442008-08-13 14:05:21 +0000597
598 tag
599 tag to add to testname
600 url
601 url of the test to run
602 """
mblighfc3da5b2010-01-06 18:37:22 +0000603 group, testname = self.pkgmgr.get_package_name(url, 'test')
604 testname, subdir, tag = self._build_tagged_test_name(testname, dargs)
605 outputdir = self._make_test_outputdir(subdir)
jadmanski10646442008-08-13 14:05:21 +0000606
607 def group_func():
608 try:
609 test.runtest(self, url, tag, args, dargs)
610 except error.TestBaseException, e:
611 self.record(e.exit_status, subdir, testname, str(e))
612 raise
613 except Exception, e:
614 info = str(e) + "\n" + traceback.format_exc()
615 self.record('FAIL', subdir, testname, info)
616 raise
617 else:
mbligh2b92b862008-11-22 13:25:32 +0000618 self.record('GOOD', subdir, testname, 'completed successfully')
jadmanskide292df2008-08-26 20:51:14 +0000619
620 result, exc_info = self._run_group(testname, subdir, group_func)
621 if exc_info and isinstance(exc_info[1], error.TestBaseException):
622 return False
623 elif exc_info:
624 raise exc_info[0], exc_info[1], exc_info[2]
625 else:
626 return True
jadmanski10646442008-08-13 14:05:21 +0000627
628
629 def _run_group(self, name, subdir, function, *args, **dargs):
630 """\
631 Underlying method for running something inside of a group.
632 """
jadmanskide292df2008-08-26 20:51:14 +0000633 result, exc_info = None, None
jadmanski10646442008-08-13 14:05:21 +0000634 try:
635 self.record('START', subdir, name)
jadmanski52053632010-06-11 21:08:10 +0000636 result = function(*args, **dargs)
jadmanski10646442008-08-13 14:05:21 +0000637 except error.TestBaseException, e:
jadmanskib88d6dc2009-01-10 00:33:18 +0000638 self.record("END %s" % e.exit_status, subdir, name)
jadmanskide292df2008-08-26 20:51:14 +0000639 exc_info = sys.exc_info()
jadmanski10646442008-08-13 14:05:21 +0000640 except Exception, e:
641 err_msg = str(e) + '\n'
642 err_msg += traceback.format_exc()
643 self.record('END ABORT', subdir, name, err_msg)
644 raise error.JobError(name + ' failed\n' + traceback.format_exc())
645 else:
646 self.record('END GOOD', subdir, name)
647
jadmanskide292df2008-08-26 20:51:14 +0000648 return result, exc_info
jadmanski10646442008-08-13 14:05:21 +0000649
650
651 def run_group(self, function, *args, **dargs):
652 """\
653 function:
654 subroutine to run
655 *args:
656 arguments for the function
657 """
658
659 name = function.__name__
660
661 # Allow the tag for the group to be specified.
662 tag = dargs.pop('tag', None)
663 if tag:
664 name = tag
665
jadmanskide292df2008-08-26 20:51:14 +0000666 return self._run_group(name, None, function, *args, **dargs)[0]
jadmanski10646442008-08-13 14:05:21 +0000667
668
669 def run_reboot(self, reboot_func, get_kernel_func):
670 """\
671 A specialization of run_group meant specifically for handling
672 a reboot. Includes support for capturing the kernel version
673 after the reboot.
674
675 reboot_func: a function that carries out the reboot
676
677 get_kernel_func: a function that returns a string
678 representing the kernel version.
679 """
jadmanski10646442008-08-13 14:05:21 +0000680 try:
681 self.record('START', None, 'reboot')
jadmanski10646442008-08-13 14:05:21 +0000682 reboot_func()
683 except Exception, e:
jadmanski10646442008-08-13 14:05:21 +0000684 err_msg = str(e) + '\n' + traceback.format_exc()
685 self.record('END FAIL', None, 'reboot', err_msg)
jadmanski4b51d542009-04-08 14:17:16 +0000686 raise
jadmanski10646442008-08-13 14:05:21 +0000687 else:
688 kernel = get_kernel_func()
jadmanski10646442008-08-13 14:05:21 +0000689 self.record('END GOOD', None, 'reboot',
Dale Curtis74a314b2011-06-23 14:55:46 -0700690 optional_fields={"kernel": kernel})
jadmanski10646442008-08-13 14:05:21 +0000691
692
jadmanskie432dd22009-01-30 15:04:51 +0000693 def run_control(self, path):
694 """Execute a control file found at path (relative to the autotest
695 path). Intended for executing a control file within a control file,
696 not for running the top-level job control file."""
697 path = os.path.join(self.autodir, path)
698 control_file = self._load_control_file(path)
mbligh0d0f67d2009-11-06 03:15:03 +0000699 self.run(control=control_file, control_file_dir=self._USE_TEMP_DIR)
jadmanskie432dd22009-01-30 15:04:51 +0000700
701
jadmanskic09fc152008-10-15 17:56:59 +0000702 def add_sysinfo_command(self, command, logfile=None, on_every_test=False):
mbligh4395bbd2009-03-25 19:34:17 +0000703 self._add_sysinfo_loggable(sysinfo.command(command, logf=logfile),
jadmanskic09fc152008-10-15 17:56:59 +0000704 on_every_test)
705
706
707 def add_sysinfo_logfile(self, file, on_every_test=False):
708 self._add_sysinfo_loggable(sysinfo.logfile(file), on_every_test)
709
710
711 def _add_sysinfo_loggable(self, loggable, on_every_test):
712 if on_every_test:
713 self.sysinfo.test_loggables.add(loggable)
714 else:
715 self.sysinfo.boot_loggables.add(loggable)
716
717
jadmanski10646442008-08-13 14:05:21 +0000718 def _read_warnings(self):
jadmanskif37df842009-02-11 00:03:26 +0000719 """Poll all the warning loggers and extract any new warnings that have
720 been logged. If the warnings belong to a category that is currently
721 disabled, this method will discard them and they will no longer be
722 retrievable.
723
724 Returns a list of (timestamp, message) tuples, where timestamp is an
725 integer epoch timestamp."""
jadmanski10646442008-08-13 14:05:21 +0000726 warnings = []
727 while True:
728 # pull in a line of output from every logger that has
729 # output ready to be read
mbligh2b92b862008-11-22 13:25:32 +0000730 loggers, _, _ = select.select(self.warning_loggers, [], [], 0)
jadmanski10646442008-08-13 14:05:21 +0000731 closed_loggers = set()
732 for logger in loggers:
733 line = logger.readline()
734 # record any broken pipes (aka line == empty)
735 if len(line) == 0:
736 closed_loggers.add(logger)
737 continue
jadmanskif37df842009-02-11 00:03:26 +0000738 # parse out the warning
739 timestamp, msgtype, msg = line.split('\t', 2)
740 timestamp = int(timestamp)
741 # if the warning is valid, add it to the results
742 if self.warning_manager.is_valid(timestamp, msgtype):
743 warnings.append((timestamp, msg.strip()))
jadmanski10646442008-08-13 14:05:21 +0000744
745 # stop listening to loggers that are closed
746 self.warning_loggers -= closed_loggers
747
748 # stop if none of the loggers have any output left
749 if not loggers:
750 break
751
752 # sort into timestamp order
753 warnings.sort()
754 return warnings
755
756
showardcc929362010-01-25 21:20:41 +0000757 def _unique_subdirectory(self, base_subdirectory_name):
758 """Compute a unique results subdirectory based on the given name.
759
760 Appends base_subdirectory_name with a number as necessary to find a
761 directory name that doesn't already exist.
762 """
763 subdirectory = base_subdirectory_name
764 counter = 1
765 while os.path.exists(os.path.join(self.resultdir, subdirectory)):
766 subdirectory = base_subdirectory_name + '.' + str(counter)
767 counter += 1
768 return subdirectory
769
770
jadmanski52053632010-06-11 21:08:10 +0000771 def get_record_context(self):
772 """Returns an object representing the current job.record context.
773
774 The object returned is an opaque object with a 0-arg restore method
775 which can be called to restore the job.record context (i.e. indentation)
776 to the current level. The intention is that it should be used when
777 something external which generate job.record calls (e.g. an autotest
778 client) can fail catastrophically and the server job record state
779 needs to be reset to its original "known good" state.
780
781 @return: A context object with a 0-arg restore() method."""
782 return self._indenter.get_context()
783
784
showardcc929362010-01-25 21:20:41 +0000785 def record_summary(self, status_code, test_name, reason='', attributes=None,
786 distinguishing_attributes=(), child_test_ids=None):
787 """Record a summary test result.
788
789 @param status_code: status code string, see
790 common_lib.log.is_valid_status()
791 @param test_name: name of the test
792 @param reason: (optional) string providing detailed reason for test
793 outcome
794 @param attributes: (optional) dict of string keyvals to associate with
795 this result
796 @param distinguishing_attributes: (optional) list of attribute names
797 that should be used to distinguish identically-named test
798 results. These attributes should be present in the attributes
799 parameter. This is used to generate user-friendly subdirectory
800 names.
801 @param child_test_ids: (optional) list of test indices for test results
802 used in generating this result.
803 """
804 subdirectory_name_parts = [test_name]
805 for attribute in distinguishing_attributes:
806 assert attributes
807 assert attribute in attributes, '%s not in %s' % (attribute,
808 attributes)
809 subdirectory_name_parts.append(attributes[attribute])
810 base_subdirectory_name = '.'.join(subdirectory_name_parts)
811
812 subdirectory = self._unique_subdirectory(base_subdirectory_name)
813 subdirectory_path = os.path.join(self.resultdir, subdirectory)
814 os.mkdir(subdirectory_path)
815
816 self.record(status_code, subdirectory, test_name,
817 status=reason, optional_fields={'is_summary': True})
818
819 if attributes:
820 utils.write_keyval(subdirectory_path, attributes)
821
822 if child_test_ids:
823 ids_string = ','.join(str(test_id) for test_id in child_test_ids)
824 summary_data = {'child_test_ids': ids_string}
825 utils.write_keyval(os.path.join(subdirectory_path, 'summary_data'),
826 summary_data)
827
828
jadmanski16a7ff72009-04-01 18:19:53 +0000829 def disable_warnings(self, warning_type):
jadmanskif37df842009-02-11 00:03:26 +0000830 self.warning_manager.disable_warnings(warning_type)
jadmanski16a7ff72009-04-01 18:19:53 +0000831 self.record("INFO", None, None,
832 "disabling %s warnings" % warning_type,
833 {"warnings.disable": warning_type})
jadmanskif37df842009-02-11 00:03:26 +0000834
835
jadmanski16a7ff72009-04-01 18:19:53 +0000836 def enable_warnings(self, warning_type):
jadmanskif37df842009-02-11 00:03:26 +0000837 self.warning_manager.enable_warnings(warning_type)
jadmanski16a7ff72009-04-01 18:19:53 +0000838 self.record("INFO", None, None,
839 "enabling %s warnings" % warning_type,
840 {"warnings.enable": warning_type})
jadmanskif37df842009-02-11 00:03:26 +0000841
842
jadmanski779bd292009-03-19 17:33:33 +0000843 def get_status_log_path(self, subdir=None):
844 """Return the path to the job status log.
845
846 @param subdir - Optional paramter indicating that you want the path
847 to a subdirectory status log.
848
849 @returns The path where the status log should be.
850 """
mbligh210bae62009-04-01 18:33:13 +0000851 if self.resultdir:
852 if subdir:
853 return os.path.join(self.resultdir, subdir, "status.log")
854 else:
855 return os.path.join(self.resultdir, "status.log")
jadmanski779bd292009-03-19 17:33:33 +0000856 else:
mbligh210bae62009-04-01 18:33:13 +0000857 return None
jadmanski779bd292009-03-19 17:33:33 +0000858
859
jadmanski6bb32d72009-03-19 20:25:24 +0000860 def _update_uncollected_logs_list(self, update_func):
861 """Updates the uncollected logs list in a multi-process safe manner.
862
863 @param update_func - a function that updates the list of uncollected
864 logs. Should take one parameter, the list to be updated.
865 """
mbligh0d0f67d2009-11-06 03:15:03 +0000866 if self._uncollected_log_file:
867 log_file = open(self._uncollected_log_file, "r+")
mbligha788dc42009-03-26 21:10:16 +0000868 fcntl.flock(log_file, fcntl.LOCK_EX)
jadmanski6bb32d72009-03-19 20:25:24 +0000869 try:
870 uncollected_logs = pickle.load(log_file)
871 update_func(uncollected_logs)
872 log_file.seek(0)
873 log_file.truncate()
874 pickle.dump(uncollected_logs, log_file)
jadmanski3bff9092009-04-22 18:09:47 +0000875 log_file.flush()
jadmanski6bb32d72009-03-19 20:25:24 +0000876 finally:
877 fcntl.flock(log_file, fcntl.LOCK_UN)
878 log_file.close()
879
880
881 def add_client_log(self, hostname, remote_path, local_path):
882 """Adds a new set of client logs to 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.append((hostname, remote_path, local_path))
891 self._update_uncollected_logs_list(update_func)
892
893
894 def remove_client_log(self, hostname, remote_path, local_path):
895 """Removes a set of client logs from the list of uncollected logs,
896 to allow for future log recovery.
897
898 @param host - the hostname of the machine holding the logs
899 @param remote_path - the directory on the remote machine holding logs
900 @param local_path - the local directory to copy the logs into
901 """
902 def update_func(logs_list):
903 logs_list.remove((hostname, remote_path, local_path))
904 self._update_uncollected_logs_list(update_func)
905
906
mbligh0d0f67d2009-11-06 03:15:03 +0000907 def get_client_logs(self):
908 """Retrieves the list of uncollected logs, if it exists.
909
910 @returns A list of (host, remote_path, local_path) tuples. Returns
911 an empty list if no uncollected logs file exists.
912 """
913 log_exists = (self._uncollected_log_file and
914 os.path.exists(self._uncollected_log_file))
915 if log_exists:
916 return pickle.load(open(self._uncollected_log_file))
917 else:
918 return []
919
920
mbligh084bc172008-10-18 14:02:45 +0000921 def _fill_server_control_namespace(self, namespace, protect=True):
mbligh2b92b862008-11-22 13:25:32 +0000922 """
923 Prepare a namespace to be used when executing server control files.
mbligh084bc172008-10-18 14:02:45 +0000924
925 This sets up the control file API by importing modules and making them
926 available under the appropriate names within namespace.
927
928 For use by _execute_code().
929
930 Args:
931 namespace: The namespace dictionary to fill in.
932 protect: Boolean. If True (the default) any operation that would
933 clobber an existing entry in namespace will cause an error.
934 Raises:
935 error.AutoservError: When a name would be clobbered by import.
936 """
937 def _import_names(module_name, names=()):
mbligh2b92b862008-11-22 13:25:32 +0000938 """
939 Import a module and assign named attributes into namespace.
mbligh084bc172008-10-18 14:02:45 +0000940
941 Args:
942 module_name: The string module name.
943 names: A limiting list of names to import from module_name. If
944 empty (the default), all names are imported from the module
945 similar to a "from foo.bar import *" statement.
946 Raises:
947 error.AutoservError: When a name being imported would clobber
948 a name already in namespace.
949 """
950 module = __import__(module_name, {}, {}, names)
951
952 # No names supplied? Import * from the lowest level module.
953 # (Ugh, why do I have to implement this part myself?)
954 if not names:
955 for submodule_name in module_name.split('.')[1:]:
956 module = getattr(module, submodule_name)
957 if hasattr(module, '__all__'):
958 names = getattr(module, '__all__')
959 else:
960 names = dir(module)
961
962 # Install each name into namespace, checking to make sure it
963 # doesn't override anything that already exists.
964 for name in names:
965 # Check for conflicts to help prevent future problems.
966 if name in namespace and protect:
967 if namespace[name] is not getattr(module, name):
968 raise error.AutoservError('importing name '
969 '%s from %s %r would override %r' %
970 (name, module_name, getattr(module, name),
971 namespace[name]))
972 else:
973 # Encourage cleanliness and the use of __all__ for a
974 # more concrete API with less surprises on '*' imports.
975 warnings.warn('%s (%r) being imported from %s for use '
976 'in server control files is not the '
977 'first occurrance of that import.' %
978 (name, namespace[name], module_name))
979
980 namespace[name] = getattr(module, name)
981
982
983 # This is the equivalent of prepending a bunch of import statements to
984 # the front of the control script.
mbligha2b07dd2009-06-22 18:26:13 +0000985 namespace.update(os=os, sys=sys, logging=logging)
mbligh084bc172008-10-18 14:02:45 +0000986 _import_names('autotest_lib.server',
987 ('hosts', 'autotest', 'kvm', 'git', 'standalone_profiler',
988 'source_kernel', 'rpm_kernel', 'deb_kernel', 'git_kernel'))
989 _import_names('autotest_lib.server.subcommand',
990 ('parallel', 'parallel_simple', 'subcommand'))
991 _import_names('autotest_lib.server.utils',
992 ('run', 'get_tmp_dir', 'sh_escape', 'parse_machine'))
993 _import_names('autotest_lib.client.common_lib.error')
994 _import_names('autotest_lib.client.common_lib.barrier', ('barrier',))
995
996 # Inject ourself as the job object into other classes within the API.
997 # (Yuck, this injection is a gross thing be part of a public API. -gps)
998 #
999 # XXX Base & SiteAutotest do not appear to use .job. Who does?
1000 namespace['autotest'].Autotest.job = self
1001 # server.hosts.base_classes.Host uses .job.
1002 namespace['hosts'].Host.job = self
Eric Li10222b82010-11-24 09:33:15 -08001003 namespace['hosts'].factory.ssh_user = self._ssh_user
1004 namespace['hosts'].factory.ssh_port = self._ssh_port
1005 namespace['hosts'].factory.ssh_pass = self._ssh_pass
mbligh084bc172008-10-18 14:02:45 +00001006
1007
1008 def _execute_code(self, code_file, namespace, protect=True):
mbligh2b92b862008-11-22 13:25:32 +00001009 """
1010 Execute code using a copy of namespace as a server control script.
mbligh084bc172008-10-18 14:02:45 +00001011
1012 Unless protect_namespace is explicitly set to False, the dict will not
1013 be modified.
1014
1015 Args:
1016 code_file: The filename of the control file to execute.
1017 namespace: A dict containing names to make available during execution.
1018 protect: Boolean. If True (the default) a copy of the namespace dict
1019 is used during execution to prevent the code from modifying its
1020 contents outside of this function. If False the raw dict is
1021 passed in and modifications will be allowed.
1022 """
1023 if protect:
1024 namespace = namespace.copy()
1025 self._fill_server_control_namespace(namespace, protect=protect)
1026 # TODO: Simplify and get rid of the special cases for only 1 machine.
showard3e66e8c2008-10-27 19:20:51 +00001027 if len(self.machines) > 1:
mbligh084bc172008-10-18 14:02:45 +00001028 machines_text = '\n'.join(self.machines) + '\n'
1029 # Only rewrite the file if it does not match our machine list.
1030 try:
1031 machines_f = open(MACHINES_FILENAME, 'r')
1032 existing_machines_text = machines_f.read()
1033 machines_f.close()
1034 except EnvironmentError:
1035 existing_machines_text = None
1036 if machines_text != existing_machines_text:
1037 utils.open_write_close(MACHINES_FILENAME, machines_text)
1038 execfile(code_file, namespace, namespace)
jadmanski10646442008-08-13 14:05:21 +00001039
1040
jadmanskie29d0e42010-06-17 16:06:52 +00001041 def _parse_status(self, new_line):
mbligh0d0f67d2009-11-06 03:15:03 +00001042 if not self._using_parser:
jadmanski10646442008-08-13 14:05:21 +00001043 return
jadmanskie29d0e42010-06-17 16:06:52 +00001044 new_tests = self.parser.process_lines([new_line])
jadmanski10646442008-08-13 14:05:21 +00001045 for test in new_tests:
1046 self.__insert_test(test)
1047
1048
1049 def __insert_test(self, test):
mbligh2b92b862008-11-22 13:25:32 +00001050 """
1051 An internal method to insert a new test result into the
jadmanski10646442008-08-13 14:05:21 +00001052 database. This method will not raise an exception, even if an
1053 error occurs during the insert, to avoid failing a test
1054 simply because of unexpected database issues."""
showard21baa452008-10-21 00:08:39 +00001055 self.num_tests_run += 1
1056 if status_lib.is_worse_than_or_equal_to(test.status, 'FAIL'):
1057 self.num_tests_failed += 1
jadmanski10646442008-08-13 14:05:21 +00001058 try:
1059 self.results_db.insert_test(self.job_model, test)
1060 except Exception:
1061 msg = ("WARNING: An unexpected error occured while "
1062 "inserting test results into the database. "
1063 "Ignoring error.\n" + traceback.format_exc())
1064 print >> sys.stderr, msg
1065
mblighcaa62c22008-04-07 21:51:17 +00001066
mblighfc3da5b2010-01-06 18:37:22 +00001067 def preprocess_client_state(self):
1068 """
1069 Produce a state file for initializing the state of a client job.
1070
1071 Creates a new client state file with all the current server state, as
1072 well as some pre-set client state.
1073
1074 @returns The path of the file the state was written into.
1075 """
1076 # initialize the sysinfo state
1077 self._state.set('client', 'sysinfo', self.sysinfo.serialize())
1078
1079 # dump the state out to a tempfile
1080 fd, file_path = tempfile.mkstemp(dir=self.tmpdir)
1081 os.close(fd)
mbligha2c99492010-01-27 22:59:50 +00001082
1083 # write_to_file doesn't need locking, we exclusively own file_path
mblighfc3da5b2010-01-06 18:37:22 +00001084 self._state.write_to_file(file_path)
1085 return file_path
1086
1087
1088 def postprocess_client_state(self, state_path):
1089 """
1090 Update the state of this job with the state from a client job.
1091
1092 Updates the state of the server side of a job with the final state
1093 of a client job that was run. Updates the non-client-specific state,
1094 pulls in some specific bits from the client-specific state, and then
1095 discards the rest. Removes the state file afterwards
1096
1097 @param state_file A path to the state file from the client.
1098 """
1099 # update the on-disk state
mblighfc3da5b2010-01-06 18:37:22 +00001100 try:
jadmanskib6e7bdb2010-04-13 16:00:39 +00001101 self._state.read_from_file(state_path)
mblighfc3da5b2010-01-06 18:37:22 +00001102 os.remove(state_path)
mbligha2c99492010-01-27 22:59:50 +00001103 except OSError, e:
mblighfc3da5b2010-01-06 18:37:22 +00001104 # ignore file-not-found errors
1105 if e.errno != errno.ENOENT:
1106 raise
jadmanskib6e7bdb2010-04-13 16:00:39 +00001107 else:
1108 logging.debug('Client state file %s not found', state_path)
mblighfc3da5b2010-01-06 18:37:22 +00001109
1110 # update the sysinfo state
1111 if self._state.has('client', 'sysinfo'):
1112 self.sysinfo.deserialize(self._state.get('client', 'sysinfo'))
1113
1114 # drop all the client-specific state
1115 self._state.discard_namespace('client')
1116
1117
mbligh0a883702010-04-21 01:58:34 +00001118 def clear_all_known_hosts(self):
1119 """Clears known hosts files for all AbstractSSHHosts."""
1120 for host in self.hosts:
1121 if isinstance(host, abstract_ssh.AbstractSSHHost):
1122 host.clear_known_hosts()
1123
1124
jadmanskif37df842009-02-11 00:03:26 +00001125class warning_manager(object):
1126 """Class for controlling warning logs. Manages the enabling and disabling
1127 of warnings."""
1128 def __init__(self):
1129 # a map of warning types to a list of disabled time intervals
1130 self.disabled_warnings = {}
1131
1132
1133 def is_valid(self, timestamp, warning_type):
1134 """Indicates if a warning (based on the time it occured and its type)
1135 is a valid warning. A warning is considered "invalid" if this type of
1136 warning was marked as "disabled" at the time the warning occured."""
1137 disabled_intervals = self.disabled_warnings.get(warning_type, [])
1138 for start, end in disabled_intervals:
1139 if timestamp >= start and (end is None or timestamp < end):
1140 return False
1141 return True
1142
1143
1144 def disable_warnings(self, warning_type, current_time_func=time.time):
1145 """As of now, disables all further warnings of this type."""
1146 intervals = self.disabled_warnings.setdefault(warning_type, [])
1147 if not intervals or intervals[-1][1] is not None:
jadmanski16a7ff72009-04-01 18:19:53 +00001148 intervals.append((int(current_time_func()), None))
jadmanskif37df842009-02-11 00:03:26 +00001149
1150
1151 def enable_warnings(self, warning_type, current_time_func=time.time):
1152 """As of now, enables all further warnings of this type."""
1153 intervals = self.disabled_warnings.get(warning_type, [])
1154 if intervals and intervals[-1][1] is None:
jadmanski16a7ff72009-04-01 18:19:53 +00001155 intervals[-1] = (intervals[-1][0], int(current_time_func()))
Paul Pendlebury57593562011-06-15 10:45:49 -07001156
1157
1158# load up site-specific code for generating site-specific job data
1159get_site_job_data = utils.import_site_function(__file__,
1160 "autotest_lib.server.site_server_job", "get_site_job_data",
1161 _get_site_job_data_dummy)
1162
1163
1164site_server_job = utils.import_site_class(
1165 __file__, "autotest_lib.server.site_server_job", "site_server_job",
1166 base_server_job)
1167
1168
1169class server_job(site_server_job):
Dale Curtis456d3c12011-07-19 11:42:51 -07001170 pass