blob: 225820a3d9220cbab9475df91e7383a15240ae34 [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='',
Fang Dengd1c2b732013-08-20 12:59:46 -0700146 ssh_user='root', ssh_port=22, ssh_pass='',
147 ssh_verbosity_flag='', test_retry=0, group_name='',
148 tag='', disable_sysinfo=False,
mblighe0cbc912010-03-11 18:03:07 +0000149 control_filename=SERVER_CONTROL_FILENAME):
jadmanski10646442008-08-13 14:05:21 +0000150 """
mbligh374f3412009-05-13 21:29:45 +0000151 Create a server side job object.
mblighb5dac432008-11-27 00:38:44 +0000152
mblighe7d9c602009-07-02 19:02:33 +0000153 @param control: The pathname of the control file.
154 @param args: Passed to the control file.
155 @param resultdir: Where to throw the results.
156 @param label: Description of the job.
157 @param user: Username for the job (email address).
158 @param client: True if this is a client-side control file.
159 @param parse_job: string, if supplied it is the job execution tag that
160 the results will be passed through to the TKO parser with.
161 @param ssh_user: The SSH username. [root]
162 @param ssh_port: The SSH port number. [22]
163 @param ssh_pass: The SSH passphrase, if needed.
Fang Dengd1c2b732013-08-20 12:59:46 -0700164 @param ssh_verbosity_flag: The SSH verbosity flag, '-v', '-vv',
165 '-vvv', or an empty string if not needed.
Scott Zawalski91493c82013-01-25 16:15:20 -0500166 @param test_retry: The number of times to retry a test if the test did
167 not complete successfully.
mblighe7d9c602009-07-02 19:02:33 +0000168 @param group_name: If supplied, this will be written out as
mbligh374f3412009-05-13 21:29:45 +0000169 host_group_name in the keyvals file for the parser.
mblighe7d9c602009-07-02 19:02:33 +0000170 @param tag: The job execution tag from the scheduler. [optional]
Christopher Wiley8a91f232013-07-09 11:02:27 -0700171 @param disable_sysinfo: Whether we should disable the sysinfo step of
172 tests for a modest shortening of test time. [optional]
mblighe0cbc912010-03-11 18:03:07 +0000173 @param control_filename: The filename where the server control file
174 should be written in the results directory.
jadmanski10646442008-08-13 14:05:21 +0000175 """
Scott Zawalski91493c82013-01-25 16:15:20 -0500176 super(base_server_job, self).__init__(resultdir=resultdir,
177 test_retry=test_retry)
mbligh0d0f67d2009-11-06 03:15:03 +0000178 path = os.path.dirname(__file__)
Scott Zawalski91493c82013-01-25 16:15:20 -0500179 self.test_retry = test_retry
mbligh0d0f67d2009-11-06 03:15:03 +0000180 self.control = control
181 self._uncollected_log_file = os.path.join(self.resultdir,
182 'uncollected_logs')
183 debugdir = os.path.join(self.resultdir, 'debug')
184 if not os.path.exists(debugdir):
185 os.mkdir(debugdir)
186
187 if user:
188 self.user = user
189 else:
190 self.user = getpass.getuser()
191
jadmanski808f4b12010-04-09 22:30:31 +0000192 self.args = args
Peter Mayo7a875762012-06-13 14:38:15 -0400193 self.label = label
jadmanski10646442008-08-13 14:05:21 +0000194 self.machines = machines
mbligh0d0f67d2009-11-06 03:15:03 +0000195 self._client = client
jadmanski10646442008-08-13 14:05:21 +0000196 self.warning_loggers = set()
jadmanskif37df842009-02-11 00:03:26 +0000197 self.warning_manager = warning_manager()
mbligh0d0f67d2009-11-06 03:15:03 +0000198 self._ssh_user = ssh_user
199 self._ssh_port = ssh_port
200 self._ssh_pass = ssh_pass
Fang Dengd1c2b732013-08-20 12:59:46 -0700201 self._ssh_verbosity_flag = ssh_verbosity_flag
mblighe7d9c602009-07-02 19:02:33 +0000202 self.tag = tag
mbligh09108442008-10-15 16:27:38 +0000203 self.last_boot_tag = None
jadmanski53aaf382008-11-17 16:22:31 +0000204 self.hosts = set()
mbligh0d0f67d2009-11-06 03:15:03 +0000205 self.drop_caches = False
mblighb5dac432008-11-27 00:38:44 +0000206 self.drop_caches_between_iterations = False
mblighe0cbc912010-03-11 18:03:07 +0000207 self._control_filename = control_filename
Christopher Wiley8a91f232013-07-09 11:02:27 -0700208 self._disable_sysinfo = disable_sysinfo
jadmanski10646442008-08-13 14:05:21 +0000209
showard75cdfee2009-06-10 17:40:41 +0000210 self.logging = logging_manager.get_logging_manager(
211 manage_stdout_and_stderr=True, redirect_fds=True)
212 subcommand.logging_manager_object = self.logging
jadmanski10646442008-08-13 14:05:21 +0000213
mbligh0d0f67d2009-11-06 03:15:03 +0000214 self.sysinfo = sysinfo.sysinfo(self.resultdir)
jadmanski043e1132008-11-19 17:10:32 +0000215 self.profilers = profilers.profilers(self)
jadmanskic09fc152008-10-15 17:56:59 +0000216
jadmanski10646442008-08-13 14:05:21 +0000217 job_data = {'label' : label, 'user' : user,
218 'hostname' : ','.join(machines),
Eric Li861b2d52011-02-04 14:50:35 -0800219 'drone' : platform.node(),
mbligh0d0f67d2009-11-06 03:15:03 +0000220 'status_version' : str(self._STATUS_VERSION),
showard170873e2009-01-07 00:22:26 +0000221 'job_started' : str(int(time.time()))}
mbligh374f3412009-05-13 21:29:45 +0000222 if group_name:
223 job_data['host_group_name'] = group_name
jadmanski10646442008-08-13 14:05:21 +0000224
mbligh0d0f67d2009-11-06 03:15:03 +0000225 # only write these keyvals out on the first job in a resultdir
226 if 'job_started' not in utils.read_keyval(self.resultdir):
227 job_data.update(get_site_job_data(self))
228 utils.write_keyval(self.resultdir, job_data)
229
230 self._parse_job = parse_job
showardcc929362010-01-25 21:20:41 +0000231 self._using_parser = (self._parse_job and len(machines) <= 1)
mbligh0d0f67d2009-11-06 03:15:03 +0000232 self.pkgmgr = packages.PackageManager(
233 self.autodir, run_function_dargs={'timeout':600})
showard21baa452008-10-21 00:08:39 +0000234 self.num_tests_run = 0
235 self.num_tests_failed = 0
236
jadmanski550fdc22008-11-20 16:32:08 +0000237 self._register_subcommand_hooks()
238
mbligh0d0f67d2009-11-06 03:15:03 +0000239 # these components aren't usable on the server
240 self.bootloader = None
241 self.harness = None
242
jadmanski2a89dac2010-06-11 14:32:58 +0000243 # set up the status logger
jadmanski52053632010-06-11 21:08:10 +0000244 self._indenter = status_indenter()
jadmanski2a89dac2010-06-11 14:32:58 +0000245 self._logger = base_job.status_logger(
jadmanski52053632010-06-11 21:08:10 +0000246 self, self._indenter, 'status.log', 'status.log',
jadmanski2a89dac2010-06-11 14:32:58 +0000247 record_hook=server_job_record_hook(self))
248
Dan Shib03ea9d2013-08-15 17:13:27 -0700249 # Initialize a flag to indicate DUT failure during the test, e.g.,
250 # unexpected reboot.
251 self.failed_with_device_error = False
252
mbligh0d0f67d2009-11-06 03:15:03 +0000253
254 @classmethod
255 def _find_base_directories(cls):
256 """
257 Determine locations of autodir, clientdir and serverdir. Assumes
258 that this file is located within serverdir and uses __file__ along
259 with relative paths to resolve the location.
260 """
261 serverdir = os.path.abspath(os.path.dirname(__file__))
262 autodir = os.path.normpath(os.path.join(serverdir, '..'))
263 clientdir = os.path.join(autodir, 'client')
264 return autodir, clientdir, serverdir
265
266
Scott Zawalski91493c82013-01-25 16:15:20 -0500267 def _find_resultdir(self, resultdir, *args, **dargs):
mbligh0d0f67d2009-11-06 03:15:03 +0000268 """
269 Determine the location of resultdir. For server jobs we expect one to
270 always be explicitly passed in to __init__, so just return that.
271 """
272 if resultdir:
273 return os.path.normpath(resultdir)
274 else:
275 return None
276
jadmanski550fdc22008-11-20 16:32:08 +0000277
jadmanski2a89dac2010-06-11 14:32:58 +0000278 def _get_status_logger(self):
279 """Return a reference to the status logger."""
280 return self._logger
281
282
jadmanskie432dd22009-01-30 15:04:51 +0000283 @staticmethod
284 def _load_control_file(path):
285 f = open(path)
286 try:
287 control_file = f.read()
288 finally:
289 f.close()
290 return re.sub('\r', '', control_file)
291
292
jadmanski550fdc22008-11-20 16:32:08 +0000293 def _register_subcommand_hooks(self):
mbligh2b92b862008-11-22 13:25:32 +0000294 """
295 Register some hooks into the subcommand modules that allow us
296 to properly clean up self.hosts created in forked subprocesses.
297 """
jadmanski550fdc22008-11-20 16:32:08 +0000298 def on_fork(cmd):
299 self._existing_hosts_on_fork = set(self.hosts)
300 def on_join(cmd):
301 new_hosts = self.hosts - self._existing_hosts_on_fork
302 for host in new_hosts:
303 host.close()
304 subcommand.subcommand.register_fork_hook(on_fork)
305 subcommand.subcommand.register_join_hook(on_join)
306
jadmanski10646442008-08-13 14:05:21 +0000307
mbligh4608b002010-01-05 18:22:35 +0000308 def init_parser(self):
mbligh2b92b862008-11-22 13:25:32 +0000309 """
mbligh4608b002010-01-05 18:22:35 +0000310 Start the continuous parsing of self.resultdir. This sets up
jadmanski10646442008-08-13 14:05:21 +0000311 the database connection and inserts the basic job object into
mbligh2b92b862008-11-22 13:25:32 +0000312 the database if necessary.
313 """
mbligh4608b002010-01-05 18:22:35 +0000314 if not self._using_parser:
315 return
jadmanski10646442008-08-13 14:05:21 +0000316 # redirect parser debugging to .parse.log
mbligh4608b002010-01-05 18:22:35 +0000317 parse_log = os.path.join(self.resultdir, '.parse.log')
jadmanski10646442008-08-13 14:05:21 +0000318 parse_log = open(parse_log, 'w', 0)
319 tko_utils.redirect_parser_debugging(parse_log)
320 # create a job model object and set up the db
321 self.results_db = tko_db.db(autocommit=True)
mbligh0d0f67d2009-11-06 03:15:03 +0000322 self.parser = status_lib.parser(self._STATUS_VERSION)
mbligh4608b002010-01-05 18:22:35 +0000323 self.job_model = self.parser.make_job(self.resultdir)
jadmanski10646442008-08-13 14:05:21 +0000324 self.parser.start(self.job_model)
325 # check if a job already exists in the db and insert it if
326 # it does not
mbligh0d0f67d2009-11-06 03:15:03 +0000327 job_idx = self.results_db.find_job(self._parse_job)
jadmanski10646442008-08-13 14:05:21 +0000328 if job_idx is None:
mbligh0d0f67d2009-11-06 03:15:03 +0000329 self.results_db.insert_job(self._parse_job, self.job_model)
jadmanski10646442008-08-13 14:05:21 +0000330 else:
mbligh2b92b862008-11-22 13:25:32 +0000331 machine_idx = self.results_db.lookup_machine(self.job_model.machine)
jadmanski10646442008-08-13 14:05:21 +0000332 self.job_model.index = job_idx
333 self.job_model.machine_idx = machine_idx
334
335
336 def cleanup_parser(self):
mbligh2b92b862008-11-22 13:25:32 +0000337 """
338 This should be called after the server job is finished
jadmanski10646442008-08-13 14:05:21 +0000339 to carry out any remaining cleanup (e.g. flushing any
mbligh2b92b862008-11-22 13:25:32 +0000340 remaining test results to the results db)
341 """
mbligh0d0f67d2009-11-06 03:15:03 +0000342 if not self._using_parser:
jadmanski10646442008-08-13 14:05:21 +0000343 return
344 final_tests = self.parser.end()
345 for test in final_tests:
346 self.__insert_test(test)
mbligh0d0f67d2009-11-06 03:15:03 +0000347 self._using_parser = False
jadmanski10646442008-08-13 14:05:21 +0000348
349
350 def verify(self):
Dan Shi07e09af2013-04-12 09:31:29 -0700351 """Verify machines are all ssh-able."""
jadmanski10646442008-08-13 14:05:21 +0000352 if not self.machines:
mbligh084bc172008-10-18 14:02:45 +0000353 raise error.AutoservError('No machines specified to verify')
mbligh0fce4112008-11-27 00:37:17 +0000354 if self.resultdir:
355 os.chdir(self.resultdir)
jadmanski10646442008-08-13 14:05:21 +0000356 try:
jadmanskicdd0c402008-09-19 21:21:31 +0000357 namespace = {'machines' : self.machines, 'job' : self,
mbligh0d0f67d2009-11-06 03:15:03 +0000358 'ssh_user' : self._ssh_user,
359 'ssh_port' : self._ssh_port,
Fang Dengd1c2b732013-08-20 12:59:46 -0700360 'ssh_pass' : self._ssh_pass,
361 'ssh_verbosity_flag' : self._ssh_verbosity_flag}
mbligh084bc172008-10-18 14:02:45 +0000362 self._execute_code(VERIFY_CONTROL_FILE, namespace, protect=False)
jadmanski10646442008-08-13 14:05:21 +0000363 except Exception, e:
mbligh2b92b862008-11-22 13:25:32 +0000364 msg = ('Verify failed\n' + str(e) + '\n' + traceback.format_exc())
jadmanski10646442008-08-13 14:05:21 +0000365 self.record('ABORT', None, None, msg)
366 raise
367
368
Dan Shi07e09af2013-04-12 09:31:29 -0700369 def reset(self):
370 """Reset machines by first cleanup then verify each machine."""
371 if not self.machines:
372 raise error.AutoservError('No machines specified to reset.')
373 if self.resultdir:
374 os.chdir(self.resultdir)
375
376 try:
377 namespace = {'machines' : self.machines, 'job' : self,
378 'ssh_user' : self._ssh_user,
379 'ssh_port' : self._ssh_port,
Fang Dengd1c2b732013-08-20 12:59:46 -0700380 'ssh_pass' : self._ssh_pass,
381 'ssh_verbosity_flag' : self._ssh_verbosity_flag}
Dan Shi07e09af2013-04-12 09:31:29 -0700382 self._execute_code(RESET_CONTROL_FILE, namespace, protect=False)
383 except Exception as e:
384 msg = ('Reset failed\n' + str(e) + '\n' +
385 traceback.format_exc())
386 self.record('ABORT', None, None, msg)
387 raise
388
389
jadmanski10646442008-08-13 14:05:21 +0000390 def repair(self, host_protection):
391 if not self.machines:
392 raise error.AutoservError('No machines specified to repair')
mbligh0fce4112008-11-27 00:37:17 +0000393 if self.resultdir:
394 os.chdir(self.resultdir)
jadmanski10646442008-08-13 14:05:21 +0000395 namespace = {'machines': self.machines, 'job': self,
mbligh0d0f67d2009-11-06 03:15:03 +0000396 'ssh_user': self._ssh_user, 'ssh_port': self._ssh_port,
397 'ssh_pass': self._ssh_pass,
Fang Dengd1c2b732013-08-20 12:59:46 -0700398 'ssh_verbosity_flag' : self._ssh_verbosity_flag,
jadmanski10646442008-08-13 14:05:21 +0000399 'protection_level': host_protection}
mbligh25c0b8c2009-01-24 01:44:17 +0000400
mbligh0931b0a2009-04-08 17:44:48 +0000401 self._execute_code(REPAIR_CONTROL_FILE, namespace, protect=False)
jadmanski10646442008-08-13 14:05:21 +0000402
403
Alex Millercb79ba72013-05-29 14:43:00 -0700404 def provision(self, labels):
405 """
406 Provision all hosts to match |labels|.
407
408 @param labels: A comma seperated string of labels to provision the
409 host to.
410
411 """
412 namespace = {'provision_labels': labels}
413 control = self._load_control_file(PROVISION_CONTROL_FILE)
414 self.run(control=control, namespace=namespace)
415
416
jadmanski10646442008-08-13 14:05:21 +0000417 def precheck(self):
418 """
419 perform any additional checks in derived classes.
420 """
421 pass
422
423
424 def enable_external_logging(self):
mbligh2b92b862008-11-22 13:25:32 +0000425 """
426 Start or restart external logging mechanism.
jadmanski10646442008-08-13 14:05:21 +0000427 """
428 pass
429
430
431 def disable_external_logging(self):
mbligh2b92b862008-11-22 13:25:32 +0000432 """
433 Pause or stop external logging mechanism.
jadmanski10646442008-08-13 14:05:21 +0000434 """
435 pass
436
437
438 def use_external_logging(self):
mbligh2b92b862008-11-22 13:25:32 +0000439 """
440 Return True if external logging should be used.
jadmanski10646442008-08-13 14:05:21 +0000441 """
442 return False
443
444
mbligh415dc212009-06-15 21:53:34 +0000445 def _make_parallel_wrapper(self, function, machines, log):
446 """Wrap function as appropriate for calling by parallel_simple."""
mbligh2b92b862008-11-22 13:25:32 +0000447 is_forking = not (len(machines) == 1 and self.machines == machines)
mbligh0d0f67d2009-11-06 03:15:03 +0000448 if self._parse_job and is_forking and log:
jadmanski10646442008-08-13 14:05:21 +0000449 def wrapper(machine):
mbligh0d0f67d2009-11-06 03:15:03 +0000450 self._parse_job += "/" + machine
451 self._using_parser = True
jadmanski10646442008-08-13 14:05:21 +0000452 self.machines = [machine]
mbligh0d0f67d2009-11-06 03:15:03 +0000453 self.push_execution_context(machine)
jadmanski609a5f42008-08-26 20:52:42 +0000454 os.chdir(self.resultdir)
showard2bab8f42008-11-12 18:15:22 +0000455 utils.write_keyval(self.resultdir, {"hostname": machine})
mbligh4608b002010-01-05 18:22:35 +0000456 self.init_parser()
jadmanski10646442008-08-13 14:05:21 +0000457 result = function(machine)
458 self.cleanup_parser()
459 return result
jadmanski4dd1a002008-09-05 20:27:30 +0000460 elif len(machines) > 1 and log:
jadmanski10646442008-08-13 14:05:21 +0000461 def wrapper(machine):
mbligh0d0f67d2009-11-06 03:15:03 +0000462 self.push_execution_context(machine)
jadmanski609a5f42008-08-26 20:52:42 +0000463 os.chdir(self.resultdir)
mbligh838d82d2009-03-11 17:14:31 +0000464 machine_data = {'hostname' : machine,
mbligh0d0f67d2009-11-06 03:15:03 +0000465 'status_version' : str(self._STATUS_VERSION)}
mbligh838d82d2009-03-11 17:14:31 +0000466 utils.write_keyval(self.resultdir, machine_data)
jadmanski10646442008-08-13 14:05:21 +0000467 result = function(machine)
468 return result
469 else:
470 wrapper = function
mbligh415dc212009-06-15 21:53:34 +0000471 return wrapper
472
473
474 def parallel_simple(self, function, machines, log=True, timeout=None,
475 return_results=False):
476 """
477 Run 'function' using parallel_simple, with an extra wrapper to handle
478 the necessary setup for continuous parsing, if possible. If continuous
479 parsing is already properly initialized then this should just work.
480
481 @param function: A callable to run in parallel given each machine.
482 @param machines: A list of machine names to be passed one per subcommand
483 invocation of function.
484 @param log: If True, output will be written to output in a subdirectory
485 named after each machine.
486 @param timeout: Seconds after which the function call should timeout.
487 @param return_results: If True instead of an AutoServError being raised
488 on any error a list of the results|exceptions from the function
489 called on each arg is returned. [default: False]
490
491 @raises error.AutotestError: If any of the functions failed.
492 """
493 wrapper = self._make_parallel_wrapper(function, machines, log)
494 return subcommand.parallel_simple(wrapper, machines,
495 log=log, timeout=timeout,
496 return_results=return_results)
497
498
499 def parallel_on_machines(self, function, machines, timeout=None):
500 """
showardcd5fac42009-07-06 20:19:43 +0000501 @param function: Called in parallel with one machine as its argument.
mbligh415dc212009-06-15 21:53:34 +0000502 @param machines: A list of machines to call function(machine) on.
503 @param timeout: Seconds after which the function call should timeout.
504
505 @returns A list of machines on which function(machine) returned
506 without raising an exception.
507 """
showardcd5fac42009-07-06 20:19:43 +0000508 results = self.parallel_simple(function, machines, timeout=timeout,
mbligh415dc212009-06-15 21:53:34 +0000509 return_results=True)
510 success_machines = []
511 for result, machine in itertools.izip(results, machines):
512 if not isinstance(result, Exception):
513 success_machines.append(machine)
514 return success_machines
jadmanski10646442008-08-13 14:05:21 +0000515
516
mbligh0d0f67d2009-11-06 03:15:03 +0000517 _USE_TEMP_DIR = object()
mbligh2b92b862008-11-22 13:25:32 +0000518 def run(self, cleanup=False, install_before=False, install_after=False,
jadmanskie432dd22009-01-30 15:04:51 +0000519 collect_crashdumps=True, namespace={}, control=None,
beepscb6f1e22013-06-28 19:14:10 -0700520 control_file_dir=None, verify_job_repo_url=False,
Christopher Wileyf594c5e2013-07-03 18:25:30 -0700521 only_collect_crashinfo=False, skip_crash_collection=False):
jadmanskifb9c0fa2009-04-29 17:39:16 +0000522 # for a normal job, make sure the uncollected logs file exists
523 # for a crashinfo-only run it should already exist, bail out otherwise
jadmanski648c39f2010-03-19 17:38:01 +0000524 created_uncollected_logs = False
Alex Miller45554f32013-08-13 16:48:29 -0700525 logging.info("I am PID %s", os.getpid())
mbligh0d0f67d2009-11-06 03:15:03 +0000526 if self.resultdir and not os.path.exists(self._uncollected_log_file):
jadmanskifb9c0fa2009-04-29 17:39:16 +0000527 if only_collect_crashinfo:
528 # if this is a crashinfo-only run, and there were no existing
529 # uncollected logs, just bail out early
530 logging.info("No existing uncollected logs, "
531 "skipping crashinfo collection")
532 return
533 else:
mbligh0d0f67d2009-11-06 03:15:03 +0000534 log_file = open(self._uncollected_log_file, "w")
jadmanskifb9c0fa2009-04-29 17:39:16 +0000535 pickle.dump([], log_file)
536 log_file.close()
jadmanski648c39f2010-03-19 17:38:01 +0000537 created_uncollected_logs = True
jadmanskifb9c0fa2009-04-29 17:39:16 +0000538
jadmanski10646442008-08-13 14:05:21 +0000539 # use a copy so changes don't affect the original dictionary
540 namespace = namespace.copy()
541 machines = self.machines
jadmanskie432dd22009-01-30 15:04:51 +0000542 if control is None:
jadmanski02a3ba22009-11-13 20:47:27 +0000543 if self.control is None:
544 control = ''
545 else:
546 control = self._load_control_file(self.control)
jadmanskie432dd22009-01-30 15:04:51 +0000547 if control_file_dir is None:
548 control_file_dir = self.resultdir
jadmanski10646442008-08-13 14:05:21 +0000549
550 self.aborted = False
551 namespace['machines'] = machines
jadmanski808f4b12010-04-09 22:30:31 +0000552 namespace['args'] = self.args
jadmanski10646442008-08-13 14:05:21 +0000553 namespace['job'] = self
mbligh0d0f67d2009-11-06 03:15:03 +0000554 namespace['ssh_user'] = self._ssh_user
555 namespace['ssh_port'] = self._ssh_port
556 namespace['ssh_pass'] = self._ssh_pass
Fang Dengd1c2b732013-08-20 12:59:46 -0700557 namespace['ssh_verbosity_flag'] = self._ssh_verbosity_flag
jadmanski10646442008-08-13 14:05:21 +0000558 test_start_time = int(time.time())
559
mbligh80e1eba2008-11-19 00:26:18 +0000560 if self.resultdir:
561 os.chdir(self.resultdir)
jadmanski779bd292009-03-19 17:33:33 +0000562 # touch status.log so that the parser knows a job is running here
jadmanski382303a2009-04-21 19:53:39 +0000563 open(self.get_status_log_path(), 'a').close()
mbligh80e1eba2008-11-19 00:26:18 +0000564 self.enable_external_logging()
jadmanskie432dd22009-01-30 15:04:51 +0000565
jadmanskicdd0c402008-09-19 21:21:31 +0000566 collect_crashinfo = True
mblighaebe3b62008-12-22 14:45:40 +0000567 temp_control_file_dir = None
jadmanski10646442008-08-13 14:05:21 +0000568 try:
showardcf8d4922009-10-14 16:08:39 +0000569 try:
570 if install_before and machines:
571 self._execute_code(INSTALL_CONTROL_FILE, namespace)
jadmanskie432dd22009-01-30 15:04:51 +0000572
showardcf8d4922009-10-14 16:08:39 +0000573 if only_collect_crashinfo:
574 return
575
beepscb6f1e22013-06-28 19:14:10 -0700576 # If the verify_job_repo_url option is set but we're unable
577 # to actually verify that the job_repo_url contains the autotest
578 # package, this job will fail.
579 if verify_job_repo_url:
580 self._execute_code(VERIFY_JOB_REPO_URL_CONTROL_FILE,
581 namespace)
582 else:
583 logging.warning('Not checking if job_repo_url contains '
584 'autotest packages on %s', machines)
585
jadmanskidef0c3c2009-03-25 20:07:10 +0000586 # determine the dir to write the control files to
587 cfd_specified = (control_file_dir
mbligh0d0f67d2009-11-06 03:15:03 +0000588 and control_file_dir is not self._USE_TEMP_DIR)
jadmanskidef0c3c2009-03-25 20:07:10 +0000589 if cfd_specified:
590 temp_control_file_dir = None
591 else:
592 temp_control_file_dir = tempfile.mkdtemp(
593 suffix='temp_control_file_dir')
594 control_file_dir = temp_control_file_dir
595 server_control_file = os.path.join(control_file_dir,
mblighe0cbc912010-03-11 18:03:07 +0000596 self._control_filename)
jadmanskidef0c3c2009-03-25 20:07:10 +0000597 client_control_file = os.path.join(control_file_dir,
598 CLIENT_CONTROL_FILENAME)
mbligh0d0f67d2009-11-06 03:15:03 +0000599 if self._client:
jadmanskidef0c3c2009-03-25 20:07:10 +0000600 namespace['control'] = control
601 utils.open_write_close(client_control_file, control)
mblighfeac0102009-04-28 18:31:12 +0000602 shutil.copyfile(CLIENT_WRAPPER_CONTROL_FILE,
603 server_control_file)
jadmanskidef0c3c2009-03-25 20:07:10 +0000604 else:
605 utils.open_write_close(server_control_file, control)
mbligh26f0d882009-06-22 18:30:01 +0000606 logging.info("Processing control file")
jadmanskidef0c3c2009-03-25 20:07:10 +0000607 self._execute_code(server_control_file, namespace)
mbligh26f0d882009-06-22 18:30:01 +0000608 logging.info("Finished processing control file")
jadmanski10646442008-08-13 14:05:21 +0000609
Dan Shib03ea9d2013-08-15 17:13:27 -0700610 # If no device error occured, no need to collect crashinfo.
611 collect_crashinfo = self.failed_with_device_error
Eric Li6f27d4f2010-09-29 10:55:17 -0700612 except Exception, e:
showardcf8d4922009-10-14 16:08:39 +0000613 try:
614 logging.exception(
615 'Exception escaped control file, job aborting:')
Eric Li6f27d4f2010-09-29 10:55:17 -0700616 self.record('INFO', None, None, str(e),
617 {'job_abort_reason': str(e)})
showardcf8d4922009-10-14 16:08:39 +0000618 except:
619 pass # don't let logging exceptions here interfere
620 raise
jadmanski10646442008-08-13 14:05:21 +0000621 finally:
mblighaebe3b62008-12-22 14:45:40 +0000622 if temp_control_file_dir:
jadmanskie432dd22009-01-30 15:04:51 +0000623 # Clean up temp directory used for copies of the control files
mblighaebe3b62008-12-22 14:45:40 +0000624 try:
625 shutil.rmtree(temp_control_file_dir)
626 except Exception, e:
mblighe7d9c602009-07-02 19:02:33 +0000627 logging.warn('Could not remove temp directory %s: %s',
628 temp_control_file_dir, e)
jadmanskie432dd22009-01-30 15:04:51 +0000629
jadmanskicdd0c402008-09-19 21:21:31 +0000630 if machines and (collect_crashdumps or collect_crashinfo):
jadmanski10646442008-08-13 14:05:21 +0000631 namespace['test_start_time'] = test_start_time
Christopher Wileyf594c5e2013-07-03 18:25:30 -0700632 if skip_crash_collection:
633 logging.info('Skipping crash dump/info collection '
634 'as requested.')
635 elif collect_crashinfo:
mbligh084bc172008-10-18 14:02:45 +0000636 # includes crashdumps
637 self._execute_code(CRASHINFO_CONTROL_FILE, namespace)
jadmanskicdd0c402008-09-19 21:21:31 +0000638 else:
mbligh084bc172008-10-18 14:02:45 +0000639 self._execute_code(CRASHDUMPS_CONTROL_FILE, namespace)
jadmanski10646442008-08-13 14:05:21 +0000640 self.disable_external_logging()
showard45ae8192008-11-05 19:32:53 +0000641 if cleanup and machines:
642 self._execute_code(CLEANUP_CONTROL_FILE, namespace)
Chris Sosaf4d43ff2012-10-30 11:21:05 -0700643 if self._uncollected_log_file and created_uncollected_logs:
644 os.remove(self._uncollected_log_file)
jadmanski10646442008-08-13 14:05:21 +0000645 if install_after and machines:
mbligh084bc172008-10-18 14:02:45 +0000646 self._execute_code(INSTALL_CONTROL_FILE, namespace)
jadmanski10646442008-08-13 14:05:21 +0000647
648
649 def run_test(self, url, *args, **dargs):
mbligh2b92b862008-11-22 13:25:32 +0000650 """
651 Summon a test object and run it.
jadmanski10646442008-08-13 14:05:21 +0000652
653 tag
654 tag to add to testname
655 url
656 url of the test to run
657 """
Christopher Wiley8a91f232013-07-09 11:02:27 -0700658 if self._disable_sysinfo:
659 dargs['disable_sysinfo'] = True
660
mblighfc3da5b2010-01-06 18:37:22 +0000661 group, testname = self.pkgmgr.get_package_name(url, 'test')
662 testname, subdir, tag = self._build_tagged_test_name(testname, dargs)
663 outputdir = self._make_test_outputdir(subdir)
jadmanski10646442008-08-13 14:05:21 +0000664
665 def group_func():
666 try:
667 test.runtest(self, url, tag, args, dargs)
668 except error.TestBaseException, e:
669 self.record(e.exit_status, subdir, testname, str(e))
670 raise
671 except Exception, e:
672 info = str(e) + "\n" + traceback.format_exc()
673 self.record('FAIL', subdir, testname, info)
674 raise
675 else:
mbligh2b92b862008-11-22 13:25:32 +0000676 self.record('GOOD', subdir, testname, 'completed successfully')
jadmanskide292df2008-08-26 20:51:14 +0000677
678 result, exc_info = self._run_group(testname, subdir, group_func)
679 if exc_info and isinstance(exc_info[1], error.TestBaseException):
680 return False
681 elif exc_info:
682 raise exc_info[0], exc_info[1], exc_info[2]
683 else:
684 return True
jadmanski10646442008-08-13 14:05:21 +0000685
686
687 def _run_group(self, name, subdir, function, *args, **dargs):
688 """\
689 Underlying method for running something inside of a group.
690 """
jadmanskide292df2008-08-26 20:51:14 +0000691 result, exc_info = None, None
jadmanski10646442008-08-13 14:05:21 +0000692 try:
693 self.record('START', subdir, name)
jadmanski52053632010-06-11 21:08:10 +0000694 result = function(*args, **dargs)
jadmanski10646442008-08-13 14:05:21 +0000695 except error.TestBaseException, e:
jadmanskib88d6dc2009-01-10 00:33:18 +0000696 self.record("END %s" % e.exit_status, subdir, name)
jadmanskide292df2008-08-26 20:51:14 +0000697 exc_info = sys.exc_info()
jadmanski10646442008-08-13 14:05:21 +0000698 except Exception, e:
699 err_msg = str(e) + '\n'
700 err_msg += traceback.format_exc()
701 self.record('END ABORT', subdir, name, err_msg)
702 raise error.JobError(name + ' failed\n' + traceback.format_exc())
703 else:
704 self.record('END GOOD', subdir, name)
705
jadmanskide292df2008-08-26 20:51:14 +0000706 return result, exc_info
jadmanski10646442008-08-13 14:05:21 +0000707
708
709 def run_group(self, function, *args, **dargs):
710 """\
711 function:
712 subroutine to run
713 *args:
714 arguments for the function
715 """
716
717 name = function.__name__
718
719 # Allow the tag for the group to be specified.
720 tag = dargs.pop('tag', None)
721 if tag:
722 name = tag
723
jadmanskide292df2008-08-26 20:51:14 +0000724 return self._run_group(name, None, function, *args, **dargs)[0]
jadmanski10646442008-08-13 14:05:21 +0000725
726
727 def run_reboot(self, reboot_func, get_kernel_func):
728 """\
729 A specialization of run_group meant specifically for handling
730 a reboot. Includes support for capturing the kernel version
731 after the reboot.
732
733 reboot_func: a function that carries out the reboot
734
735 get_kernel_func: a function that returns a string
736 representing the kernel version.
737 """
jadmanski10646442008-08-13 14:05:21 +0000738 try:
739 self.record('START', None, 'reboot')
jadmanski10646442008-08-13 14:05:21 +0000740 reboot_func()
741 except Exception, e:
jadmanski10646442008-08-13 14:05:21 +0000742 err_msg = str(e) + '\n' + traceback.format_exc()
743 self.record('END FAIL', None, 'reboot', err_msg)
jadmanski4b51d542009-04-08 14:17:16 +0000744 raise
jadmanski10646442008-08-13 14:05:21 +0000745 else:
746 kernel = get_kernel_func()
jadmanski10646442008-08-13 14:05:21 +0000747 self.record('END GOOD', None, 'reboot',
Dale Curtis74a314b2011-06-23 14:55:46 -0700748 optional_fields={"kernel": kernel})
jadmanski10646442008-08-13 14:05:21 +0000749
750
jadmanskie432dd22009-01-30 15:04:51 +0000751 def run_control(self, path):
752 """Execute a control file found at path (relative to the autotest
753 path). Intended for executing a control file within a control file,
754 not for running the top-level job control file."""
755 path = os.path.join(self.autodir, path)
756 control_file = self._load_control_file(path)
mbligh0d0f67d2009-11-06 03:15:03 +0000757 self.run(control=control_file, control_file_dir=self._USE_TEMP_DIR)
jadmanskie432dd22009-01-30 15:04:51 +0000758
759
jadmanskic09fc152008-10-15 17:56:59 +0000760 def add_sysinfo_command(self, command, logfile=None, on_every_test=False):
mbligh4395bbd2009-03-25 19:34:17 +0000761 self._add_sysinfo_loggable(sysinfo.command(command, logf=logfile),
jadmanskic09fc152008-10-15 17:56:59 +0000762 on_every_test)
763
764
765 def add_sysinfo_logfile(self, file, on_every_test=False):
766 self._add_sysinfo_loggable(sysinfo.logfile(file), on_every_test)
767
768
769 def _add_sysinfo_loggable(self, loggable, on_every_test):
770 if on_every_test:
771 self.sysinfo.test_loggables.add(loggable)
772 else:
773 self.sysinfo.boot_loggables.add(loggable)
774
775
jadmanski10646442008-08-13 14:05:21 +0000776 def _read_warnings(self):
jadmanskif37df842009-02-11 00:03:26 +0000777 """Poll all the warning loggers and extract any new warnings that have
778 been logged. If the warnings belong to a category that is currently
779 disabled, this method will discard them and they will no longer be
780 retrievable.
781
782 Returns a list of (timestamp, message) tuples, where timestamp is an
783 integer epoch timestamp."""
jadmanski10646442008-08-13 14:05:21 +0000784 warnings = []
785 while True:
786 # pull in a line of output from every logger that has
787 # output ready to be read
mbligh2b92b862008-11-22 13:25:32 +0000788 loggers, _, _ = select.select(self.warning_loggers, [], [], 0)
jadmanski10646442008-08-13 14:05:21 +0000789 closed_loggers = set()
790 for logger in loggers:
791 line = logger.readline()
792 # record any broken pipes (aka line == empty)
793 if len(line) == 0:
794 closed_loggers.add(logger)
795 continue
jadmanskif37df842009-02-11 00:03:26 +0000796 # parse out the warning
797 timestamp, msgtype, msg = line.split('\t', 2)
798 timestamp = int(timestamp)
799 # if the warning is valid, add it to the results
800 if self.warning_manager.is_valid(timestamp, msgtype):
801 warnings.append((timestamp, msg.strip()))
jadmanski10646442008-08-13 14:05:21 +0000802
803 # stop listening to loggers that are closed
804 self.warning_loggers -= closed_loggers
805
806 # stop if none of the loggers have any output left
807 if not loggers:
808 break
809
810 # sort into timestamp order
811 warnings.sort()
812 return warnings
813
814
showardcc929362010-01-25 21:20:41 +0000815 def _unique_subdirectory(self, base_subdirectory_name):
816 """Compute a unique results subdirectory based on the given name.
817
818 Appends base_subdirectory_name with a number as necessary to find a
819 directory name that doesn't already exist.
820 """
821 subdirectory = base_subdirectory_name
822 counter = 1
823 while os.path.exists(os.path.join(self.resultdir, subdirectory)):
824 subdirectory = base_subdirectory_name + '.' + str(counter)
825 counter += 1
826 return subdirectory
827
828
jadmanski52053632010-06-11 21:08:10 +0000829 def get_record_context(self):
830 """Returns an object representing the current job.record context.
831
832 The object returned is an opaque object with a 0-arg restore method
833 which can be called to restore the job.record context (i.e. indentation)
834 to the current level. The intention is that it should be used when
835 something external which generate job.record calls (e.g. an autotest
836 client) can fail catastrophically and the server job record state
837 needs to be reset to its original "known good" state.
838
839 @return: A context object with a 0-arg restore() method."""
840 return self._indenter.get_context()
841
842
showardcc929362010-01-25 21:20:41 +0000843 def record_summary(self, status_code, test_name, reason='', attributes=None,
844 distinguishing_attributes=(), child_test_ids=None):
845 """Record a summary test result.
846
847 @param status_code: status code string, see
848 common_lib.log.is_valid_status()
849 @param test_name: name of the test
850 @param reason: (optional) string providing detailed reason for test
851 outcome
852 @param attributes: (optional) dict of string keyvals to associate with
853 this result
854 @param distinguishing_attributes: (optional) list of attribute names
855 that should be used to distinguish identically-named test
856 results. These attributes should be present in the attributes
857 parameter. This is used to generate user-friendly subdirectory
858 names.
859 @param child_test_ids: (optional) list of test indices for test results
860 used in generating this result.
861 """
862 subdirectory_name_parts = [test_name]
863 for attribute in distinguishing_attributes:
864 assert attributes
865 assert attribute in attributes, '%s not in %s' % (attribute,
866 attributes)
867 subdirectory_name_parts.append(attributes[attribute])
868 base_subdirectory_name = '.'.join(subdirectory_name_parts)
869
870 subdirectory = self._unique_subdirectory(base_subdirectory_name)
871 subdirectory_path = os.path.join(self.resultdir, subdirectory)
872 os.mkdir(subdirectory_path)
873
874 self.record(status_code, subdirectory, test_name,
875 status=reason, optional_fields={'is_summary': True})
876
877 if attributes:
878 utils.write_keyval(subdirectory_path, attributes)
879
880 if child_test_ids:
881 ids_string = ','.join(str(test_id) for test_id in child_test_ids)
882 summary_data = {'child_test_ids': ids_string}
883 utils.write_keyval(os.path.join(subdirectory_path, 'summary_data'),
884 summary_data)
885
886
jadmanski16a7ff72009-04-01 18:19:53 +0000887 def disable_warnings(self, warning_type):
jadmanskif37df842009-02-11 00:03:26 +0000888 self.warning_manager.disable_warnings(warning_type)
jadmanski16a7ff72009-04-01 18:19:53 +0000889 self.record("INFO", None, None,
890 "disabling %s warnings" % warning_type,
891 {"warnings.disable": warning_type})
jadmanskif37df842009-02-11 00:03:26 +0000892
893
jadmanski16a7ff72009-04-01 18:19:53 +0000894 def enable_warnings(self, warning_type):
jadmanskif37df842009-02-11 00:03:26 +0000895 self.warning_manager.enable_warnings(warning_type)
jadmanski16a7ff72009-04-01 18:19:53 +0000896 self.record("INFO", None, None,
897 "enabling %s warnings" % warning_type,
898 {"warnings.enable": warning_type})
jadmanskif37df842009-02-11 00:03:26 +0000899
900
jadmanski779bd292009-03-19 17:33:33 +0000901 def get_status_log_path(self, subdir=None):
902 """Return the path to the job status log.
903
904 @param subdir - Optional paramter indicating that you want the path
905 to a subdirectory status log.
906
907 @returns The path where the status log should be.
908 """
mbligh210bae62009-04-01 18:33:13 +0000909 if self.resultdir:
910 if subdir:
911 return os.path.join(self.resultdir, subdir, "status.log")
912 else:
913 return os.path.join(self.resultdir, "status.log")
jadmanski779bd292009-03-19 17:33:33 +0000914 else:
mbligh210bae62009-04-01 18:33:13 +0000915 return None
jadmanski779bd292009-03-19 17:33:33 +0000916
917
jadmanski6bb32d72009-03-19 20:25:24 +0000918 def _update_uncollected_logs_list(self, update_func):
919 """Updates the uncollected logs list in a multi-process safe manner.
920
921 @param update_func - a function that updates the list of uncollected
922 logs. Should take one parameter, the list to be updated.
923 """
Dan Shi07e09af2013-04-12 09:31:29 -0700924 # Skip log collection if file _uncollected_log_file does not exist.
925 if not (self._uncollected_log_file and
926 os.path.exists(self._uncollected_log_file)):
927 return
mbligh0d0f67d2009-11-06 03:15:03 +0000928 if self._uncollected_log_file:
929 log_file = open(self._uncollected_log_file, "r+")
mbligha788dc42009-03-26 21:10:16 +0000930 fcntl.flock(log_file, fcntl.LOCK_EX)
jadmanski6bb32d72009-03-19 20:25:24 +0000931 try:
932 uncollected_logs = pickle.load(log_file)
933 update_func(uncollected_logs)
934 log_file.seek(0)
935 log_file.truncate()
936 pickle.dump(uncollected_logs, log_file)
jadmanski3bff9092009-04-22 18:09:47 +0000937 log_file.flush()
jadmanski6bb32d72009-03-19 20:25:24 +0000938 finally:
939 fcntl.flock(log_file, fcntl.LOCK_UN)
940 log_file.close()
941
942
943 def add_client_log(self, hostname, remote_path, local_path):
944 """Adds a new set of client logs to the list of uncollected logs,
945 to allow for future log recovery.
946
947 @param host - the hostname of the machine holding the logs
948 @param remote_path - the directory on the remote machine holding logs
949 @param local_path - the local directory to copy the logs into
950 """
951 def update_func(logs_list):
952 logs_list.append((hostname, remote_path, local_path))
953 self._update_uncollected_logs_list(update_func)
954
955
956 def remove_client_log(self, hostname, remote_path, local_path):
957 """Removes a set of client logs from the list of uncollected logs,
958 to allow for future log recovery.
959
960 @param host - the hostname of the machine holding the logs
961 @param remote_path - the directory on the remote machine holding logs
962 @param local_path - the local directory to copy the logs into
963 """
964 def update_func(logs_list):
965 logs_list.remove((hostname, remote_path, local_path))
966 self._update_uncollected_logs_list(update_func)
967
968
mbligh0d0f67d2009-11-06 03:15:03 +0000969 def get_client_logs(self):
970 """Retrieves the list of uncollected logs, if it exists.
971
972 @returns A list of (host, remote_path, local_path) tuples. Returns
973 an empty list if no uncollected logs file exists.
974 """
975 log_exists = (self._uncollected_log_file and
976 os.path.exists(self._uncollected_log_file))
977 if log_exists:
978 return pickle.load(open(self._uncollected_log_file))
979 else:
980 return []
981
982
mbligh084bc172008-10-18 14:02:45 +0000983 def _fill_server_control_namespace(self, namespace, protect=True):
mbligh2b92b862008-11-22 13:25:32 +0000984 """
985 Prepare a namespace to be used when executing server control files.
mbligh084bc172008-10-18 14:02:45 +0000986
987 This sets up the control file API by importing modules and making them
988 available under the appropriate names within namespace.
989
990 For use by _execute_code().
991
992 Args:
993 namespace: The namespace dictionary to fill in.
994 protect: Boolean. If True (the default) any operation that would
995 clobber an existing entry in namespace will cause an error.
996 Raises:
997 error.AutoservError: When a name would be clobbered by import.
998 """
999 def _import_names(module_name, names=()):
mbligh2b92b862008-11-22 13:25:32 +00001000 """
1001 Import a module and assign named attributes into namespace.
mbligh084bc172008-10-18 14:02:45 +00001002
1003 Args:
1004 module_name: The string module name.
1005 names: A limiting list of names to import from module_name. If
1006 empty (the default), all names are imported from the module
1007 similar to a "from foo.bar import *" statement.
1008 Raises:
1009 error.AutoservError: When a name being imported would clobber
1010 a name already in namespace.
1011 """
1012 module = __import__(module_name, {}, {}, names)
1013
1014 # No names supplied? Import * from the lowest level module.
1015 # (Ugh, why do I have to implement this part myself?)
1016 if not names:
1017 for submodule_name in module_name.split('.')[1:]:
1018 module = getattr(module, submodule_name)
1019 if hasattr(module, '__all__'):
1020 names = getattr(module, '__all__')
1021 else:
1022 names = dir(module)
1023
1024 # Install each name into namespace, checking to make sure it
1025 # doesn't override anything that already exists.
1026 for name in names:
1027 # Check for conflicts to help prevent future problems.
1028 if name in namespace and protect:
1029 if namespace[name] is not getattr(module, name):
1030 raise error.AutoservError('importing name '
1031 '%s from %s %r would override %r' %
1032 (name, module_name, getattr(module, name),
1033 namespace[name]))
1034 else:
1035 # Encourage cleanliness and the use of __all__ for a
1036 # more concrete API with less surprises on '*' imports.
1037 warnings.warn('%s (%r) being imported from %s for use '
1038 'in server control files is not the '
1039 'first occurrance of that import.' %
1040 (name, namespace[name], module_name))
1041
1042 namespace[name] = getattr(module, name)
1043
1044
1045 # This is the equivalent of prepending a bunch of import statements to
1046 # the front of the control script.
mbligha2b07dd2009-06-22 18:26:13 +00001047 namespace.update(os=os, sys=sys, logging=logging)
mbligh084bc172008-10-18 14:02:45 +00001048 _import_names('autotest_lib.server',
1049 ('hosts', 'autotest', 'kvm', 'git', 'standalone_profiler',
1050 'source_kernel', 'rpm_kernel', 'deb_kernel', 'git_kernel'))
1051 _import_names('autotest_lib.server.subcommand',
1052 ('parallel', 'parallel_simple', 'subcommand'))
1053 _import_names('autotest_lib.server.utils',
1054 ('run', 'get_tmp_dir', 'sh_escape', 'parse_machine'))
1055 _import_names('autotest_lib.client.common_lib.error')
1056 _import_names('autotest_lib.client.common_lib.barrier', ('barrier',))
1057
1058 # Inject ourself as the job object into other classes within the API.
1059 # (Yuck, this injection is a gross thing be part of a public API. -gps)
1060 #
1061 # XXX Base & SiteAutotest do not appear to use .job. Who does?
1062 namespace['autotest'].Autotest.job = self
1063 # server.hosts.base_classes.Host uses .job.
1064 namespace['hosts'].Host.job = self
Eric Li10222b82010-11-24 09:33:15 -08001065 namespace['hosts'].factory.ssh_user = self._ssh_user
1066 namespace['hosts'].factory.ssh_port = self._ssh_port
1067 namespace['hosts'].factory.ssh_pass = self._ssh_pass
Fang Dengd1c2b732013-08-20 12:59:46 -07001068 namespace['hosts'].factory.ssh_verbosity_flag = (
1069 self._ssh_verbosity_flag)
mbligh084bc172008-10-18 14:02:45 +00001070
1071
1072 def _execute_code(self, code_file, namespace, protect=True):
mbligh2b92b862008-11-22 13:25:32 +00001073 """
1074 Execute code using a copy of namespace as a server control script.
mbligh084bc172008-10-18 14:02:45 +00001075
1076 Unless protect_namespace is explicitly set to False, the dict will not
1077 be modified.
1078
1079 Args:
1080 code_file: The filename of the control file to execute.
1081 namespace: A dict containing names to make available during execution.
1082 protect: Boolean. If True (the default) a copy of the namespace dict
1083 is used during execution to prevent the code from modifying its
1084 contents outside of this function. If False the raw dict is
1085 passed in and modifications will be allowed.
1086 """
1087 if protect:
1088 namespace = namespace.copy()
1089 self._fill_server_control_namespace(namespace, protect=protect)
1090 # TODO: Simplify and get rid of the special cases for only 1 machine.
showard3e66e8c2008-10-27 19:20:51 +00001091 if len(self.machines) > 1:
mbligh084bc172008-10-18 14:02:45 +00001092 machines_text = '\n'.join(self.machines) + '\n'
1093 # Only rewrite the file if it does not match our machine list.
1094 try:
1095 machines_f = open(MACHINES_FILENAME, 'r')
1096 existing_machines_text = machines_f.read()
1097 machines_f.close()
1098 except EnvironmentError:
1099 existing_machines_text = None
1100 if machines_text != existing_machines_text:
1101 utils.open_write_close(MACHINES_FILENAME, machines_text)
1102 execfile(code_file, namespace, namespace)
jadmanski10646442008-08-13 14:05:21 +00001103
1104
jadmanskie29d0e42010-06-17 16:06:52 +00001105 def _parse_status(self, new_line):
mbligh0d0f67d2009-11-06 03:15:03 +00001106 if not self._using_parser:
jadmanski10646442008-08-13 14:05:21 +00001107 return
jadmanskie29d0e42010-06-17 16:06:52 +00001108 new_tests = self.parser.process_lines([new_line])
jadmanski10646442008-08-13 14:05:21 +00001109 for test in new_tests:
1110 self.__insert_test(test)
1111
1112
1113 def __insert_test(self, test):
mbligh2b92b862008-11-22 13:25:32 +00001114 """
1115 An internal method to insert a new test result into the
jadmanski10646442008-08-13 14:05:21 +00001116 database. This method will not raise an exception, even if an
1117 error occurs during the insert, to avoid failing a test
1118 simply because of unexpected database issues."""
showard21baa452008-10-21 00:08:39 +00001119 self.num_tests_run += 1
1120 if status_lib.is_worse_than_or_equal_to(test.status, 'FAIL'):
1121 self.num_tests_failed += 1
jadmanski10646442008-08-13 14:05:21 +00001122 try:
1123 self.results_db.insert_test(self.job_model, test)
1124 except Exception:
1125 msg = ("WARNING: An unexpected error occured while "
1126 "inserting test results into the database. "
1127 "Ignoring error.\n" + traceback.format_exc())
1128 print >> sys.stderr, msg
1129
mblighcaa62c22008-04-07 21:51:17 +00001130
mblighfc3da5b2010-01-06 18:37:22 +00001131 def preprocess_client_state(self):
1132 """
1133 Produce a state file for initializing the state of a client job.
1134
1135 Creates a new client state file with all the current server state, as
1136 well as some pre-set client state.
1137
1138 @returns The path of the file the state was written into.
1139 """
1140 # initialize the sysinfo state
1141 self._state.set('client', 'sysinfo', self.sysinfo.serialize())
1142
1143 # dump the state out to a tempfile
1144 fd, file_path = tempfile.mkstemp(dir=self.tmpdir)
1145 os.close(fd)
mbligha2c99492010-01-27 22:59:50 +00001146
1147 # write_to_file doesn't need locking, we exclusively own file_path
mblighfc3da5b2010-01-06 18:37:22 +00001148 self._state.write_to_file(file_path)
1149 return file_path
1150
1151
1152 def postprocess_client_state(self, state_path):
1153 """
1154 Update the state of this job with the state from a client job.
1155
1156 Updates the state of the server side of a job with the final state
1157 of a client job that was run. Updates the non-client-specific state,
1158 pulls in some specific bits from the client-specific state, and then
1159 discards the rest. Removes the state file afterwards
1160
1161 @param state_file A path to the state file from the client.
1162 """
1163 # update the on-disk state
mblighfc3da5b2010-01-06 18:37:22 +00001164 try:
jadmanskib6e7bdb2010-04-13 16:00:39 +00001165 self._state.read_from_file(state_path)
mblighfc3da5b2010-01-06 18:37:22 +00001166 os.remove(state_path)
mbligha2c99492010-01-27 22:59:50 +00001167 except OSError, e:
mblighfc3da5b2010-01-06 18:37:22 +00001168 # ignore file-not-found errors
1169 if e.errno != errno.ENOENT:
1170 raise
jadmanskib6e7bdb2010-04-13 16:00:39 +00001171 else:
1172 logging.debug('Client state file %s not found', state_path)
mblighfc3da5b2010-01-06 18:37:22 +00001173
1174 # update the sysinfo state
1175 if self._state.has('client', 'sysinfo'):
1176 self.sysinfo.deserialize(self._state.get('client', 'sysinfo'))
1177
1178 # drop all the client-specific state
1179 self._state.discard_namespace('client')
1180
1181
mbligh0a883702010-04-21 01:58:34 +00001182 def clear_all_known_hosts(self):
1183 """Clears known hosts files for all AbstractSSHHosts."""
1184 for host in self.hosts:
1185 if isinstance(host, abstract_ssh.AbstractSSHHost):
1186 host.clear_known_hosts()
1187
1188
jadmanskif37df842009-02-11 00:03:26 +00001189class warning_manager(object):
1190 """Class for controlling warning logs. Manages the enabling and disabling
1191 of warnings."""
1192 def __init__(self):
1193 # a map of warning types to a list of disabled time intervals
1194 self.disabled_warnings = {}
1195
1196
1197 def is_valid(self, timestamp, warning_type):
1198 """Indicates if a warning (based on the time it occured and its type)
1199 is a valid warning. A warning is considered "invalid" if this type of
1200 warning was marked as "disabled" at the time the warning occured."""
1201 disabled_intervals = self.disabled_warnings.get(warning_type, [])
1202 for start, end in disabled_intervals:
1203 if timestamp >= start and (end is None or timestamp < end):
1204 return False
1205 return True
1206
1207
1208 def disable_warnings(self, warning_type, current_time_func=time.time):
1209 """As of now, disables all further warnings of this type."""
1210 intervals = self.disabled_warnings.setdefault(warning_type, [])
1211 if not intervals or intervals[-1][1] is not None:
jadmanski16a7ff72009-04-01 18:19:53 +00001212 intervals.append((int(current_time_func()), None))
jadmanskif37df842009-02-11 00:03:26 +00001213
1214
1215 def enable_warnings(self, warning_type, current_time_func=time.time):
1216 """As of now, enables all further warnings of this type."""
1217 intervals = self.disabled_warnings.get(warning_type, [])
1218 if intervals and intervals[-1][1] is None:
jadmanski16a7ff72009-04-01 18:19:53 +00001219 intervals[-1] = (intervals[-1][0], int(current_time_func()))
Paul Pendlebury57593562011-06-15 10:45:49 -07001220
1221
1222# load up site-specific code for generating site-specific job data
1223get_site_job_data = utils.import_site_function(__file__,
1224 "autotest_lib.server.site_server_job", "get_site_job_data",
1225 _get_site_job_data_dummy)
1226
1227
1228site_server_job = utils.import_site_class(
1229 __file__, "autotest_lib.server.site_server_job", "site_server_job",
1230 base_server_job)
1231
1232
1233class server_job(site_server_job):
Dale Curtis456d3c12011-07-19 11:42:51 -07001234 pass