blob: 17c6790e4711fddbb52a96dfe24985e75ba22e08 [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
mbligh0d0f67d2009-11-06 03:15:03 +0000135
136 num_tests_run
137 num_tests_failed
138
139 warning_manager
140 warning_loggers
jadmanski10646442008-08-13 14:05:21 +0000141 """
142
mbligh0d0f67d2009-11-06 03:15:03 +0000143 _STATUS_VERSION = 1
jadmanski10646442008-08-13 14:05:21 +0000144
Aviv Keshetc5947fa2013-09-04 14:06:29 -0700145 # TODO crbug.com/285395 eliminate ssh_verbosity_flag
jadmanski10646442008-08-13 14:05:21 +0000146 def __init__(self, control, args, resultdir, label, user, machines,
147 client=False, parse_job='',
beepsd0672682013-09-16 17:32:16 -0700148 ssh_user=host_factory.DEFAULT_SSH_USER,
149 ssh_port=host_factory.DEFAULT_SSH_PORT,
150 ssh_pass=host_factory.DEFAULT_SSH_PASS,
151 ssh_verbosity_flag=host_factory.DEFAULT_SSH_VERBOSITY,
152 ssh_options=host_factory.DEFAULT_SSH_OPTIONS,
Aviv Keshetc5947fa2013-09-04 14:06:29 -0700153 test_retry=0, group_name='',
Fang Dengd1c2b732013-08-20 12:59:46 -0700154 tag='', disable_sysinfo=False,
Dan Shi70647ca2015-07-16 22:52:35 -0700155 control_filename=SERVER_CONTROL_FILENAME,
156 parent_job_id=None):
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.
Dan Shi70647ca2015-07-16 22:52:35 -0700184 @param parent_job_id: Job ID of the parent job. Default to None if the
185 job does not have a parent job.
jadmanski10646442008-08-13 14:05:21 +0000186 """
Scott Zawalski91493c82013-01-25 16:15:20 -0500187 super(base_server_job, self).__init__(resultdir=resultdir,
188 test_retry=test_retry)
mbligh0d0f67d2009-11-06 03:15:03 +0000189 path = os.path.dirname(__file__)
Scott Zawalski91493c82013-01-25 16:15:20 -0500190 self.test_retry = test_retry
mbligh0d0f67d2009-11-06 03:15:03 +0000191 self.control = control
192 self._uncollected_log_file = os.path.join(self.resultdir,
193 'uncollected_logs')
194 debugdir = os.path.join(self.resultdir, 'debug')
195 if not os.path.exists(debugdir):
196 os.mkdir(debugdir)
197
198 if user:
199 self.user = user
200 else:
201 self.user = getpass.getuser()
202
jadmanski808f4b12010-04-09 22:30:31 +0000203 self.args = args
Peter Mayo7a875762012-06-13 14:38:15 -0400204 self.label = label
jadmanski10646442008-08-13 14:05:21 +0000205 self.machines = machines
mbligh0d0f67d2009-11-06 03:15:03 +0000206 self._client = client
jadmanski10646442008-08-13 14:05:21 +0000207 self.warning_loggers = set()
jadmanskif37df842009-02-11 00:03:26 +0000208 self.warning_manager = warning_manager()
mbligh0d0f67d2009-11-06 03:15:03 +0000209 self._ssh_user = ssh_user
210 self._ssh_port = ssh_port
211 self._ssh_pass = ssh_pass
Fang Dengd1c2b732013-08-20 12:59:46 -0700212 self._ssh_verbosity_flag = ssh_verbosity_flag
Aviv Keshetc5947fa2013-09-04 14:06:29 -0700213 self._ssh_options = ssh_options
mblighe7d9c602009-07-02 19:02:33 +0000214 self.tag = tag
mbligh09108442008-10-15 16:27:38 +0000215 self.last_boot_tag = None
jadmanski53aaf382008-11-17 16:22:31 +0000216 self.hosts = set()
mbligh0d0f67d2009-11-06 03:15:03 +0000217 self.drop_caches = False
mblighb5dac432008-11-27 00:38:44 +0000218 self.drop_caches_between_iterations = False
mblighe0cbc912010-03-11 18:03:07 +0000219 self._control_filename = control_filename
Christopher Wiley8a91f232013-07-09 11:02:27 -0700220 self._disable_sysinfo = disable_sysinfo
jadmanski10646442008-08-13 14:05:21 +0000221
showard75cdfee2009-06-10 17:40:41 +0000222 self.logging = logging_manager.get_logging_manager(
223 manage_stdout_and_stderr=True, redirect_fds=True)
224 subcommand.logging_manager_object = self.logging
jadmanski10646442008-08-13 14:05:21 +0000225
mbligh0d0f67d2009-11-06 03:15:03 +0000226 self.sysinfo = sysinfo.sysinfo(self.resultdir)
jadmanski043e1132008-11-19 17:10:32 +0000227 self.profilers = profilers.profilers(self)
jadmanskic09fc152008-10-15 17:56:59 +0000228
jadmanski10646442008-08-13 14:05:21 +0000229 job_data = {'label' : label, 'user' : user,
230 'hostname' : ','.join(machines),
Eric Li861b2d52011-02-04 14:50:35 -0800231 'drone' : platform.node(),
mbligh0d0f67d2009-11-06 03:15:03 +0000232 'status_version' : str(self._STATUS_VERSION),
showard170873e2009-01-07 00:22:26 +0000233 'job_started' : str(int(time.time()))}
Dan Shi70647ca2015-07-16 22:52:35 -0700234 # Save parent job id to keyvals, so parser can retrieve the info and
235 # write to tko_jobs record.
236 if parent_job_id:
237 job_data['parent_job_id'] = parent_job_id
mbligh374f3412009-05-13 21:29:45 +0000238 if group_name:
239 job_data['host_group_name'] = group_name
jadmanski10646442008-08-13 14:05:21 +0000240
mbligh0d0f67d2009-11-06 03:15:03 +0000241 # only write these keyvals out on the first job in a resultdir
242 if 'job_started' not in utils.read_keyval(self.resultdir):
243 job_data.update(get_site_job_data(self))
244 utils.write_keyval(self.resultdir, job_data)
245
246 self._parse_job = parse_job
Alex Miller44ae9232014-06-20 17:24:25 -0700247 self._using_parser = (INCREMENTAL_TKO_PARSING and self._parse_job
248 and len(machines) <= 1)
mbligh0d0f67d2009-11-06 03:15:03 +0000249 self.pkgmgr = packages.PackageManager(
250 self.autodir, run_function_dargs={'timeout':600})
showard21baa452008-10-21 00:08:39 +0000251 self.num_tests_run = 0
252 self.num_tests_failed = 0
253
jadmanski550fdc22008-11-20 16:32:08 +0000254 self._register_subcommand_hooks()
255
mbligh0d0f67d2009-11-06 03:15:03 +0000256 # these components aren't usable on the server
257 self.bootloader = None
258 self.harness = None
259
jadmanski2a89dac2010-06-11 14:32:58 +0000260 # set up the status logger
jadmanski52053632010-06-11 21:08:10 +0000261 self._indenter = status_indenter()
jadmanski2a89dac2010-06-11 14:32:58 +0000262 self._logger = base_job.status_logger(
jadmanski52053632010-06-11 21:08:10 +0000263 self, self._indenter, 'status.log', 'status.log',
jadmanski2a89dac2010-06-11 14:32:58 +0000264 record_hook=server_job_record_hook(self))
265
Dan Shib03ea9d2013-08-15 17:13:27 -0700266 # Initialize a flag to indicate DUT failure during the test, e.g.,
267 # unexpected reboot.
268 self.failed_with_device_error = False
269
Dan Shi70647ca2015-07-16 22:52:35 -0700270 self.parent_job_id = parent_job_id
271
mbligh0d0f67d2009-11-06 03:15:03 +0000272
273 @classmethod
274 def _find_base_directories(cls):
275 """
276 Determine locations of autodir, clientdir and serverdir. Assumes
277 that this file is located within serverdir and uses __file__ along
278 with relative paths to resolve the location.
279 """
280 serverdir = os.path.abspath(os.path.dirname(__file__))
281 autodir = os.path.normpath(os.path.join(serverdir, '..'))
282 clientdir = os.path.join(autodir, 'client')
283 return autodir, clientdir, serverdir
284
285
Scott Zawalski91493c82013-01-25 16:15:20 -0500286 def _find_resultdir(self, resultdir, *args, **dargs):
mbligh0d0f67d2009-11-06 03:15:03 +0000287 """
288 Determine the location of resultdir. For server jobs we expect one to
289 always be explicitly passed in to __init__, so just return that.
290 """
291 if resultdir:
292 return os.path.normpath(resultdir)
293 else:
294 return None
295
jadmanski550fdc22008-11-20 16:32:08 +0000296
jadmanski2a89dac2010-06-11 14:32:58 +0000297 def _get_status_logger(self):
298 """Return a reference to the status logger."""
299 return self._logger
300
301
jadmanskie432dd22009-01-30 15:04:51 +0000302 @staticmethod
303 def _load_control_file(path):
304 f = open(path)
305 try:
306 control_file = f.read()
307 finally:
308 f.close()
309 return re.sub('\r', '', control_file)
310
311
jadmanski550fdc22008-11-20 16:32:08 +0000312 def _register_subcommand_hooks(self):
mbligh2b92b862008-11-22 13:25:32 +0000313 """
314 Register some hooks into the subcommand modules that allow us
315 to properly clean up self.hosts created in forked subprocesses.
316 """
jadmanski550fdc22008-11-20 16:32:08 +0000317 def on_fork(cmd):
318 self._existing_hosts_on_fork = set(self.hosts)
319 def on_join(cmd):
320 new_hosts = self.hosts - self._existing_hosts_on_fork
321 for host in new_hosts:
322 host.close()
323 subcommand.subcommand.register_fork_hook(on_fork)
324 subcommand.subcommand.register_join_hook(on_join)
325
jadmanski10646442008-08-13 14:05:21 +0000326
mbligh4608b002010-01-05 18:22:35 +0000327 def init_parser(self):
mbligh2b92b862008-11-22 13:25:32 +0000328 """
mbligh4608b002010-01-05 18:22:35 +0000329 Start the continuous parsing of self.resultdir. This sets up
jadmanski10646442008-08-13 14:05:21 +0000330 the database connection and inserts the basic job object into
mbligh2b92b862008-11-22 13:25:32 +0000331 the database if necessary.
332 """
mbligh4608b002010-01-05 18:22:35 +0000333 if not self._using_parser:
334 return
jadmanski10646442008-08-13 14:05:21 +0000335 # redirect parser debugging to .parse.log
mbligh4608b002010-01-05 18:22:35 +0000336 parse_log = os.path.join(self.resultdir, '.parse.log')
jadmanski10646442008-08-13 14:05:21 +0000337 parse_log = open(parse_log, 'w', 0)
338 tko_utils.redirect_parser_debugging(parse_log)
339 # create a job model object and set up the db
340 self.results_db = tko_db.db(autocommit=True)
mbligh0d0f67d2009-11-06 03:15:03 +0000341 self.parser = status_lib.parser(self._STATUS_VERSION)
mbligh4608b002010-01-05 18:22:35 +0000342 self.job_model = self.parser.make_job(self.resultdir)
jadmanski10646442008-08-13 14:05:21 +0000343 self.parser.start(self.job_model)
344 # check if a job already exists in the db and insert it if
345 # it does not
mbligh0d0f67d2009-11-06 03:15:03 +0000346 job_idx = self.results_db.find_job(self._parse_job)
jadmanski10646442008-08-13 14:05:21 +0000347 if job_idx is None:
Dan Shi70647ca2015-07-16 22:52:35 -0700348 self.results_db.insert_job(self._parse_job, self.job_model,
349 self.parent_job_id)
jadmanski10646442008-08-13 14:05:21 +0000350 else:
mbligh2b92b862008-11-22 13:25:32 +0000351 machine_idx = self.results_db.lookup_machine(self.job_model.machine)
jadmanski10646442008-08-13 14:05:21 +0000352 self.job_model.index = job_idx
353 self.job_model.machine_idx = machine_idx
354
355
356 def cleanup_parser(self):
mbligh2b92b862008-11-22 13:25:32 +0000357 """
358 This should be called after the server job is finished
jadmanski10646442008-08-13 14:05:21 +0000359 to carry out any remaining cleanup (e.g. flushing any
mbligh2b92b862008-11-22 13:25:32 +0000360 remaining test results to the results db)
361 """
mbligh0d0f67d2009-11-06 03:15:03 +0000362 if not self._using_parser:
jadmanski10646442008-08-13 14:05:21 +0000363 return
364 final_tests = self.parser.end()
365 for test in final_tests:
366 self.__insert_test(test)
mbligh0d0f67d2009-11-06 03:15:03 +0000367 self._using_parser = False
jadmanski10646442008-08-13 14:05:21 +0000368
Aviv Keshetc5947fa2013-09-04 14:06:29 -0700369 # TODO crbug.com/285395 add a kwargs parameter.
370 def _make_namespace(self):
371 """Create a namespace dictionary to be passed along to control file.
372
373 Creates a namespace argument populated with standard values:
374 machines, job, ssh_user, ssh_port, ssh_pass, ssh_verbosity_flag,
375 and ssh_options.
376 """
377 namespace = {'machines' : self.machines,
378 'job' : self,
379 'ssh_user' : self._ssh_user,
380 'ssh_port' : self._ssh_port,
381 'ssh_pass' : self._ssh_pass,
382 'ssh_verbosity_flag' : self._ssh_verbosity_flag,
383 'ssh_options' : self._ssh_options}
384 return namespace
385
jadmanski10646442008-08-13 14:05:21 +0000386
J. Richard Barnettec2d99cf2015-11-18 12:46:15 -0800387 def cleanup(self, labels):
Fang Dengad78aca2014-10-02 18:15:46 -0700388 """Cleanup machines.
389
390 @param labels: Comma separated job labels, will be used to
391 determine special task actions.
392 """
393 if not self.machines:
394 raise error.AutoservError('No machines specified to cleanup')
395 if self.resultdir:
396 os.chdir(self.resultdir)
397
398 namespace = self._make_namespace()
399 namespace.update({'job_labels': labels, 'args': ''})
400 self._execute_code(CLEANUP_CONTROL_FILE, namespace, protect=False)
401
402
J. Richard Barnettec2d99cf2015-11-18 12:46:15 -0800403 def verify(self, labels):
Fang Dengad78aca2014-10-02 18:15:46 -0700404 """Verify machines are all ssh-able.
405
406 @param labels: Comma separated job labels, will be used to
407 determine special task actions.
408 """
jadmanski10646442008-08-13 14:05:21 +0000409 if not self.machines:
mbligh084bc172008-10-18 14:02:45 +0000410 raise error.AutoservError('No machines specified to verify')
mbligh0fce4112008-11-27 00:37:17 +0000411 if self.resultdir:
412 os.chdir(self.resultdir)
Fang Dengad78aca2014-10-02 18:15:46 -0700413
414 namespace = self._make_namespace()
415 namespace.update({'job_labels': labels, 'args': ''})
416 self._execute_code(VERIFY_CONTROL_FILE, namespace, protect=False)
jadmanski10646442008-08-13 14:05:21 +0000417
418
J. Richard Barnettec2d99cf2015-11-18 12:46:15 -0800419 def reset(self, labels):
Fang Dengad78aca2014-10-02 18:15:46 -0700420 """Reset machines by first cleanup then verify each machine.
421
422 @param labels: Comma separated job labels, will be used to
423 determine special task actions.
424 """
Dan Shi07e09af2013-04-12 09:31:29 -0700425 if not self.machines:
426 raise error.AutoservError('No machines specified to reset.')
427 if self.resultdir:
428 os.chdir(self.resultdir)
429
Fang Dengad78aca2014-10-02 18:15:46 -0700430 namespace = self._make_namespace()
431 namespace.update({'job_labels': labels, 'args': ''})
432 self._execute_code(RESET_CONTROL_FILE, namespace, protect=False)
Dan Shi07e09af2013-04-12 09:31:29 -0700433
434
J. Richard Barnettec2d99cf2015-11-18 12:46:15 -0800435 def repair(self, labels):
Fang Dengad78aca2014-10-02 18:15:46 -0700436 """Repair machines.
437
Fang Dengad78aca2014-10-02 18:15:46 -0700438 @param labels: Comma separated job labels, will be used to
439 determine special task actions.
440 """
jadmanski10646442008-08-13 14:05:21 +0000441 if not self.machines:
442 raise error.AutoservError('No machines specified to repair')
mbligh0fce4112008-11-27 00:37:17 +0000443 if self.resultdir:
444 os.chdir(self.resultdir)
Aviv Keshetc5947fa2013-09-04 14:06:29 -0700445
446 namespace = self._make_namespace()
J. Richard Barnettec2d99cf2015-11-18 12:46:15 -0800447 namespace.update({'job_labels': labels, 'args': ''})
mbligh0931b0a2009-04-08 17:44:48 +0000448 self._execute_code(REPAIR_CONTROL_FILE, namespace, protect=False)
jadmanski10646442008-08-13 14:05:21 +0000449
450
Alex Millercb79ba72013-05-29 14:43:00 -0700451 def provision(self, labels):
452 """
453 Provision all hosts to match |labels|.
454
455 @param labels: A comma seperated string of labels to provision the
456 host to.
457
458 """
Alex Millercb79ba72013-05-29 14:43:00 -0700459 control = self._load_control_file(PROVISION_CONTROL_FILE)
Alex Miller686fca82014-04-23 17:21:13 -0700460 self.run(control=control, job_labels=labels)
Alex Millercb79ba72013-05-29 14:43:00 -0700461
462
jadmanski10646442008-08-13 14:05:21 +0000463 def precheck(self):
464 """
465 perform any additional checks in derived classes.
466 """
467 pass
468
469
470 def enable_external_logging(self):
mbligh2b92b862008-11-22 13:25:32 +0000471 """
472 Start or restart external logging mechanism.
jadmanski10646442008-08-13 14:05:21 +0000473 """
474 pass
475
476
477 def disable_external_logging(self):
mbligh2b92b862008-11-22 13:25:32 +0000478 """
479 Pause or stop external logging mechanism.
jadmanski10646442008-08-13 14:05:21 +0000480 """
481 pass
482
483
484 def use_external_logging(self):
mbligh2b92b862008-11-22 13:25:32 +0000485 """
486 Return True if external logging should be used.
jadmanski10646442008-08-13 14:05:21 +0000487 """
488 return False
489
490
mbligh415dc212009-06-15 21:53:34 +0000491 def _make_parallel_wrapper(self, function, machines, log):
492 """Wrap function as appropriate for calling by parallel_simple."""
mbligh2b92b862008-11-22 13:25:32 +0000493 is_forking = not (len(machines) == 1 and self.machines == machines)
mbligh0d0f67d2009-11-06 03:15:03 +0000494 if self._parse_job and is_forking and log:
jadmanski10646442008-08-13 14:05:21 +0000495 def wrapper(machine):
mbligh0d0f67d2009-11-06 03:15:03 +0000496 self._parse_job += "/" + machine
Alex Miller44ae9232014-06-20 17:24:25 -0700497 self._using_parser = INCREMENTAL_TKO_PARSING
jadmanski10646442008-08-13 14:05:21 +0000498 self.machines = [machine]
mbligh0d0f67d2009-11-06 03:15:03 +0000499 self.push_execution_context(machine)
jadmanski609a5f42008-08-26 20:52:42 +0000500 os.chdir(self.resultdir)
showard2bab8f42008-11-12 18:15:22 +0000501 utils.write_keyval(self.resultdir, {"hostname": machine})
mbligh4608b002010-01-05 18:22:35 +0000502 self.init_parser()
jadmanski10646442008-08-13 14:05:21 +0000503 result = function(machine)
504 self.cleanup_parser()
505 return result
jadmanski4dd1a002008-09-05 20:27:30 +0000506 elif len(machines) > 1 and log:
jadmanski10646442008-08-13 14:05:21 +0000507 def wrapper(machine):
mbligh0d0f67d2009-11-06 03:15:03 +0000508 self.push_execution_context(machine)
jadmanski609a5f42008-08-26 20:52:42 +0000509 os.chdir(self.resultdir)
mbligh838d82d2009-03-11 17:14:31 +0000510 machine_data = {'hostname' : machine,
mbligh0d0f67d2009-11-06 03:15:03 +0000511 'status_version' : str(self._STATUS_VERSION)}
mbligh838d82d2009-03-11 17:14:31 +0000512 utils.write_keyval(self.resultdir, machine_data)
jadmanski10646442008-08-13 14:05:21 +0000513 result = function(machine)
514 return result
515 else:
516 wrapper = function
mbligh415dc212009-06-15 21:53:34 +0000517 return wrapper
518
519
520 def parallel_simple(self, function, machines, log=True, timeout=None,
521 return_results=False):
522 """
523 Run 'function' using parallel_simple, with an extra wrapper to handle
524 the necessary setup for continuous parsing, if possible. If continuous
525 parsing is already properly initialized then this should just work.
526
527 @param function: A callable to run in parallel given each machine.
528 @param machines: A list of machine names to be passed one per subcommand
529 invocation of function.
530 @param log: If True, output will be written to output in a subdirectory
531 named after each machine.
532 @param timeout: Seconds after which the function call should timeout.
533 @param return_results: If True instead of an AutoServError being raised
534 on any error a list of the results|exceptions from the function
535 called on each arg is returned. [default: False]
536
537 @raises error.AutotestError: If any of the functions failed.
538 """
539 wrapper = self._make_parallel_wrapper(function, machines, log)
540 return subcommand.parallel_simple(wrapper, machines,
541 log=log, timeout=timeout,
542 return_results=return_results)
543
544
545 def parallel_on_machines(self, function, machines, timeout=None):
546 """
showardcd5fac42009-07-06 20:19:43 +0000547 @param function: Called in parallel with one machine as its argument.
mbligh415dc212009-06-15 21:53:34 +0000548 @param machines: A list of machines to call function(machine) on.
549 @param timeout: Seconds after which the function call should timeout.
550
551 @returns A list of machines on which function(machine) returned
552 without raising an exception.
553 """
showardcd5fac42009-07-06 20:19:43 +0000554 results = self.parallel_simple(function, machines, timeout=timeout,
mbligh415dc212009-06-15 21:53:34 +0000555 return_results=True)
556 success_machines = []
557 for result, machine in itertools.izip(results, machines):
558 if not isinstance(result, Exception):
559 success_machines.append(machine)
560 return success_machines
jadmanski10646442008-08-13 14:05:21 +0000561
562
mbligh0d0f67d2009-11-06 03:15:03 +0000563 _USE_TEMP_DIR = object()
Fang Dengad78aca2014-10-02 18:15:46 -0700564 def run(self, install_before=False, install_after=False,
jadmanskie432dd22009-01-30 15:04:51 +0000565 collect_crashdumps=True, namespace={}, control=None,
beepscb6f1e22013-06-28 19:14:10 -0700566 control_file_dir=None, verify_job_repo_url=False,
Alex Millerca76bcc2014-04-18 18:47:28 -0700567 only_collect_crashinfo=False, skip_crash_collection=False,
Dan Shib669cbd2013-09-13 11:17:17 -0700568 job_labels='', use_packaging=True):
jadmanskifb9c0fa2009-04-29 17:39:16 +0000569 # for a normal job, make sure the uncollected logs file exists
570 # for a crashinfo-only run it should already exist, bail out otherwise
jadmanski648c39f2010-03-19 17:38:01 +0000571 created_uncollected_logs = False
Alex Miller45554f32013-08-13 16:48:29 -0700572 logging.info("I am PID %s", os.getpid())
mbligh0d0f67d2009-11-06 03:15:03 +0000573 if self.resultdir and not os.path.exists(self._uncollected_log_file):
jadmanskifb9c0fa2009-04-29 17:39:16 +0000574 if only_collect_crashinfo:
575 # if this is a crashinfo-only run, and there were no existing
576 # uncollected logs, just bail out early
577 logging.info("No existing uncollected logs, "
578 "skipping crashinfo collection")
579 return
580 else:
mbligh0d0f67d2009-11-06 03:15:03 +0000581 log_file = open(self._uncollected_log_file, "w")
jadmanskifb9c0fa2009-04-29 17:39:16 +0000582 pickle.dump([], log_file)
583 log_file.close()
jadmanski648c39f2010-03-19 17:38:01 +0000584 created_uncollected_logs = True
jadmanskifb9c0fa2009-04-29 17:39:16 +0000585
jadmanski10646442008-08-13 14:05:21 +0000586 # use a copy so changes don't affect the original dictionary
587 namespace = namespace.copy()
588 machines = self.machines
jadmanskie432dd22009-01-30 15:04:51 +0000589 if control is None:
jadmanski02a3ba22009-11-13 20:47:27 +0000590 if self.control is None:
591 control = ''
592 else:
593 control = self._load_control_file(self.control)
jadmanskie432dd22009-01-30 15:04:51 +0000594 if control_file_dir is None:
595 control_file_dir = self.resultdir
jadmanski10646442008-08-13 14:05:21 +0000596
597 self.aborted = False
Aviv Keshetc5947fa2013-09-04 14:06:29 -0700598 namespace.update(self._make_namespace())
Alex Millerca76bcc2014-04-18 18:47:28 -0700599 namespace.update({'args' : self.args,
600 'job_labels' : job_labels})
jadmanski10646442008-08-13 14:05:21 +0000601 test_start_time = int(time.time())
602
mbligh80e1eba2008-11-19 00:26:18 +0000603 if self.resultdir:
604 os.chdir(self.resultdir)
jadmanski779bd292009-03-19 17:33:33 +0000605 # touch status.log so that the parser knows a job is running here
jadmanski382303a2009-04-21 19:53:39 +0000606 open(self.get_status_log_path(), 'a').close()
mbligh80e1eba2008-11-19 00:26:18 +0000607 self.enable_external_logging()
jadmanskie432dd22009-01-30 15:04:51 +0000608
jadmanskicdd0c402008-09-19 21:21:31 +0000609 collect_crashinfo = True
mblighaebe3b62008-12-22 14:45:40 +0000610 temp_control_file_dir = None
jadmanski10646442008-08-13 14:05:21 +0000611 try:
showardcf8d4922009-10-14 16:08:39 +0000612 try:
613 if install_before and machines:
614 self._execute_code(INSTALL_CONTROL_FILE, namespace)
jadmanskie432dd22009-01-30 15:04:51 +0000615
showardcf8d4922009-10-14 16:08:39 +0000616 if only_collect_crashinfo:
617 return
618
beepscb6f1e22013-06-28 19:14:10 -0700619 # If the verify_job_repo_url option is set but we're unable
620 # to actually verify that the job_repo_url contains the autotest
621 # package, this job will fail.
622 if verify_job_repo_url:
623 self._execute_code(VERIFY_JOB_REPO_URL_CONTROL_FILE,
Dan Shicf4d2032015-03-12 15:04:21 -0700624 namespace)
beepscb6f1e22013-06-28 19:14:10 -0700625 else:
626 logging.warning('Not checking if job_repo_url contains '
627 'autotest packages on %s', machines)
628
jadmanskidef0c3c2009-03-25 20:07:10 +0000629 # determine the dir to write the control files to
630 cfd_specified = (control_file_dir
mbligh0d0f67d2009-11-06 03:15:03 +0000631 and control_file_dir is not self._USE_TEMP_DIR)
jadmanskidef0c3c2009-03-25 20:07:10 +0000632 if cfd_specified:
633 temp_control_file_dir = None
634 else:
635 temp_control_file_dir = tempfile.mkdtemp(
636 suffix='temp_control_file_dir')
637 control_file_dir = temp_control_file_dir
638 server_control_file = os.path.join(control_file_dir,
mblighe0cbc912010-03-11 18:03:07 +0000639 self._control_filename)
jadmanskidef0c3c2009-03-25 20:07:10 +0000640 client_control_file = os.path.join(control_file_dir,
641 CLIENT_CONTROL_FILENAME)
mbligh0d0f67d2009-11-06 03:15:03 +0000642 if self._client:
jadmanskidef0c3c2009-03-25 20:07:10 +0000643 namespace['control'] = control
644 utils.open_write_close(client_control_file, control)
mblighfeac0102009-04-28 18:31:12 +0000645 shutil.copyfile(CLIENT_WRAPPER_CONTROL_FILE,
646 server_control_file)
jadmanskidef0c3c2009-03-25 20:07:10 +0000647 else:
648 utils.open_write_close(server_control_file, control)
Dan Shicf4d2032015-03-12 15:04:21 -0700649
mbligh26f0d882009-06-22 18:30:01 +0000650 logging.info("Processing control file")
Dan Shib669cbd2013-09-13 11:17:17 -0700651 namespace['use_packaging'] = use_packaging
Dan Shicf4d2032015-03-12 15:04:21 -0700652 self._execute_code(server_control_file, namespace)
mbligh26f0d882009-06-22 18:30:01 +0000653 logging.info("Finished processing control file")
jadmanski10646442008-08-13 14:05:21 +0000654
Dan Shib03ea9d2013-08-15 17:13:27 -0700655 # If no device error occured, no need to collect crashinfo.
656 collect_crashinfo = self.failed_with_device_error
Eric Li6f27d4f2010-09-29 10:55:17 -0700657 except Exception, e:
showardcf8d4922009-10-14 16:08:39 +0000658 try:
659 logging.exception(
660 'Exception escaped control file, job aborting:')
Eric Li6f27d4f2010-09-29 10:55:17 -0700661 self.record('INFO', None, None, str(e),
662 {'job_abort_reason': str(e)})
showardcf8d4922009-10-14 16:08:39 +0000663 except:
664 pass # don't let logging exceptions here interfere
665 raise
jadmanski10646442008-08-13 14:05:21 +0000666 finally:
mblighaebe3b62008-12-22 14:45:40 +0000667 if temp_control_file_dir:
jadmanskie432dd22009-01-30 15:04:51 +0000668 # Clean up temp directory used for copies of the control files
mblighaebe3b62008-12-22 14:45:40 +0000669 try:
670 shutil.rmtree(temp_control_file_dir)
671 except Exception, e:
Ilja H. Friedel04be2bd2014-05-07 21:29:59 -0700672 logging.warning('Could not remove temp directory %s: %s',
mblighe7d9c602009-07-02 19:02:33 +0000673 temp_control_file_dir, e)
jadmanskie432dd22009-01-30 15:04:51 +0000674
jadmanskicdd0c402008-09-19 21:21:31 +0000675 if machines and (collect_crashdumps or collect_crashinfo):
jadmanski10646442008-08-13 14:05:21 +0000676 namespace['test_start_time'] = test_start_time
Christopher Wileyf594c5e2013-07-03 18:25:30 -0700677 if skip_crash_collection:
678 logging.info('Skipping crash dump/info collection '
679 'as requested.')
680 elif collect_crashinfo:
mbligh084bc172008-10-18 14:02:45 +0000681 # includes crashdumps
682 self._execute_code(CRASHINFO_CONTROL_FILE, namespace)
jadmanskicdd0c402008-09-19 21:21:31 +0000683 else:
mbligh084bc172008-10-18 14:02:45 +0000684 self._execute_code(CRASHDUMPS_CONTROL_FILE, namespace)
jadmanski10646442008-08-13 14:05:21 +0000685 self.disable_external_logging()
Chris Sosaf4d43ff2012-10-30 11:21:05 -0700686 if self._uncollected_log_file and created_uncollected_logs:
687 os.remove(self._uncollected_log_file)
jadmanski10646442008-08-13 14:05:21 +0000688 if install_after and machines:
mbligh084bc172008-10-18 14:02:45 +0000689 self._execute_code(INSTALL_CONTROL_FILE, namespace)
jadmanski10646442008-08-13 14:05:21 +0000690
691
692 def run_test(self, url, *args, **dargs):
mbligh2b92b862008-11-22 13:25:32 +0000693 """
694 Summon a test object and run it.
jadmanski10646442008-08-13 14:05:21 +0000695
696 tag
697 tag to add to testname
698 url
699 url of the test to run
700 """
Christopher Wiley8a91f232013-07-09 11:02:27 -0700701 if self._disable_sysinfo:
702 dargs['disable_sysinfo'] = True
703
mblighfc3da5b2010-01-06 18:37:22 +0000704 group, testname = self.pkgmgr.get_package_name(url, 'test')
705 testname, subdir, tag = self._build_tagged_test_name(testname, dargs)
706 outputdir = self._make_test_outputdir(subdir)
jadmanski10646442008-08-13 14:05:21 +0000707
708 def group_func():
709 try:
710 test.runtest(self, url, tag, args, dargs)
711 except error.TestBaseException, e:
712 self.record(e.exit_status, subdir, testname, str(e))
713 raise
714 except Exception, e:
715 info = str(e) + "\n" + traceback.format_exc()
716 self.record('FAIL', subdir, testname, info)
717 raise
718 else:
mbligh2b92b862008-11-22 13:25:32 +0000719 self.record('GOOD', subdir, testname, 'completed successfully')
jadmanskide292df2008-08-26 20:51:14 +0000720
721 result, exc_info = self._run_group(testname, subdir, group_func)
722 if exc_info and isinstance(exc_info[1], error.TestBaseException):
723 return False
724 elif exc_info:
725 raise exc_info[0], exc_info[1], exc_info[2]
726 else:
727 return True
jadmanski10646442008-08-13 14:05:21 +0000728
729
730 def _run_group(self, name, subdir, function, *args, **dargs):
731 """\
732 Underlying method for running something inside of a group.
733 """
jadmanskide292df2008-08-26 20:51:14 +0000734 result, exc_info = None, None
jadmanski10646442008-08-13 14:05:21 +0000735 try:
736 self.record('START', subdir, name)
jadmanski52053632010-06-11 21:08:10 +0000737 result = function(*args, **dargs)
jadmanski10646442008-08-13 14:05:21 +0000738 except error.TestBaseException, e:
jadmanskib88d6dc2009-01-10 00:33:18 +0000739 self.record("END %s" % e.exit_status, subdir, name)
jadmanskide292df2008-08-26 20:51:14 +0000740 exc_info = sys.exc_info()
jadmanski10646442008-08-13 14:05:21 +0000741 except Exception, e:
742 err_msg = str(e) + '\n'
743 err_msg += traceback.format_exc()
744 self.record('END ABORT', subdir, name, err_msg)
745 raise error.JobError(name + ' failed\n' + traceback.format_exc())
746 else:
747 self.record('END GOOD', subdir, name)
748
jadmanskide292df2008-08-26 20:51:14 +0000749 return result, exc_info
jadmanski10646442008-08-13 14:05:21 +0000750
751
752 def run_group(self, function, *args, **dargs):
753 """\
754 function:
755 subroutine to run
756 *args:
757 arguments for the function
758 """
759
760 name = function.__name__
761
762 # Allow the tag for the group to be specified.
763 tag = dargs.pop('tag', None)
764 if tag:
765 name = tag
766
jadmanskide292df2008-08-26 20:51:14 +0000767 return self._run_group(name, None, function, *args, **dargs)[0]
jadmanski10646442008-08-13 14:05:21 +0000768
769
Gwendal Grignou7a61d2f2014-05-23 11:05:51 -0700770 def run_op(self, op, op_func, get_kernel_func):
jadmanski10646442008-08-13 14:05:21 +0000771 """\
772 A specialization of run_group meant specifically for handling
Gwendal Grignou7a61d2f2014-05-23 11:05:51 -0700773 management operation. Includes support for capturing the kernel version
774 after the operation.
jadmanski10646442008-08-13 14:05:21 +0000775
Gwendal Grignou7a61d2f2014-05-23 11:05:51 -0700776 Args:
777 op: name of the operation.
778 op_func: a function that carries out the operation (reboot, suspend)
779 get_kernel_func: a function that returns a string
780 representing the kernel version.
jadmanski10646442008-08-13 14:05:21 +0000781 """
jadmanski10646442008-08-13 14:05:21 +0000782 try:
Gwendal Grignou7a61d2f2014-05-23 11:05:51 -0700783 self.record('START', None, op)
784 op_func()
jadmanski10646442008-08-13 14:05:21 +0000785 except Exception, e:
jadmanski10646442008-08-13 14:05:21 +0000786 err_msg = str(e) + '\n' + traceback.format_exc()
Gwendal Grignou7a61d2f2014-05-23 11:05:51 -0700787 self.record('END FAIL', None, op, err_msg)
jadmanski4b51d542009-04-08 14:17:16 +0000788 raise
jadmanski10646442008-08-13 14:05:21 +0000789 else:
790 kernel = get_kernel_func()
Gwendal Grignou7a61d2f2014-05-23 11:05:51 -0700791 self.record('END GOOD', None, op,
Dale Curtis74a314b2011-06-23 14:55:46 -0700792 optional_fields={"kernel": kernel})
jadmanski10646442008-08-13 14:05:21 +0000793
794
jadmanskie432dd22009-01-30 15:04:51 +0000795 def run_control(self, path):
796 """Execute a control file found at path (relative to the autotest
797 path). Intended for executing a control file within a control file,
798 not for running the top-level job control file."""
799 path = os.path.join(self.autodir, path)
800 control_file = self._load_control_file(path)
mbligh0d0f67d2009-11-06 03:15:03 +0000801 self.run(control=control_file, control_file_dir=self._USE_TEMP_DIR)
jadmanskie432dd22009-01-30 15:04:51 +0000802
803
jadmanskic09fc152008-10-15 17:56:59 +0000804 def add_sysinfo_command(self, command, logfile=None, on_every_test=False):
mbligh4395bbd2009-03-25 19:34:17 +0000805 self._add_sysinfo_loggable(sysinfo.command(command, logf=logfile),
jadmanskic09fc152008-10-15 17:56:59 +0000806 on_every_test)
807
808
809 def add_sysinfo_logfile(self, file, on_every_test=False):
810 self._add_sysinfo_loggable(sysinfo.logfile(file), on_every_test)
811
812
813 def _add_sysinfo_loggable(self, loggable, on_every_test):
814 if on_every_test:
815 self.sysinfo.test_loggables.add(loggable)
816 else:
817 self.sysinfo.boot_loggables.add(loggable)
818
819
jadmanski10646442008-08-13 14:05:21 +0000820 def _read_warnings(self):
jadmanskif37df842009-02-11 00:03:26 +0000821 """Poll all the warning loggers and extract any new warnings that have
822 been logged. If the warnings belong to a category that is currently
823 disabled, this method will discard them and they will no longer be
824 retrievable.
825
826 Returns a list of (timestamp, message) tuples, where timestamp is an
827 integer epoch timestamp."""
jadmanski10646442008-08-13 14:05:21 +0000828 warnings = []
829 while True:
830 # pull in a line of output from every logger that has
831 # output ready to be read
mbligh2b92b862008-11-22 13:25:32 +0000832 loggers, _, _ = select.select(self.warning_loggers, [], [], 0)
jadmanski10646442008-08-13 14:05:21 +0000833 closed_loggers = set()
834 for logger in loggers:
835 line = logger.readline()
836 # record any broken pipes (aka line == empty)
837 if len(line) == 0:
838 closed_loggers.add(logger)
839 continue
jadmanskif37df842009-02-11 00:03:26 +0000840 # parse out the warning
841 timestamp, msgtype, msg = line.split('\t', 2)
842 timestamp = int(timestamp)
843 # if the warning is valid, add it to the results
844 if self.warning_manager.is_valid(timestamp, msgtype):
845 warnings.append((timestamp, msg.strip()))
jadmanski10646442008-08-13 14:05:21 +0000846
847 # stop listening to loggers that are closed
848 self.warning_loggers -= closed_loggers
849
850 # stop if none of the loggers have any output left
851 if not loggers:
852 break
853
854 # sort into timestamp order
855 warnings.sort()
856 return warnings
857
858
showardcc929362010-01-25 21:20:41 +0000859 def _unique_subdirectory(self, base_subdirectory_name):
860 """Compute a unique results subdirectory based on the given name.
861
862 Appends base_subdirectory_name with a number as necessary to find a
863 directory name that doesn't already exist.
864 """
865 subdirectory = base_subdirectory_name
866 counter = 1
867 while os.path.exists(os.path.join(self.resultdir, subdirectory)):
868 subdirectory = base_subdirectory_name + '.' + str(counter)
869 counter += 1
870 return subdirectory
871
872
jadmanski52053632010-06-11 21:08:10 +0000873 def get_record_context(self):
874 """Returns an object representing the current job.record context.
875
876 The object returned is an opaque object with a 0-arg restore method
877 which can be called to restore the job.record context (i.e. indentation)
878 to the current level. The intention is that it should be used when
879 something external which generate job.record calls (e.g. an autotest
880 client) can fail catastrophically and the server job record state
881 needs to be reset to its original "known good" state.
882
883 @return: A context object with a 0-arg restore() method."""
884 return self._indenter.get_context()
885
886
showardcc929362010-01-25 21:20:41 +0000887 def record_summary(self, status_code, test_name, reason='', attributes=None,
888 distinguishing_attributes=(), child_test_ids=None):
889 """Record a summary test result.
890
891 @param status_code: status code string, see
892 common_lib.log.is_valid_status()
893 @param test_name: name of the test
894 @param reason: (optional) string providing detailed reason for test
895 outcome
896 @param attributes: (optional) dict of string keyvals to associate with
897 this result
898 @param distinguishing_attributes: (optional) list of attribute names
899 that should be used to distinguish identically-named test
900 results. These attributes should be present in the attributes
901 parameter. This is used to generate user-friendly subdirectory
902 names.
903 @param child_test_ids: (optional) list of test indices for test results
904 used in generating this result.
905 """
906 subdirectory_name_parts = [test_name]
907 for attribute in distinguishing_attributes:
908 assert attributes
909 assert attribute in attributes, '%s not in %s' % (attribute,
910 attributes)
911 subdirectory_name_parts.append(attributes[attribute])
912 base_subdirectory_name = '.'.join(subdirectory_name_parts)
913
914 subdirectory = self._unique_subdirectory(base_subdirectory_name)
915 subdirectory_path = os.path.join(self.resultdir, subdirectory)
916 os.mkdir(subdirectory_path)
917
918 self.record(status_code, subdirectory, test_name,
919 status=reason, optional_fields={'is_summary': True})
920
921 if attributes:
922 utils.write_keyval(subdirectory_path, attributes)
923
924 if child_test_ids:
925 ids_string = ','.join(str(test_id) for test_id in child_test_ids)
926 summary_data = {'child_test_ids': ids_string}
927 utils.write_keyval(os.path.join(subdirectory_path, 'summary_data'),
928 summary_data)
929
930
jadmanski16a7ff72009-04-01 18:19:53 +0000931 def disable_warnings(self, warning_type):
jadmanskif37df842009-02-11 00:03:26 +0000932 self.warning_manager.disable_warnings(warning_type)
jadmanski16a7ff72009-04-01 18:19:53 +0000933 self.record("INFO", None, None,
934 "disabling %s warnings" % warning_type,
935 {"warnings.disable": warning_type})
jadmanskif37df842009-02-11 00:03:26 +0000936
937
jadmanski16a7ff72009-04-01 18:19:53 +0000938 def enable_warnings(self, warning_type):
jadmanskif37df842009-02-11 00:03:26 +0000939 self.warning_manager.enable_warnings(warning_type)
jadmanski16a7ff72009-04-01 18:19:53 +0000940 self.record("INFO", None, None,
941 "enabling %s warnings" % warning_type,
942 {"warnings.enable": warning_type})
jadmanskif37df842009-02-11 00:03:26 +0000943
944
jadmanski779bd292009-03-19 17:33:33 +0000945 def get_status_log_path(self, subdir=None):
946 """Return the path to the job status log.
947
948 @param subdir - Optional paramter indicating that you want the path
949 to a subdirectory status log.
950
951 @returns The path where the status log should be.
952 """
mbligh210bae62009-04-01 18:33:13 +0000953 if self.resultdir:
954 if subdir:
955 return os.path.join(self.resultdir, subdir, "status.log")
956 else:
957 return os.path.join(self.resultdir, "status.log")
jadmanski779bd292009-03-19 17:33:33 +0000958 else:
mbligh210bae62009-04-01 18:33:13 +0000959 return None
jadmanski779bd292009-03-19 17:33:33 +0000960
961
jadmanski6bb32d72009-03-19 20:25:24 +0000962 def _update_uncollected_logs_list(self, update_func):
963 """Updates the uncollected logs list in a multi-process safe manner.
964
965 @param update_func - a function that updates the list of uncollected
966 logs. Should take one parameter, the list to be updated.
967 """
Dan Shi07e09af2013-04-12 09:31:29 -0700968 # Skip log collection if file _uncollected_log_file does not exist.
969 if not (self._uncollected_log_file and
970 os.path.exists(self._uncollected_log_file)):
971 return
mbligh0d0f67d2009-11-06 03:15:03 +0000972 if self._uncollected_log_file:
973 log_file = open(self._uncollected_log_file, "r+")
mbligha788dc42009-03-26 21:10:16 +0000974 fcntl.flock(log_file, fcntl.LOCK_EX)
jadmanski6bb32d72009-03-19 20:25:24 +0000975 try:
976 uncollected_logs = pickle.load(log_file)
977 update_func(uncollected_logs)
978 log_file.seek(0)
979 log_file.truncate()
980 pickle.dump(uncollected_logs, log_file)
jadmanski3bff9092009-04-22 18:09:47 +0000981 log_file.flush()
jadmanski6bb32d72009-03-19 20:25:24 +0000982 finally:
983 fcntl.flock(log_file, fcntl.LOCK_UN)
984 log_file.close()
985
986
987 def add_client_log(self, hostname, remote_path, local_path):
988 """Adds a new set of client logs to the list of uncollected logs,
989 to allow for future log recovery.
990
991 @param host - the hostname of the machine holding the logs
992 @param remote_path - the directory on the remote machine holding logs
993 @param local_path - the local directory to copy the logs into
994 """
995 def update_func(logs_list):
996 logs_list.append((hostname, remote_path, local_path))
997 self._update_uncollected_logs_list(update_func)
998
999
1000 def remove_client_log(self, hostname, remote_path, local_path):
1001 """Removes a set of client logs from the list of uncollected logs,
1002 to allow for future log recovery.
1003
1004 @param host - the hostname of the machine holding the logs
1005 @param remote_path - the directory on the remote machine holding logs
1006 @param local_path - the local directory to copy the logs into
1007 """
1008 def update_func(logs_list):
1009 logs_list.remove((hostname, remote_path, local_path))
1010 self._update_uncollected_logs_list(update_func)
1011
1012
mbligh0d0f67d2009-11-06 03:15:03 +00001013 def get_client_logs(self):
1014 """Retrieves the list of uncollected logs, if it exists.
1015
1016 @returns A list of (host, remote_path, local_path) tuples. Returns
1017 an empty list if no uncollected logs file exists.
1018 """
1019 log_exists = (self._uncollected_log_file and
1020 os.path.exists(self._uncollected_log_file))
1021 if log_exists:
1022 return pickle.load(open(self._uncollected_log_file))
1023 else:
1024 return []
1025
1026
mbligh084bc172008-10-18 14:02:45 +00001027 def _fill_server_control_namespace(self, namespace, protect=True):
mbligh2b92b862008-11-22 13:25:32 +00001028 """
1029 Prepare a namespace to be used when executing server control files.
mbligh084bc172008-10-18 14:02:45 +00001030
1031 This sets up the control file API by importing modules and making them
1032 available under the appropriate names within namespace.
1033
1034 For use by _execute_code().
1035
1036 Args:
1037 namespace: The namespace dictionary to fill in.
1038 protect: Boolean. If True (the default) any operation that would
1039 clobber an existing entry in namespace will cause an error.
1040 Raises:
1041 error.AutoservError: When a name would be clobbered by import.
1042 """
1043 def _import_names(module_name, names=()):
mbligh2b92b862008-11-22 13:25:32 +00001044 """
1045 Import a module and assign named attributes into namespace.
mbligh084bc172008-10-18 14:02:45 +00001046
1047 Args:
1048 module_name: The string module name.
1049 names: A limiting list of names to import from module_name. If
1050 empty (the default), all names are imported from the module
1051 similar to a "from foo.bar import *" statement.
1052 Raises:
1053 error.AutoservError: When a name being imported would clobber
1054 a name already in namespace.
1055 """
1056 module = __import__(module_name, {}, {}, names)
1057
1058 # No names supplied? Import * from the lowest level module.
1059 # (Ugh, why do I have to implement this part myself?)
1060 if not names:
1061 for submodule_name in module_name.split('.')[1:]:
1062 module = getattr(module, submodule_name)
1063 if hasattr(module, '__all__'):
1064 names = getattr(module, '__all__')
1065 else:
1066 names = dir(module)
1067
1068 # Install each name into namespace, checking to make sure it
1069 # doesn't override anything that already exists.
1070 for name in names:
1071 # Check for conflicts to help prevent future problems.
1072 if name in namespace and protect:
1073 if namespace[name] is not getattr(module, name):
1074 raise error.AutoservError('importing name '
1075 '%s from %s %r would override %r' %
1076 (name, module_name, getattr(module, name),
1077 namespace[name]))
1078 else:
1079 # Encourage cleanliness and the use of __all__ for a
1080 # more concrete API with less surprises on '*' imports.
1081 warnings.warn('%s (%r) being imported from %s for use '
1082 'in server control files is not the '
1083 'first occurrance of that import.' %
1084 (name, namespace[name], module_name))
1085
1086 namespace[name] = getattr(module, name)
1087
1088
1089 # This is the equivalent of prepending a bunch of import statements to
1090 # the front of the control script.
mbligha2b07dd2009-06-22 18:26:13 +00001091 namespace.update(os=os, sys=sys, logging=logging)
mbligh084bc172008-10-18 14:02:45 +00001092 _import_names('autotest_lib.server',
1093 ('hosts', 'autotest', 'kvm', 'git', 'standalone_profiler',
1094 'source_kernel', 'rpm_kernel', 'deb_kernel', 'git_kernel'))
1095 _import_names('autotest_lib.server.subcommand',
1096 ('parallel', 'parallel_simple', 'subcommand'))
1097 _import_names('autotest_lib.server.utils',
1098 ('run', 'get_tmp_dir', 'sh_escape', 'parse_machine'))
1099 _import_names('autotest_lib.client.common_lib.error')
1100 _import_names('autotest_lib.client.common_lib.barrier', ('barrier',))
1101
1102 # Inject ourself as the job object into other classes within the API.
1103 # (Yuck, this injection is a gross thing be part of a public API. -gps)
1104 #
1105 # XXX Base & SiteAutotest do not appear to use .job. Who does?
1106 namespace['autotest'].Autotest.job = self
1107 # server.hosts.base_classes.Host uses .job.
1108 namespace['hosts'].Host.job = self
Eric Li10222b82010-11-24 09:33:15 -08001109 namespace['hosts'].factory.ssh_user = self._ssh_user
1110 namespace['hosts'].factory.ssh_port = self._ssh_port
1111 namespace['hosts'].factory.ssh_pass = self._ssh_pass
Fang Dengd1c2b732013-08-20 12:59:46 -07001112 namespace['hosts'].factory.ssh_verbosity_flag = (
1113 self._ssh_verbosity_flag)
Aviv Keshetc5947fa2013-09-04 14:06:29 -07001114 namespace['hosts'].factory.ssh_options = self._ssh_options
mbligh084bc172008-10-18 14:02:45 +00001115
1116
1117 def _execute_code(self, code_file, namespace, protect=True):
mbligh2b92b862008-11-22 13:25:32 +00001118 """
1119 Execute code using a copy of namespace as a server control script.
mbligh084bc172008-10-18 14:02:45 +00001120
1121 Unless protect_namespace is explicitly set to False, the dict will not
1122 be modified.
1123
1124 Args:
1125 code_file: The filename of the control file to execute.
1126 namespace: A dict containing names to make available during execution.
1127 protect: Boolean. If True (the default) a copy of the namespace dict
1128 is used during execution to prevent the code from modifying its
1129 contents outside of this function. If False the raw dict is
1130 passed in and modifications will be allowed.
1131 """
1132 if protect:
1133 namespace = namespace.copy()
1134 self._fill_server_control_namespace(namespace, protect=protect)
1135 # TODO: Simplify and get rid of the special cases for only 1 machine.
showard3e66e8c2008-10-27 19:20:51 +00001136 if len(self.machines) > 1:
mbligh084bc172008-10-18 14:02:45 +00001137 machines_text = '\n'.join(self.machines) + '\n'
1138 # Only rewrite the file if it does not match our machine list.
1139 try:
1140 machines_f = open(MACHINES_FILENAME, 'r')
1141 existing_machines_text = machines_f.read()
1142 machines_f.close()
1143 except EnvironmentError:
1144 existing_machines_text = None
1145 if machines_text != existing_machines_text:
1146 utils.open_write_close(MACHINES_FILENAME, machines_text)
1147 execfile(code_file, namespace, namespace)
jadmanski10646442008-08-13 14:05:21 +00001148
1149
jadmanskie29d0e42010-06-17 16:06:52 +00001150 def _parse_status(self, new_line):
mbligh0d0f67d2009-11-06 03:15:03 +00001151 if not self._using_parser:
jadmanski10646442008-08-13 14:05:21 +00001152 return
jadmanskie29d0e42010-06-17 16:06:52 +00001153 new_tests = self.parser.process_lines([new_line])
jadmanski10646442008-08-13 14:05:21 +00001154 for test in new_tests:
1155 self.__insert_test(test)
1156
1157
1158 def __insert_test(self, test):
mbligh2b92b862008-11-22 13:25:32 +00001159 """
1160 An internal method to insert a new test result into the
jadmanski10646442008-08-13 14:05:21 +00001161 database. This method will not raise an exception, even if an
1162 error occurs during the insert, to avoid failing a test
1163 simply because of unexpected database issues."""
showard21baa452008-10-21 00:08:39 +00001164 self.num_tests_run += 1
1165 if status_lib.is_worse_than_or_equal_to(test.status, 'FAIL'):
1166 self.num_tests_failed += 1
jadmanski10646442008-08-13 14:05:21 +00001167 try:
1168 self.results_db.insert_test(self.job_model, test)
1169 except Exception:
1170 msg = ("WARNING: An unexpected error occured while "
1171 "inserting test results into the database. "
1172 "Ignoring error.\n" + traceback.format_exc())
1173 print >> sys.stderr, msg
1174
mblighcaa62c22008-04-07 21:51:17 +00001175
mblighfc3da5b2010-01-06 18:37:22 +00001176 def preprocess_client_state(self):
1177 """
1178 Produce a state file for initializing the state of a client job.
1179
1180 Creates a new client state file with all the current server state, as
1181 well as some pre-set client state.
1182
1183 @returns The path of the file the state was written into.
1184 """
1185 # initialize the sysinfo state
1186 self._state.set('client', 'sysinfo', self.sysinfo.serialize())
1187
1188 # dump the state out to a tempfile
1189 fd, file_path = tempfile.mkstemp(dir=self.tmpdir)
1190 os.close(fd)
mbligha2c99492010-01-27 22:59:50 +00001191
1192 # write_to_file doesn't need locking, we exclusively own file_path
mblighfc3da5b2010-01-06 18:37:22 +00001193 self._state.write_to_file(file_path)
1194 return file_path
1195
1196
1197 def postprocess_client_state(self, state_path):
1198 """
1199 Update the state of this job with the state from a client job.
1200
1201 Updates the state of the server side of a job with the final state
1202 of a client job that was run. Updates the non-client-specific state,
1203 pulls in some specific bits from the client-specific state, and then
1204 discards the rest. Removes the state file afterwards
1205
1206 @param state_file A path to the state file from the client.
1207 """
1208 # update the on-disk state
mblighfc3da5b2010-01-06 18:37:22 +00001209 try:
jadmanskib6e7bdb2010-04-13 16:00:39 +00001210 self._state.read_from_file(state_path)
mblighfc3da5b2010-01-06 18:37:22 +00001211 os.remove(state_path)
mbligha2c99492010-01-27 22:59:50 +00001212 except OSError, e:
mblighfc3da5b2010-01-06 18:37:22 +00001213 # ignore file-not-found errors
1214 if e.errno != errno.ENOENT:
1215 raise
jadmanskib6e7bdb2010-04-13 16:00:39 +00001216 else:
1217 logging.debug('Client state file %s not found', state_path)
mblighfc3da5b2010-01-06 18:37:22 +00001218
1219 # update the sysinfo state
1220 if self._state.has('client', 'sysinfo'):
1221 self.sysinfo.deserialize(self._state.get('client', 'sysinfo'))
1222
1223 # drop all the client-specific state
1224 self._state.discard_namespace('client')
1225
1226
mbligh0a883702010-04-21 01:58:34 +00001227 def clear_all_known_hosts(self):
1228 """Clears known hosts files for all AbstractSSHHosts."""
1229 for host in self.hosts:
1230 if isinstance(host, abstract_ssh.AbstractSSHHost):
1231 host.clear_known_hosts()
1232
1233
jadmanskif37df842009-02-11 00:03:26 +00001234class warning_manager(object):
1235 """Class for controlling warning logs. Manages the enabling and disabling
1236 of warnings."""
1237 def __init__(self):
1238 # a map of warning types to a list of disabled time intervals
1239 self.disabled_warnings = {}
1240
1241
1242 def is_valid(self, timestamp, warning_type):
1243 """Indicates if a warning (based on the time it occured and its type)
1244 is a valid warning. A warning is considered "invalid" if this type of
1245 warning was marked as "disabled" at the time the warning occured."""
1246 disabled_intervals = self.disabled_warnings.get(warning_type, [])
1247 for start, end in disabled_intervals:
1248 if timestamp >= start and (end is None or timestamp < end):
1249 return False
1250 return True
1251
1252
1253 def disable_warnings(self, warning_type, current_time_func=time.time):
1254 """As of now, disables all further warnings of this type."""
1255 intervals = self.disabled_warnings.setdefault(warning_type, [])
1256 if not intervals or intervals[-1][1] is not None:
jadmanski16a7ff72009-04-01 18:19:53 +00001257 intervals.append((int(current_time_func()), None))
jadmanskif37df842009-02-11 00:03:26 +00001258
1259
1260 def enable_warnings(self, warning_type, current_time_func=time.time):
1261 """As of now, enables all further warnings of this type."""
1262 intervals = self.disabled_warnings.get(warning_type, [])
1263 if intervals and intervals[-1][1] is None:
jadmanski16a7ff72009-04-01 18:19:53 +00001264 intervals[-1] = (intervals[-1][0], int(current_time_func()))
Paul Pendlebury57593562011-06-15 10:45:49 -07001265
1266
1267# load up site-specific code for generating site-specific job data
1268get_site_job_data = utils.import_site_function(__file__,
1269 "autotest_lib.server.site_server_job", "get_site_job_data",
1270 _get_site_job_data_dummy)
1271
1272
1273site_server_job = utils.import_site_class(
1274 __file__, "autotest_lib.server.site_server_job", "site_server_job",
1275 base_server_job)
1276
1277
1278class server_job(site_server_job):
Dale Curtis456d3c12011-07-19 11:42:51 -07001279 pass