blob: 2f504926c1a8c86d647a24bece8530ee0ffb4611 [file] [log] [blame]
Dan Shi07e09af2013-04-12 09:31:29 -07001# pylint: disable-msg=C0111
2
Paul Pendlebury7c1fdcf2011-05-04 12:39:15 -07003# Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
mbligh57e78662008-06-17 19:53:49 +00006"""
7The main job wrapper for the server side.
8
9This is the core infrastructure. Derived from the client side job.py
10
11Copyright Martin J. Bligh, Andy Whitcroft 2007
12"""
13
Scott Zawalski91493c82013-01-25 16:15:20 -050014import getpass, os, sys, re, tempfile, time, select, platform
mblighfc3da5b2010-01-06 18:37:22 +000015import traceback, shutil, warnings, fcntl, pickle, logging, itertools, errno
showard75cdfee2009-06-10 17:40:41 +000016from autotest_lib.client.bin import sysinfo
mbligh0d0f67d2009-11-06 03:15:03 +000017from autotest_lib.client.common_lib import base_job
Scott Zawalski91493c82013-01-25 16:15:20 -050018from autotest_lib.client.common_lib import error, utils, packages
showard75cdfee2009-06-10 17:40:41 +000019from autotest_lib.client.common_lib import logging_manager
Paul Pendlebury57593562011-06-15 10:45:49 -070020from autotest_lib.server import test, subcommand, profilers
mbligh0a883702010-04-21 01:58:34 +000021from autotest_lib.server.hosts import abstract_ssh
jadmanski10646442008-08-13 14:05:21 +000022from autotest_lib.tko import db as tko_db, status_lib, utils as tko_utils
jadmanski10646442008-08-13 14:05:21 +000023
24
mbligh084bc172008-10-18 14:02:45 +000025def _control_segment_path(name):
26 """Get the pathname of the named control segment file."""
jadmanski10646442008-08-13 14:05:21 +000027 server_dir = os.path.dirname(os.path.abspath(__file__))
mbligh084bc172008-10-18 14:02:45 +000028 return os.path.join(server_dir, "control_segments", name)
jadmanski10646442008-08-13 14:05:21 +000029
30
mbligh084bc172008-10-18 14:02:45 +000031CLIENT_CONTROL_FILENAME = 'control'
32SERVER_CONTROL_FILENAME = 'control.srv'
33MACHINES_FILENAME = '.machines'
jadmanski10646442008-08-13 14:05:21 +000034
mbligh084bc172008-10-18 14:02:45 +000035CLIENT_WRAPPER_CONTROL_FILE = _control_segment_path('client_wrapper')
36CRASHDUMPS_CONTROL_FILE = _control_segment_path('crashdumps')
37CRASHINFO_CONTROL_FILE = _control_segment_path('crashinfo')
mbligh084bc172008-10-18 14:02:45 +000038INSTALL_CONTROL_FILE = _control_segment_path('install')
showard45ae8192008-11-05 19:32:53 +000039CLEANUP_CONTROL_FILE = _control_segment_path('cleanup')
mbligh084bc172008-10-18 14:02:45 +000040VERIFY_CONTROL_FILE = _control_segment_path('verify')
mbligh084bc172008-10-18 14:02:45 +000041REPAIR_CONTROL_FILE = _control_segment_path('repair')
Alex Millercb79ba72013-05-29 14:43:00 -070042PROVISION_CONTROL_FILE = _control_segment_path('provision')
beepscb6f1e22013-06-28 19:14:10 -070043VERIFY_JOB_REPO_URL_CONTROL_FILE = _control_segment_path('verify_job_repo_url')
Dan Shi07e09af2013-04-12 09:31:29 -070044RESET_CONTROL_FILE = _control_segment_path('reset')
jadmanski10646442008-08-13 14:05:21 +000045
46
mbligh062ed152009-01-13 00:57:14 +000047# by default provide a stub that generates no site data
48def _get_site_job_data_dummy(job):
49 return {}
50
51
jadmanski2a89dac2010-06-11 14:32:58 +000052class status_indenter(base_job.status_indenter):
53 """Provide a simple integer-backed status indenter."""
54 def __init__(self):
55 self._indent = 0
56
57
58 @property
59 def indent(self):
60 return self._indent
61
62
63 def increment(self):
64 self._indent += 1
65
66
67 def decrement(self):
68 self._indent -= 1
69
70
jadmanski52053632010-06-11 21:08:10 +000071 def get_context(self):
72 """Returns a context object for use by job.get_record_context."""
73 class context(object):
74 def __init__(self, indenter, indent):
75 self._indenter = indenter
76 self._indent = indent
77 def restore(self):
78 self._indenter._indent = self._indent
79 return context(self, self._indent)
80
81
jadmanski2a89dac2010-06-11 14:32:58 +000082class server_job_record_hook(object):
83 """The job.record hook for server job. Used to inject WARN messages from
84 the console or vlm whenever new logs are written, and to echo any logs
85 to INFO level logging. Implemented as a class so that it can use state to
86 block recursive calls, so that the hook can call job.record itself to
87 log WARN messages.
88
89 Depends on job._read_warnings and job._logger.
90 """
91 def __init__(self, job):
92 self._job = job
93 self._being_called = False
94
95
96 def __call__(self, entry):
97 """A wrapper around the 'real' record hook, the _hook method, which
98 prevents recursion. This isn't making any effort to be threadsafe,
99 the intent is to outright block infinite recursion via a
100 job.record->_hook->job.record->_hook->job.record... chain."""
101 if self._being_called:
102 return
103 self._being_called = True
104 try:
105 self._hook(self._job, entry)
106 finally:
107 self._being_called = False
108
109
110 @staticmethod
111 def _hook(job, entry):
112 """The core hook, which can safely call job.record."""
113 entries = []
114 # poll all our warning loggers for new warnings
115 for timestamp, msg in job._read_warnings():
116 warning_entry = base_job.status_log_entry(
117 'WARN', None, None, msg, {}, timestamp=timestamp)
118 entries.append(warning_entry)
119 job.record_entry(warning_entry)
120 # echo rendered versions of all the status logs to info
121 entries.append(entry)
122 for entry in entries:
123 rendered_entry = job._logger.render_entry(entry)
124 logging.info(rendered_entry)
jadmanskie29d0e42010-06-17 16:06:52 +0000125 job._parse_status(rendered_entry)
jadmanski2a89dac2010-06-11 14:32:58 +0000126
127
mbligh0d0f67d2009-11-06 03:15:03 +0000128class base_server_job(base_job.base_job):
129 """The server-side concrete implementation of base_job.
jadmanski10646442008-08-13 14:05:21 +0000130
mbligh0d0f67d2009-11-06 03:15:03 +0000131 Optional properties provided by this implementation:
132 serverdir
133 conmuxdir
134
135 num_tests_run
136 num_tests_failed
137
138 warning_manager
139 warning_loggers
jadmanski10646442008-08-13 14:05:21 +0000140 """
141
mbligh0d0f67d2009-11-06 03:15:03 +0000142 _STATUS_VERSION = 1
jadmanski10646442008-08-13 14:05:21 +0000143
144 def __init__(self, control, args, resultdir, label, user, machines,
145 client=False, parse_job='',
Scott Zawalski91493c82013-01-25 16:15:20 -0500146 ssh_user='root', ssh_port=22, ssh_pass='', test_retry=0,
mblighe0cbc912010-03-11 18:03:07 +0000147 group_name='', tag='',
148 control_filename=SERVER_CONTROL_FILENAME):
jadmanski10646442008-08-13 14:05:21 +0000149 """
mbligh374f3412009-05-13 21:29:45 +0000150 Create a server side job object.
mblighb5dac432008-11-27 00:38:44 +0000151
mblighe7d9c602009-07-02 19:02:33 +0000152 @param control: The pathname of the control file.
153 @param args: Passed to the control file.
154 @param resultdir: Where to throw the results.
155 @param label: Description of the job.
156 @param user: Username for the job (email address).
157 @param client: True if this is a client-side control file.
158 @param parse_job: string, if supplied it is the job execution tag that
159 the results will be passed through to the TKO parser with.
160 @param ssh_user: The SSH username. [root]
161 @param ssh_port: The SSH port number. [22]
162 @param ssh_pass: The SSH passphrase, if needed.
Scott Zawalski91493c82013-01-25 16:15:20 -0500163 @param test_retry: The number of times to retry a test if the test did
164 not complete successfully.
mblighe7d9c602009-07-02 19:02:33 +0000165 @param group_name: If supplied, this will be written out as
mbligh374f3412009-05-13 21:29:45 +0000166 host_group_name in the keyvals file for the parser.
mblighe7d9c602009-07-02 19:02:33 +0000167 @param tag: The job execution tag from the scheduler. [optional]
mblighe0cbc912010-03-11 18:03:07 +0000168 @param control_filename: The filename where the server control file
169 should be written in the results directory.
jadmanski10646442008-08-13 14:05:21 +0000170 """
Scott Zawalski91493c82013-01-25 16:15:20 -0500171 super(base_server_job, self).__init__(resultdir=resultdir,
172 test_retry=test_retry)
mbligh0d0f67d2009-11-06 03:15:03 +0000173 path = os.path.dirname(__file__)
Scott Zawalski91493c82013-01-25 16:15:20 -0500174 self.test_retry = test_retry
mbligh0d0f67d2009-11-06 03:15:03 +0000175 self.control = control
176 self._uncollected_log_file = os.path.join(self.resultdir,
177 'uncollected_logs')
178 debugdir = os.path.join(self.resultdir, 'debug')
179 if not os.path.exists(debugdir):
180 os.mkdir(debugdir)
181
182 if user:
183 self.user = user
184 else:
185 self.user = getpass.getuser()
186
jadmanski808f4b12010-04-09 22:30:31 +0000187 self.args = args
Peter Mayo7a875762012-06-13 14:38:15 -0400188 self.label = label
jadmanski10646442008-08-13 14:05:21 +0000189 self.machines = machines
mbligh0d0f67d2009-11-06 03:15:03 +0000190 self._client = client
jadmanski10646442008-08-13 14:05:21 +0000191 self.warning_loggers = set()
jadmanskif37df842009-02-11 00:03:26 +0000192 self.warning_manager = warning_manager()
mbligh0d0f67d2009-11-06 03:15:03 +0000193 self._ssh_user = ssh_user
194 self._ssh_port = ssh_port
195 self._ssh_pass = ssh_pass
mblighe7d9c602009-07-02 19:02:33 +0000196 self.tag = tag
mbligh09108442008-10-15 16:27:38 +0000197 self.last_boot_tag = None
jadmanski53aaf382008-11-17 16:22:31 +0000198 self.hosts = set()
mbligh0d0f67d2009-11-06 03:15:03 +0000199 self.drop_caches = False
mblighb5dac432008-11-27 00:38:44 +0000200 self.drop_caches_between_iterations = False
mblighe0cbc912010-03-11 18:03:07 +0000201 self._control_filename = control_filename
jadmanski10646442008-08-13 14:05:21 +0000202
showard75cdfee2009-06-10 17:40:41 +0000203 self.logging = logging_manager.get_logging_manager(
204 manage_stdout_and_stderr=True, redirect_fds=True)
205 subcommand.logging_manager_object = self.logging
jadmanski10646442008-08-13 14:05:21 +0000206
mbligh0d0f67d2009-11-06 03:15:03 +0000207 self.sysinfo = sysinfo.sysinfo(self.resultdir)
jadmanski043e1132008-11-19 17:10:32 +0000208 self.profilers = profilers.profilers(self)
jadmanskic09fc152008-10-15 17:56:59 +0000209
jadmanski10646442008-08-13 14:05:21 +0000210 job_data = {'label' : label, 'user' : user,
211 'hostname' : ','.join(machines),
Eric Li861b2d52011-02-04 14:50:35 -0800212 'drone' : platform.node(),
mbligh0d0f67d2009-11-06 03:15:03 +0000213 'status_version' : str(self._STATUS_VERSION),
showard170873e2009-01-07 00:22:26 +0000214 'job_started' : str(int(time.time()))}
mbligh374f3412009-05-13 21:29:45 +0000215 if group_name:
216 job_data['host_group_name'] = group_name
jadmanski10646442008-08-13 14:05:21 +0000217
mbligh0d0f67d2009-11-06 03:15:03 +0000218 # only write these keyvals out on the first job in a resultdir
219 if 'job_started' not in utils.read_keyval(self.resultdir):
220 job_data.update(get_site_job_data(self))
221 utils.write_keyval(self.resultdir, job_data)
222
223 self._parse_job = parse_job
showardcc929362010-01-25 21:20:41 +0000224 self._using_parser = (self._parse_job and len(machines) <= 1)
mbligh0d0f67d2009-11-06 03:15:03 +0000225 self.pkgmgr = packages.PackageManager(
226 self.autodir, run_function_dargs={'timeout':600})
showard21baa452008-10-21 00:08:39 +0000227 self.num_tests_run = 0
228 self.num_tests_failed = 0
229
jadmanski550fdc22008-11-20 16:32:08 +0000230 self._register_subcommand_hooks()
231
mbligh0d0f67d2009-11-06 03:15:03 +0000232 # these components aren't usable on the server
233 self.bootloader = None
234 self.harness = None
235
jadmanski2a89dac2010-06-11 14:32:58 +0000236 # set up the status logger
jadmanski52053632010-06-11 21:08:10 +0000237 self._indenter = status_indenter()
jadmanski2a89dac2010-06-11 14:32:58 +0000238 self._logger = base_job.status_logger(
jadmanski52053632010-06-11 21:08:10 +0000239 self, self._indenter, 'status.log', 'status.log',
jadmanski2a89dac2010-06-11 14:32:58 +0000240 record_hook=server_job_record_hook(self))
241
mbligh0d0f67d2009-11-06 03:15:03 +0000242
243 @classmethod
244 def _find_base_directories(cls):
245 """
246 Determine locations of autodir, clientdir and serverdir. Assumes
247 that this file is located within serverdir and uses __file__ along
248 with relative paths to resolve the location.
249 """
250 serverdir = os.path.abspath(os.path.dirname(__file__))
251 autodir = os.path.normpath(os.path.join(serverdir, '..'))
252 clientdir = os.path.join(autodir, 'client')
253 return autodir, clientdir, serverdir
254
255
Scott Zawalski91493c82013-01-25 16:15:20 -0500256 def _find_resultdir(self, resultdir, *args, **dargs):
mbligh0d0f67d2009-11-06 03:15:03 +0000257 """
258 Determine the location of resultdir. For server jobs we expect one to
259 always be explicitly passed in to __init__, so just return that.
260 """
261 if resultdir:
262 return os.path.normpath(resultdir)
263 else:
264 return None
265
jadmanski550fdc22008-11-20 16:32:08 +0000266
jadmanski2a89dac2010-06-11 14:32:58 +0000267 def _get_status_logger(self):
268 """Return a reference to the status logger."""
269 return self._logger
270
271
jadmanskie432dd22009-01-30 15:04:51 +0000272 @staticmethod
273 def _load_control_file(path):
274 f = open(path)
275 try:
276 control_file = f.read()
277 finally:
278 f.close()
279 return re.sub('\r', '', control_file)
280
281
jadmanski550fdc22008-11-20 16:32:08 +0000282 def _register_subcommand_hooks(self):
mbligh2b92b862008-11-22 13:25:32 +0000283 """
284 Register some hooks into the subcommand modules that allow us
285 to properly clean up self.hosts created in forked subprocesses.
286 """
jadmanski550fdc22008-11-20 16:32:08 +0000287 def on_fork(cmd):
288 self._existing_hosts_on_fork = set(self.hosts)
289 def on_join(cmd):
290 new_hosts = self.hosts - self._existing_hosts_on_fork
291 for host in new_hosts:
292 host.close()
293 subcommand.subcommand.register_fork_hook(on_fork)
294 subcommand.subcommand.register_join_hook(on_join)
295
jadmanski10646442008-08-13 14:05:21 +0000296
mbligh4608b002010-01-05 18:22:35 +0000297 def init_parser(self):
mbligh2b92b862008-11-22 13:25:32 +0000298 """
mbligh4608b002010-01-05 18:22:35 +0000299 Start the continuous parsing of self.resultdir. This sets up
jadmanski10646442008-08-13 14:05:21 +0000300 the database connection and inserts the basic job object into
mbligh2b92b862008-11-22 13:25:32 +0000301 the database if necessary.
302 """
mbligh4608b002010-01-05 18:22:35 +0000303 if not self._using_parser:
304 return
jadmanski10646442008-08-13 14:05:21 +0000305 # redirect parser debugging to .parse.log
mbligh4608b002010-01-05 18:22:35 +0000306 parse_log = os.path.join(self.resultdir, '.parse.log')
jadmanski10646442008-08-13 14:05:21 +0000307 parse_log = open(parse_log, 'w', 0)
308 tko_utils.redirect_parser_debugging(parse_log)
309 # create a job model object and set up the db
310 self.results_db = tko_db.db(autocommit=True)
mbligh0d0f67d2009-11-06 03:15:03 +0000311 self.parser = status_lib.parser(self._STATUS_VERSION)
mbligh4608b002010-01-05 18:22:35 +0000312 self.job_model = self.parser.make_job(self.resultdir)
jadmanski10646442008-08-13 14:05:21 +0000313 self.parser.start(self.job_model)
314 # check if a job already exists in the db and insert it if
315 # it does not
mbligh0d0f67d2009-11-06 03:15:03 +0000316 job_idx = self.results_db.find_job(self._parse_job)
jadmanski10646442008-08-13 14:05:21 +0000317 if job_idx is None:
mbligh0d0f67d2009-11-06 03:15:03 +0000318 self.results_db.insert_job(self._parse_job, self.job_model)
jadmanski10646442008-08-13 14:05:21 +0000319 else:
mbligh2b92b862008-11-22 13:25:32 +0000320 machine_idx = self.results_db.lookup_machine(self.job_model.machine)
jadmanski10646442008-08-13 14:05:21 +0000321 self.job_model.index = job_idx
322 self.job_model.machine_idx = machine_idx
323
324
325 def cleanup_parser(self):
mbligh2b92b862008-11-22 13:25:32 +0000326 """
327 This should be called after the server job is finished
jadmanski10646442008-08-13 14:05:21 +0000328 to carry out any remaining cleanup (e.g. flushing any
mbligh2b92b862008-11-22 13:25:32 +0000329 remaining test results to the results db)
330 """
mbligh0d0f67d2009-11-06 03:15:03 +0000331 if not self._using_parser:
jadmanski10646442008-08-13 14:05:21 +0000332 return
333 final_tests = self.parser.end()
334 for test in final_tests:
335 self.__insert_test(test)
mbligh0d0f67d2009-11-06 03:15:03 +0000336 self._using_parser = False
jadmanski10646442008-08-13 14:05:21 +0000337
338
339 def verify(self):
Dan Shi07e09af2013-04-12 09:31:29 -0700340 """Verify machines are all ssh-able."""
jadmanski10646442008-08-13 14:05:21 +0000341 if not self.machines:
mbligh084bc172008-10-18 14:02:45 +0000342 raise error.AutoservError('No machines specified to verify')
mbligh0fce4112008-11-27 00:37:17 +0000343 if self.resultdir:
344 os.chdir(self.resultdir)
jadmanski10646442008-08-13 14:05:21 +0000345 try:
jadmanskicdd0c402008-09-19 21:21:31 +0000346 namespace = {'machines' : self.machines, 'job' : self,
mbligh0d0f67d2009-11-06 03:15:03 +0000347 'ssh_user' : self._ssh_user,
348 'ssh_port' : self._ssh_port,
349 'ssh_pass' : self._ssh_pass}
mbligh084bc172008-10-18 14:02:45 +0000350 self._execute_code(VERIFY_CONTROL_FILE, namespace, protect=False)
jadmanski10646442008-08-13 14:05:21 +0000351 except Exception, e:
mbligh2b92b862008-11-22 13:25:32 +0000352 msg = ('Verify failed\n' + str(e) + '\n' + traceback.format_exc())
jadmanski10646442008-08-13 14:05:21 +0000353 self.record('ABORT', None, None, msg)
354 raise
355
356
Dan Shi07e09af2013-04-12 09:31:29 -0700357 def reset(self):
358 """Reset machines by first cleanup then verify each machine."""
359 if not self.machines:
360 raise error.AutoservError('No machines specified to reset.')
361 if self.resultdir:
362 os.chdir(self.resultdir)
363
364 try:
365 namespace = {'machines' : self.machines, 'job' : self,
366 'ssh_user' : self._ssh_user,
367 'ssh_port' : self._ssh_port,
368 'ssh_pass' : self._ssh_pass}
369 self._execute_code(RESET_CONTROL_FILE, namespace, protect=False)
370 except Exception as e:
371 msg = ('Reset failed\n' + str(e) + '\n' +
372 traceback.format_exc())
373 self.record('ABORT', None, None, msg)
374 raise
375
376
jadmanski10646442008-08-13 14:05:21 +0000377 def repair(self, host_protection):
378 if not self.machines:
379 raise error.AutoservError('No machines specified to repair')
mbligh0fce4112008-11-27 00:37:17 +0000380 if self.resultdir:
381 os.chdir(self.resultdir)
jadmanski10646442008-08-13 14:05:21 +0000382 namespace = {'machines': self.machines, 'job': self,
mbligh0d0f67d2009-11-06 03:15:03 +0000383 'ssh_user': self._ssh_user, 'ssh_port': self._ssh_port,
384 'ssh_pass': self._ssh_pass,
jadmanski10646442008-08-13 14:05:21 +0000385 'protection_level': host_protection}
mbligh25c0b8c2009-01-24 01:44:17 +0000386
mbligh0931b0a2009-04-08 17:44:48 +0000387 self._execute_code(REPAIR_CONTROL_FILE, namespace, protect=False)
jadmanski10646442008-08-13 14:05:21 +0000388
389
Alex Millercb79ba72013-05-29 14:43:00 -0700390 def provision(self, labels):
391 """
392 Provision all hosts to match |labels|.
393
394 @param labels: A comma seperated string of labels to provision the
395 host to.
396
397 """
398 namespace = {'provision_labels': labels}
399 control = self._load_control_file(PROVISION_CONTROL_FILE)
400 self.run(control=control, namespace=namespace)
401
402
jadmanski10646442008-08-13 14:05:21 +0000403 def precheck(self):
404 """
405 perform any additional checks in derived classes.
406 """
407 pass
408
409
410 def enable_external_logging(self):
mbligh2b92b862008-11-22 13:25:32 +0000411 """
412 Start or restart external logging mechanism.
jadmanski10646442008-08-13 14:05:21 +0000413 """
414 pass
415
416
417 def disable_external_logging(self):
mbligh2b92b862008-11-22 13:25:32 +0000418 """
419 Pause or stop external logging mechanism.
jadmanski10646442008-08-13 14:05:21 +0000420 """
421 pass
422
423
424 def use_external_logging(self):
mbligh2b92b862008-11-22 13:25:32 +0000425 """
426 Return True if external logging should be used.
jadmanski10646442008-08-13 14:05:21 +0000427 """
428 return False
429
430
mbligh415dc212009-06-15 21:53:34 +0000431 def _make_parallel_wrapper(self, function, machines, log):
432 """Wrap function as appropriate for calling by parallel_simple."""
mbligh2b92b862008-11-22 13:25:32 +0000433 is_forking = not (len(machines) == 1 and self.machines == machines)
mbligh0d0f67d2009-11-06 03:15:03 +0000434 if self._parse_job and is_forking and log:
jadmanski10646442008-08-13 14:05:21 +0000435 def wrapper(machine):
mbligh0d0f67d2009-11-06 03:15:03 +0000436 self._parse_job += "/" + machine
437 self._using_parser = True
jadmanski10646442008-08-13 14:05:21 +0000438 self.machines = [machine]
mbligh0d0f67d2009-11-06 03:15:03 +0000439 self.push_execution_context(machine)
jadmanski609a5f42008-08-26 20:52:42 +0000440 os.chdir(self.resultdir)
showard2bab8f42008-11-12 18:15:22 +0000441 utils.write_keyval(self.resultdir, {"hostname": machine})
mbligh4608b002010-01-05 18:22:35 +0000442 self.init_parser()
jadmanski10646442008-08-13 14:05:21 +0000443 result = function(machine)
444 self.cleanup_parser()
445 return result
jadmanski4dd1a002008-09-05 20:27:30 +0000446 elif len(machines) > 1 and log:
jadmanski10646442008-08-13 14:05:21 +0000447 def wrapper(machine):
mbligh0d0f67d2009-11-06 03:15:03 +0000448 self.push_execution_context(machine)
jadmanski609a5f42008-08-26 20:52:42 +0000449 os.chdir(self.resultdir)
mbligh838d82d2009-03-11 17:14:31 +0000450 machine_data = {'hostname' : machine,
mbligh0d0f67d2009-11-06 03:15:03 +0000451 'status_version' : str(self._STATUS_VERSION)}
mbligh838d82d2009-03-11 17:14:31 +0000452 utils.write_keyval(self.resultdir, machine_data)
jadmanski10646442008-08-13 14:05:21 +0000453 result = function(machine)
454 return result
455 else:
456 wrapper = function
mbligh415dc212009-06-15 21:53:34 +0000457 return wrapper
458
459
460 def parallel_simple(self, function, machines, log=True, timeout=None,
461 return_results=False):
462 """
463 Run 'function' using parallel_simple, with an extra wrapper to handle
464 the necessary setup for continuous parsing, if possible. If continuous
465 parsing is already properly initialized then this should just work.
466
467 @param function: A callable to run in parallel given each machine.
468 @param machines: A list of machine names to be passed one per subcommand
469 invocation of function.
470 @param log: If True, output will be written to output in a subdirectory
471 named after each machine.
472 @param timeout: Seconds after which the function call should timeout.
473 @param return_results: If True instead of an AutoServError being raised
474 on any error a list of the results|exceptions from the function
475 called on each arg is returned. [default: False]
476
477 @raises error.AutotestError: If any of the functions failed.
478 """
479 wrapper = self._make_parallel_wrapper(function, machines, log)
480 return subcommand.parallel_simple(wrapper, machines,
481 log=log, timeout=timeout,
482 return_results=return_results)
483
484
485 def parallel_on_machines(self, function, machines, timeout=None):
486 """
showardcd5fac42009-07-06 20:19:43 +0000487 @param function: Called in parallel with one machine as its argument.
mbligh415dc212009-06-15 21:53:34 +0000488 @param machines: A list of machines to call function(machine) on.
489 @param timeout: Seconds after which the function call should timeout.
490
491 @returns A list of machines on which function(machine) returned
492 without raising an exception.
493 """
showardcd5fac42009-07-06 20:19:43 +0000494 results = self.parallel_simple(function, machines, timeout=timeout,
mbligh415dc212009-06-15 21:53:34 +0000495 return_results=True)
496 success_machines = []
497 for result, machine in itertools.izip(results, machines):
498 if not isinstance(result, Exception):
499 success_machines.append(machine)
500 return success_machines
jadmanski10646442008-08-13 14:05:21 +0000501
502
mbligh0d0f67d2009-11-06 03:15:03 +0000503 _USE_TEMP_DIR = object()
mbligh2b92b862008-11-22 13:25:32 +0000504 def run(self, cleanup=False, install_before=False, install_after=False,
jadmanskie432dd22009-01-30 15:04:51 +0000505 collect_crashdumps=True, namespace={}, control=None,
beepscb6f1e22013-06-28 19:14:10 -0700506 control_file_dir=None, verify_job_repo_url=False,
Christopher Wileyf594c5e2013-07-03 18:25:30 -0700507 only_collect_crashinfo=False, skip_crash_collection=False):
jadmanskifb9c0fa2009-04-29 17:39:16 +0000508 # for a normal job, make sure the uncollected logs file exists
509 # for a crashinfo-only run it should already exist, bail out otherwise
jadmanski648c39f2010-03-19 17:38:01 +0000510 created_uncollected_logs = False
mbligh0d0f67d2009-11-06 03:15:03 +0000511 if self.resultdir and not os.path.exists(self._uncollected_log_file):
jadmanskifb9c0fa2009-04-29 17:39:16 +0000512 if only_collect_crashinfo:
513 # if this is a crashinfo-only run, and there were no existing
514 # uncollected logs, just bail out early
515 logging.info("No existing uncollected logs, "
516 "skipping crashinfo collection")
517 return
518 else:
mbligh0d0f67d2009-11-06 03:15:03 +0000519 log_file = open(self._uncollected_log_file, "w")
jadmanskifb9c0fa2009-04-29 17:39:16 +0000520 pickle.dump([], log_file)
521 log_file.close()
jadmanski648c39f2010-03-19 17:38:01 +0000522 created_uncollected_logs = True
jadmanskifb9c0fa2009-04-29 17:39:16 +0000523
jadmanski10646442008-08-13 14:05:21 +0000524 # use a copy so changes don't affect the original dictionary
525 namespace = namespace.copy()
526 machines = self.machines
jadmanskie432dd22009-01-30 15:04:51 +0000527 if control is None:
jadmanski02a3ba22009-11-13 20:47:27 +0000528 if self.control is None:
529 control = ''
530 else:
531 control = self._load_control_file(self.control)
jadmanskie432dd22009-01-30 15:04:51 +0000532 if control_file_dir is None:
533 control_file_dir = self.resultdir
jadmanski10646442008-08-13 14:05:21 +0000534
535 self.aborted = False
536 namespace['machines'] = machines
jadmanski808f4b12010-04-09 22:30:31 +0000537 namespace['args'] = self.args
jadmanski10646442008-08-13 14:05:21 +0000538 namespace['job'] = self
mbligh0d0f67d2009-11-06 03:15:03 +0000539 namespace['ssh_user'] = self._ssh_user
540 namespace['ssh_port'] = self._ssh_port
541 namespace['ssh_pass'] = self._ssh_pass
jadmanski10646442008-08-13 14:05:21 +0000542 test_start_time = int(time.time())
543
mbligh80e1eba2008-11-19 00:26:18 +0000544 if self.resultdir:
545 os.chdir(self.resultdir)
jadmanski779bd292009-03-19 17:33:33 +0000546 # touch status.log so that the parser knows a job is running here
jadmanski382303a2009-04-21 19:53:39 +0000547 open(self.get_status_log_path(), 'a').close()
mbligh80e1eba2008-11-19 00:26:18 +0000548 self.enable_external_logging()
jadmanskie432dd22009-01-30 15:04:51 +0000549
jadmanskicdd0c402008-09-19 21:21:31 +0000550 collect_crashinfo = True
mblighaebe3b62008-12-22 14:45:40 +0000551 temp_control_file_dir = None
jadmanski10646442008-08-13 14:05:21 +0000552 try:
showardcf8d4922009-10-14 16:08:39 +0000553 try:
554 if install_before and machines:
555 self._execute_code(INSTALL_CONTROL_FILE, namespace)
jadmanskie432dd22009-01-30 15:04:51 +0000556
showardcf8d4922009-10-14 16:08:39 +0000557 if only_collect_crashinfo:
558 return
559
beepscb6f1e22013-06-28 19:14:10 -0700560 # If the verify_job_repo_url option is set but we're unable
561 # to actually verify that the job_repo_url contains the autotest
562 # package, this job will fail.
563 if verify_job_repo_url:
564 self._execute_code(VERIFY_JOB_REPO_URL_CONTROL_FILE,
565 namespace)
566 else:
567 logging.warning('Not checking if job_repo_url contains '
568 'autotest packages on %s', machines)
569
jadmanskidef0c3c2009-03-25 20:07:10 +0000570 # determine the dir to write the control files to
571 cfd_specified = (control_file_dir
mbligh0d0f67d2009-11-06 03:15:03 +0000572 and control_file_dir is not self._USE_TEMP_DIR)
jadmanskidef0c3c2009-03-25 20:07:10 +0000573 if cfd_specified:
574 temp_control_file_dir = None
575 else:
576 temp_control_file_dir = tempfile.mkdtemp(
577 suffix='temp_control_file_dir')
578 control_file_dir = temp_control_file_dir
579 server_control_file = os.path.join(control_file_dir,
mblighe0cbc912010-03-11 18:03:07 +0000580 self._control_filename)
jadmanskidef0c3c2009-03-25 20:07:10 +0000581 client_control_file = os.path.join(control_file_dir,
582 CLIENT_CONTROL_FILENAME)
mbligh0d0f67d2009-11-06 03:15:03 +0000583 if self._client:
jadmanskidef0c3c2009-03-25 20:07:10 +0000584 namespace['control'] = control
585 utils.open_write_close(client_control_file, control)
mblighfeac0102009-04-28 18:31:12 +0000586 shutil.copyfile(CLIENT_WRAPPER_CONTROL_FILE,
587 server_control_file)
jadmanskidef0c3c2009-03-25 20:07:10 +0000588 else:
589 utils.open_write_close(server_control_file, control)
mbligh26f0d882009-06-22 18:30:01 +0000590 logging.info("Processing control file")
jadmanskidef0c3c2009-03-25 20:07:10 +0000591 self._execute_code(server_control_file, namespace)
mbligh26f0d882009-06-22 18:30:01 +0000592 logging.info("Finished processing control file")
jadmanski10646442008-08-13 14:05:21 +0000593
jadmanskidef0c3c2009-03-25 20:07:10 +0000594 # no error occured, so we don't need to collect crashinfo
595 collect_crashinfo = False
Eric Li6f27d4f2010-09-29 10:55:17 -0700596 except Exception, e:
showardcf8d4922009-10-14 16:08:39 +0000597 try:
598 logging.exception(
599 'Exception escaped control file, job aborting:')
Eric Li6f27d4f2010-09-29 10:55:17 -0700600 self.record('INFO', None, None, str(e),
601 {'job_abort_reason': str(e)})
showardcf8d4922009-10-14 16:08:39 +0000602 except:
603 pass # don't let logging exceptions here interfere
604 raise
jadmanski10646442008-08-13 14:05:21 +0000605 finally:
mblighaebe3b62008-12-22 14:45:40 +0000606 if temp_control_file_dir:
jadmanskie432dd22009-01-30 15:04:51 +0000607 # Clean up temp directory used for copies of the control files
mblighaebe3b62008-12-22 14:45:40 +0000608 try:
609 shutil.rmtree(temp_control_file_dir)
610 except Exception, e:
mblighe7d9c602009-07-02 19:02:33 +0000611 logging.warn('Could not remove temp directory %s: %s',
612 temp_control_file_dir, e)
jadmanskie432dd22009-01-30 15:04:51 +0000613
jadmanskicdd0c402008-09-19 21:21:31 +0000614 if machines and (collect_crashdumps or collect_crashinfo):
jadmanski10646442008-08-13 14:05:21 +0000615 namespace['test_start_time'] = test_start_time
Christopher Wileyf594c5e2013-07-03 18:25:30 -0700616 if skip_crash_collection:
617 logging.info('Skipping crash dump/info collection '
618 'as requested.')
619 elif collect_crashinfo:
mbligh084bc172008-10-18 14:02:45 +0000620 # includes crashdumps
621 self._execute_code(CRASHINFO_CONTROL_FILE, namespace)
jadmanskicdd0c402008-09-19 21:21:31 +0000622 else:
mbligh084bc172008-10-18 14:02:45 +0000623 self._execute_code(CRASHDUMPS_CONTROL_FILE, namespace)
jadmanski10646442008-08-13 14:05:21 +0000624 self.disable_external_logging()
showard45ae8192008-11-05 19:32:53 +0000625 if cleanup and machines:
626 self._execute_code(CLEANUP_CONTROL_FILE, namespace)
Chris Sosaf4d43ff2012-10-30 11:21:05 -0700627 if self._uncollected_log_file and created_uncollected_logs:
628 os.remove(self._uncollected_log_file)
jadmanski10646442008-08-13 14:05:21 +0000629 if install_after and machines:
mbligh084bc172008-10-18 14:02:45 +0000630 self._execute_code(INSTALL_CONTROL_FILE, namespace)
jadmanski10646442008-08-13 14:05:21 +0000631
632
633 def run_test(self, url, *args, **dargs):
mbligh2b92b862008-11-22 13:25:32 +0000634 """
635 Summon a test object and run it.
jadmanski10646442008-08-13 14:05:21 +0000636
637 tag
638 tag to add to testname
639 url
640 url of the test to run
641 """
mblighfc3da5b2010-01-06 18:37:22 +0000642 group, testname = self.pkgmgr.get_package_name(url, 'test')
643 testname, subdir, tag = self._build_tagged_test_name(testname, dargs)
644 outputdir = self._make_test_outputdir(subdir)
jadmanski10646442008-08-13 14:05:21 +0000645
646 def group_func():
647 try:
648 test.runtest(self, url, tag, args, dargs)
649 except error.TestBaseException, e:
650 self.record(e.exit_status, subdir, testname, str(e))
651 raise
652 except Exception, e:
653 info = str(e) + "\n" + traceback.format_exc()
654 self.record('FAIL', subdir, testname, info)
655 raise
656 else:
mbligh2b92b862008-11-22 13:25:32 +0000657 self.record('GOOD', subdir, testname, 'completed successfully')
jadmanskide292df2008-08-26 20:51:14 +0000658
659 result, exc_info = self._run_group(testname, subdir, group_func)
660 if exc_info and isinstance(exc_info[1], error.TestBaseException):
661 return False
662 elif exc_info:
663 raise exc_info[0], exc_info[1], exc_info[2]
664 else:
665 return True
jadmanski10646442008-08-13 14:05:21 +0000666
667
668 def _run_group(self, name, subdir, function, *args, **dargs):
669 """\
670 Underlying method for running something inside of a group.
671 """
jadmanskide292df2008-08-26 20:51:14 +0000672 result, exc_info = None, None
jadmanski10646442008-08-13 14:05:21 +0000673 try:
674 self.record('START', subdir, name)
jadmanski52053632010-06-11 21:08:10 +0000675 result = function(*args, **dargs)
jadmanski10646442008-08-13 14:05:21 +0000676 except error.TestBaseException, e:
jadmanskib88d6dc2009-01-10 00:33:18 +0000677 self.record("END %s" % e.exit_status, subdir, name)
jadmanskide292df2008-08-26 20:51:14 +0000678 exc_info = sys.exc_info()
jadmanski10646442008-08-13 14:05:21 +0000679 except Exception, e:
680 err_msg = str(e) + '\n'
681 err_msg += traceback.format_exc()
682 self.record('END ABORT', subdir, name, err_msg)
683 raise error.JobError(name + ' failed\n' + traceback.format_exc())
684 else:
685 self.record('END GOOD', subdir, name)
686
jadmanskide292df2008-08-26 20:51:14 +0000687 return result, exc_info
jadmanski10646442008-08-13 14:05:21 +0000688
689
690 def run_group(self, function, *args, **dargs):
691 """\
692 function:
693 subroutine to run
694 *args:
695 arguments for the function
696 """
697
698 name = function.__name__
699
700 # Allow the tag for the group to be specified.
701 tag = dargs.pop('tag', None)
702 if tag:
703 name = tag
704
jadmanskide292df2008-08-26 20:51:14 +0000705 return self._run_group(name, None, function, *args, **dargs)[0]
jadmanski10646442008-08-13 14:05:21 +0000706
707
708 def run_reboot(self, reboot_func, get_kernel_func):
709 """\
710 A specialization of run_group meant specifically for handling
711 a reboot. Includes support for capturing the kernel version
712 after the reboot.
713
714 reboot_func: a function that carries out the reboot
715
716 get_kernel_func: a function that returns a string
717 representing the kernel version.
718 """
jadmanski10646442008-08-13 14:05:21 +0000719 try:
720 self.record('START', None, 'reboot')
jadmanski10646442008-08-13 14:05:21 +0000721 reboot_func()
722 except Exception, e:
jadmanski10646442008-08-13 14:05:21 +0000723 err_msg = str(e) + '\n' + traceback.format_exc()
724 self.record('END FAIL', None, 'reboot', err_msg)
jadmanski4b51d542009-04-08 14:17:16 +0000725 raise
jadmanski10646442008-08-13 14:05:21 +0000726 else:
727 kernel = get_kernel_func()
jadmanski10646442008-08-13 14:05:21 +0000728 self.record('END GOOD', None, 'reboot',
Dale Curtis74a314b2011-06-23 14:55:46 -0700729 optional_fields={"kernel": kernel})
jadmanski10646442008-08-13 14:05:21 +0000730
731
jadmanskie432dd22009-01-30 15:04:51 +0000732 def run_control(self, path):
733 """Execute a control file found at path (relative to the autotest
734 path). Intended for executing a control file within a control file,
735 not for running the top-level job control file."""
736 path = os.path.join(self.autodir, path)
737 control_file = self._load_control_file(path)
mbligh0d0f67d2009-11-06 03:15:03 +0000738 self.run(control=control_file, control_file_dir=self._USE_TEMP_DIR)
jadmanskie432dd22009-01-30 15:04:51 +0000739
740
jadmanskic09fc152008-10-15 17:56:59 +0000741 def add_sysinfo_command(self, command, logfile=None, on_every_test=False):
mbligh4395bbd2009-03-25 19:34:17 +0000742 self._add_sysinfo_loggable(sysinfo.command(command, logf=logfile),
jadmanskic09fc152008-10-15 17:56:59 +0000743 on_every_test)
744
745
746 def add_sysinfo_logfile(self, file, on_every_test=False):
747 self._add_sysinfo_loggable(sysinfo.logfile(file), on_every_test)
748
749
750 def _add_sysinfo_loggable(self, loggable, on_every_test):
751 if on_every_test:
752 self.sysinfo.test_loggables.add(loggable)
753 else:
754 self.sysinfo.boot_loggables.add(loggable)
755
756
jadmanski10646442008-08-13 14:05:21 +0000757 def _read_warnings(self):
jadmanskif37df842009-02-11 00:03:26 +0000758 """Poll all the warning loggers and extract any new warnings that have
759 been logged. If the warnings belong to a category that is currently
760 disabled, this method will discard them and they will no longer be
761 retrievable.
762
763 Returns a list of (timestamp, message) tuples, where timestamp is an
764 integer epoch timestamp."""
jadmanski10646442008-08-13 14:05:21 +0000765 warnings = []
766 while True:
767 # pull in a line of output from every logger that has
768 # output ready to be read
mbligh2b92b862008-11-22 13:25:32 +0000769 loggers, _, _ = select.select(self.warning_loggers, [], [], 0)
jadmanski10646442008-08-13 14:05:21 +0000770 closed_loggers = set()
771 for logger in loggers:
772 line = logger.readline()
773 # record any broken pipes (aka line == empty)
774 if len(line) == 0:
775 closed_loggers.add(logger)
776 continue
jadmanskif37df842009-02-11 00:03:26 +0000777 # parse out the warning
778 timestamp, msgtype, msg = line.split('\t', 2)
779 timestamp = int(timestamp)
780 # if the warning is valid, add it to the results
781 if self.warning_manager.is_valid(timestamp, msgtype):
782 warnings.append((timestamp, msg.strip()))
jadmanski10646442008-08-13 14:05:21 +0000783
784 # stop listening to loggers that are closed
785 self.warning_loggers -= closed_loggers
786
787 # stop if none of the loggers have any output left
788 if not loggers:
789 break
790
791 # sort into timestamp order
792 warnings.sort()
793 return warnings
794
795
showardcc929362010-01-25 21:20:41 +0000796 def _unique_subdirectory(self, base_subdirectory_name):
797 """Compute a unique results subdirectory based on the given name.
798
799 Appends base_subdirectory_name with a number as necessary to find a
800 directory name that doesn't already exist.
801 """
802 subdirectory = base_subdirectory_name
803 counter = 1
804 while os.path.exists(os.path.join(self.resultdir, subdirectory)):
805 subdirectory = base_subdirectory_name + '.' + str(counter)
806 counter += 1
807 return subdirectory
808
809
jadmanski52053632010-06-11 21:08:10 +0000810 def get_record_context(self):
811 """Returns an object representing the current job.record context.
812
813 The object returned is an opaque object with a 0-arg restore method
814 which can be called to restore the job.record context (i.e. indentation)
815 to the current level. The intention is that it should be used when
816 something external which generate job.record calls (e.g. an autotest
817 client) can fail catastrophically and the server job record state
818 needs to be reset to its original "known good" state.
819
820 @return: A context object with a 0-arg restore() method."""
821 return self._indenter.get_context()
822
823
showardcc929362010-01-25 21:20:41 +0000824 def record_summary(self, status_code, test_name, reason='', attributes=None,
825 distinguishing_attributes=(), child_test_ids=None):
826 """Record a summary test result.
827
828 @param status_code: status code string, see
829 common_lib.log.is_valid_status()
830 @param test_name: name of the test
831 @param reason: (optional) string providing detailed reason for test
832 outcome
833 @param attributes: (optional) dict of string keyvals to associate with
834 this result
835 @param distinguishing_attributes: (optional) list of attribute names
836 that should be used to distinguish identically-named test
837 results. These attributes should be present in the attributes
838 parameter. This is used to generate user-friendly subdirectory
839 names.
840 @param child_test_ids: (optional) list of test indices for test results
841 used in generating this result.
842 """
843 subdirectory_name_parts = [test_name]
844 for attribute in distinguishing_attributes:
845 assert attributes
846 assert attribute in attributes, '%s not in %s' % (attribute,
847 attributes)
848 subdirectory_name_parts.append(attributes[attribute])
849 base_subdirectory_name = '.'.join(subdirectory_name_parts)
850
851 subdirectory = self._unique_subdirectory(base_subdirectory_name)
852 subdirectory_path = os.path.join(self.resultdir, subdirectory)
853 os.mkdir(subdirectory_path)
854
855 self.record(status_code, subdirectory, test_name,
856 status=reason, optional_fields={'is_summary': True})
857
858 if attributes:
859 utils.write_keyval(subdirectory_path, attributes)
860
861 if child_test_ids:
862 ids_string = ','.join(str(test_id) for test_id in child_test_ids)
863 summary_data = {'child_test_ids': ids_string}
864 utils.write_keyval(os.path.join(subdirectory_path, 'summary_data'),
865 summary_data)
866
867
jadmanski16a7ff72009-04-01 18:19:53 +0000868 def disable_warnings(self, warning_type):
jadmanskif37df842009-02-11 00:03:26 +0000869 self.warning_manager.disable_warnings(warning_type)
jadmanski16a7ff72009-04-01 18:19:53 +0000870 self.record("INFO", None, None,
871 "disabling %s warnings" % warning_type,
872 {"warnings.disable": warning_type})
jadmanskif37df842009-02-11 00:03:26 +0000873
874
jadmanski16a7ff72009-04-01 18:19:53 +0000875 def enable_warnings(self, warning_type):
jadmanskif37df842009-02-11 00:03:26 +0000876 self.warning_manager.enable_warnings(warning_type)
jadmanski16a7ff72009-04-01 18:19:53 +0000877 self.record("INFO", None, None,
878 "enabling %s warnings" % warning_type,
879 {"warnings.enable": warning_type})
jadmanskif37df842009-02-11 00:03:26 +0000880
881
jadmanski779bd292009-03-19 17:33:33 +0000882 def get_status_log_path(self, subdir=None):
883 """Return the path to the job status log.
884
885 @param subdir - Optional paramter indicating that you want the path
886 to a subdirectory status log.
887
888 @returns The path where the status log should be.
889 """
mbligh210bae62009-04-01 18:33:13 +0000890 if self.resultdir:
891 if subdir:
892 return os.path.join(self.resultdir, subdir, "status.log")
893 else:
894 return os.path.join(self.resultdir, "status.log")
jadmanski779bd292009-03-19 17:33:33 +0000895 else:
mbligh210bae62009-04-01 18:33:13 +0000896 return None
jadmanski779bd292009-03-19 17:33:33 +0000897
898
jadmanski6bb32d72009-03-19 20:25:24 +0000899 def _update_uncollected_logs_list(self, update_func):
900 """Updates the uncollected logs list in a multi-process safe manner.
901
902 @param update_func - a function that updates the list of uncollected
903 logs. Should take one parameter, the list to be updated.
904 """
Dan Shi07e09af2013-04-12 09:31:29 -0700905 # Skip log collection if file _uncollected_log_file does not exist.
906 if not (self._uncollected_log_file and
907 os.path.exists(self._uncollected_log_file)):
908 return
mbligh0d0f67d2009-11-06 03:15:03 +0000909 if self._uncollected_log_file:
910 log_file = open(self._uncollected_log_file, "r+")
mbligha788dc42009-03-26 21:10:16 +0000911 fcntl.flock(log_file, fcntl.LOCK_EX)
jadmanski6bb32d72009-03-19 20:25:24 +0000912 try:
913 uncollected_logs = pickle.load(log_file)
914 update_func(uncollected_logs)
915 log_file.seek(0)
916 log_file.truncate()
917 pickle.dump(uncollected_logs, log_file)
jadmanski3bff9092009-04-22 18:09:47 +0000918 log_file.flush()
jadmanski6bb32d72009-03-19 20:25:24 +0000919 finally:
920 fcntl.flock(log_file, fcntl.LOCK_UN)
921 log_file.close()
922
923
924 def add_client_log(self, hostname, remote_path, local_path):
925 """Adds a new set of client logs to the list of uncollected logs,
926 to allow for future log recovery.
927
928 @param host - the hostname of the machine holding the logs
929 @param remote_path - the directory on the remote machine holding logs
930 @param local_path - the local directory to copy the logs into
931 """
932 def update_func(logs_list):
933 logs_list.append((hostname, remote_path, local_path))
934 self._update_uncollected_logs_list(update_func)
935
936
937 def remove_client_log(self, hostname, remote_path, local_path):
938 """Removes a set of client logs from the list of uncollected logs,
939 to allow for future log recovery.
940
941 @param host - the hostname of the machine holding the logs
942 @param remote_path - the directory on the remote machine holding logs
943 @param local_path - the local directory to copy the logs into
944 """
945 def update_func(logs_list):
946 logs_list.remove((hostname, remote_path, local_path))
947 self._update_uncollected_logs_list(update_func)
948
949
mbligh0d0f67d2009-11-06 03:15:03 +0000950 def get_client_logs(self):
951 """Retrieves the list of uncollected logs, if it exists.
952
953 @returns A list of (host, remote_path, local_path) tuples. Returns
954 an empty list if no uncollected logs file exists.
955 """
956 log_exists = (self._uncollected_log_file and
957 os.path.exists(self._uncollected_log_file))
958 if log_exists:
959 return pickle.load(open(self._uncollected_log_file))
960 else:
961 return []
962
963
mbligh084bc172008-10-18 14:02:45 +0000964 def _fill_server_control_namespace(self, namespace, protect=True):
mbligh2b92b862008-11-22 13:25:32 +0000965 """
966 Prepare a namespace to be used when executing server control files.
mbligh084bc172008-10-18 14:02:45 +0000967
968 This sets up the control file API by importing modules and making them
969 available under the appropriate names within namespace.
970
971 For use by _execute_code().
972
973 Args:
974 namespace: The namespace dictionary to fill in.
975 protect: Boolean. If True (the default) any operation that would
976 clobber an existing entry in namespace will cause an error.
977 Raises:
978 error.AutoservError: When a name would be clobbered by import.
979 """
980 def _import_names(module_name, names=()):
mbligh2b92b862008-11-22 13:25:32 +0000981 """
982 Import a module and assign named attributes into namespace.
mbligh084bc172008-10-18 14:02:45 +0000983
984 Args:
985 module_name: The string module name.
986 names: A limiting list of names to import from module_name. If
987 empty (the default), all names are imported from the module
988 similar to a "from foo.bar import *" statement.
989 Raises:
990 error.AutoservError: When a name being imported would clobber
991 a name already in namespace.
992 """
993 module = __import__(module_name, {}, {}, names)
994
995 # No names supplied? Import * from the lowest level module.
996 # (Ugh, why do I have to implement this part myself?)
997 if not names:
998 for submodule_name in module_name.split('.')[1:]:
999 module = getattr(module, submodule_name)
1000 if hasattr(module, '__all__'):
1001 names = getattr(module, '__all__')
1002 else:
1003 names = dir(module)
1004
1005 # Install each name into namespace, checking to make sure it
1006 # doesn't override anything that already exists.
1007 for name in names:
1008 # Check for conflicts to help prevent future problems.
1009 if name in namespace and protect:
1010 if namespace[name] is not getattr(module, name):
1011 raise error.AutoservError('importing name '
1012 '%s from %s %r would override %r' %
1013 (name, module_name, getattr(module, name),
1014 namespace[name]))
1015 else:
1016 # Encourage cleanliness and the use of __all__ for a
1017 # more concrete API with less surprises on '*' imports.
1018 warnings.warn('%s (%r) being imported from %s for use '
1019 'in server control files is not the '
1020 'first occurrance of that import.' %
1021 (name, namespace[name], module_name))
1022
1023 namespace[name] = getattr(module, name)
1024
1025
1026 # This is the equivalent of prepending a bunch of import statements to
1027 # the front of the control script.
mbligha2b07dd2009-06-22 18:26:13 +00001028 namespace.update(os=os, sys=sys, logging=logging)
mbligh084bc172008-10-18 14:02:45 +00001029 _import_names('autotest_lib.server',
1030 ('hosts', 'autotest', 'kvm', 'git', 'standalone_profiler',
1031 'source_kernel', 'rpm_kernel', 'deb_kernel', 'git_kernel'))
1032 _import_names('autotest_lib.server.subcommand',
1033 ('parallel', 'parallel_simple', 'subcommand'))
1034 _import_names('autotest_lib.server.utils',
1035 ('run', 'get_tmp_dir', 'sh_escape', 'parse_machine'))
1036 _import_names('autotest_lib.client.common_lib.error')
1037 _import_names('autotest_lib.client.common_lib.barrier', ('barrier',))
1038
1039 # Inject ourself as the job object into other classes within the API.
1040 # (Yuck, this injection is a gross thing be part of a public API. -gps)
1041 #
1042 # XXX Base & SiteAutotest do not appear to use .job. Who does?
1043 namespace['autotest'].Autotest.job = self
1044 # server.hosts.base_classes.Host uses .job.
1045 namespace['hosts'].Host.job = self
Eric Li10222b82010-11-24 09:33:15 -08001046 namespace['hosts'].factory.ssh_user = self._ssh_user
1047 namespace['hosts'].factory.ssh_port = self._ssh_port
1048 namespace['hosts'].factory.ssh_pass = self._ssh_pass
mbligh084bc172008-10-18 14:02:45 +00001049
1050
1051 def _execute_code(self, code_file, namespace, protect=True):
mbligh2b92b862008-11-22 13:25:32 +00001052 """
1053 Execute code using a copy of namespace as a server control script.
mbligh084bc172008-10-18 14:02:45 +00001054
1055 Unless protect_namespace is explicitly set to False, the dict will not
1056 be modified.
1057
1058 Args:
1059 code_file: The filename of the control file to execute.
1060 namespace: A dict containing names to make available during execution.
1061 protect: Boolean. If True (the default) a copy of the namespace dict
1062 is used during execution to prevent the code from modifying its
1063 contents outside of this function. If False the raw dict is
1064 passed in and modifications will be allowed.
1065 """
1066 if protect:
1067 namespace = namespace.copy()
1068 self._fill_server_control_namespace(namespace, protect=protect)
1069 # TODO: Simplify and get rid of the special cases for only 1 machine.
showard3e66e8c2008-10-27 19:20:51 +00001070 if len(self.machines) > 1:
mbligh084bc172008-10-18 14:02:45 +00001071 machines_text = '\n'.join(self.machines) + '\n'
1072 # Only rewrite the file if it does not match our machine list.
1073 try:
1074 machines_f = open(MACHINES_FILENAME, 'r')
1075 existing_machines_text = machines_f.read()
1076 machines_f.close()
1077 except EnvironmentError:
1078 existing_machines_text = None
1079 if machines_text != existing_machines_text:
1080 utils.open_write_close(MACHINES_FILENAME, machines_text)
1081 execfile(code_file, namespace, namespace)
jadmanski10646442008-08-13 14:05:21 +00001082
1083
jadmanskie29d0e42010-06-17 16:06:52 +00001084 def _parse_status(self, new_line):
mbligh0d0f67d2009-11-06 03:15:03 +00001085 if not self._using_parser:
jadmanski10646442008-08-13 14:05:21 +00001086 return
jadmanskie29d0e42010-06-17 16:06:52 +00001087 new_tests = self.parser.process_lines([new_line])
jadmanski10646442008-08-13 14:05:21 +00001088 for test in new_tests:
1089 self.__insert_test(test)
1090
1091
1092 def __insert_test(self, test):
mbligh2b92b862008-11-22 13:25:32 +00001093 """
1094 An internal method to insert a new test result into the
jadmanski10646442008-08-13 14:05:21 +00001095 database. This method will not raise an exception, even if an
1096 error occurs during the insert, to avoid failing a test
1097 simply because of unexpected database issues."""
showard21baa452008-10-21 00:08:39 +00001098 self.num_tests_run += 1
1099 if status_lib.is_worse_than_or_equal_to(test.status, 'FAIL'):
1100 self.num_tests_failed += 1
jadmanski10646442008-08-13 14:05:21 +00001101 try:
1102 self.results_db.insert_test(self.job_model, test)
1103 except Exception:
1104 msg = ("WARNING: An unexpected error occured while "
1105 "inserting test results into the database. "
1106 "Ignoring error.\n" + traceback.format_exc())
1107 print >> sys.stderr, msg
1108
mblighcaa62c22008-04-07 21:51:17 +00001109
mblighfc3da5b2010-01-06 18:37:22 +00001110 def preprocess_client_state(self):
1111 """
1112 Produce a state file for initializing the state of a client job.
1113
1114 Creates a new client state file with all the current server state, as
1115 well as some pre-set client state.
1116
1117 @returns The path of the file the state was written into.
1118 """
1119 # initialize the sysinfo state
1120 self._state.set('client', 'sysinfo', self.sysinfo.serialize())
1121
1122 # dump the state out to a tempfile
1123 fd, file_path = tempfile.mkstemp(dir=self.tmpdir)
1124 os.close(fd)
mbligha2c99492010-01-27 22:59:50 +00001125
1126 # write_to_file doesn't need locking, we exclusively own file_path
mblighfc3da5b2010-01-06 18:37:22 +00001127 self._state.write_to_file(file_path)
1128 return file_path
1129
1130
1131 def postprocess_client_state(self, state_path):
1132 """
1133 Update the state of this job with the state from a client job.
1134
1135 Updates the state of the server side of a job with the final state
1136 of a client job that was run. Updates the non-client-specific state,
1137 pulls in some specific bits from the client-specific state, and then
1138 discards the rest. Removes the state file afterwards
1139
1140 @param state_file A path to the state file from the client.
1141 """
1142 # update the on-disk state
mblighfc3da5b2010-01-06 18:37:22 +00001143 try:
jadmanskib6e7bdb2010-04-13 16:00:39 +00001144 self._state.read_from_file(state_path)
mblighfc3da5b2010-01-06 18:37:22 +00001145 os.remove(state_path)
mbligha2c99492010-01-27 22:59:50 +00001146 except OSError, e:
mblighfc3da5b2010-01-06 18:37:22 +00001147 # ignore file-not-found errors
1148 if e.errno != errno.ENOENT:
1149 raise
jadmanskib6e7bdb2010-04-13 16:00:39 +00001150 else:
1151 logging.debug('Client state file %s not found', state_path)
mblighfc3da5b2010-01-06 18:37:22 +00001152
1153 # update the sysinfo state
1154 if self._state.has('client', 'sysinfo'):
1155 self.sysinfo.deserialize(self._state.get('client', 'sysinfo'))
1156
1157 # drop all the client-specific state
1158 self._state.discard_namespace('client')
1159
1160
mbligh0a883702010-04-21 01:58:34 +00001161 def clear_all_known_hosts(self):
1162 """Clears known hosts files for all AbstractSSHHosts."""
1163 for host in self.hosts:
1164 if isinstance(host, abstract_ssh.AbstractSSHHost):
1165 host.clear_known_hosts()
1166
1167
jadmanskif37df842009-02-11 00:03:26 +00001168class warning_manager(object):
1169 """Class for controlling warning logs. Manages the enabling and disabling
1170 of warnings."""
1171 def __init__(self):
1172 # a map of warning types to a list of disabled time intervals
1173 self.disabled_warnings = {}
1174
1175
1176 def is_valid(self, timestamp, warning_type):
1177 """Indicates if a warning (based on the time it occured and its type)
1178 is a valid warning. A warning is considered "invalid" if this type of
1179 warning was marked as "disabled" at the time the warning occured."""
1180 disabled_intervals = self.disabled_warnings.get(warning_type, [])
1181 for start, end in disabled_intervals:
1182 if timestamp >= start and (end is None or timestamp < end):
1183 return False
1184 return True
1185
1186
1187 def disable_warnings(self, warning_type, current_time_func=time.time):
1188 """As of now, disables all further warnings of this type."""
1189 intervals = self.disabled_warnings.setdefault(warning_type, [])
1190 if not intervals or intervals[-1][1] is not None:
jadmanski16a7ff72009-04-01 18:19:53 +00001191 intervals.append((int(current_time_func()), None))
jadmanskif37df842009-02-11 00:03:26 +00001192
1193
1194 def enable_warnings(self, warning_type, current_time_func=time.time):
1195 """As of now, enables all further warnings of this type."""
1196 intervals = self.disabled_warnings.get(warning_type, [])
1197 if intervals and intervals[-1][1] is None:
jadmanski16a7ff72009-04-01 18:19:53 +00001198 intervals[-1] = (intervals[-1][0], int(current_time_func()))
Paul Pendlebury57593562011-06-15 10:45:49 -07001199
1200
1201# load up site-specific code for generating site-specific job data
1202get_site_job_data = utils.import_site_function(__file__,
1203 "autotest_lib.server.site_server_job", "get_site_job_data",
1204 _get_site_job_data_dummy)
1205
1206
1207site_server_job = utils.import_site_class(
1208 __file__, "autotest_lib.server.site_server_job", "site_server_job",
1209 base_server_job)
1210
1211
1212class server_job(site_server_job):
Dale Curtis456d3c12011-07-19 11:42:51 -07001213 pass