blob: 6ec3f97d27ccf23039933ff8ab0272cf629c655e [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,
483 only_collect_crashinfo=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
jadmanskicdd0c402008-09-19 21:21:31 +0000592 if collect_crashinfo:
mbligh084bc172008-10-18 14:02:45 +0000593 # includes crashdumps
594 self._execute_code(CRASHINFO_CONTROL_FILE, namespace)
jadmanskicdd0c402008-09-19 21:21:31 +0000595 else:
mbligh084bc172008-10-18 14:02:45 +0000596 self._execute_code(CRASHDUMPS_CONTROL_FILE, namespace)
jadmanski10646442008-08-13 14:05:21 +0000597 self.disable_external_logging()
showard45ae8192008-11-05 19:32:53 +0000598 if cleanup and machines:
599 self._execute_code(CLEANUP_CONTROL_FILE, namespace)
Chris Sosaf4d43ff2012-10-30 11:21:05 -0700600 if self._uncollected_log_file and created_uncollected_logs:
601 os.remove(self._uncollected_log_file)
jadmanski10646442008-08-13 14:05:21 +0000602 if install_after and machines:
mbligh084bc172008-10-18 14:02:45 +0000603 self._execute_code(INSTALL_CONTROL_FILE, namespace)
jadmanski10646442008-08-13 14:05:21 +0000604
605
606 def run_test(self, url, *args, **dargs):
mbligh2b92b862008-11-22 13:25:32 +0000607 """
608 Summon a test object and run it.
jadmanski10646442008-08-13 14:05:21 +0000609
610 tag
611 tag to add to testname
612 url
613 url of the test to run
614 """
mblighfc3da5b2010-01-06 18:37:22 +0000615 group, testname = self.pkgmgr.get_package_name(url, 'test')
616 testname, subdir, tag = self._build_tagged_test_name(testname, dargs)
617 outputdir = self._make_test_outputdir(subdir)
jadmanski10646442008-08-13 14:05:21 +0000618
619 def group_func():
620 try:
621 test.runtest(self, url, tag, args, dargs)
622 except error.TestBaseException, e:
623 self.record(e.exit_status, subdir, testname, str(e))
624 raise
625 except Exception, e:
626 info = str(e) + "\n" + traceback.format_exc()
627 self.record('FAIL', subdir, testname, info)
628 raise
629 else:
mbligh2b92b862008-11-22 13:25:32 +0000630 self.record('GOOD', subdir, testname, 'completed successfully')
jadmanskide292df2008-08-26 20:51:14 +0000631
632 result, exc_info = self._run_group(testname, subdir, group_func)
633 if exc_info and isinstance(exc_info[1], error.TestBaseException):
634 return False
635 elif exc_info:
636 raise exc_info[0], exc_info[1], exc_info[2]
637 else:
638 return True
jadmanski10646442008-08-13 14:05:21 +0000639
640
641 def _run_group(self, name, subdir, function, *args, **dargs):
642 """\
643 Underlying method for running something inside of a group.
644 """
jadmanskide292df2008-08-26 20:51:14 +0000645 result, exc_info = None, None
jadmanski10646442008-08-13 14:05:21 +0000646 try:
647 self.record('START', subdir, name)
jadmanski52053632010-06-11 21:08:10 +0000648 result = function(*args, **dargs)
jadmanski10646442008-08-13 14:05:21 +0000649 except error.TestBaseException, e:
jadmanskib88d6dc2009-01-10 00:33:18 +0000650 self.record("END %s" % e.exit_status, subdir, name)
jadmanskide292df2008-08-26 20:51:14 +0000651 exc_info = sys.exc_info()
jadmanski10646442008-08-13 14:05:21 +0000652 except Exception, e:
653 err_msg = str(e) + '\n'
654 err_msg += traceback.format_exc()
655 self.record('END ABORT', subdir, name, err_msg)
656 raise error.JobError(name + ' failed\n' + traceback.format_exc())
657 else:
658 self.record('END GOOD', subdir, name)
659
jadmanskide292df2008-08-26 20:51:14 +0000660 return result, exc_info
jadmanski10646442008-08-13 14:05:21 +0000661
662
663 def run_group(self, function, *args, **dargs):
664 """\
665 function:
666 subroutine to run
667 *args:
668 arguments for the function
669 """
670
671 name = function.__name__
672
673 # Allow the tag for the group to be specified.
674 tag = dargs.pop('tag', None)
675 if tag:
676 name = tag
677
jadmanskide292df2008-08-26 20:51:14 +0000678 return self._run_group(name, None, function, *args, **dargs)[0]
jadmanski10646442008-08-13 14:05:21 +0000679
680
681 def run_reboot(self, reboot_func, get_kernel_func):
682 """\
683 A specialization of run_group meant specifically for handling
684 a reboot. Includes support for capturing the kernel version
685 after the reboot.
686
687 reboot_func: a function that carries out the reboot
688
689 get_kernel_func: a function that returns a string
690 representing the kernel version.
691 """
jadmanski10646442008-08-13 14:05:21 +0000692 try:
693 self.record('START', None, 'reboot')
jadmanski10646442008-08-13 14:05:21 +0000694 reboot_func()
695 except Exception, e:
jadmanski10646442008-08-13 14:05:21 +0000696 err_msg = str(e) + '\n' + traceback.format_exc()
697 self.record('END FAIL', None, 'reboot', err_msg)
jadmanski4b51d542009-04-08 14:17:16 +0000698 raise
jadmanski10646442008-08-13 14:05:21 +0000699 else:
700 kernel = get_kernel_func()
jadmanski10646442008-08-13 14:05:21 +0000701 self.record('END GOOD', None, 'reboot',
Dale Curtis74a314b2011-06-23 14:55:46 -0700702 optional_fields={"kernel": kernel})
jadmanski10646442008-08-13 14:05:21 +0000703
704
jadmanskie432dd22009-01-30 15:04:51 +0000705 def run_control(self, path):
706 """Execute a control file found at path (relative to the autotest
707 path). Intended for executing a control file within a control file,
708 not for running the top-level job control file."""
709 path = os.path.join(self.autodir, path)
710 control_file = self._load_control_file(path)
mbligh0d0f67d2009-11-06 03:15:03 +0000711 self.run(control=control_file, control_file_dir=self._USE_TEMP_DIR)
jadmanskie432dd22009-01-30 15:04:51 +0000712
713
jadmanskic09fc152008-10-15 17:56:59 +0000714 def add_sysinfo_command(self, command, logfile=None, on_every_test=False):
mbligh4395bbd2009-03-25 19:34:17 +0000715 self._add_sysinfo_loggable(sysinfo.command(command, logf=logfile),
jadmanskic09fc152008-10-15 17:56:59 +0000716 on_every_test)
717
718
719 def add_sysinfo_logfile(self, file, on_every_test=False):
720 self._add_sysinfo_loggable(sysinfo.logfile(file), on_every_test)
721
722
723 def _add_sysinfo_loggable(self, loggable, on_every_test):
724 if on_every_test:
725 self.sysinfo.test_loggables.add(loggable)
726 else:
727 self.sysinfo.boot_loggables.add(loggable)
728
729
jadmanski10646442008-08-13 14:05:21 +0000730 def _read_warnings(self):
jadmanskif37df842009-02-11 00:03:26 +0000731 """Poll all the warning loggers and extract any new warnings that have
732 been logged. If the warnings belong to a category that is currently
733 disabled, this method will discard them and they will no longer be
734 retrievable.
735
736 Returns a list of (timestamp, message) tuples, where timestamp is an
737 integer epoch timestamp."""
jadmanski10646442008-08-13 14:05:21 +0000738 warnings = []
739 while True:
740 # pull in a line of output from every logger that has
741 # output ready to be read
mbligh2b92b862008-11-22 13:25:32 +0000742 loggers, _, _ = select.select(self.warning_loggers, [], [], 0)
jadmanski10646442008-08-13 14:05:21 +0000743 closed_loggers = set()
744 for logger in loggers:
745 line = logger.readline()
746 # record any broken pipes (aka line == empty)
747 if len(line) == 0:
748 closed_loggers.add(logger)
749 continue
jadmanskif37df842009-02-11 00:03:26 +0000750 # parse out the warning
751 timestamp, msgtype, msg = line.split('\t', 2)
752 timestamp = int(timestamp)
753 # if the warning is valid, add it to the results
754 if self.warning_manager.is_valid(timestamp, msgtype):
755 warnings.append((timestamp, msg.strip()))
jadmanski10646442008-08-13 14:05:21 +0000756
757 # stop listening to loggers that are closed
758 self.warning_loggers -= closed_loggers
759
760 # stop if none of the loggers have any output left
761 if not loggers:
762 break
763
764 # sort into timestamp order
765 warnings.sort()
766 return warnings
767
768
showardcc929362010-01-25 21:20:41 +0000769 def _unique_subdirectory(self, base_subdirectory_name):
770 """Compute a unique results subdirectory based on the given name.
771
772 Appends base_subdirectory_name with a number as necessary to find a
773 directory name that doesn't already exist.
774 """
775 subdirectory = base_subdirectory_name
776 counter = 1
777 while os.path.exists(os.path.join(self.resultdir, subdirectory)):
778 subdirectory = base_subdirectory_name + '.' + str(counter)
779 counter += 1
780 return subdirectory
781
782
jadmanski52053632010-06-11 21:08:10 +0000783 def get_record_context(self):
784 """Returns an object representing the current job.record context.
785
786 The object returned is an opaque object with a 0-arg restore method
787 which can be called to restore the job.record context (i.e. indentation)
788 to the current level. The intention is that it should be used when
789 something external which generate job.record calls (e.g. an autotest
790 client) can fail catastrophically and the server job record state
791 needs to be reset to its original "known good" state.
792
793 @return: A context object with a 0-arg restore() method."""
794 return self._indenter.get_context()
795
796
showardcc929362010-01-25 21:20:41 +0000797 def record_summary(self, status_code, test_name, reason='', attributes=None,
798 distinguishing_attributes=(), child_test_ids=None):
799 """Record a summary test result.
800
801 @param status_code: status code string, see
802 common_lib.log.is_valid_status()
803 @param test_name: name of the test
804 @param reason: (optional) string providing detailed reason for test
805 outcome
806 @param attributes: (optional) dict of string keyvals to associate with
807 this result
808 @param distinguishing_attributes: (optional) list of attribute names
809 that should be used to distinguish identically-named test
810 results. These attributes should be present in the attributes
811 parameter. This is used to generate user-friendly subdirectory
812 names.
813 @param child_test_ids: (optional) list of test indices for test results
814 used in generating this result.
815 """
816 subdirectory_name_parts = [test_name]
817 for attribute in distinguishing_attributes:
818 assert attributes
819 assert attribute in attributes, '%s not in %s' % (attribute,
820 attributes)
821 subdirectory_name_parts.append(attributes[attribute])
822 base_subdirectory_name = '.'.join(subdirectory_name_parts)
823
824 subdirectory = self._unique_subdirectory(base_subdirectory_name)
825 subdirectory_path = os.path.join(self.resultdir, subdirectory)
826 os.mkdir(subdirectory_path)
827
828 self.record(status_code, subdirectory, test_name,
829 status=reason, optional_fields={'is_summary': True})
830
831 if attributes:
832 utils.write_keyval(subdirectory_path, attributes)
833
834 if child_test_ids:
835 ids_string = ','.join(str(test_id) for test_id in child_test_ids)
836 summary_data = {'child_test_ids': ids_string}
837 utils.write_keyval(os.path.join(subdirectory_path, 'summary_data'),
838 summary_data)
839
840
jadmanski16a7ff72009-04-01 18:19:53 +0000841 def disable_warnings(self, warning_type):
jadmanskif37df842009-02-11 00:03:26 +0000842 self.warning_manager.disable_warnings(warning_type)
jadmanski16a7ff72009-04-01 18:19:53 +0000843 self.record("INFO", None, None,
844 "disabling %s warnings" % warning_type,
845 {"warnings.disable": warning_type})
jadmanskif37df842009-02-11 00:03:26 +0000846
847
jadmanski16a7ff72009-04-01 18:19:53 +0000848 def enable_warnings(self, warning_type):
jadmanskif37df842009-02-11 00:03:26 +0000849 self.warning_manager.enable_warnings(warning_type)
jadmanski16a7ff72009-04-01 18:19:53 +0000850 self.record("INFO", None, None,
851 "enabling %s warnings" % warning_type,
852 {"warnings.enable": warning_type})
jadmanskif37df842009-02-11 00:03:26 +0000853
854
jadmanski779bd292009-03-19 17:33:33 +0000855 def get_status_log_path(self, subdir=None):
856 """Return the path to the job status log.
857
858 @param subdir - Optional paramter indicating that you want the path
859 to a subdirectory status log.
860
861 @returns The path where the status log should be.
862 """
mbligh210bae62009-04-01 18:33:13 +0000863 if self.resultdir:
864 if subdir:
865 return os.path.join(self.resultdir, subdir, "status.log")
866 else:
867 return os.path.join(self.resultdir, "status.log")
jadmanski779bd292009-03-19 17:33:33 +0000868 else:
mbligh210bae62009-04-01 18:33:13 +0000869 return None
jadmanski779bd292009-03-19 17:33:33 +0000870
871
jadmanski6bb32d72009-03-19 20:25:24 +0000872 def _update_uncollected_logs_list(self, update_func):
873 """Updates the uncollected logs list in a multi-process safe manner.
874
875 @param update_func - a function that updates the list of uncollected
876 logs. Should take one parameter, the list to be updated.
877 """
mbligh0d0f67d2009-11-06 03:15:03 +0000878 if self._uncollected_log_file:
879 log_file = open(self._uncollected_log_file, "r+")
mbligha788dc42009-03-26 21:10:16 +0000880 fcntl.flock(log_file, fcntl.LOCK_EX)
jadmanski6bb32d72009-03-19 20:25:24 +0000881 try:
882 uncollected_logs = pickle.load(log_file)
883 update_func(uncollected_logs)
884 log_file.seek(0)
885 log_file.truncate()
886 pickle.dump(uncollected_logs, log_file)
jadmanski3bff9092009-04-22 18:09:47 +0000887 log_file.flush()
jadmanski6bb32d72009-03-19 20:25:24 +0000888 finally:
889 fcntl.flock(log_file, fcntl.LOCK_UN)
890 log_file.close()
891
892
893 def add_client_log(self, hostname, remote_path, local_path):
894 """Adds a new set of client logs to the list of uncollected logs,
895 to allow for future log recovery.
896
897 @param host - the hostname of the machine holding the logs
898 @param remote_path - the directory on the remote machine holding logs
899 @param local_path - the local directory to copy the logs into
900 """
901 def update_func(logs_list):
902 logs_list.append((hostname, remote_path, local_path))
903 self._update_uncollected_logs_list(update_func)
904
905
906 def remove_client_log(self, hostname, remote_path, local_path):
907 """Removes a set of client logs from the list of uncollected logs,
908 to allow for future log recovery.
909
910 @param host - the hostname of the machine holding the logs
911 @param remote_path - the directory on the remote machine holding logs
912 @param local_path - the local directory to copy the logs into
913 """
914 def update_func(logs_list):
915 logs_list.remove((hostname, remote_path, local_path))
916 self._update_uncollected_logs_list(update_func)
917
918
mbligh0d0f67d2009-11-06 03:15:03 +0000919 def get_client_logs(self):
920 """Retrieves the list of uncollected logs, if it exists.
921
922 @returns A list of (host, remote_path, local_path) tuples. Returns
923 an empty list if no uncollected logs file exists.
924 """
925 log_exists = (self._uncollected_log_file and
926 os.path.exists(self._uncollected_log_file))
927 if log_exists:
928 return pickle.load(open(self._uncollected_log_file))
929 else:
930 return []
931
932
mbligh084bc172008-10-18 14:02:45 +0000933 def _fill_server_control_namespace(self, namespace, protect=True):
mbligh2b92b862008-11-22 13:25:32 +0000934 """
935 Prepare a namespace to be used when executing server control files.
mbligh084bc172008-10-18 14:02:45 +0000936
937 This sets up the control file API by importing modules and making them
938 available under the appropriate names within namespace.
939
940 For use by _execute_code().
941
942 Args:
943 namespace: The namespace dictionary to fill in.
944 protect: Boolean. If True (the default) any operation that would
945 clobber an existing entry in namespace will cause an error.
946 Raises:
947 error.AutoservError: When a name would be clobbered by import.
948 """
949 def _import_names(module_name, names=()):
mbligh2b92b862008-11-22 13:25:32 +0000950 """
951 Import a module and assign named attributes into namespace.
mbligh084bc172008-10-18 14:02:45 +0000952
953 Args:
954 module_name: The string module name.
955 names: A limiting list of names to import from module_name. If
956 empty (the default), all names are imported from the module
957 similar to a "from foo.bar import *" statement.
958 Raises:
959 error.AutoservError: When a name being imported would clobber
960 a name already in namespace.
961 """
962 module = __import__(module_name, {}, {}, names)
963
964 # No names supplied? Import * from the lowest level module.
965 # (Ugh, why do I have to implement this part myself?)
966 if not names:
967 for submodule_name in module_name.split('.')[1:]:
968 module = getattr(module, submodule_name)
969 if hasattr(module, '__all__'):
970 names = getattr(module, '__all__')
971 else:
972 names = dir(module)
973
974 # Install each name into namespace, checking to make sure it
975 # doesn't override anything that already exists.
976 for name in names:
977 # Check for conflicts to help prevent future problems.
978 if name in namespace and protect:
979 if namespace[name] is not getattr(module, name):
980 raise error.AutoservError('importing name '
981 '%s from %s %r would override %r' %
982 (name, module_name, getattr(module, name),
983 namespace[name]))
984 else:
985 # Encourage cleanliness and the use of __all__ for a
986 # more concrete API with less surprises on '*' imports.
987 warnings.warn('%s (%r) being imported from %s for use '
988 'in server control files is not the '
989 'first occurrance of that import.' %
990 (name, namespace[name], module_name))
991
992 namespace[name] = getattr(module, name)
993
994
995 # This is the equivalent of prepending a bunch of import statements to
996 # the front of the control script.
mbligha2b07dd2009-06-22 18:26:13 +0000997 namespace.update(os=os, sys=sys, logging=logging)
mbligh084bc172008-10-18 14:02:45 +0000998 _import_names('autotest_lib.server',
999 ('hosts', 'autotest', 'kvm', 'git', 'standalone_profiler',
1000 'source_kernel', 'rpm_kernel', 'deb_kernel', 'git_kernel'))
1001 _import_names('autotest_lib.server.subcommand',
1002 ('parallel', 'parallel_simple', 'subcommand'))
1003 _import_names('autotest_lib.server.utils',
1004 ('run', 'get_tmp_dir', 'sh_escape', 'parse_machine'))
1005 _import_names('autotest_lib.client.common_lib.error')
1006 _import_names('autotest_lib.client.common_lib.barrier', ('barrier',))
1007
1008 # Inject ourself as the job object into other classes within the API.
1009 # (Yuck, this injection is a gross thing be part of a public API. -gps)
1010 #
1011 # XXX Base & SiteAutotest do not appear to use .job. Who does?
1012 namespace['autotest'].Autotest.job = self
1013 # server.hosts.base_classes.Host uses .job.
1014 namespace['hosts'].Host.job = self
Eric Li10222b82010-11-24 09:33:15 -08001015 namespace['hosts'].factory.ssh_user = self._ssh_user
1016 namespace['hosts'].factory.ssh_port = self._ssh_port
1017 namespace['hosts'].factory.ssh_pass = self._ssh_pass
mbligh084bc172008-10-18 14:02:45 +00001018
1019
1020 def _execute_code(self, code_file, namespace, protect=True):
mbligh2b92b862008-11-22 13:25:32 +00001021 """
1022 Execute code using a copy of namespace as a server control script.
mbligh084bc172008-10-18 14:02:45 +00001023
1024 Unless protect_namespace is explicitly set to False, the dict will not
1025 be modified.
1026
1027 Args:
1028 code_file: The filename of the control file to execute.
1029 namespace: A dict containing names to make available during execution.
1030 protect: Boolean. If True (the default) a copy of the namespace dict
1031 is used during execution to prevent the code from modifying its
1032 contents outside of this function. If False the raw dict is
1033 passed in and modifications will be allowed.
1034 """
1035 if protect:
1036 namespace = namespace.copy()
1037 self._fill_server_control_namespace(namespace, protect=protect)
1038 # TODO: Simplify and get rid of the special cases for only 1 machine.
showard3e66e8c2008-10-27 19:20:51 +00001039 if len(self.machines) > 1:
mbligh084bc172008-10-18 14:02:45 +00001040 machines_text = '\n'.join(self.machines) + '\n'
1041 # Only rewrite the file if it does not match our machine list.
1042 try:
1043 machines_f = open(MACHINES_FILENAME, 'r')
1044 existing_machines_text = machines_f.read()
1045 machines_f.close()
1046 except EnvironmentError:
1047 existing_machines_text = None
1048 if machines_text != existing_machines_text:
1049 utils.open_write_close(MACHINES_FILENAME, machines_text)
1050 execfile(code_file, namespace, namespace)
jadmanski10646442008-08-13 14:05:21 +00001051
1052
jadmanskie29d0e42010-06-17 16:06:52 +00001053 def _parse_status(self, new_line):
mbligh0d0f67d2009-11-06 03:15:03 +00001054 if not self._using_parser:
jadmanski10646442008-08-13 14:05:21 +00001055 return
jadmanskie29d0e42010-06-17 16:06:52 +00001056 new_tests = self.parser.process_lines([new_line])
jadmanski10646442008-08-13 14:05:21 +00001057 for test in new_tests:
1058 self.__insert_test(test)
1059
1060
1061 def __insert_test(self, test):
mbligh2b92b862008-11-22 13:25:32 +00001062 """
1063 An internal method to insert a new test result into the
jadmanski10646442008-08-13 14:05:21 +00001064 database. This method will not raise an exception, even if an
1065 error occurs during the insert, to avoid failing a test
1066 simply because of unexpected database issues."""
showard21baa452008-10-21 00:08:39 +00001067 self.num_tests_run += 1
1068 if status_lib.is_worse_than_or_equal_to(test.status, 'FAIL'):
1069 self.num_tests_failed += 1
jadmanski10646442008-08-13 14:05:21 +00001070 try:
1071 self.results_db.insert_test(self.job_model, test)
1072 except Exception:
1073 msg = ("WARNING: An unexpected error occured while "
1074 "inserting test results into the database. "
1075 "Ignoring error.\n" + traceback.format_exc())
1076 print >> sys.stderr, msg
1077
mblighcaa62c22008-04-07 21:51:17 +00001078
mblighfc3da5b2010-01-06 18:37:22 +00001079 def preprocess_client_state(self):
1080 """
1081 Produce a state file for initializing the state of a client job.
1082
1083 Creates a new client state file with all the current server state, as
1084 well as some pre-set client state.
1085
1086 @returns The path of the file the state was written into.
1087 """
1088 # initialize the sysinfo state
1089 self._state.set('client', 'sysinfo', self.sysinfo.serialize())
1090
1091 # dump the state out to a tempfile
1092 fd, file_path = tempfile.mkstemp(dir=self.tmpdir)
1093 os.close(fd)
mbligha2c99492010-01-27 22:59:50 +00001094
1095 # write_to_file doesn't need locking, we exclusively own file_path
mblighfc3da5b2010-01-06 18:37:22 +00001096 self._state.write_to_file(file_path)
1097 return file_path
1098
1099
1100 def postprocess_client_state(self, state_path):
1101 """
1102 Update the state of this job with the state from a client job.
1103
1104 Updates the state of the server side of a job with the final state
1105 of a client job that was run. Updates the non-client-specific state,
1106 pulls in some specific bits from the client-specific state, and then
1107 discards the rest. Removes the state file afterwards
1108
1109 @param state_file A path to the state file from the client.
1110 """
1111 # update the on-disk state
mblighfc3da5b2010-01-06 18:37:22 +00001112 try:
jadmanskib6e7bdb2010-04-13 16:00:39 +00001113 self._state.read_from_file(state_path)
mblighfc3da5b2010-01-06 18:37:22 +00001114 os.remove(state_path)
mbligha2c99492010-01-27 22:59:50 +00001115 except OSError, e:
mblighfc3da5b2010-01-06 18:37:22 +00001116 # ignore file-not-found errors
1117 if e.errno != errno.ENOENT:
1118 raise
jadmanskib6e7bdb2010-04-13 16:00:39 +00001119 else:
1120 logging.debug('Client state file %s not found', state_path)
mblighfc3da5b2010-01-06 18:37:22 +00001121
1122 # update the sysinfo state
1123 if self._state.has('client', 'sysinfo'):
1124 self.sysinfo.deserialize(self._state.get('client', 'sysinfo'))
1125
1126 # drop all the client-specific state
1127 self._state.discard_namespace('client')
1128
1129
mbligh0a883702010-04-21 01:58:34 +00001130 def clear_all_known_hosts(self):
1131 """Clears known hosts files for all AbstractSSHHosts."""
1132 for host in self.hosts:
1133 if isinstance(host, abstract_ssh.AbstractSSHHost):
1134 host.clear_known_hosts()
1135
1136
jadmanskif37df842009-02-11 00:03:26 +00001137class warning_manager(object):
1138 """Class for controlling warning logs. Manages the enabling and disabling
1139 of warnings."""
1140 def __init__(self):
1141 # a map of warning types to a list of disabled time intervals
1142 self.disabled_warnings = {}
1143
1144
1145 def is_valid(self, timestamp, warning_type):
1146 """Indicates if a warning (based on the time it occured and its type)
1147 is a valid warning. A warning is considered "invalid" if this type of
1148 warning was marked as "disabled" at the time the warning occured."""
1149 disabled_intervals = self.disabled_warnings.get(warning_type, [])
1150 for start, end in disabled_intervals:
1151 if timestamp >= start and (end is None or timestamp < end):
1152 return False
1153 return True
1154
1155
1156 def disable_warnings(self, warning_type, current_time_func=time.time):
1157 """As of now, disables all further warnings of this type."""
1158 intervals = self.disabled_warnings.setdefault(warning_type, [])
1159 if not intervals or intervals[-1][1] is not None:
jadmanski16a7ff72009-04-01 18:19:53 +00001160 intervals.append((int(current_time_func()), None))
jadmanskif37df842009-02-11 00:03:26 +00001161
1162
1163 def enable_warnings(self, warning_type, current_time_func=time.time):
1164 """As of now, enables all further warnings of this type."""
1165 intervals = self.disabled_warnings.get(warning_type, [])
1166 if intervals and intervals[-1][1] is None:
jadmanski16a7ff72009-04-01 18:19:53 +00001167 intervals[-1] = (intervals[-1][0], int(current_time_func()))
Paul Pendlebury57593562011-06-15 10:45:49 -07001168
1169
1170# load up site-specific code for generating site-specific job data
1171get_site_job_data = utils.import_site_function(__file__,
1172 "autotest_lib.server.site_server_job", "get_site_job_data",
1173 _get_site_job_data_dummy)
1174
1175
1176site_server_job = utils.import_site_class(
1177 __file__, "autotest_lib.server.site_server_job", "site_server_job",
1178 base_server_job)
1179
1180
1181class server_job(site_server_job):
Dale Curtis456d3c12011-07-19 11:42:51 -07001182 pass