blob: 92bf33ef23f3b0edc9a2969dafb64f73334b9256 [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
Simran Basi1bf60eb2015-12-01 16:39:29 -080021from autotest_lib.server import utils as server_utils
22from autotest_lib.server.cros.dynamic_suite import frontend_wrappers
beepsd0672682013-09-16 17:32:16 -070023from autotest_lib.server.hosts import abstract_ssh, factory as host_factory
jadmanski10646442008-08-13 14:05:21 +000024from autotest_lib.tko import db as tko_db, status_lib, utils as tko_utils
jadmanski10646442008-08-13 14:05:21 +000025
26
Alex Miller44ae9232014-06-20 17:24:25 -070027INCREMENTAL_TKO_PARSING = global_config.global_config.get_config_value(
28 'autoserv', 'incremental_tko_parsing', type=bool, default=False)
29
mbligh084bc172008-10-18 14:02:45 +000030def _control_segment_path(name):
31 """Get the pathname of the named control segment file."""
jadmanski10646442008-08-13 14:05:21 +000032 server_dir = os.path.dirname(os.path.abspath(__file__))
mbligh084bc172008-10-18 14:02:45 +000033 return os.path.join(server_dir, "control_segments", name)
jadmanski10646442008-08-13 14:05:21 +000034
35
mbligh084bc172008-10-18 14:02:45 +000036CLIENT_CONTROL_FILENAME = 'control'
37SERVER_CONTROL_FILENAME = 'control.srv'
38MACHINES_FILENAME = '.machines'
jadmanski10646442008-08-13 14:05:21 +000039
mbligh084bc172008-10-18 14:02:45 +000040CLIENT_WRAPPER_CONTROL_FILE = _control_segment_path('client_wrapper')
41CRASHDUMPS_CONTROL_FILE = _control_segment_path('crashdumps')
42CRASHINFO_CONTROL_FILE = _control_segment_path('crashinfo')
mbligh084bc172008-10-18 14:02:45 +000043INSTALL_CONTROL_FILE = _control_segment_path('install')
showard45ae8192008-11-05 19:32:53 +000044CLEANUP_CONTROL_FILE = _control_segment_path('cleanup')
mbligh084bc172008-10-18 14:02:45 +000045VERIFY_CONTROL_FILE = _control_segment_path('verify')
mbligh084bc172008-10-18 14:02:45 +000046REPAIR_CONTROL_FILE = _control_segment_path('repair')
Alex Millercb79ba72013-05-29 14:43:00 -070047PROVISION_CONTROL_FILE = _control_segment_path('provision')
beepscb6f1e22013-06-28 19:14:10 -070048VERIFY_JOB_REPO_URL_CONTROL_FILE = _control_segment_path('verify_job_repo_url')
Dan Shi07e09af2013-04-12 09:31:29 -070049RESET_CONTROL_FILE = _control_segment_path('reset')
jadmanski10646442008-08-13 14:05:21 +000050
mbligh062ed152009-01-13 00:57:14 +000051# by default provide a stub that generates no site data
52def _get_site_job_data_dummy(job):
53 return {}
54
55
jadmanski2a89dac2010-06-11 14:32:58 +000056class status_indenter(base_job.status_indenter):
57 """Provide a simple integer-backed status indenter."""
58 def __init__(self):
59 self._indent = 0
60
61
62 @property
63 def indent(self):
64 return self._indent
65
66
67 def increment(self):
68 self._indent += 1
69
70
71 def decrement(self):
72 self._indent -= 1
73
74
jadmanski52053632010-06-11 21:08:10 +000075 def get_context(self):
76 """Returns a context object for use by job.get_record_context."""
77 class context(object):
78 def __init__(self, indenter, indent):
79 self._indenter = indenter
80 self._indent = indent
81 def restore(self):
82 self._indenter._indent = self._indent
83 return context(self, self._indent)
84
85
jadmanski2a89dac2010-06-11 14:32:58 +000086class server_job_record_hook(object):
87 """The job.record hook for server job. Used to inject WARN messages from
88 the console or vlm whenever new logs are written, and to echo any logs
89 to INFO level logging. Implemented as a class so that it can use state to
90 block recursive calls, so that the hook can call job.record itself to
91 log WARN messages.
92
93 Depends on job._read_warnings and job._logger.
94 """
95 def __init__(self, job):
96 self._job = job
97 self._being_called = False
98
99
100 def __call__(self, entry):
101 """A wrapper around the 'real' record hook, the _hook method, which
102 prevents recursion. This isn't making any effort to be threadsafe,
103 the intent is to outright block infinite recursion via a
104 job.record->_hook->job.record->_hook->job.record... chain."""
105 if self._being_called:
106 return
107 self._being_called = True
108 try:
109 self._hook(self._job, entry)
110 finally:
111 self._being_called = False
112
113
114 @staticmethod
115 def _hook(job, entry):
116 """The core hook, which can safely call job.record."""
117 entries = []
118 # poll all our warning loggers for new warnings
119 for timestamp, msg in job._read_warnings():
120 warning_entry = base_job.status_log_entry(
121 'WARN', None, None, msg, {}, timestamp=timestamp)
122 entries.append(warning_entry)
123 job.record_entry(warning_entry)
124 # echo rendered versions of all the status logs to info
125 entries.append(entry)
126 for entry in entries:
127 rendered_entry = job._logger.render_entry(entry)
128 logging.info(rendered_entry)
jadmanskie29d0e42010-06-17 16:06:52 +0000129 job._parse_status(rendered_entry)
jadmanski2a89dac2010-06-11 14:32:58 +0000130
131
mbligh0d0f67d2009-11-06 03:15:03 +0000132class base_server_job(base_job.base_job):
133 """The server-side concrete implementation of base_job.
jadmanski10646442008-08-13 14:05:21 +0000134
mbligh0d0f67d2009-11-06 03:15:03 +0000135 Optional properties provided by this implementation:
136 serverdir
mbligh0d0f67d2009-11-06 03:15:03 +0000137
138 num_tests_run
139 num_tests_failed
140
141 warning_manager
142 warning_loggers
jadmanski10646442008-08-13 14:05:21 +0000143 """
144
mbligh0d0f67d2009-11-06 03:15:03 +0000145 _STATUS_VERSION = 1
jadmanski10646442008-08-13 14:05:21 +0000146
Aviv Keshetc5947fa2013-09-04 14:06:29 -0700147 # TODO crbug.com/285395 eliminate ssh_verbosity_flag
jadmanski10646442008-08-13 14:05:21 +0000148 def __init__(self, control, args, resultdir, label, user, machines,
149 client=False, parse_job='',
beepsd0672682013-09-16 17:32:16 -0700150 ssh_user=host_factory.DEFAULT_SSH_USER,
151 ssh_port=host_factory.DEFAULT_SSH_PORT,
152 ssh_pass=host_factory.DEFAULT_SSH_PASS,
153 ssh_verbosity_flag=host_factory.DEFAULT_SSH_VERBOSITY,
154 ssh_options=host_factory.DEFAULT_SSH_OPTIONS,
Aviv Keshetc5947fa2013-09-04 14:06:29 -0700155 test_retry=0, group_name='',
Fang Dengd1c2b732013-08-20 12:59:46 -0700156 tag='', disable_sysinfo=False,
Dan Shi70647ca2015-07-16 22:52:35 -0700157 control_filename=SERVER_CONTROL_FILENAME,
Simran Basi1bf60eb2015-12-01 16:39:29 -0800158 parent_job_id=None, host_attributes=None, in_lab=False):
jadmanski10646442008-08-13 14:05:21 +0000159 """
mbligh374f3412009-05-13 21:29:45 +0000160 Create a server side job object.
mblighb5dac432008-11-27 00:38:44 +0000161
mblighe7d9c602009-07-02 19:02:33 +0000162 @param control: The pathname of the control file.
163 @param args: Passed to the control file.
164 @param resultdir: Where to throw the results.
165 @param label: Description of the job.
166 @param user: Username for the job (email address).
167 @param client: True if this is a client-side control file.
168 @param parse_job: string, if supplied it is the job execution tag that
169 the results will be passed through to the TKO parser with.
170 @param ssh_user: The SSH username. [root]
171 @param ssh_port: The SSH port number. [22]
172 @param ssh_pass: The SSH passphrase, if needed.
Fang Dengd1c2b732013-08-20 12:59:46 -0700173 @param ssh_verbosity_flag: The SSH verbosity flag, '-v', '-vv',
174 '-vvv', or an empty string if not needed.
Aviv Keshetc5947fa2013-09-04 14:06:29 -0700175 @param ssh_options: A string giving additional options that will be
176 included in ssh commands.
Scott Zawalski91493c82013-01-25 16:15:20 -0500177 @param test_retry: The number of times to retry a test if the test did
178 not complete successfully.
mblighe7d9c602009-07-02 19:02:33 +0000179 @param group_name: If supplied, this will be written out as
mbligh374f3412009-05-13 21:29:45 +0000180 host_group_name in the keyvals file for the parser.
mblighe7d9c602009-07-02 19:02:33 +0000181 @param tag: The job execution tag from the scheduler. [optional]
Christopher Wiley8a91f232013-07-09 11:02:27 -0700182 @param disable_sysinfo: Whether we should disable the sysinfo step of
183 tests for a modest shortening of test time. [optional]
mblighe0cbc912010-03-11 18:03:07 +0000184 @param control_filename: The filename where the server control file
185 should be written in the results directory.
Dan Shi70647ca2015-07-16 22:52:35 -0700186 @param parent_job_id: Job ID of the parent job. Default to None if the
187 job does not have a parent job.
Simran Basi1bf60eb2015-12-01 16:39:29 -0800188 @param host_attributes: Dict of host attributes passed into autoserv
189 via the command line. If specified here, these
190 attributes will apply to all machines.
191 @param in_lab: Boolean that indicates if this is running in the lab
192 environment.
jadmanski10646442008-08-13 14:05:21 +0000193 """
Scott Zawalski91493c82013-01-25 16:15:20 -0500194 super(base_server_job, self).__init__(resultdir=resultdir,
195 test_retry=test_retry)
mbligh0d0f67d2009-11-06 03:15:03 +0000196 path = os.path.dirname(__file__)
Scott Zawalski91493c82013-01-25 16:15:20 -0500197 self.test_retry = test_retry
mbligh0d0f67d2009-11-06 03:15:03 +0000198 self.control = control
199 self._uncollected_log_file = os.path.join(self.resultdir,
200 'uncollected_logs')
201 debugdir = os.path.join(self.resultdir, 'debug')
202 if not os.path.exists(debugdir):
203 os.mkdir(debugdir)
204
205 if user:
206 self.user = user
207 else:
208 self.user = getpass.getuser()
209
jadmanski808f4b12010-04-09 22:30:31 +0000210 self.args = args
Peter Mayo7a875762012-06-13 14:38:15 -0400211 self.label = label
jadmanski10646442008-08-13 14:05:21 +0000212 self.machines = machines
mbligh0d0f67d2009-11-06 03:15:03 +0000213 self._client = client
jadmanski10646442008-08-13 14:05:21 +0000214 self.warning_loggers = set()
jadmanskif37df842009-02-11 00:03:26 +0000215 self.warning_manager = warning_manager()
mbligh0d0f67d2009-11-06 03:15:03 +0000216 self._ssh_user = ssh_user
217 self._ssh_port = ssh_port
218 self._ssh_pass = ssh_pass
Fang Dengd1c2b732013-08-20 12:59:46 -0700219 self._ssh_verbosity_flag = ssh_verbosity_flag
Aviv Keshetc5947fa2013-09-04 14:06:29 -0700220 self._ssh_options = ssh_options
mblighe7d9c602009-07-02 19:02:33 +0000221 self.tag = tag
mbligh09108442008-10-15 16:27:38 +0000222 self.last_boot_tag = None
jadmanski53aaf382008-11-17 16:22:31 +0000223 self.hosts = set()
mbligh0d0f67d2009-11-06 03:15:03 +0000224 self.drop_caches = False
mblighb5dac432008-11-27 00:38:44 +0000225 self.drop_caches_between_iterations = False
mblighe0cbc912010-03-11 18:03:07 +0000226 self._control_filename = control_filename
Christopher Wiley8a91f232013-07-09 11:02:27 -0700227 self._disable_sysinfo = disable_sysinfo
jadmanski10646442008-08-13 14:05:21 +0000228
showard75cdfee2009-06-10 17:40:41 +0000229 self.logging = logging_manager.get_logging_manager(
230 manage_stdout_and_stderr=True, redirect_fds=True)
231 subcommand.logging_manager_object = self.logging
jadmanski10646442008-08-13 14:05:21 +0000232
mbligh0d0f67d2009-11-06 03:15:03 +0000233 self.sysinfo = sysinfo.sysinfo(self.resultdir)
jadmanski043e1132008-11-19 17:10:32 +0000234 self.profilers = profilers.profilers(self)
jadmanskic09fc152008-10-15 17:56:59 +0000235
jadmanski10646442008-08-13 14:05:21 +0000236 job_data = {'label' : label, 'user' : user,
237 'hostname' : ','.join(machines),
Eric Li861b2d52011-02-04 14:50:35 -0800238 'drone' : platform.node(),
mbligh0d0f67d2009-11-06 03:15:03 +0000239 'status_version' : str(self._STATUS_VERSION),
showard170873e2009-01-07 00:22:26 +0000240 'job_started' : str(int(time.time()))}
Dan Shi70647ca2015-07-16 22:52:35 -0700241 # Save parent job id to keyvals, so parser can retrieve the info and
242 # write to tko_jobs record.
243 if parent_job_id:
244 job_data['parent_job_id'] = parent_job_id
mbligh374f3412009-05-13 21:29:45 +0000245 if group_name:
246 job_data['host_group_name'] = group_name
jadmanski10646442008-08-13 14:05:21 +0000247
mbligh0d0f67d2009-11-06 03:15:03 +0000248 # only write these keyvals out on the first job in a resultdir
249 if 'job_started' not in utils.read_keyval(self.resultdir):
250 job_data.update(get_site_job_data(self))
251 utils.write_keyval(self.resultdir, job_data)
252
253 self._parse_job = parse_job
Alex Miller44ae9232014-06-20 17:24:25 -0700254 self._using_parser = (INCREMENTAL_TKO_PARSING and self._parse_job
255 and len(machines) <= 1)
mbligh0d0f67d2009-11-06 03:15:03 +0000256 self.pkgmgr = packages.PackageManager(
257 self.autodir, run_function_dargs={'timeout':600})
showard21baa452008-10-21 00:08:39 +0000258 self.num_tests_run = 0
259 self.num_tests_failed = 0
260
jadmanski550fdc22008-11-20 16:32:08 +0000261 self._register_subcommand_hooks()
262
mbligh0d0f67d2009-11-06 03:15:03 +0000263 # these components aren't usable on the server
264 self.bootloader = None
265 self.harness = None
266
jadmanski2a89dac2010-06-11 14:32:58 +0000267 # set up the status logger
jadmanski52053632010-06-11 21:08:10 +0000268 self._indenter = status_indenter()
jadmanski2a89dac2010-06-11 14:32:58 +0000269 self._logger = base_job.status_logger(
jadmanski52053632010-06-11 21:08:10 +0000270 self, self._indenter, 'status.log', 'status.log',
jadmanski2a89dac2010-06-11 14:32:58 +0000271 record_hook=server_job_record_hook(self))
272
Dan Shib03ea9d2013-08-15 17:13:27 -0700273 # Initialize a flag to indicate DUT failure during the test, e.g.,
274 # unexpected reboot.
275 self.failed_with_device_error = False
276
Dan Shi70647ca2015-07-16 22:52:35 -0700277 self.parent_job_id = parent_job_id
Simran Basi1bf60eb2015-12-01 16:39:29 -0800278 self.in_lab = in_lab
279 afe = frontend_wrappers.RetryingAFE(timeout_min=5, delay_sec=10)
280 self.machine_dict_list = []
281 for machine in self.machines:
282 host_attributes = host_attributes or {}
283 if self.in_lab:
284 host = afe.get_hosts(hostname=machine)[0]
285 host_attributes.update(host.attributes)
286 self.machine_dict_list.append(
287 {'hostname' : machine,
288 'host_attributes' : host_attributes})
Dan Shi70647ca2015-07-16 22:52:35 -0700289
mbligh0d0f67d2009-11-06 03:15:03 +0000290
291 @classmethod
292 def _find_base_directories(cls):
293 """
294 Determine locations of autodir, clientdir and serverdir. Assumes
295 that this file is located within serverdir and uses __file__ along
296 with relative paths to resolve the location.
297 """
298 serverdir = os.path.abspath(os.path.dirname(__file__))
299 autodir = os.path.normpath(os.path.join(serverdir, '..'))
300 clientdir = os.path.join(autodir, 'client')
301 return autodir, clientdir, serverdir
302
303
Scott Zawalski91493c82013-01-25 16:15:20 -0500304 def _find_resultdir(self, resultdir, *args, **dargs):
mbligh0d0f67d2009-11-06 03:15:03 +0000305 """
306 Determine the location of resultdir. For server jobs we expect one to
307 always be explicitly passed in to __init__, so just return that.
308 """
309 if resultdir:
310 return os.path.normpath(resultdir)
311 else:
312 return None
313
jadmanski550fdc22008-11-20 16:32:08 +0000314
jadmanski2a89dac2010-06-11 14:32:58 +0000315 def _get_status_logger(self):
316 """Return a reference to the status logger."""
317 return self._logger
318
319
jadmanskie432dd22009-01-30 15:04:51 +0000320 @staticmethod
321 def _load_control_file(path):
322 f = open(path)
323 try:
324 control_file = f.read()
325 finally:
326 f.close()
327 return re.sub('\r', '', control_file)
328
329
jadmanski550fdc22008-11-20 16:32:08 +0000330 def _register_subcommand_hooks(self):
mbligh2b92b862008-11-22 13:25:32 +0000331 """
332 Register some hooks into the subcommand modules that allow us
333 to properly clean up self.hosts created in forked subprocesses.
334 """
jadmanski550fdc22008-11-20 16:32:08 +0000335 def on_fork(cmd):
336 self._existing_hosts_on_fork = set(self.hosts)
337 def on_join(cmd):
338 new_hosts = self.hosts - self._existing_hosts_on_fork
339 for host in new_hosts:
340 host.close()
341 subcommand.subcommand.register_fork_hook(on_fork)
342 subcommand.subcommand.register_join_hook(on_join)
343
jadmanski10646442008-08-13 14:05:21 +0000344
mbligh4608b002010-01-05 18:22:35 +0000345 def init_parser(self):
mbligh2b92b862008-11-22 13:25:32 +0000346 """
mbligh4608b002010-01-05 18:22:35 +0000347 Start the continuous parsing of self.resultdir. This sets up
jadmanski10646442008-08-13 14:05:21 +0000348 the database connection and inserts the basic job object into
mbligh2b92b862008-11-22 13:25:32 +0000349 the database if necessary.
350 """
mbligh4608b002010-01-05 18:22:35 +0000351 if not self._using_parser:
352 return
jadmanski10646442008-08-13 14:05:21 +0000353 # redirect parser debugging to .parse.log
mbligh4608b002010-01-05 18:22:35 +0000354 parse_log = os.path.join(self.resultdir, '.parse.log')
jadmanski10646442008-08-13 14:05:21 +0000355 parse_log = open(parse_log, 'w', 0)
356 tko_utils.redirect_parser_debugging(parse_log)
357 # create a job model object and set up the db
358 self.results_db = tko_db.db(autocommit=True)
mbligh0d0f67d2009-11-06 03:15:03 +0000359 self.parser = status_lib.parser(self._STATUS_VERSION)
mbligh4608b002010-01-05 18:22:35 +0000360 self.job_model = self.parser.make_job(self.resultdir)
jadmanski10646442008-08-13 14:05:21 +0000361 self.parser.start(self.job_model)
362 # check if a job already exists in the db and insert it if
363 # it does not
mbligh0d0f67d2009-11-06 03:15:03 +0000364 job_idx = self.results_db.find_job(self._parse_job)
jadmanski10646442008-08-13 14:05:21 +0000365 if job_idx is None:
Dan Shi70647ca2015-07-16 22:52:35 -0700366 self.results_db.insert_job(self._parse_job, self.job_model,
367 self.parent_job_id)
jadmanski10646442008-08-13 14:05:21 +0000368 else:
mbligh2b92b862008-11-22 13:25:32 +0000369 machine_idx = self.results_db.lookup_machine(self.job_model.machine)
jadmanski10646442008-08-13 14:05:21 +0000370 self.job_model.index = job_idx
371 self.job_model.machine_idx = machine_idx
372
373
374 def cleanup_parser(self):
mbligh2b92b862008-11-22 13:25:32 +0000375 """
376 This should be called after the server job is finished
jadmanski10646442008-08-13 14:05:21 +0000377 to carry out any remaining cleanup (e.g. flushing any
mbligh2b92b862008-11-22 13:25:32 +0000378 remaining test results to the results db)
379 """
mbligh0d0f67d2009-11-06 03:15:03 +0000380 if not self._using_parser:
jadmanski10646442008-08-13 14:05:21 +0000381 return
382 final_tests = self.parser.end()
383 for test in final_tests:
384 self.__insert_test(test)
mbligh0d0f67d2009-11-06 03:15:03 +0000385 self._using_parser = False
jadmanski10646442008-08-13 14:05:21 +0000386
Aviv Keshetc5947fa2013-09-04 14:06:29 -0700387 # TODO crbug.com/285395 add a kwargs parameter.
388 def _make_namespace(self):
389 """Create a namespace dictionary to be passed along to control file.
390
391 Creates a namespace argument populated with standard values:
392 machines, job, ssh_user, ssh_port, ssh_pass, ssh_verbosity_flag,
393 and ssh_options.
394 """
Simran Basi1bf60eb2015-12-01 16:39:29 -0800395 namespace = {'machines' : self.machine_dict_list,
Aviv Keshetc5947fa2013-09-04 14:06:29 -0700396 'job' : self,
397 'ssh_user' : self._ssh_user,
398 'ssh_port' : self._ssh_port,
399 'ssh_pass' : self._ssh_pass,
400 'ssh_verbosity_flag' : self._ssh_verbosity_flag,
401 'ssh_options' : self._ssh_options}
402 return namespace
403
jadmanski10646442008-08-13 14:05:21 +0000404
J. Richard Barnettec2d99cf2015-11-18 12:46:15 -0800405 def cleanup(self, labels):
Fang Dengad78aca2014-10-02 18:15:46 -0700406 """Cleanup machines.
407
408 @param labels: Comma separated job labels, will be used to
409 determine special task actions.
410 """
411 if not self.machines:
412 raise error.AutoservError('No machines specified to cleanup')
413 if self.resultdir:
414 os.chdir(self.resultdir)
415
416 namespace = self._make_namespace()
417 namespace.update({'job_labels': labels, 'args': ''})
418 self._execute_code(CLEANUP_CONTROL_FILE, namespace, protect=False)
419
420
J. Richard Barnettec2d99cf2015-11-18 12:46:15 -0800421 def verify(self, labels):
Fang Dengad78aca2014-10-02 18:15:46 -0700422 """Verify machines are all ssh-able.
423
424 @param labels: Comma separated job labels, will be used to
425 determine special task actions.
426 """
jadmanski10646442008-08-13 14:05:21 +0000427 if not self.machines:
mbligh084bc172008-10-18 14:02:45 +0000428 raise error.AutoservError('No machines specified to verify')
mbligh0fce4112008-11-27 00:37:17 +0000429 if self.resultdir:
430 os.chdir(self.resultdir)
Fang Dengad78aca2014-10-02 18:15:46 -0700431
432 namespace = self._make_namespace()
433 namespace.update({'job_labels': labels, 'args': ''})
434 self._execute_code(VERIFY_CONTROL_FILE, namespace, protect=False)
jadmanski10646442008-08-13 14:05:21 +0000435
436
J. Richard Barnettec2d99cf2015-11-18 12:46:15 -0800437 def reset(self, labels):
Fang Dengad78aca2014-10-02 18:15:46 -0700438 """Reset machines by first cleanup then verify each machine.
439
440 @param labels: Comma separated job labels, will be used to
441 determine special task actions.
442 """
Dan Shi07e09af2013-04-12 09:31:29 -0700443 if not self.machines:
444 raise error.AutoservError('No machines specified to reset.')
445 if self.resultdir:
446 os.chdir(self.resultdir)
447
Fang Dengad78aca2014-10-02 18:15:46 -0700448 namespace = self._make_namespace()
449 namespace.update({'job_labels': labels, 'args': ''})
450 self._execute_code(RESET_CONTROL_FILE, namespace, protect=False)
Dan Shi07e09af2013-04-12 09:31:29 -0700451
452
J. Richard Barnettec2d99cf2015-11-18 12:46:15 -0800453 def repair(self, labels):
Fang Dengad78aca2014-10-02 18:15:46 -0700454 """Repair machines.
455
Fang Dengad78aca2014-10-02 18:15:46 -0700456 @param labels: Comma separated job labels, will be used to
457 determine special task actions.
458 """
jadmanski10646442008-08-13 14:05:21 +0000459 if not self.machines:
460 raise error.AutoservError('No machines specified to repair')
mbligh0fce4112008-11-27 00:37:17 +0000461 if self.resultdir:
462 os.chdir(self.resultdir)
Aviv Keshetc5947fa2013-09-04 14:06:29 -0700463
464 namespace = self._make_namespace()
J. Richard Barnettec2d99cf2015-11-18 12:46:15 -0800465 namespace.update({'job_labels': labels, 'args': ''})
mbligh0931b0a2009-04-08 17:44:48 +0000466 self._execute_code(REPAIR_CONTROL_FILE, namespace, protect=False)
jadmanski10646442008-08-13 14:05:21 +0000467
468
Alex Millercb79ba72013-05-29 14:43:00 -0700469 def provision(self, labels):
470 """
471 Provision all hosts to match |labels|.
472
473 @param labels: A comma seperated string of labels to provision the
474 host to.
475
476 """
Alex Millercb79ba72013-05-29 14:43:00 -0700477 control = self._load_control_file(PROVISION_CONTROL_FILE)
Alex Miller686fca82014-04-23 17:21:13 -0700478 self.run(control=control, job_labels=labels)
Alex Millercb79ba72013-05-29 14:43:00 -0700479
480
jadmanski10646442008-08-13 14:05:21 +0000481 def precheck(self):
482 """
483 perform any additional checks in derived classes.
484 """
485 pass
486
487
488 def enable_external_logging(self):
mbligh2b92b862008-11-22 13:25:32 +0000489 """
490 Start or restart external logging mechanism.
jadmanski10646442008-08-13 14:05:21 +0000491 """
492 pass
493
494
495 def disable_external_logging(self):
mbligh2b92b862008-11-22 13:25:32 +0000496 """
497 Pause or stop external logging mechanism.
jadmanski10646442008-08-13 14:05:21 +0000498 """
499 pass
500
501
502 def use_external_logging(self):
mbligh2b92b862008-11-22 13:25:32 +0000503 """
504 Return True if external logging should be used.
jadmanski10646442008-08-13 14:05:21 +0000505 """
506 return False
507
508
mbligh415dc212009-06-15 21:53:34 +0000509 def _make_parallel_wrapper(self, function, machines, log):
510 """Wrap function as appropriate for calling by parallel_simple."""
mbligh2b92b862008-11-22 13:25:32 +0000511 is_forking = not (len(machines) == 1 and self.machines == machines)
mbligh0d0f67d2009-11-06 03:15:03 +0000512 if self._parse_job and is_forking and log:
jadmanski10646442008-08-13 14:05:21 +0000513 def wrapper(machine):
Simran Basi1bf60eb2015-12-01 16:39:29 -0800514 hostname = server_utils.get_hostname_from_machine(machine)
515 self._parse_job += "/" + hostname
Alex Miller44ae9232014-06-20 17:24:25 -0700516 self._using_parser = INCREMENTAL_TKO_PARSING
jadmanski10646442008-08-13 14:05:21 +0000517 self.machines = [machine]
Simran Basi1bf60eb2015-12-01 16:39:29 -0800518 self.push_execution_context(hostname)
jadmanski609a5f42008-08-26 20:52:42 +0000519 os.chdir(self.resultdir)
Simran Basi1bf60eb2015-12-01 16:39:29 -0800520 utils.write_keyval(self.resultdir, {"hostname": hostname})
mbligh4608b002010-01-05 18:22:35 +0000521 self.init_parser()
jadmanski10646442008-08-13 14:05:21 +0000522 result = function(machine)
523 self.cleanup_parser()
524 return result
jadmanski4dd1a002008-09-05 20:27:30 +0000525 elif len(machines) > 1 and log:
jadmanski10646442008-08-13 14:05:21 +0000526 def wrapper(machine):
Simran Basi1bf60eb2015-12-01 16:39:29 -0800527 hostname = server_utils.get_hostname_from_machine(machine)
528 self.push_execution_context(hostname)
jadmanski609a5f42008-08-26 20:52:42 +0000529 os.chdir(self.resultdir)
Simran Basi1bf60eb2015-12-01 16:39:29 -0800530 machine_data = {'hostname' : hostname,
mbligh0d0f67d2009-11-06 03:15:03 +0000531 'status_version' : str(self._STATUS_VERSION)}
mbligh838d82d2009-03-11 17:14:31 +0000532 utils.write_keyval(self.resultdir, machine_data)
jadmanski10646442008-08-13 14:05:21 +0000533 result = function(machine)
534 return result
535 else:
536 wrapper = function
mbligh415dc212009-06-15 21:53:34 +0000537 return wrapper
538
539
540 def parallel_simple(self, function, machines, log=True, timeout=None,
541 return_results=False):
542 """
543 Run 'function' using parallel_simple, with an extra wrapper to handle
544 the necessary setup for continuous parsing, if possible. If continuous
545 parsing is already properly initialized then this should just work.
546
547 @param function: A callable to run in parallel given each machine.
548 @param machines: A list of machine names to be passed one per subcommand
549 invocation of function.
550 @param log: If True, output will be written to output in a subdirectory
551 named after each machine.
552 @param timeout: Seconds after which the function call should timeout.
553 @param return_results: If True instead of an AutoServError being raised
554 on any error a list of the results|exceptions from the function
555 called on each arg is returned. [default: False]
556
557 @raises error.AutotestError: If any of the functions failed.
558 """
559 wrapper = self._make_parallel_wrapper(function, machines, log)
560 return subcommand.parallel_simple(wrapper, machines,
561 log=log, timeout=timeout,
562 return_results=return_results)
563
564
565 def parallel_on_machines(self, function, machines, timeout=None):
566 """
showardcd5fac42009-07-06 20:19:43 +0000567 @param function: Called in parallel with one machine as its argument.
mbligh415dc212009-06-15 21:53:34 +0000568 @param machines: A list of machines to call function(machine) on.
569 @param timeout: Seconds after which the function call should timeout.
570
571 @returns A list of machines on which function(machine) returned
572 without raising an exception.
573 """
showardcd5fac42009-07-06 20:19:43 +0000574 results = self.parallel_simple(function, machines, timeout=timeout,
mbligh415dc212009-06-15 21:53:34 +0000575 return_results=True)
576 success_machines = []
577 for result, machine in itertools.izip(results, machines):
578 if not isinstance(result, Exception):
579 success_machines.append(machine)
580 return success_machines
jadmanski10646442008-08-13 14:05:21 +0000581
582
mbligh0d0f67d2009-11-06 03:15:03 +0000583 _USE_TEMP_DIR = object()
Fang Dengad78aca2014-10-02 18:15:46 -0700584 def run(self, install_before=False, install_after=False,
jadmanskie432dd22009-01-30 15:04:51 +0000585 collect_crashdumps=True, namespace={}, control=None,
beepscb6f1e22013-06-28 19:14:10 -0700586 control_file_dir=None, verify_job_repo_url=False,
Alex Millerca76bcc2014-04-18 18:47:28 -0700587 only_collect_crashinfo=False, skip_crash_collection=False,
Dan Shib669cbd2013-09-13 11:17:17 -0700588 job_labels='', use_packaging=True):
jadmanskifb9c0fa2009-04-29 17:39:16 +0000589 # for a normal job, make sure the uncollected logs file exists
590 # for a crashinfo-only run it should already exist, bail out otherwise
jadmanski648c39f2010-03-19 17:38:01 +0000591 created_uncollected_logs = False
Alex Miller45554f32013-08-13 16:48:29 -0700592 logging.info("I am PID %s", os.getpid())
mbligh0d0f67d2009-11-06 03:15:03 +0000593 if self.resultdir and not os.path.exists(self._uncollected_log_file):
jadmanskifb9c0fa2009-04-29 17:39:16 +0000594 if only_collect_crashinfo:
595 # if this is a crashinfo-only run, and there were no existing
596 # uncollected logs, just bail out early
597 logging.info("No existing uncollected logs, "
598 "skipping crashinfo collection")
599 return
600 else:
mbligh0d0f67d2009-11-06 03:15:03 +0000601 log_file = open(self._uncollected_log_file, "w")
jadmanskifb9c0fa2009-04-29 17:39:16 +0000602 pickle.dump([], log_file)
603 log_file.close()
jadmanski648c39f2010-03-19 17:38:01 +0000604 created_uncollected_logs = True
jadmanskifb9c0fa2009-04-29 17:39:16 +0000605
jadmanski10646442008-08-13 14:05:21 +0000606 # use a copy so changes don't affect the original dictionary
607 namespace = namespace.copy()
608 machines = self.machines
jadmanskie432dd22009-01-30 15:04:51 +0000609 if control is None:
jadmanski02a3ba22009-11-13 20:47:27 +0000610 if self.control is None:
611 control = ''
612 else:
613 control = self._load_control_file(self.control)
jadmanskie432dd22009-01-30 15:04:51 +0000614 if control_file_dir is None:
615 control_file_dir = self.resultdir
jadmanski10646442008-08-13 14:05:21 +0000616
617 self.aborted = False
Aviv Keshetc5947fa2013-09-04 14:06:29 -0700618 namespace.update(self._make_namespace())
Alex Millerca76bcc2014-04-18 18:47:28 -0700619 namespace.update({'args' : self.args,
620 'job_labels' : job_labels})
jadmanski10646442008-08-13 14:05:21 +0000621 test_start_time = int(time.time())
622
mbligh80e1eba2008-11-19 00:26:18 +0000623 if self.resultdir:
624 os.chdir(self.resultdir)
jadmanski779bd292009-03-19 17:33:33 +0000625 # touch status.log so that the parser knows a job is running here
jadmanski382303a2009-04-21 19:53:39 +0000626 open(self.get_status_log_path(), 'a').close()
mbligh80e1eba2008-11-19 00:26:18 +0000627 self.enable_external_logging()
jadmanskie432dd22009-01-30 15:04:51 +0000628
jadmanskicdd0c402008-09-19 21:21:31 +0000629 collect_crashinfo = True
mblighaebe3b62008-12-22 14:45:40 +0000630 temp_control_file_dir = None
jadmanski10646442008-08-13 14:05:21 +0000631 try:
showardcf8d4922009-10-14 16:08:39 +0000632 try:
633 if install_before and machines:
634 self._execute_code(INSTALL_CONTROL_FILE, namespace)
jadmanskie432dd22009-01-30 15:04:51 +0000635
showardcf8d4922009-10-14 16:08:39 +0000636 if only_collect_crashinfo:
637 return
638
beepscb6f1e22013-06-28 19:14:10 -0700639 # If the verify_job_repo_url option is set but we're unable
640 # to actually verify that the job_repo_url contains the autotest
641 # package, this job will fail.
642 if verify_job_repo_url:
643 self._execute_code(VERIFY_JOB_REPO_URL_CONTROL_FILE,
Dan Shicf4d2032015-03-12 15:04:21 -0700644 namespace)
beepscb6f1e22013-06-28 19:14:10 -0700645 else:
646 logging.warning('Not checking if job_repo_url contains '
647 'autotest packages on %s', machines)
648
jadmanskidef0c3c2009-03-25 20:07:10 +0000649 # determine the dir to write the control files to
650 cfd_specified = (control_file_dir
mbligh0d0f67d2009-11-06 03:15:03 +0000651 and control_file_dir is not self._USE_TEMP_DIR)
jadmanskidef0c3c2009-03-25 20:07:10 +0000652 if cfd_specified:
653 temp_control_file_dir = None
654 else:
655 temp_control_file_dir = tempfile.mkdtemp(
656 suffix='temp_control_file_dir')
657 control_file_dir = temp_control_file_dir
658 server_control_file = os.path.join(control_file_dir,
mblighe0cbc912010-03-11 18:03:07 +0000659 self._control_filename)
jadmanskidef0c3c2009-03-25 20:07:10 +0000660 client_control_file = os.path.join(control_file_dir,
661 CLIENT_CONTROL_FILENAME)
mbligh0d0f67d2009-11-06 03:15:03 +0000662 if self._client:
jadmanskidef0c3c2009-03-25 20:07:10 +0000663 namespace['control'] = control
664 utils.open_write_close(client_control_file, control)
mblighfeac0102009-04-28 18:31:12 +0000665 shutil.copyfile(CLIENT_WRAPPER_CONTROL_FILE,
666 server_control_file)
jadmanskidef0c3c2009-03-25 20:07:10 +0000667 else:
668 utils.open_write_close(server_control_file, control)
Dan Shicf4d2032015-03-12 15:04:21 -0700669
mbligh26f0d882009-06-22 18:30:01 +0000670 logging.info("Processing control file")
Dan Shib669cbd2013-09-13 11:17:17 -0700671 namespace['use_packaging'] = use_packaging
Dan Shicf4d2032015-03-12 15:04:21 -0700672 self._execute_code(server_control_file, namespace)
mbligh26f0d882009-06-22 18:30:01 +0000673 logging.info("Finished processing control file")
jadmanski10646442008-08-13 14:05:21 +0000674
Dan Shib03ea9d2013-08-15 17:13:27 -0700675 # If no device error occured, no need to collect crashinfo.
676 collect_crashinfo = self.failed_with_device_error
Eric Li6f27d4f2010-09-29 10:55:17 -0700677 except Exception, e:
showardcf8d4922009-10-14 16:08:39 +0000678 try:
679 logging.exception(
680 'Exception escaped control file, job aborting:')
Eric Li6f27d4f2010-09-29 10:55:17 -0700681 self.record('INFO', None, None, str(e),
682 {'job_abort_reason': str(e)})
showardcf8d4922009-10-14 16:08:39 +0000683 except:
684 pass # don't let logging exceptions here interfere
685 raise
jadmanski10646442008-08-13 14:05:21 +0000686 finally:
mblighaebe3b62008-12-22 14:45:40 +0000687 if temp_control_file_dir:
jadmanskie432dd22009-01-30 15:04:51 +0000688 # Clean up temp directory used for copies of the control files
mblighaebe3b62008-12-22 14:45:40 +0000689 try:
690 shutil.rmtree(temp_control_file_dir)
691 except Exception, e:
Ilja H. Friedel04be2bd2014-05-07 21:29:59 -0700692 logging.warning('Could not remove temp directory %s: %s',
mblighe7d9c602009-07-02 19:02:33 +0000693 temp_control_file_dir, e)
jadmanskie432dd22009-01-30 15:04:51 +0000694
jadmanskicdd0c402008-09-19 21:21:31 +0000695 if machines and (collect_crashdumps or collect_crashinfo):
jadmanski10646442008-08-13 14:05:21 +0000696 namespace['test_start_time'] = test_start_time
Christopher Wileyf594c5e2013-07-03 18:25:30 -0700697 if skip_crash_collection:
698 logging.info('Skipping crash dump/info collection '
699 'as requested.')
700 elif collect_crashinfo:
mbligh084bc172008-10-18 14:02:45 +0000701 # includes crashdumps
702 self._execute_code(CRASHINFO_CONTROL_FILE, namespace)
jadmanskicdd0c402008-09-19 21:21:31 +0000703 else:
mbligh084bc172008-10-18 14:02:45 +0000704 self._execute_code(CRASHDUMPS_CONTROL_FILE, namespace)
jadmanski10646442008-08-13 14:05:21 +0000705 self.disable_external_logging()
Chris Sosaf4d43ff2012-10-30 11:21:05 -0700706 if self._uncollected_log_file and created_uncollected_logs:
707 os.remove(self._uncollected_log_file)
jadmanski10646442008-08-13 14:05:21 +0000708 if install_after and machines:
mbligh084bc172008-10-18 14:02:45 +0000709 self._execute_code(INSTALL_CONTROL_FILE, namespace)
jadmanski10646442008-08-13 14:05:21 +0000710
711
712 def run_test(self, url, *args, **dargs):
mbligh2b92b862008-11-22 13:25:32 +0000713 """
714 Summon a test object and run it.
jadmanski10646442008-08-13 14:05:21 +0000715
716 tag
717 tag to add to testname
718 url
719 url of the test to run
720 """
Christopher Wiley8a91f232013-07-09 11:02:27 -0700721 if self._disable_sysinfo:
722 dargs['disable_sysinfo'] = True
723
mblighfc3da5b2010-01-06 18:37:22 +0000724 group, testname = self.pkgmgr.get_package_name(url, 'test')
725 testname, subdir, tag = self._build_tagged_test_name(testname, dargs)
726 outputdir = self._make_test_outputdir(subdir)
jadmanski10646442008-08-13 14:05:21 +0000727
728 def group_func():
729 try:
730 test.runtest(self, url, tag, args, dargs)
731 except error.TestBaseException, e:
732 self.record(e.exit_status, subdir, testname, str(e))
733 raise
734 except Exception, e:
735 info = str(e) + "\n" + traceback.format_exc()
736 self.record('FAIL', subdir, testname, info)
737 raise
738 else:
mbligh2b92b862008-11-22 13:25:32 +0000739 self.record('GOOD', subdir, testname, 'completed successfully')
jadmanskide292df2008-08-26 20:51:14 +0000740
741 result, exc_info = self._run_group(testname, subdir, group_func)
742 if exc_info and isinstance(exc_info[1], error.TestBaseException):
743 return False
744 elif exc_info:
745 raise exc_info[0], exc_info[1], exc_info[2]
746 else:
747 return True
jadmanski10646442008-08-13 14:05:21 +0000748
749
750 def _run_group(self, name, subdir, function, *args, **dargs):
751 """\
752 Underlying method for running something inside of a group.
753 """
jadmanskide292df2008-08-26 20:51:14 +0000754 result, exc_info = None, None
jadmanski10646442008-08-13 14:05:21 +0000755 try:
756 self.record('START', subdir, name)
jadmanski52053632010-06-11 21:08:10 +0000757 result = function(*args, **dargs)
jadmanski10646442008-08-13 14:05:21 +0000758 except error.TestBaseException, e:
jadmanskib88d6dc2009-01-10 00:33:18 +0000759 self.record("END %s" % e.exit_status, subdir, name)
jadmanskide292df2008-08-26 20:51:14 +0000760 exc_info = sys.exc_info()
jadmanski10646442008-08-13 14:05:21 +0000761 except Exception, e:
762 err_msg = str(e) + '\n'
763 err_msg += traceback.format_exc()
764 self.record('END ABORT', subdir, name, err_msg)
765 raise error.JobError(name + ' failed\n' + traceback.format_exc())
766 else:
767 self.record('END GOOD', subdir, name)
768
jadmanskide292df2008-08-26 20:51:14 +0000769 return result, exc_info
jadmanski10646442008-08-13 14:05:21 +0000770
771
772 def run_group(self, function, *args, **dargs):
773 """\
774 function:
775 subroutine to run
776 *args:
777 arguments for the function
778 """
779
780 name = function.__name__
781
782 # Allow the tag for the group to be specified.
783 tag = dargs.pop('tag', None)
784 if tag:
785 name = tag
786
jadmanskide292df2008-08-26 20:51:14 +0000787 return self._run_group(name, None, function, *args, **dargs)[0]
jadmanski10646442008-08-13 14:05:21 +0000788
789
Gwendal Grignou7a61d2f2014-05-23 11:05:51 -0700790 def run_op(self, op, op_func, get_kernel_func):
jadmanski10646442008-08-13 14:05:21 +0000791 """\
792 A specialization of run_group meant specifically for handling
Gwendal Grignou7a61d2f2014-05-23 11:05:51 -0700793 management operation. Includes support for capturing the kernel version
794 after the operation.
jadmanski10646442008-08-13 14:05:21 +0000795
Gwendal Grignou7a61d2f2014-05-23 11:05:51 -0700796 Args:
797 op: name of the operation.
798 op_func: a function that carries out the operation (reboot, suspend)
799 get_kernel_func: a function that returns a string
800 representing the kernel version.
jadmanski10646442008-08-13 14:05:21 +0000801 """
jadmanski10646442008-08-13 14:05:21 +0000802 try:
Gwendal Grignou7a61d2f2014-05-23 11:05:51 -0700803 self.record('START', None, op)
804 op_func()
jadmanski10646442008-08-13 14:05:21 +0000805 except Exception, e:
jadmanski10646442008-08-13 14:05:21 +0000806 err_msg = str(e) + '\n' + traceback.format_exc()
Gwendal Grignou7a61d2f2014-05-23 11:05:51 -0700807 self.record('END FAIL', None, op, err_msg)
jadmanski4b51d542009-04-08 14:17:16 +0000808 raise
jadmanski10646442008-08-13 14:05:21 +0000809 else:
810 kernel = get_kernel_func()
Gwendal Grignou7a61d2f2014-05-23 11:05:51 -0700811 self.record('END GOOD', None, op,
Dale Curtis74a314b2011-06-23 14:55:46 -0700812 optional_fields={"kernel": kernel})
jadmanski10646442008-08-13 14:05:21 +0000813
814
jadmanskie432dd22009-01-30 15:04:51 +0000815 def run_control(self, path):
816 """Execute a control file found at path (relative to the autotest
817 path). Intended for executing a control file within a control file,
818 not for running the top-level job control file."""
819 path = os.path.join(self.autodir, path)
820 control_file = self._load_control_file(path)
mbligh0d0f67d2009-11-06 03:15:03 +0000821 self.run(control=control_file, control_file_dir=self._USE_TEMP_DIR)
jadmanskie432dd22009-01-30 15:04:51 +0000822
823
jadmanskic09fc152008-10-15 17:56:59 +0000824 def add_sysinfo_command(self, command, logfile=None, on_every_test=False):
mbligh4395bbd2009-03-25 19:34:17 +0000825 self._add_sysinfo_loggable(sysinfo.command(command, logf=logfile),
jadmanskic09fc152008-10-15 17:56:59 +0000826 on_every_test)
827
828
829 def add_sysinfo_logfile(self, file, on_every_test=False):
830 self._add_sysinfo_loggable(sysinfo.logfile(file), on_every_test)
831
832
833 def _add_sysinfo_loggable(self, loggable, on_every_test):
834 if on_every_test:
835 self.sysinfo.test_loggables.add(loggable)
836 else:
837 self.sysinfo.boot_loggables.add(loggable)
838
839
jadmanski10646442008-08-13 14:05:21 +0000840 def _read_warnings(self):
jadmanskif37df842009-02-11 00:03:26 +0000841 """Poll all the warning loggers and extract any new warnings that have
842 been logged. If the warnings belong to a category that is currently
843 disabled, this method will discard them and they will no longer be
844 retrievable.
845
846 Returns a list of (timestamp, message) tuples, where timestamp is an
847 integer epoch timestamp."""
jadmanski10646442008-08-13 14:05:21 +0000848 warnings = []
849 while True:
850 # pull in a line of output from every logger that has
851 # output ready to be read
mbligh2b92b862008-11-22 13:25:32 +0000852 loggers, _, _ = select.select(self.warning_loggers, [], [], 0)
jadmanski10646442008-08-13 14:05:21 +0000853 closed_loggers = set()
854 for logger in loggers:
855 line = logger.readline()
856 # record any broken pipes (aka line == empty)
857 if len(line) == 0:
858 closed_loggers.add(logger)
859 continue
jadmanskif37df842009-02-11 00:03:26 +0000860 # parse out the warning
861 timestamp, msgtype, msg = line.split('\t', 2)
862 timestamp = int(timestamp)
863 # if the warning is valid, add it to the results
864 if self.warning_manager.is_valid(timestamp, msgtype):
865 warnings.append((timestamp, msg.strip()))
jadmanski10646442008-08-13 14:05:21 +0000866
867 # stop listening to loggers that are closed
868 self.warning_loggers -= closed_loggers
869
870 # stop if none of the loggers have any output left
871 if not loggers:
872 break
873
874 # sort into timestamp order
875 warnings.sort()
876 return warnings
877
878
showardcc929362010-01-25 21:20:41 +0000879 def _unique_subdirectory(self, base_subdirectory_name):
880 """Compute a unique results subdirectory based on the given name.
881
882 Appends base_subdirectory_name with a number as necessary to find a
883 directory name that doesn't already exist.
884 """
885 subdirectory = base_subdirectory_name
886 counter = 1
887 while os.path.exists(os.path.join(self.resultdir, subdirectory)):
888 subdirectory = base_subdirectory_name + '.' + str(counter)
889 counter += 1
890 return subdirectory
891
892
jadmanski52053632010-06-11 21:08:10 +0000893 def get_record_context(self):
894 """Returns an object representing the current job.record context.
895
896 The object returned is an opaque object with a 0-arg restore method
897 which can be called to restore the job.record context (i.e. indentation)
898 to the current level. The intention is that it should be used when
899 something external which generate job.record calls (e.g. an autotest
900 client) can fail catastrophically and the server job record state
901 needs to be reset to its original "known good" state.
902
903 @return: A context object with a 0-arg restore() method."""
904 return self._indenter.get_context()
905
906
showardcc929362010-01-25 21:20:41 +0000907 def record_summary(self, status_code, test_name, reason='', attributes=None,
908 distinguishing_attributes=(), child_test_ids=None):
909 """Record a summary test result.
910
911 @param status_code: status code string, see
912 common_lib.log.is_valid_status()
913 @param test_name: name of the test
914 @param reason: (optional) string providing detailed reason for test
915 outcome
916 @param attributes: (optional) dict of string keyvals to associate with
917 this result
918 @param distinguishing_attributes: (optional) list of attribute names
919 that should be used to distinguish identically-named test
920 results. These attributes should be present in the attributes
921 parameter. This is used to generate user-friendly subdirectory
922 names.
923 @param child_test_ids: (optional) list of test indices for test results
924 used in generating this result.
925 """
926 subdirectory_name_parts = [test_name]
927 for attribute in distinguishing_attributes:
928 assert attributes
929 assert attribute in attributes, '%s not in %s' % (attribute,
930 attributes)
931 subdirectory_name_parts.append(attributes[attribute])
932 base_subdirectory_name = '.'.join(subdirectory_name_parts)
933
934 subdirectory = self._unique_subdirectory(base_subdirectory_name)
935 subdirectory_path = os.path.join(self.resultdir, subdirectory)
936 os.mkdir(subdirectory_path)
937
938 self.record(status_code, subdirectory, test_name,
939 status=reason, optional_fields={'is_summary': True})
940
941 if attributes:
942 utils.write_keyval(subdirectory_path, attributes)
943
944 if child_test_ids:
945 ids_string = ','.join(str(test_id) for test_id in child_test_ids)
946 summary_data = {'child_test_ids': ids_string}
947 utils.write_keyval(os.path.join(subdirectory_path, 'summary_data'),
948 summary_data)
949
950
jadmanski16a7ff72009-04-01 18:19:53 +0000951 def disable_warnings(self, warning_type):
jadmanskif37df842009-02-11 00:03:26 +0000952 self.warning_manager.disable_warnings(warning_type)
jadmanski16a7ff72009-04-01 18:19:53 +0000953 self.record("INFO", None, None,
954 "disabling %s warnings" % warning_type,
955 {"warnings.disable": warning_type})
jadmanskif37df842009-02-11 00:03:26 +0000956
957
jadmanski16a7ff72009-04-01 18:19:53 +0000958 def enable_warnings(self, warning_type):
jadmanskif37df842009-02-11 00:03:26 +0000959 self.warning_manager.enable_warnings(warning_type)
jadmanski16a7ff72009-04-01 18:19:53 +0000960 self.record("INFO", None, None,
961 "enabling %s warnings" % warning_type,
962 {"warnings.enable": warning_type})
jadmanskif37df842009-02-11 00:03:26 +0000963
964
jadmanski779bd292009-03-19 17:33:33 +0000965 def get_status_log_path(self, subdir=None):
966 """Return the path to the job status log.
967
968 @param subdir - Optional paramter indicating that you want the path
969 to a subdirectory status log.
970
971 @returns The path where the status log should be.
972 """
mbligh210bae62009-04-01 18:33:13 +0000973 if self.resultdir:
974 if subdir:
975 return os.path.join(self.resultdir, subdir, "status.log")
976 else:
977 return os.path.join(self.resultdir, "status.log")
jadmanski779bd292009-03-19 17:33:33 +0000978 else:
mbligh210bae62009-04-01 18:33:13 +0000979 return None
jadmanski779bd292009-03-19 17:33:33 +0000980
981
jadmanski6bb32d72009-03-19 20:25:24 +0000982 def _update_uncollected_logs_list(self, update_func):
983 """Updates the uncollected logs list in a multi-process safe manner.
984
985 @param update_func - a function that updates the list of uncollected
986 logs. Should take one parameter, the list to be updated.
987 """
Dan Shi07e09af2013-04-12 09:31:29 -0700988 # Skip log collection if file _uncollected_log_file does not exist.
989 if not (self._uncollected_log_file and
990 os.path.exists(self._uncollected_log_file)):
991 return
mbligh0d0f67d2009-11-06 03:15:03 +0000992 if self._uncollected_log_file:
993 log_file = open(self._uncollected_log_file, "r+")
mbligha788dc42009-03-26 21:10:16 +0000994 fcntl.flock(log_file, fcntl.LOCK_EX)
jadmanski6bb32d72009-03-19 20:25:24 +0000995 try:
996 uncollected_logs = pickle.load(log_file)
997 update_func(uncollected_logs)
998 log_file.seek(0)
999 log_file.truncate()
1000 pickle.dump(uncollected_logs, log_file)
jadmanski3bff9092009-04-22 18:09:47 +00001001 log_file.flush()
jadmanski6bb32d72009-03-19 20:25:24 +00001002 finally:
1003 fcntl.flock(log_file, fcntl.LOCK_UN)
1004 log_file.close()
1005
1006
1007 def add_client_log(self, hostname, remote_path, local_path):
1008 """Adds a new set of client logs to the list of uncollected logs,
1009 to allow for future log recovery.
1010
1011 @param host - the hostname of the machine holding the logs
1012 @param remote_path - the directory on the remote machine holding logs
1013 @param local_path - the local directory to copy the logs into
1014 """
1015 def update_func(logs_list):
1016 logs_list.append((hostname, remote_path, local_path))
1017 self._update_uncollected_logs_list(update_func)
1018
1019
1020 def remove_client_log(self, hostname, remote_path, local_path):
1021 """Removes a set of client logs from the list of uncollected logs,
1022 to allow for future log recovery.
1023
1024 @param host - the hostname of the machine holding the logs
1025 @param remote_path - the directory on the remote machine holding logs
1026 @param local_path - the local directory to copy the logs into
1027 """
1028 def update_func(logs_list):
1029 logs_list.remove((hostname, remote_path, local_path))
1030 self._update_uncollected_logs_list(update_func)
1031
1032
mbligh0d0f67d2009-11-06 03:15:03 +00001033 def get_client_logs(self):
1034 """Retrieves the list of uncollected logs, if it exists.
1035
1036 @returns A list of (host, remote_path, local_path) tuples. Returns
1037 an empty list if no uncollected logs file exists.
1038 """
1039 log_exists = (self._uncollected_log_file and
1040 os.path.exists(self._uncollected_log_file))
1041 if log_exists:
1042 return pickle.load(open(self._uncollected_log_file))
1043 else:
1044 return []
1045
1046
mbligh084bc172008-10-18 14:02:45 +00001047 def _fill_server_control_namespace(self, namespace, protect=True):
mbligh2b92b862008-11-22 13:25:32 +00001048 """
1049 Prepare a namespace to be used when executing server control files.
mbligh084bc172008-10-18 14:02:45 +00001050
1051 This sets up the control file API by importing modules and making them
1052 available under the appropriate names within namespace.
1053
1054 For use by _execute_code().
1055
1056 Args:
1057 namespace: The namespace dictionary to fill in.
1058 protect: Boolean. If True (the default) any operation that would
1059 clobber an existing entry in namespace will cause an error.
1060 Raises:
1061 error.AutoservError: When a name would be clobbered by import.
1062 """
1063 def _import_names(module_name, names=()):
mbligh2b92b862008-11-22 13:25:32 +00001064 """
1065 Import a module and assign named attributes into namespace.
mbligh084bc172008-10-18 14:02:45 +00001066
1067 Args:
1068 module_name: The string module name.
1069 names: A limiting list of names to import from module_name. If
1070 empty (the default), all names are imported from the module
1071 similar to a "from foo.bar import *" statement.
1072 Raises:
1073 error.AutoservError: When a name being imported would clobber
1074 a name already in namespace.
1075 """
1076 module = __import__(module_name, {}, {}, names)
1077
1078 # No names supplied? Import * from the lowest level module.
1079 # (Ugh, why do I have to implement this part myself?)
1080 if not names:
1081 for submodule_name in module_name.split('.')[1:]:
1082 module = getattr(module, submodule_name)
1083 if hasattr(module, '__all__'):
1084 names = getattr(module, '__all__')
1085 else:
1086 names = dir(module)
1087
1088 # Install each name into namespace, checking to make sure it
1089 # doesn't override anything that already exists.
1090 for name in names:
1091 # Check for conflicts to help prevent future problems.
1092 if name in namespace and protect:
1093 if namespace[name] is not getattr(module, name):
1094 raise error.AutoservError('importing name '
1095 '%s from %s %r would override %r' %
1096 (name, module_name, getattr(module, name),
1097 namespace[name]))
1098 else:
1099 # Encourage cleanliness and the use of __all__ for a
1100 # more concrete API with less surprises on '*' imports.
1101 warnings.warn('%s (%r) being imported from %s for use '
1102 'in server control files is not the '
1103 'first occurrance of that import.' %
1104 (name, namespace[name], module_name))
1105
1106 namespace[name] = getattr(module, name)
1107
1108
1109 # This is the equivalent of prepending a bunch of import statements to
1110 # the front of the control script.
mbligha2b07dd2009-06-22 18:26:13 +00001111 namespace.update(os=os, sys=sys, logging=logging)
mbligh084bc172008-10-18 14:02:45 +00001112 _import_names('autotest_lib.server',
1113 ('hosts', 'autotest', 'kvm', 'git', 'standalone_profiler',
1114 'source_kernel', 'rpm_kernel', 'deb_kernel', 'git_kernel'))
1115 _import_names('autotest_lib.server.subcommand',
1116 ('parallel', 'parallel_simple', 'subcommand'))
1117 _import_names('autotest_lib.server.utils',
1118 ('run', 'get_tmp_dir', 'sh_escape', 'parse_machine'))
1119 _import_names('autotest_lib.client.common_lib.error')
1120 _import_names('autotest_lib.client.common_lib.barrier', ('barrier',))
1121
1122 # Inject ourself as the job object into other classes within the API.
1123 # (Yuck, this injection is a gross thing be part of a public API. -gps)
1124 #
1125 # XXX Base & SiteAutotest do not appear to use .job. Who does?
1126 namespace['autotest'].Autotest.job = self
1127 # server.hosts.base_classes.Host uses .job.
1128 namespace['hosts'].Host.job = self
Eric Li10222b82010-11-24 09:33:15 -08001129 namespace['hosts'].factory.ssh_user = self._ssh_user
1130 namespace['hosts'].factory.ssh_port = self._ssh_port
1131 namespace['hosts'].factory.ssh_pass = self._ssh_pass
Fang Dengd1c2b732013-08-20 12:59:46 -07001132 namespace['hosts'].factory.ssh_verbosity_flag = (
1133 self._ssh_verbosity_flag)
Aviv Keshetc5947fa2013-09-04 14:06:29 -07001134 namespace['hosts'].factory.ssh_options = self._ssh_options
mbligh084bc172008-10-18 14:02:45 +00001135
1136
1137 def _execute_code(self, code_file, namespace, protect=True):
mbligh2b92b862008-11-22 13:25:32 +00001138 """
1139 Execute code using a copy of namespace as a server control script.
mbligh084bc172008-10-18 14:02:45 +00001140
1141 Unless protect_namespace is explicitly set to False, the dict will not
1142 be modified.
1143
1144 Args:
1145 code_file: The filename of the control file to execute.
1146 namespace: A dict containing names to make available during execution.
1147 protect: Boolean. If True (the default) a copy of the namespace dict
1148 is used during execution to prevent the code from modifying its
1149 contents outside of this function. If False the raw dict is
1150 passed in and modifications will be allowed.
1151 """
1152 if protect:
1153 namespace = namespace.copy()
1154 self._fill_server_control_namespace(namespace, protect=protect)
1155 # TODO: Simplify and get rid of the special cases for only 1 machine.
showard3e66e8c2008-10-27 19:20:51 +00001156 if len(self.machines) > 1:
mbligh084bc172008-10-18 14:02:45 +00001157 machines_text = '\n'.join(self.machines) + '\n'
1158 # Only rewrite the file if it does not match our machine list.
1159 try:
1160 machines_f = open(MACHINES_FILENAME, 'r')
1161 existing_machines_text = machines_f.read()
1162 machines_f.close()
1163 except EnvironmentError:
1164 existing_machines_text = None
1165 if machines_text != existing_machines_text:
1166 utils.open_write_close(MACHINES_FILENAME, machines_text)
1167 execfile(code_file, namespace, namespace)
jadmanski10646442008-08-13 14:05:21 +00001168
1169
jadmanskie29d0e42010-06-17 16:06:52 +00001170 def _parse_status(self, new_line):
mbligh0d0f67d2009-11-06 03:15:03 +00001171 if not self._using_parser:
jadmanski10646442008-08-13 14:05:21 +00001172 return
jadmanskie29d0e42010-06-17 16:06:52 +00001173 new_tests = self.parser.process_lines([new_line])
jadmanski10646442008-08-13 14:05:21 +00001174 for test in new_tests:
1175 self.__insert_test(test)
1176
1177
1178 def __insert_test(self, test):
mbligh2b92b862008-11-22 13:25:32 +00001179 """
1180 An internal method to insert a new test result into the
jadmanski10646442008-08-13 14:05:21 +00001181 database. This method will not raise an exception, even if an
1182 error occurs during the insert, to avoid failing a test
1183 simply because of unexpected database issues."""
showard21baa452008-10-21 00:08:39 +00001184 self.num_tests_run += 1
1185 if status_lib.is_worse_than_or_equal_to(test.status, 'FAIL'):
1186 self.num_tests_failed += 1
jadmanski10646442008-08-13 14:05:21 +00001187 try:
1188 self.results_db.insert_test(self.job_model, test)
1189 except Exception:
1190 msg = ("WARNING: An unexpected error occured while "
1191 "inserting test results into the database. "
1192 "Ignoring error.\n" + traceback.format_exc())
1193 print >> sys.stderr, msg
1194
mblighcaa62c22008-04-07 21:51:17 +00001195
mblighfc3da5b2010-01-06 18:37:22 +00001196 def preprocess_client_state(self):
1197 """
1198 Produce a state file for initializing the state of a client job.
1199
1200 Creates a new client state file with all the current server state, as
1201 well as some pre-set client state.
1202
1203 @returns The path of the file the state was written into.
1204 """
1205 # initialize the sysinfo state
1206 self._state.set('client', 'sysinfo', self.sysinfo.serialize())
1207
1208 # dump the state out to a tempfile
1209 fd, file_path = tempfile.mkstemp(dir=self.tmpdir)
1210 os.close(fd)
mbligha2c99492010-01-27 22:59:50 +00001211
1212 # write_to_file doesn't need locking, we exclusively own file_path
mblighfc3da5b2010-01-06 18:37:22 +00001213 self._state.write_to_file(file_path)
1214 return file_path
1215
1216
1217 def postprocess_client_state(self, state_path):
1218 """
1219 Update the state of this job with the state from a client job.
1220
1221 Updates the state of the server side of a job with the final state
1222 of a client job that was run. Updates the non-client-specific state,
1223 pulls in some specific bits from the client-specific state, and then
1224 discards the rest. Removes the state file afterwards
1225
1226 @param state_file A path to the state file from the client.
1227 """
1228 # update the on-disk state
mblighfc3da5b2010-01-06 18:37:22 +00001229 try:
jadmanskib6e7bdb2010-04-13 16:00:39 +00001230 self._state.read_from_file(state_path)
mblighfc3da5b2010-01-06 18:37:22 +00001231 os.remove(state_path)
mbligha2c99492010-01-27 22:59:50 +00001232 except OSError, e:
mblighfc3da5b2010-01-06 18:37:22 +00001233 # ignore file-not-found errors
1234 if e.errno != errno.ENOENT:
1235 raise
jadmanskib6e7bdb2010-04-13 16:00:39 +00001236 else:
1237 logging.debug('Client state file %s not found', state_path)
mblighfc3da5b2010-01-06 18:37:22 +00001238
1239 # update the sysinfo state
1240 if self._state.has('client', 'sysinfo'):
1241 self.sysinfo.deserialize(self._state.get('client', 'sysinfo'))
1242
1243 # drop all the client-specific state
1244 self._state.discard_namespace('client')
1245
1246
mbligh0a883702010-04-21 01:58:34 +00001247 def clear_all_known_hosts(self):
1248 """Clears known hosts files for all AbstractSSHHosts."""
1249 for host in self.hosts:
1250 if isinstance(host, abstract_ssh.AbstractSSHHost):
1251 host.clear_known_hosts()
1252
1253
jadmanskif37df842009-02-11 00:03:26 +00001254class warning_manager(object):
1255 """Class for controlling warning logs. Manages the enabling and disabling
1256 of warnings."""
1257 def __init__(self):
1258 # a map of warning types to a list of disabled time intervals
1259 self.disabled_warnings = {}
1260
1261
1262 def is_valid(self, timestamp, warning_type):
1263 """Indicates if a warning (based on the time it occured and its type)
1264 is a valid warning. A warning is considered "invalid" if this type of
1265 warning was marked as "disabled" at the time the warning occured."""
1266 disabled_intervals = self.disabled_warnings.get(warning_type, [])
1267 for start, end in disabled_intervals:
1268 if timestamp >= start and (end is None or timestamp < end):
1269 return False
1270 return True
1271
1272
1273 def disable_warnings(self, warning_type, current_time_func=time.time):
1274 """As of now, disables all further warnings of this type."""
1275 intervals = self.disabled_warnings.setdefault(warning_type, [])
1276 if not intervals or intervals[-1][1] is not None:
jadmanski16a7ff72009-04-01 18:19:53 +00001277 intervals.append((int(current_time_func()), None))
jadmanskif37df842009-02-11 00:03:26 +00001278
1279
1280 def enable_warnings(self, warning_type, current_time_func=time.time):
1281 """As of now, enables all further warnings of this type."""
1282 intervals = self.disabled_warnings.get(warning_type, [])
1283 if intervals and intervals[-1][1] is None:
jadmanski16a7ff72009-04-01 18:19:53 +00001284 intervals[-1] = (intervals[-1][0], int(current_time_func()))
Paul Pendlebury57593562011-06-15 10:45:49 -07001285
1286
1287# load up site-specific code for generating site-specific job data
1288get_site_job_data = utils.import_site_function(__file__,
1289 "autotest_lib.server.site_server_job", "get_site_job_data",
1290 _get_site_job_data_dummy)
1291
1292
1293site_server_job = utils.import_site_class(
1294 __file__, "autotest_lib.server.site_server_job", "site_server_job",
1295 base_server_job)
1296
1297
1298class server_job(site_server_job):
Dale Curtis456d3c12011-07-19 11:42:51 -07001299 pass