blob: 38b2d0c53c8d4ed699072973a441e584cb622163 [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,
561 job_labels=''):
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 Shicf4d2032015-03-12 15:04:21 -0700644 self._execute_code(server_control_file, namespace)
mbligh26f0d882009-06-22 18:30:01 +0000645 logging.info("Finished processing control file")
jadmanski10646442008-08-13 14:05:21 +0000646
Dan Shib03ea9d2013-08-15 17:13:27 -0700647 # If no device error occured, no need to collect crashinfo.
648 collect_crashinfo = self.failed_with_device_error
Eric Li6f27d4f2010-09-29 10:55:17 -0700649 except Exception, e:
showardcf8d4922009-10-14 16:08:39 +0000650 try:
651 logging.exception(
652 'Exception escaped control file, job aborting:')
Eric Li6f27d4f2010-09-29 10:55:17 -0700653 self.record('INFO', None, None, str(e),
654 {'job_abort_reason': str(e)})
showardcf8d4922009-10-14 16:08:39 +0000655 except:
656 pass # don't let logging exceptions here interfere
657 raise
jadmanski10646442008-08-13 14:05:21 +0000658 finally:
mblighaebe3b62008-12-22 14:45:40 +0000659 if temp_control_file_dir:
jadmanskie432dd22009-01-30 15:04:51 +0000660 # Clean up temp directory used for copies of the control files
mblighaebe3b62008-12-22 14:45:40 +0000661 try:
662 shutil.rmtree(temp_control_file_dir)
663 except Exception, e:
Ilja H. Friedel04be2bd2014-05-07 21:29:59 -0700664 logging.warning('Could not remove temp directory %s: %s',
mblighe7d9c602009-07-02 19:02:33 +0000665 temp_control_file_dir, e)
jadmanskie432dd22009-01-30 15:04:51 +0000666
jadmanskicdd0c402008-09-19 21:21:31 +0000667 if machines and (collect_crashdumps or collect_crashinfo):
jadmanski10646442008-08-13 14:05:21 +0000668 namespace['test_start_time'] = test_start_time
Christopher Wileyf594c5e2013-07-03 18:25:30 -0700669 if skip_crash_collection:
670 logging.info('Skipping crash dump/info collection '
671 'as requested.')
672 elif collect_crashinfo:
mbligh084bc172008-10-18 14:02:45 +0000673 # includes crashdumps
674 self._execute_code(CRASHINFO_CONTROL_FILE, namespace)
jadmanskicdd0c402008-09-19 21:21:31 +0000675 else:
mbligh084bc172008-10-18 14:02:45 +0000676 self._execute_code(CRASHDUMPS_CONTROL_FILE, namespace)
jadmanski10646442008-08-13 14:05:21 +0000677 self.disable_external_logging()
Chris Sosaf4d43ff2012-10-30 11:21:05 -0700678 if self._uncollected_log_file and created_uncollected_logs:
679 os.remove(self._uncollected_log_file)
jadmanski10646442008-08-13 14:05:21 +0000680 if install_after and machines:
mbligh084bc172008-10-18 14:02:45 +0000681 self._execute_code(INSTALL_CONTROL_FILE, namespace)
jadmanski10646442008-08-13 14:05:21 +0000682
683
684 def run_test(self, url, *args, **dargs):
mbligh2b92b862008-11-22 13:25:32 +0000685 """
686 Summon a test object and run it.
jadmanski10646442008-08-13 14:05:21 +0000687
688 tag
689 tag to add to testname
690 url
691 url of the test to run
692 """
Christopher Wiley8a91f232013-07-09 11:02:27 -0700693 if self._disable_sysinfo:
694 dargs['disable_sysinfo'] = True
695
mblighfc3da5b2010-01-06 18:37:22 +0000696 group, testname = self.pkgmgr.get_package_name(url, 'test')
697 testname, subdir, tag = self._build_tagged_test_name(testname, dargs)
698 outputdir = self._make_test_outputdir(subdir)
jadmanski10646442008-08-13 14:05:21 +0000699
700 def group_func():
701 try:
702 test.runtest(self, url, tag, args, dargs)
703 except error.TestBaseException, e:
704 self.record(e.exit_status, subdir, testname, str(e))
705 raise
706 except Exception, e:
707 info = str(e) + "\n" + traceback.format_exc()
708 self.record('FAIL', subdir, testname, info)
709 raise
710 else:
mbligh2b92b862008-11-22 13:25:32 +0000711 self.record('GOOD', subdir, testname, 'completed successfully')
jadmanskide292df2008-08-26 20:51:14 +0000712
713 result, exc_info = self._run_group(testname, subdir, group_func)
714 if exc_info and isinstance(exc_info[1], error.TestBaseException):
715 return False
716 elif exc_info:
717 raise exc_info[0], exc_info[1], exc_info[2]
718 else:
719 return True
jadmanski10646442008-08-13 14:05:21 +0000720
721
722 def _run_group(self, name, subdir, function, *args, **dargs):
723 """\
724 Underlying method for running something inside of a group.
725 """
jadmanskide292df2008-08-26 20:51:14 +0000726 result, exc_info = None, None
jadmanski10646442008-08-13 14:05:21 +0000727 try:
728 self.record('START', subdir, name)
jadmanski52053632010-06-11 21:08:10 +0000729 result = function(*args, **dargs)
jadmanski10646442008-08-13 14:05:21 +0000730 except error.TestBaseException, e:
jadmanskib88d6dc2009-01-10 00:33:18 +0000731 self.record("END %s" % e.exit_status, subdir, name)
jadmanskide292df2008-08-26 20:51:14 +0000732 exc_info = sys.exc_info()
jadmanski10646442008-08-13 14:05:21 +0000733 except Exception, e:
734 err_msg = str(e) + '\n'
735 err_msg += traceback.format_exc()
736 self.record('END ABORT', subdir, name, err_msg)
737 raise error.JobError(name + ' failed\n' + traceback.format_exc())
738 else:
739 self.record('END GOOD', subdir, name)
740
jadmanskide292df2008-08-26 20:51:14 +0000741 return result, exc_info
jadmanski10646442008-08-13 14:05:21 +0000742
743
744 def run_group(self, function, *args, **dargs):
745 """\
746 function:
747 subroutine to run
748 *args:
749 arguments for the function
750 """
751
752 name = function.__name__
753
754 # Allow the tag for the group to be specified.
755 tag = dargs.pop('tag', None)
756 if tag:
757 name = tag
758
jadmanskide292df2008-08-26 20:51:14 +0000759 return self._run_group(name, None, function, *args, **dargs)[0]
jadmanski10646442008-08-13 14:05:21 +0000760
761
Gwendal Grignou7a61d2f2014-05-23 11:05:51 -0700762 def run_op(self, op, op_func, get_kernel_func):
jadmanski10646442008-08-13 14:05:21 +0000763 """\
764 A specialization of run_group meant specifically for handling
Gwendal Grignou7a61d2f2014-05-23 11:05:51 -0700765 management operation. Includes support for capturing the kernel version
766 after the operation.
jadmanski10646442008-08-13 14:05:21 +0000767
Gwendal Grignou7a61d2f2014-05-23 11:05:51 -0700768 Args:
769 op: name of the operation.
770 op_func: a function that carries out the operation (reboot, suspend)
771 get_kernel_func: a function that returns a string
772 representing the kernel version.
jadmanski10646442008-08-13 14:05:21 +0000773 """
jadmanski10646442008-08-13 14:05:21 +0000774 try:
Gwendal Grignou7a61d2f2014-05-23 11:05:51 -0700775 self.record('START', None, op)
776 op_func()
jadmanski10646442008-08-13 14:05:21 +0000777 except Exception, e:
jadmanski10646442008-08-13 14:05:21 +0000778 err_msg = str(e) + '\n' + traceback.format_exc()
Gwendal Grignou7a61d2f2014-05-23 11:05:51 -0700779 self.record('END FAIL', None, op, err_msg)
jadmanski4b51d542009-04-08 14:17:16 +0000780 raise
jadmanski10646442008-08-13 14:05:21 +0000781 else:
782 kernel = get_kernel_func()
Gwendal Grignou7a61d2f2014-05-23 11:05:51 -0700783 self.record('END GOOD', None, op,
Dale Curtis74a314b2011-06-23 14:55:46 -0700784 optional_fields={"kernel": kernel})
jadmanski10646442008-08-13 14:05:21 +0000785
786
jadmanskie432dd22009-01-30 15:04:51 +0000787 def run_control(self, path):
788 """Execute a control file found at path (relative to the autotest
789 path). Intended for executing a control file within a control file,
790 not for running the top-level job control file."""
791 path = os.path.join(self.autodir, path)
792 control_file = self._load_control_file(path)
mbligh0d0f67d2009-11-06 03:15:03 +0000793 self.run(control=control_file, control_file_dir=self._USE_TEMP_DIR)
jadmanskie432dd22009-01-30 15:04:51 +0000794
795
jadmanskic09fc152008-10-15 17:56:59 +0000796 def add_sysinfo_command(self, command, logfile=None, on_every_test=False):
mbligh4395bbd2009-03-25 19:34:17 +0000797 self._add_sysinfo_loggable(sysinfo.command(command, logf=logfile),
jadmanskic09fc152008-10-15 17:56:59 +0000798 on_every_test)
799
800
801 def add_sysinfo_logfile(self, file, on_every_test=False):
802 self._add_sysinfo_loggable(sysinfo.logfile(file), on_every_test)
803
804
805 def _add_sysinfo_loggable(self, loggable, on_every_test):
806 if on_every_test:
807 self.sysinfo.test_loggables.add(loggable)
808 else:
809 self.sysinfo.boot_loggables.add(loggable)
810
811
jadmanski10646442008-08-13 14:05:21 +0000812 def _read_warnings(self):
jadmanskif37df842009-02-11 00:03:26 +0000813 """Poll all the warning loggers and extract any new warnings that have
814 been logged. If the warnings belong to a category that is currently
815 disabled, this method will discard them and they will no longer be
816 retrievable.
817
818 Returns a list of (timestamp, message) tuples, where timestamp is an
819 integer epoch timestamp."""
jadmanski10646442008-08-13 14:05:21 +0000820 warnings = []
821 while True:
822 # pull in a line of output from every logger that has
823 # output ready to be read
mbligh2b92b862008-11-22 13:25:32 +0000824 loggers, _, _ = select.select(self.warning_loggers, [], [], 0)
jadmanski10646442008-08-13 14:05:21 +0000825 closed_loggers = set()
826 for logger in loggers:
827 line = logger.readline()
828 # record any broken pipes (aka line == empty)
829 if len(line) == 0:
830 closed_loggers.add(logger)
831 continue
jadmanskif37df842009-02-11 00:03:26 +0000832 # parse out the warning
833 timestamp, msgtype, msg = line.split('\t', 2)
834 timestamp = int(timestamp)
835 # if the warning is valid, add it to the results
836 if self.warning_manager.is_valid(timestamp, msgtype):
837 warnings.append((timestamp, msg.strip()))
jadmanski10646442008-08-13 14:05:21 +0000838
839 # stop listening to loggers that are closed
840 self.warning_loggers -= closed_loggers
841
842 # stop if none of the loggers have any output left
843 if not loggers:
844 break
845
846 # sort into timestamp order
847 warnings.sort()
848 return warnings
849
850
showardcc929362010-01-25 21:20:41 +0000851 def _unique_subdirectory(self, base_subdirectory_name):
852 """Compute a unique results subdirectory based on the given name.
853
854 Appends base_subdirectory_name with a number as necessary to find a
855 directory name that doesn't already exist.
856 """
857 subdirectory = base_subdirectory_name
858 counter = 1
859 while os.path.exists(os.path.join(self.resultdir, subdirectory)):
860 subdirectory = base_subdirectory_name + '.' + str(counter)
861 counter += 1
862 return subdirectory
863
864
jadmanski52053632010-06-11 21:08:10 +0000865 def get_record_context(self):
866 """Returns an object representing the current job.record context.
867
868 The object returned is an opaque object with a 0-arg restore method
869 which can be called to restore the job.record context (i.e. indentation)
870 to the current level. The intention is that it should be used when
871 something external which generate job.record calls (e.g. an autotest
872 client) can fail catastrophically and the server job record state
873 needs to be reset to its original "known good" state.
874
875 @return: A context object with a 0-arg restore() method."""
876 return self._indenter.get_context()
877
878
showardcc929362010-01-25 21:20:41 +0000879 def record_summary(self, status_code, test_name, reason='', attributes=None,
880 distinguishing_attributes=(), child_test_ids=None):
881 """Record a summary test result.
882
883 @param status_code: status code string, see
884 common_lib.log.is_valid_status()
885 @param test_name: name of the test
886 @param reason: (optional) string providing detailed reason for test
887 outcome
888 @param attributes: (optional) dict of string keyvals to associate with
889 this result
890 @param distinguishing_attributes: (optional) list of attribute names
891 that should be used to distinguish identically-named test
892 results. These attributes should be present in the attributes
893 parameter. This is used to generate user-friendly subdirectory
894 names.
895 @param child_test_ids: (optional) list of test indices for test results
896 used in generating this result.
897 """
898 subdirectory_name_parts = [test_name]
899 for attribute in distinguishing_attributes:
900 assert attributes
901 assert attribute in attributes, '%s not in %s' % (attribute,
902 attributes)
903 subdirectory_name_parts.append(attributes[attribute])
904 base_subdirectory_name = '.'.join(subdirectory_name_parts)
905
906 subdirectory = self._unique_subdirectory(base_subdirectory_name)
907 subdirectory_path = os.path.join(self.resultdir, subdirectory)
908 os.mkdir(subdirectory_path)
909
910 self.record(status_code, subdirectory, test_name,
911 status=reason, optional_fields={'is_summary': True})
912
913 if attributes:
914 utils.write_keyval(subdirectory_path, attributes)
915
916 if child_test_ids:
917 ids_string = ','.join(str(test_id) for test_id in child_test_ids)
918 summary_data = {'child_test_ids': ids_string}
919 utils.write_keyval(os.path.join(subdirectory_path, 'summary_data'),
920 summary_data)
921
922
jadmanski16a7ff72009-04-01 18:19:53 +0000923 def disable_warnings(self, warning_type):
jadmanskif37df842009-02-11 00:03:26 +0000924 self.warning_manager.disable_warnings(warning_type)
jadmanski16a7ff72009-04-01 18:19:53 +0000925 self.record("INFO", None, None,
926 "disabling %s warnings" % warning_type,
927 {"warnings.disable": warning_type})
jadmanskif37df842009-02-11 00:03:26 +0000928
929
jadmanski16a7ff72009-04-01 18:19:53 +0000930 def enable_warnings(self, warning_type):
jadmanskif37df842009-02-11 00:03:26 +0000931 self.warning_manager.enable_warnings(warning_type)
jadmanski16a7ff72009-04-01 18:19:53 +0000932 self.record("INFO", None, None,
933 "enabling %s warnings" % warning_type,
934 {"warnings.enable": warning_type})
jadmanskif37df842009-02-11 00:03:26 +0000935
936
jadmanski779bd292009-03-19 17:33:33 +0000937 def get_status_log_path(self, subdir=None):
938 """Return the path to the job status log.
939
940 @param subdir - Optional paramter indicating that you want the path
941 to a subdirectory status log.
942
943 @returns The path where the status log should be.
944 """
mbligh210bae62009-04-01 18:33:13 +0000945 if self.resultdir:
946 if subdir:
947 return os.path.join(self.resultdir, subdir, "status.log")
948 else:
949 return os.path.join(self.resultdir, "status.log")
jadmanski779bd292009-03-19 17:33:33 +0000950 else:
mbligh210bae62009-04-01 18:33:13 +0000951 return None
jadmanski779bd292009-03-19 17:33:33 +0000952
953
jadmanski6bb32d72009-03-19 20:25:24 +0000954 def _update_uncollected_logs_list(self, update_func):
955 """Updates the uncollected logs list in a multi-process safe manner.
956
957 @param update_func - a function that updates the list of uncollected
958 logs. Should take one parameter, the list to be updated.
959 """
Dan Shi07e09af2013-04-12 09:31:29 -0700960 # Skip log collection if file _uncollected_log_file does not exist.
961 if not (self._uncollected_log_file and
962 os.path.exists(self._uncollected_log_file)):
963 return
mbligh0d0f67d2009-11-06 03:15:03 +0000964 if self._uncollected_log_file:
965 log_file = open(self._uncollected_log_file, "r+")
mbligha788dc42009-03-26 21:10:16 +0000966 fcntl.flock(log_file, fcntl.LOCK_EX)
jadmanski6bb32d72009-03-19 20:25:24 +0000967 try:
968 uncollected_logs = pickle.load(log_file)
969 update_func(uncollected_logs)
970 log_file.seek(0)
971 log_file.truncate()
972 pickle.dump(uncollected_logs, log_file)
jadmanski3bff9092009-04-22 18:09:47 +0000973 log_file.flush()
jadmanski6bb32d72009-03-19 20:25:24 +0000974 finally:
975 fcntl.flock(log_file, fcntl.LOCK_UN)
976 log_file.close()
977
978
979 def add_client_log(self, hostname, remote_path, local_path):
980 """Adds a new set of client logs to the list of uncollected logs,
981 to allow for future log recovery.
982
983 @param host - the hostname of the machine holding the logs
984 @param remote_path - the directory on the remote machine holding logs
985 @param local_path - the local directory to copy the logs into
986 """
987 def update_func(logs_list):
988 logs_list.append((hostname, remote_path, local_path))
989 self._update_uncollected_logs_list(update_func)
990
991
992 def remove_client_log(self, hostname, remote_path, local_path):
993 """Removes a set of client logs from the list of uncollected logs,
994 to allow for future log recovery.
995
996 @param host - the hostname of the machine holding the logs
997 @param remote_path - the directory on the remote machine holding logs
998 @param local_path - the local directory to copy the logs into
999 """
1000 def update_func(logs_list):
1001 logs_list.remove((hostname, remote_path, local_path))
1002 self._update_uncollected_logs_list(update_func)
1003
1004
mbligh0d0f67d2009-11-06 03:15:03 +00001005 def get_client_logs(self):
1006 """Retrieves the list of uncollected logs, if it exists.
1007
1008 @returns A list of (host, remote_path, local_path) tuples. Returns
1009 an empty list if no uncollected logs file exists.
1010 """
1011 log_exists = (self._uncollected_log_file and
1012 os.path.exists(self._uncollected_log_file))
1013 if log_exists:
1014 return pickle.load(open(self._uncollected_log_file))
1015 else:
1016 return []
1017
1018
mbligh084bc172008-10-18 14:02:45 +00001019 def _fill_server_control_namespace(self, namespace, protect=True):
mbligh2b92b862008-11-22 13:25:32 +00001020 """
1021 Prepare a namespace to be used when executing server control files.
mbligh084bc172008-10-18 14:02:45 +00001022
1023 This sets up the control file API by importing modules and making them
1024 available under the appropriate names within namespace.
1025
1026 For use by _execute_code().
1027
1028 Args:
1029 namespace: The namespace dictionary to fill in.
1030 protect: Boolean. If True (the default) any operation that would
1031 clobber an existing entry in namespace will cause an error.
1032 Raises:
1033 error.AutoservError: When a name would be clobbered by import.
1034 """
1035 def _import_names(module_name, names=()):
mbligh2b92b862008-11-22 13:25:32 +00001036 """
1037 Import a module and assign named attributes into namespace.
mbligh084bc172008-10-18 14:02:45 +00001038
1039 Args:
1040 module_name: The string module name.
1041 names: A limiting list of names to import from module_name. If
1042 empty (the default), all names are imported from the module
1043 similar to a "from foo.bar import *" statement.
1044 Raises:
1045 error.AutoservError: When a name being imported would clobber
1046 a name already in namespace.
1047 """
1048 module = __import__(module_name, {}, {}, names)
1049
1050 # No names supplied? Import * from the lowest level module.
1051 # (Ugh, why do I have to implement this part myself?)
1052 if not names:
1053 for submodule_name in module_name.split('.')[1:]:
1054 module = getattr(module, submodule_name)
1055 if hasattr(module, '__all__'):
1056 names = getattr(module, '__all__')
1057 else:
1058 names = dir(module)
1059
1060 # Install each name into namespace, checking to make sure it
1061 # doesn't override anything that already exists.
1062 for name in names:
1063 # Check for conflicts to help prevent future problems.
1064 if name in namespace and protect:
1065 if namespace[name] is not getattr(module, name):
1066 raise error.AutoservError('importing name '
1067 '%s from %s %r would override %r' %
1068 (name, module_name, getattr(module, name),
1069 namespace[name]))
1070 else:
1071 # Encourage cleanliness and the use of __all__ for a
1072 # more concrete API with less surprises on '*' imports.
1073 warnings.warn('%s (%r) being imported from %s for use '
1074 'in server control files is not the '
1075 'first occurrance of that import.' %
1076 (name, namespace[name], module_name))
1077
1078 namespace[name] = getattr(module, name)
1079
1080
1081 # This is the equivalent of prepending a bunch of import statements to
1082 # the front of the control script.
mbligha2b07dd2009-06-22 18:26:13 +00001083 namespace.update(os=os, sys=sys, logging=logging)
mbligh084bc172008-10-18 14:02:45 +00001084 _import_names('autotest_lib.server',
1085 ('hosts', 'autotest', 'kvm', 'git', 'standalone_profiler',
1086 'source_kernel', 'rpm_kernel', 'deb_kernel', 'git_kernel'))
1087 _import_names('autotest_lib.server.subcommand',
1088 ('parallel', 'parallel_simple', 'subcommand'))
1089 _import_names('autotest_lib.server.utils',
1090 ('run', 'get_tmp_dir', 'sh_escape', 'parse_machine'))
1091 _import_names('autotest_lib.client.common_lib.error')
1092 _import_names('autotest_lib.client.common_lib.barrier', ('barrier',))
1093
1094 # Inject ourself as the job object into other classes within the API.
1095 # (Yuck, this injection is a gross thing be part of a public API. -gps)
1096 #
1097 # XXX Base & SiteAutotest do not appear to use .job. Who does?
1098 namespace['autotest'].Autotest.job = self
1099 # server.hosts.base_classes.Host uses .job.
1100 namespace['hosts'].Host.job = self
Eric Li10222b82010-11-24 09:33:15 -08001101 namespace['hosts'].factory.ssh_user = self._ssh_user
1102 namespace['hosts'].factory.ssh_port = self._ssh_port
1103 namespace['hosts'].factory.ssh_pass = self._ssh_pass
Fang Dengd1c2b732013-08-20 12:59:46 -07001104 namespace['hosts'].factory.ssh_verbosity_flag = (
1105 self._ssh_verbosity_flag)
Aviv Keshetc5947fa2013-09-04 14:06:29 -07001106 namespace['hosts'].factory.ssh_options = self._ssh_options
mbligh084bc172008-10-18 14:02:45 +00001107
1108
1109 def _execute_code(self, code_file, namespace, protect=True):
mbligh2b92b862008-11-22 13:25:32 +00001110 """
1111 Execute code using a copy of namespace as a server control script.
mbligh084bc172008-10-18 14:02:45 +00001112
1113 Unless protect_namespace is explicitly set to False, the dict will not
1114 be modified.
1115
1116 Args:
1117 code_file: The filename of the control file to execute.
1118 namespace: A dict containing names to make available during execution.
1119 protect: Boolean. If True (the default) a copy of the namespace dict
1120 is used during execution to prevent the code from modifying its
1121 contents outside of this function. If False the raw dict is
1122 passed in and modifications will be allowed.
1123 """
1124 if protect:
1125 namespace = namespace.copy()
1126 self._fill_server_control_namespace(namespace, protect=protect)
1127 # TODO: Simplify and get rid of the special cases for only 1 machine.
showard3e66e8c2008-10-27 19:20:51 +00001128 if len(self.machines) > 1:
mbligh084bc172008-10-18 14:02:45 +00001129 machines_text = '\n'.join(self.machines) + '\n'
1130 # Only rewrite the file if it does not match our machine list.
1131 try:
1132 machines_f = open(MACHINES_FILENAME, 'r')
1133 existing_machines_text = machines_f.read()
1134 machines_f.close()
1135 except EnvironmentError:
1136 existing_machines_text = None
1137 if machines_text != existing_machines_text:
1138 utils.open_write_close(MACHINES_FILENAME, machines_text)
1139 execfile(code_file, namespace, namespace)
jadmanski10646442008-08-13 14:05:21 +00001140
1141
jadmanskie29d0e42010-06-17 16:06:52 +00001142 def _parse_status(self, new_line):
mbligh0d0f67d2009-11-06 03:15:03 +00001143 if not self._using_parser:
jadmanski10646442008-08-13 14:05:21 +00001144 return
jadmanskie29d0e42010-06-17 16:06:52 +00001145 new_tests = self.parser.process_lines([new_line])
jadmanski10646442008-08-13 14:05:21 +00001146 for test in new_tests:
1147 self.__insert_test(test)
1148
1149
1150 def __insert_test(self, test):
mbligh2b92b862008-11-22 13:25:32 +00001151 """
1152 An internal method to insert a new test result into the
jadmanski10646442008-08-13 14:05:21 +00001153 database. This method will not raise an exception, even if an
1154 error occurs during the insert, to avoid failing a test
1155 simply because of unexpected database issues."""
showard21baa452008-10-21 00:08:39 +00001156 self.num_tests_run += 1
1157 if status_lib.is_worse_than_or_equal_to(test.status, 'FAIL'):
1158 self.num_tests_failed += 1
jadmanski10646442008-08-13 14:05:21 +00001159 try:
1160 self.results_db.insert_test(self.job_model, test)
1161 except Exception:
1162 msg = ("WARNING: An unexpected error occured while "
1163 "inserting test results into the database. "
1164 "Ignoring error.\n" + traceback.format_exc())
1165 print >> sys.stderr, msg
1166
mblighcaa62c22008-04-07 21:51:17 +00001167
mblighfc3da5b2010-01-06 18:37:22 +00001168 def preprocess_client_state(self):
1169 """
1170 Produce a state file for initializing the state of a client job.
1171
1172 Creates a new client state file with all the current server state, as
1173 well as some pre-set client state.
1174
1175 @returns The path of the file the state was written into.
1176 """
1177 # initialize the sysinfo state
1178 self._state.set('client', 'sysinfo', self.sysinfo.serialize())
1179
1180 # dump the state out to a tempfile
1181 fd, file_path = tempfile.mkstemp(dir=self.tmpdir)
1182 os.close(fd)
mbligha2c99492010-01-27 22:59:50 +00001183
1184 # write_to_file doesn't need locking, we exclusively own file_path
mblighfc3da5b2010-01-06 18:37:22 +00001185 self._state.write_to_file(file_path)
1186 return file_path
1187
1188
1189 def postprocess_client_state(self, state_path):
1190 """
1191 Update the state of this job with the state from a client job.
1192
1193 Updates the state of the server side of a job with the final state
1194 of a client job that was run. Updates the non-client-specific state,
1195 pulls in some specific bits from the client-specific state, and then
1196 discards the rest. Removes the state file afterwards
1197
1198 @param state_file A path to the state file from the client.
1199 """
1200 # update the on-disk state
mblighfc3da5b2010-01-06 18:37:22 +00001201 try:
jadmanskib6e7bdb2010-04-13 16:00:39 +00001202 self._state.read_from_file(state_path)
mblighfc3da5b2010-01-06 18:37:22 +00001203 os.remove(state_path)
mbligha2c99492010-01-27 22:59:50 +00001204 except OSError, e:
mblighfc3da5b2010-01-06 18:37:22 +00001205 # ignore file-not-found errors
1206 if e.errno != errno.ENOENT:
1207 raise
jadmanskib6e7bdb2010-04-13 16:00:39 +00001208 else:
1209 logging.debug('Client state file %s not found', state_path)
mblighfc3da5b2010-01-06 18:37:22 +00001210
1211 # update the sysinfo state
1212 if self._state.has('client', 'sysinfo'):
1213 self.sysinfo.deserialize(self._state.get('client', 'sysinfo'))
1214
1215 # drop all the client-specific state
1216 self._state.discard_namespace('client')
1217
1218
mbligh0a883702010-04-21 01:58:34 +00001219 def clear_all_known_hosts(self):
1220 """Clears known hosts files for all AbstractSSHHosts."""
1221 for host in self.hosts:
1222 if isinstance(host, abstract_ssh.AbstractSSHHost):
1223 host.clear_known_hosts()
1224
1225
jadmanskif37df842009-02-11 00:03:26 +00001226class warning_manager(object):
1227 """Class for controlling warning logs. Manages the enabling and disabling
1228 of warnings."""
1229 def __init__(self):
1230 # a map of warning types to a list of disabled time intervals
1231 self.disabled_warnings = {}
1232
1233
1234 def is_valid(self, timestamp, warning_type):
1235 """Indicates if a warning (based on the time it occured and its type)
1236 is a valid warning. A warning is considered "invalid" if this type of
1237 warning was marked as "disabled" at the time the warning occured."""
1238 disabled_intervals = self.disabled_warnings.get(warning_type, [])
1239 for start, end in disabled_intervals:
1240 if timestamp >= start and (end is None or timestamp < end):
1241 return False
1242 return True
1243
1244
1245 def disable_warnings(self, warning_type, current_time_func=time.time):
1246 """As of now, disables all further warnings of this type."""
1247 intervals = self.disabled_warnings.setdefault(warning_type, [])
1248 if not intervals or intervals[-1][1] is not None:
jadmanski16a7ff72009-04-01 18:19:53 +00001249 intervals.append((int(current_time_func()), None))
jadmanskif37df842009-02-11 00:03:26 +00001250
1251
1252 def enable_warnings(self, warning_type, current_time_func=time.time):
1253 """As of now, enables all further warnings of this type."""
1254 intervals = self.disabled_warnings.get(warning_type, [])
1255 if intervals and intervals[-1][1] is None:
jadmanski16a7ff72009-04-01 18:19:53 +00001256 intervals[-1] = (intervals[-1][0], int(current_time_func()))
Paul Pendlebury57593562011-06-15 10:45:49 -07001257
1258
1259# load up site-specific code for generating site-specific job data
1260get_site_job_data = utils.import_site_function(__file__,
1261 "autotest_lib.server.site_server_job", "get_site_job_data",
1262 _get_site_job_data_dummy)
1263
1264
1265site_server_job = utils.import_site_class(
1266 __file__, "autotest_lib.server.site_server_job", "site_server_job",
1267 base_server_job)
1268
1269
1270class server_job(site_server_job):
Dale Curtis456d3c12011-07-19 11:42:51 -07001271 pass