blob: eb81db0c00a915a3182aa4b381d5ade20911da1e [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
Alex Miller44ae9232014-06-20 17:24:25 -070017from autotest_lib.client.common_lib import base_job, global_config
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
Alex Miller44ae9232014-06-20 17:24:25 -070025INCREMENTAL_TKO_PARSING = global_config.global_config.get_config_value(
26 'autoserv', 'incremental_tko_parsing', type=bool, default=False)
27
mbligh084bc172008-10-18 14:02:45 +000028def _control_segment_path(name):
29 """Get the pathname of the named control segment file."""
jadmanski10646442008-08-13 14:05:21 +000030 server_dir = os.path.dirname(os.path.abspath(__file__))
mbligh084bc172008-10-18 14:02:45 +000031 return os.path.join(server_dir, "control_segments", name)
jadmanski10646442008-08-13 14:05:21 +000032
33
mbligh084bc172008-10-18 14:02:45 +000034CLIENT_CONTROL_FILENAME = 'control'
35SERVER_CONTROL_FILENAME = 'control.srv'
36MACHINES_FILENAME = '.machines'
jadmanski10646442008-08-13 14:05:21 +000037
mbligh084bc172008-10-18 14:02:45 +000038CLIENT_WRAPPER_CONTROL_FILE = _control_segment_path('client_wrapper')
39CRASHDUMPS_CONTROL_FILE = _control_segment_path('crashdumps')
40CRASHINFO_CONTROL_FILE = _control_segment_path('crashinfo')
mbligh084bc172008-10-18 14:02:45 +000041INSTALL_CONTROL_FILE = _control_segment_path('install')
showard45ae8192008-11-05 19:32:53 +000042CLEANUP_CONTROL_FILE = _control_segment_path('cleanup')
mbligh084bc172008-10-18 14:02:45 +000043VERIFY_CONTROL_FILE = _control_segment_path('verify')
mbligh084bc172008-10-18 14:02:45 +000044REPAIR_CONTROL_FILE = _control_segment_path('repair')
Alex Millercb79ba72013-05-29 14:43:00 -070045PROVISION_CONTROL_FILE = _control_segment_path('provision')
beepscb6f1e22013-06-28 19:14:10 -070046VERIFY_JOB_REPO_URL_CONTROL_FILE = _control_segment_path('verify_job_repo_url')
Dan Shi07e09af2013-04-12 09:31:29 -070047RESET_CONTROL_FILE = _control_segment_path('reset')
jadmanski10646442008-08-13 14:05:21 +000048
mbligh062ed152009-01-13 00:57:14 +000049# by default provide a stub that generates no site data
50def _get_site_job_data_dummy(job):
51 return {}
52
53
jadmanski2a89dac2010-06-11 14:32:58 +000054class status_indenter(base_job.status_indenter):
55 """Provide a simple integer-backed status indenter."""
56 def __init__(self):
57 self._indent = 0
58
59
60 @property
61 def indent(self):
62 return self._indent
63
64
65 def increment(self):
66 self._indent += 1
67
68
69 def decrement(self):
70 self._indent -= 1
71
72
jadmanski52053632010-06-11 21:08:10 +000073 def get_context(self):
74 """Returns a context object for use by job.get_record_context."""
75 class context(object):
76 def __init__(self, indenter, indent):
77 self._indenter = indenter
78 self._indent = indent
79 def restore(self):
80 self._indenter._indent = self._indent
81 return context(self, self._indent)
82
83
jadmanski2a89dac2010-06-11 14:32:58 +000084class server_job_record_hook(object):
85 """The job.record hook for server job. Used to inject WARN messages from
86 the console or vlm whenever new logs are written, and to echo any logs
87 to INFO level logging. Implemented as a class so that it can use state to
88 block recursive calls, so that the hook can call job.record itself to
89 log WARN messages.
90
91 Depends on job._read_warnings and job._logger.
92 """
93 def __init__(self, job):
94 self._job = job
95 self._being_called = False
96
97
98 def __call__(self, entry):
99 """A wrapper around the 'real' record hook, the _hook method, which
100 prevents recursion. This isn't making any effort to be threadsafe,
101 the intent is to outright block infinite recursion via a
102 job.record->_hook->job.record->_hook->job.record... chain."""
103 if self._being_called:
104 return
105 self._being_called = True
106 try:
107 self._hook(self._job, entry)
108 finally:
109 self._being_called = False
110
111
112 @staticmethod
113 def _hook(job, entry):
114 """The core hook, which can safely call job.record."""
115 entries = []
116 # poll all our warning loggers for new warnings
117 for timestamp, msg in job._read_warnings():
118 warning_entry = base_job.status_log_entry(
119 'WARN', None, None, msg, {}, timestamp=timestamp)
120 entries.append(warning_entry)
121 job.record_entry(warning_entry)
122 # echo rendered versions of all the status logs to info
123 entries.append(entry)
124 for entry in entries:
125 rendered_entry = job._logger.render_entry(entry)
126 logging.info(rendered_entry)
jadmanskie29d0e42010-06-17 16:06:52 +0000127 job._parse_status(rendered_entry)
jadmanski2a89dac2010-06-11 14:32:58 +0000128
129
mbligh0d0f67d2009-11-06 03:15:03 +0000130class base_server_job(base_job.base_job):
131 """The server-side concrete implementation of base_job.
jadmanski10646442008-08-13 14:05:21 +0000132
mbligh0d0f67d2009-11-06 03:15:03 +0000133 Optional properties provided by this implementation:
134 serverdir
135 conmuxdir
136
137 num_tests_run
138 num_tests_failed
139
140 warning_manager
141 warning_loggers
jadmanski10646442008-08-13 14:05:21 +0000142 """
143
mbligh0d0f67d2009-11-06 03:15:03 +0000144 _STATUS_VERSION = 1
jadmanski10646442008-08-13 14:05:21 +0000145
Aviv Keshetc5947fa2013-09-04 14:06:29 -0700146 # TODO crbug.com/285395 eliminate ssh_verbosity_flag
jadmanski10646442008-08-13 14:05:21 +0000147 def __init__(self, control, args, resultdir, label, user, machines,
148 client=False, parse_job='',
beepsd0672682013-09-16 17:32:16 -0700149 ssh_user=host_factory.DEFAULT_SSH_USER,
150 ssh_port=host_factory.DEFAULT_SSH_PORT,
151 ssh_pass=host_factory.DEFAULT_SSH_PASS,
152 ssh_verbosity_flag=host_factory.DEFAULT_SSH_VERBOSITY,
153 ssh_options=host_factory.DEFAULT_SSH_OPTIONS,
Aviv Keshetc5947fa2013-09-04 14:06:29 -0700154 test_retry=0, group_name='',
Fang Dengd1c2b732013-08-20 12:59:46 -0700155 tag='', disable_sysinfo=False,
Dan Shicf4d2032015-03-12 15:04:21 -0700156 control_filename=SERVER_CONTROL_FILENAME):
jadmanski10646442008-08-13 14:05:21 +0000157 """
mbligh374f3412009-05-13 21:29:45 +0000158 Create a server side job object.
mblighb5dac432008-11-27 00:38:44 +0000159
mblighe7d9c602009-07-02 19:02:33 +0000160 @param control: The pathname of the control file.
161 @param args: Passed to the control file.
162 @param resultdir: Where to throw the results.
163 @param label: Description of the job.
164 @param user: Username for the job (email address).
165 @param client: True if this is a client-side control file.
166 @param parse_job: string, if supplied it is the job execution tag that
167 the results will be passed through to the TKO parser with.
168 @param ssh_user: The SSH username. [root]
169 @param ssh_port: The SSH port number. [22]
170 @param ssh_pass: The SSH passphrase, if needed.
Fang Dengd1c2b732013-08-20 12:59:46 -0700171 @param ssh_verbosity_flag: The SSH verbosity flag, '-v', '-vv',
172 '-vvv', or an empty string if not needed.
Aviv Keshetc5947fa2013-09-04 14:06:29 -0700173 @param ssh_options: A string giving additional options that will be
174 included in ssh commands.
Scott Zawalski91493c82013-01-25 16:15:20 -0500175 @param test_retry: The number of times to retry a test if the test did
176 not complete successfully.
mblighe7d9c602009-07-02 19:02:33 +0000177 @param group_name: If supplied, this will be written out as
mbligh374f3412009-05-13 21:29:45 +0000178 host_group_name in the keyvals file for the parser.
mblighe7d9c602009-07-02 19:02:33 +0000179 @param tag: The job execution tag from the scheduler. [optional]
Christopher Wiley8a91f232013-07-09 11:02:27 -0700180 @param disable_sysinfo: Whether we should disable the sysinfo step of
181 tests for a modest shortening of test time. [optional]
mblighe0cbc912010-03-11 18:03:07 +0000182 @param control_filename: The filename where the server control file
183 should be written in the results directory.
jadmanski10646442008-08-13 14:05:21 +0000184 """
Scott Zawalski91493c82013-01-25 16:15:20 -0500185 super(base_server_job, self).__init__(resultdir=resultdir,
186 test_retry=test_retry)
mbligh0d0f67d2009-11-06 03:15:03 +0000187 path = os.path.dirname(__file__)
Scott Zawalski91493c82013-01-25 16:15:20 -0500188 self.test_retry = test_retry
mbligh0d0f67d2009-11-06 03:15:03 +0000189 self.control = control
190 self._uncollected_log_file = os.path.join(self.resultdir,
191 'uncollected_logs')
192 debugdir = os.path.join(self.resultdir, 'debug')
193 if not os.path.exists(debugdir):
194 os.mkdir(debugdir)
195
196 if user:
197 self.user = user
198 else:
199 self.user = getpass.getuser()
200
jadmanski808f4b12010-04-09 22:30:31 +0000201 self.args = args
Peter Mayo7a875762012-06-13 14:38:15 -0400202 self.label = label
jadmanski10646442008-08-13 14:05:21 +0000203 self.machines = machines
mbligh0d0f67d2009-11-06 03:15:03 +0000204 self._client = client
jadmanski10646442008-08-13 14:05:21 +0000205 self.warning_loggers = set()
jadmanskif37df842009-02-11 00:03:26 +0000206 self.warning_manager = warning_manager()
mbligh0d0f67d2009-11-06 03:15:03 +0000207 self._ssh_user = ssh_user
208 self._ssh_port = ssh_port
209 self._ssh_pass = ssh_pass
Fang Dengd1c2b732013-08-20 12:59:46 -0700210 self._ssh_verbosity_flag = ssh_verbosity_flag
Aviv Keshetc5947fa2013-09-04 14:06:29 -0700211 self._ssh_options = ssh_options
mblighe7d9c602009-07-02 19:02:33 +0000212 self.tag = tag
mbligh09108442008-10-15 16:27:38 +0000213 self.last_boot_tag = None
jadmanski53aaf382008-11-17 16:22:31 +0000214 self.hosts = set()
mbligh0d0f67d2009-11-06 03:15:03 +0000215 self.drop_caches = False
mblighb5dac432008-11-27 00:38:44 +0000216 self.drop_caches_between_iterations = False
mblighe0cbc912010-03-11 18:03:07 +0000217 self._control_filename = control_filename
Christopher Wiley8a91f232013-07-09 11:02:27 -0700218 self._disable_sysinfo = disable_sysinfo
jadmanski10646442008-08-13 14:05:21 +0000219
showard75cdfee2009-06-10 17:40:41 +0000220 self.logging = logging_manager.get_logging_manager(
221 manage_stdout_and_stderr=True, redirect_fds=True)
222 subcommand.logging_manager_object = self.logging
jadmanski10646442008-08-13 14:05:21 +0000223
mbligh0d0f67d2009-11-06 03:15:03 +0000224 self.sysinfo = sysinfo.sysinfo(self.resultdir)
jadmanski043e1132008-11-19 17:10:32 +0000225 self.profilers = profilers.profilers(self)
jadmanskic09fc152008-10-15 17:56:59 +0000226
jadmanski10646442008-08-13 14:05:21 +0000227 job_data = {'label' : label, 'user' : user,
228 'hostname' : ','.join(machines),
Eric Li861b2d52011-02-04 14:50:35 -0800229 'drone' : platform.node(),
mbligh0d0f67d2009-11-06 03:15:03 +0000230 'status_version' : str(self._STATUS_VERSION),
showard170873e2009-01-07 00:22:26 +0000231 'job_started' : str(int(time.time()))}
mbligh374f3412009-05-13 21:29:45 +0000232 if group_name:
233 job_data['host_group_name'] = group_name
jadmanski10646442008-08-13 14:05:21 +0000234
mbligh0d0f67d2009-11-06 03:15:03 +0000235 # only write these keyvals out on the first job in a resultdir
236 if 'job_started' not in utils.read_keyval(self.resultdir):
237 job_data.update(get_site_job_data(self))
238 utils.write_keyval(self.resultdir, job_data)
239
240 self._parse_job = parse_job
Alex Miller44ae9232014-06-20 17:24:25 -0700241 self._using_parser = (INCREMENTAL_TKO_PARSING and self._parse_job
242 and len(machines) <= 1)
mbligh0d0f67d2009-11-06 03:15:03 +0000243 self.pkgmgr = packages.PackageManager(
244 self.autodir, run_function_dargs={'timeout':600})
showard21baa452008-10-21 00:08:39 +0000245 self.num_tests_run = 0
246 self.num_tests_failed = 0
247
jadmanski550fdc22008-11-20 16:32:08 +0000248 self._register_subcommand_hooks()
249
mbligh0d0f67d2009-11-06 03:15:03 +0000250 # these components aren't usable on the server
251 self.bootloader = None
252 self.harness = None
253
jadmanski2a89dac2010-06-11 14:32:58 +0000254 # set up the status logger
jadmanski52053632010-06-11 21:08:10 +0000255 self._indenter = status_indenter()
jadmanski2a89dac2010-06-11 14:32:58 +0000256 self._logger = base_job.status_logger(
jadmanski52053632010-06-11 21:08:10 +0000257 self, self._indenter, 'status.log', 'status.log',
jadmanski2a89dac2010-06-11 14:32:58 +0000258 record_hook=server_job_record_hook(self))
259
Dan Shib03ea9d2013-08-15 17:13:27 -0700260 # Initialize a flag to indicate DUT failure during the test, e.g.,
261 # unexpected reboot.
262 self.failed_with_device_error = False
263
mbligh0d0f67d2009-11-06 03:15:03 +0000264
265 @classmethod
266 def _find_base_directories(cls):
267 """
268 Determine locations of autodir, clientdir and serverdir. Assumes
269 that this file is located within serverdir and uses __file__ along
270 with relative paths to resolve the location.
271 """
272 serverdir = os.path.abspath(os.path.dirname(__file__))
273 autodir = os.path.normpath(os.path.join(serverdir, '..'))
274 clientdir = os.path.join(autodir, 'client')
275 return autodir, clientdir, serverdir
276
277
Scott Zawalski91493c82013-01-25 16:15:20 -0500278 def _find_resultdir(self, resultdir, *args, **dargs):
mbligh0d0f67d2009-11-06 03:15:03 +0000279 """
280 Determine the location of resultdir. For server jobs we expect one to
281 always be explicitly passed in to __init__, so just return that.
282 """
283 if resultdir:
284 return os.path.normpath(resultdir)
285 else:
286 return None
287
jadmanski550fdc22008-11-20 16:32:08 +0000288
jadmanski2a89dac2010-06-11 14:32:58 +0000289 def _get_status_logger(self):
290 """Return a reference to the status logger."""
291 return self._logger
292
293
jadmanskie432dd22009-01-30 15:04:51 +0000294 @staticmethod
295 def _load_control_file(path):
296 f = open(path)
297 try:
298 control_file = f.read()
299 finally:
300 f.close()
301 return re.sub('\r', '', control_file)
302
303
jadmanski550fdc22008-11-20 16:32:08 +0000304 def _register_subcommand_hooks(self):
mbligh2b92b862008-11-22 13:25:32 +0000305 """
306 Register some hooks into the subcommand modules that allow us
307 to properly clean up self.hosts created in forked subprocesses.
308 """
jadmanski550fdc22008-11-20 16:32:08 +0000309 def on_fork(cmd):
310 self._existing_hosts_on_fork = set(self.hosts)
311 def on_join(cmd):
312 new_hosts = self.hosts - self._existing_hosts_on_fork
313 for host in new_hosts:
314 host.close()
315 subcommand.subcommand.register_fork_hook(on_fork)
316 subcommand.subcommand.register_join_hook(on_join)
317
jadmanski10646442008-08-13 14:05:21 +0000318
mbligh4608b002010-01-05 18:22:35 +0000319 def init_parser(self):
mbligh2b92b862008-11-22 13:25:32 +0000320 """
mbligh4608b002010-01-05 18:22:35 +0000321 Start the continuous parsing of self.resultdir. This sets up
jadmanski10646442008-08-13 14:05:21 +0000322 the database connection and inserts the basic job object into
mbligh2b92b862008-11-22 13:25:32 +0000323 the database if necessary.
324 """
mbligh4608b002010-01-05 18:22:35 +0000325 if not self._using_parser:
326 return
jadmanski10646442008-08-13 14:05:21 +0000327 # redirect parser debugging to .parse.log
mbligh4608b002010-01-05 18:22:35 +0000328 parse_log = os.path.join(self.resultdir, '.parse.log')
jadmanski10646442008-08-13 14:05:21 +0000329 parse_log = open(parse_log, 'w', 0)
330 tko_utils.redirect_parser_debugging(parse_log)
331 # create a job model object and set up the db
332 self.results_db = tko_db.db(autocommit=True)
mbligh0d0f67d2009-11-06 03:15:03 +0000333 self.parser = status_lib.parser(self._STATUS_VERSION)
mbligh4608b002010-01-05 18:22:35 +0000334 self.job_model = self.parser.make_job(self.resultdir)
jadmanski10646442008-08-13 14:05:21 +0000335 self.parser.start(self.job_model)
336 # check if a job already exists in the db and insert it if
337 # it does not
mbligh0d0f67d2009-11-06 03:15:03 +0000338 job_idx = self.results_db.find_job(self._parse_job)
jadmanski10646442008-08-13 14:05:21 +0000339 if job_idx is None:
mbligh0d0f67d2009-11-06 03:15:03 +0000340 self.results_db.insert_job(self._parse_job, self.job_model)
jadmanski10646442008-08-13 14:05:21 +0000341 else:
mbligh2b92b862008-11-22 13:25:32 +0000342 machine_idx = self.results_db.lookup_machine(self.job_model.machine)
jadmanski10646442008-08-13 14:05:21 +0000343 self.job_model.index = job_idx
344 self.job_model.machine_idx = machine_idx
345
346
347 def cleanup_parser(self):
mbligh2b92b862008-11-22 13:25:32 +0000348 """
349 This should be called after the server job is finished
jadmanski10646442008-08-13 14:05:21 +0000350 to carry out any remaining cleanup (e.g. flushing any
mbligh2b92b862008-11-22 13:25:32 +0000351 remaining test results to the results db)
352 """
mbligh0d0f67d2009-11-06 03:15:03 +0000353 if not self._using_parser:
jadmanski10646442008-08-13 14:05:21 +0000354 return
355 final_tests = self.parser.end()
356 for test in final_tests:
357 self.__insert_test(test)
mbligh0d0f67d2009-11-06 03:15:03 +0000358 self._using_parser = False
jadmanski10646442008-08-13 14:05:21 +0000359
Aviv Keshetc5947fa2013-09-04 14:06:29 -0700360 # TODO crbug.com/285395 add a kwargs parameter.
361 def _make_namespace(self):
362 """Create a namespace dictionary to be passed along to control file.
363
364 Creates a namespace argument populated with standard values:
365 machines, job, ssh_user, ssh_port, ssh_pass, ssh_verbosity_flag,
366 and ssh_options.
367 """
368 namespace = {'machines' : self.machines,
369 'job' : self,
370 'ssh_user' : self._ssh_user,
371 'ssh_port' : self._ssh_port,
372 'ssh_pass' : self._ssh_pass,
373 'ssh_verbosity_flag' : self._ssh_verbosity_flag,
374 'ssh_options' : self._ssh_options}
375 return namespace
376
jadmanski10646442008-08-13 14:05:21 +0000377
Fang Dengad78aca2014-10-02 18:15:46 -0700378 def cleanup(self, labels=''):
379 """Cleanup machines.
380
381 @param labels: Comma separated job labels, will be used to
382 determine special task actions.
383 """
384 if not self.machines:
385 raise error.AutoservError('No machines specified to cleanup')
386 if self.resultdir:
387 os.chdir(self.resultdir)
388
389 namespace = self._make_namespace()
390 namespace.update({'job_labels': labels, 'args': ''})
391 self._execute_code(CLEANUP_CONTROL_FILE, namespace, protect=False)
392
393
Alex Miller667b5f22014-02-28 15:33:39 -0800394 def verify(self, labels=''):
Fang Dengad78aca2014-10-02 18:15:46 -0700395 """Verify machines are all ssh-able.
396
397 @param labels: Comma separated job labels, will be used to
398 determine special task actions.
399 """
jadmanski10646442008-08-13 14:05:21 +0000400 if not self.machines:
mbligh084bc172008-10-18 14:02:45 +0000401 raise error.AutoservError('No machines specified to verify')
mbligh0fce4112008-11-27 00:37:17 +0000402 if self.resultdir:
403 os.chdir(self.resultdir)
Fang Dengad78aca2014-10-02 18:15:46 -0700404
405 namespace = self._make_namespace()
406 namespace.update({'job_labels': labels, 'args': ''})
407 self._execute_code(VERIFY_CONTROL_FILE, namespace, protect=False)
jadmanski10646442008-08-13 14:05:21 +0000408
409
Alex Miller667b5f22014-02-28 15:33:39 -0800410 def reset(self, labels=''):
Fang Dengad78aca2014-10-02 18:15:46 -0700411 """Reset machines by first cleanup then verify each machine.
412
413 @param labels: Comma separated job labels, will be used to
414 determine special task actions.
415 """
Dan Shi07e09af2013-04-12 09:31:29 -0700416 if not self.machines:
417 raise error.AutoservError('No machines specified to reset.')
418 if self.resultdir:
419 os.chdir(self.resultdir)
420
Fang Dengad78aca2014-10-02 18:15:46 -0700421 namespace = self._make_namespace()
422 namespace.update({'job_labels': labels, 'args': ''})
423 self._execute_code(RESET_CONTROL_FILE, namespace, protect=False)
Dan Shi07e09af2013-04-12 09:31:29 -0700424
425
Alex Miller667b5f22014-02-28 15:33:39 -0800426 def repair(self, host_protection, labels=''):
Fang Dengad78aca2014-10-02 18:15:46 -0700427 """Repair machines.
428
429 @param host_protection: level of host protection.
430 @param labels: Comma separated job labels, will be used to
431 determine special task actions.
432 """
jadmanski10646442008-08-13 14:05:21 +0000433 if not self.machines:
434 raise error.AutoservError('No machines specified to repair')
mbligh0fce4112008-11-27 00:37:17 +0000435 if self.resultdir:
436 os.chdir(self.resultdir)
Aviv Keshetc5947fa2013-09-04 14:06:29 -0700437
438 namespace = self._make_namespace()
Alex Miller667b5f22014-02-28 15:33:39 -0800439 namespace.update({'protection_level' : host_protection,
440 'job_labels': labels, 'args': ''})
mbligh0931b0a2009-04-08 17:44:48 +0000441 self._execute_code(REPAIR_CONTROL_FILE, namespace, protect=False)
jadmanski10646442008-08-13 14:05:21 +0000442
443
Alex Millercb79ba72013-05-29 14:43:00 -0700444 def provision(self, labels):
445 """
446 Provision all hosts to match |labels|.
447
448 @param labels: A comma seperated string of labels to provision the
449 host to.
450
451 """
Alex Millercb79ba72013-05-29 14:43:00 -0700452 control = self._load_control_file(PROVISION_CONTROL_FILE)
Alex Miller686fca82014-04-23 17:21:13 -0700453 self.run(control=control, job_labels=labels)
Alex Millercb79ba72013-05-29 14:43:00 -0700454
455
jadmanski10646442008-08-13 14:05:21 +0000456 def precheck(self):
457 """
458 perform any additional checks in derived classes.
459 """
460 pass
461
462
463 def enable_external_logging(self):
mbligh2b92b862008-11-22 13:25:32 +0000464 """
465 Start or restart external logging mechanism.
jadmanski10646442008-08-13 14:05:21 +0000466 """
467 pass
468
469
470 def disable_external_logging(self):
mbligh2b92b862008-11-22 13:25:32 +0000471 """
472 Pause or stop external logging mechanism.
jadmanski10646442008-08-13 14:05:21 +0000473 """
474 pass
475
476
477 def use_external_logging(self):
mbligh2b92b862008-11-22 13:25:32 +0000478 """
479 Return True if external logging should be used.
jadmanski10646442008-08-13 14:05:21 +0000480 """
481 return False
482
483
mbligh415dc212009-06-15 21:53:34 +0000484 def _make_parallel_wrapper(self, function, machines, log):
485 """Wrap function as appropriate for calling by parallel_simple."""
mbligh2b92b862008-11-22 13:25:32 +0000486 is_forking = not (len(machines) == 1 and self.machines == machines)
mbligh0d0f67d2009-11-06 03:15:03 +0000487 if self._parse_job and is_forking and log:
jadmanski10646442008-08-13 14:05:21 +0000488 def wrapper(machine):
mbligh0d0f67d2009-11-06 03:15:03 +0000489 self._parse_job += "/" + machine
Alex Miller44ae9232014-06-20 17:24:25 -0700490 self._using_parser = INCREMENTAL_TKO_PARSING
jadmanski10646442008-08-13 14:05:21 +0000491 self.machines = [machine]
mbligh0d0f67d2009-11-06 03:15:03 +0000492 self.push_execution_context(machine)
jadmanski609a5f42008-08-26 20:52:42 +0000493 os.chdir(self.resultdir)
showard2bab8f42008-11-12 18:15:22 +0000494 utils.write_keyval(self.resultdir, {"hostname": machine})
mbligh4608b002010-01-05 18:22:35 +0000495 self.init_parser()
jadmanski10646442008-08-13 14:05:21 +0000496 result = function(machine)
497 self.cleanup_parser()
498 return result
jadmanski4dd1a002008-09-05 20:27:30 +0000499 elif len(machines) > 1 and log:
jadmanski10646442008-08-13 14:05:21 +0000500 def wrapper(machine):
mbligh0d0f67d2009-11-06 03:15:03 +0000501 self.push_execution_context(machine)
jadmanski609a5f42008-08-26 20:52:42 +0000502 os.chdir(self.resultdir)
mbligh838d82d2009-03-11 17:14:31 +0000503 machine_data = {'hostname' : machine,
mbligh0d0f67d2009-11-06 03:15:03 +0000504 'status_version' : str(self._STATUS_VERSION)}
mbligh838d82d2009-03-11 17:14:31 +0000505 utils.write_keyval(self.resultdir, machine_data)
jadmanski10646442008-08-13 14:05:21 +0000506 result = function(machine)
507 return result
508 else:
509 wrapper = function
mbligh415dc212009-06-15 21:53:34 +0000510 return wrapper
511
512
513 def parallel_simple(self, function, machines, log=True, timeout=None,
514 return_results=False):
515 """
516 Run 'function' using parallel_simple, with an extra wrapper to handle
517 the necessary setup for continuous parsing, if possible. If continuous
518 parsing is already properly initialized then this should just work.
519
520 @param function: A callable to run in parallel given each machine.
521 @param machines: A list of machine names to be passed one per subcommand
522 invocation of function.
523 @param log: If True, output will be written to output in a subdirectory
524 named after each machine.
525 @param timeout: Seconds after which the function call should timeout.
526 @param return_results: If True instead of an AutoServError being raised
527 on any error a list of the results|exceptions from the function
528 called on each arg is returned. [default: False]
529
530 @raises error.AutotestError: If any of the functions failed.
531 """
532 wrapper = self._make_parallel_wrapper(function, machines, log)
533 return subcommand.parallel_simple(wrapper, machines,
534 log=log, timeout=timeout,
535 return_results=return_results)
536
537
538 def parallel_on_machines(self, function, machines, timeout=None):
539 """
showardcd5fac42009-07-06 20:19:43 +0000540 @param function: Called in parallel with one machine as its argument.
mbligh415dc212009-06-15 21:53:34 +0000541 @param machines: A list of machines to call function(machine) on.
542 @param timeout: Seconds after which the function call should timeout.
543
544 @returns A list of machines on which function(machine) returned
545 without raising an exception.
546 """
showardcd5fac42009-07-06 20:19:43 +0000547 results = self.parallel_simple(function, machines, timeout=timeout,
mbligh415dc212009-06-15 21:53:34 +0000548 return_results=True)
549 success_machines = []
550 for result, machine in itertools.izip(results, machines):
551 if not isinstance(result, Exception):
552 success_machines.append(machine)
553 return success_machines
jadmanski10646442008-08-13 14:05:21 +0000554
555
mbligh0d0f67d2009-11-06 03:15:03 +0000556 _USE_TEMP_DIR = object()
Fang Dengad78aca2014-10-02 18:15:46 -0700557 def run(self, install_before=False, install_after=False,
jadmanskie432dd22009-01-30 15:04:51 +0000558 collect_crashdumps=True, namespace={}, control=None,
beepscb6f1e22013-06-28 19:14:10 -0700559 control_file_dir=None, verify_job_repo_url=False,
Alex Millerca76bcc2014-04-18 18:47:28 -0700560 only_collect_crashinfo=False, skip_crash_collection=False,
Dan Shib669cbd2013-09-13 11:17:17 -0700561 job_labels='', use_packaging=True):
jadmanskifb9c0fa2009-04-29 17:39:16 +0000562 # for a normal job, make sure the uncollected logs file exists
563 # for a crashinfo-only run it should already exist, bail out otherwise
jadmanski648c39f2010-03-19 17:38:01 +0000564 created_uncollected_logs = False
Alex Miller45554f32013-08-13 16:48:29 -0700565 logging.info("I am PID %s", os.getpid())
mbligh0d0f67d2009-11-06 03:15:03 +0000566 if self.resultdir and not os.path.exists(self._uncollected_log_file):
jadmanskifb9c0fa2009-04-29 17:39:16 +0000567 if only_collect_crashinfo:
568 # if this is a crashinfo-only run, and there were no existing
569 # uncollected logs, just bail out early
570 logging.info("No existing uncollected logs, "
571 "skipping crashinfo collection")
572 return
573 else:
mbligh0d0f67d2009-11-06 03:15:03 +0000574 log_file = open(self._uncollected_log_file, "w")
jadmanskifb9c0fa2009-04-29 17:39:16 +0000575 pickle.dump([], log_file)
576 log_file.close()
jadmanski648c39f2010-03-19 17:38:01 +0000577 created_uncollected_logs = True
jadmanskifb9c0fa2009-04-29 17:39:16 +0000578
jadmanski10646442008-08-13 14:05:21 +0000579 # use a copy so changes don't affect the original dictionary
580 namespace = namespace.copy()
581 machines = self.machines
jadmanskie432dd22009-01-30 15:04:51 +0000582 if control is None:
jadmanski02a3ba22009-11-13 20:47:27 +0000583 if self.control is None:
584 control = ''
585 else:
586 control = self._load_control_file(self.control)
jadmanskie432dd22009-01-30 15:04:51 +0000587 if control_file_dir is None:
588 control_file_dir = self.resultdir
jadmanski10646442008-08-13 14:05:21 +0000589
590 self.aborted = False
Aviv Keshetc5947fa2013-09-04 14:06:29 -0700591 namespace.update(self._make_namespace())
Alex Millerca76bcc2014-04-18 18:47:28 -0700592 namespace.update({'args' : self.args,
593 'job_labels' : job_labels})
jadmanski10646442008-08-13 14:05:21 +0000594 test_start_time = int(time.time())
595
mbligh80e1eba2008-11-19 00:26:18 +0000596 if self.resultdir:
597 os.chdir(self.resultdir)
jadmanski779bd292009-03-19 17:33:33 +0000598 # touch status.log so that the parser knows a job is running here
jadmanski382303a2009-04-21 19:53:39 +0000599 open(self.get_status_log_path(), 'a').close()
mbligh80e1eba2008-11-19 00:26:18 +0000600 self.enable_external_logging()
jadmanskie432dd22009-01-30 15:04:51 +0000601
jadmanskicdd0c402008-09-19 21:21:31 +0000602 collect_crashinfo = True
mblighaebe3b62008-12-22 14:45:40 +0000603 temp_control_file_dir = None
jadmanski10646442008-08-13 14:05:21 +0000604 try:
showardcf8d4922009-10-14 16:08:39 +0000605 try:
606 if install_before and machines:
607 self._execute_code(INSTALL_CONTROL_FILE, namespace)
jadmanskie432dd22009-01-30 15:04:51 +0000608
showardcf8d4922009-10-14 16:08:39 +0000609 if only_collect_crashinfo:
610 return
611
beepscb6f1e22013-06-28 19:14:10 -0700612 # If the verify_job_repo_url option is set but we're unable
613 # to actually verify that the job_repo_url contains the autotest
614 # package, this job will fail.
615 if verify_job_repo_url:
616 self._execute_code(VERIFY_JOB_REPO_URL_CONTROL_FILE,
Dan Shicf4d2032015-03-12 15:04:21 -0700617 namespace)
beepscb6f1e22013-06-28 19:14:10 -0700618 else:
619 logging.warning('Not checking if job_repo_url contains '
620 'autotest packages on %s', machines)
621
jadmanskidef0c3c2009-03-25 20:07:10 +0000622 # determine the dir to write the control files to
623 cfd_specified = (control_file_dir
mbligh0d0f67d2009-11-06 03:15:03 +0000624 and control_file_dir is not self._USE_TEMP_DIR)
jadmanskidef0c3c2009-03-25 20:07:10 +0000625 if cfd_specified:
626 temp_control_file_dir = None
627 else:
628 temp_control_file_dir = tempfile.mkdtemp(
629 suffix='temp_control_file_dir')
630 control_file_dir = temp_control_file_dir
631 server_control_file = os.path.join(control_file_dir,
mblighe0cbc912010-03-11 18:03:07 +0000632 self._control_filename)
jadmanskidef0c3c2009-03-25 20:07:10 +0000633 client_control_file = os.path.join(control_file_dir,
634 CLIENT_CONTROL_FILENAME)
mbligh0d0f67d2009-11-06 03:15:03 +0000635 if self._client:
jadmanskidef0c3c2009-03-25 20:07:10 +0000636 namespace['control'] = control
637 utils.open_write_close(client_control_file, control)
mblighfeac0102009-04-28 18:31:12 +0000638 shutil.copyfile(CLIENT_WRAPPER_CONTROL_FILE,
639 server_control_file)
jadmanskidef0c3c2009-03-25 20:07:10 +0000640 else:
641 utils.open_write_close(server_control_file, control)
Dan Shicf4d2032015-03-12 15:04:21 -0700642
mbligh26f0d882009-06-22 18:30:01 +0000643 logging.info("Processing control file")
Dan Shib669cbd2013-09-13 11:17:17 -0700644 namespace['use_packaging'] = use_packaging
Dan Shicf4d2032015-03-12 15:04:21 -0700645 self._execute_code(server_control_file, namespace)
mbligh26f0d882009-06-22 18:30:01 +0000646 logging.info("Finished processing control file")
jadmanski10646442008-08-13 14:05:21 +0000647
Dan Shib03ea9d2013-08-15 17:13:27 -0700648 # If no device error occured, no need to collect crashinfo.
649 collect_crashinfo = self.failed_with_device_error
Eric Li6f27d4f2010-09-29 10:55:17 -0700650 except Exception, e:
showardcf8d4922009-10-14 16:08:39 +0000651 try:
652 logging.exception(
653 'Exception escaped control file, job aborting:')
Eric Li6f27d4f2010-09-29 10:55:17 -0700654 self.record('INFO', None, None, str(e),
655 {'job_abort_reason': str(e)})
showardcf8d4922009-10-14 16:08:39 +0000656 except:
657 pass # don't let logging exceptions here interfere
658 raise
jadmanski10646442008-08-13 14:05:21 +0000659 finally:
mblighaebe3b62008-12-22 14:45:40 +0000660 if temp_control_file_dir:
jadmanskie432dd22009-01-30 15:04:51 +0000661 # Clean up temp directory used for copies of the control files
mblighaebe3b62008-12-22 14:45:40 +0000662 try:
663 shutil.rmtree(temp_control_file_dir)
664 except Exception, e:
Ilja H. Friedel04be2bd2014-05-07 21:29:59 -0700665 logging.warning('Could not remove temp directory %s: %s',
mblighe7d9c602009-07-02 19:02:33 +0000666 temp_control_file_dir, e)
jadmanskie432dd22009-01-30 15:04:51 +0000667
jadmanskicdd0c402008-09-19 21:21:31 +0000668 if machines and (collect_crashdumps or collect_crashinfo):
jadmanski10646442008-08-13 14:05:21 +0000669 namespace['test_start_time'] = test_start_time
Christopher Wileyf594c5e2013-07-03 18:25:30 -0700670 if skip_crash_collection:
671 logging.info('Skipping crash dump/info collection '
672 'as requested.')
673 elif collect_crashinfo:
mbligh084bc172008-10-18 14:02:45 +0000674 # includes crashdumps
675 self._execute_code(CRASHINFO_CONTROL_FILE, namespace)
jadmanskicdd0c402008-09-19 21:21:31 +0000676 else:
mbligh084bc172008-10-18 14:02:45 +0000677 self._execute_code(CRASHDUMPS_CONTROL_FILE, namespace)
jadmanski10646442008-08-13 14:05:21 +0000678 self.disable_external_logging()
Chris Sosaf4d43ff2012-10-30 11:21:05 -0700679 if self._uncollected_log_file and created_uncollected_logs:
680 os.remove(self._uncollected_log_file)
jadmanski10646442008-08-13 14:05:21 +0000681 if install_after and machines:
mbligh084bc172008-10-18 14:02:45 +0000682 self._execute_code(INSTALL_CONTROL_FILE, namespace)
jadmanski10646442008-08-13 14:05:21 +0000683
684
685 def run_test(self, url, *args, **dargs):
mbligh2b92b862008-11-22 13:25:32 +0000686 """
687 Summon a test object and run it.
jadmanski10646442008-08-13 14:05:21 +0000688
689 tag
690 tag to add to testname
691 url
692 url of the test to run
693 """
Christopher Wiley8a91f232013-07-09 11:02:27 -0700694 if self._disable_sysinfo:
695 dargs['disable_sysinfo'] = True
696
mblighfc3da5b2010-01-06 18:37:22 +0000697 group, testname = self.pkgmgr.get_package_name(url, 'test')
698 testname, subdir, tag = self._build_tagged_test_name(testname, dargs)
699 outputdir = self._make_test_outputdir(subdir)
jadmanski10646442008-08-13 14:05:21 +0000700
701 def group_func():
702 try:
703 test.runtest(self, url, tag, args, dargs)
704 except error.TestBaseException, e:
705 self.record(e.exit_status, subdir, testname, str(e))
706 raise
707 except Exception, e:
708 info = str(e) + "\n" + traceback.format_exc()
709 self.record('FAIL', subdir, testname, info)
710 raise
711 else:
mbligh2b92b862008-11-22 13:25:32 +0000712 self.record('GOOD', subdir, testname, 'completed successfully')
jadmanskide292df2008-08-26 20:51:14 +0000713
714 result, exc_info = self._run_group(testname, subdir, group_func)
715 if exc_info and isinstance(exc_info[1], error.TestBaseException):
716 return False
717 elif exc_info:
718 raise exc_info[0], exc_info[1], exc_info[2]
719 else:
720 return True
jadmanski10646442008-08-13 14:05:21 +0000721
722
723 def _run_group(self, name, subdir, function, *args, **dargs):
724 """\
725 Underlying method for running something inside of a group.
726 """
jadmanskide292df2008-08-26 20:51:14 +0000727 result, exc_info = None, None
jadmanski10646442008-08-13 14:05:21 +0000728 try:
729 self.record('START', subdir, name)
jadmanski52053632010-06-11 21:08:10 +0000730 result = function(*args, **dargs)
jadmanski10646442008-08-13 14:05:21 +0000731 except error.TestBaseException, e:
jadmanskib88d6dc2009-01-10 00:33:18 +0000732 self.record("END %s" % e.exit_status, subdir, name)
jadmanskide292df2008-08-26 20:51:14 +0000733 exc_info = sys.exc_info()
jadmanski10646442008-08-13 14:05:21 +0000734 except Exception, e:
735 err_msg = str(e) + '\n'
736 err_msg += traceback.format_exc()
737 self.record('END ABORT', subdir, name, err_msg)
738 raise error.JobError(name + ' failed\n' + traceback.format_exc())
739 else:
740 self.record('END GOOD', subdir, name)
741
jadmanskide292df2008-08-26 20:51:14 +0000742 return result, exc_info
jadmanski10646442008-08-13 14:05:21 +0000743
744
745 def run_group(self, function, *args, **dargs):
746 """\
747 function:
748 subroutine to run
749 *args:
750 arguments for the function
751 """
752
753 name = function.__name__
754
755 # Allow the tag for the group to be specified.
756 tag = dargs.pop('tag', None)
757 if tag:
758 name = tag
759
jadmanskide292df2008-08-26 20:51:14 +0000760 return self._run_group(name, None, function, *args, **dargs)[0]
jadmanski10646442008-08-13 14:05:21 +0000761
762
Gwendal Grignou7a61d2f2014-05-23 11:05:51 -0700763 def run_op(self, op, op_func, get_kernel_func):
jadmanski10646442008-08-13 14:05:21 +0000764 """\
765 A specialization of run_group meant specifically for handling
Gwendal Grignou7a61d2f2014-05-23 11:05:51 -0700766 management operation. Includes support for capturing the kernel version
767 after the operation.
jadmanski10646442008-08-13 14:05:21 +0000768
Gwendal Grignou7a61d2f2014-05-23 11:05:51 -0700769 Args:
770 op: name of the operation.
771 op_func: a function that carries out the operation (reboot, suspend)
772 get_kernel_func: a function that returns a string
773 representing the kernel version.
jadmanski10646442008-08-13 14:05:21 +0000774 """
jadmanski10646442008-08-13 14:05:21 +0000775 try:
Gwendal Grignou7a61d2f2014-05-23 11:05:51 -0700776 self.record('START', None, op)
777 op_func()
jadmanski10646442008-08-13 14:05:21 +0000778 except Exception, e:
jadmanski10646442008-08-13 14:05:21 +0000779 err_msg = str(e) + '\n' + traceback.format_exc()
Gwendal Grignou7a61d2f2014-05-23 11:05:51 -0700780 self.record('END FAIL', None, op, err_msg)
jadmanski4b51d542009-04-08 14:17:16 +0000781 raise
jadmanski10646442008-08-13 14:05:21 +0000782 else:
783 kernel = get_kernel_func()
Gwendal Grignou7a61d2f2014-05-23 11:05:51 -0700784 self.record('END GOOD', None, op,
Dale Curtis74a314b2011-06-23 14:55:46 -0700785 optional_fields={"kernel": kernel})
jadmanski10646442008-08-13 14:05:21 +0000786
787
jadmanskie432dd22009-01-30 15:04:51 +0000788 def run_control(self, path):
789 """Execute a control file found at path (relative to the autotest
790 path). Intended for executing a control file within a control file,
791 not for running the top-level job control file."""
792 path = os.path.join(self.autodir, path)
793 control_file = self._load_control_file(path)
mbligh0d0f67d2009-11-06 03:15:03 +0000794 self.run(control=control_file, control_file_dir=self._USE_TEMP_DIR)
jadmanskie432dd22009-01-30 15:04:51 +0000795
796
jadmanskic09fc152008-10-15 17:56:59 +0000797 def add_sysinfo_command(self, command, logfile=None, on_every_test=False):
mbligh4395bbd2009-03-25 19:34:17 +0000798 self._add_sysinfo_loggable(sysinfo.command(command, logf=logfile),
jadmanskic09fc152008-10-15 17:56:59 +0000799 on_every_test)
800
801
802 def add_sysinfo_logfile(self, file, on_every_test=False):
803 self._add_sysinfo_loggable(sysinfo.logfile(file), on_every_test)
804
805
806 def _add_sysinfo_loggable(self, loggable, on_every_test):
807 if on_every_test:
808 self.sysinfo.test_loggables.add(loggable)
809 else:
810 self.sysinfo.boot_loggables.add(loggable)
811
812
jadmanski10646442008-08-13 14:05:21 +0000813 def _read_warnings(self):
jadmanskif37df842009-02-11 00:03:26 +0000814 """Poll all the warning loggers and extract any new warnings that have
815 been logged. If the warnings belong to a category that is currently
816 disabled, this method will discard them and they will no longer be
817 retrievable.
818
819 Returns a list of (timestamp, message) tuples, where timestamp is an
820 integer epoch timestamp."""
jadmanski10646442008-08-13 14:05:21 +0000821 warnings = []
822 while True:
823 # pull in a line of output from every logger that has
824 # output ready to be read
mbligh2b92b862008-11-22 13:25:32 +0000825 loggers, _, _ = select.select(self.warning_loggers, [], [], 0)
jadmanski10646442008-08-13 14:05:21 +0000826 closed_loggers = set()
827 for logger in loggers:
828 line = logger.readline()
829 # record any broken pipes (aka line == empty)
830 if len(line) == 0:
831 closed_loggers.add(logger)
832 continue
jadmanskif37df842009-02-11 00:03:26 +0000833 # parse out the warning
834 timestamp, msgtype, msg = line.split('\t', 2)
835 timestamp = int(timestamp)
836 # if the warning is valid, add it to the results
837 if self.warning_manager.is_valid(timestamp, msgtype):
838 warnings.append((timestamp, msg.strip()))
jadmanski10646442008-08-13 14:05:21 +0000839
840 # stop listening to loggers that are closed
841 self.warning_loggers -= closed_loggers
842
843 # stop if none of the loggers have any output left
844 if not loggers:
845 break
846
847 # sort into timestamp order
848 warnings.sort()
849 return warnings
850
851
showardcc929362010-01-25 21:20:41 +0000852 def _unique_subdirectory(self, base_subdirectory_name):
853 """Compute a unique results subdirectory based on the given name.
854
855 Appends base_subdirectory_name with a number as necessary to find a
856 directory name that doesn't already exist.
857 """
858 subdirectory = base_subdirectory_name
859 counter = 1
860 while os.path.exists(os.path.join(self.resultdir, subdirectory)):
861 subdirectory = base_subdirectory_name + '.' + str(counter)
862 counter += 1
863 return subdirectory
864
865
jadmanski52053632010-06-11 21:08:10 +0000866 def get_record_context(self):
867 """Returns an object representing the current job.record context.
868
869 The object returned is an opaque object with a 0-arg restore method
870 which can be called to restore the job.record context (i.e. indentation)
871 to the current level. The intention is that it should be used when
872 something external which generate job.record calls (e.g. an autotest
873 client) can fail catastrophically and the server job record state
874 needs to be reset to its original "known good" state.
875
876 @return: A context object with a 0-arg restore() method."""
877 return self._indenter.get_context()
878
879
showardcc929362010-01-25 21:20:41 +0000880 def record_summary(self, status_code, test_name, reason='', attributes=None,
881 distinguishing_attributes=(), child_test_ids=None):
882 """Record a summary test result.
883
884 @param status_code: status code string, see
885 common_lib.log.is_valid_status()
886 @param test_name: name of the test
887 @param reason: (optional) string providing detailed reason for test
888 outcome
889 @param attributes: (optional) dict of string keyvals to associate with
890 this result
891 @param distinguishing_attributes: (optional) list of attribute names
892 that should be used to distinguish identically-named test
893 results. These attributes should be present in the attributes
894 parameter. This is used to generate user-friendly subdirectory
895 names.
896 @param child_test_ids: (optional) list of test indices for test results
897 used in generating this result.
898 """
899 subdirectory_name_parts = [test_name]
900 for attribute in distinguishing_attributes:
901 assert attributes
902 assert attribute in attributes, '%s not in %s' % (attribute,
903 attributes)
904 subdirectory_name_parts.append(attributes[attribute])
905 base_subdirectory_name = '.'.join(subdirectory_name_parts)
906
907 subdirectory = self._unique_subdirectory(base_subdirectory_name)
908 subdirectory_path = os.path.join(self.resultdir, subdirectory)
909 os.mkdir(subdirectory_path)
910
911 self.record(status_code, subdirectory, test_name,
912 status=reason, optional_fields={'is_summary': True})
913
914 if attributes:
915 utils.write_keyval(subdirectory_path, attributes)
916
917 if child_test_ids:
918 ids_string = ','.join(str(test_id) for test_id in child_test_ids)
919 summary_data = {'child_test_ids': ids_string}
920 utils.write_keyval(os.path.join(subdirectory_path, 'summary_data'),
921 summary_data)
922
923
jadmanski16a7ff72009-04-01 18:19:53 +0000924 def disable_warnings(self, warning_type):
jadmanskif37df842009-02-11 00:03:26 +0000925 self.warning_manager.disable_warnings(warning_type)
jadmanski16a7ff72009-04-01 18:19:53 +0000926 self.record("INFO", None, None,
927 "disabling %s warnings" % warning_type,
928 {"warnings.disable": warning_type})
jadmanskif37df842009-02-11 00:03:26 +0000929
930
jadmanski16a7ff72009-04-01 18:19:53 +0000931 def enable_warnings(self, warning_type):
jadmanskif37df842009-02-11 00:03:26 +0000932 self.warning_manager.enable_warnings(warning_type)
jadmanski16a7ff72009-04-01 18:19:53 +0000933 self.record("INFO", None, None,
934 "enabling %s warnings" % warning_type,
935 {"warnings.enable": warning_type})
jadmanskif37df842009-02-11 00:03:26 +0000936
937
jadmanski779bd292009-03-19 17:33:33 +0000938 def get_status_log_path(self, subdir=None):
939 """Return the path to the job status log.
940
941 @param subdir - Optional paramter indicating that you want the path
942 to a subdirectory status log.
943
944 @returns The path where the status log should be.
945 """
mbligh210bae62009-04-01 18:33:13 +0000946 if self.resultdir:
947 if subdir:
948 return os.path.join(self.resultdir, subdir, "status.log")
949 else:
950 return os.path.join(self.resultdir, "status.log")
jadmanski779bd292009-03-19 17:33:33 +0000951 else:
mbligh210bae62009-04-01 18:33:13 +0000952 return None
jadmanski779bd292009-03-19 17:33:33 +0000953
954
jadmanski6bb32d72009-03-19 20:25:24 +0000955 def _update_uncollected_logs_list(self, update_func):
956 """Updates the uncollected logs list in a multi-process safe manner.
957
958 @param update_func - a function that updates the list of uncollected
959 logs. Should take one parameter, the list to be updated.
960 """
Dan Shi07e09af2013-04-12 09:31:29 -0700961 # Skip log collection if file _uncollected_log_file does not exist.
962 if not (self._uncollected_log_file and
963 os.path.exists(self._uncollected_log_file)):
964 return
mbligh0d0f67d2009-11-06 03:15:03 +0000965 if self._uncollected_log_file:
966 log_file = open(self._uncollected_log_file, "r+")
mbligha788dc42009-03-26 21:10:16 +0000967 fcntl.flock(log_file, fcntl.LOCK_EX)
jadmanski6bb32d72009-03-19 20:25:24 +0000968 try:
969 uncollected_logs = pickle.load(log_file)
970 update_func(uncollected_logs)
971 log_file.seek(0)
972 log_file.truncate()
973 pickle.dump(uncollected_logs, log_file)
jadmanski3bff9092009-04-22 18:09:47 +0000974 log_file.flush()
jadmanski6bb32d72009-03-19 20:25:24 +0000975 finally:
976 fcntl.flock(log_file, fcntl.LOCK_UN)
977 log_file.close()
978
979
980 def add_client_log(self, hostname, remote_path, local_path):
981 """Adds a new set of client logs to the list of uncollected logs,
982 to allow for future log recovery.
983
984 @param host - the hostname of the machine holding the logs
985 @param remote_path - the directory on the remote machine holding logs
986 @param local_path - the local directory to copy the logs into
987 """
988 def update_func(logs_list):
989 logs_list.append((hostname, remote_path, local_path))
990 self._update_uncollected_logs_list(update_func)
991
992
993 def remove_client_log(self, hostname, remote_path, local_path):
994 """Removes a set of client logs from the list of uncollected logs,
995 to allow for future log recovery.
996
997 @param host - the hostname of the machine holding the logs
998 @param remote_path - the directory on the remote machine holding logs
999 @param local_path - the local directory to copy the logs into
1000 """
1001 def update_func(logs_list):
1002 logs_list.remove((hostname, remote_path, local_path))
1003 self._update_uncollected_logs_list(update_func)
1004
1005
mbligh0d0f67d2009-11-06 03:15:03 +00001006 def get_client_logs(self):
1007 """Retrieves the list of uncollected logs, if it exists.
1008
1009 @returns A list of (host, remote_path, local_path) tuples. Returns
1010 an empty list if no uncollected logs file exists.
1011 """
1012 log_exists = (self._uncollected_log_file and
1013 os.path.exists(self._uncollected_log_file))
1014 if log_exists:
1015 return pickle.load(open(self._uncollected_log_file))
1016 else:
1017 return []
1018
1019
mbligh084bc172008-10-18 14:02:45 +00001020 def _fill_server_control_namespace(self, namespace, protect=True):
mbligh2b92b862008-11-22 13:25:32 +00001021 """
1022 Prepare a namespace to be used when executing server control files.
mbligh084bc172008-10-18 14:02:45 +00001023
1024 This sets up the control file API by importing modules and making them
1025 available under the appropriate names within namespace.
1026
1027 For use by _execute_code().
1028
1029 Args:
1030 namespace: The namespace dictionary to fill in.
1031 protect: Boolean. If True (the default) any operation that would
1032 clobber an existing entry in namespace will cause an error.
1033 Raises:
1034 error.AutoservError: When a name would be clobbered by import.
1035 """
1036 def _import_names(module_name, names=()):
mbligh2b92b862008-11-22 13:25:32 +00001037 """
1038 Import a module and assign named attributes into namespace.
mbligh084bc172008-10-18 14:02:45 +00001039
1040 Args:
1041 module_name: The string module name.
1042 names: A limiting list of names to import from module_name. If
1043 empty (the default), all names are imported from the module
1044 similar to a "from foo.bar import *" statement.
1045 Raises:
1046 error.AutoservError: When a name being imported would clobber
1047 a name already in namespace.
1048 """
1049 module = __import__(module_name, {}, {}, names)
1050
1051 # No names supplied? Import * from the lowest level module.
1052 # (Ugh, why do I have to implement this part myself?)
1053 if not names:
1054 for submodule_name in module_name.split('.')[1:]:
1055 module = getattr(module, submodule_name)
1056 if hasattr(module, '__all__'):
1057 names = getattr(module, '__all__')
1058 else:
1059 names = dir(module)
1060
1061 # Install each name into namespace, checking to make sure it
1062 # doesn't override anything that already exists.
1063 for name in names:
1064 # Check for conflicts to help prevent future problems.
1065 if name in namespace and protect:
1066 if namespace[name] is not getattr(module, name):
1067 raise error.AutoservError('importing name '
1068 '%s from %s %r would override %r' %
1069 (name, module_name, getattr(module, name),
1070 namespace[name]))
1071 else:
1072 # Encourage cleanliness and the use of __all__ for a
1073 # more concrete API with less surprises on '*' imports.
1074 warnings.warn('%s (%r) being imported from %s for use '
1075 'in server control files is not the '
1076 'first occurrance of that import.' %
1077 (name, namespace[name], module_name))
1078
1079 namespace[name] = getattr(module, name)
1080
1081
1082 # This is the equivalent of prepending a bunch of import statements to
1083 # the front of the control script.
mbligha2b07dd2009-06-22 18:26:13 +00001084 namespace.update(os=os, sys=sys, logging=logging)
mbligh084bc172008-10-18 14:02:45 +00001085 _import_names('autotest_lib.server',
1086 ('hosts', 'autotest', 'kvm', 'git', 'standalone_profiler',
1087 'source_kernel', 'rpm_kernel', 'deb_kernel', 'git_kernel'))
1088 _import_names('autotest_lib.server.subcommand',
1089 ('parallel', 'parallel_simple', 'subcommand'))
1090 _import_names('autotest_lib.server.utils',
1091 ('run', 'get_tmp_dir', 'sh_escape', 'parse_machine'))
1092 _import_names('autotest_lib.client.common_lib.error')
1093 _import_names('autotest_lib.client.common_lib.barrier', ('barrier',))
1094
1095 # Inject ourself as the job object into other classes within the API.
1096 # (Yuck, this injection is a gross thing be part of a public API. -gps)
1097 #
1098 # XXX Base & SiteAutotest do not appear to use .job. Who does?
1099 namespace['autotest'].Autotest.job = self
1100 # server.hosts.base_classes.Host uses .job.
1101 namespace['hosts'].Host.job = self
Eric Li10222b82010-11-24 09:33:15 -08001102 namespace['hosts'].factory.ssh_user = self._ssh_user
1103 namespace['hosts'].factory.ssh_port = self._ssh_port
1104 namespace['hosts'].factory.ssh_pass = self._ssh_pass
Fang Dengd1c2b732013-08-20 12:59:46 -07001105 namespace['hosts'].factory.ssh_verbosity_flag = (
1106 self._ssh_verbosity_flag)
Aviv Keshetc5947fa2013-09-04 14:06:29 -07001107 namespace['hosts'].factory.ssh_options = self._ssh_options
mbligh084bc172008-10-18 14:02:45 +00001108
1109
1110 def _execute_code(self, code_file, namespace, protect=True):
mbligh2b92b862008-11-22 13:25:32 +00001111 """
1112 Execute code using a copy of namespace as a server control script.
mbligh084bc172008-10-18 14:02:45 +00001113
1114 Unless protect_namespace is explicitly set to False, the dict will not
1115 be modified.
1116
1117 Args:
1118 code_file: The filename of the control file to execute.
1119 namespace: A dict containing names to make available during execution.
1120 protect: Boolean. If True (the default) a copy of the namespace dict
1121 is used during execution to prevent the code from modifying its
1122 contents outside of this function. If False the raw dict is
1123 passed in and modifications will be allowed.
1124 """
1125 if protect:
1126 namespace = namespace.copy()
1127 self._fill_server_control_namespace(namespace, protect=protect)
1128 # TODO: Simplify and get rid of the special cases for only 1 machine.
showard3e66e8c2008-10-27 19:20:51 +00001129 if len(self.machines) > 1:
mbligh084bc172008-10-18 14:02:45 +00001130 machines_text = '\n'.join(self.machines) + '\n'
1131 # Only rewrite the file if it does not match our machine list.
1132 try:
1133 machines_f = open(MACHINES_FILENAME, 'r')
1134 existing_machines_text = machines_f.read()
1135 machines_f.close()
1136 except EnvironmentError:
1137 existing_machines_text = None
1138 if machines_text != existing_machines_text:
1139 utils.open_write_close(MACHINES_FILENAME, machines_text)
1140 execfile(code_file, namespace, namespace)
jadmanski10646442008-08-13 14:05:21 +00001141
1142
jadmanskie29d0e42010-06-17 16:06:52 +00001143 def _parse_status(self, new_line):
mbligh0d0f67d2009-11-06 03:15:03 +00001144 if not self._using_parser:
jadmanski10646442008-08-13 14:05:21 +00001145 return
jadmanskie29d0e42010-06-17 16:06:52 +00001146 new_tests = self.parser.process_lines([new_line])
jadmanski10646442008-08-13 14:05:21 +00001147 for test in new_tests:
1148 self.__insert_test(test)
1149
1150
1151 def __insert_test(self, test):
mbligh2b92b862008-11-22 13:25:32 +00001152 """
1153 An internal method to insert a new test result into the
jadmanski10646442008-08-13 14:05:21 +00001154 database. This method will not raise an exception, even if an
1155 error occurs during the insert, to avoid failing a test
1156 simply because of unexpected database issues."""
showard21baa452008-10-21 00:08:39 +00001157 self.num_tests_run += 1
1158 if status_lib.is_worse_than_or_equal_to(test.status, 'FAIL'):
1159 self.num_tests_failed += 1
jadmanski10646442008-08-13 14:05:21 +00001160 try:
1161 self.results_db.insert_test(self.job_model, test)
1162 except Exception:
1163 msg = ("WARNING: An unexpected error occured while "
1164 "inserting test results into the database. "
1165 "Ignoring error.\n" + traceback.format_exc())
1166 print >> sys.stderr, msg
1167
mblighcaa62c22008-04-07 21:51:17 +00001168
mblighfc3da5b2010-01-06 18:37:22 +00001169 def preprocess_client_state(self):
1170 """
1171 Produce a state file for initializing the state of a client job.
1172
1173 Creates a new client state file with all the current server state, as
1174 well as some pre-set client state.
1175
1176 @returns The path of the file the state was written into.
1177 """
1178 # initialize the sysinfo state
1179 self._state.set('client', 'sysinfo', self.sysinfo.serialize())
1180
1181 # dump the state out to a tempfile
1182 fd, file_path = tempfile.mkstemp(dir=self.tmpdir)
1183 os.close(fd)
mbligha2c99492010-01-27 22:59:50 +00001184
1185 # write_to_file doesn't need locking, we exclusively own file_path
mblighfc3da5b2010-01-06 18:37:22 +00001186 self._state.write_to_file(file_path)
1187 return file_path
1188
1189
1190 def postprocess_client_state(self, state_path):
1191 """
1192 Update the state of this job with the state from a client job.
1193
1194 Updates the state of the server side of a job with the final state
1195 of a client job that was run. Updates the non-client-specific state,
1196 pulls in some specific bits from the client-specific state, and then
1197 discards the rest. Removes the state file afterwards
1198
1199 @param state_file A path to the state file from the client.
1200 """
1201 # update the on-disk state
mblighfc3da5b2010-01-06 18:37:22 +00001202 try:
jadmanskib6e7bdb2010-04-13 16:00:39 +00001203 self._state.read_from_file(state_path)
mblighfc3da5b2010-01-06 18:37:22 +00001204 os.remove(state_path)
mbligha2c99492010-01-27 22:59:50 +00001205 except OSError, e:
mblighfc3da5b2010-01-06 18:37:22 +00001206 # ignore file-not-found errors
1207 if e.errno != errno.ENOENT:
1208 raise
jadmanskib6e7bdb2010-04-13 16:00:39 +00001209 else:
1210 logging.debug('Client state file %s not found', state_path)
mblighfc3da5b2010-01-06 18:37:22 +00001211
1212 # update the sysinfo state
1213 if self._state.has('client', 'sysinfo'):
1214 self.sysinfo.deserialize(self._state.get('client', 'sysinfo'))
1215
1216 # drop all the client-specific state
1217 self._state.discard_namespace('client')
1218
1219
mbligh0a883702010-04-21 01:58:34 +00001220 def clear_all_known_hosts(self):
1221 """Clears known hosts files for all AbstractSSHHosts."""
1222 for host in self.hosts:
1223 if isinstance(host, abstract_ssh.AbstractSSHHost):
1224 host.clear_known_hosts()
1225
1226
jadmanskif37df842009-02-11 00:03:26 +00001227class warning_manager(object):
1228 """Class for controlling warning logs. Manages the enabling and disabling
1229 of warnings."""
1230 def __init__(self):
1231 # a map of warning types to a list of disabled time intervals
1232 self.disabled_warnings = {}
1233
1234
1235 def is_valid(self, timestamp, warning_type):
1236 """Indicates if a warning (based on the time it occured and its type)
1237 is a valid warning. A warning is considered "invalid" if this type of
1238 warning was marked as "disabled" at the time the warning occured."""
1239 disabled_intervals = self.disabled_warnings.get(warning_type, [])
1240 for start, end in disabled_intervals:
1241 if timestamp >= start and (end is None or timestamp < end):
1242 return False
1243 return True
1244
1245
1246 def disable_warnings(self, warning_type, current_time_func=time.time):
1247 """As of now, disables all further warnings of this type."""
1248 intervals = self.disabled_warnings.setdefault(warning_type, [])
1249 if not intervals or intervals[-1][1] is not None:
jadmanski16a7ff72009-04-01 18:19:53 +00001250 intervals.append((int(current_time_func()), None))
jadmanskif37df842009-02-11 00:03:26 +00001251
1252
1253 def enable_warnings(self, warning_type, current_time_func=time.time):
1254 """As of now, enables all further warnings of this type."""
1255 intervals = self.disabled_warnings.get(warning_type, [])
1256 if intervals and intervals[-1][1] is None:
jadmanski16a7ff72009-04-01 18:19:53 +00001257 intervals[-1] = (intervals[-1][0], int(current_time_func()))
Paul Pendlebury57593562011-06-15 10:45:49 -07001258
1259
1260# load up site-specific code for generating site-specific job data
1261get_site_job_data = utils.import_site_function(__file__,
1262 "autotest_lib.server.site_server_job", "get_site_job_data",
1263 _get_site_job_data_dummy)
1264
1265
1266site_server_job = utils.import_site_class(
1267 __file__, "autotest_lib.server.site_server_job", "site_server_job",
1268 base_server_job)
1269
1270
1271class server_job(site_server_job):
Dale Curtis456d3c12011-07-19 11:42:51 -07001272 pass