blob: 34b9925a44c01da25f055b19ef4622fe28ede229 [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
beepsd0672682013-09-16 17:32:16 -070021from autotest_lib.server.hosts import abstract_ssh, factory as host_factory
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
mbligh062ed152009-01-13 00:57:14 +000046# by default provide a stub that generates no site data
47def _get_site_job_data_dummy(job):
48 return {}
49
50
jadmanski2a89dac2010-06-11 14:32:58 +000051class status_indenter(base_job.status_indenter):
52 """Provide a simple integer-backed status indenter."""
53 def __init__(self):
54 self._indent = 0
55
56
57 @property
58 def indent(self):
59 return self._indent
60
61
62 def increment(self):
63 self._indent += 1
64
65
66 def decrement(self):
67 self._indent -= 1
68
69
jadmanski52053632010-06-11 21:08:10 +000070 def get_context(self):
71 """Returns a context object for use by job.get_record_context."""
72 class context(object):
73 def __init__(self, indenter, indent):
74 self._indenter = indenter
75 self._indent = indent
76 def restore(self):
77 self._indenter._indent = self._indent
78 return context(self, self._indent)
79
80
jadmanski2a89dac2010-06-11 14:32:58 +000081class server_job_record_hook(object):
82 """The job.record hook for server job. Used to inject WARN messages from
83 the console or vlm whenever new logs are written, and to echo any logs
84 to INFO level logging. Implemented as a class so that it can use state to
85 block recursive calls, so that the hook can call job.record itself to
86 log WARN messages.
87
88 Depends on job._read_warnings and job._logger.
89 """
90 def __init__(self, job):
91 self._job = job
92 self._being_called = False
93
94
95 def __call__(self, entry):
96 """A wrapper around the 'real' record hook, the _hook method, which
97 prevents recursion. This isn't making any effort to be threadsafe,
98 the intent is to outright block infinite recursion via a
99 job.record->_hook->job.record->_hook->job.record... chain."""
100 if self._being_called:
101 return
102 self._being_called = True
103 try:
104 self._hook(self._job, entry)
105 finally:
106 self._being_called = False
107
108
109 @staticmethod
110 def _hook(job, entry):
111 """The core hook, which can safely call job.record."""
112 entries = []
113 # poll all our warning loggers for new warnings
114 for timestamp, msg in job._read_warnings():
115 warning_entry = base_job.status_log_entry(
116 'WARN', None, None, msg, {}, timestamp=timestamp)
117 entries.append(warning_entry)
118 job.record_entry(warning_entry)
119 # echo rendered versions of all the status logs to info
120 entries.append(entry)
121 for entry in entries:
122 rendered_entry = job._logger.render_entry(entry)
123 logging.info(rendered_entry)
jadmanskie29d0e42010-06-17 16:06:52 +0000124 job._parse_status(rendered_entry)
jadmanski2a89dac2010-06-11 14:32:58 +0000125
126
mbligh0d0f67d2009-11-06 03:15:03 +0000127class base_server_job(base_job.base_job):
128 """The server-side concrete implementation of base_job.
jadmanski10646442008-08-13 14:05:21 +0000129
mbligh0d0f67d2009-11-06 03:15:03 +0000130 Optional properties provided by this implementation:
131 serverdir
132 conmuxdir
133
134 num_tests_run
135 num_tests_failed
136
137 warning_manager
138 warning_loggers
jadmanski10646442008-08-13 14:05:21 +0000139 """
140
mbligh0d0f67d2009-11-06 03:15:03 +0000141 _STATUS_VERSION = 1
jadmanski10646442008-08-13 14:05:21 +0000142
Aviv Keshetc5947fa2013-09-04 14:06:29 -0700143 # TODO crbug.com/285395 eliminate ssh_verbosity_flag
jadmanski10646442008-08-13 14:05:21 +0000144 def __init__(self, control, args, resultdir, label, user, machines,
145 client=False, parse_job='',
beepsd0672682013-09-16 17:32:16 -0700146 ssh_user=host_factory.DEFAULT_SSH_USER,
147 ssh_port=host_factory.DEFAULT_SSH_PORT,
148 ssh_pass=host_factory.DEFAULT_SSH_PASS,
149 ssh_verbosity_flag=host_factory.DEFAULT_SSH_VERBOSITY,
150 ssh_options=host_factory.DEFAULT_SSH_OPTIONS,
Aviv Keshetc5947fa2013-09-04 14:06:29 -0700151 test_retry=0, group_name='',
Fang Dengd1c2b732013-08-20 12:59:46 -0700152 tag='', disable_sysinfo=False,
mblighe0cbc912010-03-11 18:03:07 +0000153 control_filename=SERVER_CONTROL_FILENAME):
jadmanski10646442008-08-13 14:05:21 +0000154 """
mbligh374f3412009-05-13 21:29:45 +0000155 Create a server side job object.
mblighb5dac432008-11-27 00:38:44 +0000156
mblighe7d9c602009-07-02 19:02:33 +0000157 @param control: The pathname of the control file.
158 @param args: Passed to the control file.
159 @param resultdir: Where to throw the results.
160 @param label: Description of the job.
161 @param user: Username for the job (email address).
162 @param client: True if this is a client-side control file.
163 @param parse_job: string, if supplied it is the job execution tag that
164 the results will be passed through to the TKO parser with.
165 @param ssh_user: The SSH username. [root]
166 @param ssh_port: The SSH port number. [22]
167 @param ssh_pass: The SSH passphrase, if needed.
Fang Dengd1c2b732013-08-20 12:59:46 -0700168 @param ssh_verbosity_flag: The SSH verbosity flag, '-v', '-vv',
169 '-vvv', or an empty string if not needed.
Aviv Keshetc5947fa2013-09-04 14:06:29 -0700170 @param ssh_options: A string giving additional options that will be
171 included in ssh commands.
Scott Zawalski91493c82013-01-25 16:15:20 -0500172 @param test_retry: The number of times to retry a test if the test did
173 not complete successfully.
mblighe7d9c602009-07-02 19:02:33 +0000174 @param group_name: If supplied, this will be written out as
mbligh374f3412009-05-13 21:29:45 +0000175 host_group_name in the keyvals file for the parser.
mblighe7d9c602009-07-02 19:02:33 +0000176 @param tag: The job execution tag from the scheduler. [optional]
Christopher Wiley8a91f232013-07-09 11:02:27 -0700177 @param disable_sysinfo: Whether we should disable the sysinfo step of
178 tests for a modest shortening of test time. [optional]
mblighe0cbc912010-03-11 18:03:07 +0000179 @param control_filename: The filename where the server control file
180 should be written in the results directory.
jadmanski10646442008-08-13 14:05:21 +0000181 """
Scott Zawalski91493c82013-01-25 16:15:20 -0500182 super(base_server_job, self).__init__(resultdir=resultdir,
183 test_retry=test_retry)
mbligh0d0f67d2009-11-06 03:15:03 +0000184 path = os.path.dirname(__file__)
Scott Zawalski91493c82013-01-25 16:15:20 -0500185 self.test_retry = test_retry
mbligh0d0f67d2009-11-06 03:15:03 +0000186 self.control = control
187 self._uncollected_log_file = os.path.join(self.resultdir,
188 'uncollected_logs')
189 debugdir = os.path.join(self.resultdir, 'debug')
190 if not os.path.exists(debugdir):
191 os.mkdir(debugdir)
192
193 if user:
194 self.user = user
195 else:
196 self.user = getpass.getuser()
197
jadmanski808f4b12010-04-09 22:30:31 +0000198 self.args = args
Peter Mayo7a875762012-06-13 14:38:15 -0400199 self.label = label
jadmanski10646442008-08-13 14:05:21 +0000200 self.machines = machines
mbligh0d0f67d2009-11-06 03:15:03 +0000201 self._client = client
jadmanski10646442008-08-13 14:05:21 +0000202 self.warning_loggers = set()
jadmanskif37df842009-02-11 00:03:26 +0000203 self.warning_manager = warning_manager()
mbligh0d0f67d2009-11-06 03:15:03 +0000204 self._ssh_user = ssh_user
205 self._ssh_port = ssh_port
206 self._ssh_pass = ssh_pass
Fang Dengd1c2b732013-08-20 12:59:46 -0700207 self._ssh_verbosity_flag = ssh_verbosity_flag
Aviv Keshetc5947fa2013-09-04 14:06:29 -0700208 self._ssh_options = ssh_options
mblighe7d9c602009-07-02 19:02:33 +0000209 self.tag = tag
mbligh09108442008-10-15 16:27:38 +0000210 self.last_boot_tag = None
jadmanski53aaf382008-11-17 16:22:31 +0000211 self.hosts = set()
mbligh0d0f67d2009-11-06 03:15:03 +0000212 self.drop_caches = False
mblighb5dac432008-11-27 00:38:44 +0000213 self.drop_caches_between_iterations = False
mblighe0cbc912010-03-11 18:03:07 +0000214 self._control_filename = control_filename
Christopher Wiley8a91f232013-07-09 11:02:27 -0700215 self._disable_sysinfo = disable_sysinfo
jadmanski10646442008-08-13 14:05:21 +0000216
showard75cdfee2009-06-10 17:40:41 +0000217 self.logging = logging_manager.get_logging_manager(
218 manage_stdout_and_stderr=True, redirect_fds=True)
219 subcommand.logging_manager_object = self.logging
jadmanski10646442008-08-13 14:05:21 +0000220
mbligh0d0f67d2009-11-06 03:15:03 +0000221 self.sysinfo = sysinfo.sysinfo(self.resultdir)
jadmanski043e1132008-11-19 17:10:32 +0000222 self.profilers = profilers.profilers(self)
jadmanskic09fc152008-10-15 17:56:59 +0000223
jadmanski10646442008-08-13 14:05:21 +0000224 job_data = {'label' : label, 'user' : user,
225 'hostname' : ','.join(machines),
Eric Li861b2d52011-02-04 14:50:35 -0800226 'drone' : platform.node(),
mbligh0d0f67d2009-11-06 03:15:03 +0000227 'status_version' : str(self._STATUS_VERSION),
showard170873e2009-01-07 00:22:26 +0000228 'job_started' : str(int(time.time()))}
mbligh374f3412009-05-13 21:29:45 +0000229 if group_name:
230 job_data['host_group_name'] = group_name
jadmanski10646442008-08-13 14:05:21 +0000231
mbligh0d0f67d2009-11-06 03:15:03 +0000232 # only write these keyvals out on the first job in a resultdir
233 if 'job_started' not in utils.read_keyval(self.resultdir):
234 job_data.update(get_site_job_data(self))
235 utils.write_keyval(self.resultdir, job_data)
236
237 self._parse_job = parse_job
showardcc929362010-01-25 21:20:41 +0000238 self._using_parser = (self._parse_job and len(machines) <= 1)
mbligh0d0f67d2009-11-06 03:15:03 +0000239 self.pkgmgr = packages.PackageManager(
240 self.autodir, run_function_dargs={'timeout':600})
showard21baa452008-10-21 00:08:39 +0000241 self.num_tests_run = 0
242 self.num_tests_failed = 0
243
jadmanski550fdc22008-11-20 16:32:08 +0000244 self._register_subcommand_hooks()
245
mbligh0d0f67d2009-11-06 03:15:03 +0000246 # these components aren't usable on the server
247 self.bootloader = None
248 self.harness = None
249
jadmanski2a89dac2010-06-11 14:32:58 +0000250 # set up the status logger
jadmanski52053632010-06-11 21:08:10 +0000251 self._indenter = status_indenter()
jadmanski2a89dac2010-06-11 14:32:58 +0000252 self._logger = base_job.status_logger(
jadmanski52053632010-06-11 21:08:10 +0000253 self, self._indenter, 'status.log', 'status.log',
jadmanski2a89dac2010-06-11 14:32:58 +0000254 record_hook=server_job_record_hook(self))
255
Dan Shib03ea9d2013-08-15 17:13:27 -0700256 # Initialize a flag to indicate DUT failure during the test, e.g.,
257 # unexpected reboot.
258 self.failed_with_device_error = False
259
mbligh0d0f67d2009-11-06 03:15:03 +0000260
261 @classmethod
262 def _find_base_directories(cls):
263 """
264 Determine locations of autodir, clientdir and serverdir. Assumes
265 that this file is located within serverdir and uses __file__ along
266 with relative paths to resolve the location.
267 """
268 serverdir = os.path.abspath(os.path.dirname(__file__))
269 autodir = os.path.normpath(os.path.join(serverdir, '..'))
270 clientdir = os.path.join(autodir, 'client')
271 return autodir, clientdir, serverdir
272
273
Scott Zawalski91493c82013-01-25 16:15:20 -0500274 def _find_resultdir(self, resultdir, *args, **dargs):
mbligh0d0f67d2009-11-06 03:15:03 +0000275 """
276 Determine the location of resultdir. For server jobs we expect one to
277 always be explicitly passed in to __init__, so just return that.
278 """
279 if resultdir:
280 return os.path.normpath(resultdir)
281 else:
282 return None
283
jadmanski550fdc22008-11-20 16:32:08 +0000284
jadmanski2a89dac2010-06-11 14:32:58 +0000285 def _get_status_logger(self):
286 """Return a reference to the status logger."""
287 return self._logger
288
289
jadmanskie432dd22009-01-30 15:04:51 +0000290 @staticmethod
291 def _load_control_file(path):
292 f = open(path)
293 try:
294 control_file = f.read()
295 finally:
296 f.close()
297 return re.sub('\r', '', control_file)
298
299
jadmanski550fdc22008-11-20 16:32:08 +0000300 def _register_subcommand_hooks(self):
mbligh2b92b862008-11-22 13:25:32 +0000301 """
302 Register some hooks into the subcommand modules that allow us
303 to properly clean up self.hosts created in forked subprocesses.
304 """
jadmanski550fdc22008-11-20 16:32:08 +0000305 def on_fork(cmd):
306 self._existing_hosts_on_fork = set(self.hosts)
307 def on_join(cmd):
308 new_hosts = self.hosts - self._existing_hosts_on_fork
309 for host in new_hosts:
310 host.close()
311 subcommand.subcommand.register_fork_hook(on_fork)
312 subcommand.subcommand.register_join_hook(on_join)
313
jadmanski10646442008-08-13 14:05:21 +0000314
mbligh4608b002010-01-05 18:22:35 +0000315 def init_parser(self):
mbligh2b92b862008-11-22 13:25:32 +0000316 """
mbligh4608b002010-01-05 18:22:35 +0000317 Start the continuous parsing of self.resultdir. This sets up
jadmanski10646442008-08-13 14:05:21 +0000318 the database connection and inserts the basic job object into
mbligh2b92b862008-11-22 13:25:32 +0000319 the database if necessary.
320 """
mbligh4608b002010-01-05 18:22:35 +0000321 if not self._using_parser:
322 return
jadmanski10646442008-08-13 14:05:21 +0000323 # redirect parser debugging to .parse.log
mbligh4608b002010-01-05 18:22:35 +0000324 parse_log = os.path.join(self.resultdir, '.parse.log')
jadmanski10646442008-08-13 14:05:21 +0000325 parse_log = open(parse_log, 'w', 0)
326 tko_utils.redirect_parser_debugging(parse_log)
327 # create a job model object and set up the db
328 self.results_db = tko_db.db(autocommit=True)
mbligh0d0f67d2009-11-06 03:15:03 +0000329 self.parser = status_lib.parser(self._STATUS_VERSION)
mbligh4608b002010-01-05 18:22:35 +0000330 self.job_model = self.parser.make_job(self.resultdir)
jadmanski10646442008-08-13 14:05:21 +0000331 self.parser.start(self.job_model)
332 # check if a job already exists in the db and insert it if
333 # it does not
mbligh0d0f67d2009-11-06 03:15:03 +0000334 job_idx = self.results_db.find_job(self._parse_job)
jadmanski10646442008-08-13 14:05:21 +0000335 if job_idx is None:
mbligh0d0f67d2009-11-06 03:15:03 +0000336 self.results_db.insert_job(self._parse_job, self.job_model)
jadmanski10646442008-08-13 14:05:21 +0000337 else:
mbligh2b92b862008-11-22 13:25:32 +0000338 machine_idx = self.results_db.lookup_machine(self.job_model.machine)
jadmanski10646442008-08-13 14:05:21 +0000339 self.job_model.index = job_idx
340 self.job_model.machine_idx = machine_idx
341
342
343 def cleanup_parser(self):
mbligh2b92b862008-11-22 13:25:32 +0000344 """
345 This should be called after the server job is finished
jadmanski10646442008-08-13 14:05:21 +0000346 to carry out any remaining cleanup (e.g. flushing any
mbligh2b92b862008-11-22 13:25:32 +0000347 remaining test results to the results db)
348 """
mbligh0d0f67d2009-11-06 03:15:03 +0000349 if not self._using_parser:
jadmanski10646442008-08-13 14:05:21 +0000350 return
351 final_tests = self.parser.end()
352 for test in final_tests:
353 self.__insert_test(test)
mbligh0d0f67d2009-11-06 03:15:03 +0000354 self._using_parser = False
jadmanski10646442008-08-13 14:05:21 +0000355
Aviv Keshetc5947fa2013-09-04 14:06:29 -0700356 # TODO crbug.com/285395 add a kwargs parameter.
357 def _make_namespace(self):
358 """Create a namespace dictionary to be passed along to control file.
359
360 Creates a namespace argument populated with standard values:
361 machines, job, ssh_user, ssh_port, ssh_pass, ssh_verbosity_flag,
362 and ssh_options.
363 """
364 namespace = {'machines' : self.machines,
365 'job' : self,
366 'ssh_user' : self._ssh_user,
367 'ssh_port' : self._ssh_port,
368 'ssh_pass' : self._ssh_pass,
369 'ssh_verbosity_flag' : self._ssh_verbosity_flag,
370 'ssh_options' : self._ssh_options}
371 return namespace
372
jadmanski10646442008-08-13 14:05:21 +0000373
374 def verify(self):
Dan Shi07e09af2013-04-12 09:31:29 -0700375 """Verify machines are all ssh-able."""
jadmanski10646442008-08-13 14:05:21 +0000376 if not self.machines:
mbligh084bc172008-10-18 14:02:45 +0000377 raise error.AutoservError('No machines specified to verify')
mbligh0fce4112008-11-27 00:37:17 +0000378 if self.resultdir:
379 os.chdir(self.resultdir)
jadmanski10646442008-08-13 14:05:21 +0000380 try:
Aviv Keshetc5947fa2013-09-04 14:06:29 -0700381 namespace = self._make_namespace()
mbligh084bc172008-10-18 14:02:45 +0000382 self._execute_code(VERIFY_CONTROL_FILE, namespace, protect=False)
jadmanski10646442008-08-13 14:05:21 +0000383 except Exception, e:
mbligh2b92b862008-11-22 13:25:32 +0000384 msg = ('Verify failed\n' + str(e) + '\n' + traceback.format_exc())
jadmanski10646442008-08-13 14:05:21 +0000385 self.record('ABORT', None, None, msg)
386 raise
387
388
Dan Shi07e09af2013-04-12 09:31:29 -0700389 def reset(self):
390 """Reset machines by first cleanup then verify each machine."""
391 if not self.machines:
392 raise error.AutoservError('No machines specified to reset.')
393 if self.resultdir:
394 os.chdir(self.resultdir)
395
396 try:
Aviv Keshetc5947fa2013-09-04 14:06:29 -0700397 namespace = self._make_namespace()
Dan Shi07e09af2013-04-12 09:31:29 -0700398 self._execute_code(RESET_CONTROL_FILE, namespace, protect=False)
399 except Exception as e:
400 msg = ('Reset failed\n' + str(e) + '\n' +
401 traceback.format_exc())
402 self.record('ABORT', None, None, msg)
403 raise
404
405
jadmanski10646442008-08-13 14:05:21 +0000406 def repair(self, host_protection):
407 if not self.machines:
408 raise error.AutoservError('No machines specified to repair')
mbligh0fce4112008-11-27 00:37:17 +0000409 if self.resultdir:
410 os.chdir(self.resultdir)
Aviv Keshetc5947fa2013-09-04 14:06:29 -0700411
412 namespace = self._make_namespace()
413 namespace.update({'protection_level' : host_protection})
mbligh25c0b8c2009-01-24 01:44:17 +0000414
mbligh0931b0a2009-04-08 17:44:48 +0000415 self._execute_code(REPAIR_CONTROL_FILE, namespace, protect=False)
jadmanski10646442008-08-13 14:05:21 +0000416
417
Alex Millercb79ba72013-05-29 14:43:00 -0700418 def provision(self, labels):
419 """
420 Provision all hosts to match |labels|.
421
422 @param labels: A comma seperated string of labels to provision the
423 host to.
424
425 """
426 namespace = {'provision_labels': labels}
427 control = self._load_control_file(PROVISION_CONTROL_FILE)
428 self.run(control=control, namespace=namespace)
429
430
jadmanski10646442008-08-13 14:05:21 +0000431 def precheck(self):
432 """
433 perform any additional checks in derived classes.
434 """
435 pass
436
437
438 def enable_external_logging(self):
mbligh2b92b862008-11-22 13:25:32 +0000439 """
440 Start or restart external logging mechanism.
jadmanski10646442008-08-13 14:05:21 +0000441 """
442 pass
443
444
445 def disable_external_logging(self):
mbligh2b92b862008-11-22 13:25:32 +0000446 """
447 Pause or stop external logging mechanism.
jadmanski10646442008-08-13 14:05:21 +0000448 """
449 pass
450
451
452 def use_external_logging(self):
mbligh2b92b862008-11-22 13:25:32 +0000453 """
454 Return True if external logging should be used.
jadmanski10646442008-08-13 14:05:21 +0000455 """
456 return False
457
458
mbligh415dc212009-06-15 21:53:34 +0000459 def _make_parallel_wrapper(self, function, machines, log):
460 """Wrap function as appropriate for calling by parallel_simple."""
mbligh2b92b862008-11-22 13:25:32 +0000461 is_forking = not (len(machines) == 1 and self.machines == machines)
mbligh0d0f67d2009-11-06 03:15:03 +0000462 if self._parse_job and is_forking and log:
jadmanski10646442008-08-13 14:05:21 +0000463 def wrapper(machine):
mbligh0d0f67d2009-11-06 03:15:03 +0000464 self._parse_job += "/" + machine
465 self._using_parser = True
jadmanski10646442008-08-13 14:05:21 +0000466 self.machines = [machine]
mbligh0d0f67d2009-11-06 03:15:03 +0000467 self.push_execution_context(machine)
jadmanski609a5f42008-08-26 20:52:42 +0000468 os.chdir(self.resultdir)
showard2bab8f42008-11-12 18:15:22 +0000469 utils.write_keyval(self.resultdir, {"hostname": machine})
mbligh4608b002010-01-05 18:22:35 +0000470 self.init_parser()
jadmanski10646442008-08-13 14:05:21 +0000471 result = function(machine)
472 self.cleanup_parser()
473 return result
jadmanski4dd1a002008-09-05 20:27:30 +0000474 elif len(machines) > 1 and log:
jadmanski10646442008-08-13 14:05:21 +0000475 def wrapper(machine):
mbligh0d0f67d2009-11-06 03:15:03 +0000476 self.push_execution_context(machine)
jadmanski609a5f42008-08-26 20:52:42 +0000477 os.chdir(self.resultdir)
mbligh838d82d2009-03-11 17:14:31 +0000478 machine_data = {'hostname' : machine,
mbligh0d0f67d2009-11-06 03:15:03 +0000479 'status_version' : str(self._STATUS_VERSION)}
mbligh838d82d2009-03-11 17:14:31 +0000480 utils.write_keyval(self.resultdir, machine_data)
jadmanski10646442008-08-13 14:05:21 +0000481 result = function(machine)
482 return result
483 else:
484 wrapper = function
mbligh415dc212009-06-15 21:53:34 +0000485 return wrapper
486
487
488 def parallel_simple(self, function, machines, log=True, timeout=None,
489 return_results=False):
490 """
491 Run 'function' using parallel_simple, with an extra wrapper to handle
492 the necessary setup for continuous parsing, if possible. If continuous
493 parsing is already properly initialized then this should just work.
494
495 @param function: A callable to run in parallel given each machine.
496 @param machines: A list of machine names to be passed one per subcommand
497 invocation of function.
498 @param log: If True, output will be written to output in a subdirectory
499 named after each machine.
500 @param timeout: Seconds after which the function call should timeout.
501 @param return_results: If True instead of an AutoServError being raised
502 on any error a list of the results|exceptions from the function
503 called on each arg is returned. [default: False]
504
505 @raises error.AutotestError: If any of the functions failed.
506 """
507 wrapper = self._make_parallel_wrapper(function, machines, log)
508 return subcommand.parallel_simple(wrapper, machines,
509 log=log, timeout=timeout,
510 return_results=return_results)
511
512
513 def parallel_on_machines(self, function, machines, timeout=None):
514 """
showardcd5fac42009-07-06 20:19:43 +0000515 @param function: Called in parallel with one machine as its argument.
mbligh415dc212009-06-15 21:53:34 +0000516 @param machines: A list of machines to call function(machine) on.
517 @param timeout: Seconds after which the function call should timeout.
518
519 @returns A list of machines on which function(machine) returned
520 without raising an exception.
521 """
showardcd5fac42009-07-06 20:19:43 +0000522 results = self.parallel_simple(function, machines, timeout=timeout,
mbligh415dc212009-06-15 21:53:34 +0000523 return_results=True)
524 success_machines = []
525 for result, machine in itertools.izip(results, machines):
526 if not isinstance(result, Exception):
527 success_machines.append(machine)
528 return success_machines
jadmanski10646442008-08-13 14:05:21 +0000529
530
mbligh0d0f67d2009-11-06 03:15:03 +0000531 _USE_TEMP_DIR = object()
mbligh2b92b862008-11-22 13:25:32 +0000532 def run(self, cleanup=False, install_before=False, install_after=False,
jadmanskie432dd22009-01-30 15:04:51 +0000533 collect_crashdumps=True, namespace={}, control=None,
beepscb6f1e22013-06-28 19:14:10 -0700534 control_file_dir=None, verify_job_repo_url=False,
Christopher Wileyf594c5e2013-07-03 18:25:30 -0700535 only_collect_crashinfo=False, skip_crash_collection=False):
jadmanskifb9c0fa2009-04-29 17:39:16 +0000536 # for a normal job, make sure the uncollected logs file exists
537 # for a crashinfo-only run it should already exist, bail out otherwise
jadmanski648c39f2010-03-19 17:38:01 +0000538 created_uncollected_logs = False
Alex Miller45554f32013-08-13 16:48:29 -0700539 logging.info("I am PID %s", os.getpid())
mbligh0d0f67d2009-11-06 03:15:03 +0000540 if self.resultdir and not os.path.exists(self._uncollected_log_file):
jadmanskifb9c0fa2009-04-29 17:39:16 +0000541 if only_collect_crashinfo:
542 # if this is a crashinfo-only run, and there were no existing
543 # uncollected logs, just bail out early
544 logging.info("No existing uncollected logs, "
545 "skipping crashinfo collection")
546 return
547 else:
mbligh0d0f67d2009-11-06 03:15:03 +0000548 log_file = open(self._uncollected_log_file, "w")
jadmanskifb9c0fa2009-04-29 17:39:16 +0000549 pickle.dump([], log_file)
550 log_file.close()
jadmanski648c39f2010-03-19 17:38:01 +0000551 created_uncollected_logs = True
jadmanskifb9c0fa2009-04-29 17:39:16 +0000552
jadmanski10646442008-08-13 14:05:21 +0000553 # use a copy so changes don't affect the original dictionary
554 namespace = namespace.copy()
555 machines = self.machines
jadmanskie432dd22009-01-30 15:04:51 +0000556 if control is None:
jadmanski02a3ba22009-11-13 20:47:27 +0000557 if self.control is None:
558 control = ''
559 else:
560 control = self._load_control_file(self.control)
jadmanskie432dd22009-01-30 15:04:51 +0000561 if control_file_dir is None:
562 control_file_dir = self.resultdir
jadmanski10646442008-08-13 14:05:21 +0000563
564 self.aborted = False
Aviv Keshetc5947fa2013-09-04 14:06:29 -0700565 namespace.update(self._make_namespace())
566 namespace.update({'args' : self.args})
jadmanski10646442008-08-13 14:05:21 +0000567 test_start_time = int(time.time())
568
mbligh80e1eba2008-11-19 00:26:18 +0000569 if self.resultdir:
570 os.chdir(self.resultdir)
jadmanski779bd292009-03-19 17:33:33 +0000571 # touch status.log so that the parser knows a job is running here
jadmanski382303a2009-04-21 19:53:39 +0000572 open(self.get_status_log_path(), 'a').close()
mbligh80e1eba2008-11-19 00:26:18 +0000573 self.enable_external_logging()
jadmanskie432dd22009-01-30 15:04:51 +0000574
jadmanskicdd0c402008-09-19 21:21:31 +0000575 collect_crashinfo = True
mblighaebe3b62008-12-22 14:45:40 +0000576 temp_control_file_dir = None
jadmanski10646442008-08-13 14:05:21 +0000577 try:
showardcf8d4922009-10-14 16:08:39 +0000578 try:
579 if install_before and machines:
580 self._execute_code(INSTALL_CONTROL_FILE, namespace)
jadmanskie432dd22009-01-30 15:04:51 +0000581
showardcf8d4922009-10-14 16:08:39 +0000582 if only_collect_crashinfo:
583 return
584
beepscb6f1e22013-06-28 19:14:10 -0700585 # If the verify_job_repo_url option is set but we're unable
586 # to actually verify that the job_repo_url contains the autotest
587 # package, this job will fail.
588 if verify_job_repo_url:
589 self._execute_code(VERIFY_JOB_REPO_URL_CONTROL_FILE,
590 namespace)
591 else:
592 logging.warning('Not checking if job_repo_url contains '
593 'autotest packages on %s', machines)
594
jadmanskidef0c3c2009-03-25 20:07:10 +0000595 # determine the dir to write the control files to
596 cfd_specified = (control_file_dir
mbligh0d0f67d2009-11-06 03:15:03 +0000597 and control_file_dir is not self._USE_TEMP_DIR)
jadmanskidef0c3c2009-03-25 20:07:10 +0000598 if cfd_specified:
599 temp_control_file_dir = None
600 else:
601 temp_control_file_dir = tempfile.mkdtemp(
602 suffix='temp_control_file_dir')
603 control_file_dir = temp_control_file_dir
604 server_control_file = os.path.join(control_file_dir,
mblighe0cbc912010-03-11 18:03:07 +0000605 self._control_filename)
jadmanskidef0c3c2009-03-25 20:07:10 +0000606 client_control_file = os.path.join(control_file_dir,
607 CLIENT_CONTROL_FILENAME)
mbligh0d0f67d2009-11-06 03:15:03 +0000608 if self._client:
jadmanskidef0c3c2009-03-25 20:07:10 +0000609 namespace['control'] = control
610 utils.open_write_close(client_control_file, control)
mblighfeac0102009-04-28 18:31:12 +0000611 shutil.copyfile(CLIENT_WRAPPER_CONTROL_FILE,
612 server_control_file)
jadmanskidef0c3c2009-03-25 20:07:10 +0000613 else:
614 utils.open_write_close(server_control_file, control)
mbligh26f0d882009-06-22 18:30:01 +0000615 logging.info("Processing control file")
jadmanskidef0c3c2009-03-25 20:07:10 +0000616 self._execute_code(server_control_file, namespace)
mbligh26f0d882009-06-22 18:30:01 +0000617 logging.info("Finished processing control file")
jadmanski10646442008-08-13 14:05:21 +0000618
Dan Shib03ea9d2013-08-15 17:13:27 -0700619 # If no device error occured, no need to collect crashinfo.
620 collect_crashinfo = self.failed_with_device_error
Eric Li6f27d4f2010-09-29 10:55:17 -0700621 except Exception, e:
showardcf8d4922009-10-14 16:08:39 +0000622 try:
623 logging.exception(
624 'Exception escaped control file, job aborting:')
Eric Li6f27d4f2010-09-29 10:55:17 -0700625 self.record('INFO', None, None, str(e),
626 {'job_abort_reason': str(e)})
showardcf8d4922009-10-14 16:08:39 +0000627 except:
628 pass # don't let logging exceptions here interfere
629 raise
jadmanski10646442008-08-13 14:05:21 +0000630 finally:
mblighaebe3b62008-12-22 14:45:40 +0000631 if temp_control_file_dir:
jadmanskie432dd22009-01-30 15:04:51 +0000632 # Clean up temp directory used for copies of the control files
mblighaebe3b62008-12-22 14:45:40 +0000633 try:
634 shutil.rmtree(temp_control_file_dir)
635 except Exception, e:
mblighe7d9c602009-07-02 19:02:33 +0000636 logging.warn('Could not remove temp directory %s: %s',
637 temp_control_file_dir, e)
jadmanskie432dd22009-01-30 15:04:51 +0000638
jadmanskicdd0c402008-09-19 21:21:31 +0000639 if machines and (collect_crashdumps or collect_crashinfo):
jadmanski10646442008-08-13 14:05:21 +0000640 namespace['test_start_time'] = test_start_time
Christopher Wileyf594c5e2013-07-03 18:25:30 -0700641 if skip_crash_collection:
642 logging.info('Skipping crash dump/info collection '
643 'as requested.')
644 elif collect_crashinfo:
mbligh084bc172008-10-18 14:02:45 +0000645 # includes crashdumps
646 self._execute_code(CRASHINFO_CONTROL_FILE, namespace)
jadmanskicdd0c402008-09-19 21:21:31 +0000647 else:
mbligh084bc172008-10-18 14:02:45 +0000648 self._execute_code(CRASHDUMPS_CONTROL_FILE, namespace)
jadmanski10646442008-08-13 14:05:21 +0000649 self.disable_external_logging()
showard45ae8192008-11-05 19:32:53 +0000650 if cleanup and machines:
651 self._execute_code(CLEANUP_CONTROL_FILE, namespace)
Chris Sosaf4d43ff2012-10-30 11:21:05 -0700652 if self._uncollected_log_file and created_uncollected_logs:
653 os.remove(self._uncollected_log_file)
jadmanski10646442008-08-13 14:05:21 +0000654 if install_after and machines:
mbligh084bc172008-10-18 14:02:45 +0000655 self._execute_code(INSTALL_CONTROL_FILE, namespace)
jadmanski10646442008-08-13 14:05:21 +0000656
657
658 def run_test(self, url, *args, **dargs):
mbligh2b92b862008-11-22 13:25:32 +0000659 """
660 Summon a test object and run it.
jadmanski10646442008-08-13 14:05:21 +0000661
662 tag
663 tag to add to testname
664 url
665 url of the test to run
666 """
Christopher Wiley8a91f232013-07-09 11:02:27 -0700667 if self._disable_sysinfo:
668 dargs['disable_sysinfo'] = True
669
mblighfc3da5b2010-01-06 18:37:22 +0000670 group, testname = self.pkgmgr.get_package_name(url, 'test')
671 testname, subdir, tag = self._build_tagged_test_name(testname, dargs)
672 outputdir = self._make_test_outputdir(subdir)
jadmanski10646442008-08-13 14:05:21 +0000673
674 def group_func():
675 try:
676 test.runtest(self, url, tag, args, dargs)
677 except error.TestBaseException, e:
678 self.record(e.exit_status, subdir, testname, str(e))
679 raise
680 except Exception, e:
681 info = str(e) + "\n" + traceback.format_exc()
682 self.record('FAIL', subdir, testname, info)
683 raise
684 else:
mbligh2b92b862008-11-22 13:25:32 +0000685 self.record('GOOD', subdir, testname, 'completed successfully')
jadmanskide292df2008-08-26 20:51:14 +0000686
687 result, exc_info = self._run_group(testname, subdir, group_func)
688 if exc_info and isinstance(exc_info[1], error.TestBaseException):
689 return False
690 elif exc_info:
691 raise exc_info[0], exc_info[1], exc_info[2]
692 else:
693 return True
jadmanski10646442008-08-13 14:05:21 +0000694
695
696 def _run_group(self, name, subdir, function, *args, **dargs):
697 """\
698 Underlying method for running something inside of a group.
699 """
jadmanskide292df2008-08-26 20:51:14 +0000700 result, exc_info = None, None
jadmanski10646442008-08-13 14:05:21 +0000701 try:
702 self.record('START', subdir, name)
jadmanski52053632010-06-11 21:08:10 +0000703 result = function(*args, **dargs)
jadmanski10646442008-08-13 14:05:21 +0000704 except error.TestBaseException, e:
jadmanskib88d6dc2009-01-10 00:33:18 +0000705 self.record("END %s" % e.exit_status, subdir, name)
jadmanskide292df2008-08-26 20:51:14 +0000706 exc_info = sys.exc_info()
jadmanski10646442008-08-13 14:05:21 +0000707 except Exception, e:
708 err_msg = str(e) + '\n'
709 err_msg += traceback.format_exc()
710 self.record('END ABORT', subdir, name, err_msg)
711 raise error.JobError(name + ' failed\n' + traceback.format_exc())
712 else:
713 self.record('END GOOD', subdir, name)
714
jadmanskide292df2008-08-26 20:51:14 +0000715 return result, exc_info
jadmanski10646442008-08-13 14:05:21 +0000716
717
718 def run_group(self, function, *args, **dargs):
719 """\
720 function:
721 subroutine to run
722 *args:
723 arguments for the function
724 """
725
726 name = function.__name__
727
728 # Allow the tag for the group to be specified.
729 tag = dargs.pop('tag', None)
730 if tag:
731 name = tag
732
jadmanskide292df2008-08-26 20:51:14 +0000733 return self._run_group(name, None, function, *args, **dargs)[0]
jadmanski10646442008-08-13 14:05:21 +0000734
735
736 def run_reboot(self, reboot_func, get_kernel_func):
737 """\
738 A specialization of run_group meant specifically for handling
739 a reboot. Includes support for capturing the kernel version
740 after the reboot.
741
742 reboot_func: a function that carries out the reboot
743
744 get_kernel_func: a function that returns a string
745 representing the kernel version.
746 """
jadmanski10646442008-08-13 14:05:21 +0000747 try:
748 self.record('START', None, 'reboot')
jadmanski10646442008-08-13 14:05:21 +0000749 reboot_func()
750 except Exception, e:
jadmanski10646442008-08-13 14:05:21 +0000751 err_msg = str(e) + '\n' + traceback.format_exc()
752 self.record('END FAIL', None, 'reboot', err_msg)
jadmanski4b51d542009-04-08 14:17:16 +0000753 raise
jadmanski10646442008-08-13 14:05:21 +0000754 else:
755 kernel = get_kernel_func()
jadmanski10646442008-08-13 14:05:21 +0000756 self.record('END GOOD', None, 'reboot',
Dale Curtis74a314b2011-06-23 14:55:46 -0700757 optional_fields={"kernel": kernel})
jadmanski10646442008-08-13 14:05:21 +0000758
759
jadmanskie432dd22009-01-30 15:04:51 +0000760 def run_control(self, path):
761 """Execute a control file found at path (relative to the autotest
762 path). Intended for executing a control file within a control file,
763 not for running the top-level job control file."""
764 path = os.path.join(self.autodir, path)
765 control_file = self._load_control_file(path)
mbligh0d0f67d2009-11-06 03:15:03 +0000766 self.run(control=control_file, control_file_dir=self._USE_TEMP_DIR)
jadmanskie432dd22009-01-30 15:04:51 +0000767
768
jadmanskic09fc152008-10-15 17:56:59 +0000769 def add_sysinfo_command(self, command, logfile=None, on_every_test=False):
mbligh4395bbd2009-03-25 19:34:17 +0000770 self._add_sysinfo_loggable(sysinfo.command(command, logf=logfile),
jadmanskic09fc152008-10-15 17:56:59 +0000771 on_every_test)
772
773
774 def add_sysinfo_logfile(self, file, on_every_test=False):
775 self._add_sysinfo_loggable(sysinfo.logfile(file), on_every_test)
776
777
778 def _add_sysinfo_loggable(self, loggable, on_every_test):
779 if on_every_test:
780 self.sysinfo.test_loggables.add(loggable)
781 else:
782 self.sysinfo.boot_loggables.add(loggable)
783
784
jadmanski10646442008-08-13 14:05:21 +0000785 def _read_warnings(self):
jadmanskif37df842009-02-11 00:03:26 +0000786 """Poll all the warning loggers and extract any new warnings that have
787 been logged. If the warnings belong to a category that is currently
788 disabled, this method will discard them and they will no longer be
789 retrievable.
790
791 Returns a list of (timestamp, message) tuples, where timestamp is an
792 integer epoch timestamp."""
jadmanski10646442008-08-13 14:05:21 +0000793 warnings = []
794 while True:
795 # pull in a line of output from every logger that has
796 # output ready to be read
mbligh2b92b862008-11-22 13:25:32 +0000797 loggers, _, _ = select.select(self.warning_loggers, [], [], 0)
jadmanski10646442008-08-13 14:05:21 +0000798 closed_loggers = set()
799 for logger in loggers:
800 line = logger.readline()
801 # record any broken pipes (aka line == empty)
802 if len(line) == 0:
803 closed_loggers.add(logger)
804 continue
jadmanskif37df842009-02-11 00:03:26 +0000805 # parse out the warning
806 timestamp, msgtype, msg = line.split('\t', 2)
807 timestamp = int(timestamp)
808 # if the warning is valid, add it to the results
809 if self.warning_manager.is_valid(timestamp, msgtype):
810 warnings.append((timestamp, msg.strip()))
jadmanski10646442008-08-13 14:05:21 +0000811
812 # stop listening to loggers that are closed
813 self.warning_loggers -= closed_loggers
814
815 # stop if none of the loggers have any output left
816 if not loggers:
817 break
818
819 # sort into timestamp order
820 warnings.sort()
821 return warnings
822
823
showardcc929362010-01-25 21:20:41 +0000824 def _unique_subdirectory(self, base_subdirectory_name):
825 """Compute a unique results subdirectory based on the given name.
826
827 Appends base_subdirectory_name with a number as necessary to find a
828 directory name that doesn't already exist.
829 """
830 subdirectory = base_subdirectory_name
831 counter = 1
832 while os.path.exists(os.path.join(self.resultdir, subdirectory)):
833 subdirectory = base_subdirectory_name + '.' + str(counter)
834 counter += 1
835 return subdirectory
836
837
jadmanski52053632010-06-11 21:08:10 +0000838 def get_record_context(self):
839 """Returns an object representing the current job.record context.
840
841 The object returned is an opaque object with a 0-arg restore method
842 which can be called to restore the job.record context (i.e. indentation)
843 to the current level. The intention is that it should be used when
844 something external which generate job.record calls (e.g. an autotest
845 client) can fail catastrophically and the server job record state
846 needs to be reset to its original "known good" state.
847
848 @return: A context object with a 0-arg restore() method."""
849 return self._indenter.get_context()
850
851
showardcc929362010-01-25 21:20:41 +0000852 def record_summary(self, status_code, test_name, reason='', attributes=None,
853 distinguishing_attributes=(), child_test_ids=None):
854 """Record a summary test result.
855
856 @param status_code: status code string, see
857 common_lib.log.is_valid_status()
858 @param test_name: name of the test
859 @param reason: (optional) string providing detailed reason for test
860 outcome
861 @param attributes: (optional) dict of string keyvals to associate with
862 this result
863 @param distinguishing_attributes: (optional) list of attribute names
864 that should be used to distinguish identically-named test
865 results. These attributes should be present in the attributes
866 parameter. This is used to generate user-friendly subdirectory
867 names.
868 @param child_test_ids: (optional) list of test indices for test results
869 used in generating this result.
870 """
871 subdirectory_name_parts = [test_name]
872 for attribute in distinguishing_attributes:
873 assert attributes
874 assert attribute in attributes, '%s not in %s' % (attribute,
875 attributes)
876 subdirectory_name_parts.append(attributes[attribute])
877 base_subdirectory_name = '.'.join(subdirectory_name_parts)
878
879 subdirectory = self._unique_subdirectory(base_subdirectory_name)
880 subdirectory_path = os.path.join(self.resultdir, subdirectory)
881 os.mkdir(subdirectory_path)
882
883 self.record(status_code, subdirectory, test_name,
884 status=reason, optional_fields={'is_summary': True})
885
886 if attributes:
887 utils.write_keyval(subdirectory_path, attributes)
888
889 if child_test_ids:
890 ids_string = ','.join(str(test_id) for test_id in child_test_ids)
891 summary_data = {'child_test_ids': ids_string}
892 utils.write_keyval(os.path.join(subdirectory_path, 'summary_data'),
893 summary_data)
894
895
jadmanski16a7ff72009-04-01 18:19:53 +0000896 def disable_warnings(self, warning_type):
jadmanskif37df842009-02-11 00:03:26 +0000897 self.warning_manager.disable_warnings(warning_type)
jadmanski16a7ff72009-04-01 18:19:53 +0000898 self.record("INFO", None, None,
899 "disabling %s warnings" % warning_type,
900 {"warnings.disable": warning_type})
jadmanskif37df842009-02-11 00:03:26 +0000901
902
jadmanski16a7ff72009-04-01 18:19:53 +0000903 def enable_warnings(self, warning_type):
jadmanskif37df842009-02-11 00:03:26 +0000904 self.warning_manager.enable_warnings(warning_type)
jadmanski16a7ff72009-04-01 18:19:53 +0000905 self.record("INFO", None, None,
906 "enabling %s warnings" % warning_type,
907 {"warnings.enable": warning_type})
jadmanskif37df842009-02-11 00:03:26 +0000908
909
jadmanski779bd292009-03-19 17:33:33 +0000910 def get_status_log_path(self, subdir=None):
911 """Return the path to the job status log.
912
913 @param subdir - Optional paramter indicating that you want the path
914 to a subdirectory status log.
915
916 @returns The path where the status log should be.
917 """
mbligh210bae62009-04-01 18:33:13 +0000918 if self.resultdir:
919 if subdir:
920 return os.path.join(self.resultdir, subdir, "status.log")
921 else:
922 return os.path.join(self.resultdir, "status.log")
jadmanski779bd292009-03-19 17:33:33 +0000923 else:
mbligh210bae62009-04-01 18:33:13 +0000924 return None
jadmanski779bd292009-03-19 17:33:33 +0000925
926
jadmanski6bb32d72009-03-19 20:25:24 +0000927 def _update_uncollected_logs_list(self, update_func):
928 """Updates the uncollected logs list in a multi-process safe manner.
929
930 @param update_func - a function that updates the list of uncollected
931 logs. Should take one parameter, the list to be updated.
932 """
Dan Shi07e09af2013-04-12 09:31:29 -0700933 # Skip log collection if file _uncollected_log_file does not exist.
934 if not (self._uncollected_log_file and
935 os.path.exists(self._uncollected_log_file)):
936 return
mbligh0d0f67d2009-11-06 03:15:03 +0000937 if self._uncollected_log_file:
938 log_file = open(self._uncollected_log_file, "r+")
mbligha788dc42009-03-26 21:10:16 +0000939 fcntl.flock(log_file, fcntl.LOCK_EX)
jadmanski6bb32d72009-03-19 20:25:24 +0000940 try:
941 uncollected_logs = pickle.load(log_file)
942 update_func(uncollected_logs)
943 log_file.seek(0)
944 log_file.truncate()
945 pickle.dump(uncollected_logs, log_file)
jadmanski3bff9092009-04-22 18:09:47 +0000946 log_file.flush()
jadmanski6bb32d72009-03-19 20:25:24 +0000947 finally:
948 fcntl.flock(log_file, fcntl.LOCK_UN)
949 log_file.close()
950
951
952 def add_client_log(self, hostname, remote_path, local_path):
953 """Adds a new set of client logs to the list of uncollected logs,
954 to allow for future log recovery.
955
956 @param host - the hostname of the machine holding the logs
957 @param remote_path - the directory on the remote machine holding logs
958 @param local_path - the local directory to copy the logs into
959 """
960 def update_func(logs_list):
961 logs_list.append((hostname, remote_path, local_path))
962 self._update_uncollected_logs_list(update_func)
963
964
965 def remove_client_log(self, hostname, remote_path, local_path):
966 """Removes a set of client logs from the list of uncollected logs,
967 to allow for future log recovery.
968
969 @param host - the hostname of the machine holding the logs
970 @param remote_path - the directory on the remote machine holding logs
971 @param local_path - the local directory to copy the logs into
972 """
973 def update_func(logs_list):
974 logs_list.remove((hostname, remote_path, local_path))
975 self._update_uncollected_logs_list(update_func)
976
977
mbligh0d0f67d2009-11-06 03:15:03 +0000978 def get_client_logs(self):
979 """Retrieves the list of uncollected logs, if it exists.
980
981 @returns A list of (host, remote_path, local_path) tuples. Returns
982 an empty list if no uncollected logs file exists.
983 """
984 log_exists = (self._uncollected_log_file and
985 os.path.exists(self._uncollected_log_file))
986 if log_exists:
987 return pickle.load(open(self._uncollected_log_file))
988 else:
989 return []
990
991
mbligh084bc172008-10-18 14:02:45 +0000992 def _fill_server_control_namespace(self, namespace, protect=True):
mbligh2b92b862008-11-22 13:25:32 +0000993 """
994 Prepare a namespace to be used when executing server control files.
mbligh084bc172008-10-18 14:02:45 +0000995
996 This sets up the control file API by importing modules and making them
997 available under the appropriate names within namespace.
998
999 For use by _execute_code().
1000
1001 Args:
1002 namespace: The namespace dictionary to fill in.
1003 protect: Boolean. If True (the default) any operation that would
1004 clobber an existing entry in namespace will cause an error.
1005 Raises:
1006 error.AutoservError: When a name would be clobbered by import.
1007 """
1008 def _import_names(module_name, names=()):
mbligh2b92b862008-11-22 13:25:32 +00001009 """
1010 Import a module and assign named attributes into namespace.
mbligh084bc172008-10-18 14:02:45 +00001011
1012 Args:
1013 module_name: The string module name.
1014 names: A limiting list of names to import from module_name. If
1015 empty (the default), all names are imported from the module
1016 similar to a "from foo.bar import *" statement.
1017 Raises:
1018 error.AutoservError: When a name being imported would clobber
1019 a name already in namespace.
1020 """
1021 module = __import__(module_name, {}, {}, names)
1022
1023 # No names supplied? Import * from the lowest level module.
1024 # (Ugh, why do I have to implement this part myself?)
1025 if not names:
1026 for submodule_name in module_name.split('.')[1:]:
1027 module = getattr(module, submodule_name)
1028 if hasattr(module, '__all__'):
1029 names = getattr(module, '__all__')
1030 else:
1031 names = dir(module)
1032
1033 # Install each name into namespace, checking to make sure it
1034 # doesn't override anything that already exists.
1035 for name in names:
1036 # Check for conflicts to help prevent future problems.
1037 if name in namespace and protect:
1038 if namespace[name] is not getattr(module, name):
1039 raise error.AutoservError('importing name '
1040 '%s from %s %r would override %r' %
1041 (name, module_name, getattr(module, name),
1042 namespace[name]))
1043 else:
1044 # Encourage cleanliness and the use of __all__ for a
1045 # more concrete API with less surprises on '*' imports.
1046 warnings.warn('%s (%r) being imported from %s for use '
1047 'in server control files is not the '
1048 'first occurrance of that import.' %
1049 (name, namespace[name], module_name))
1050
1051 namespace[name] = getattr(module, name)
1052
1053
1054 # This is the equivalent of prepending a bunch of import statements to
1055 # the front of the control script.
mbligha2b07dd2009-06-22 18:26:13 +00001056 namespace.update(os=os, sys=sys, logging=logging)
mbligh084bc172008-10-18 14:02:45 +00001057 _import_names('autotest_lib.server',
1058 ('hosts', 'autotest', 'kvm', 'git', 'standalone_profiler',
1059 'source_kernel', 'rpm_kernel', 'deb_kernel', 'git_kernel'))
1060 _import_names('autotest_lib.server.subcommand',
1061 ('parallel', 'parallel_simple', 'subcommand'))
1062 _import_names('autotest_lib.server.utils',
1063 ('run', 'get_tmp_dir', 'sh_escape', 'parse_machine'))
1064 _import_names('autotest_lib.client.common_lib.error')
1065 _import_names('autotest_lib.client.common_lib.barrier', ('barrier',))
1066
1067 # Inject ourself as the job object into other classes within the API.
1068 # (Yuck, this injection is a gross thing be part of a public API. -gps)
1069 #
1070 # XXX Base & SiteAutotest do not appear to use .job. Who does?
1071 namespace['autotest'].Autotest.job = self
1072 # server.hosts.base_classes.Host uses .job.
1073 namespace['hosts'].Host.job = self
Eric Li10222b82010-11-24 09:33:15 -08001074 namespace['hosts'].factory.ssh_user = self._ssh_user
1075 namespace['hosts'].factory.ssh_port = self._ssh_port
1076 namespace['hosts'].factory.ssh_pass = self._ssh_pass
Fang Dengd1c2b732013-08-20 12:59:46 -07001077 namespace['hosts'].factory.ssh_verbosity_flag = (
1078 self._ssh_verbosity_flag)
Aviv Keshetc5947fa2013-09-04 14:06:29 -07001079 namespace['hosts'].factory.ssh_options = self._ssh_options
mbligh084bc172008-10-18 14:02:45 +00001080
1081
1082 def _execute_code(self, code_file, namespace, protect=True):
mbligh2b92b862008-11-22 13:25:32 +00001083 """
1084 Execute code using a copy of namespace as a server control script.
mbligh084bc172008-10-18 14:02:45 +00001085
1086 Unless protect_namespace is explicitly set to False, the dict will not
1087 be modified.
1088
1089 Args:
1090 code_file: The filename of the control file to execute.
1091 namespace: A dict containing names to make available during execution.
1092 protect: Boolean. If True (the default) a copy of the namespace dict
1093 is used during execution to prevent the code from modifying its
1094 contents outside of this function. If False the raw dict is
1095 passed in and modifications will be allowed.
1096 """
1097 if protect:
1098 namespace = namespace.copy()
1099 self._fill_server_control_namespace(namespace, protect=protect)
1100 # TODO: Simplify and get rid of the special cases for only 1 machine.
showard3e66e8c2008-10-27 19:20:51 +00001101 if len(self.machines) > 1:
mbligh084bc172008-10-18 14:02:45 +00001102 machines_text = '\n'.join(self.machines) + '\n'
1103 # Only rewrite the file if it does not match our machine list.
1104 try:
1105 machines_f = open(MACHINES_FILENAME, 'r')
1106 existing_machines_text = machines_f.read()
1107 machines_f.close()
1108 except EnvironmentError:
1109 existing_machines_text = None
1110 if machines_text != existing_machines_text:
1111 utils.open_write_close(MACHINES_FILENAME, machines_text)
1112 execfile(code_file, namespace, namespace)
jadmanski10646442008-08-13 14:05:21 +00001113
1114
jadmanskie29d0e42010-06-17 16:06:52 +00001115 def _parse_status(self, new_line):
mbligh0d0f67d2009-11-06 03:15:03 +00001116 if not self._using_parser:
jadmanski10646442008-08-13 14:05:21 +00001117 return
jadmanskie29d0e42010-06-17 16:06:52 +00001118 new_tests = self.parser.process_lines([new_line])
jadmanski10646442008-08-13 14:05:21 +00001119 for test in new_tests:
1120 self.__insert_test(test)
1121
1122
1123 def __insert_test(self, test):
mbligh2b92b862008-11-22 13:25:32 +00001124 """
1125 An internal method to insert a new test result into the
jadmanski10646442008-08-13 14:05:21 +00001126 database. This method will not raise an exception, even if an
1127 error occurs during the insert, to avoid failing a test
1128 simply because of unexpected database issues."""
showard21baa452008-10-21 00:08:39 +00001129 self.num_tests_run += 1
1130 if status_lib.is_worse_than_or_equal_to(test.status, 'FAIL'):
1131 self.num_tests_failed += 1
jadmanski10646442008-08-13 14:05:21 +00001132 try:
1133 self.results_db.insert_test(self.job_model, test)
1134 except Exception:
1135 msg = ("WARNING: An unexpected error occured while "
1136 "inserting test results into the database. "
1137 "Ignoring error.\n" + traceback.format_exc())
1138 print >> sys.stderr, msg
1139
mblighcaa62c22008-04-07 21:51:17 +00001140
mblighfc3da5b2010-01-06 18:37:22 +00001141 def preprocess_client_state(self):
1142 """
1143 Produce a state file for initializing the state of a client job.
1144
1145 Creates a new client state file with all the current server state, as
1146 well as some pre-set client state.
1147
1148 @returns The path of the file the state was written into.
1149 """
1150 # initialize the sysinfo state
1151 self._state.set('client', 'sysinfo', self.sysinfo.serialize())
1152
1153 # dump the state out to a tempfile
1154 fd, file_path = tempfile.mkstemp(dir=self.tmpdir)
1155 os.close(fd)
mbligha2c99492010-01-27 22:59:50 +00001156
1157 # write_to_file doesn't need locking, we exclusively own file_path
mblighfc3da5b2010-01-06 18:37:22 +00001158 self._state.write_to_file(file_path)
1159 return file_path
1160
1161
1162 def postprocess_client_state(self, state_path):
1163 """
1164 Update the state of this job with the state from a client job.
1165
1166 Updates the state of the server side of a job with the final state
1167 of a client job that was run. Updates the non-client-specific state,
1168 pulls in some specific bits from the client-specific state, and then
1169 discards the rest. Removes the state file afterwards
1170
1171 @param state_file A path to the state file from the client.
1172 """
1173 # update the on-disk state
mblighfc3da5b2010-01-06 18:37:22 +00001174 try:
jadmanskib6e7bdb2010-04-13 16:00:39 +00001175 self._state.read_from_file(state_path)
mblighfc3da5b2010-01-06 18:37:22 +00001176 os.remove(state_path)
mbligha2c99492010-01-27 22:59:50 +00001177 except OSError, e:
mblighfc3da5b2010-01-06 18:37:22 +00001178 # ignore file-not-found errors
1179 if e.errno != errno.ENOENT:
1180 raise
jadmanskib6e7bdb2010-04-13 16:00:39 +00001181 else:
1182 logging.debug('Client state file %s not found', state_path)
mblighfc3da5b2010-01-06 18:37:22 +00001183
1184 # update the sysinfo state
1185 if self._state.has('client', 'sysinfo'):
1186 self.sysinfo.deserialize(self._state.get('client', 'sysinfo'))
1187
1188 # drop all the client-specific state
1189 self._state.discard_namespace('client')
1190
1191
mbligh0a883702010-04-21 01:58:34 +00001192 def clear_all_known_hosts(self):
1193 """Clears known hosts files for all AbstractSSHHosts."""
1194 for host in self.hosts:
1195 if isinstance(host, abstract_ssh.AbstractSSHHost):
1196 host.clear_known_hosts()
1197
1198
jadmanskif37df842009-02-11 00:03:26 +00001199class warning_manager(object):
1200 """Class for controlling warning logs. Manages the enabling and disabling
1201 of warnings."""
1202 def __init__(self):
1203 # a map of warning types to a list of disabled time intervals
1204 self.disabled_warnings = {}
1205
1206
1207 def is_valid(self, timestamp, warning_type):
1208 """Indicates if a warning (based on the time it occured and its type)
1209 is a valid warning. A warning is considered "invalid" if this type of
1210 warning was marked as "disabled" at the time the warning occured."""
1211 disabled_intervals = self.disabled_warnings.get(warning_type, [])
1212 for start, end in disabled_intervals:
1213 if timestamp >= start and (end is None or timestamp < end):
1214 return False
1215 return True
1216
1217
1218 def disable_warnings(self, warning_type, current_time_func=time.time):
1219 """As of now, disables all further warnings of this type."""
1220 intervals = self.disabled_warnings.setdefault(warning_type, [])
1221 if not intervals or intervals[-1][1] is not None:
jadmanski16a7ff72009-04-01 18:19:53 +00001222 intervals.append((int(current_time_func()), None))
jadmanskif37df842009-02-11 00:03:26 +00001223
1224
1225 def enable_warnings(self, warning_type, current_time_func=time.time):
1226 """As of now, enables all further warnings of this type."""
1227 intervals = self.disabled_warnings.get(warning_type, [])
1228 if intervals and intervals[-1][1] is None:
jadmanski16a7ff72009-04-01 18:19:53 +00001229 intervals[-1] = (intervals[-1][0], int(current_time_func()))
Paul Pendlebury57593562011-06-15 10:45:49 -07001230
1231
1232# load up site-specific code for generating site-specific job data
1233get_site_job_data = utils.import_site_function(__file__,
1234 "autotest_lib.server.site_server_job", "get_site_job_data",
1235 _get_site_job_data_dummy)
1236
1237
1238site_server_job = utils.import_site_class(
1239 __file__, "autotest_lib.server.site_server_job", "site_server_job",
1240 base_server_job)
1241
1242
1243class server_job(site_server_job):
Dale Curtis456d3c12011-07-19 11:42:51 -07001244 pass