blob: c573047cc63fb4d91990536875655d8c82fbe3fd [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,
mblighe0cbc912010-03-11 18:03:07 +0000156 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,
617 namespace)
618 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)
mbligh26f0d882009-06-22 18:30:01 +0000642 logging.info("Processing control file")
jadmanskidef0c3c2009-03-25 20:07:10 +0000643 self._execute_code(server_control_file, namespace)
mbligh26f0d882009-06-22 18:30:01 +0000644 logging.info("Finished processing control file")
jadmanski10646442008-08-13 14:05:21 +0000645
Dan Shib03ea9d2013-08-15 17:13:27 -0700646 # If no device error occured, no need to collect crashinfo.
647 collect_crashinfo = self.failed_with_device_error
Eric Li6f27d4f2010-09-29 10:55:17 -0700648 except Exception, e:
showardcf8d4922009-10-14 16:08:39 +0000649 try:
650 logging.exception(
651 'Exception escaped control file, job aborting:')
Eric Li6f27d4f2010-09-29 10:55:17 -0700652 self.record('INFO', None, None, str(e),
653 {'job_abort_reason': str(e)})
showardcf8d4922009-10-14 16:08:39 +0000654 except:
655 pass # don't let logging exceptions here interfere
656 raise
jadmanski10646442008-08-13 14:05:21 +0000657 finally:
mblighaebe3b62008-12-22 14:45:40 +0000658 if temp_control_file_dir:
jadmanskie432dd22009-01-30 15:04:51 +0000659 # Clean up temp directory used for copies of the control files
mblighaebe3b62008-12-22 14:45:40 +0000660 try:
661 shutil.rmtree(temp_control_file_dir)
662 except Exception, e:
Ilja H. Friedel04be2bd2014-05-07 21:29:59 -0700663 logging.warning('Could not remove temp directory %s: %s',
mblighe7d9c602009-07-02 19:02:33 +0000664 temp_control_file_dir, e)
jadmanskie432dd22009-01-30 15:04:51 +0000665
jadmanskicdd0c402008-09-19 21:21:31 +0000666 if machines and (collect_crashdumps or collect_crashinfo):
jadmanski10646442008-08-13 14:05:21 +0000667 namespace['test_start_time'] = test_start_time
Christopher Wileyf594c5e2013-07-03 18:25:30 -0700668 if skip_crash_collection:
669 logging.info('Skipping crash dump/info collection '
670 'as requested.')
671 elif collect_crashinfo:
mbligh084bc172008-10-18 14:02:45 +0000672 # includes crashdumps
673 self._execute_code(CRASHINFO_CONTROL_FILE, namespace)
jadmanskicdd0c402008-09-19 21:21:31 +0000674 else:
mbligh084bc172008-10-18 14:02:45 +0000675 self._execute_code(CRASHDUMPS_CONTROL_FILE, namespace)
jadmanski10646442008-08-13 14:05:21 +0000676 self.disable_external_logging()
Chris Sosaf4d43ff2012-10-30 11:21:05 -0700677 if self._uncollected_log_file and created_uncollected_logs:
678 os.remove(self._uncollected_log_file)
jadmanski10646442008-08-13 14:05:21 +0000679 if install_after and machines:
mbligh084bc172008-10-18 14:02:45 +0000680 self._execute_code(INSTALL_CONTROL_FILE, namespace)
jadmanski10646442008-08-13 14:05:21 +0000681
682
683 def run_test(self, url, *args, **dargs):
mbligh2b92b862008-11-22 13:25:32 +0000684 """
685 Summon a test object and run it.
jadmanski10646442008-08-13 14:05:21 +0000686
687 tag
688 tag to add to testname
689 url
690 url of the test to run
691 """
Christopher Wiley8a91f232013-07-09 11:02:27 -0700692 if self._disable_sysinfo:
693 dargs['disable_sysinfo'] = True
694
mblighfc3da5b2010-01-06 18:37:22 +0000695 group, testname = self.pkgmgr.get_package_name(url, 'test')
696 testname, subdir, tag = self._build_tagged_test_name(testname, dargs)
697 outputdir = self._make_test_outputdir(subdir)
jadmanski10646442008-08-13 14:05:21 +0000698
699 def group_func():
700 try:
701 test.runtest(self, url, tag, args, dargs)
702 except error.TestBaseException, e:
703 self.record(e.exit_status, subdir, testname, str(e))
704 raise
705 except Exception, e:
706 info = str(e) + "\n" + traceback.format_exc()
707 self.record('FAIL', subdir, testname, info)
708 raise
709 else:
mbligh2b92b862008-11-22 13:25:32 +0000710 self.record('GOOD', subdir, testname, 'completed successfully')
jadmanskide292df2008-08-26 20:51:14 +0000711
712 result, exc_info = self._run_group(testname, subdir, group_func)
713 if exc_info and isinstance(exc_info[1], error.TestBaseException):
714 return False
715 elif exc_info:
716 raise exc_info[0], exc_info[1], exc_info[2]
717 else:
718 return True
jadmanski10646442008-08-13 14:05:21 +0000719
720
721 def _run_group(self, name, subdir, function, *args, **dargs):
722 """\
723 Underlying method for running something inside of a group.
724 """
jadmanskide292df2008-08-26 20:51:14 +0000725 result, exc_info = None, None
jadmanski10646442008-08-13 14:05:21 +0000726 try:
727 self.record('START', subdir, name)
jadmanski52053632010-06-11 21:08:10 +0000728 result = function(*args, **dargs)
jadmanski10646442008-08-13 14:05:21 +0000729 except error.TestBaseException, e:
jadmanskib88d6dc2009-01-10 00:33:18 +0000730 self.record("END %s" % e.exit_status, subdir, name)
jadmanskide292df2008-08-26 20:51:14 +0000731 exc_info = sys.exc_info()
jadmanski10646442008-08-13 14:05:21 +0000732 except Exception, e:
733 err_msg = str(e) + '\n'
734 err_msg += traceback.format_exc()
735 self.record('END ABORT', subdir, name, err_msg)
736 raise error.JobError(name + ' failed\n' + traceback.format_exc())
737 else:
738 self.record('END GOOD', subdir, name)
739
jadmanskide292df2008-08-26 20:51:14 +0000740 return result, exc_info
jadmanski10646442008-08-13 14:05:21 +0000741
742
743 def run_group(self, function, *args, **dargs):
744 """\
745 function:
746 subroutine to run
747 *args:
748 arguments for the function
749 """
750
751 name = function.__name__
752
753 # Allow the tag for the group to be specified.
754 tag = dargs.pop('tag', None)
755 if tag:
756 name = tag
757
jadmanskide292df2008-08-26 20:51:14 +0000758 return self._run_group(name, None, function, *args, **dargs)[0]
jadmanski10646442008-08-13 14:05:21 +0000759
760
Gwendal Grignou7a61d2f2014-05-23 11:05:51 -0700761 def run_op(self, op, op_func, get_kernel_func):
jadmanski10646442008-08-13 14:05:21 +0000762 """\
763 A specialization of run_group meant specifically for handling
Gwendal Grignou7a61d2f2014-05-23 11:05:51 -0700764 management operation. Includes support for capturing the kernel version
765 after the operation.
jadmanski10646442008-08-13 14:05:21 +0000766
Gwendal Grignou7a61d2f2014-05-23 11:05:51 -0700767 Args:
768 op: name of the operation.
769 op_func: a function that carries out the operation (reboot, suspend)
770 get_kernel_func: a function that returns a string
771 representing the kernel version.
jadmanski10646442008-08-13 14:05:21 +0000772 """
jadmanski10646442008-08-13 14:05:21 +0000773 try:
Gwendal Grignou7a61d2f2014-05-23 11:05:51 -0700774 self.record('START', None, op)
775 op_func()
jadmanski10646442008-08-13 14:05:21 +0000776 except Exception, e:
jadmanski10646442008-08-13 14:05:21 +0000777 err_msg = str(e) + '\n' + traceback.format_exc()
Gwendal Grignou7a61d2f2014-05-23 11:05:51 -0700778 self.record('END FAIL', None, op, err_msg)
jadmanski4b51d542009-04-08 14:17:16 +0000779 raise
jadmanski10646442008-08-13 14:05:21 +0000780 else:
781 kernel = get_kernel_func()
Gwendal Grignou7a61d2f2014-05-23 11:05:51 -0700782 self.record('END GOOD', None, op,
Dale Curtis74a314b2011-06-23 14:55:46 -0700783 optional_fields={"kernel": kernel})
jadmanski10646442008-08-13 14:05:21 +0000784
785
jadmanskie432dd22009-01-30 15:04:51 +0000786 def run_control(self, path):
787 """Execute a control file found at path (relative to the autotest
788 path). Intended for executing a control file within a control file,
789 not for running the top-level job control file."""
790 path = os.path.join(self.autodir, path)
791 control_file = self._load_control_file(path)
mbligh0d0f67d2009-11-06 03:15:03 +0000792 self.run(control=control_file, control_file_dir=self._USE_TEMP_DIR)
jadmanskie432dd22009-01-30 15:04:51 +0000793
794
jadmanskic09fc152008-10-15 17:56:59 +0000795 def add_sysinfo_command(self, command, logfile=None, on_every_test=False):
mbligh4395bbd2009-03-25 19:34:17 +0000796 self._add_sysinfo_loggable(sysinfo.command(command, logf=logfile),
jadmanskic09fc152008-10-15 17:56:59 +0000797 on_every_test)
798
799
800 def add_sysinfo_logfile(self, file, on_every_test=False):
801 self._add_sysinfo_loggable(sysinfo.logfile(file), on_every_test)
802
803
804 def _add_sysinfo_loggable(self, loggable, on_every_test):
805 if on_every_test:
806 self.sysinfo.test_loggables.add(loggable)
807 else:
808 self.sysinfo.boot_loggables.add(loggable)
809
810
jadmanski10646442008-08-13 14:05:21 +0000811 def _read_warnings(self):
jadmanskif37df842009-02-11 00:03:26 +0000812 """Poll all the warning loggers and extract any new warnings that have
813 been logged. If the warnings belong to a category that is currently
814 disabled, this method will discard them and they will no longer be
815 retrievable.
816
817 Returns a list of (timestamp, message) tuples, where timestamp is an
818 integer epoch timestamp."""
jadmanski10646442008-08-13 14:05:21 +0000819 warnings = []
820 while True:
821 # pull in a line of output from every logger that has
822 # output ready to be read
mbligh2b92b862008-11-22 13:25:32 +0000823 loggers, _, _ = select.select(self.warning_loggers, [], [], 0)
jadmanski10646442008-08-13 14:05:21 +0000824 closed_loggers = set()
825 for logger in loggers:
826 line = logger.readline()
827 # record any broken pipes (aka line == empty)
828 if len(line) == 0:
829 closed_loggers.add(logger)
830 continue
jadmanskif37df842009-02-11 00:03:26 +0000831 # parse out the warning
832 timestamp, msgtype, msg = line.split('\t', 2)
833 timestamp = int(timestamp)
834 # if the warning is valid, add it to the results
835 if self.warning_manager.is_valid(timestamp, msgtype):
836 warnings.append((timestamp, msg.strip()))
jadmanski10646442008-08-13 14:05:21 +0000837
838 # stop listening to loggers that are closed
839 self.warning_loggers -= closed_loggers
840
841 # stop if none of the loggers have any output left
842 if not loggers:
843 break
844
845 # sort into timestamp order
846 warnings.sort()
847 return warnings
848
849
showardcc929362010-01-25 21:20:41 +0000850 def _unique_subdirectory(self, base_subdirectory_name):
851 """Compute a unique results subdirectory based on the given name.
852
853 Appends base_subdirectory_name with a number as necessary to find a
854 directory name that doesn't already exist.
855 """
856 subdirectory = base_subdirectory_name
857 counter = 1
858 while os.path.exists(os.path.join(self.resultdir, subdirectory)):
859 subdirectory = base_subdirectory_name + '.' + str(counter)
860 counter += 1
861 return subdirectory
862
863
jadmanski52053632010-06-11 21:08:10 +0000864 def get_record_context(self):
865 """Returns an object representing the current job.record context.
866
867 The object returned is an opaque object with a 0-arg restore method
868 which can be called to restore the job.record context (i.e. indentation)
869 to the current level. The intention is that it should be used when
870 something external which generate job.record calls (e.g. an autotest
871 client) can fail catastrophically and the server job record state
872 needs to be reset to its original "known good" state.
873
874 @return: A context object with a 0-arg restore() method."""
875 return self._indenter.get_context()
876
877
showardcc929362010-01-25 21:20:41 +0000878 def record_summary(self, status_code, test_name, reason='', attributes=None,
879 distinguishing_attributes=(), child_test_ids=None):
880 """Record a summary test result.
881
882 @param status_code: status code string, see
883 common_lib.log.is_valid_status()
884 @param test_name: name of the test
885 @param reason: (optional) string providing detailed reason for test
886 outcome
887 @param attributes: (optional) dict of string keyvals to associate with
888 this result
889 @param distinguishing_attributes: (optional) list of attribute names
890 that should be used to distinguish identically-named test
891 results. These attributes should be present in the attributes
892 parameter. This is used to generate user-friendly subdirectory
893 names.
894 @param child_test_ids: (optional) list of test indices for test results
895 used in generating this result.
896 """
897 subdirectory_name_parts = [test_name]
898 for attribute in distinguishing_attributes:
899 assert attributes
900 assert attribute in attributes, '%s not in %s' % (attribute,
901 attributes)
902 subdirectory_name_parts.append(attributes[attribute])
903 base_subdirectory_name = '.'.join(subdirectory_name_parts)
904
905 subdirectory = self._unique_subdirectory(base_subdirectory_name)
906 subdirectory_path = os.path.join(self.resultdir, subdirectory)
907 os.mkdir(subdirectory_path)
908
909 self.record(status_code, subdirectory, test_name,
910 status=reason, optional_fields={'is_summary': True})
911
912 if attributes:
913 utils.write_keyval(subdirectory_path, attributes)
914
915 if child_test_ids:
916 ids_string = ','.join(str(test_id) for test_id in child_test_ids)
917 summary_data = {'child_test_ids': ids_string}
918 utils.write_keyval(os.path.join(subdirectory_path, 'summary_data'),
919 summary_data)
920
921
jadmanski16a7ff72009-04-01 18:19:53 +0000922 def disable_warnings(self, warning_type):
jadmanskif37df842009-02-11 00:03:26 +0000923 self.warning_manager.disable_warnings(warning_type)
jadmanski16a7ff72009-04-01 18:19:53 +0000924 self.record("INFO", None, None,
925 "disabling %s warnings" % warning_type,
926 {"warnings.disable": warning_type})
jadmanskif37df842009-02-11 00:03:26 +0000927
928
jadmanski16a7ff72009-04-01 18:19:53 +0000929 def enable_warnings(self, warning_type):
jadmanskif37df842009-02-11 00:03:26 +0000930 self.warning_manager.enable_warnings(warning_type)
jadmanski16a7ff72009-04-01 18:19:53 +0000931 self.record("INFO", None, None,
932 "enabling %s warnings" % warning_type,
933 {"warnings.enable": warning_type})
jadmanskif37df842009-02-11 00:03:26 +0000934
935
jadmanski779bd292009-03-19 17:33:33 +0000936 def get_status_log_path(self, subdir=None):
937 """Return the path to the job status log.
938
939 @param subdir - Optional paramter indicating that you want the path
940 to a subdirectory status log.
941
942 @returns The path where the status log should be.
943 """
mbligh210bae62009-04-01 18:33:13 +0000944 if self.resultdir:
945 if subdir:
946 return os.path.join(self.resultdir, subdir, "status.log")
947 else:
948 return os.path.join(self.resultdir, "status.log")
jadmanski779bd292009-03-19 17:33:33 +0000949 else:
mbligh210bae62009-04-01 18:33:13 +0000950 return None
jadmanski779bd292009-03-19 17:33:33 +0000951
952
jadmanski6bb32d72009-03-19 20:25:24 +0000953 def _update_uncollected_logs_list(self, update_func):
954 """Updates the uncollected logs list in a multi-process safe manner.
955
956 @param update_func - a function that updates the list of uncollected
957 logs. Should take one parameter, the list to be updated.
958 """
Dan Shi07e09af2013-04-12 09:31:29 -0700959 # Skip log collection if file _uncollected_log_file does not exist.
960 if not (self._uncollected_log_file and
961 os.path.exists(self._uncollected_log_file)):
962 return
mbligh0d0f67d2009-11-06 03:15:03 +0000963 if self._uncollected_log_file:
964 log_file = open(self._uncollected_log_file, "r+")
mbligha788dc42009-03-26 21:10:16 +0000965 fcntl.flock(log_file, fcntl.LOCK_EX)
jadmanski6bb32d72009-03-19 20:25:24 +0000966 try:
967 uncollected_logs = pickle.load(log_file)
968 update_func(uncollected_logs)
969 log_file.seek(0)
970 log_file.truncate()
971 pickle.dump(uncollected_logs, log_file)
jadmanski3bff9092009-04-22 18:09:47 +0000972 log_file.flush()
jadmanski6bb32d72009-03-19 20:25:24 +0000973 finally:
974 fcntl.flock(log_file, fcntl.LOCK_UN)
975 log_file.close()
976
977
978 def add_client_log(self, hostname, remote_path, local_path):
979 """Adds a new set of client logs to the list of uncollected logs,
980 to allow for future log recovery.
981
982 @param host - the hostname of the machine holding the logs
983 @param remote_path - the directory on the remote machine holding logs
984 @param local_path - the local directory to copy the logs into
985 """
986 def update_func(logs_list):
987 logs_list.append((hostname, remote_path, local_path))
988 self._update_uncollected_logs_list(update_func)
989
990
991 def remove_client_log(self, hostname, remote_path, local_path):
992 """Removes a set of client logs from the list of uncollected logs,
993 to allow for future log recovery.
994
995 @param host - the hostname of the machine holding the logs
996 @param remote_path - the directory on the remote machine holding logs
997 @param local_path - the local directory to copy the logs into
998 """
999 def update_func(logs_list):
1000 logs_list.remove((hostname, remote_path, local_path))
1001 self._update_uncollected_logs_list(update_func)
1002
1003
mbligh0d0f67d2009-11-06 03:15:03 +00001004 def get_client_logs(self):
1005 """Retrieves the list of uncollected logs, if it exists.
1006
1007 @returns A list of (host, remote_path, local_path) tuples. Returns
1008 an empty list if no uncollected logs file exists.
1009 """
1010 log_exists = (self._uncollected_log_file and
1011 os.path.exists(self._uncollected_log_file))
1012 if log_exists:
1013 return pickle.load(open(self._uncollected_log_file))
1014 else:
1015 return []
1016
1017
mbligh084bc172008-10-18 14:02:45 +00001018 def _fill_server_control_namespace(self, namespace, protect=True):
mbligh2b92b862008-11-22 13:25:32 +00001019 """
1020 Prepare a namespace to be used when executing server control files.
mbligh084bc172008-10-18 14:02:45 +00001021
1022 This sets up the control file API by importing modules and making them
1023 available under the appropriate names within namespace.
1024
1025 For use by _execute_code().
1026
1027 Args:
1028 namespace: The namespace dictionary to fill in.
1029 protect: Boolean. If True (the default) any operation that would
1030 clobber an existing entry in namespace will cause an error.
1031 Raises:
1032 error.AutoservError: When a name would be clobbered by import.
1033 """
1034 def _import_names(module_name, names=()):
mbligh2b92b862008-11-22 13:25:32 +00001035 """
1036 Import a module and assign named attributes into namespace.
mbligh084bc172008-10-18 14:02:45 +00001037
1038 Args:
1039 module_name: The string module name.
1040 names: A limiting list of names to import from module_name. If
1041 empty (the default), all names are imported from the module
1042 similar to a "from foo.bar import *" statement.
1043 Raises:
1044 error.AutoservError: When a name being imported would clobber
1045 a name already in namespace.
1046 """
1047 module = __import__(module_name, {}, {}, names)
1048
1049 # No names supplied? Import * from the lowest level module.
1050 # (Ugh, why do I have to implement this part myself?)
1051 if not names:
1052 for submodule_name in module_name.split('.')[1:]:
1053 module = getattr(module, submodule_name)
1054 if hasattr(module, '__all__'):
1055 names = getattr(module, '__all__')
1056 else:
1057 names = dir(module)
1058
1059 # Install each name into namespace, checking to make sure it
1060 # doesn't override anything that already exists.
1061 for name in names:
1062 # Check for conflicts to help prevent future problems.
1063 if name in namespace and protect:
1064 if namespace[name] is not getattr(module, name):
1065 raise error.AutoservError('importing name '
1066 '%s from %s %r would override %r' %
1067 (name, module_name, getattr(module, name),
1068 namespace[name]))
1069 else:
1070 # Encourage cleanliness and the use of __all__ for a
1071 # more concrete API with less surprises on '*' imports.
1072 warnings.warn('%s (%r) being imported from %s for use '
1073 'in server control files is not the '
1074 'first occurrance of that import.' %
1075 (name, namespace[name], module_name))
1076
1077 namespace[name] = getattr(module, name)
1078
1079
1080 # This is the equivalent of prepending a bunch of import statements to
1081 # the front of the control script.
mbligha2b07dd2009-06-22 18:26:13 +00001082 namespace.update(os=os, sys=sys, logging=logging)
mbligh084bc172008-10-18 14:02:45 +00001083 _import_names('autotest_lib.server',
1084 ('hosts', 'autotest', 'kvm', 'git', 'standalone_profiler',
1085 'source_kernel', 'rpm_kernel', 'deb_kernel', 'git_kernel'))
1086 _import_names('autotest_lib.server.subcommand',
1087 ('parallel', 'parallel_simple', 'subcommand'))
1088 _import_names('autotest_lib.server.utils',
1089 ('run', 'get_tmp_dir', 'sh_escape', 'parse_machine'))
1090 _import_names('autotest_lib.client.common_lib.error')
1091 _import_names('autotest_lib.client.common_lib.barrier', ('barrier',))
1092
1093 # Inject ourself as the job object into other classes within the API.
1094 # (Yuck, this injection is a gross thing be part of a public API. -gps)
1095 #
1096 # XXX Base & SiteAutotest do not appear to use .job. Who does?
1097 namespace['autotest'].Autotest.job = self
1098 # server.hosts.base_classes.Host uses .job.
1099 namespace['hosts'].Host.job = self
Eric Li10222b82010-11-24 09:33:15 -08001100 namespace['hosts'].factory.ssh_user = self._ssh_user
1101 namespace['hosts'].factory.ssh_port = self._ssh_port
1102 namespace['hosts'].factory.ssh_pass = self._ssh_pass
Fang Dengd1c2b732013-08-20 12:59:46 -07001103 namespace['hosts'].factory.ssh_verbosity_flag = (
1104 self._ssh_verbosity_flag)
Aviv Keshetc5947fa2013-09-04 14:06:29 -07001105 namespace['hosts'].factory.ssh_options = self._ssh_options
mbligh084bc172008-10-18 14:02:45 +00001106
1107
1108 def _execute_code(self, code_file, namespace, protect=True):
mbligh2b92b862008-11-22 13:25:32 +00001109 """
1110 Execute code using a copy of namespace as a server control script.
mbligh084bc172008-10-18 14:02:45 +00001111
1112 Unless protect_namespace is explicitly set to False, the dict will not
1113 be modified.
1114
1115 Args:
1116 code_file: The filename of the control file to execute.
1117 namespace: A dict containing names to make available during execution.
1118 protect: Boolean. If True (the default) a copy of the namespace dict
1119 is used during execution to prevent the code from modifying its
1120 contents outside of this function. If False the raw dict is
1121 passed in and modifications will be allowed.
1122 """
1123 if protect:
1124 namespace = namespace.copy()
1125 self._fill_server_control_namespace(namespace, protect=protect)
1126 # TODO: Simplify and get rid of the special cases for only 1 machine.
showard3e66e8c2008-10-27 19:20:51 +00001127 if len(self.machines) > 1:
mbligh084bc172008-10-18 14:02:45 +00001128 machines_text = '\n'.join(self.machines) + '\n'
1129 # Only rewrite the file if it does not match our machine list.
1130 try:
1131 machines_f = open(MACHINES_FILENAME, 'r')
1132 existing_machines_text = machines_f.read()
1133 machines_f.close()
1134 except EnvironmentError:
1135 existing_machines_text = None
1136 if machines_text != existing_machines_text:
1137 utils.open_write_close(MACHINES_FILENAME, machines_text)
1138 execfile(code_file, namespace, namespace)
jadmanski10646442008-08-13 14:05:21 +00001139
1140
jadmanskie29d0e42010-06-17 16:06:52 +00001141 def _parse_status(self, new_line):
mbligh0d0f67d2009-11-06 03:15:03 +00001142 if not self._using_parser:
jadmanski10646442008-08-13 14:05:21 +00001143 return
jadmanskie29d0e42010-06-17 16:06:52 +00001144 new_tests = self.parser.process_lines([new_line])
jadmanski10646442008-08-13 14:05:21 +00001145 for test in new_tests:
1146 self.__insert_test(test)
1147
1148
1149 def __insert_test(self, test):
mbligh2b92b862008-11-22 13:25:32 +00001150 """
1151 An internal method to insert a new test result into the
jadmanski10646442008-08-13 14:05:21 +00001152 database. This method will not raise an exception, even if an
1153 error occurs during the insert, to avoid failing a test
1154 simply because of unexpected database issues."""
showard21baa452008-10-21 00:08:39 +00001155 self.num_tests_run += 1
1156 if status_lib.is_worse_than_or_equal_to(test.status, 'FAIL'):
1157 self.num_tests_failed += 1
jadmanski10646442008-08-13 14:05:21 +00001158 try:
1159 self.results_db.insert_test(self.job_model, test)
1160 except Exception:
1161 msg = ("WARNING: An unexpected error occured while "
1162 "inserting test results into the database. "
1163 "Ignoring error.\n" + traceback.format_exc())
1164 print >> sys.stderr, msg
1165
mblighcaa62c22008-04-07 21:51:17 +00001166
mblighfc3da5b2010-01-06 18:37:22 +00001167 def preprocess_client_state(self):
1168 """
1169 Produce a state file for initializing the state of a client job.
1170
1171 Creates a new client state file with all the current server state, as
1172 well as some pre-set client state.
1173
1174 @returns The path of the file the state was written into.
1175 """
1176 # initialize the sysinfo state
1177 self._state.set('client', 'sysinfo', self.sysinfo.serialize())
1178
1179 # dump the state out to a tempfile
1180 fd, file_path = tempfile.mkstemp(dir=self.tmpdir)
1181 os.close(fd)
mbligha2c99492010-01-27 22:59:50 +00001182
1183 # write_to_file doesn't need locking, we exclusively own file_path
mblighfc3da5b2010-01-06 18:37:22 +00001184 self._state.write_to_file(file_path)
1185 return file_path
1186
1187
1188 def postprocess_client_state(self, state_path):
1189 """
1190 Update the state of this job with the state from a client job.
1191
1192 Updates the state of the server side of a job with the final state
1193 of a client job that was run. Updates the non-client-specific state,
1194 pulls in some specific bits from the client-specific state, and then
1195 discards the rest. Removes the state file afterwards
1196
1197 @param state_file A path to the state file from the client.
1198 """
1199 # update the on-disk state
mblighfc3da5b2010-01-06 18:37:22 +00001200 try:
jadmanskib6e7bdb2010-04-13 16:00:39 +00001201 self._state.read_from_file(state_path)
mblighfc3da5b2010-01-06 18:37:22 +00001202 os.remove(state_path)
mbligha2c99492010-01-27 22:59:50 +00001203 except OSError, e:
mblighfc3da5b2010-01-06 18:37:22 +00001204 # ignore file-not-found errors
1205 if e.errno != errno.ENOENT:
1206 raise
jadmanskib6e7bdb2010-04-13 16:00:39 +00001207 else:
1208 logging.debug('Client state file %s not found', state_path)
mblighfc3da5b2010-01-06 18:37:22 +00001209
1210 # update the sysinfo state
1211 if self._state.has('client', 'sysinfo'):
1212 self.sysinfo.deserialize(self._state.get('client', 'sysinfo'))
1213
1214 # drop all the client-specific state
1215 self._state.discard_namespace('client')
1216
1217
mbligh0a883702010-04-21 01:58:34 +00001218 def clear_all_known_hosts(self):
1219 """Clears known hosts files for all AbstractSSHHosts."""
1220 for host in self.hosts:
1221 if isinstance(host, abstract_ssh.AbstractSSHHost):
1222 host.clear_known_hosts()
1223
1224
jadmanskif37df842009-02-11 00:03:26 +00001225class warning_manager(object):
1226 """Class for controlling warning logs. Manages the enabling and disabling
1227 of warnings."""
1228 def __init__(self):
1229 # a map of warning types to a list of disabled time intervals
1230 self.disabled_warnings = {}
1231
1232
1233 def is_valid(self, timestamp, warning_type):
1234 """Indicates if a warning (based on the time it occured and its type)
1235 is a valid warning. A warning is considered "invalid" if this type of
1236 warning was marked as "disabled" at the time the warning occured."""
1237 disabled_intervals = self.disabled_warnings.get(warning_type, [])
1238 for start, end in disabled_intervals:
1239 if timestamp >= start and (end is None or timestamp < end):
1240 return False
1241 return True
1242
1243
1244 def disable_warnings(self, warning_type, current_time_func=time.time):
1245 """As of now, disables all further warnings of this type."""
1246 intervals = self.disabled_warnings.setdefault(warning_type, [])
1247 if not intervals or intervals[-1][1] is not None:
jadmanski16a7ff72009-04-01 18:19:53 +00001248 intervals.append((int(current_time_func()), None))
jadmanskif37df842009-02-11 00:03:26 +00001249
1250
1251 def enable_warnings(self, warning_type, current_time_func=time.time):
1252 """As of now, enables all further warnings of this type."""
1253 intervals = self.disabled_warnings.get(warning_type, [])
1254 if intervals and intervals[-1][1] is None:
jadmanski16a7ff72009-04-01 18:19:53 +00001255 intervals[-1] = (intervals[-1][0], int(current_time_func()))
Paul Pendlebury57593562011-06-15 10:45:49 -07001256
1257
1258# load up site-specific code for generating site-specific job data
1259get_site_job_data = utils.import_site_function(__file__,
1260 "autotest_lib.server.site_server_job", "get_site_job_data",
1261 _get_site_job_data_dummy)
1262
1263
1264site_server_job = utils.import_site_class(
1265 __file__, "autotest_lib.server.site_server_job", "site_server_job",
1266 base_server_job)
1267
1268
1269class server_job(site_server_job):
Dale Curtis456d3c12011-07-19 11:42:51 -07001270 pass