blob: 7c33a119dca3e9607387f93d716cfaf586e36254 [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
Eric Li861b2d52011-02-04 14:50:35 -080012import getpass, os, sys, re, stat, tempfile, time, select, subprocess, 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
mbligh09108442008-10-15 16:27:38 +000016from autotest_lib.client.common_lib import error, log, 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='',
mbligh374f3412009-05-13 21:29:45 +0000142 ssh_user='root', ssh_port=22, ssh_pass='',
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.
159 @param group_name: If supplied, this will be written out as
mbligh374f3412009-05-13 21:29:45 +0000160 host_group_name in the keyvals file for the parser.
mblighe7d9c602009-07-02 19:02:33 +0000161 @param tag: The job execution tag from the scheduler. [optional]
mblighe0cbc912010-03-11 18:03:07 +0000162 @param control_filename: The filename where the server control file
163 should be written in the results directory.
jadmanski10646442008-08-13 14:05:21 +0000164 """
mbligh0d0f67d2009-11-06 03:15:03 +0000165 super(base_server_job, self).__init__(resultdir=resultdir)
mbligha788dc42009-03-26 21:10:16 +0000166
mbligh0d0f67d2009-11-06 03:15:03 +0000167 path = os.path.dirname(__file__)
168 self.control = control
169 self._uncollected_log_file = os.path.join(self.resultdir,
170 'uncollected_logs')
171 debugdir = os.path.join(self.resultdir, 'debug')
172 if not os.path.exists(debugdir):
173 os.mkdir(debugdir)
174
175 if user:
176 self.user = user
177 else:
178 self.user = getpass.getuser()
179
jadmanski808f4b12010-04-09 22:30:31 +0000180 self.args = args
jadmanski10646442008-08-13 14:05:21 +0000181 self.machines = machines
mbligh0d0f67d2009-11-06 03:15:03 +0000182 self._client = client
jadmanski10646442008-08-13 14:05:21 +0000183 self.warning_loggers = set()
jadmanskif37df842009-02-11 00:03:26 +0000184 self.warning_manager = warning_manager()
mbligh0d0f67d2009-11-06 03:15:03 +0000185 self._ssh_user = ssh_user
186 self._ssh_port = ssh_port
187 self._ssh_pass = ssh_pass
mblighe7d9c602009-07-02 19:02:33 +0000188 self.tag = tag
mbligh09108442008-10-15 16:27:38 +0000189 self.last_boot_tag = None
jadmanski53aaf382008-11-17 16:22:31 +0000190 self.hosts = set()
mbligh0d0f67d2009-11-06 03:15:03 +0000191 self.drop_caches = False
mblighb5dac432008-11-27 00:38:44 +0000192 self.drop_caches_between_iterations = False
mblighe0cbc912010-03-11 18:03:07 +0000193 self._control_filename = control_filename
jadmanski10646442008-08-13 14:05:21 +0000194
showard75cdfee2009-06-10 17:40:41 +0000195 self.logging = logging_manager.get_logging_manager(
196 manage_stdout_and_stderr=True, redirect_fds=True)
197 subcommand.logging_manager_object = self.logging
jadmanski10646442008-08-13 14:05:21 +0000198
mbligh0d0f67d2009-11-06 03:15:03 +0000199 self.sysinfo = sysinfo.sysinfo(self.resultdir)
jadmanski043e1132008-11-19 17:10:32 +0000200 self.profilers = profilers.profilers(self)
jadmanskic09fc152008-10-15 17:56:59 +0000201
jadmanski10646442008-08-13 14:05:21 +0000202 job_data = {'label' : label, 'user' : user,
203 'hostname' : ','.join(machines),
Eric Li861b2d52011-02-04 14:50:35 -0800204 'drone' : platform.node(),
mbligh0d0f67d2009-11-06 03:15:03 +0000205 'status_version' : str(self._STATUS_VERSION),
showard170873e2009-01-07 00:22:26 +0000206 'job_started' : str(int(time.time()))}
mbligh374f3412009-05-13 21:29:45 +0000207 if group_name:
208 job_data['host_group_name'] = group_name
jadmanski10646442008-08-13 14:05:21 +0000209
mbligh0d0f67d2009-11-06 03:15:03 +0000210 # only write these keyvals out on the first job in a resultdir
211 if 'job_started' not in utils.read_keyval(self.resultdir):
212 job_data.update(get_site_job_data(self))
213 utils.write_keyval(self.resultdir, job_data)
214
215 self._parse_job = parse_job
showardcc929362010-01-25 21:20:41 +0000216 self._using_parser = (self._parse_job and len(machines) <= 1)
mbligh0d0f67d2009-11-06 03:15:03 +0000217 self.pkgmgr = packages.PackageManager(
218 self.autodir, run_function_dargs={'timeout':600})
showard21baa452008-10-21 00:08:39 +0000219 self.num_tests_run = 0
220 self.num_tests_failed = 0
221
jadmanski550fdc22008-11-20 16:32:08 +0000222 self._register_subcommand_hooks()
223
mbligh0d0f67d2009-11-06 03:15:03 +0000224 # these components aren't usable on the server
225 self.bootloader = None
226 self.harness = None
227
jadmanski2a89dac2010-06-11 14:32:58 +0000228 # set up the status logger
jadmanski52053632010-06-11 21:08:10 +0000229 self._indenter = status_indenter()
jadmanski2a89dac2010-06-11 14:32:58 +0000230 self._logger = base_job.status_logger(
jadmanski52053632010-06-11 21:08:10 +0000231 self, self._indenter, 'status.log', 'status.log',
jadmanski2a89dac2010-06-11 14:32:58 +0000232 record_hook=server_job_record_hook(self))
233
mbligh0d0f67d2009-11-06 03:15:03 +0000234
235 @classmethod
236 def _find_base_directories(cls):
237 """
238 Determine locations of autodir, clientdir and serverdir. Assumes
239 that this file is located within serverdir and uses __file__ along
240 with relative paths to resolve the location.
241 """
242 serverdir = os.path.abspath(os.path.dirname(__file__))
243 autodir = os.path.normpath(os.path.join(serverdir, '..'))
244 clientdir = os.path.join(autodir, 'client')
245 return autodir, clientdir, serverdir
246
247
248 def _find_resultdir(self, resultdir):
249 """
250 Determine the location of resultdir. For server jobs we expect one to
251 always be explicitly passed in to __init__, so just return that.
252 """
253 if resultdir:
254 return os.path.normpath(resultdir)
255 else:
256 return None
257
jadmanski550fdc22008-11-20 16:32:08 +0000258
jadmanski2a89dac2010-06-11 14:32:58 +0000259 def _get_status_logger(self):
260 """Return a reference to the status logger."""
261 return self._logger
262
263
jadmanskie432dd22009-01-30 15:04:51 +0000264 @staticmethod
265 def _load_control_file(path):
266 f = open(path)
267 try:
268 control_file = f.read()
269 finally:
270 f.close()
271 return re.sub('\r', '', control_file)
272
273
jadmanski550fdc22008-11-20 16:32:08 +0000274 def _register_subcommand_hooks(self):
mbligh2b92b862008-11-22 13:25:32 +0000275 """
276 Register some hooks into the subcommand modules that allow us
277 to properly clean up self.hosts created in forked subprocesses.
278 """
jadmanski550fdc22008-11-20 16:32:08 +0000279 def on_fork(cmd):
280 self._existing_hosts_on_fork = set(self.hosts)
281 def on_join(cmd):
282 new_hosts = self.hosts - self._existing_hosts_on_fork
283 for host in new_hosts:
284 host.close()
285 subcommand.subcommand.register_fork_hook(on_fork)
286 subcommand.subcommand.register_join_hook(on_join)
287
jadmanski10646442008-08-13 14:05:21 +0000288
mbligh4608b002010-01-05 18:22:35 +0000289 def init_parser(self):
mbligh2b92b862008-11-22 13:25:32 +0000290 """
mbligh4608b002010-01-05 18:22:35 +0000291 Start the continuous parsing of self.resultdir. This sets up
jadmanski10646442008-08-13 14:05:21 +0000292 the database connection and inserts the basic job object into
mbligh2b92b862008-11-22 13:25:32 +0000293 the database if necessary.
294 """
mbligh4608b002010-01-05 18:22:35 +0000295 if not self._using_parser:
296 return
jadmanski10646442008-08-13 14:05:21 +0000297 # redirect parser debugging to .parse.log
mbligh4608b002010-01-05 18:22:35 +0000298 parse_log = os.path.join(self.resultdir, '.parse.log')
jadmanski10646442008-08-13 14:05:21 +0000299 parse_log = open(parse_log, 'w', 0)
300 tko_utils.redirect_parser_debugging(parse_log)
301 # create a job model object and set up the db
302 self.results_db = tko_db.db(autocommit=True)
mbligh0d0f67d2009-11-06 03:15:03 +0000303 self.parser = status_lib.parser(self._STATUS_VERSION)
mbligh4608b002010-01-05 18:22:35 +0000304 self.job_model = self.parser.make_job(self.resultdir)
jadmanski10646442008-08-13 14:05:21 +0000305 self.parser.start(self.job_model)
306 # check if a job already exists in the db and insert it if
307 # it does not
mbligh0d0f67d2009-11-06 03:15:03 +0000308 job_idx = self.results_db.find_job(self._parse_job)
jadmanski10646442008-08-13 14:05:21 +0000309 if job_idx is None:
mbligh0d0f67d2009-11-06 03:15:03 +0000310 self.results_db.insert_job(self._parse_job, self.job_model)
jadmanski10646442008-08-13 14:05:21 +0000311 else:
mbligh2b92b862008-11-22 13:25:32 +0000312 machine_idx = self.results_db.lookup_machine(self.job_model.machine)
jadmanski10646442008-08-13 14:05:21 +0000313 self.job_model.index = job_idx
314 self.job_model.machine_idx = machine_idx
315
316
317 def cleanup_parser(self):
mbligh2b92b862008-11-22 13:25:32 +0000318 """
319 This should be called after the server job is finished
jadmanski10646442008-08-13 14:05:21 +0000320 to carry out any remaining cleanup (e.g. flushing any
mbligh2b92b862008-11-22 13:25:32 +0000321 remaining test results to the results db)
322 """
mbligh0d0f67d2009-11-06 03:15:03 +0000323 if not self._using_parser:
jadmanski10646442008-08-13 14:05:21 +0000324 return
325 final_tests = self.parser.end()
326 for test in final_tests:
327 self.__insert_test(test)
mbligh0d0f67d2009-11-06 03:15:03 +0000328 self._using_parser = False
jadmanski10646442008-08-13 14:05:21 +0000329
330
331 def verify(self):
332 if not self.machines:
mbligh084bc172008-10-18 14:02:45 +0000333 raise error.AutoservError('No machines specified to verify')
mbligh0fce4112008-11-27 00:37:17 +0000334 if self.resultdir:
335 os.chdir(self.resultdir)
jadmanski10646442008-08-13 14:05:21 +0000336 try:
jadmanskicdd0c402008-09-19 21:21:31 +0000337 namespace = {'machines' : self.machines, 'job' : self,
mbligh0d0f67d2009-11-06 03:15:03 +0000338 'ssh_user' : self._ssh_user,
339 'ssh_port' : self._ssh_port,
340 'ssh_pass' : self._ssh_pass}
mbligh084bc172008-10-18 14:02:45 +0000341 self._execute_code(VERIFY_CONTROL_FILE, namespace, protect=False)
jadmanski10646442008-08-13 14:05:21 +0000342 except Exception, e:
mbligh2b92b862008-11-22 13:25:32 +0000343 msg = ('Verify failed\n' + str(e) + '\n' + traceback.format_exc())
jadmanski10646442008-08-13 14:05:21 +0000344 self.record('ABORT', None, None, msg)
345 raise
346
347
348 def repair(self, host_protection):
349 if not self.machines:
350 raise error.AutoservError('No machines specified to repair')
mbligh0fce4112008-11-27 00:37:17 +0000351 if self.resultdir:
352 os.chdir(self.resultdir)
jadmanski10646442008-08-13 14:05:21 +0000353 namespace = {'machines': self.machines, 'job': self,
mbligh0d0f67d2009-11-06 03:15:03 +0000354 'ssh_user': self._ssh_user, 'ssh_port': self._ssh_port,
355 'ssh_pass': self._ssh_pass,
jadmanski10646442008-08-13 14:05:21 +0000356 'protection_level': host_protection}
mbligh25c0b8c2009-01-24 01:44:17 +0000357
mbligh0931b0a2009-04-08 17:44:48 +0000358 self._execute_code(REPAIR_CONTROL_FILE, namespace, protect=False)
jadmanski10646442008-08-13 14:05:21 +0000359
360
361 def precheck(self):
362 """
363 perform any additional checks in derived classes.
364 """
365 pass
366
367
368 def enable_external_logging(self):
mbligh2b92b862008-11-22 13:25:32 +0000369 """
370 Start or restart external logging mechanism.
jadmanski10646442008-08-13 14:05:21 +0000371 """
372 pass
373
374
375 def disable_external_logging(self):
mbligh2b92b862008-11-22 13:25:32 +0000376 """
377 Pause or stop external logging mechanism.
jadmanski10646442008-08-13 14:05:21 +0000378 """
379 pass
380
381
382 def use_external_logging(self):
mbligh2b92b862008-11-22 13:25:32 +0000383 """
384 Return True if external logging should be used.
jadmanski10646442008-08-13 14:05:21 +0000385 """
386 return False
387
388
mbligh415dc212009-06-15 21:53:34 +0000389 def _make_parallel_wrapper(self, function, machines, log):
390 """Wrap function as appropriate for calling by parallel_simple."""
mbligh2b92b862008-11-22 13:25:32 +0000391 is_forking = not (len(machines) == 1 and self.machines == machines)
mbligh0d0f67d2009-11-06 03:15:03 +0000392 if self._parse_job and is_forking and log:
jadmanski10646442008-08-13 14:05:21 +0000393 def wrapper(machine):
mbligh0d0f67d2009-11-06 03:15:03 +0000394 self._parse_job += "/" + machine
395 self._using_parser = True
jadmanski10646442008-08-13 14:05:21 +0000396 self.machines = [machine]
mbligh0d0f67d2009-11-06 03:15:03 +0000397 self.push_execution_context(machine)
jadmanski609a5f42008-08-26 20:52:42 +0000398 os.chdir(self.resultdir)
showard2bab8f42008-11-12 18:15:22 +0000399 utils.write_keyval(self.resultdir, {"hostname": machine})
mbligh4608b002010-01-05 18:22:35 +0000400 self.init_parser()
jadmanski10646442008-08-13 14:05:21 +0000401 result = function(machine)
402 self.cleanup_parser()
403 return result
jadmanski4dd1a002008-09-05 20:27:30 +0000404 elif len(machines) > 1 and log:
jadmanski10646442008-08-13 14:05:21 +0000405 def wrapper(machine):
mbligh0d0f67d2009-11-06 03:15:03 +0000406 self.push_execution_context(machine)
jadmanski609a5f42008-08-26 20:52:42 +0000407 os.chdir(self.resultdir)
mbligh838d82d2009-03-11 17:14:31 +0000408 machine_data = {'hostname' : machine,
mbligh0d0f67d2009-11-06 03:15:03 +0000409 'status_version' : str(self._STATUS_VERSION)}
mbligh838d82d2009-03-11 17:14:31 +0000410 utils.write_keyval(self.resultdir, machine_data)
jadmanski10646442008-08-13 14:05:21 +0000411 result = function(machine)
412 return result
413 else:
414 wrapper = function
mbligh415dc212009-06-15 21:53:34 +0000415 return wrapper
416
417
418 def parallel_simple(self, function, machines, log=True, timeout=None,
419 return_results=False):
420 """
421 Run 'function' using parallel_simple, with an extra wrapper to handle
422 the necessary setup for continuous parsing, if possible. If continuous
423 parsing is already properly initialized then this should just work.
424
425 @param function: A callable to run in parallel given each machine.
426 @param machines: A list of machine names to be passed one per subcommand
427 invocation of function.
428 @param log: If True, output will be written to output in a subdirectory
429 named after each machine.
430 @param timeout: Seconds after which the function call should timeout.
431 @param return_results: If True instead of an AutoServError being raised
432 on any error a list of the results|exceptions from the function
433 called on each arg is returned. [default: False]
434
435 @raises error.AutotestError: If any of the functions failed.
436 """
437 wrapper = self._make_parallel_wrapper(function, machines, log)
438 return subcommand.parallel_simple(wrapper, machines,
439 log=log, timeout=timeout,
440 return_results=return_results)
441
442
443 def parallel_on_machines(self, function, machines, timeout=None):
444 """
showardcd5fac42009-07-06 20:19:43 +0000445 @param function: Called in parallel with one machine as its argument.
mbligh415dc212009-06-15 21:53:34 +0000446 @param machines: A list of machines to call function(machine) on.
447 @param timeout: Seconds after which the function call should timeout.
448
449 @returns A list of machines on which function(machine) returned
450 without raising an exception.
451 """
showardcd5fac42009-07-06 20:19:43 +0000452 results = self.parallel_simple(function, machines, timeout=timeout,
mbligh415dc212009-06-15 21:53:34 +0000453 return_results=True)
454 success_machines = []
455 for result, machine in itertools.izip(results, machines):
456 if not isinstance(result, Exception):
457 success_machines.append(machine)
458 return success_machines
jadmanski10646442008-08-13 14:05:21 +0000459
460
mbligh0d0f67d2009-11-06 03:15:03 +0000461 _USE_TEMP_DIR = object()
mbligh2b92b862008-11-22 13:25:32 +0000462 def run(self, cleanup=False, install_before=False, install_after=False,
jadmanskie432dd22009-01-30 15:04:51 +0000463 collect_crashdumps=True, namespace={}, control=None,
jadmanskidef0c3c2009-03-25 20:07:10 +0000464 control_file_dir=None, only_collect_crashinfo=False):
jadmanskifb9c0fa2009-04-29 17:39:16 +0000465 # for a normal job, make sure the uncollected logs file exists
466 # for a crashinfo-only run it should already exist, bail out otherwise
jadmanski648c39f2010-03-19 17:38:01 +0000467 created_uncollected_logs = False
mbligh0d0f67d2009-11-06 03:15:03 +0000468 if self.resultdir and not os.path.exists(self._uncollected_log_file):
jadmanskifb9c0fa2009-04-29 17:39:16 +0000469 if only_collect_crashinfo:
470 # if this is a crashinfo-only run, and there were no existing
471 # uncollected logs, just bail out early
472 logging.info("No existing uncollected logs, "
473 "skipping crashinfo collection")
474 return
475 else:
mbligh0d0f67d2009-11-06 03:15:03 +0000476 log_file = open(self._uncollected_log_file, "w")
jadmanskifb9c0fa2009-04-29 17:39:16 +0000477 pickle.dump([], log_file)
478 log_file.close()
jadmanski648c39f2010-03-19 17:38:01 +0000479 created_uncollected_logs = True
jadmanskifb9c0fa2009-04-29 17:39:16 +0000480
jadmanski10646442008-08-13 14:05:21 +0000481 # use a copy so changes don't affect the original dictionary
482 namespace = namespace.copy()
483 machines = self.machines
jadmanskie432dd22009-01-30 15:04:51 +0000484 if control is None:
jadmanski02a3ba22009-11-13 20:47:27 +0000485 if self.control is None:
486 control = ''
487 else:
488 control = self._load_control_file(self.control)
jadmanskie432dd22009-01-30 15:04:51 +0000489 if control_file_dir is None:
490 control_file_dir = self.resultdir
jadmanski10646442008-08-13 14:05:21 +0000491
492 self.aborted = False
493 namespace['machines'] = machines
jadmanski808f4b12010-04-09 22:30:31 +0000494 namespace['args'] = self.args
jadmanski10646442008-08-13 14:05:21 +0000495 namespace['job'] = self
mbligh0d0f67d2009-11-06 03:15:03 +0000496 namespace['ssh_user'] = self._ssh_user
497 namespace['ssh_port'] = self._ssh_port
498 namespace['ssh_pass'] = self._ssh_pass
jadmanski10646442008-08-13 14:05:21 +0000499 test_start_time = int(time.time())
500
mbligh80e1eba2008-11-19 00:26:18 +0000501 if self.resultdir:
502 os.chdir(self.resultdir)
jadmanski779bd292009-03-19 17:33:33 +0000503 # touch status.log so that the parser knows a job is running here
jadmanski382303a2009-04-21 19:53:39 +0000504 open(self.get_status_log_path(), 'a').close()
mbligh80e1eba2008-11-19 00:26:18 +0000505 self.enable_external_logging()
jadmanskie432dd22009-01-30 15:04:51 +0000506
jadmanskicdd0c402008-09-19 21:21:31 +0000507 collect_crashinfo = True
mblighaebe3b62008-12-22 14:45:40 +0000508 temp_control_file_dir = None
jadmanski10646442008-08-13 14:05:21 +0000509 try:
showardcf8d4922009-10-14 16:08:39 +0000510 try:
511 if install_before and machines:
512 self._execute_code(INSTALL_CONTROL_FILE, namespace)
jadmanskie432dd22009-01-30 15:04:51 +0000513
showardcf8d4922009-10-14 16:08:39 +0000514 if only_collect_crashinfo:
515 return
516
jadmanskidef0c3c2009-03-25 20:07:10 +0000517 # determine the dir to write the control files to
518 cfd_specified = (control_file_dir
mbligh0d0f67d2009-11-06 03:15:03 +0000519 and control_file_dir is not self._USE_TEMP_DIR)
jadmanskidef0c3c2009-03-25 20:07:10 +0000520 if cfd_specified:
521 temp_control_file_dir = None
522 else:
523 temp_control_file_dir = tempfile.mkdtemp(
524 suffix='temp_control_file_dir')
525 control_file_dir = temp_control_file_dir
526 server_control_file = os.path.join(control_file_dir,
mblighe0cbc912010-03-11 18:03:07 +0000527 self._control_filename)
jadmanskidef0c3c2009-03-25 20:07:10 +0000528 client_control_file = os.path.join(control_file_dir,
529 CLIENT_CONTROL_FILENAME)
mbligh0d0f67d2009-11-06 03:15:03 +0000530 if self._client:
jadmanskidef0c3c2009-03-25 20:07:10 +0000531 namespace['control'] = control
532 utils.open_write_close(client_control_file, control)
mblighfeac0102009-04-28 18:31:12 +0000533 shutil.copyfile(CLIENT_WRAPPER_CONTROL_FILE,
534 server_control_file)
jadmanskidef0c3c2009-03-25 20:07:10 +0000535 else:
536 utils.open_write_close(server_control_file, control)
mbligh26f0d882009-06-22 18:30:01 +0000537 logging.info("Processing control file")
jadmanskidef0c3c2009-03-25 20:07:10 +0000538 self._execute_code(server_control_file, namespace)
mbligh26f0d882009-06-22 18:30:01 +0000539 logging.info("Finished processing control file")
jadmanski10646442008-08-13 14:05:21 +0000540
jadmanskidef0c3c2009-03-25 20:07:10 +0000541 # no error occured, so we don't need to collect crashinfo
542 collect_crashinfo = False
Eric Li6f27d4f2010-09-29 10:55:17 -0700543 except Exception, e:
showardcf8d4922009-10-14 16:08:39 +0000544 try:
545 logging.exception(
546 'Exception escaped control file, job aborting:')
Eric Li6f27d4f2010-09-29 10:55:17 -0700547 self.record('INFO', None, None, str(e),
548 {'job_abort_reason': str(e)})
showardcf8d4922009-10-14 16:08:39 +0000549 except:
550 pass # don't let logging exceptions here interfere
551 raise
jadmanski10646442008-08-13 14:05:21 +0000552 finally:
mblighaebe3b62008-12-22 14:45:40 +0000553 if temp_control_file_dir:
jadmanskie432dd22009-01-30 15:04:51 +0000554 # Clean up temp directory used for copies of the control files
mblighaebe3b62008-12-22 14:45:40 +0000555 try:
556 shutil.rmtree(temp_control_file_dir)
557 except Exception, e:
mblighe7d9c602009-07-02 19:02:33 +0000558 logging.warn('Could not remove temp directory %s: %s',
559 temp_control_file_dir, e)
jadmanskie432dd22009-01-30 15:04:51 +0000560
jadmanskicdd0c402008-09-19 21:21:31 +0000561 if machines and (collect_crashdumps or collect_crashinfo):
jadmanski10646442008-08-13 14:05:21 +0000562 namespace['test_start_time'] = test_start_time
jadmanskicdd0c402008-09-19 21:21:31 +0000563 if collect_crashinfo:
mbligh084bc172008-10-18 14:02:45 +0000564 # includes crashdumps
565 self._execute_code(CRASHINFO_CONTROL_FILE, namespace)
jadmanskicdd0c402008-09-19 21:21:31 +0000566 else:
mbligh084bc172008-10-18 14:02:45 +0000567 self._execute_code(CRASHDUMPS_CONTROL_FILE, namespace)
jadmanski648c39f2010-03-19 17:38:01 +0000568 if self._uncollected_log_file and created_uncollected_logs:
mbligh0d0f67d2009-11-06 03:15:03 +0000569 os.remove(self._uncollected_log_file)
jadmanski10646442008-08-13 14:05:21 +0000570 self.disable_external_logging()
showard45ae8192008-11-05 19:32:53 +0000571 if cleanup and machines:
572 self._execute_code(CLEANUP_CONTROL_FILE, namespace)
jadmanski10646442008-08-13 14:05:21 +0000573 if install_after and machines:
mbligh084bc172008-10-18 14:02:45 +0000574 self._execute_code(INSTALL_CONTROL_FILE, namespace)
jadmanski10646442008-08-13 14:05:21 +0000575
576
577 def run_test(self, url, *args, **dargs):
mbligh2b92b862008-11-22 13:25:32 +0000578 """
579 Summon a test object and run it.
jadmanski10646442008-08-13 14:05:21 +0000580
581 tag
582 tag to add to testname
583 url
584 url of the test to run
585 """
mblighfc3da5b2010-01-06 18:37:22 +0000586 group, testname = self.pkgmgr.get_package_name(url, 'test')
587 testname, subdir, tag = self._build_tagged_test_name(testname, dargs)
588 outputdir = self._make_test_outputdir(subdir)
jadmanski10646442008-08-13 14:05:21 +0000589
590 def group_func():
591 try:
592 test.runtest(self, url, tag, args, dargs)
593 except error.TestBaseException, e:
594 self.record(e.exit_status, subdir, testname, str(e))
595 raise
596 except Exception, e:
597 info = str(e) + "\n" + traceback.format_exc()
598 self.record('FAIL', subdir, testname, info)
599 raise
600 else:
mbligh2b92b862008-11-22 13:25:32 +0000601 self.record('GOOD', subdir, testname, 'completed successfully')
jadmanskide292df2008-08-26 20:51:14 +0000602
603 result, exc_info = self._run_group(testname, subdir, group_func)
604 if exc_info and isinstance(exc_info[1], error.TestBaseException):
605 return False
606 elif exc_info:
607 raise exc_info[0], exc_info[1], exc_info[2]
608 else:
609 return True
jadmanski10646442008-08-13 14:05:21 +0000610
611
612 def _run_group(self, name, subdir, function, *args, **dargs):
613 """\
614 Underlying method for running something inside of a group.
615 """
jadmanskide292df2008-08-26 20:51:14 +0000616 result, exc_info = None, None
jadmanski10646442008-08-13 14:05:21 +0000617 try:
618 self.record('START', subdir, name)
jadmanski52053632010-06-11 21:08:10 +0000619 result = function(*args, **dargs)
jadmanski10646442008-08-13 14:05:21 +0000620 except error.TestBaseException, e:
jadmanskib88d6dc2009-01-10 00:33:18 +0000621 self.record("END %s" % e.exit_status, subdir, name)
jadmanskide292df2008-08-26 20:51:14 +0000622 exc_info = sys.exc_info()
jadmanski10646442008-08-13 14:05:21 +0000623 except Exception, e:
624 err_msg = str(e) + '\n'
625 err_msg += traceback.format_exc()
626 self.record('END ABORT', subdir, name, err_msg)
627 raise error.JobError(name + ' failed\n' + traceback.format_exc())
628 else:
629 self.record('END GOOD', subdir, name)
630
jadmanskide292df2008-08-26 20:51:14 +0000631 return result, exc_info
jadmanski10646442008-08-13 14:05:21 +0000632
633
634 def run_group(self, function, *args, **dargs):
635 """\
636 function:
637 subroutine to run
638 *args:
639 arguments for the function
640 """
641
642 name = function.__name__
643
644 # Allow the tag for the group to be specified.
645 tag = dargs.pop('tag', None)
646 if tag:
647 name = tag
648
jadmanskide292df2008-08-26 20:51:14 +0000649 return self._run_group(name, None, function, *args, **dargs)[0]
jadmanski10646442008-08-13 14:05:21 +0000650
651
652 def run_reboot(self, reboot_func, get_kernel_func):
653 """\
654 A specialization of run_group meant specifically for handling
655 a reboot. Includes support for capturing the kernel version
656 after the reboot.
657
658 reboot_func: a function that carries out the reboot
659
660 get_kernel_func: a function that returns a string
661 representing the kernel version.
662 """
jadmanski10646442008-08-13 14:05:21 +0000663 try:
664 self.record('START', None, 'reboot')
jadmanski10646442008-08-13 14:05:21 +0000665 reboot_func()
666 except Exception, e:
jadmanski10646442008-08-13 14:05:21 +0000667 err_msg = str(e) + '\n' + traceback.format_exc()
668 self.record('END FAIL', None, 'reboot', err_msg)
jadmanski4b51d542009-04-08 14:17:16 +0000669 raise
jadmanski10646442008-08-13 14:05:21 +0000670 else:
671 kernel = get_kernel_func()
jadmanski10646442008-08-13 14:05:21 +0000672 self.record('END GOOD', None, 'reboot',
Dale Curtis74a314b2011-06-23 14:55:46 -0700673 optional_fields={"kernel": kernel})
jadmanski10646442008-08-13 14:05:21 +0000674
675
jadmanskie432dd22009-01-30 15:04:51 +0000676 def run_control(self, path):
677 """Execute a control file found at path (relative to the autotest
678 path). Intended for executing a control file within a control file,
679 not for running the top-level job control file."""
680 path = os.path.join(self.autodir, path)
681 control_file = self._load_control_file(path)
mbligh0d0f67d2009-11-06 03:15:03 +0000682 self.run(control=control_file, control_file_dir=self._USE_TEMP_DIR)
jadmanskie432dd22009-01-30 15:04:51 +0000683
684
jadmanskic09fc152008-10-15 17:56:59 +0000685 def add_sysinfo_command(self, command, logfile=None, on_every_test=False):
mbligh4395bbd2009-03-25 19:34:17 +0000686 self._add_sysinfo_loggable(sysinfo.command(command, logf=logfile),
jadmanskic09fc152008-10-15 17:56:59 +0000687 on_every_test)
688
689
690 def add_sysinfo_logfile(self, file, on_every_test=False):
691 self._add_sysinfo_loggable(sysinfo.logfile(file), on_every_test)
692
693
694 def _add_sysinfo_loggable(self, loggable, on_every_test):
695 if on_every_test:
696 self.sysinfo.test_loggables.add(loggable)
697 else:
698 self.sysinfo.boot_loggables.add(loggable)
699
700
jadmanski10646442008-08-13 14:05:21 +0000701 def _read_warnings(self):
jadmanskif37df842009-02-11 00:03:26 +0000702 """Poll all the warning loggers and extract any new warnings that have
703 been logged. If the warnings belong to a category that is currently
704 disabled, this method will discard them and they will no longer be
705 retrievable.
706
707 Returns a list of (timestamp, message) tuples, where timestamp is an
708 integer epoch timestamp."""
jadmanski10646442008-08-13 14:05:21 +0000709 warnings = []
710 while True:
711 # pull in a line of output from every logger that has
712 # output ready to be read
mbligh2b92b862008-11-22 13:25:32 +0000713 loggers, _, _ = select.select(self.warning_loggers, [], [], 0)
jadmanski10646442008-08-13 14:05:21 +0000714 closed_loggers = set()
715 for logger in loggers:
716 line = logger.readline()
717 # record any broken pipes (aka line == empty)
718 if len(line) == 0:
719 closed_loggers.add(logger)
720 continue
jadmanskif37df842009-02-11 00:03:26 +0000721 # parse out the warning
722 timestamp, msgtype, msg = line.split('\t', 2)
723 timestamp = int(timestamp)
724 # if the warning is valid, add it to the results
725 if self.warning_manager.is_valid(timestamp, msgtype):
726 warnings.append((timestamp, msg.strip()))
jadmanski10646442008-08-13 14:05:21 +0000727
728 # stop listening to loggers that are closed
729 self.warning_loggers -= closed_loggers
730
731 # stop if none of the loggers have any output left
732 if not loggers:
733 break
734
735 # sort into timestamp order
736 warnings.sort()
737 return warnings
738
739
showardcc929362010-01-25 21:20:41 +0000740 def _unique_subdirectory(self, base_subdirectory_name):
741 """Compute a unique results subdirectory based on the given name.
742
743 Appends base_subdirectory_name with a number as necessary to find a
744 directory name that doesn't already exist.
745 """
746 subdirectory = base_subdirectory_name
747 counter = 1
748 while os.path.exists(os.path.join(self.resultdir, subdirectory)):
749 subdirectory = base_subdirectory_name + '.' + str(counter)
750 counter += 1
751 return subdirectory
752
753
jadmanski52053632010-06-11 21:08:10 +0000754 def get_record_context(self):
755 """Returns an object representing the current job.record context.
756
757 The object returned is an opaque object with a 0-arg restore method
758 which can be called to restore the job.record context (i.e. indentation)
759 to the current level. The intention is that it should be used when
760 something external which generate job.record calls (e.g. an autotest
761 client) can fail catastrophically and the server job record state
762 needs to be reset to its original "known good" state.
763
764 @return: A context object with a 0-arg restore() method."""
765 return self._indenter.get_context()
766
767
showardcc929362010-01-25 21:20:41 +0000768 def record_summary(self, status_code, test_name, reason='', attributes=None,
769 distinguishing_attributes=(), child_test_ids=None):
770 """Record a summary test result.
771
772 @param status_code: status code string, see
773 common_lib.log.is_valid_status()
774 @param test_name: name of the test
775 @param reason: (optional) string providing detailed reason for test
776 outcome
777 @param attributes: (optional) dict of string keyvals to associate with
778 this result
779 @param distinguishing_attributes: (optional) list of attribute names
780 that should be used to distinguish identically-named test
781 results. These attributes should be present in the attributes
782 parameter. This is used to generate user-friendly subdirectory
783 names.
784 @param child_test_ids: (optional) list of test indices for test results
785 used in generating this result.
786 """
787 subdirectory_name_parts = [test_name]
788 for attribute in distinguishing_attributes:
789 assert attributes
790 assert attribute in attributes, '%s not in %s' % (attribute,
791 attributes)
792 subdirectory_name_parts.append(attributes[attribute])
793 base_subdirectory_name = '.'.join(subdirectory_name_parts)
794
795 subdirectory = self._unique_subdirectory(base_subdirectory_name)
796 subdirectory_path = os.path.join(self.resultdir, subdirectory)
797 os.mkdir(subdirectory_path)
798
799 self.record(status_code, subdirectory, test_name,
800 status=reason, optional_fields={'is_summary': True})
801
802 if attributes:
803 utils.write_keyval(subdirectory_path, attributes)
804
805 if child_test_ids:
806 ids_string = ','.join(str(test_id) for test_id in child_test_ids)
807 summary_data = {'child_test_ids': ids_string}
808 utils.write_keyval(os.path.join(subdirectory_path, 'summary_data'),
809 summary_data)
810
811
jadmanski16a7ff72009-04-01 18:19:53 +0000812 def disable_warnings(self, warning_type):
jadmanskif37df842009-02-11 00:03:26 +0000813 self.warning_manager.disable_warnings(warning_type)
jadmanski16a7ff72009-04-01 18:19:53 +0000814 self.record("INFO", None, None,
815 "disabling %s warnings" % warning_type,
816 {"warnings.disable": warning_type})
jadmanskif37df842009-02-11 00:03:26 +0000817
818
jadmanski16a7ff72009-04-01 18:19:53 +0000819 def enable_warnings(self, warning_type):
jadmanskif37df842009-02-11 00:03:26 +0000820 self.warning_manager.enable_warnings(warning_type)
jadmanski16a7ff72009-04-01 18:19:53 +0000821 self.record("INFO", None, None,
822 "enabling %s warnings" % warning_type,
823 {"warnings.enable": warning_type})
jadmanskif37df842009-02-11 00:03:26 +0000824
825
jadmanski779bd292009-03-19 17:33:33 +0000826 def get_status_log_path(self, subdir=None):
827 """Return the path to the job status log.
828
829 @param subdir - Optional paramter indicating that you want the path
830 to a subdirectory status log.
831
832 @returns The path where the status log should be.
833 """
mbligh210bae62009-04-01 18:33:13 +0000834 if self.resultdir:
835 if subdir:
836 return os.path.join(self.resultdir, subdir, "status.log")
837 else:
838 return os.path.join(self.resultdir, "status.log")
jadmanski779bd292009-03-19 17:33:33 +0000839 else:
mbligh210bae62009-04-01 18:33:13 +0000840 return None
jadmanski779bd292009-03-19 17:33:33 +0000841
842
jadmanski6bb32d72009-03-19 20:25:24 +0000843 def _update_uncollected_logs_list(self, update_func):
844 """Updates the uncollected logs list in a multi-process safe manner.
845
846 @param update_func - a function that updates the list of uncollected
847 logs. Should take one parameter, the list to be updated.
848 """
mbligh0d0f67d2009-11-06 03:15:03 +0000849 if self._uncollected_log_file:
850 log_file = open(self._uncollected_log_file, "r+")
mbligha788dc42009-03-26 21:10:16 +0000851 fcntl.flock(log_file, fcntl.LOCK_EX)
jadmanski6bb32d72009-03-19 20:25:24 +0000852 try:
853 uncollected_logs = pickle.load(log_file)
854 update_func(uncollected_logs)
855 log_file.seek(0)
856 log_file.truncate()
857 pickle.dump(uncollected_logs, log_file)
jadmanski3bff9092009-04-22 18:09:47 +0000858 log_file.flush()
jadmanski6bb32d72009-03-19 20:25:24 +0000859 finally:
860 fcntl.flock(log_file, fcntl.LOCK_UN)
861 log_file.close()
862
863
864 def add_client_log(self, hostname, remote_path, local_path):
865 """Adds a new set of client logs to the list of uncollected logs,
866 to allow for future log recovery.
867
868 @param host - the hostname of the machine holding the logs
869 @param remote_path - the directory on the remote machine holding logs
870 @param local_path - the local directory to copy the logs into
871 """
872 def update_func(logs_list):
873 logs_list.append((hostname, remote_path, local_path))
874 self._update_uncollected_logs_list(update_func)
875
876
877 def remove_client_log(self, hostname, remote_path, local_path):
878 """Removes a set of client logs from the list of uncollected logs,
879 to allow for future log recovery.
880
881 @param host - the hostname of the machine holding the logs
882 @param remote_path - the directory on the remote machine holding logs
883 @param local_path - the local directory to copy the logs into
884 """
885 def update_func(logs_list):
886 logs_list.remove((hostname, remote_path, local_path))
887 self._update_uncollected_logs_list(update_func)
888
889
mbligh0d0f67d2009-11-06 03:15:03 +0000890 def get_client_logs(self):
891 """Retrieves the list of uncollected logs, if it exists.
892
893 @returns A list of (host, remote_path, local_path) tuples. Returns
894 an empty list if no uncollected logs file exists.
895 """
896 log_exists = (self._uncollected_log_file and
897 os.path.exists(self._uncollected_log_file))
898 if log_exists:
899 return pickle.load(open(self._uncollected_log_file))
900 else:
901 return []
902
903
mbligh084bc172008-10-18 14:02:45 +0000904 def _fill_server_control_namespace(self, namespace, protect=True):
mbligh2b92b862008-11-22 13:25:32 +0000905 """
906 Prepare a namespace to be used when executing server control files.
mbligh084bc172008-10-18 14:02:45 +0000907
908 This sets up the control file API by importing modules and making them
909 available under the appropriate names within namespace.
910
911 For use by _execute_code().
912
913 Args:
914 namespace: The namespace dictionary to fill in.
915 protect: Boolean. If True (the default) any operation that would
916 clobber an existing entry in namespace will cause an error.
917 Raises:
918 error.AutoservError: When a name would be clobbered by import.
919 """
920 def _import_names(module_name, names=()):
mbligh2b92b862008-11-22 13:25:32 +0000921 """
922 Import a module and assign named attributes into namespace.
mbligh084bc172008-10-18 14:02:45 +0000923
924 Args:
925 module_name: The string module name.
926 names: A limiting list of names to import from module_name. If
927 empty (the default), all names are imported from the module
928 similar to a "from foo.bar import *" statement.
929 Raises:
930 error.AutoservError: When a name being imported would clobber
931 a name already in namespace.
932 """
933 module = __import__(module_name, {}, {}, names)
934
935 # No names supplied? Import * from the lowest level module.
936 # (Ugh, why do I have to implement this part myself?)
937 if not names:
938 for submodule_name in module_name.split('.')[1:]:
939 module = getattr(module, submodule_name)
940 if hasattr(module, '__all__'):
941 names = getattr(module, '__all__')
942 else:
943 names = dir(module)
944
945 # Install each name into namespace, checking to make sure it
946 # doesn't override anything that already exists.
947 for name in names:
948 # Check for conflicts to help prevent future problems.
949 if name in namespace and protect:
950 if namespace[name] is not getattr(module, name):
951 raise error.AutoservError('importing name '
952 '%s from %s %r would override %r' %
953 (name, module_name, getattr(module, name),
954 namespace[name]))
955 else:
956 # Encourage cleanliness and the use of __all__ for a
957 # more concrete API with less surprises on '*' imports.
958 warnings.warn('%s (%r) being imported from %s for use '
959 'in server control files is not the '
960 'first occurrance of that import.' %
961 (name, namespace[name], module_name))
962
963 namespace[name] = getattr(module, name)
964
965
966 # This is the equivalent of prepending a bunch of import statements to
967 # the front of the control script.
mbligha2b07dd2009-06-22 18:26:13 +0000968 namespace.update(os=os, sys=sys, logging=logging)
mbligh084bc172008-10-18 14:02:45 +0000969 _import_names('autotest_lib.server',
970 ('hosts', 'autotest', 'kvm', 'git', 'standalone_profiler',
971 'source_kernel', 'rpm_kernel', 'deb_kernel', 'git_kernel'))
972 _import_names('autotest_lib.server.subcommand',
973 ('parallel', 'parallel_simple', 'subcommand'))
974 _import_names('autotest_lib.server.utils',
975 ('run', 'get_tmp_dir', 'sh_escape', 'parse_machine'))
976 _import_names('autotest_lib.client.common_lib.error')
977 _import_names('autotest_lib.client.common_lib.barrier', ('barrier',))
978
979 # Inject ourself as the job object into other classes within the API.
980 # (Yuck, this injection is a gross thing be part of a public API. -gps)
981 #
982 # XXX Base & SiteAutotest do not appear to use .job. Who does?
983 namespace['autotest'].Autotest.job = self
984 # server.hosts.base_classes.Host uses .job.
985 namespace['hosts'].Host.job = self
Eric Li10222b82010-11-24 09:33:15 -0800986 namespace['hosts'].factory.ssh_user = self._ssh_user
987 namespace['hosts'].factory.ssh_port = self._ssh_port
988 namespace['hosts'].factory.ssh_pass = self._ssh_pass
mbligh084bc172008-10-18 14:02:45 +0000989
990
991 def _execute_code(self, code_file, namespace, protect=True):
mbligh2b92b862008-11-22 13:25:32 +0000992 """
993 Execute code using a copy of namespace as a server control script.
mbligh084bc172008-10-18 14:02:45 +0000994
995 Unless protect_namespace is explicitly set to False, the dict will not
996 be modified.
997
998 Args:
999 code_file: The filename of the control file to execute.
1000 namespace: A dict containing names to make available during execution.
1001 protect: Boolean. If True (the default) a copy of the namespace dict
1002 is used during execution to prevent the code from modifying its
1003 contents outside of this function. If False the raw dict is
1004 passed in and modifications will be allowed.
1005 """
1006 if protect:
1007 namespace = namespace.copy()
1008 self._fill_server_control_namespace(namespace, protect=protect)
1009 # TODO: Simplify and get rid of the special cases for only 1 machine.
showard3e66e8c2008-10-27 19:20:51 +00001010 if len(self.machines) > 1:
mbligh084bc172008-10-18 14:02:45 +00001011 machines_text = '\n'.join(self.machines) + '\n'
1012 # Only rewrite the file if it does not match our machine list.
1013 try:
1014 machines_f = open(MACHINES_FILENAME, 'r')
1015 existing_machines_text = machines_f.read()
1016 machines_f.close()
1017 except EnvironmentError:
1018 existing_machines_text = None
1019 if machines_text != existing_machines_text:
1020 utils.open_write_close(MACHINES_FILENAME, machines_text)
1021 execfile(code_file, namespace, namespace)
jadmanski10646442008-08-13 14:05:21 +00001022
1023
jadmanskie29d0e42010-06-17 16:06:52 +00001024 def _parse_status(self, new_line):
mbligh0d0f67d2009-11-06 03:15:03 +00001025 if not self._using_parser:
jadmanski10646442008-08-13 14:05:21 +00001026 return
jadmanskie29d0e42010-06-17 16:06:52 +00001027 new_tests = self.parser.process_lines([new_line])
jadmanski10646442008-08-13 14:05:21 +00001028 for test in new_tests:
1029 self.__insert_test(test)
1030
1031
1032 def __insert_test(self, test):
mbligh2b92b862008-11-22 13:25:32 +00001033 """
1034 An internal method to insert a new test result into the
jadmanski10646442008-08-13 14:05:21 +00001035 database. This method will not raise an exception, even if an
1036 error occurs during the insert, to avoid failing a test
1037 simply because of unexpected database issues."""
showard21baa452008-10-21 00:08:39 +00001038 self.num_tests_run += 1
1039 if status_lib.is_worse_than_or_equal_to(test.status, 'FAIL'):
1040 self.num_tests_failed += 1
jadmanski10646442008-08-13 14:05:21 +00001041 try:
1042 self.results_db.insert_test(self.job_model, test)
1043 except Exception:
1044 msg = ("WARNING: An unexpected error occured while "
1045 "inserting test results into the database. "
1046 "Ignoring error.\n" + traceback.format_exc())
1047 print >> sys.stderr, msg
1048
mblighcaa62c22008-04-07 21:51:17 +00001049
mblighfc3da5b2010-01-06 18:37:22 +00001050 def preprocess_client_state(self):
1051 """
1052 Produce a state file for initializing the state of a client job.
1053
1054 Creates a new client state file with all the current server state, as
1055 well as some pre-set client state.
1056
1057 @returns The path of the file the state was written into.
1058 """
1059 # initialize the sysinfo state
1060 self._state.set('client', 'sysinfo', self.sysinfo.serialize())
1061
1062 # dump the state out to a tempfile
1063 fd, file_path = tempfile.mkstemp(dir=self.tmpdir)
1064 os.close(fd)
mbligha2c99492010-01-27 22:59:50 +00001065
1066 # write_to_file doesn't need locking, we exclusively own file_path
mblighfc3da5b2010-01-06 18:37:22 +00001067 self._state.write_to_file(file_path)
1068 return file_path
1069
1070
1071 def postprocess_client_state(self, state_path):
1072 """
1073 Update the state of this job with the state from a client job.
1074
1075 Updates the state of the server side of a job with the final state
1076 of a client job that was run. Updates the non-client-specific state,
1077 pulls in some specific bits from the client-specific state, and then
1078 discards the rest. Removes the state file afterwards
1079
1080 @param state_file A path to the state file from the client.
1081 """
1082 # update the on-disk state
mblighfc3da5b2010-01-06 18:37:22 +00001083 try:
jadmanskib6e7bdb2010-04-13 16:00:39 +00001084 self._state.read_from_file(state_path)
mblighfc3da5b2010-01-06 18:37:22 +00001085 os.remove(state_path)
mbligha2c99492010-01-27 22:59:50 +00001086 except OSError, e:
mblighfc3da5b2010-01-06 18:37:22 +00001087 # ignore file-not-found errors
1088 if e.errno != errno.ENOENT:
1089 raise
jadmanskib6e7bdb2010-04-13 16:00:39 +00001090 else:
1091 logging.debug('Client state file %s not found', state_path)
mblighfc3da5b2010-01-06 18:37:22 +00001092
1093 # update the sysinfo state
1094 if self._state.has('client', 'sysinfo'):
1095 self.sysinfo.deserialize(self._state.get('client', 'sysinfo'))
1096
1097 # drop all the client-specific state
1098 self._state.discard_namespace('client')
1099
1100
mbligh0a883702010-04-21 01:58:34 +00001101 def clear_all_known_hosts(self):
1102 """Clears known hosts files for all AbstractSSHHosts."""
1103 for host in self.hosts:
1104 if isinstance(host, abstract_ssh.AbstractSSHHost):
1105 host.clear_known_hosts()
1106
1107
jadmanskif37df842009-02-11 00:03:26 +00001108class warning_manager(object):
1109 """Class for controlling warning logs. Manages the enabling and disabling
1110 of warnings."""
1111 def __init__(self):
1112 # a map of warning types to a list of disabled time intervals
1113 self.disabled_warnings = {}
1114
1115
1116 def is_valid(self, timestamp, warning_type):
1117 """Indicates if a warning (based on the time it occured and its type)
1118 is a valid warning. A warning is considered "invalid" if this type of
1119 warning was marked as "disabled" at the time the warning occured."""
1120 disabled_intervals = self.disabled_warnings.get(warning_type, [])
1121 for start, end in disabled_intervals:
1122 if timestamp >= start and (end is None or timestamp < end):
1123 return False
1124 return True
1125
1126
1127 def disable_warnings(self, warning_type, current_time_func=time.time):
1128 """As of now, disables all further warnings of this type."""
1129 intervals = self.disabled_warnings.setdefault(warning_type, [])
1130 if not intervals or intervals[-1][1] is not None:
jadmanski16a7ff72009-04-01 18:19:53 +00001131 intervals.append((int(current_time_func()), None))
jadmanskif37df842009-02-11 00:03:26 +00001132
1133
1134 def enable_warnings(self, warning_type, current_time_func=time.time):
1135 """As of now, enables all further warnings of this type."""
1136 intervals = self.disabled_warnings.get(warning_type, [])
1137 if intervals and intervals[-1][1] is None:
jadmanski16a7ff72009-04-01 18:19:53 +00001138 intervals[-1] = (intervals[-1][0], int(current_time_func()))
Paul Pendlebury57593562011-06-15 10:45:49 -07001139
1140
1141# load up site-specific code for generating site-specific job data
1142get_site_job_data = utils.import_site_function(__file__,
1143 "autotest_lib.server.site_server_job", "get_site_job_data",
1144 _get_site_job_data_dummy)
1145
1146
1147site_server_job = utils.import_site_class(
1148 __file__, "autotest_lib.server.site_server_job", "site_server_job",
1149 base_server_job)
1150
1151
1152class server_job(site_server_job):
Dale Curtis456d3c12011-07-19 11:42:51 -07001153 pass