blob: 1936b01d703ec6b51f45706d203406dfb496be39 [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
mbligh0d0f67d2009-11-06 03:15:03 +000017from autotest_lib.client.common_lib import base_job
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
mbligh084bc172008-10-18 14:02:45 +000025def _control_segment_path(name):
26 """Get the pathname of the named control segment file."""
jadmanski10646442008-08-13 14:05:21 +000027 server_dir = os.path.dirname(os.path.abspath(__file__))
mbligh084bc172008-10-18 14:02:45 +000028 return os.path.join(server_dir, "control_segments", name)
jadmanski10646442008-08-13 14:05:21 +000029
30
mbligh084bc172008-10-18 14:02:45 +000031CLIENT_CONTROL_FILENAME = 'control'
32SERVER_CONTROL_FILENAME = 'control.srv'
33MACHINES_FILENAME = '.machines'
jadmanski10646442008-08-13 14:05:21 +000034
mbligh084bc172008-10-18 14:02:45 +000035CLIENT_WRAPPER_CONTROL_FILE = _control_segment_path('client_wrapper')
36CRASHDUMPS_CONTROL_FILE = _control_segment_path('crashdumps')
37CRASHINFO_CONTROL_FILE = _control_segment_path('crashinfo')
mbligh084bc172008-10-18 14:02:45 +000038INSTALL_CONTROL_FILE = _control_segment_path('install')
showard45ae8192008-11-05 19:32:53 +000039CLEANUP_CONTROL_FILE = _control_segment_path('cleanup')
mbligh084bc172008-10-18 14:02:45 +000040VERIFY_CONTROL_FILE = _control_segment_path('verify')
mbligh084bc172008-10-18 14:02:45 +000041REPAIR_CONTROL_FILE = _control_segment_path('repair')
Alex Millercb79ba72013-05-29 14:43:00 -070042PROVISION_CONTROL_FILE = _control_segment_path('provision')
beepscb6f1e22013-06-28 19:14:10 -070043VERIFY_JOB_REPO_URL_CONTROL_FILE = _control_segment_path('verify_job_repo_url')
Dan Shi07e09af2013-04-12 09:31:29 -070044RESET_CONTROL_FILE = _control_segment_path('reset')
jadmanski10646442008-08-13 14:05:21 +000045
mbligh062ed152009-01-13 00:57:14 +000046# by default provide a stub that generates no site data
47def _get_site_job_data_dummy(job):
48 return {}
49
50
jadmanski2a89dac2010-06-11 14:32:58 +000051class status_indenter(base_job.status_indenter):
52 """Provide a simple integer-backed status indenter."""
53 def __init__(self):
54 self._indent = 0
55
56
57 @property
58 def indent(self):
59 return self._indent
60
61
62 def increment(self):
63 self._indent += 1
64
65
66 def decrement(self):
67 self._indent -= 1
68
69
jadmanski52053632010-06-11 21:08:10 +000070 def get_context(self):
71 """Returns a context object for use by job.get_record_context."""
72 class context(object):
73 def __init__(self, indenter, indent):
74 self._indenter = indenter
75 self._indent = indent
76 def restore(self):
77 self._indenter._indent = self._indent
78 return context(self, self._indent)
79
80
jadmanski2a89dac2010-06-11 14:32:58 +000081class server_job_record_hook(object):
82 """The job.record hook for server job. Used to inject WARN messages from
83 the console or vlm whenever new logs are written, and to echo any logs
84 to INFO level logging. Implemented as a class so that it can use state to
85 block recursive calls, so that the hook can call job.record itself to
86 log WARN messages.
87
88 Depends on job._read_warnings and job._logger.
89 """
90 def __init__(self, job):
91 self._job = job
92 self._being_called = False
93
94
95 def __call__(self, entry):
96 """A wrapper around the 'real' record hook, the _hook method, which
97 prevents recursion. This isn't making any effort to be threadsafe,
98 the intent is to outright block infinite recursion via a
99 job.record->_hook->job.record->_hook->job.record... chain."""
100 if self._being_called:
101 return
102 self._being_called = True
103 try:
104 self._hook(self._job, entry)
105 finally:
106 self._being_called = False
107
108
109 @staticmethod
110 def _hook(job, entry):
111 """The core hook, which can safely call job.record."""
112 entries = []
113 # poll all our warning loggers for new warnings
114 for timestamp, msg in job._read_warnings():
115 warning_entry = base_job.status_log_entry(
116 'WARN', None, None, msg, {}, timestamp=timestamp)
117 entries.append(warning_entry)
118 job.record_entry(warning_entry)
119 # echo rendered versions of all the status logs to info
120 entries.append(entry)
121 for entry in entries:
122 rendered_entry = job._logger.render_entry(entry)
123 logging.info(rendered_entry)
jadmanskie29d0e42010-06-17 16:06:52 +0000124 job._parse_status(rendered_entry)
jadmanski2a89dac2010-06-11 14:32:58 +0000125
126
mbligh0d0f67d2009-11-06 03:15:03 +0000127class base_server_job(base_job.base_job):
128 """The server-side concrete implementation of base_job.
jadmanski10646442008-08-13 14:05:21 +0000129
mbligh0d0f67d2009-11-06 03:15:03 +0000130 Optional properties provided by this implementation:
131 serverdir
132 conmuxdir
133
134 num_tests_run
135 num_tests_failed
136
137 warning_manager
138 warning_loggers
jadmanski10646442008-08-13 14:05:21 +0000139 """
140
mbligh0d0f67d2009-11-06 03:15:03 +0000141 _STATUS_VERSION = 1
jadmanski10646442008-08-13 14:05:21 +0000142
Aviv Keshetc5947fa2013-09-04 14:06:29 -0700143 # TODO crbug.com/285395 eliminate ssh_verbosity_flag
jadmanski10646442008-08-13 14:05:21 +0000144 def __init__(self, control, args, resultdir, label, user, machines,
145 client=False, parse_job='',
beepsd0672682013-09-16 17:32:16 -0700146 ssh_user=host_factory.DEFAULT_SSH_USER,
147 ssh_port=host_factory.DEFAULT_SSH_PORT,
148 ssh_pass=host_factory.DEFAULT_SSH_PASS,
149 ssh_verbosity_flag=host_factory.DEFAULT_SSH_VERBOSITY,
150 ssh_options=host_factory.DEFAULT_SSH_OPTIONS,
Aviv Keshetc5947fa2013-09-04 14:06:29 -0700151 test_retry=0, group_name='',
Fang Dengd1c2b732013-08-20 12:59:46 -0700152 tag='', disable_sysinfo=False,
mblighe0cbc912010-03-11 18:03:07 +0000153 control_filename=SERVER_CONTROL_FILENAME):
jadmanski10646442008-08-13 14:05:21 +0000154 """
mbligh374f3412009-05-13 21:29:45 +0000155 Create a server side job object.
mblighb5dac432008-11-27 00:38:44 +0000156
mblighe7d9c602009-07-02 19:02:33 +0000157 @param control: The pathname of the control file.
158 @param args: Passed to the control file.
159 @param resultdir: Where to throw the results.
160 @param label: Description of the job.
161 @param user: Username for the job (email address).
162 @param client: True if this is a client-side control file.
163 @param parse_job: string, if supplied it is the job execution tag that
164 the results will be passed through to the TKO parser with.
165 @param ssh_user: The SSH username. [root]
166 @param ssh_port: The SSH port number. [22]
167 @param ssh_pass: The SSH passphrase, if needed.
Fang Dengd1c2b732013-08-20 12:59:46 -0700168 @param ssh_verbosity_flag: The SSH verbosity flag, '-v', '-vv',
169 '-vvv', or an empty string if not needed.
Aviv Keshetc5947fa2013-09-04 14:06:29 -0700170 @param ssh_options: A string giving additional options that will be
171 included in ssh commands.
Scott Zawalski91493c82013-01-25 16:15:20 -0500172 @param test_retry: The number of times to retry a test if the test did
173 not complete successfully.
mblighe7d9c602009-07-02 19:02:33 +0000174 @param group_name: If supplied, this will be written out as
mbligh374f3412009-05-13 21:29:45 +0000175 host_group_name in the keyvals file for the parser.
mblighe7d9c602009-07-02 19:02:33 +0000176 @param tag: The job execution tag from the scheduler. [optional]
Christopher Wiley8a91f232013-07-09 11:02:27 -0700177 @param disable_sysinfo: Whether we should disable the sysinfo step of
178 tests for a modest shortening of test time. [optional]
mblighe0cbc912010-03-11 18:03:07 +0000179 @param control_filename: The filename where the server control file
180 should be written in the results directory.
jadmanski10646442008-08-13 14:05:21 +0000181 """
Scott Zawalski91493c82013-01-25 16:15:20 -0500182 super(base_server_job, self).__init__(resultdir=resultdir,
183 test_retry=test_retry)
mbligh0d0f67d2009-11-06 03:15:03 +0000184 path = os.path.dirname(__file__)
Scott Zawalski91493c82013-01-25 16:15:20 -0500185 self.test_retry = test_retry
mbligh0d0f67d2009-11-06 03:15:03 +0000186 self.control = control
187 self._uncollected_log_file = os.path.join(self.resultdir,
188 'uncollected_logs')
189 debugdir = os.path.join(self.resultdir, 'debug')
190 if not os.path.exists(debugdir):
191 os.mkdir(debugdir)
192
193 if user:
194 self.user = user
195 else:
196 self.user = getpass.getuser()
197
jadmanski808f4b12010-04-09 22:30:31 +0000198 self.args = args
Peter Mayo7a875762012-06-13 14:38:15 -0400199 self.label = label
jadmanski10646442008-08-13 14:05:21 +0000200 self.machines = machines
mbligh0d0f67d2009-11-06 03:15:03 +0000201 self._client = client
jadmanski10646442008-08-13 14:05:21 +0000202 self.warning_loggers = set()
jadmanskif37df842009-02-11 00:03:26 +0000203 self.warning_manager = warning_manager()
mbligh0d0f67d2009-11-06 03:15:03 +0000204 self._ssh_user = ssh_user
205 self._ssh_port = ssh_port
206 self._ssh_pass = ssh_pass
Fang Dengd1c2b732013-08-20 12:59:46 -0700207 self._ssh_verbosity_flag = ssh_verbosity_flag
Aviv Keshetc5947fa2013-09-04 14:06:29 -0700208 self._ssh_options = ssh_options
mblighe7d9c602009-07-02 19:02:33 +0000209 self.tag = tag
mbligh09108442008-10-15 16:27:38 +0000210 self.last_boot_tag = None
jadmanski53aaf382008-11-17 16:22:31 +0000211 self.hosts = set()
mbligh0d0f67d2009-11-06 03:15:03 +0000212 self.drop_caches = False
mblighb5dac432008-11-27 00:38:44 +0000213 self.drop_caches_between_iterations = False
mblighe0cbc912010-03-11 18:03:07 +0000214 self._control_filename = control_filename
Christopher Wiley8a91f232013-07-09 11:02:27 -0700215 self._disable_sysinfo = disable_sysinfo
jadmanski10646442008-08-13 14:05:21 +0000216
showard75cdfee2009-06-10 17:40:41 +0000217 self.logging = logging_manager.get_logging_manager(
218 manage_stdout_and_stderr=True, redirect_fds=True)
219 subcommand.logging_manager_object = self.logging
jadmanski10646442008-08-13 14:05:21 +0000220
mbligh0d0f67d2009-11-06 03:15:03 +0000221 self.sysinfo = sysinfo.sysinfo(self.resultdir)
jadmanski043e1132008-11-19 17:10:32 +0000222 self.profilers = profilers.profilers(self)
jadmanskic09fc152008-10-15 17:56:59 +0000223
jadmanski10646442008-08-13 14:05:21 +0000224 job_data = {'label' : label, 'user' : user,
225 'hostname' : ','.join(machines),
Eric Li861b2d52011-02-04 14:50:35 -0800226 'drone' : platform.node(),
mbligh0d0f67d2009-11-06 03:15:03 +0000227 'status_version' : str(self._STATUS_VERSION),
showard170873e2009-01-07 00:22:26 +0000228 'job_started' : str(int(time.time()))}
mbligh374f3412009-05-13 21:29:45 +0000229 if group_name:
230 job_data['host_group_name'] = group_name
jadmanski10646442008-08-13 14:05:21 +0000231
mbligh0d0f67d2009-11-06 03:15:03 +0000232 # only write these keyvals out on the first job in a resultdir
233 if 'job_started' not in utils.read_keyval(self.resultdir):
234 job_data.update(get_site_job_data(self))
235 utils.write_keyval(self.resultdir, job_data)
236
237 self._parse_job = parse_job
showardcc929362010-01-25 21:20:41 +0000238 self._using_parser = (self._parse_job and len(machines) <= 1)
mbligh0d0f67d2009-11-06 03:15:03 +0000239 self.pkgmgr = packages.PackageManager(
240 self.autodir, run_function_dargs={'timeout':600})
showard21baa452008-10-21 00:08:39 +0000241 self.num_tests_run = 0
242 self.num_tests_failed = 0
243
jadmanski550fdc22008-11-20 16:32:08 +0000244 self._register_subcommand_hooks()
245
mbligh0d0f67d2009-11-06 03:15:03 +0000246 # these components aren't usable on the server
247 self.bootloader = None
248 self.harness = None
249
jadmanski2a89dac2010-06-11 14:32:58 +0000250 # set up the status logger
jadmanski52053632010-06-11 21:08:10 +0000251 self._indenter = status_indenter()
jadmanski2a89dac2010-06-11 14:32:58 +0000252 self._logger = base_job.status_logger(
jadmanski52053632010-06-11 21:08:10 +0000253 self, self._indenter, 'status.log', 'status.log',
jadmanski2a89dac2010-06-11 14:32:58 +0000254 record_hook=server_job_record_hook(self))
255
Dan Shib03ea9d2013-08-15 17:13:27 -0700256 # Initialize a flag to indicate DUT failure during the test, e.g.,
257 # unexpected reboot.
258 self.failed_with_device_error = False
259
mbligh0d0f67d2009-11-06 03:15:03 +0000260
261 @classmethod
262 def _find_base_directories(cls):
263 """
264 Determine locations of autodir, clientdir and serverdir. Assumes
265 that this file is located within serverdir and uses __file__ along
266 with relative paths to resolve the location.
267 """
268 serverdir = os.path.abspath(os.path.dirname(__file__))
269 autodir = os.path.normpath(os.path.join(serverdir, '..'))
270 clientdir = os.path.join(autodir, 'client')
271 return autodir, clientdir, serverdir
272
273
Scott Zawalski91493c82013-01-25 16:15:20 -0500274 def _find_resultdir(self, resultdir, *args, **dargs):
mbligh0d0f67d2009-11-06 03:15:03 +0000275 """
276 Determine the location of resultdir. For server jobs we expect one to
277 always be explicitly passed in to __init__, so just return that.
278 """
279 if resultdir:
280 return os.path.normpath(resultdir)
281 else:
282 return None
283
jadmanski550fdc22008-11-20 16:32:08 +0000284
jadmanski2a89dac2010-06-11 14:32:58 +0000285 def _get_status_logger(self):
286 """Return a reference to the status logger."""
287 return self._logger
288
289
jadmanskie432dd22009-01-30 15:04:51 +0000290 @staticmethod
291 def _load_control_file(path):
292 f = open(path)
293 try:
294 control_file = f.read()
295 finally:
296 f.close()
297 return re.sub('\r', '', control_file)
298
299
jadmanski550fdc22008-11-20 16:32:08 +0000300 def _register_subcommand_hooks(self):
mbligh2b92b862008-11-22 13:25:32 +0000301 """
302 Register some hooks into the subcommand modules that allow us
303 to properly clean up self.hosts created in forked subprocesses.
304 """
jadmanski550fdc22008-11-20 16:32:08 +0000305 def on_fork(cmd):
306 self._existing_hosts_on_fork = set(self.hosts)
307 def on_join(cmd):
308 new_hosts = self.hosts - self._existing_hosts_on_fork
309 for host in new_hosts:
310 host.close()
311 subcommand.subcommand.register_fork_hook(on_fork)
312 subcommand.subcommand.register_join_hook(on_join)
313
jadmanski10646442008-08-13 14:05:21 +0000314
mbligh4608b002010-01-05 18:22:35 +0000315 def init_parser(self):
mbligh2b92b862008-11-22 13:25:32 +0000316 """
mbligh4608b002010-01-05 18:22:35 +0000317 Start the continuous parsing of self.resultdir. This sets up
jadmanski10646442008-08-13 14:05:21 +0000318 the database connection and inserts the basic job object into
mbligh2b92b862008-11-22 13:25:32 +0000319 the database if necessary.
320 """
mbligh4608b002010-01-05 18:22:35 +0000321 if not self._using_parser:
322 return
jadmanski10646442008-08-13 14:05:21 +0000323 # redirect parser debugging to .parse.log
mbligh4608b002010-01-05 18:22:35 +0000324 parse_log = os.path.join(self.resultdir, '.parse.log')
jadmanski10646442008-08-13 14:05:21 +0000325 parse_log = open(parse_log, 'w', 0)
326 tko_utils.redirect_parser_debugging(parse_log)
327 # create a job model object and set up the db
328 self.results_db = tko_db.db(autocommit=True)
mbligh0d0f67d2009-11-06 03:15:03 +0000329 self.parser = status_lib.parser(self._STATUS_VERSION)
mbligh4608b002010-01-05 18:22:35 +0000330 self.job_model = self.parser.make_job(self.resultdir)
jadmanski10646442008-08-13 14:05:21 +0000331 self.parser.start(self.job_model)
332 # check if a job already exists in the db and insert it if
333 # it does not
mbligh0d0f67d2009-11-06 03:15:03 +0000334 job_idx = self.results_db.find_job(self._parse_job)
jadmanski10646442008-08-13 14:05:21 +0000335 if job_idx is None:
mbligh0d0f67d2009-11-06 03:15:03 +0000336 self.results_db.insert_job(self._parse_job, self.job_model)
jadmanski10646442008-08-13 14:05:21 +0000337 else:
mbligh2b92b862008-11-22 13:25:32 +0000338 machine_idx = self.results_db.lookup_machine(self.job_model.machine)
jadmanski10646442008-08-13 14:05:21 +0000339 self.job_model.index = job_idx
340 self.job_model.machine_idx = machine_idx
341
342
343 def cleanup_parser(self):
mbligh2b92b862008-11-22 13:25:32 +0000344 """
345 This should be called after the server job is finished
jadmanski10646442008-08-13 14:05:21 +0000346 to carry out any remaining cleanup (e.g. flushing any
mbligh2b92b862008-11-22 13:25:32 +0000347 remaining test results to the results db)
348 """
mbligh0d0f67d2009-11-06 03:15:03 +0000349 if not self._using_parser:
jadmanski10646442008-08-13 14:05:21 +0000350 return
351 final_tests = self.parser.end()
352 for test in final_tests:
353 self.__insert_test(test)
mbligh0d0f67d2009-11-06 03:15:03 +0000354 self._using_parser = False
jadmanski10646442008-08-13 14:05:21 +0000355
Aviv Keshetc5947fa2013-09-04 14:06:29 -0700356 # TODO crbug.com/285395 add a kwargs parameter.
357 def _make_namespace(self):
358 """Create a namespace dictionary to be passed along to control file.
359
360 Creates a namespace argument populated with standard values:
361 machines, job, ssh_user, ssh_port, ssh_pass, ssh_verbosity_flag,
362 and ssh_options.
363 """
364 namespace = {'machines' : self.machines,
365 'job' : self,
366 'ssh_user' : self._ssh_user,
367 'ssh_port' : self._ssh_port,
368 'ssh_pass' : self._ssh_pass,
369 'ssh_verbosity_flag' : self._ssh_verbosity_flag,
370 'ssh_options' : self._ssh_options}
371 return namespace
372
jadmanski10646442008-08-13 14:05:21 +0000373
Alex Miller667b5f22014-02-28 15:33:39 -0800374 def verify(self, labels=''):
Dan Shi07e09af2013-04-12 09:31:29 -0700375 """Verify machines are all ssh-able."""
jadmanski10646442008-08-13 14:05:21 +0000376 if not self.machines:
mbligh084bc172008-10-18 14:02:45 +0000377 raise error.AutoservError('No machines specified to verify')
mbligh0fce4112008-11-27 00:37:17 +0000378 if self.resultdir:
379 os.chdir(self.resultdir)
jadmanski10646442008-08-13 14:05:21 +0000380 try:
Aviv Keshetc5947fa2013-09-04 14:06:29 -0700381 namespace = self._make_namespace()
Alex Miller667b5f22014-02-28 15:33:39 -0800382 namespace.update({'job_labels': labels, 'args': ''})
mbligh084bc172008-10-18 14:02:45 +0000383 self._execute_code(VERIFY_CONTROL_FILE, namespace, protect=False)
jadmanski10646442008-08-13 14:05:21 +0000384 except Exception, e:
mbligh2b92b862008-11-22 13:25:32 +0000385 msg = ('Verify failed\n' + str(e) + '\n' + traceback.format_exc())
jadmanski10646442008-08-13 14:05:21 +0000386 raise
387
388
Alex Miller667b5f22014-02-28 15:33:39 -0800389 def reset(self, labels=''):
Dan Shi07e09af2013-04-12 09:31:29 -0700390 """Reset machines by first cleanup then verify each machine."""
391 if not self.machines:
392 raise error.AutoservError('No machines specified to reset.')
393 if self.resultdir:
394 os.chdir(self.resultdir)
395
396 try:
Aviv Keshetc5947fa2013-09-04 14:06:29 -0700397 namespace = self._make_namespace()
Alex Miller667b5f22014-02-28 15:33:39 -0800398 namespace.update({'job_labels': labels, 'args': ''})
Dan Shi07e09af2013-04-12 09:31:29 -0700399 self._execute_code(RESET_CONTROL_FILE, namespace, protect=False)
400 except Exception as e:
401 msg = ('Reset failed\n' + str(e) + '\n' +
402 traceback.format_exc())
Dan Shi07e09af2013-04-12 09:31:29 -0700403 raise
404
405
Alex Miller667b5f22014-02-28 15:33:39 -0800406 def repair(self, host_protection, labels=''):
jadmanski10646442008-08-13 14:05:21 +0000407 if not self.machines:
408 raise error.AutoservError('No machines specified to repair')
mbligh0fce4112008-11-27 00:37:17 +0000409 if self.resultdir:
410 os.chdir(self.resultdir)
Aviv Keshetc5947fa2013-09-04 14:06:29 -0700411
412 namespace = self._make_namespace()
Alex Miller667b5f22014-02-28 15:33:39 -0800413 namespace.update({'protection_level' : host_protection,
414 'job_labels': labels, 'args': ''})
mbligh25c0b8c2009-01-24 01:44:17 +0000415
mbligh0931b0a2009-04-08 17:44:48 +0000416 self._execute_code(REPAIR_CONTROL_FILE, namespace, protect=False)
jadmanski10646442008-08-13 14:05:21 +0000417
418
Alex Millercb79ba72013-05-29 14:43:00 -0700419 def provision(self, labels):
420 """
421 Provision all hosts to match |labels|.
422
423 @param labels: A comma seperated string of labels to provision the
424 host to.
425
426 """
Alex Millercb79ba72013-05-29 14:43:00 -0700427 control = self._load_control_file(PROVISION_CONTROL_FILE)
Alex Miller686fca82014-04-23 17:21:13 -0700428 self.run(control=control, job_labels=labels)
Alex Millercb79ba72013-05-29 14:43:00 -0700429
430
jadmanski10646442008-08-13 14:05:21 +0000431 def precheck(self):
432 """
433 perform any additional checks in derived classes.
434 """
435 pass
436
437
438 def enable_external_logging(self):
mbligh2b92b862008-11-22 13:25:32 +0000439 """
440 Start or restart external logging mechanism.
jadmanski10646442008-08-13 14:05:21 +0000441 """
442 pass
443
444
445 def disable_external_logging(self):
mbligh2b92b862008-11-22 13:25:32 +0000446 """
447 Pause or stop external logging mechanism.
jadmanski10646442008-08-13 14:05:21 +0000448 """
449 pass
450
451
452 def use_external_logging(self):
mbligh2b92b862008-11-22 13:25:32 +0000453 """
454 Return True if external logging should be used.
jadmanski10646442008-08-13 14:05:21 +0000455 """
456 return False
457
458
mbligh415dc212009-06-15 21:53:34 +0000459 def _make_parallel_wrapper(self, function, machines, log):
460 """Wrap function as appropriate for calling by parallel_simple."""
mbligh2b92b862008-11-22 13:25:32 +0000461 is_forking = not (len(machines) == 1 and self.machines == machines)
mbligh0d0f67d2009-11-06 03:15:03 +0000462 if self._parse_job and is_forking and log:
jadmanski10646442008-08-13 14:05:21 +0000463 def wrapper(machine):
mbligh0d0f67d2009-11-06 03:15:03 +0000464 self._parse_job += "/" + machine
465 self._using_parser = True
jadmanski10646442008-08-13 14:05:21 +0000466 self.machines = [machine]
mbligh0d0f67d2009-11-06 03:15:03 +0000467 self.push_execution_context(machine)
jadmanski609a5f42008-08-26 20:52:42 +0000468 os.chdir(self.resultdir)
showard2bab8f42008-11-12 18:15:22 +0000469 utils.write_keyval(self.resultdir, {"hostname": machine})
mbligh4608b002010-01-05 18:22:35 +0000470 self.init_parser()
jadmanski10646442008-08-13 14:05:21 +0000471 result = function(machine)
472 self.cleanup_parser()
473 return result
jadmanski4dd1a002008-09-05 20:27:30 +0000474 elif len(machines) > 1 and log:
jadmanski10646442008-08-13 14:05:21 +0000475 def wrapper(machine):
mbligh0d0f67d2009-11-06 03:15:03 +0000476 self.push_execution_context(machine)
jadmanski609a5f42008-08-26 20:52:42 +0000477 os.chdir(self.resultdir)
mbligh838d82d2009-03-11 17:14:31 +0000478 machine_data = {'hostname' : machine,
mbligh0d0f67d2009-11-06 03:15:03 +0000479 'status_version' : str(self._STATUS_VERSION)}
mbligh838d82d2009-03-11 17:14:31 +0000480 utils.write_keyval(self.resultdir, machine_data)
jadmanski10646442008-08-13 14:05:21 +0000481 result = function(machine)
482 return result
483 else:
484 wrapper = function
mbligh415dc212009-06-15 21:53:34 +0000485 return wrapper
486
487
488 def parallel_simple(self, function, machines, log=True, timeout=None,
489 return_results=False):
490 """
491 Run 'function' using parallel_simple, with an extra wrapper to handle
492 the necessary setup for continuous parsing, if possible. If continuous
493 parsing is already properly initialized then this should just work.
494
495 @param function: A callable to run in parallel given each machine.
496 @param machines: A list of machine names to be passed one per subcommand
497 invocation of function.
498 @param log: If True, output will be written to output in a subdirectory
499 named after each machine.
500 @param timeout: Seconds after which the function call should timeout.
501 @param return_results: If True instead of an AutoServError being raised
502 on any error a list of the results|exceptions from the function
503 called on each arg is returned. [default: False]
504
505 @raises error.AutotestError: If any of the functions failed.
506 """
507 wrapper = self._make_parallel_wrapper(function, machines, log)
508 return subcommand.parallel_simple(wrapper, machines,
509 log=log, timeout=timeout,
510 return_results=return_results)
511
512
513 def parallel_on_machines(self, function, machines, timeout=None):
514 """
showardcd5fac42009-07-06 20:19:43 +0000515 @param function: Called in parallel with one machine as its argument.
mbligh415dc212009-06-15 21:53:34 +0000516 @param machines: A list of machines to call function(machine) on.
517 @param timeout: Seconds after which the function call should timeout.
518
519 @returns A list of machines on which function(machine) returned
520 without raising an exception.
521 """
showardcd5fac42009-07-06 20:19:43 +0000522 results = self.parallel_simple(function, machines, timeout=timeout,
mbligh415dc212009-06-15 21:53:34 +0000523 return_results=True)
524 success_machines = []
525 for result, machine in itertools.izip(results, machines):
526 if not isinstance(result, Exception):
527 success_machines.append(machine)
528 return success_machines
jadmanski10646442008-08-13 14:05:21 +0000529
530
mbligh0d0f67d2009-11-06 03:15:03 +0000531 _USE_TEMP_DIR = object()
mbligh2b92b862008-11-22 13:25:32 +0000532 def run(self, cleanup=False, install_before=False, install_after=False,
jadmanskie432dd22009-01-30 15:04:51 +0000533 collect_crashdumps=True, namespace={}, control=None,
beepscb6f1e22013-06-28 19:14:10 -0700534 control_file_dir=None, verify_job_repo_url=False,
Alex Millerca76bcc2014-04-18 18:47:28 -0700535 only_collect_crashinfo=False, skip_crash_collection=False,
536 job_labels=''):
jadmanskifb9c0fa2009-04-29 17:39:16 +0000537 # for a normal job, make sure the uncollected logs file exists
538 # for a crashinfo-only run it should already exist, bail out otherwise
jadmanski648c39f2010-03-19 17:38:01 +0000539 created_uncollected_logs = False
Alex Miller45554f32013-08-13 16:48:29 -0700540 logging.info("I am PID %s", os.getpid())
mbligh0d0f67d2009-11-06 03:15:03 +0000541 if self.resultdir and not os.path.exists(self._uncollected_log_file):
jadmanskifb9c0fa2009-04-29 17:39:16 +0000542 if only_collect_crashinfo:
543 # if this is a crashinfo-only run, and there were no existing
544 # uncollected logs, just bail out early
545 logging.info("No existing uncollected logs, "
546 "skipping crashinfo collection")
547 return
548 else:
mbligh0d0f67d2009-11-06 03:15:03 +0000549 log_file = open(self._uncollected_log_file, "w")
jadmanskifb9c0fa2009-04-29 17:39:16 +0000550 pickle.dump([], log_file)
551 log_file.close()
jadmanski648c39f2010-03-19 17:38:01 +0000552 created_uncollected_logs = True
jadmanskifb9c0fa2009-04-29 17:39:16 +0000553
jadmanski10646442008-08-13 14:05:21 +0000554 # use a copy so changes don't affect the original dictionary
555 namespace = namespace.copy()
556 machines = self.machines
jadmanskie432dd22009-01-30 15:04:51 +0000557 if control is None:
jadmanski02a3ba22009-11-13 20:47:27 +0000558 if self.control is None:
559 control = ''
560 else:
561 control = self._load_control_file(self.control)
jadmanskie432dd22009-01-30 15:04:51 +0000562 if control_file_dir is None:
563 control_file_dir = self.resultdir
jadmanski10646442008-08-13 14:05:21 +0000564
565 self.aborted = False
Aviv Keshetc5947fa2013-09-04 14:06:29 -0700566 namespace.update(self._make_namespace())
Alex Millerca76bcc2014-04-18 18:47:28 -0700567 namespace.update({'args' : self.args,
568 'job_labels' : job_labels})
jadmanski10646442008-08-13 14:05:21 +0000569 test_start_time = int(time.time())
570
mbligh80e1eba2008-11-19 00:26:18 +0000571 if self.resultdir:
572 os.chdir(self.resultdir)
jadmanski779bd292009-03-19 17:33:33 +0000573 # touch status.log so that the parser knows a job is running here
jadmanski382303a2009-04-21 19:53:39 +0000574 open(self.get_status_log_path(), 'a').close()
mbligh80e1eba2008-11-19 00:26:18 +0000575 self.enable_external_logging()
jadmanskie432dd22009-01-30 15:04:51 +0000576
jadmanskicdd0c402008-09-19 21:21:31 +0000577 collect_crashinfo = True
mblighaebe3b62008-12-22 14:45:40 +0000578 temp_control_file_dir = None
jadmanski10646442008-08-13 14:05:21 +0000579 try:
showardcf8d4922009-10-14 16:08:39 +0000580 try:
581 if install_before and machines:
582 self._execute_code(INSTALL_CONTROL_FILE, namespace)
jadmanskie432dd22009-01-30 15:04:51 +0000583
showardcf8d4922009-10-14 16:08:39 +0000584 if only_collect_crashinfo:
585 return
586
beepscb6f1e22013-06-28 19:14:10 -0700587 # If the verify_job_repo_url option is set but we're unable
588 # to actually verify that the job_repo_url contains the autotest
589 # package, this job will fail.
590 if verify_job_repo_url:
591 self._execute_code(VERIFY_JOB_REPO_URL_CONTROL_FILE,
592 namespace)
593 else:
594 logging.warning('Not checking if job_repo_url contains '
595 'autotest packages on %s', machines)
596
jadmanskidef0c3c2009-03-25 20:07:10 +0000597 # determine the dir to write the control files to
598 cfd_specified = (control_file_dir
mbligh0d0f67d2009-11-06 03:15:03 +0000599 and control_file_dir is not self._USE_TEMP_DIR)
jadmanskidef0c3c2009-03-25 20:07:10 +0000600 if cfd_specified:
601 temp_control_file_dir = None
602 else:
603 temp_control_file_dir = tempfile.mkdtemp(
604 suffix='temp_control_file_dir')
605 control_file_dir = temp_control_file_dir
606 server_control_file = os.path.join(control_file_dir,
mblighe0cbc912010-03-11 18:03:07 +0000607 self._control_filename)
jadmanskidef0c3c2009-03-25 20:07:10 +0000608 client_control_file = os.path.join(control_file_dir,
609 CLIENT_CONTROL_FILENAME)
mbligh0d0f67d2009-11-06 03:15:03 +0000610 if self._client:
jadmanskidef0c3c2009-03-25 20:07:10 +0000611 namespace['control'] = control
612 utils.open_write_close(client_control_file, control)
mblighfeac0102009-04-28 18:31:12 +0000613 shutil.copyfile(CLIENT_WRAPPER_CONTROL_FILE,
614 server_control_file)
jadmanskidef0c3c2009-03-25 20:07:10 +0000615 else:
616 utils.open_write_close(server_control_file, control)
mbligh26f0d882009-06-22 18:30:01 +0000617 logging.info("Processing control file")
jadmanskidef0c3c2009-03-25 20:07:10 +0000618 self._execute_code(server_control_file, namespace)
mbligh26f0d882009-06-22 18:30:01 +0000619 logging.info("Finished processing control file")
jadmanski10646442008-08-13 14:05:21 +0000620
Dan Shib03ea9d2013-08-15 17:13:27 -0700621 # If no device error occured, no need to collect crashinfo.
622 collect_crashinfo = self.failed_with_device_error
Eric Li6f27d4f2010-09-29 10:55:17 -0700623 except Exception, e:
showardcf8d4922009-10-14 16:08:39 +0000624 try:
625 logging.exception(
626 'Exception escaped control file, job aborting:')
Eric Li6f27d4f2010-09-29 10:55:17 -0700627 self.record('INFO', None, None, str(e),
628 {'job_abort_reason': str(e)})
showardcf8d4922009-10-14 16:08:39 +0000629 except:
630 pass # don't let logging exceptions here interfere
631 raise
jadmanski10646442008-08-13 14:05:21 +0000632 finally:
mblighaebe3b62008-12-22 14:45:40 +0000633 if temp_control_file_dir:
jadmanskie432dd22009-01-30 15:04:51 +0000634 # Clean up temp directory used for copies of the control files
mblighaebe3b62008-12-22 14:45:40 +0000635 try:
636 shutil.rmtree(temp_control_file_dir)
637 except Exception, e:
mblighe7d9c602009-07-02 19:02:33 +0000638 logging.warn('Could not remove temp directory %s: %s',
639 temp_control_file_dir, e)
jadmanskie432dd22009-01-30 15:04:51 +0000640
jadmanskicdd0c402008-09-19 21:21:31 +0000641 if machines and (collect_crashdumps or collect_crashinfo):
jadmanski10646442008-08-13 14:05:21 +0000642 namespace['test_start_time'] = test_start_time
Christopher Wileyf594c5e2013-07-03 18:25:30 -0700643 if skip_crash_collection:
644 logging.info('Skipping crash dump/info collection '
645 'as requested.')
646 elif collect_crashinfo:
mbligh084bc172008-10-18 14:02:45 +0000647 # includes crashdumps
648 self._execute_code(CRASHINFO_CONTROL_FILE, namespace)
jadmanskicdd0c402008-09-19 21:21:31 +0000649 else:
mbligh084bc172008-10-18 14:02:45 +0000650 self._execute_code(CRASHDUMPS_CONTROL_FILE, namespace)
jadmanski10646442008-08-13 14:05:21 +0000651 self.disable_external_logging()
showard45ae8192008-11-05 19:32:53 +0000652 if cleanup and machines:
653 self._execute_code(CLEANUP_CONTROL_FILE, namespace)
Chris Sosaf4d43ff2012-10-30 11:21:05 -0700654 if self._uncollected_log_file and created_uncollected_logs:
655 os.remove(self._uncollected_log_file)
jadmanski10646442008-08-13 14:05:21 +0000656 if install_after and machines:
mbligh084bc172008-10-18 14:02:45 +0000657 self._execute_code(INSTALL_CONTROL_FILE, namespace)
jadmanski10646442008-08-13 14:05:21 +0000658
659
660 def run_test(self, url, *args, **dargs):
mbligh2b92b862008-11-22 13:25:32 +0000661 """
662 Summon a test object and run it.
jadmanski10646442008-08-13 14:05:21 +0000663
664 tag
665 tag to add to testname
666 url
667 url of the test to run
668 """
Christopher Wiley8a91f232013-07-09 11:02:27 -0700669 if self._disable_sysinfo:
670 dargs['disable_sysinfo'] = True
671
mblighfc3da5b2010-01-06 18:37:22 +0000672 group, testname = self.pkgmgr.get_package_name(url, 'test')
673 testname, subdir, tag = self._build_tagged_test_name(testname, dargs)
674 outputdir = self._make_test_outputdir(subdir)
jadmanski10646442008-08-13 14:05:21 +0000675
676 def group_func():
677 try:
678 test.runtest(self, url, tag, args, dargs)
679 except error.TestBaseException, e:
680 self.record(e.exit_status, subdir, testname, str(e))
681 raise
682 except Exception, e:
683 info = str(e) + "\n" + traceback.format_exc()
684 self.record('FAIL', subdir, testname, info)
685 raise
686 else:
mbligh2b92b862008-11-22 13:25:32 +0000687 self.record('GOOD', subdir, testname, 'completed successfully')
jadmanskide292df2008-08-26 20:51:14 +0000688
689 result, exc_info = self._run_group(testname, subdir, group_func)
690 if exc_info and isinstance(exc_info[1], error.TestBaseException):
691 return False
692 elif exc_info:
693 raise exc_info[0], exc_info[1], exc_info[2]
694 else:
695 return True
jadmanski10646442008-08-13 14:05:21 +0000696
697
698 def _run_group(self, name, subdir, function, *args, **dargs):
699 """\
700 Underlying method for running something inside of a group.
701 """
jadmanskide292df2008-08-26 20:51:14 +0000702 result, exc_info = None, None
jadmanski10646442008-08-13 14:05:21 +0000703 try:
704 self.record('START', subdir, name)
jadmanski52053632010-06-11 21:08:10 +0000705 result = function(*args, **dargs)
jadmanski10646442008-08-13 14:05:21 +0000706 except error.TestBaseException, e:
jadmanskib88d6dc2009-01-10 00:33:18 +0000707 self.record("END %s" % e.exit_status, subdir, name)
jadmanskide292df2008-08-26 20:51:14 +0000708 exc_info = sys.exc_info()
jadmanski10646442008-08-13 14:05:21 +0000709 except Exception, e:
710 err_msg = str(e) + '\n'
711 err_msg += traceback.format_exc()
712 self.record('END ABORT', subdir, name, err_msg)
713 raise error.JobError(name + ' failed\n' + traceback.format_exc())
714 else:
715 self.record('END GOOD', subdir, name)
716
jadmanskide292df2008-08-26 20:51:14 +0000717 return result, exc_info
jadmanski10646442008-08-13 14:05:21 +0000718
719
720 def run_group(self, function, *args, **dargs):
721 """\
722 function:
723 subroutine to run
724 *args:
725 arguments for the function
726 """
727
728 name = function.__name__
729
730 # Allow the tag for the group to be specified.
731 tag = dargs.pop('tag', None)
732 if tag:
733 name = tag
734
jadmanskide292df2008-08-26 20:51:14 +0000735 return self._run_group(name, None, function, *args, **dargs)[0]
jadmanski10646442008-08-13 14:05:21 +0000736
737
738 def run_reboot(self, reboot_func, get_kernel_func):
739 """\
740 A specialization of run_group meant specifically for handling
741 a reboot. Includes support for capturing the kernel version
742 after the reboot.
743
744 reboot_func: a function that carries out the reboot
745
746 get_kernel_func: a function that returns a string
747 representing the kernel version.
748 """
jadmanski10646442008-08-13 14:05:21 +0000749 try:
750 self.record('START', None, 'reboot')
jadmanski10646442008-08-13 14:05:21 +0000751 reboot_func()
752 except Exception, e:
jadmanski10646442008-08-13 14:05:21 +0000753 err_msg = str(e) + '\n' + traceback.format_exc()
754 self.record('END FAIL', None, 'reboot', err_msg)
jadmanski4b51d542009-04-08 14:17:16 +0000755 raise
jadmanski10646442008-08-13 14:05:21 +0000756 else:
757 kernel = get_kernel_func()
jadmanski10646442008-08-13 14:05:21 +0000758 self.record('END GOOD', None, 'reboot',
Dale Curtis74a314b2011-06-23 14:55:46 -0700759 optional_fields={"kernel": kernel})
jadmanski10646442008-08-13 14:05:21 +0000760
761
jadmanskie432dd22009-01-30 15:04:51 +0000762 def run_control(self, path):
763 """Execute a control file found at path (relative to the autotest
764 path). Intended for executing a control file within a control file,
765 not for running the top-level job control file."""
766 path = os.path.join(self.autodir, path)
767 control_file = self._load_control_file(path)
mbligh0d0f67d2009-11-06 03:15:03 +0000768 self.run(control=control_file, control_file_dir=self._USE_TEMP_DIR)
jadmanskie432dd22009-01-30 15:04:51 +0000769
770
jadmanskic09fc152008-10-15 17:56:59 +0000771 def add_sysinfo_command(self, command, logfile=None, on_every_test=False):
mbligh4395bbd2009-03-25 19:34:17 +0000772 self._add_sysinfo_loggable(sysinfo.command(command, logf=logfile),
jadmanskic09fc152008-10-15 17:56:59 +0000773 on_every_test)
774
775
776 def add_sysinfo_logfile(self, file, on_every_test=False):
777 self._add_sysinfo_loggable(sysinfo.logfile(file), on_every_test)
778
779
780 def _add_sysinfo_loggable(self, loggable, on_every_test):
781 if on_every_test:
782 self.sysinfo.test_loggables.add(loggable)
783 else:
784 self.sysinfo.boot_loggables.add(loggable)
785
786
jadmanski10646442008-08-13 14:05:21 +0000787 def _read_warnings(self):
jadmanskif37df842009-02-11 00:03:26 +0000788 """Poll all the warning loggers and extract any new warnings that have
789 been logged. If the warnings belong to a category that is currently
790 disabled, this method will discard them and they will no longer be
791 retrievable.
792
793 Returns a list of (timestamp, message) tuples, where timestamp is an
794 integer epoch timestamp."""
jadmanski10646442008-08-13 14:05:21 +0000795 warnings = []
796 while True:
797 # pull in a line of output from every logger that has
798 # output ready to be read
mbligh2b92b862008-11-22 13:25:32 +0000799 loggers, _, _ = select.select(self.warning_loggers, [], [], 0)
jadmanski10646442008-08-13 14:05:21 +0000800 closed_loggers = set()
801 for logger in loggers:
802 line = logger.readline()
803 # record any broken pipes (aka line == empty)
804 if len(line) == 0:
805 closed_loggers.add(logger)
806 continue
jadmanskif37df842009-02-11 00:03:26 +0000807 # parse out the warning
808 timestamp, msgtype, msg = line.split('\t', 2)
809 timestamp = int(timestamp)
810 # if the warning is valid, add it to the results
811 if self.warning_manager.is_valid(timestamp, msgtype):
812 warnings.append((timestamp, msg.strip()))
jadmanski10646442008-08-13 14:05:21 +0000813
814 # stop listening to loggers that are closed
815 self.warning_loggers -= closed_loggers
816
817 # stop if none of the loggers have any output left
818 if not loggers:
819 break
820
821 # sort into timestamp order
822 warnings.sort()
823 return warnings
824
825
showardcc929362010-01-25 21:20:41 +0000826 def _unique_subdirectory(self, base_subdirectory_name):
827 """Compute a unique results subdirectory based on the given name.
828
829 Appends base_subdirectory_name with a number as necessary to find a
830 directory name that doesn't already exist.
831 """
832 subdirectory = base_subdirectory_name
833 counter = 1
834 while os.path.exists(os.path.join(self.resultdir, subdirectory)):
835 subdirectory = base_subdirectory_name + '.' + str(counter)
836 counter += 1
837 return subdirectory
838
839
jadmanski52053632010-06-11 21:08:10 +0000840 def get_record_context(self):
841 """Returns an object representing the current job.record context.
842
843 The object returned is an opaque object with a 0-arg restore method
844 which can be called to restore the job.record context (i.e. indentation)
845 to the current level. The intention is that it should be used when
846 something external which generate job.record calls (e.g. an autotest
847 client) can fail catastrophically and the server job record state
848 needs to be reset to its original "known good" state.
849
850 @return: A context object with a 0-arg restore() method."""
851 return self._indenter.get_context()
852
853
showardcc929362010-01-25 21:20:41 +0000854 def record_summary(self, status_code, test_name, reason='', attributes=None,
855 distinguishing_attributes=(), child_test_ids=None):
856 """Record a summary test result.
857
858 @param status_code: status code string, see
859 common_lib.log.is_valid_status()
860 @param test_name: name of the test
861 @param reason: (optional) string providing detailed reason for test
862 outcome
863 @param attributes: (optional) dict of string keyvals to associate with
864 this result
865 @param distinguishing_attributes: (optional) list of attribute names
866 that should be used to distinguish identically-named test
867 results. These attributes should be present in the attributes
868 parameter. This is used to generate user-friendly subdirectory
869 names.
870 @param child_test_ids: (optional) list of test indices for test results
871 used in generating this result.
872 """
873 subdirectory_name_parts = [test_name]
874 for attribute in distinguishing_attributes:
875 assert attributes
876 assert attribute in attributes, '%s not in %s' % (attribute,
877 attributes)
878 subdirectory_name_parts.append(attributes[attribute])
879 base_subdirectory_name = '.'.join(subdirectory_name_parts)
880
881 subdirectory = self._unique_subdirectory(base_subdirectory_name)
882 subdirectory_path = os.path.join(self.resultdir, subdirectory)
883 os.mkdir(subdirectory_path)
884
885 self.record(status_code, subdirectory, test_name,
886 status=reason, optional_fields={'is_summary': True})
887
888 if attributes:
889 utils.write_keyval(subdirectory_path, attributes)
890
891 if child_test_ids:
892 ids_string = ','.join(str(test_id) for test_id in child_test_ids)
893 summary_data = {'child_test_ids': ids_string}
894 utils.write_keyval(os.path.join(subdirectory_path, 'summary_data'),
895 summary_data)
896
897
jadmanski16a7ff72009-04-01 18:19:53 +0000898 def disable_warnings(self, warning_type):
jadmanskif37df842009-02-11 00:03:26 +0000899 self.warning_manager.disable_warnings(warning_type)
jadmanski16a7ff72009-04-01 18:19:53 +0000900 self.record("INFO", None, None,
901 "disabling %s warnings" % warning_type,
902 {"warnings.disable": warning_type})
jadmanskif37df842009-02-11 00:03:26 +0000903
904
jadmanski16a7ff72009-04-01 18:19:53 +0000905 def enable_warnings(self, warning_type):
jadmanskif37df842009-02-11 00:03:26 +0000906 self.warning_manager.enable_warnings(warning_type)
jadmanski16a7ff72009-04-01 18:19:53 +0000907 self.record("INFO", None, None,
908 "enabling %s warnings" % warning_type,
909 {"warnings.enable": warning_type})
jadmanskif37df842009-02-11 00:03:26 +0000910
911
jadmanski779bd292009-03-19 17:33:33 +0000912 def get_status_log_path(self, subdir=None):
913 """Return the path to the job status log.
914
915 @param subdir - Optional paramter indicating that you want the path
916 to a subdirectory status log.
917
918 @returns The path where the status log should be.
919 """
mbligh210bae62009-04-01 18:33:13 +0000920 if self.resultdir:
921 if subdir:
922 return os.path.join(self.resultdir, subdir, "status.log")
923 else:
924 return os.path.join(self.resultdir, "status.log")
jadmanski779bd292009-03-19 17:33:33 +0000925 else:
mbligh210bae62009-04-01 18:33:13 +0000926 return None
jadmanski779bd292009-03-19 17:33:33 +0000927
928
jadmanski6bb32d72009-03-19 20:25:24 +0000929 def _update_uncollected_logs_list(self, update_func):
930 """Updates the uncollected logs list in a multi-process safe manner.
931
932 @param update_func - a function that updates the list of uncollected
933 logs. Should take one parameter, the list to be updated.
934 """
Dan Shi07e09af2013-04-12 09:31:29 -0700935 # Skip log collection if file _uncollected_log_file does not exist.
936 if not (self._uncollected_log_file and
937 os.path.exists(self._uncollected_log_file)):
938 return
mbligh0d0f67d2009-11-06 03:15:03 +0000939 if self._uncollected_log_file:
940 log_file = open(self._uncollected_log_file, "r+")
mbligha788dc42009-03-26 21:10:16 +0000941 fcntl.flock(log_file, fcntl.LOCK_EX)
jadmanski6bb32d72009-03-19 20:25:24 +0000942 try:
943 uncollected_logs = pickle.load(log_file)
944 update_func(uncollected_logs)
945 log_file.seek(0)
946 log_file.truncate()
947 pickle.dump(uncollected_logs, log_file)
jadmanski3bff9092009-04-22 18:09:47 +0000948 log_file.flush()
jadmanski6bb32d72009-03-19 20:25:24 +0000949 finally:
950 fcntl.flock(log_file, fcntl.LOCK_UN)
951 log_file.close()
952
953
954 def add_client_log(self, hostname, remote_path, local_path):
955 """Adds a new set of client logs to the list of uncollected logs,
956 to allow for future log recovery.
957
958 @param host - the hostname of the machine holding the logs
959 @param remote_path - the directory on the remote machine holding logs
960 @param local_path - the local directory to copy the logs into
961 """
962 def update_func(logs_list):
963 logs_list.append((hostname, remote_path, local_path))
964 self._update_uncollected_logs_list(update_func)
965
966
967 def remove_client_log(self, hostname, remote_path, local_path):
968 """Removes a set of client logs from the list of uncollected logs,
969 to allow for future log recovery.
970
971 @param host - the hostname of the machine holding the logs
972 @param remote_path - the directory on the remote machine holding logs
973 @param local_path - the local directory to copy the logs into
974 """
975 def update_func(logs_list):
976 logs_list.remove((hostname, remote_path, local_path))
977 self._update_uncollected_logs_list(update_func)
978
979
mbligh0d0f67d2009-11-06 03:15:03 +0000980 def get_client_logs(self):
981 """Retrieves the list of uncollected logs, if it exists.
982
983 @returns A list of (host, remote_path, local_path) tuples. Returns
984 an empty list if no uncollected logs file exists.
985 """
986 log_exists = (self._uncollected_log_file and
987 os.path.exists(self._uncollected_log_file))
988 if log_exists:
989 return pickle.load(open(self._uncollected_log_file))
990 else:
991 return []
992
993
mbligh084bc172008-10-18 14:02:45 +0000994 def _fill_server_control_namespace(self, namespace, protect=True):
mbligh2b92b862008-11-22 13:25:32 +0000995 """
996 Prepare a namespace to be used when executing server control files.
mbligh084bc172008-10-18 14:02:45 +0000997
998 This sets up the control file API by importing modules and making them
999 available under the appropriate names within namespace.
1000
1001 For use by _execute_code().
1002
1003 Args:
1004 namespace: The namespace dictionary to fill in.
1005 protect: Boolean. If True (the default) any operation that would
1006 clobber an existing entry in namespace will cause an error.
1007 Raises:
1008 error.AutoservError: When a name would be clobbered by import.
1009 """
1010 def _import_names(module_name, names=()):
mbligh2b92b862008-11-22 13:25:32 +00001011 """
1012 Import a module and assign named attributes into namespace.
mbligh084bc172008-10-18 14:02:45 +00001013
1014 Args:
1015 module_name: The string module name.
1016 names: A limiting list of names to import from module_name. If
1017 empty (the default), all names are imported from the module
1018 similar to a "from foo.bar import *" statement.
1019 Raises:
1020 error.AutoservError: When a name being imported would clobber
1021 a name already in namespace.
1022 """
1023 module = __import__(module_name, {}, {}, names)
1024
1025 # No names supplied? Import * from the lowest level module.
1026 # (Ugh, why do I have to implement this part myself?)
1027 if not names:
1028 for submodule_name in module_name.split('.')[1:]:
1029 module = getattr(module, submodule_name)
1030 if hasattr(module, '__all__'):
1031 names = getattr(module, '__all__')
1032 else:
1033 names = dir(module)
1034
1035 # Install each name into namespace, checking to make sure it
1036 # doesn't override anything that already exists.
1037 for name in names:
1038 # Check for conflicts to help prevent future problems.
1039 if name in namespace and protect:
1040 if namespace[name] is not getattr(module, name):
1041 raise error.AutoservError('importing name '
1042 '%s from %s %r would override %r' %
1043 (name, module_name, getattr(module, name),
1044 namespace[name]))
1045 else:
1046 # Encourage cleanliness and the use of __all__ for a
1047 # more concrete API with less surprises on '*' imports.
1048 warnings.warn('%s (%r) being imported from %s for use '
1049 'in server control files is not the '
1050 'first occurrance of that import.' %
1051 (name, namespace[name], module_name))
1052
1053 namespace[name] = getattr(module, name)
1054
1055
1056 # This is the equivalent of prepending a bunch of import statements to
1057 # the front of the control script.
mbligha2b07dd2009-06-22 18:26:13 +00001058 namespace.update(os=os, sys=sys, logging=logging)
mbligh084bc172008-10-18 14:02:45 +00001059 _import_names('autotest_lib.server',
1060 ('hosts', 'autotest', 'kvm', 'git', 'standalone_profiler',
1061 'source_kernel', 'rpm_kernel', 'deb_kernel', 'git_kernel'))
1062 _import_names('autotest_lib.server.subcommand',
1063 ('parallel', 'parallel_simple', 'subcommand'))
1064 _import_names('autotest_lib.server.utils',
1065 ('run', 'get_tmp_dir', 'sh_escape', 'parse_machine'))
1066 _import_names('autotest_lib.client.common_lib.error')
1067 _import_names('autotest_lib.client.common_lib.barrier', ('barrier',))
1068
1069 # Inject ourself as the job object into other classes within the API.
1070 # (Yuck, this injection is a gross thing be part of a public API. -gps)
1071 #
1072 # XXX Base & SiteAutotest do not appear to use .job. Who does?
1073 namespace['autotest'].Autotest.job = self
1074 # server.hosts.base_classes.Host uses .job.
1075 namespace['hosts'].Host.job = self
Eric Li10222b82010-11-24 09:33:15 -08001076 namespace['hosts'].factory.ssh_user = self._ssh_user
1077 namespace['hosts'].factory.ssh_port = self._ssh_port
1078 namespace['hosts'].factory.ssh_pass = self._ssh_pass
Fang Dengd1c2b732013-08-20 12:59:46 -07001079 namespace['hosts'].factory.ssh_verbosity_flag = (
1080 self._ssh_verbosity_flag)
Aviv Keshetc5947fa2013-09-04 14:06:29 -07001081 namespace['hosts'].factory.ssh_options = self._ssh_options
mbligh084bc172008-10-18 14:02:45 +00001082
1083
1084 def _execute_code(self, code_file, namespace, protect=True):
mbligh2b92b862008-11-22 13:25:32 +00001085 """
1086 Execute code using a copy of namespace as a server control script.
mbligh084bc172008-10-18 14:02:45 +00001087
1088 Unless protect_namespace is explicitly set to False, the dict will not
1089 be modified.
1090
1091 Args:
1092 code_file: The filename of the control file to execute.
1093 namespace: A dict containing names to make available during execution.
1094 protect: Boolean. If True (the default) a copy of the namespace dict
1095 is used during execution to prevent the code from modifying its
1096 contents outside of this function. If False the raw dict is
1097 passed in and modifications will be allowed.
1098 """
1099 if protect:
1100 namespace = namespace.copy()
1101 self._fill_server_control_namespace(namespace, protect=protect)
1102 # TODO: Simplify and get rid of the special cases for only 1 machine.
showard3e66e8c2008-10-27 19:20:51 +00001103 if len(self.machines) > 1:
mbligh084bc172008-10-18 14:02:45 +00001104 machines_text = '\n'.join(self.machines) + '\n'
1105 # Only rewrite the file if it does not match our machine list.
1106 try:
1107 machines_f = open(MACHINES_FILENAME, 'r')
1108 existing_machines_text = machines_f.read()
1109 machines_f.close()
1110 except EnvironmentError:
1111 existing_machines_text = None
1112 if machines_text != existing_machines_text:
1113 utils.open_write_close(MACHINES_FILENAME, machines_text)
1114 execfile(code_file, namespace, namespace)
jadmanski10646442008-08-13 14:05:21 +00001115
1116
jadmanskie29d0e42010-06-17 16:06:52 +00001117 def _parse_status(self, new_line):
mbligh0d0f67d2009-11-06 03:15:03 +00001118 if not self._using_parser:
jadmanski10646442008-08-13 14:05:21 +00001119 return
jadmanskie29d0e42010-06-17 16:06:52 +00001120 new_tests = self.parser.process_lines([new_line])
jadmanski10646442008-08-13 14:05:21 +00001121 for test in new_tests:
1122 self.__insert_test(test)
1123
1124
1125 def __insert_test(self, test):
mbligh2b92b862008-11-22 13:25:32 +00001126 """
1127 An internal method to insert a new test result into the
jadmanski10646442008-08-13 14:05:21 +00001128 database. This method will not raise an exception, even if an
1129 error occurs during the insert, to avoid failing a test
1130 simply because of unexpected database issues."""
showard21baa452008-10-21 00:08:39 +00001131 self.num_tests_run += 1
1132 if status_lib.is_worse_than_or_equal_to(test.status, 'FAIL'):
1133 self.num_tests_failed += 1
jadmanski10646442008-08-13 14:05:21 +00001134 try:
1135 self.results_db.insert_test(self.job_model, test)
1136 except Exception:
1137 msg = ("WARNING: An unexpected error occured while "
1138 "inserting test results into the database. "
1139 "Ignoring error.\n" + traceback.format_exc())
1140 print >> sys.stderr, msg
1141
mblighcaa62c22008-04-07 21:51:17 +00001142
mblighfc3da5b2010-01-06 18:37:22 +00001143 def preprocess_client_state(self):
1144 """
1145 Produce a state file for initializing the state of a client job.
1146
1147 Creates a new client state file with all the current server state, as
1148 well as some pre-set client state.
1149
1150 @returns The path of the file the state was written into.
1151 """
1152 # initialize the sysinfo state
1153 self._state.set('client', 'sysinfo', self.sysinfo.serialize())
1154
1155 # dump the state out to a tempfile
1156 fd, file_path = tempfile.mkstemp(dir=self.tmpdir)
1157 os.close(fd)
mbligha2c99492010-01-27 22:59:50 +00001158
1159 # write_to_file doesn't need locking, we exclusively own file_path
mblighfc3da5b2010-01-06 18:37:22 +00001160 self._state.write_to_file(file_path)
1161 return file_path
1162
1163
1164 def postprocess_client_state(self, state_path):
1165 """
1166 Update the state of this job with the state from a client job.
1167
1168 Updates the state of the server side of a job with the final state
1169 of a client job that was run. Updates the non-client-specific state,
1170 pulls in some specific bits from the client-specific state, and then
1171 discards the rest. Removes the state file afterwards
1172
1173 @param state_file A path to the state file from the client.
1174 """
1175 # update the on-disk state
mblighfc3da5b2010-01-06 18:37:22 +00001176 try:
jadmanskib6e7bdb2010-04-13 16:00:39 +00001177 self._state.read_from_file(state_path)
mblighfc3da5b2010-01-06 18:37:22 +00001178 os.remove(state_path)
mbligha2c99492010-01-27 22:59:50 +00001179 except OSError, e:
mblighfc3da5b2010-01-06 18:37:22 +00001180 # ignore file-not-found errors
1181 if e.errno != errno.ENOENT:
1182 raise
jadmanskib6e7bdb2010-04-13 16:00:39 +00001183 else:
1184 logging.debug('Client state file %s not found', state_path)
mblighfc3da5b2010-01-06 18:37:22 +00001185
1186 # update the sysinfo state
1187 if self._state.has('client', 'sysinfo'):
1188 self.sysinfo.deserialize(self._state.get('client', 'sysinfo'))
1189
1190 # drop all the client-specific state
1191 self._state.discard_namespace('client')
1192
1193
mbligh0a883702010-04-21 01:58:34 +00001194 def clear_all_known_hosts(self):
1195 """Clears known hosts files for all AbstractSSHHosts."""
1196 for host in self.hosts:
1197 if isinstance(host, abstract_ssh.AbstractSSHHost):
1198 host.clear_known_hosts()
1199
1200
jadmanskif37df842009-02-11 00:03:26 +00001201class warning_manager(object):
1202 """Class for controlling warning logs. Manages the enabling and disabling
1203 of warnings."""
1204 def __init__(self):
1205 # a map of warning types to a list of disabled time intervals
1206 self.disabled_warnings = {}
1207
1208
1209 def is_valid(self, timestamp, warning_type):
1210 """Indicates if a warning (based on the time it occured and its type)
1211 is a valid warning. A warning is considered "invalid" if this type of
1212 warning was marked as "disabled" at the time the warning occured."""
1213 disabled_intervals = self.disabled_warnings.get(warning_type, [])
1214 for start, end in disabled_intervals:
1215 if timestamp >= start and (end is None or timestamp < end):
1216 return False
1217 return True
1218
1219
1220 def disable_warnings(self, warning_type, current_time_func=time.time):
1221 """As of now, disables all further warnings of this type."""
1222 intervals = self.disabled_warnings.setdefault(warning_type, [])
1223 if not intervals or intervals[-1][1] is not None:
jadmanski16a7ff72009-04-01 18:19:53 +00001224 intervals.append((int(current_time_func()), None))
jadmanskif37df842009-02-11 00:03:26 +00001225
1226
1227 def enable_warnings(self, warning_type, current_time_func=time.time):
1228 """As of now, enables all further warnings of this type."""
1229 intervals = self.disabled_warnings.get(warning_type, [])
1230 if intervals and intervals[-1][1] is None:
jadmanski16a7ff72009-04-01 18:19:53 +00001231 intervals[-1] = (intervals[-1][0], int(current_time_func()))
Paul Pendlebury57593562011-06-15 10:45:49 -07001232
1233
1234# load up site-specific code for generating site-specific job data
1235get_site_job_data = utils.import_site_function(__file__,
1236 "autotest_lib.server.site_server_job", "get_site_job_data",
1237 _get_site_job_data_dummy)
1238
1239
1240site_server_job = utils.import_site_class(
1241 __file__, "autotest_lib.server.site_server_job", "site_server_job",
1242 base_server_job)
1243
1244
1245class server_job(site_server_job):
Dale Curtis456d3c12011-07-19 11:42:51 -07001246 pass