blob: 779d3a48c9e7130be5eeac92155d51734c0ea90d [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
Peter Mayo7a875762012-06-13 14:38:15 -0400181 self.label = label
jadmanski10646442008-08-13 14:05:21 +0000182 self.machines = machines
mbligh0d0f67d2009-11-06 03:15:03 +0000183 self._client = client
jadmanski10646442008-08-13 14:05:21 +0000184 self.warning_loggers = set()
jadmanskif37df842009-02-11 00:03:26 +0000185 self.warning_manager = warning_manager()
mbligh0d0f67d2009-11-06 03:15:03 +0000186 self._ssh_user = ssh_user
187 self._ssh_port = ssh_port
188 self._ssh_pass = ssh_pass
mblighe7d9c602009-07-02 19:02:33 +0000189 self.tag = tag
mbligh09108442008-10-15 16:27:38 +0000190 self.last_boot_tag = None
jadmanski53aaf382008-11-17 16:22:31 +0000191 self.hosts = set()
mbligh0d0f67d2009-11-06 03:15:03 +0000192 self.drop_caches = False
mblighb5dac432008-11-27 00:38:44 +0000193 self.drop_caches_between_iterations = False
mblighe0cbc912010-03-11 18:03:07 +0000194 self._control_filename = control_filename
jadmanski10646442008-08-13 14:05:21 +0000195
showard75cdfee2009-06-10 17:40:41 +0000196 self.logging = logging_manager.get_logging_manager(
197 manage_stdout_and_stderr=True, redirect_fds=True)
198 subcommand.logging_manager_object = self.logging
jadmanski10646442008-08-13 14:05:21 +0000199
mbligh0d0f67d2009-11-06 03:15:03 +0000200 self.sysinfo = sysinfo.sysinfo(self.resultdir)
jadmanski043e1132008-11-19 17:10:32 +0000201 self.profilers = profilers.profilers(self)
jadmanskic09fc152008-10-15 17:56:59 +0000202
jadmanski10646442008-08-13 14:05:21 +0000203 job_data = {'label' : label, 'user' : user,
204 'hostname' : ','.join(machines),
Eric Li861b2d52011-02-04 14:50:35 -0800205 'drone' : platform.node(),
mbligh0d0f67d2009-11-06 03:15:03 +0000206 'status_version' : str(self._STATUS_VERSION),
showard170873e2009-01-07 00:22:26 +0000207 'job_started' : str(int(time.time()))}
mbligh374f3412009-05-13 21:29:45 +0000208 if group_name:
209 job_data['host_group_name'] = group_name
jadmanski10646442008-08-13 14:05:21 +0000210
mbligh0d0f67d2009-11-06 03:15:03 +0000211 # only write these keyvals out on the first job in a resultdir
212 if 'job_started' not in utils.read_keyval(self.resultdir):
213 job_data.update(get_site_job_data(self))
214 utils.write_keyval(self.resultdir, job_data)
215
216 self._parse_job = parse_job
showardcc929362010-01-25 21:20:41 +0000217 self._using_parser = (self._parse_job and len(machines) <= 1)
mbligh0d0f67d2009-11-06 03:15:03 +0000218 self.pkgmgr = packages.PackageManager(
219 self.autodir, run_function_dargs={'timeout':600})
showard21baa452008-10-21 00:08:39 +0000220 self.num_tests_run = 0
221 self.num_tests_failed = 0
222
jadmanski550fdc22008-11-20 16:32:08 +0000223 self._register_subcommand_hooks()
224
mbligh0d0f67d2009-11-06 03:15:03 +0000225 # these components aren't usable on the server
226 self.bootloader = None
227 self.harness = None
228
jadmanski2a89dac2010-06-11 14:32:58 +0000229 # set up the status logger
jadmanski52053632010-06-11 21:08:10 +0000230 self._indenter = status_indenter()
jadmanski2a89dac2010-06-11 14:32:58 +0000231 self._logger = base_job.status_logger(
jadmanski52053632010-06-11 21:08:10 +0000232 self, self._indenter, 'status.log', 'status.log',
jadmanski2a89dac2010-06-11 14:32:58 +0000233 record_hook=server_job_record_hook(self))
234
mbligh0d0f67d2009-11-06 03:15:03 +0000235
236 @classmethod
237 def _find_base_directories(cls):
238 """
239 Determine locations of autodir, clientdir and serverdir. Assumes
240 that this file is located within serverdir and uses __file__ along
241 with relative paths to resolve the location.
242 """
243 serverdir = os.path.abspath(os.path.dirname(__file__))
244 autodir = os.path.normpath(os.path.join(serverdir, '..'))
245 clientdir = os.path.join(autodir, 'client')
246 return autodir, clientdir, serverdir
247
248
249 def _find_resultdir(self, resultdir):
250 """
251 Determine the location of resultdir. For server jobs we expect one to
252 always be explicitly passed in to __init__, so just return that.
253 """
254 if resultdir:
255 return os.path.normpath(resultdir)
256 else:
257 return None
258
jadmanski550fdc22008-11-20 16:32:08 +0000259
jadmanski2a89dac2010-06-11 14:32:58 +0000260 def _get_status_logger(self):
261 """Return a reference to the status logger."""
262 return self._logger
263
264
jadmanskie432dd22009-01-30 15:04:51 +0000265 @staticmethod
266 def _load_control_file(path):
267 f = open(path)
268 try:
269 control_file = f.read()
270 finally:
271 f.close()
272 return re.sub('\r', '', control_file)
273
274
jadmanski550fdc22008-11-20 16:32:08 +0000275 def _register_subcommand_hooks(self):
mbligh2b92b862008-11-22 13:25:32 +0000276 """
277 Register some hooks into the subcommand modules that allow us
278 to properly clean up self.hosts created in forked subprocesses.
279 """
jadmanski550fdc22008-11-20 16:32:08 +0000280 def on_fork(cmd):
281 self._existing_hosts_on_fork = set(self.hosts)
282 def on_join(cmd):
283 new_hosts = self.hosts - self._existing_hosts_on_fork
284 for host in new_hosts:
285 host.close()
286 subcommand.subcommand.register_fork_hook(on_fork)
287 subcommand.subcommand.register_join_hook(on_join)
288
jadmanski10646442008-08-13 14:05:21 +0000289
mbligh4608b002010-01-05 18:22:35 +0000290 def init_parser(self):
mbligh2b92b862008-11-22 13:25:32 +0000291 """
mbligh4608b002010-01-05 18:22:35 +0000292 Start the continuous parsing of self.resultdir. This sets up
jadmanski10646442008-08-13 14:05:21 +0000293 the database connection and inserts the basic job object into
mbligh2b92b862008-11-22 13:25:32 +0000294 the database if necessary.
295 """
mbligh4608b002010-01-05 18:22:35 +0000296 if not self._using_parser:
297 return
jadmanski10646442008-08-13 14:05:21 +0000298 # redirect parser debugging to .parse.log
mbligh4608b002010-01-05 18:22:35 +0000299 parse_log = os.path.join(self.resultdir, '.parse.log')
jadmanski10646442008-08-13 14:05:21 +0000300 parse_log = open(parse_log, 'w', 0)
301 tko_utils.redirect_parser_debugging(parse_log)
302 # create a job model object and set up the db
303 self.results_db = tko_db.db(autocommit=True)
mbligh0d0f67d2009-11-06 03:15:03 +0000304 self.parser = status_lib.parser(self._STATUS_VERSION)
mbligh4608b002010-01-05 18:22:35 +0000305 self.job_model = self.parser.make_job(self.resultdir)
jadmanski10646442008-08-13 14:05:21 +0000306 self.parser.start(self.job_model)
307 # check if a job already exists in the db and insert it if
308 # it does not
mbligh0d0f67d2009-11-06 03:15:03 +0000309 job_idx = self.results_db.find_job(self._parse_job)
jadmanski10646442008-08-13 14:05:21 +0000310 if job_idx is None:
mbligh0d0f67d2009-11-06 03:15:03 +0000311 self.results_db.insert_job(self._parse_job, self.job_model)
jadmanski10646442008-08-13 14:05:21 +0000312 else:
mbligh2b92b862008-11-22 13:25:32 +0000313 machine_idx = self.results_db.lookup_machine(self.job_model.machine)
jadmanski10646442008-08-13 14:05:21 +0000314 self.job_model.index = job_idx
315 self.job_model.machine_idx = machine_idx
316
317
318 def cleanup_parser(self):
mbligh2b92b862008-11-22 13:25:32 +0000319 """
320 This should be called after the server job is finished
jadmanski10646442008-08-13 14:05:21 +0000321 to carry out any remaining cleanup (e.g. flushing any
mbligh2b92b862008-11-22 13:25:32 +0000322 remaining test results to the results db)
323 """
mbligh0d0f67d2009-11-06 03:15:03 +0000324 if not self._using_parser:
jadmanski10646442008-08-13 14:05:21 +0000325 return
326 final_tests = self.parser.end()
327 for test in final_tests:
328 self.__insert_test(test)
mbligh0d0f67d2009-11-06 03:15:03 +0000329 self._using_parser = False
jadmanski10646442008-08-13 14:05:21 +0000330
331
332 def verify(self):
333 if not self.machines:
mbligh084bc172008-10-18 14:02:45 +0000334 raise error.AutoservError('No machines specified to verify')
mbligh0fce4112008-11-27 00:37:17 +0000335 if self.resultdir:
336 os.chdir(self.resultdir)
jadmanski10646442008-08-13 14:05:21 +0000337 try:
jadmanskicdd0c402008-09-19 21:21:31 +0000338 namespace = {'machines' : self.machines, 'job' : self,
mbligh0d0f67d2009-11-06 03:15:03 +0000339 'ssh_user' : self._ssh_user,
340 'ssh_port' : self._ssh_port,
341 'ssh_pass' : self._ssh_pass}
mbligh084bc172008-10-18 14:02:45 +0000342 self._execute_code(VERIFY_CONTROL_FILE, namespace, protect=False)
jadmanski10646442008-08-13 14:05:21 +0000343 except Exception, e:
mbligh2b92b862008-11-22 13:25:32 +0000344 msg = ('Verify failed\n' + str(e) + '\n' + traceback.format_exc())
jadmanski10646442008-08-13 14:05:21 +0000345 self.record('ABORT', None, None, msg)
346 raise
347
348
349 def repair(self, host_protection):
350 if not self.machines:
351 raise error.AutoservError('No machines specified to repair')
mbligh0fce4112008-11-27 00:37:17 +0000352 if self.resultdir:
353 os.chdir(self.resultdir)
jadmanski10646442008-08-13 14:05:21 +0000354 namespace = {'machines': self.machines, 'job': self,
mbligh0d0f67d2009-11-06 03:15:03 +0000355 'ssh_user': self._ssh_user, 'ssh_port': self._ssh_port,
356 'ssh_pass': self._ssh_pass,
jadmanski10646442008-08-13 14:05:21 +0000357 'protection_level': host_protection}
mbligh25c0b8c2009-01-24 01:44:17 +0000358
mbligh0931b0a2009-04-08 17:44:48 +0000359 self._execute_code(REPAIR_CONTROL_FILE, namespace, protect=False)
jadmanski10646442008-08-13 14:05:21 +0000360
361
362 def precheck(self):
363 """
364 perform any additional checks in derived classes.
365 """
366 pass
367
368
369 def enable_external_logging(self):
mbligh2b92b862008-11-22 13:25:32 +0000370 """
371 Start or restart external logging mechanism.
jadmanski10646442008-08-13 14:05:21 +0000372 """
373 pass
374
375
376 def disable_external_logging(self):
mbligh2b92b862008-11-22 13:25:32 +0000377 """
378 Pause or stop external logging mechanism.
jadmanski10646442008-08-13 14:05:21 +0000379 """
380 pass
381
382
383 def use_external_logging(self):
mbligh2b92b862008-11-22 13:25:32 +0000384 """
385 Return True if external logging should be used.
jadmanski10646442008-08-13 14:05:21 +0000386 """
387 return False
388
389
mbligh415dc212009-06-15 21:53:34 +0000390 def _make_parallel_wrapper(self, function, machines, log):
391 """Wrap function as appropriate for calling by parallel_simple."""
mbligh2b92b862008-11-22 13:25:32 +0000392 is_forking = not (len(machines) == 1 and self.machines == machines)
mbligh0d0f67d2009-11-06 03:15:03 +0000393 if self._parse_job and is_forking and log:
jadmanski10646442008-08-13 14:05:21 +0000394 def wrapper(machine):
mbligh0d0f67d2009-11-06 03:15:03 +0000395 self._parse_job += "/" + machine
396 self._using_parser = True
jadmanski10646442008-08-13 14:05:21 +0000397 self.machines = [machine]
mbligh0d0f67d2009-11-06 03:15:03 +0000398 self.push_execution_context(machine)
jadmanski609a5f42008-08-26 20:52:42 +0000399 os.chdir(self.resultdir)
showard2bab8f42008-11-12 18:15:22 +0000400 utils.write_keyval(self.resultdir, {"hostname": machine})
mbligh4608b002010-01-05 18:22:35 +0000401 self.init_parser()
jadmanski10646442008-08-13 14:05:21 +0000402 result = function(machine)
403 self.cleanup_parser()
404 return result
jadmanski4dd1a002008-09-05 20:27:30 +0000405 elif len(machines) > 1 and log:
jadmanski10646442008-08-13 14:05:21 +0000406 def wrapper(machine):
mbligh0d0f67d2009-11-06 03:15:03 +0000407 self.push_execution_context(machine)
jadmanski609a5f42008-08-26 20:52:42 +0000408 os.chdir(self.resultdir)
mbligh838d82d2009-03-11 17:14:31 +0000409 machine_data = {'hostname' : machine,
mbligh0d0f67d2009-11-06 03:15:03 +0000410 'status_version' : str(self._STATUS_VERSION)}
mbligh838d82d2009-03-11 17:14:31 +0000411 utils.write_keyval(self.resultdir, machine_data)
jadmanski10646442008-08-13 14:05:21 +0000412 result = function(machine)
413 return result
414 else:
415 wrapper = function
mbligh415dc212009-06-15 21:53:34 +0000416 return wrapper
417
418
419 def parallel_simple(self, function, machines, log=True, timeout=None,
420 return_results=False):
421 """
422 Run 'function' using parallel_simple, with an extra wrapper to handle
423 the necessary setup for continuous parsing, if possible. If continuous
424 parsing is already properly initialized then this should just work.
425
426 @param function: A callable to run in parallel given each machine.
427 @param machines: A list of machine names to be passed one per subcommand
428 invocation of function.
429 @param log: If True, output will be written to output in a subdirectory
430 named after each machine.
431 @param timeout: Seconds after which the function call should timeout.
432 @param return_results: If True instead of an AutoServError being raised
433 on any error a list of the results|exceptions from the function
434 called on each arg is returned. [default: False]
435
436 @raises error.AutotestError: If any of the functions failed.
437 """
438 wrapper = self._make_parallel_wrapper(function, machines, log)
439 return subcommand.parallel_simple(wrapper, machines,
440 log=log, timeout=timeout,
441 return_results=return_results)
442
443
444 def parallel_on_machines(self, function, machines, timeout=None):
445 """
showardcd5fac42009-07-06 20:19:43 +0000446 @param function: Called in parallel with one machine as its argument.
mbligh415dc212009-06-15 21:53:34 +0000447 @param machines: A list of machines to call function(machine) on.
448 @param timeout: Seconds after which the function call should timeout.
449
450 @returns A list of machines on which function(machine) returned
451 without raising an exception.
452 """
showardcd5fac42009-07-06 20:19:43 +0000453 results = self.parallel_simple(function, machines, timeout=timeout,
mbligh415dc212009-06-15 21:53:34 +0000454 return_results=True)
455 success_machines = []
456 for result, machine in itertools.izip(results, machines):
457 if not isinstance(result, Exception):
458 success_machines.append(machine)
459 return success_machines
jadmanski10646442008-08-13 14:05:21 +0000460
461
mbligh0d0f67d2009-11-06 03:15:03 +0000462 _USE_TEMP_DIR = object()
mbligh2b92b862008-11-22 13:25:32 +0000463 def run(self, cleanup=False, install_before=False, install_after=False,
jadmanskie432dd22009-01-30 15:04:51 +0000464 collect_crashdumps=True, namespace={}, control=None,
jadmanskidef0c3c2009-03-25 20:07:10 +0000465 control_file_dir=None, only_collect_crashinfo=False):
jadmanskifb9c0fa2009-04-29 17:39:16 +0000466 # for a normal job, make sure the uncollected logs file exists
467 # for a crashinfo-only run it should already exist, bail out otherwise
jadmanski648c39f2010-03-19 17:38:01 +0000468 created_uncollected_logs = False
mbligh0d0f67d2009-11-06 03:15:03 +0000469 if self.resultdir and not os.path.exists(self._uncollected_log_file):
jadmanskifb9c0fa2009-04-29 17:39:16 +0000470 if only_collect_crashinfo:
471 # if this is a crashinfo-only run, and there were no existing
472 # uncollected logs, just bail out early
473 logging.info("No existing uncollected logs, "
474 "skipping crashinfo collection")
475 return
476 else:
mbligh0d0f67d2009-11-06 03:15:03 +0000477 log_file = open(self._uncollected_log_file, "w")
jadmanskifb9c0fa2009-04-29 17:39:16 +0000478 pickle.dump([], log_file)
479 log_file.close()
jadmanski648c39f2010-03-19 17:38:01 +0000480 created_uncollected_logs = True
jadmanskifb9c0fa2009-04-29 17:39:16 +0000481
jadmanski10646442008-08-13 14:05:21 +0000482 # use a copy so changes don't affect the original dictionary
483 namespace = namespace.copy()
484 machines = self.machines
jadmanskie432dd22009-01-30 15:04:51 +0000485 if control is None:
jadmanski02a3ba22009-11-13 20:47:27 +0000486 if self.control is None:
487 control = ''
488 else:
489 control = self._load_control_file(self.control)
jadmanskie432dd22009-01-30 15:04:51 +0000490 if control_file_dir is None:
491 control_file_dir = self.resultdir
jadmanski10646442008-08-13 14:05:21 +0000492
493 self.aborted = False
494 namespace['machines'] = machines
jadmanski808f4b12010-04-09 22:30:31 +0000495 namespace['args'] = self.args
jadmanski10646442008-08-13 14:05:21 +0000496 namespace['job'] = self
mbligh0d0f67d2009-11-06 03:15:03 +0000497 namespace['ssh_user'] = self._ssh_user
498 namespace['ssh_port'] = self._ssh_port
499 namespace['ssh_pass'] = self._ssh_pass
jadmanski10646442008-08-13 14:05:21 +0000500 test_start_time = int(time.time())
501
mbligh80e1eba2008-11-19 00:26:18 +0000502 if self.resultdir:
503 os.chdir(self.resultdir)
jadmanski779bd292009-03-19 17:33:33 +0000504 # touch status.log so that the parser knows a job is running here
jadmanski382303a2009-04-21 19:53:39 +0000505 open(self.get_status_log_path(), 'a').close()
mbligh80e1eba2008-11-19 00:26:18 +0000506 self.enable_external_logging()
jadmanskie432dd22009-01-30 15:04:51 +0000507
jadmanskicdd0c402008-09-19 21:21:31 +0000508 collect_crashinfo = True
mblighaebe3b62008-12-22 14:45:40 +0000509 temp_control_file_dir = None
jadmanski10646442008-08-13 14:05:21 +0000510 try:
showardcf8d4922009-10-14 16:08:39 +0000511 try:
512 if install_before and machines:
513 self._execute_code(INSTALL_CONTROL_FILE, namespace)
jadmanskie432dd22009-01-30 15:04:51 +0000514
showardcf8d4922009-10-14 16:08:39 +0000515 if only_collect_crashinfo:
516 return
517
jadmanskidef0c3c2009-03-25 20:07:10 +0000518 # determine the dir to write the control files to
519 cfd_specified = (control_file_dir
mbligh0d0f67d2009-11-06 03:15:03 +0000520 and control_file_dir is not self._USE_TEMP_DIR)
jadmanskidef0c3c2009-03-25 20:07:10 +0000521 if cfd_specified:
522 temp_control_file_dir = None
523 else:
524 temp_control_file_dir = tempfile.mkdtemp(
525 suffix='temp_control_file_dir')
526 control_file_dir = temp_control_file_dir
527 server_control_file = os.path.join(control_file_dir,
mblighe0cbc912010-03-11 18:03:07 +0000528 self._control_filename)
jadmanskidef0c3c2009-03-25 20:07:10 +0000529 client_control_file = os.path.join(control_file_dir,
530 CLIENT_CONTROL_FILENAME)
mbligh0d0f67d2009-11-06 03:15:03 +0000531 if self._client:
jadmanskidef0c3c2009-03-25 20:07:10 +0000532 namespace['control'] = control
533 utils.open_write_close(client_control_file, control)
mblighfeac0102009-04-28 18:31:12 +0000534 shutil.copyfile(CLIENT_WRAPPER_CONTROL_FILE,
535 server_control_file)
jadmanskidef0c3c2009-03-25 20:07:10 +0000536 else:
537 utils.open_write_close(server_control_file, control)
mbligh26f0d882009-06-22 18:30:01 +0000538 logging.info("Processing control file")
jadmanskidef0c3c2009-03-25 20:07:10 +0000539 self._execute_code(server_control_file, namespace)
mbligh26f0d882009-06-22 18:30:01 +0000540 logging.info("Finished processing control file")
jadmanski10646442008-08-13 14:05:21 +0000541
jadmanskidef0c3c2009-03-25 20:07:10 +0000542 # no error occured, so we don't need to collect crashinfo
543 collect_crashinfo = False
Eric Li6f27d4f2010-09-29 10:55:17 -0700544 except Exception, e:
showardcf8d4922009-10-14 16:08:39 +0000545 try:
546 logging.exception(
547 'Exception escaped control file, job aborting:')
Eric Li6f27d4f2010-09-29 10:55:17 -0700548 self.record('INFO', None, None, str(e),
549 {'job_abort_reason': str(e)})
showardcf8d4922009-10-14 16:08:39 +0000550 except:
551 pass # don't let logging exceptions here interfere
552 raise
jadmanski10646442008-08-13 14:05:21 +0000553 finally:
mblighaebe3b62008-12-22 14:45:40 +0000554 if temp_control_file_dir:
jadmanskie432dd22009-01-30 15:04:51 +0000555 # Clean up temp directory used for copies of the control files
mblighaebe3b62008-12-22 14:45:40 +0000556 try:
557 shutil.rmtree(temp_control_file_dir)
558 except Exception, e:
mblighe7d9c602009-07-02 19:02:33 +0000559 logging.warn('Could not remove temp directory %s: %s',
560 temp_control_file_dir, e)
jadmanskie432dd22009-01-30 15:04:51 +0000561
jadmanskicdd0c402008-09-19 21:21:31 +0000562 if machines and (collect_crashdumps or collect_crashinfo):
jadmanski10646442008-08-13 14:05:21 +0000563 namespace['test_start_time'] = test_start_time
jadmanskicdd0c402008-09-19 21:21:31 +0000564 if collect_crashinfo:
mbligh084bc172008-10-18 14:02:45 +0000565 # includes crashdumps
566 self._execute_code(CRASHINFO_CONTROL_FILE, namespace)
jadmanskicdd0c402008-09-19 21:21:31 +0000567 else:
mbligh084bc172008-10-18 14:02:45 +0000568 self._execute_code(CRASHDUMPS_CONTROL_FILE, namespace)
jadmanski10646442008-08-13 14:05:21 +0000569 self.disable_external_logging()
showard45ae8192008-11-05 19:32:53 +0000570 if cleanup and machines:
571 self._execute_code(CLEANUP_CONTROL_FILE, namespace)
Chris Sosaf4d43ff2012-10-30 11:21:05 -0700572 if self._uncollected_log_file and created_uncollected_logs:
573 os.remove(self._uncollected_log_file)
jadmanski10646442008-08-13 14:05:21 +0000574 if install_after and machines:
mbligh084bc172008-10-18 14:02:45 +0000575 self._execute_code(INSTALL_CONTROL_FILE, namespace)
jadmanski10646442008-08-13 14:05:21 +0000576
577
578 def run_test(self, url, *args, **dargs):
mbligh2b92b862008-11-22 13:25:32 +0000579 """
580 Summon a test object and run it.
jadmanski10646442008-08-13 14:05:21 +0000581
582 tag
583 tag to add to testname
584 url
585 url of the test to run
586 """
mblighfc3da5b2010-01-06 18:37:22 +0000587 group, testname = self.pkgmgr.get_package_name(url, 'test')
588 testname, subdir, tag = self._build_tagged_test_name(testname, dargs)
589 outputdir = self._make_test_outputdir(subdir)
jadmanski10646442008-08-13 14:05:21 +0000590
591 def group_func():
592 try:
593 test.runtest(self, url, tag, args, dargs)
594 except error.TestBaseException, e:
595 self.record(e.exit_status, subdir, testname, str(e))
596 raise
597 except Exception, e:
598 info = str(e) + "\n" + traceback.format_exc()
599 self.record('FAIL', subdir, testname, info)
600 raise
601 else:
mbligh2b92b862008-11-22 13:25:32 +0000602 self.record('GOOD', subdir, testname, 'completed successfully')
jadmanskide292df2008-08-26 20:51:14 +0000603
604 result, exc_info = self._run_group(testname, subdir, group_func)
605 if exc_info and isinstance(exc_info[1], error.TestBaseException):
606 return False
607 elif exc_info:
608 raise exc_info[0], exc_info[1], exc_info[2]
609 else:
610 return True
jadmanski10646442008-08-13 14:05:21 +0000611
612
613 def _run_group(self, name, subdir, function, *args, **dargs):
614 """\
615 Underlying method for running something inside of a group.
616 """
jadmanskide292df2008-08-26 20:51:14 +0000617 result, exc_info = None, None
jadmanski10646442008-08-13 14:05:21 +0000618 try:
619 self.record('START', subdir, name)
jadmanski52053632010-06-11 21:08:10 +0000620 result = function(*args, **dargs)
jadmanski10646442008-08-13 14:05:21 +0000621 except error.TestBaseException, e:
jadmanskib88d6dc2009-01-10 00:33:18 +0000622 self.record("END %s" % e.exit_status, subdir, name)
jadmanskide292df2008-08-26 20:51:14 +0000623 exc_info = sys.exc_info()
jadmanski10646442008-08-13 14:05:21 +0000624 except Exception, e:
625 err_msg = str(e) + '\n'
626 err_msg += traceback.format_exc()
627 self.record('END ABORT', subdir, name, err_msg)
628 raise error.JobError(name + ' failed\n' + traceback.format_exc())
629 else:
630 self.record('END GOOD', subdir, name)
631
jadmanskide292df2008-08-26 20:51:14 +0000632 return result, exc_info
jadmanski10646442008-08-13 14:05:21 +0000633
634
635 def run_group(self, function, *args, **dargs):
636 """\
637 function:
638 subroutine to run
639 *args:
640 arguments for the function
641 """
642
643 name = function.__name__
644
645 # Allow the tag for the group to be specified.
646 tag = dargs.pop('tag', None)
647 if tag:
648 name = tag
649
jadmanskide292df2008-08-26 20:51:14 +0000650 return self._run_group(name, None, function, *args, **dargs)[0]
jadmanski10646442008-08-13 14:05:21 +0000651
652
653 def run_reboot(self, reboot_func, get_kernel_func):
654 """\
655 A specialization of run_group meant specifically for handling
656 a reboot. Includes support for capturing the kernel version
657 after the reboot.
658
659 reboot_func: a function that carries out the reboot
660
661 get_kernel_func: a function that returns a string
662 representing the kernel version.
663 """
jadmanski10646442008-08-13 14:05:21 +0000664 try:
665 self.record('START', None, 'reboot')
jadmanski10646442008-08-13 14:05:21 +0000666 reboot_func()
667 except Exception, e:
jadmanski10646442008-08-13 14:05:21 +0000668 err_msg = str(e) + '\n' + traceback.format_exc()
669 self.record('END FAIL', None, 'reboot', err_msg)
jadmanski4b51d542009-04-08 14:17:16 +0000670 raise
jadmanski10646442008-08-13 14:05:21 +0000671 else:
672 kernel = get_kernel_func()
jadmanski10646442008-08-13 14:05:21 +0000673 self.record('END GOOD', None, 'reboot',
Dale Curtis74a314b2011-06-23 14:55:46 -0700674 optional_fields={"kernel": kernel})
jadmanski10646442008-08-13 14:05:21 +0000675
676
jadmanskie432dd22009-01-30 15:04:51 +0000677 def run_control(self, path):
678 """Execute a control file found at path (relative to the autotest
679 path). Intended for executing a control file within a control file,
680 not for running the top-level job control file."""
681 path = os.path.join(self.autodir, path)
682 control_file = self._load_control_file(path)
mbligh0d0f67d2009-11-06 03:15:03 +0000683 self.run(control=control_file, control_file_dir=self._USE_TEMP_DIR)
jadmanskie432dd22009-01-30 15:04:51 +0000684
685
jadmanskic09fc152008-10-15 17:56:59 +0000686 def add_sysinfo_command(self, command, logfile=None, on_every_test=False):
mbligh4395bbd2009-03-25 19:34:17 +0000687 self._add_sysinfo_loggable(sysinfo.command(command, logf=logfile),
jadmanskic09fc152008-10-15 17:56:59 +0000688 on_every_test)
689
690
691 def add_sysinfo_logfile(self, file, on_every_test=False):
692 self._add_sysinfo_loggable(sysinfo.logfile(file), on_every_test)
693
694
695 def _add_sysinfo_loggable(self, loggable, on_every_test):
696 if on_every_test:
697 self.sysinfo.test_loggables.add(loggable)
698 else:
699 self.sysinfo.boot_loggables.add(loggable)
700
701
jadmanski10646442008-08-13 14:05:21 +0000702 def _read_warnings(self):
jadmanskif37df842009-02-11 00:03:26 +0000703 """Poll all the warning loggers and extract any new warnings that have
704 been logged. If the warnings belong to a category that is currently
705 disabled, this method will discard them and they will no longer be
706 retrievable.
707
708 Returns a list of (timestamp, message) tuples, where timestamp is an
709 integer epoch timestamp."""
jadmanski10646442008-08-13 14:05:21 +0000710 warnings = []
711 while True:
712 # pull in a line of output from every logger that has
713 # output ready to be read
mbligh2b92b862008-11-22 13:25:32 +0000714 loggers, _, _ = select.select(self.warning_loggers, [], [], 0)
jadmanski10646442008-08-13 14:05:21 +0000715 closed_loggers = set()
716 for logger in loggers:
717 line = logger.readline()
718 # record any broken pipes (aka line == empty)
719 if len(line) == 0:
720 closed_loggers.add(logger)
721 continue
jadmanskif37df842009-02-11 00:03:26 +0000722 # parse out the warning
723 timestamp, msgtype, msg = line.split('\t', 2)
724 timestamp = int(timestamp)
725 # if the warning is valid, add it to the results
726 if self.warning_manager.is_valid(timestamp, msgtype):
727 warnings.append((timestamp, msg.strip()))
jadmanski10646442008-08-13 14:05:21 +0000728
729 # stop listening to loggers that are closed
730 self.warning_loggers -= closed_loggers
731
732 # stop if none of the loggers have any output left
733 if not loggers:
734 break
735
736 # sort into timestamp order
737 warnings.sort()
738 return warnings
739
740
showardcc929362010-01-25 21:20:41 +0000741 def _unique_subdirectory(self, base_subdirectory_name):
742 """Compute a unique results subdirectory based on the given name.
743
744 Appends base_subdirectory_name with a number as necessary to find a
745 directory name that doesn't already exist.
746 """
747 subdirectory = base_subdirectory_name
748 counter = 1
749 while os.path.exists(os.path.join(self.resultdir, subdirectory)):
750 subdirectory = base_subdirectory_name + '.' + str(counter)
751 counter += 1
752 return subdirectory
753
754
jadmanski52053632010-06-11 21:08:10 +0000755 def get_record_context(self):
756 """Returns an object representing the current job.record context.
757
758 The object returned is an opaque object with a 0-arg restore method
759 which can be called to restore the job.record context (i.e. indentation)
760 to the current level. The intention is that it should be used when
761 something external which generate job.record calls (e.g. an autotest
762 client) can fail catastrophically and the server job record state
763 needs to be reset to its original "known good" state.
764
765 @return: A context object with a 0-arg restore() method."""
766 return self._indenter.get_context()
767
768
showardcc929362010-01-25 21:20:41 +0000769 def record_summary(self, status_code, test_name, reason='', attributes=None,
770 distinguishing_attributes=(), child_test_ids=None):
771 """Record a summary test result.
772
773 @param status_code: status code string, see
774 common_lib.log.is_valid_status()
775 @param test_name: name of the test
776 @param reason: (optional) string providing detailed reason for test
777 outcome
778 @param attributes: (optional) dict of string keyvals to associate with
779 this result
780 @param distinguishing_attributes: (optional) list of attribute names
781 that should be used to distinguish identically-named test
782 results. These attributes should be present in the attributes
783 parameter. This is used to generate user-friendly subdirectory
784 names.
785 @param child_test_ids: (optional) list of test indices for test results
786 used in generating this result.
787 """
788 subdirectory_name_parts = [test_name]
789 for attribute in distinguishing_attributes:
790 assert attributes
791 assert attribute in attributes, '%s not in %s' % (attribute,
792 attributes)
793 subdirectory_name_parts.append(attributes[attribute])
794 base_subdirectory_name = '.'.join(subdirectory_name_parts)
795
796 subdirectory = self._unique_subdirectory(base_subdirectory_name)
797 subdirectory_path = os.path.join(self.resultdir, subdirectory)
798 os.mkdir(subdirectory_path)
799
800 self.record(status_code, subdirectory, test_name,
801 status=reason, optional_fields={'is_summary': True})
802
803 if attributes:
804 utils.write_keyval(subdirectory_path, attributes)
805
806 if child_test_ids:
807 ids_string = ','.join(str(test_id) for test_id in child_test_ids)
808 summary_data = {'child_test_ids': ids_string}
809 utils.write_keyval(os.path.join(subdirectory_path, 'summary_data'),
810 summary_data)
811
812
jadmanski16a7ff72009-04-01 18:19:53 +0000813 def disable_warnings(self, warning_type):
jadmanskif37df842009-02-11 00:03:26 +0000814 self.warning_manager.disable_warnings(warning_type)
jadmanski16a7ff72009-04-01 18:19:53 +0000815 self.record("INFO", None, None,
816 "disabling %s warnings" % warning_type,
817 {"warnings.disable": warning_type})
jadmanskif37df842009-02-11 00:03:26 +0000818
819
jadmanski16a7ff72009-04-01 18:19:53 +0000820 def enable_warnings(self, warning_type):
jadmanskif37df842009-02-11 00:03:26 +0000821 self.warning_manager.enable_warnings(warning_type)
jadmanski16a7ff72009-04-01 18:19:53 +0000822 self.record("INFO", None, None,
823 "enabling %s warnings" % warning_type,
824 {"warnings.enable": warning_type})
jadmanskif37df842009-02-11 00:03:26 +0000825
826
jadmanski779bd292009-03-19 17:33:33 +0000827 def get_status_log_path(self, subdir=None):
828 """Return the path to the job status log.
829
830 @param subdir - Optional paramter indicating that you want the path
831 to a subdirectory status log.
832
833 @returns The path where the status log should be.
834 """
mbligh210bae62009-04-01 18:33:13 +0000835 if self.resultdir:
836 if subdir:
837 return os.path.join(self.resultdir, subdir, "status.log")
838 else:
839 return os.path.join(self.resultdir, "status.log")
jadmanski779bd292009-03-19 17:33:33 +0000840 else:
mbligh210bae62009-04-01 18:33:13 +0000841 return None
jadmanski779bd292009-03-19 17:33:33 +0000842
843
jadmanski6bb32d72009-03-19 20:25:24 +0000844 def _update_uncollected_logs_list(self, update_func):
845 """Updates the uncollected logs list in a multi-process safe manner.
846
847 @param update_func - a function that updates the list of uncollected
848 logs. Should take one parameter, the list to be updated.
849 """
mbligh0d0f67d2009-11-06 03:15:03 +0000850 if self._uncollected_log_file:
851 log_file = open(self._uncollected_log_file, "r+")
mbligha788dc42009-03-26 21:10:16 +0000852 fcntl.flock(log_file, fcntl.LOCK_EX)
jadmanski6bb32d72009-03-19 20:25:24 +0000853 try:
854 uncollected_logs = pickle.load(log_file)
855 update_func(uncollected_logs)
856 log_file.seek(0)
857 log_file.truncate()
858 pickle.dump(uncollected_logs, log_file)
jadmanski3bff9092009-04-22 18:09:47 +0000859 log_file.flush()
jadmanski6bb32d72009-03-19 20:25:24 +0000860 finally:
861 fcntl.flock(log_file, fcntl.LOCK_UN)
862 log_file.close()
863
864
865 def add_client_log(self, hostname, remote_path, local_path):
866 """Adds a new set of client logs to the list of uncollected logs,
867 to allow for future log recovery.
868
869 @param host - the hostname of the machine holding the logs
870 @param remote_path - the directory on the remote machine holding logs
871 @param local_path - the local directory to copy the logs into
872 """
873 def update_func(logs_list):
874 logs_list.append((hostname, remote_path, local_path))
875 self._update_uncollected_logs_list(update_func)
876
877
878 def remove_client_log(self, hostname, remote_path, local_path):
879 """Removes a set of client logs from the list of uncollected logs,
880 to allow for future log recovery.
881
882 @param host - the hostname of the machine holding the logs
883 @param remote_path - the directory on the remote machine holding logs
884 @param local_path - the local directory to copy the logs into
885 """
886 def update_func(logs_list):
887 logs_list.remove((hostname, remote_path, local_path))
888 self._update_uncollected_logs_list(update_func)
889
890
mbligh0d0f67d2009-11-06 03:15:03 +0000891 def get_client_logs(self):
892 """Retrieves the list of uncollected logs, if it exists.
893
894 @returns A list of (host, remote_path, local_path) tuples. Returns
895 an empty list if no uncollected logs file exists.
896 """
897 log_exists = (self._uncollected_log_file and
898 os.path.exists(self._uncollected_log_file))
899 if log_exists:
900 return pickle.load(open(self._uncollected_log_file))
901 else:
902 return []
903
904
mbligh084bc172008-10-18 14:02:45 +0000905 def _fill_server_control_namespace(self, namespace, protect=True):
mbligh2b92b862008-11-22 13:25:32 +0000906 """
907 Prepare a namespace to be used when executing server control files.
mbligh084bc172008-10-18 14:02:45 +0000908
909 This sets up the control file API by importing modules and making them
910 available under the appropriate names within namespace.
911
912 For use by _execute_code().
913
914 Args:
915 namespace: The namespace dictionary to fill in.
916 protect: Boolean. If True (the default) any operation that would
917 clobber an existing entry in namespace will cause an error.
918 Raises:
919 error.AutoservError: When a name would be clobbered by import.
920 """
921 def _import_names(module_name, names=()):
mbligh2b92b862008-11-22 13:25:32 +0000922 """
923 Import a module and assign named attributes into namespace.
mbligh084bc172008-10-18 14:02:45 +0000924
925 Args:
926 module_name: The string module name.
927 names: A limiting list of names to import from module_name. If
928 empty (the default), all names are imported from the module
929 similar to a "from foo.bar import *" statement.
930 Raises:
931 error.AutoservError: When a name being imported would clobber
932 a name already in namespace.
933 """
934 module = __import__(module_name, {}, {}, names)
935
936 # No names supplied? Import * from the lowest level module.
937 # (Ugh, why do I have to implement this part myself?)
938 if not names:
939 for submodule_name in module_name.split('.')[1:]:
940 module = getattr(module, submodule_name)
941 if hasattr(module, '__all__'):
942 names = getattr(module, '__all__')
943 else:
944 names = dir(module)
945
946 # Install each name into namespace, checking to make sure it
947 # doesn't override anything that already exists.
948 for name in names:
949 # Check for conflicts to help prevent future problems.
950 if name in namespace and protect:
951 if namespace[name] is not getattr(module, name):
952 raise error.AutoservError('importing name '
953 '%s from %s %r would override %r' %
954 (name, module_name, getattr(module, name),
955 namespace[name]))
956 else:
957 # Encourage cleanliness and the use of __all__ for a
958 # more concrete API with less surprises on '*' imports.
959 warnings.warn('%s (%r) being imported from %s for use '
960 'in server control files is not the '
961 'first occurrance of that import.' %
962 (name, namespace[name], module_name))
963
964 namespace[name] = getattr(module, name)
965
966
967 # This is the equivalent of prepending a bunch of import statements to
968 # the front of the control script.
mbligha2b07dd2009-06-22 18:26:13 +0000969 namespace.update(os=os, sys=sys, logging=logging)
mbligh084bc172008-10-18 14:02:45 +0000970 _import_names('autotest_lib.server',
971 ('hosts', 'autotest', 'kvm', 'git', 'standalone_profiler',
972 'source_kernel', 'rpm_kernel', 'deb_kernel', 'git_kernel'))
973 _import_names('autotest_lib.server.subcommand',
974 ('parallel', 'parallel_simple', 'subcommand'))
975 _import_names('autotest_lib.server.utils',
976 ('run', 'get_tmp_dir', 'sh_escape', 'parse_machine'))
977 _import_names('autotest_lib.client.common_lib.error')
978 _import_names('autotest_lib.client.common_lib.barrier', ('barrier',))
979
980 # Inject ourself as the job object into other classes within the API.
981 # (Yuck, this injection is a gross thing be part of a public API. -gps)
982 #
983 # XXX Base & SiteAutotest do not appear to use .job. Who does?
984 namespace['autotest'].Autotest.job = self
985 # server.hosts.base_classes.Host uses .job.
986 namespace['hosts'].Host.job = self
Eric Li10222b82010-11-24 09:33:15 -0800987 namespace['hosts'].factory.ssh_user = self._ssh_user
988 namespace['hosts'].factory.ssh_port = self._ssh_port
989 namespace['hosts'].factory.ssh_pass = self._ssh_pass
mbligh084bc172008-10-18 14:02:45 +0000990
991
992 def _execute_code(self, code_file, namespace, protect=True):
mbligh2b92b862008-11-22 13:25:32 +0000993 """
994 Execute code using a copy of namespace as a server control script.
mbligh084bc172008-10-18 14:02:45 +0000995
996 Unless protect_namespace is explicitly set to False, the dict will not
997 be modified.
998
999 Args:
1000 code_file: The filename of the control file to execute.
1001 namespace: A dict containing names to make available during execution.
1002 protect: Boolean. If True (the default) a copy of the namespace dict
1003 is used during execution to prevent the code from modifying its
1004 contents outside of this function. If False the raw dict is
1005 passed in and modifications will be allowed.
1006 """
1007 if protect:
1008 namespace = namespace.copy()
1009 self._fill_server_control_namespace(namespace, protect=protect)
1010 # TODO: Simplify and get rid of the special cases for only 1 machine.
showard3e66e8c2008-10-27 19:20:51 +00001011 if len(self.machines) > 1:
mbligh084bc172008-10-18 14:02:45 +00001012 machines_text = '\n'.join(self.machines) + '\n'
1013 # Only rewrite the file if it does not match our machine list.
1014 try:
1015 machines_f = open(MACHINES_FILENAME, 'r')
1016 existing_machines_text = machines_f.read()
1017 machines_f.close()
1018 except EnvironmentError:
1019 existing_machines_text = None
1020 if machines_text != existing_machines_text:
1021 utils.open_write_close(MACHINES_FILENAME, machines_text)
1022 execfile(code_file, namespace, namespace)
jadmanski10646442008-08-13 14:05:21 +00001023
1024
jadmanskie29d0e42010-06-17 16:06:52 +00001025 def _parse_status(self, new_line):
mbligh0d0f67d2009-11-06 03:15:03 +00001026 if not self._using_parser:
jadmanski10646442008-08-13 14:05:21 +00001027 return
jadmanskie29d0e42010-06-17 16:06:52 +00001028 new_tests = self.parser.process_lines([new_line])
jadmanski10646442008-08-13 14:05:21 +00001029 for test in new_tests:
1030 self.__insert_test(test)
1031
1032
1033 def __insert_test(self, test):
mbligh2b92b862008-11-22 13:25:32 +00001034 """
1035 An internal method to insert a new test result into the
jadmanski10646442008-08-13 14:05:21 +00001036 database. This method will not raise an exception, even if an
1037 error occurs during the insert, to avoid failing a test
1038 simply because of unexpected database issues."""
showard21baa452008-10-21 00:08:39 +00001039 self.num_tests_run += 1
1040 if status_lib.is_worse_than_or_equal_to(test.status, 'FAIL'):
1041 self.num_tests_failed += 1
jadmanski10646442008-08-13 14:05:21 +00001042 try:
1043 self.results_db.insert_test(self.job_model, test)
1044 except Exception:
1045 msg = ("WARNING: An unexpected error occured while "
1046 "inserting test results into the database. "
1047 "Ignoring error.\n" + traceback.format_exc())
1048 print >> sys.stderr, msg
1049
mblighcaa62c22008-04-07 21:51:17 +00001050
mblighfc3da5b2010-01-06 18:37:22 +00001051 def preprocess_client_state(self):
1052 """
1053 Produce a state file for initializing the state of a client job.
1054
1055 Creates a new client state file with all the current server state, as
1056 well as some pre-set client state.
1057
1058 @returns The path of the file the state was written into.
1059 """
1060 # initialize the sysinfo state
1061 self._state.set('client', 'sysinfo', self.sysinfo.serialize())
1062
1063 # dump the state out to a tempfile
1064 fd, file_path = tempfile.mkstemp(dir=self.tmpdir)
1065 os.close(fd)
mbligha2c99492010-01-27 22:59:50 +00001066
1067 # write_to_file doesn't need locking, we exclusively own file_path
mblighfc3da5b2010-01-06 18:37:22 +00001068 self._state.write_to_file(file_path)
1069 return file_path
1070
1071
1072 def postprocess_client_state(self, state_path):
1073 """
1074 Update the state of this job with the state from a client job.
1075
1076 Updates the state of the server side of a job with the final state
1077 of a client job that was run. Updates the non-client-specific state,
1078 pulls in some specific bits from the client-specific state, and then
1079 discards the rest. Removes the state file afterwards
1080
1081 @param state_file A path to the state file from the client.
1082 """
1083 # update the on-disk state
mblighfc3da5b2010-01-06 18:37:22 +00001084 try:
jadmanskib6e7bdb2010-04-13 16:00:39 +00001085 self._state.read_from_file(state_path)
mblighfc3da5b2010-01-06 18:37:22 +00001086 os.remove(state_path)
mbligha2c99492010-01-27 22:59:50 +00001087 except OSError, e:
mblighfc3da5b2010-01-06 18:37:22 +00001088 # ignore file-not-found errors
1089 if e.errno != errno.ENOENT:
1090 raise
jadmanskib6e7bdb2010-04-13 16:00:39 +00001091 else:
1092 logging.debug('Client state file %s not found', state_path)
mblighfc3da5b2010-01-06 18:37:22 +00001093
1094 # update the sysinfo state
1095 if self._state.has('client', 'sysinfo'):
1096 self.sysinfo.deserialize(self._state.get('client', 'sysinfo'))
1097
1098 # drop all the client-specific state
1099 self._state.discard_namespace('client')
1100
1101
mbligh0a883702010-04-21 01:58:34 +00001102 def clear_all_known_hosts(self):
1103 """Clears known hosts files for all AbstractSSHHosts."""
1104 for host in self.hosts:
1105 if isinstance(host, abstract_ssh.AbstractSSHHost):
1106 host.clear_known_hosts()
1107
1108
jadmanskif37df842009-02-11 00:03:26 +00001109class warning_manager(object):
1110 """Class for controlling warning logs. Manages the enabling and disabling
1111 of warnings."""
1112 def __init__(self):
1113 # a map of warning types to a list of disabled time intervals
1114 self.disabled_warnings = {}
1115
1116
1117 def is_valid(self, timestamp, warning_type):
1118 """Indicates if a warning (based on the time it occured and its type)
1119 is a valid warning. A warning is considered "invalid" if this type of
1120 warning was marked as "disabled" at the time the warning occured."""
1121 disabled_intervals = self.disabled_warnings.get(warning_type, [])
1122 for start, end in disabled_intervals:
1123 if timestamp >= start and (end is None or timestamp < end):
1124 return False
1125 return True
1126
1127
1128 def disable_warnings(self, warning_type, current_time_func=time.time):
1129 """As of now, disables all further warnings of this type."""
1130 intervals = self.disabled_warnings.setdefault(warning_type, [])
1131 if not intervals or intervals[-1][1] is not None:
jadmanski16a7ff72009-04-01 18:19:53 +00001132 intervals.append((int(current_time_func()), None))
jadmanskif37df842009-02-11 00:03:26 +00001133
1134
1135 def enable_warnings(self, warning_type, current_time_func=time.time):
1136 """As of now, enables all further warnings of this type."""
1137 intervals = self.disabled_warnings.get(warning_type, [])
1138 if intervals and intervals[-1][1] is None:
jadmanski16a7ff72009-04-01 18:19:53 +00001139 intervals[-1] = (intervals[-1][0], int(current_time_func()))
Paul Pendlebury57593562011-06-15 10:45:49 -07001140
1141
1142# load up site-specific code for generating site-specific job data
1143get_site_job_data = utils.import_site_function(__file__,
1144 "autotest_lib.server.site_server_job", "get_site_job_data",
1145 _get_site_job_data_dummy)
1146
1147
1148site_server_job = utils.import_site_class(
1149 __file__, "autotest_lib.server.site_server_job", "site_server_job",
1150 base_server_job)
1151
1152
1153class server_job(site_server_job):
Dale Curtis456d3c12011-07-19 11:42:51 -07001154 pass