blob: fcf7d7736b39e48a6fb5ec8552006dfe1e187b86 [file] [log] [blame]
Paul Pendlebury7c1fdcf2011-05-04 12:39:15 -07001# Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
mbligh57e78662008-06-17 19:53:49 +00004"""
5The main job wrapper for the server side.
6
7This is the core infrastructure. Derived from the client side job.py
8
9Copyright Martin J. Bligh, Andy Whitcroft 2007
10"""
11
Scott Zawalski91493c82013-01-25 16:15:20 -050012import getpass, os, sys, re, tempfile, time, select, platform
mblighfc3da5b2010-01-06 18:37:22 +000013import traceback, shutil, warnings, fcntl, pickle, logging, itertools, errno
showard75cdfee2009-06-10 17:40:41 +000014from autotest_lib.client.bin import sysinfo
mbligh0d0f67d2009-11-06 03:15:03 +000015from autotest_lib.client.common_lib import base_job
Scott Zawalski91493c82013-01-25 16:15:20 -050016from autotest_lib.client.common_lib import error, utils, packages
showard75cdfee2009-06-10 17:40:41 +000017from autotest_lib.client.common_lib import logging_manager
Paul Pendlebury57593562011-06-15 10:45:49 -070018from autotest_lib.server import test, subcommand, profilers
mbligh0a883702010-04-21 01:58:34 +000019from autotest_lib.server.hosts import abstract_ssh
jadmanski10646442008-08-13 14:05:21 +000020from autotest_lib.tko import db as tko_db, status_lib, utils as tko_utils
jadmanski10646442008-08-13 14:05:21 +000021
22
mbligh084bc172008-10-18 14:02:45 +000023def _control_segment_path(name):
24 """Get the pathname of the named control segment file."""
jadmanski10646442008-08-13 14:05:21 +000025 server_dir = os.path.dirname(os.path.abspath(__file__))
mbligh084bc172008-10-18 14:02:45 +000026 return os.path.join(server_dir, "control_segments", name)
jadmanski10646442008-08-13 14:05:21 +000027
28
mbligh084bc172008-10-18 14:02:45 +000029CLIENT_CONTROL_FILENAME = 'control'
30SERVER_CONTROL_FILENAME = 'control.srv'
31MACHINES_FILENAME = '.machines'
jadmanski10646442008-08-13 14:05:21 +000032
mbligh084bc172008-10-18 14:02:45 +000033CLIENT_WRAPPER_CONTROL_FILE = _control_segment_path('client_wrapper')
34CRASHDUMPS_CONTROL_FILE = _control_segment_path('crashdumps')
35CRASHINFO_CONTROL_FILE = _control_segment_path('crashinfo')
mbligh084bc172008-10-18 14:02:45 +000036INSTALL_CONTROL_FILE = _control_segment_path('install')
showard45ae8192008-11-05 19:32:53 +000037CLEANUP_CONTROL_FILE = _control_segment_path('cleanup')
mbligh084bc172008-10-18 14:02:45 +000038VERIFY_CONTROL_FILE = _control_segment_path('verify')
mbligh084bc172008-10-18 14:02:45 +000039REPAIR_CONTROL_FILE = _control_segment_path('repair')
Alex Millercb79ba72013-05-29 14:43:00 -070040PROVISION_CONTROL_FILE = _control_segment_path('provision')
beepscb6f1e22013-06-28 19:14:10 -070041VERIFY_JOB_REPO_URL_CONTROL_FILE = _control_segment_path('verify_job_repo_url')
jadmanski10646442008-08-13 14:05:21 +000042
43
mbligh062ed152009-01-13 00:57:14 +000044# by default provide a stub that generates no site data
45def _get_site_job_data_dummy(job):
46 return {}
47
48
jadmanski2a89dac2010-06-11 14:32:58 +000049class status_indenter(base_job.status_indenter):
50 """Provide a simple integer-backed status indenter."""
51 def __init__(self):
52 self._indent = 0
53
54
55 @property
56 def indent(self):
57 return self._indent
58
59
60 def increment(self):
61 self._indent += 1
62
63
64 def decrement(self):
65 self._indent -= 1
66
67
jadmanski52053632010-06-11 21:08:10 +000068 def get_context(self):
69 """Returns a context object for use by job.get_record_context."""
70 class context(object):
71 def __init__(self, indenter, indent):
72 self._indenter = indenter
73 self._indent = indent
74 def restore(self):
75 self._indenter._indent = self._indent
76 return context(self, self._indent)
77
78
jadmanski2a89dac2010-06-11 14:32:58 +000079class server_job_record_hook(object):
80 """The job.record hook for server job. Used to inject WARN messages from
81 the console or vlm whenever new logs are written, and to echo any logs
82 to INFO level logging. Implemented as a class so that it can use state to
83 block recursive calls, so that the hook can call job.record itself to
84 log WARN messages.
85
86 Depends on job._read_warnings and job._logger.
87 """
88 def __init__(self, job):
89 self._job = job
90 self._being_called = False
91
92
93 def __call__(self, entry):
94 """A wrapper around the 'real' record hook, the _hook method, which
95 prevents recursion. This isn't making any effort to be threadsafe,
96 the intent is to outright block infinite recursion via a
97 job.record->_hook->job.record->_hook->job.record... chain."""
98 if self._being_called:
99 return
100 self._being_called = True
101 try:
102 self._hook(self._job, entry)
103 finally:
104 self._being_called = False
105
106
107 @staticmethod
108 def _hook(job, entry):
109 """The core hook, which can safely call job.record."""
110 entries = []
111 # poll all our warning loggers for new warnings
112 for timestamp, msg in job._read_warnings():
113 warning_entry = base_job.status_log_entry(
114 'WARN', None, None, msg, {}, timestamp=timestamp)
115 entries.append(warning_entry)
116 job.record_entry(warning_entry)
117 # echo rendered versions of all the status logs to info
118 entries.append(entry)
119 for entry in entries:
120 rendered_entry = job._logger.render_entry(entry)
121 logging.info(rendered_entry)
jadmanskie29d0e42010-06-17 16:06:52 +0000122 job._parse_status(rendered_entry)
jadmanski2a89dac2010-06-11 14:32:58 +0000123
124
mbligh0d0f67d2009-11-06 03:15:03 +0000125class base_server_job(base_job.base_job):
126 """The server-side concrete implementation of base_job.
jadmanski10646442008-08-13 14:05:21 +0000127
mbligh0d0f67d2009-11-06 03:15:03 +0000128 Optional properties provided by this implementation:
129 serverdir
130 conmuxdir
131
132 num_tests_run
133 num_tests_failed
134
135 warning_manager
136 warning_loggers
jadmanski10646442008-08-13 14:05:21 +0000137 """
138
mbligh0d0f67d2009-11-06 03:15:03 +0000139 _STATUS_VERSION = 1
jadmanski10646442008-08-13 14:05:21 +0000140
141 def __init__(self, control, args, resultdir, label, user, machines,
142 client=False, parse_job='',
Scott Zawalski91493c82013-01-25 16:15:20 -0500143 ssh_user='root', ssh_port=22, ssh_pass='', test_retry=0,
mblighe0cbc912010-03-11 18:03:07 +0000144 group_name='', tag='',
145 control_filename=SERVER_CONTROL_FILENAME):
jadmanski10646442008-08-13 14:05:21 +0000146 """
mbligh374f3412009-05-13 21:29:45 +0000147 Create a server side job object.
mblighb5dac432008-11-27 00:38:44 +0000148
mblighe7d9c602009-07-02 19:02:33 +0000149 @param control: The pathname of the control file.
150 @param args: Passed to the control file.
151 @param resultdir: Where to throw the results.
152 @param label: Description of the job.
153 @param user: Username for the job (email address).
154 @param client: True if this is a client-side control file.
155 @param parse_job: string, if supplied it is the job execution tag that
156 the results will be passed through to the TKO parser with.
157 @param ssh_user: The SSH username. [root]
158 @param ssh_port: The SSH port number. [22]
159 @param ssh_pass: The SSH passphrase, if needed.
Scott Zawalski91493c82013-01-25 16:15:20 -0500160 @param test_retry: The number of times to retry a test if the test did
161 not complete successfully.
mblighe7d9c602009-07-02 19:02:33 +0000162 @param group_name: If supplied, this will be written out as
mbligh374f3412009-05-13 21:29:45 +0000163 host_group_name in the keyvals file for the parser.
mblighe7d9c602009-07-02 19:02:33 +0000164 @param tag: The job execution tag from the scheduler. [optional]
mblighe0cbc912010-03-11 18:03:07 +0000165 @param control_filename: The filename where the server control file
166 should be written in the results directory.
jadmanski10646442008-08-13 14:05:21 +0000167 """
Scott Zawalski91493c82013-01-25 16:15:20 -0500168 super(base_server_job, self).__init__(resultdir=resultdir,
169 test_retry=test_retry)
mbligh0d0f67d2009-11-06 03:15:03 +0000170 path = os.path.dirname(__file__)
Scott Zawalski91493c82013-01-25 16:15:20 -0500171 self.test_retry = test_retry
mbligh0d0f67d2009-11-06 03:15:03 +0000172 self.control = control
173 self._uncollected_log_file = os.path.join(self.resultdir,
174 'uncollected_logs')
175 debugdir = os.path.join(self.resultdir, 'debug')
176 if not os.path.exists(debugdir):
177 os.mkdir(debugdir)
178
179 if user:
180 self.user = user
181 else:
182 self.user = getpass.getuser()
183
jadmanski808f4b12010-04-09 22:30:31 +0000184 self.args = args
Peter Mayo7a875762012-06-13 14:38:15 -0400185 self.label = label
jadmanski10646442008-08-13 14:05:21 +0000186 self.machines = machines
mbligh0d0f67d2009-11-06 03:15:03 +0000187 self._client = client
jadmanski10646442008-08-13 14:05:21 +0000188 self.warning_loggers = set()
jadmanskif37df842009-02-11 00:03:26 +0000189 self.warning_manager = warning_manager()
mbligh0d0f67d2009-11-06 03:15:03 +0000190 self._ssh_user = ssh_user
191 self._ssh_port = ssh_port
192 self._ssh_pass = ssh_pass
mblighe7d9c602009-07-02 19:02:33 +0000193 self.tag = tag
mbligh09108442008-10-15 16:27:38 +0000194 self.last_boot_tag = None
jadmanski53aaf382008-11-17 16:22:31 +0000195 self.hosts = set()
mbligh0d0f67d2009-11-06 03:15:03 +0000196 self.drop_caches = False
mblighb5dac432008-11-27 00:38:44 +0000197 self.drop_caches_between_iterations = False
mblighe0cbc912010-03-11 18:03:07 +0000198 self._control_filename = control_filename
jadmanski10646442008-08-13 14:05:21 +0000199
showard75cdfee2009-06-10 17:40:41 +0000200 self.logging = logging_manager.get_logging_manager(
201 manage_stdout_and_stderr=True, redirect_fds=True)
202 subcommand.logging_manager_object = self.logging
jadmanski10646442008-08-13 14:05:21 +0000203
mbligh0d0f67d2009-11-06 03:15:03 +0000204 self.sysinfo = sysinfo.sysinfo(self.resultdir)
jadmanski043e1132008-11-19 17:10:32 +0000205 self.profilers = profilers.profilers(self)
jadmanskic09fc152008-10-15 17:56:59 +0000206
jadmanski10646442008-08-13 14:05:21 +0000207 job_data = {'label' : label, 'user' : user,
208 'hostname' : ','.join(machines),
Eric Li861b2d52011-02-04 14:50:35 -0800209 'drone' : platform.node(),
mbligh0d0f67d2009-11-06 03:15:03 +0000210 'status_version' : str(self._STATUS_VERSION),
showard170873e2009-01-07 00:22:26 +0000211 'job_started' : str(int(time.time()))}
mbligh374f3412009-05-13 21:29:45 +0000212 if group_name:
213 job_data['host_group_name'] = group_name
jadmanski10646442008-08-13 14:05:21 +0000214
mbligh0d0f67d2009-11-06 03:15:03 +0000215 # only write these keyvals out on the first job in a resultdir
216 if 'job_started' not in utils.read_keyval(self.resultdir):
217 job_data.update(get_site_job_data(self))
218 utils.write_keyval(self.resultdir, job_data)
219
220 self._parse_job = parse_job
showardcc929362010-01-25 21:20:41 +0000221 self._using_parser = (self._parse_job and len(machines) <= 1)
mbligh0d0f67d2009-11-06 03:15:03 +0000222 self.pkgmgr = packages.PackageManager(
223 self.autodir, run_function_dargs={'timeout':600})
showard21baa452008-10-21 00:08:39 +0000224 self.num_tests_run = 0
225 self.num_tests_failed = 0
226
jadmanski550fdc22008-11-20 16:32:08 +0000227 self._register_subcommand_hooks()
228
mbligh0d0f67d2009-11-06 03:15:03 +0000229 # these components aren't usable on the server
230 self.bootloader = None
231 self.harness = None
232
jadmanski2a89dac2010-06-11 14:32:58 +0000233 # set up the status logger
jadmanski52053632010-06-11 21:08:10 +0000234 self._indenter = status_indenter()
jadmanski2a89dac2010-06-11 14:32:58 +0000235 self._logger = base_job.status_logger(
jadmanski52053632010-06-11 21:08:10 +0000236 self, self._indenter, 'status.log', 'status.log',
jadmanski2a89dac2010-06-11 14:32:58 +0000237 record_hook=server_job_record_hook(self))
238
mbligh0d0f67d2009-11-06 03:15:03 +0000239
240 @classmethod
241 def _find_base_directories(cls):
242 """
243 Determine locations of autodir, clientdir and serverdir. Assumes
244 that this file is located within serverdir and uses __file__ along
245 with relative paths to resolve the location.
246 """
247 serverdir = os.path.abspath(os.path.dirname(__file__))
248 autodir = os.path.normpath(os.path.join(serverdir, '..'))
249 clientdir = os.path.join(autodir, 'client')
250 return autodir, clientdir, serverdir
251
252
Scott Zawalski91493c82013-01-25 16:15:20 -0500253 def _find_resultdir(self, resultdir, *args, **dargs):
mbligh0d0f67d2009-11-06 03:15:03 +0000254 """
255 Determine the location of resultdir. For server jobs we expect one to
256 always be explicitly passed in to __init__, so just return that.
257 """
258 if resultdir:
259 return os.path.normpath(resultdir)
260 else:
261 return None
262
jadmanski550fdc22008-11-20 16:32:08 +0000263
jadmanski2a89dac2010-06-11 14:32:58 +0000264 def _get_status_logger(self):
265 """Return a reference to the status logger."""
266 return self._logger
267
268
jadmanskie432dd22009-01-30 15:04:51 +0000269 @staticmethod
270 def _load_control_file(path):
271 f = open(path)
272 try:
273 control_file = f.read()
274 finally:
275 f.close()
276 return re.sub('\r', '', control_file)
277
278
jadmanski550fdc22008-11-20 16:32:08 +0000279 def _register_subcommand_hooks(self):
mbligh2b92b862008-11-22 13:25:32 +0000280 """
281 Register some hooks into the subcommand modules that allow us
282 to properly clean up self.hosts created in forked subprocesses.
283 """
jadmanski550fdc22008-11-20 16:32:08 +0000284 def on_fork(cmd):
285 self._existing_hosts_on_fork = set(self.hosts)
286 def on_join(cmd):
287 new_hosts = self.hosts - self._existing_hosts_on_fork
288 for host in new_hosts:
289 host.close()
290 subcommand.subcommand.register_fork_hook(on_fork)
291 subcommand.subcommand.register_join_hook(on_join)
292
jadmanski10646442008-08-13 14:05:21 +0000293
mbligh4608b002010-01-05 18:22:35 +0000294 def init_parser(self):
mbligh2b92b862008-11-22 13:25:32 +0000295 """
mbligh4608b002010-01-05 18:22:35 +0000296 Start the continuous parsing of self.resultdir. This sets up
jadmanski10646442008-08-13 14:05:21 +0000297 the database connection and inserts the basic job object into
mbligh2b92b862008-11-22 13:25:32 +0000298 the database if necessary.
299 """
mbligh4608b002010-01-05 18:22:35 +0000300 if not self._using_parser:
301 return
jadmanski10646442008-08-13 14:05:21 +0000302 # redirect parser debugging to .parse.log
mbligh4608b002010-01-05 18:22:35 +0000303 parse_log = os.path.join(self.resultdir, '.parse.log')
jadmanski10646442008-08-13 14:05:21 +0000304 parse_log = open(parse_log, 'w', 0)
305 tko_utils.redirect_parser_debugging(parse_log)
306 # create a job model object and set up the db
307 self.results_db = tko_db.db(autocommit=True)
mbligh0d0f67d2009-11-06 03:15:03 +0000308 self.parser = status_lib.parser(self._STATUS_VERSION)
mbligh4608b002010-01-05 18:22:35 +0000309 self.job_model = self.parser.make_job(self.resultdir)
jadmanski10646442008-08-13 14:05:21 +0000310 self.parser.start(self.job_model)
311 # check if a job already exists in the db and insert it if
312 # it does not
mbligh0d0f67d2009-11-06 03:15:03 +0000313 job_idx = self.results_db.find_job(self._parse_job)
jadmanski10646442008-08-13 14:05:21 +0000314 if job_idx is None:
mbligh0d0f67d2009-11-06 03:15:03 +0000315 self.results_db.insert_job(self._parse_job, self.job_model)
jadmanski10646442008-08-13 14:05:21 +0000316 else:
mbligh2b92b862008-11-22 13:25:32 +0000317 machine_idx = self.results_db.lookup_machine(self.job_model.machine)
jadmanski10646442008-08-13 14:05:21 +0000318 self.job_model.index = job_idx
319 self.job_model.machine_idx = machine_idx
320
321
322 def cleanup_parser(self):
mbligh2b92b862008-11-22 13:25:32 +0000323 """
324 This should be called after the server job is finished
jadmanski10646442008-08-13 14:05:21 +0000325 to carry out any remaining cleanup (e.g. flushing any
mbligh2b92b862008-11-22 13:25:32 +0000326 remaining test results to the results db)
327 """
mbligh0d0f67d2009-11-06 03:15:03 +0000328 if not self._using_parser:
jadmanski10646442008-08-13 14:05:21 +0000329 return
330 final_tests = self.parser.end()
331 for test in final_tests:
332 self.__insert_test(test)
mbligh0d0f67d2009-11-06 03:15:03 +0000333 self._using_parser = False
jadmanski10646442008-08-13 14:05:21 +0000334
335
336 def verify(self):
337 if not self.machines:
mbligh084bc172008-10-18 14:02:45 +0000338 raise error.AutoservError('No machines specified to verify')
mbligh0fce4112008-11-27 00:37:17 +0000339 if self.resultdir:
340 os.chdir(self.resultdir)
jadmanski10646442008-08-13 14:05:21 +0000341 try:
jadmanskicdd0c402008-09-19 21:21:31 +0000342 namespace = {'machines' : self.machines, 'job' : self,
mbligh0d0f67d2009-11-06 03:15:03 +0000343 'ssh_user' : self._ssh_user,
344 'ssh_port' : self._ssh_port,
345 'ssh_pass' : self._ssh_pass}
mbligh084bc172008-10-18 14:02:45 +0000346 self._execute_code(VERIFY_CONTROL_FILE, namespace, protect=False)
jadmanski10646442008-08-13 14:05:21 +0000347 except Exception, e:
mbligh2b92b862008-11-22 13:25:32 +0000348 msg = ('Verify failed\n' + str(e) + '\n' + traceback.format_exc())
jadmanski10646442008-08-13 14:05:21 +0000349 self.record('ABORT', None, None, msg)
350 raise
351
352
353 def repair(self, host_protection):
354 if not self.machines:
355 raise error.AutoservError('No machines specified to repair')
mbligh0fce4112008-11-27 00:37:17 +0000356 if self.resultdir:
357 os.chdir(self.resultdir)
jadmanski10646442008-08-13 14:05:21 +0000358 namespace = {'machines': self.machines, 'job': self,
mbligh0d0f67d2009-11-06 03:15:03 +0000359 'ssh_user': self._ssh_user, 'ssh_port': self._ssh_port,
360 'ssh_pass': self._ssh_pass,
jadmanski10646442008-08-13 14:05:21 +0000361 'protection_level': host_protection}
mbligh25c0b8c2009-01-24 01:44:17 +0000362
mbligh0931b0a2009-04-08 17:44:48 +0000363 self._execute_code(REPAIR_CONTROL_FILE, namespace, protect=False)
jadmanski10646442008-08-13 14:05:21 +0000364
365
Alex Millercb79ba72013-05-29 14:43:00 -0700366 def provision(self, labels):
367 """
368 Provision all hosts to match |labels|.
369
370 @param labels: A comma seperated string of labels to provision the
371 host to.
372
373 """
374 namespace = {'provision_labels': labels}
375 control = self._load_control_file(PROVISION_CONTROL_FILE)
376 self.run(control=control, namespace=namespace)
377
378
jadmanski10646442008-08-13 14:05:21 +0000379 def precheck(self):
380 """
381 perform any additional checks in derived classes.
382 """
383 pass
384
385
386 def enable_external_logging(self):
mbligh2b92b862008-11-22 13:25:32 +0000387 """
388 Start or restart external logging mechanism.
jadmanski10646442008-08-13 14:05:21 +0000389 """
390 pass
391
392
393 def disable_external_logging(self):
mbligh2b92b862008-11-22 13:25:32 +0000394 """
395 Pause or stop external logging mechanism.
jadmanski10646442008-08-13 14:05:21 +0000396 """
397 pass
398
399
400 def use_external_logging(self):
mbligh2b92b862008-11-22 13:25:32 +0000401 """
402 Return True if external logging should be used.
jadmanski10646442008-08-13 14:05:21 +0000403 """
404 return False
405
406
mbligh415dc212009-06-15 21:53:34 +0000407 def _make_parallel_wrapper(self, function, machines, log):
408 """Wrap function as appropriate for calling by parallel_simple."""
mbligh2b92b862008-11-22 13:25:32 +0000409 is_forking = not (len(machines) == 1 and self.machines == machines)
mbligh0d0f67d2009-11-06 03:15:03 +0000410 if self._parse_job and is_forking and log:
jadmanski10646442008-08-13 14:05:21 +0000411 def wrapper(machine):
mbligh0d0f67d2009-11-06 03:15:03 +0000412 self._parse_job += "/" + machine
413 self._using_parser = True
jadmanski10646442008-08-13 14:05:21 +0000414 self.machines = [machine]
mbligh0d0f67d2009-11-06 03:15:03 +0000415 self.push_execution_context(machine)
jadmanski609a5f42008-08-26 20:52:42 +0000416 os.chdir(self.resultdir)
showard2bab8f42008-11-12 18:15:22 +0000417 utils.write_keyval(self.resultdir, {"hostname": machine})
mbligh4608b002010-01-05 18:22:35 +0000418 self.init_parser()
jadmanski10646442008-08-13 14:05:21 +0000419 result = function(machine)
420 self.cleanup_parser()
421 return result
jadmanski4dd1a002008-09-05 20:27:30 +0000422 elif len(machines) > 1 and log:
jadmanski10646442008-08-13 14:05:21 +0000423 def wrapper(machine):
mbligh0d0f67d2009-11-06 03:15:03 +0000424 self.push_execution_context(machine)
jadmanski609a5f42008-08-26 20:52:42 +0000425 os.chdir(self.resultdir)
mbligh838d82d2009-03-11 17:14:31 +0000426 machine_data = {'hostname' : machine,
mbligh0d0f67d2009-11-06 03:15:03 +0000427 'status_version' : str(self._STATUS_VERSION)}
mbligh838d82d2009-03-11 17:14:31 +0000428 utils.write_keyval(self.resultdir, machine_data)
jadmanski10646442008-08-13 14:05:21 +0000429 result = function(machine)
430 return result
431 else:
432 wrapper = function
mbligh415dc212009-06-15 21:53:34 +0000433 return wrapper
434
435
436 def parallel_simple(self, function, machines, log=True, timeout=None,
437 return_results=False):
438 """
439 Run 'function' using parallel_simple, with an extra wrapper to handle
440 the necessary setup for continuous parsing, if possible. If continuous
441 parsing is already properly initialized then this should just work.
442
443 @param function: A callable to run in parallel given each machine.
444 @param machines: A list of machine names to be passed one per subcommand
445 invocation of function.
446 @param log: If True, output will be written to output in a subdirectory
447 named after each machine.
448 @param timeout: Seconds after which the function call should timeout.
449 @param return_results: If True instead of an AutoServError being raised
450 on any error a list of the results|exceptions from the function
451 called on each arg is returned. [default: False]
452
453 @raises error.AutotestError: If any of the functions failed.
454 """
455 wrapper = self._make_parallel_wrapper(function, machines, log)
456 return subcommand.parallel_simple(wrapper, machines,
457 log=log, timeout=timeout,
458 return_results=return_results)
459
460
461 def parallel_on_machines(self, function, machines, timeout=None):
462 """
showardcd5fac42009-07-06 20:19:43 +0000463 @param function: Called in parallel with one machine as its argument.
mbligh415dc212009-06-15 21:53:34 +0000464 @param machines: A list of machines to call function(machine) on.
465 @param timeout: Seconds after which the function call should timeout.
466
467 @returns A list of machines on which function(machine) returned
468 without raising an exception.
469 """
showardcd5fac42009-07-06 20:19:43 +0000470 results = self.parallel_simple(function, machines, timeout=timeout,
mbligh415dc212009-06-15 21:53:34 +0000471 return_results=True)
472 success_machines = []
473 for result, machine in itertools.izip(results, machines):
474 if not isinstance(result, Exception):
475 success_machines.append(machine)
476 return success_machines
jadmanski10646442008-08-13 14:05:21 +0000477
478
mbligh0d0f67d2009-11-06 03:15:03 +0000479 _USE_TEMP_DIR = object()
mbligh2b92b862008-11-22 13:25:32 +0000480 def run(self, cleanup=False, install_before=False, install_after=False,
jadmanskie432dd22009-01-30 15:04:51 +0000481 collect_crashdumps=True, namespace={}, control=None,
beepscb6f1e22013-06-28 19:14:10 -0700482 control_file_dir=None, verify_job_repo_url=False,
Christopher Wileyf594c5e2013-07-03 18:25:30 -0700483 only_collect_crashinfo=False, skip_crash_collection=False):
jadmanskifb9c0fa2009-04-29 17:39:16 +0000484 # for a normal job, make sure the uncollected logs file exists
485 # for a crashinfo-only run it should already exist, bail out otherwise
jadmanski648c39f2010-03-19 17:38:01 +0000486 created_uncollected_logs = False
mbligh0d0f67d2009-11-06 03:15:03 +0000487 if self.resultdir and not os.path.exists(self._uncollected_log_file):
jadmanskifb9c0fa2009-04-29 17:39:16 +0000488 if only_collect_crashinfo:
489 # if this is a crashinfo-only run, and there were no existing
490 # uncollected logs, just bail out early
491 logging.info("No existing uncollected logs, "
492 "skipping crashinfo collection")
493 return
494 else:
mbligh0d0f67d2009-11-06 03:15:03 +0000495 log_file = open(self._uncollected_log_file, "w")
jadmanskifb9c0fa2009-04-29 17:39:16 +0000496 pickle.dump([], log_file)
497 log_file.close()
jadmanski648c39f2010-03-19 17:38:01 +0000498 created_uncollected_logs = True
jadmanskifb9c0fa2009-04-29 17:39:16 +0000499
jadmanski10646442008-08-13 14:05:21 +0000500 # use a copy so changes don't affect the original dictionary
501 namespace = namespace.copy()
502 machines = self.machines
jadmanskie432dd22009-01-30 15:04:51 +0000503 if control is None:
jadmanski02a3ba22009-11-13 20:47:27 +0000504 if self.control is None:
505 control = ''
506 else:
507 control = self._load_control_file(self.control)
jadmanskie432dd22009-01-30 15:04:51 +0000508 if control_file_dir is None:
509 control_file_dir = self.resultdir
jadmanski10646442008-08-13 14:05:21 +0000510
511 self.aborted = False
512 namespace['machines'] = machines
jadmanski808f4b12010-04-09 22:30:31 +0000513 namespace['args'] = self.args
jadmanski10646442008-08-13 14:05:21 +0000514 namespace['job'] = self
mbligh0d0f67d2009-11-06 03:15:03 +0000515 namespace['ssh_user'] = self._ssh_user
516 namespace['ssh_port'] = self._ssh_port
517 namespace['ssh_pass'] = self._ssh_pass
jadmanski10646442008-08-13 14:05:21 +0000518 test_start_time = int(time.time())
519
mbligh80e1eba2008-11-19 00:26:18 +0000520 if self.resultdir:
521 os.chdir(self.resultdir)
jadmanski779bd292009-03-19 17:33:33 +0000522 # touch status.log so that the parser knows a job is running here
jadmanski382303a2009-04-21 19:53:39 +0000523 open(self.get_status_log_path(), 'a').close()
mbligh80e1eba2008-11-19 00:26:18 +0000524 self.enable_external_logging()
jadmanskie432dd22009-01-30 15:04:51 +0000525
jadmanskicdd0c402008-09-19 21:21:31 +0000526 collect_crashinfo = True
mblighaebe3b62008-12-22 14:45:40 +0000527 temp_control_file_dir = None
jadmanski10646442008-08-13 14:05:21 +0000528 try:
showardcf8d4922009-10-14 16:08:39 +0000529 try:
530 if install_before and machines:
531 self._execute_code(INSTALL_CONTROL_FILE, namespace)
jadmanskie432dd22009-01-30 15:04:51 +0000532
showardcf8d4922009-10-14 16:08:39 +0000533 if only_collect_crashinfo:
534 return
535
beepscb6f1e22013-06-28 19:14:10 -0700536 # If the verify_job_repo_url option is set but we're unable
537 # to actually verify that the job_repo_url contains the autotest
538 # package, this job will fail.
539 if verify_job_repo_url:
540 self._execute_code(VERIFY_JOB_REPO_URL_CONTROL_FILE,
541 namespace)
542 else:
543 logging.warning('Not checking if job_repo_url contains '
544 'autotest packages on %s', machines)
545
jadmanskidef0c3c2009-03-25 20:07:10 +0000546 # determine the dir to write the control files to
547 cfd_specified = (control_file_dir
mbligh0d0f67d2009-11-06 03:15:03 +0000548 and control_file_dir is not self._USE_TEMP_DIR)
jadmanskidef0c3c2009-03-25 20:07:10 +0000549 if cfd_specified:
550 temp_control_file_dir = None
551 else:
552 temp_control_file_dir = tempfile.mkdtemp(
553 suffix='temp_control_file_dir')
554 control_file_dir = temp_control_file_dir
555 server_control_file = os.path.join(control_file_dir,
mblighe0cbc912010-03-11 18:03:07 +0000556 self._control_filename)
jadmanskidef0c3c2009-03-25 20:07:10 +0000557 client_control_file = os.path.join(control_file_dir,
558 CLIENT_CONTROL_FILENAME)
mbligh0d0f67d2009-11-06 03:15:03 +0000559 if self._client:
jadmanskidef0c3c2009-03-25 20:07:10 +0000560 namespace['control'] = control
561 utils.open_write_close(client_control_file, control)
mblighfeac0102009-04-28 18:31:12 +0000562 shutil.copyfile(CLIENT_WRAPPER_CONTROL_FILE,
563 server_control_file)
jadmanskidef0c3c2009-03-25 20:07:10 +0000564 else:
565 utils.open_write_close(server_control_file, control)
mbligh26f0d882009-06-22 18:30:01 +0000566 logging.info("Processing control file")
jadmanskidef0c3c2009-03-25 20:07:10 +0000567 self._execute_code(server_control_file, namespace)
mbligh26f0d882009-06-22 18:30:01 +0000568 logging.info("Finished processing control file")
jadmanski10646442008-08-13 14:05:21 +0000569
jadmanskidef0c3c2009-03-25 20:07:10 +0000570 # no error occured, so we don't need to collect crashinfo
571 collect_crashinfo = False
Eric Li6f27d4f2010-09-29 10:55:17 -0700572 except Exception, e:
showardcf8d4922009-10-14 16:08:39 +0000573 try:
574 logging.exception(
575 'Exception escaped control file, job aborting:')
Eric Li6f27d4f2010-09-29 10:55:17 -0700576 self.record('INFO', None, None, str(e),
577 {'job_abort_reason': str(e)})
showardcf8d4922009-10-14 16:08:39 +0000578 except:
579 pass # don't let logging exceptions here interfere
580 raise
jadmanski10646442008-08-13 14:05:21 +0000581 finally:
mblighaebe3b62008-12-22 14:45:40 +0000582 if temp_control_file_dir:
jadmanskie432dd22009-01-30 15:04:51 +0000583 # Clean up temp directory used for copies of the control files
mblighaebe3b62008-12-22 14:45:40 +0000584 try:
585 shutil.rmtree(temp_control_file_dir)
586 except Exception, e:
mblighe7d9c602009-07-02 19:02:33 +0000587 logging.warn('Could not remove temp directory %s: %s',
588 temp_control_file_dir, e)
jadmanskie432dd22009-01-30 15:04:51 +0000589
jadmanskicdd0c402008-09-19 21:21:31 +0000590 if machines and (collect_crashdumps or collect_crashinfo):
jadmanski10646442008-08-13 14:05:21 +0000591 namespace['test_start_time'] = test_start_time
Christopher Wileyf594c5e2013-07-03 18:25:30 -0700592 if skip_crash_collection:
593 logging.info('Skipping crash dump/info collection '
594 'as requested.')
595 elif collect_crashinfo:
mbligh084bc172008-10-18 14:02:45 +0000596 # includes crashdumps
597 self._execute_code(CRASHINFO_CONTROL_FILE, namespace)
jadmanskicdd0c402008-09-19 21:21:31 +0000598 else:
mbligh084bc172008-10-18 14:02:45 +0000599 self._execute_code(CRASHDUMPS_CONTROL_FILE, namespace)
jadmanski10646442008-08-13 14:05:21 +0000600 self.disable_external_logging()
showard45ae8192008-11-05 19:32:53 +0000601 if cleanup and machines:
602 self._execute_code(CLEANUP_CONTROL_FILE, namespace)
Chris Sosaf4d43ff2012-10-30 11:21:05 -0700603 if self._uncollected_log_file and created_uncollected_logs:
604 os.remove(self._uncollected_log_file)
jadmanski10646442008-08-13 14:05:21 +0000605 if install_after and machines:
mbligh084bc172008-10-18 14:02:45 +0000606 self._execute_code(INSTALL_CONTROL_FILE, namespace)
jadmanski10646442008-08-13 14:05:21 +0000607
608
609 def run_test(self, url, *args, **dargs):
mbligh2b92b862008-11-22 13:25:32 +0000610 """
611 Summon a test object and run it.
jadmanski10646442008-08-13 14:05:21 +0000612
613 tag
614 tag to add to testname
615 url
616 url of the test to run
617 """
mblighfc3da5b2010-01-06 18:37:22 +0000618 group, testname = self.pkgmgr.get_package_name(url, 'test')
619 testname, subdir, tag = self._build_tagged_test_name(testname, dargs)
620 outputdir = self._make_test_outputdir(subdir)
jadmanski10646442008-08-13 14:05:21 +0000621
622 def group_func():
623 try:
624 test.runtest(self, url, tag, args, dargs)
625 except error.TestBaseException, e:
626 self.record(e.exit_status, subdir, testname, str(e))
627 raise
628 except Exception, e:
629 info = str(e) + "\n" + traceback.format_exc()
630 self.record('FAIL', subdir, testname, info)
631 raise
632 else:
mbligh2b92b862008-11-22 13:25:32 +0000633 self.record('GOOD', subdir, testname, 'completed successfully')
jadmanskide292df2008-08-26 20:51:14 +0000634
635 result, exc_info = self._run_group(testname, subdir, group_func)
636 if exc_info and isinstance(exc_info[1], error.TestBaseException):
637 return False
638 elif exc_info:
639 raise exc_info[0], exc_info[1], exc_info[2]
640 else:
641 return True
jadmanski10646442008-08-13 14:05:21 +0000642
643
644 def _run_group(self, name, subdir, function, *args, **dargs):
645 """\
646 Underlying method for running something inside of a group.
647 """
jadmanskide292df2008-08-26 20:51:14 +0000648 result, exc_info = None, None
jadmanski10646442008-08-13 14:05:21 +0000649 try:
650 self.record('START', subdir, name)
jadmanski52053632010-06-11 21:08:10 +0000651 result = function(*args, **dargs)
jadmanski10646442008-08-13 14:05:21 +0000652 except error.TestBaseException, e:
jadmanskib88d6dc2009-01-10 00:33:18 +0000653 self.record("END %s" % e.exit_status, subdir, name)
jadmanskide292df2008-08-26 20:51:14 +0000654 exc_info = sys.exc_info()
jadmanski10646442008-08-13 14:05:21 +0000655 except Exception, e:
656 err_msg = str(e) + '\n'
657 err_msg += traceback.format_exc()
658 self.record('END ABORT', subdir, name, err_msg)
659 raise error.JobError(name + ' failed\n' + traceback.format_exc())
660 else:
661 self.record('END GOOD', subdir, name)
662
jadmanskide292df2008-08-26 20:51:14 +0000663 return result, exc_info
jadmanski10646442008-08-13 14:05:21 +0000664
665
666 def run_group(self, function, *args, **dargs):
667 """\
668 function:
669 subroutine to run
670 *args:
671 arguments for the function
672 """
673
674 name = function.__name__
675
676 # Allow the tag for the group to be specified.
677 tag = dargs.pop('tag', None)
678 if tag:
679 name = tag
680
jadmanskide292df2008-08-26 20:51:14 +0000681 return self._run_group(name, None, function, *args, **dargs)[0]
jadmanski10646442008-08-13 14:05:21 +0000682
683
684 def run_reboot(self, reboot_func, get_kernel_func):
685 """\
686 A specialization of run_group meant specifically for handling
687 a reboot. Includes support for capturing the kernel version
688 after the reboot.
689
690 reboot_func: a function that carries out the reboot
691
692 get_kernel_func: a function that returns a string
693 representing the kernel version.
694 """
jadmanski10646442008-08-13 14:05:21 +0000695 try:
696 self.record('START', None, 'reboot')
jadmanski10646442008-08-13 14:05:21 +0000697 reboot_func()
698 except Exception, e:
jadmanski10646442008-08-13 14:05:21 +0000699 err_msg = str(e) + '\n' + traceback.format_exc()
700 self.record('END FAIL', None, 'reboot', err_msg)
jadmanski4b51d542009-04-08 14:17:16 +0000701 raise
jadmanski10646442008-08-13 14:05:21 +0000702 else:
703 kernel = get_kernel_func()
jadmanski10646442008-08-13 14:05:21 +0000704 self.record('END GOOD', None, 'reboot',
Dale Curtis74a314b2011-06-23 14:55:46 -0700705 optional_fields={"kernel": kernel})
jadmanski10646442008-08-13 14:05:21 +0000706
707
jadmanskie432dd22009-01-30 15:04:51 +0000708 def run_control(self, path):
709 """Execute a control file found at path (relative to the autotest
710 path). Intended for executing a control file within a control file,
711 not for running the top-level job control file."""
712 path = os.path.join(self.autodir, path)
713 control_file = self._load_control_file(path)
mbligh0d0f67d2009-11-06 03:15:03 +0000714 self.run(control=control_file, control_file_dir=self._USE_TEMP_DIR)
jadmanskie432dd22009-01-30 15:04:51 +0000715
716
jadmanskic09fc152008-10-15 17:56:59 +0000717 def add_sysinfo_command(self, command, logfile=None, on_every_test=False):
mbligh4395bbd2009-03-25 19:34:17 +0000718 self._add_sysinfo_loggable(sysinfo.command(command, logf=logfile),
jadmanskic09fc152008-10-15 17:56:59 +0000719 on_every_test)
720
721
722 def add_sysinfo_logfile(self, file, on_every_test=False):
723 self._add_sysinfo_loggable(sysinfo.logfile(file), on_every_test)
724
725
726 def _add_sysinfo_loggable(self, loggable, on_every_test):
727 if on_every_test:
728 self.sysinfo.test_loggables.add(loggable)
729 else:
730 self.sysinfo.boot_loggables.add(loggable)
731
732
jadmanski10646442008-08-13 14:05:21 +0000733 def _read_warnings(self):
jadmanskif37df842009-02-11 00:03:26 +0000734 """Poll all the warning loggers and extract any new warnings that have
735 been logged. If the warnings belong to a category that is currently
736 disabled, this method will discard them and they will no longer be
737 retrievable.
738
739 Returns a list of (timestamp, message) tuples, where timestamp is an
740 integer epoch timestamp."""
jadmanski10646442008-08-13 14:05:21 +0000741 warnings = []
742 while True:
743 # pull in a line of output from every logger that has
744 # output ready to be read
mbligh2b92b862008-11-22 13:25:32 +0000745 loggers, _, _ = select.select(self.warning_loggers, [], [], 0)
jadmanski10646442008-08-13 14:05:21 +0000746 closed_loggers = set()
747 for logger in loggers:
748 line = logger.readline()
749 # record any broken pipes (aka line == empty)
750 if len(line) == 0:
751 closed_loggers.add(logger)
752 continue
jadmanskif37df842009-02-11 00:03:26 +0000753 # parse out the warning
754 timestamp, msgtype, msg = line.split('\t', 2)
755 timestamp = int(timestamp)
756 # if the warning is valid, add it to the results
757 if self.warning_manager.is_valid(timestamp, msgtype):
758 warnings.append((timestamp, msg.strip()))
jadmanski10646442008-08-13 14:05:21 +0000759
760 # stop listening to loggers that are closed
761 self.warning_loggers -= closed_loggers
762
763 # stop if none of the loggers have any output left
764 if not loggers:
765 break
766
767 # sort into timestamp order
768 warnings.sort()
769 return warnings
770
771
showardcc929362010-01-25 21:20:41 +0000772 def _unique_subdirectory(self, base_subdirectory_name):
773 """Compute a unique results subdirectory based on the given name.
774
775 Appends base_subdirectory_name with a number as necessary to find a
776 directory name that doesn't already exist.
777 """
778 subdirectory = base_subdirectory_name
779 counter = 1
780 while os.path.exists(os.path.join(self.resultdir, subdirectory)):
781 subdirectory = base_subdirectory_name + '.' + str(counter)
782 counter += 1
783 return subdirectory
784
785
jadmanski52053632010-06-11 21:08:10 +0000786 def get_record_context(self):
787 """Returns an object representing the current job.record context.
788
789 The object returned is an opaque object with a 0-arg restore method
790 which can be called to restore the job.record context (i.e. indentation)
791 to the current level. The intention is that it should be used when
792 something external which generate job.record calls (e.g. an autotest
793 client) can fail catastrophically and the server job record state
794 needs to be reset to its original "known good" state.
795
796 @return: A context object with a 0-arg restore() method."""
797 return self._indenter.get_context()
798
799
showardcc929362010-01-25 21:20:41 +0000800 def record_summary(self, status_code, test_name, reason='', attributes=None,
801 distinguishing_attributes=(), child_test_ids=None):
802 """Record a summary test result.
803
804 @param status_code: status code string, see
805 common_lib.log.is_valid_status()
806 @param test_name: name of the test
807 @param reason: (optional) string providing detailed reason for test
808 outcome
809 @param attributes: (optional) dict of string keyvals to associate with
810 this result
811 @param distinguishing_attributes: (optional) list of attribute names
812 that should be used to distinguish identically-named test
813 results. These attributes should be present in the attributes
814 parameter. This is used to generate user-friendly subdirectory
815 names.
816 @param child_test_ids: (optional) list of test indices for test results
817 used in generating this result.
818 """
819 subdirectory_name_parts = [test_name]
820 for attribute in distinguishing_attributes:
821 assert attributes
822 assert attribute in attributes, '%s not in %s' % (attribute,
823 attributes)
824 subdirectory_name_parts.append(attributes[attribute])
825 base_subdirectory_name = '.'.join(subdirectory_name_parts)
826
827 subdirectory = self._unique_subdirectory(base_subdirectory_name)
828 subdirectory_path = os.path.join(self.resultdir, subdirectory)
829 os.mkdir(subdirectory_path)
830
831 self.record(status_code, subdirectory, test_name,
832 status=reason, optional_fields={'is_summary': True})
833
834 if attributes:
835 utils.write_keyval(subdirectory_path, attributes)
836
837 if child_test_ids:
838 ids_string = ','.join(str(test_id) for test_id in child_test_ids)
839 summary_data = {'child_test_ids': ids_string}
840 utils.write_keyval(os.path.join(subdirectory_path, 'summary_data'),
841 summary_data)
842
843
jadmanski16a7ff72009-04-01 18:19:53 +0000844 def disable_warnings(self, warning_type):
jadmanskif37df842009-02-11 00:03:26 +0000845 self.warning_manager.disable_warnings(warning_type)
jadmanski16a7ff72009-04-01 18:19:53 +0000846 self.record("INFO", None, None,
847 "disabling %s warnings" % warning_type,
848 {"warnings.disable": warning_type})
jadmanskif37df842009-02-11 00:03:26 +0000849
850
jadmanski16a7ff72009-04-01 18:19:53 +0000851 def enable_warnings(self, warning_type):
jadmanskif37df842009-02-11 00:03:26 +0000852 self.warning_manager.enable_warnings(warning_type)
jadmanski16a7ff72009-04-01 18:19:53 +0000853 self.record("INFO", None, None,
854 "enabling %s warnings" % warning_type,
855 {"warnings.enable": warning_type})
jadmanskif37df842009-02-11 00:03:26 +0000856
857
jadmanski779bd292009-03-19 17:33:33 +0000858 def get_status_log_path(self, subdir=None):
859 """Return the path to the job status log.
860
861 @param subdir - Optional paramter indicating that you want the path
862 to a subdirectory status log.
863
864 @returns The path where the status log should be.
865 """
mbligh210bae62009-04-01 18:33:13 +0000866 if self.resultdir:
867 if subdir:
868 return os.path.join(self.resultdir, subdir, "status.log")
869 else:
870 return os.path.join(self.resultdir, "status.log")
jadmanski779bd292009-03-19 17:33:33 +0000871 else:
mbligh210bae62009-04-01 18:33:13 +0000872 return None
jadmanski779bd292009-03-19 17:33:33 +0000873
874
jadmanski6bb32d72009-03-19 20:25:24 +0000875 def _update_uncollected_logs_list(self, update_func):
876 """Updates the uncollected logs list in a multi-process safe manner.
877
878 @param update_func - a function that updates the list of uncollected
879 logs. Should take one parameter, the list to be updated.
880 """
mbligh0d0f67d2009-11-06 03:15:03 +0000881 if self._uncollected_log_file:
882 log_file = open(self._uncollected_log_file, "r+")
mbligha788dc42009-03-26 21:10:16 +0000883 fcntl.flock(log_file, fcntl.LOCK_EX)
jadmanski6bb32d72009-03-19 20:25:24 +0000884 try:
885 uncollected_logs = pickle.load(log_file)
886 update_func(uncollected_logs)
887 log_file.seek(0)
888 log_file.truncate()
889 pickle.dump(uncollected_logs, log_file)
jadmanski3bff9092009-04-22 18:09:47 +0000890 log_file.flush()
jadmanski6bb32d72009-03-19 20:25:24 +0000891 finally:
892 fcntl.flock(log_file, fcntl.LOCK_UN)
893 log_file.close()
894
895
896 def add_client_log(self, hostname, remote_path, local_path):
897 """Adds a new set of client logs to the list of uncollected logs,
898 to allow for future log recovery.
899
900 @param host - the hostname of the machine holding the logs
901 @param remote_path - the directory on the remote machine holding logs
902 @param local_path - the local directory to copy the logs into
903 """
904 def update_func(logs_list):
905 logs_list.append((hostname, remote_path, local_path))
906 self._update_uncollected_logs_list(update_func)
907
908
909 def remove_client_log(self, hostname, remote_path, local_path):
910 """Removes a set of client logs from the list of uncollected logs,
911 to allow for future log recovery.
912
913 @param host - the hostname of the machine holding the logs
914 @param remote_path - the directory on the remote machine holding logs
915 @param local_path - the local directory to copy the logs into
916 """
917 def update_func(logs_list):
918 logs_list.remove((hostname, remote_path, local_path))
919 self._update_uncollected_logs_list(update_func)
920
921
mbligh0d0f67d2009-11-06 03:15:03 +0000922 def get_client_logs(self):
923 """Retrieves the list of uncollected logs, if it exists.
924
925 @returns A list of (host, remote_path, local_path) tuples. Returns
926 an empty list if no uncollected logs file exists.
927 """
928 log_exists = (self._uncollected_log_file and
929 os.path.exists(self._uncollected_log_file))
930 if log_exists:
931 return pickle.load(open(self._uncollected_log_file))
932 else:
933 return []
934
935
mbligh084bc172008-10-18 14:02:45 +0000936 def _fill_server_control_namespace(self, namespace, protect=True):
mbligh2b92b862008-11-22 13:25:32 +0000937 """
938 Prepare a namespace to be used when executing server control files.
mbligh084bc172008-10-18 14:02:45 +0000939
940 This sets up the control file API by importing modules and making them
941 available under the appropriate names within namespace.
942
943 For use by _execute_code().
944
945 Args:
946 namespace: The namespace dictionary to fill in.
947 protect: Boolean. If True (the default) any operation that would
948 clobber an existing entry in namespace will cause an error.
949 Raises:
950 error.AutoservError: When a name would be clobbered by import.
951 """
952 def _import_names(module_name, names=()):
mbligh2b92b862008-11-22 13:25:32 +0000953 """
954 Import a module and assign named attributes into namespace.
mbligh084bc172008-10-18 14:02:45 +0000955
956 Args:
957 module_name: The string module name.
958 names: A limiting list of names to import from module_name. If
959 empty (the default), all names are imported from the module
960 similar to a "from foo.bar import *" statement.
961 Raises:
962 error.AutoservError: When a name being imported would clobber
963 a name already in namespace.
964 """
965 module = __import__(module_name, {}, {}, names)
966
967 # No names supplied? Import * from the lowest level module.
968 # (Ugh, why do I have to implement this part myself?)
969 if not names:
970 for submodule_name in module_name.split('.')[1:]:
971 module = getattr(module, submodule_name)
972 if hasattr(module, '__all__'):
973 names = getattr(module, '__all__')
974 else:
975 names = dir(module)
976
977 # Install each name into namespace, checking to make sure it
978 # doesn't override anything that already exists.
979 for name in names:
980 # Check for conflicts to help prevent future problems.
981 if name in namespace and protect:
982 if namespace[name] is not getattr(module, name):
983 raise error.AutoservError('importing name '
984 '%s from %s %r would override %r' %
985 (name, module_name, getattr(module, name),
986 namespace[name]))
987 else:
988 # Encourage cleanliness and the use of __all__ for a
989 # more concrete API with less surprises on '*' imports.
990 warnings.warn('%s (%r) being imported from %s for use '
991 'in server control files is not the '
992 'first occurrance of that import.' %
993 (name, namespace[name], module_name))
994
995 namespace[name] = getattr(module, name)
996
997
998 # This is the equivalent of prepending a bunch of import statements to
999 # the front of the control script.
mbligha2b07dd2009-06-22 18:26:13 +00001000 namespace.update(os=os, sys=sys, logging=logging)
mbligh084bc172008-10-18 14:02:45 +00001001 _import_names('autotest_lib.server',
1002 ('hosts', 'autotest', 'kvm', 'git', 'standalone_profiler',
1003 'source_kernel', 'rpm_kernel', 'deb_kernel', 'git_kernel'))
1004 _import_names('autotest_lib.server.subcommand',
1005 ('parallel', 'parallel_simple', 'subcommand'))
1006 _import_names('autotest_lib.server.utils',
1007 ('run', 'get_tmp_dir', 'sh_escape', 'parse_machine'))
1008 _import_names('autotest_lib.client.common_lib.error')
1009 _import_names('autotest_lib.client.common_lib.barrier', ('barrier',))
1010
1011 # Inject ourself as the job object into other classes within the API.
1012 # (Yuck, this injection is a gross thing be part of a public API. -gps)
1013 #
1014 # XXX Base & SiteAutotest do not appear to use .job. Who does?
1015 namespace['autotest'].Autotest.job = self
1016 # server.hosts.base_classes.Host uses .job.
1017 namespace['hosts'].Host.job = self
Eric Li10222b82010-11-24 09:33:15 -08001018 namespace['hosts'].factory.ssh_user = self._ssh_user
1019 namespace['hosts'].factory.ssh_port = self._ssh_port
1020 namespace['hosts'].factory.ssh_pass = self._ssh_pass
mbligh084bc172008-10-18 14:02:45 +00001021
1022
1023 def _execute_code(self, code_file, namespace, protect=True):
mbligh2b92b862008-11-22 13:25:32 +00001024 """
1025 Execute code using a copy of namespace as a server control script.
mbligh084bc172008-10-18 14:02:45 +00001026
1027 Unless protect_namespace is explicitly set to False, the dict will not
1028 be modified.
1029
1030 Args:
1031 code_file: The filename of the control file to execute.
1032 namespace: A dict containing names to make available during execution.
1033 protect: Boolean. If True (the default) a copy of the namespace dict
1034 is used during execution to prevent the code from modifying its
1035 contents outside of this function. If False the raw dict is
1036 passed in and modifications will be allowed.
1037 """
1038 if protect:
1039 namespace = namespace.copy()
1040 self._fill_server_control_namespace(namespace, protect=protect)
1041 # TODO: Simplify and get rid of the special cases for only 1 machine.
showard3e66e8c2008-10-27 19:20:51 +00001042 if len(self.machines) > 1:
mbligh084bc172008-10-18 14:02:45 +00001043 machines_text = '\n'.join(self.machines) + '\n'
1044 # Only rewrite the file if it does not match our machine list.
1045 try:
1046 machines_f = open(MACHINES_FILENAME, 'r')
1047 existing_machines_text = machines_f.read()
1048 machines_f.close()
1049 except EnvironmentError:
1050 existing_machines_text = None
1051 if machines_text != existing_machines_text:
1052 utils.open_write_close(MACHINES_FILENAME, machines_text)
1053 execfile(code_file, namespace, namespace)
jadmanski10646442008-08-13 14:05:21 +00001054
1055
jadmanskie29d0e42010-06-17 16:06:52 +00001056 def _parse_status(self, new_line):
mbligh0d0f67d2009-11-06 03:15:03 +00001057 if not self._using_parser:
jadmanski10646442008-08-13 14:05:21 +00001058 return
jadmanskie29d0e42010-06-17 16:06:52 +00001059 new_tests = self.parser.process_lines([new_line])
jadmanski10646442008-08-13 14:05:21 +00001060 for test in new_tests:
1061 self.__insert_test(test)
1062
1063
1064 def __insert_test(self, test):
mbligh2b92b862008-11-22 13:25:32 +00001065 """
1066 An internal method to insert a new test result into the
jadmanski10646442008-08-13 14:05:21 +00001067 database. This method will not raise an exception, even if an
1068 error occurs during the insert, to avoid failing a test
1069 simply because of unexpected database issues."""
showard21baa452008-10-21 00:08:39 +00001070 self.num_tests_run += 1
1071 if status_lib.is_worse_than_or_equal_to(test.status, 'FAIL'):
1072 self.num_tests_failed += 1
jadmanski10646442008-08-13 14:05:21 +00001073 try:
1074 self.results_db.insert_test(self.job_model, test)
1075 except Exception:
1076 msg = ("WARNING: An unexpected error occured while "
1077 "inserting test results into the database. "
1078 "Ignoring error.\n" + traceback.format_exc())
1079 print >> sys.stderr, msg
1080
mblighcaa62c22008-04-07 21:51:17 +00001081
mblighfc3da5b2010-01-06 18:37:22 +00001082 def preprocess_client_state(self):
1083 """
1084 Produce a state file for initializing the state of a client job.
1085
1086 Creates a new client state file with all the current server state, as
1087 well as some pre-set client state.
1088
1089 @returns The path of the file the state was written into.
1090 """
1091 # initialize the sysinfo state
1092 self._state.set('client', 'sysinfo', self.sysinfo.serialize())
1093
1094 # dump the state out to a tempfile
1095 fd, file_path = tempfile.mkstemp(dir=self.tmpdir)
1096 os.close(fd)
mbligha2c99492010-01-27 22:59:50 +00001097
1098 # write_to_file doesn't need locking, we exclusively own file_path
mblighfc3da5b2010-01-06 18:37:22 +00001099 self._state.write_to_file(file_path)
1100 return file_path
1101
1102
1103 def postprocess_client_state(self, state_path):
1104 """
1105 Update the state of this job with the state from a client job.
1106
1107 Updates the state of the server side of a job with the final state
1108 of a client job that was run. Updates the non-client-specific state,
1109 pulls in some specific bits from the client-specific state, and then
1110 discards the rest. Removes the state file afterwards
1111
1112 @param state_file A path to the state file from the client.
1113 """
1114 # update the on-disk state
mblighfc3da5b2010-01-06 18:37:22 +00001115 try:
jadmanskib6e7bdb2010-04-13 16:00:39 +00001116 self._state.read_from_file(state_path)
mblighfc3da5b2010-01-06 18:37:22 +00001117 os.remove(state_path)
mbligha2c99492010-01-27 22:59:50 +00001118 except OSError, e:
mblighfc3da5b2010-01-06 18:37:22 +00001119 # ignore file-not-found errors
1120 if e.errno != errno.ENOENT:
1121 raise
jadmanskib6e7bdb2010-04-13 16:00:39 +00001122 else:
1123 logging.debug('Client state file %s not found', state_path)
mblighfc3da5b2010-01-06 18:37:22 +00001124
1125 # update the sysinfo state
1126 if self._state.has('client', 'sysinfo'):
1127 self.sysinfo.deserialize(self._state.get('client', 'sysinfo'))
1128
1129 # drop all the client-specific state
1130 self._state.discard_namespace('client')
1131
1132
mbligh0a883702010-04-21 01:58:34 +00001133 def clear_all_known_hosts(self):
1134 """Clears known hosts files for all AbstractSSHHosts."""
1135 for host in self.hosts:
1136 if isinstance(host, abstract_ssh.AbstractSSHHost):
1137 host.clear_known_hosts()
1138
1139
jadmanskif37df842009-02-11 00:03:26 +00001140class warning_manager(object):
1141 """Class for controlling warning logs. Manages the enabling and disabling
1142 of warnings."""
1143 def __init__(self):
1144 # a map of warning types to a list of disabled time intervals
1145 self.disabled_warnings = {}
1146
1147
1148 def is_valid(self, timestamp, warning_type):
1149 """Indicates if a warning (based on the time it occured and its type)
1150 is a valid warning. A warning is considered "invalid" if this type of
1151 warning was marked as "disabled" at the time the warning occured."""
1152 disabled_intervals = self.disabled_warnings.get(warning_type, [])
1153 for start, end in disabled_intervals:
1154 if timestamp >= start and (end is None or timestamp < end):
1155 return False
1156 return True
1157
1158
1159 def disable_warnings(self, warning_type, current_time_func=time.time):
1160 """As of now, disables all further warnings of this type."""
1161 intervals = self.disabled_warnings.setdefault(warning_type, [])
1162 if not intervals or intervals[-1][1] is not None:
jadmanski16a7ff72009-04-01 18:19:53 +00001163 intervals.append((int(current_time_func()), None))
jadmanskif37df842009-02-11 00:03:26 +00001164
1165
1166 def enable_warnings(self, warning_type, current_time_func=time.time):
1167 """As of now, enables all further warnings of this type."""
1168 intervals = self.disabled_warnings.get(warning_type, [])
1169 if intervals and intervals[-1][1] is None:
jadmanski16a7ff72009-04-01 18:19:53 +00001170 intervals[-1] = (intervals[-1][0], int(current_time_func()))
Paul Pendlebury57593562011-06-15 10:45:49 -07001171
1172
1173# load up site-specific code for generating site-specific job data
1174get_site_job_data = utils.import_site_function(__file__,
1175 "autotest_lib.server.site_server_job", "get_site_job_data",
1176 _get_site_job_data_dummy)
1177
1178
1179site_server_job = utils.import_site_class(
1180 __file__, "autotest_lib.server.site_server_job", "site_server_job",
1181 base_server_job)
1182
1183
1184class server_job(site_server_job):
Dale Curtis456d3c12011-07-19 11:42:51 -07001185 pass