blob: fa9c87f326b43ef47c85a0e135abaab544e7c91b [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 Miller667b5f22014-02-28 15:33:39 -0800427 namespace = {'job_labels': labels}
Alex Millercb79ba72013-05-29 14:43:00 -0700428 control = self._load_control_file(PROVISION_CONTROL_FILE)
429 self.run(control=control, namespace=namespace)
430
431
jadmanski10646442008-08-13 14:05:21 +0000432 def precheck(self):
433 """
434 perform any additional checks in derived classes.
435 """
436 pass
437
438
439 def enable_external_logging(self):
mbligh2b92b862008-11-22 13:25:32 +0000440 """
441 Start or restart external logging mechanism.
jadmanski10646442008-08-13 14:05:21 +0000442 """
443 pass
444
445
446 def disable_external_logging(self):
mbligh2b92b862008-11-22 13:25:32 +0000447 """
448 Pause or stop external logging mechanism.
jadmanski10646442008-08-13 14:05:21 +0000449 """
450 pass
451
452
453 def use_external_logging(self):
mbligh2b92b862008-11-22 13:25:32 +0000454 """
455 Return True if external logging should be used.
jadmanski10646442008-08-13 14:05:21 +0000456 """
457 return False
458
459
mbligh415dc212009-06-15 21:53:34 +0000460 def _make_parallel_wrapper(self, function, machines, log):
461 """Wrap function as appropriate for calling by parallel_simple."""
mbligh2b92b862008-11-22 13:25:32 +0000462 is_forking = not (len(machines) == 1 and self.machines == machines)
mbligh0d0f67d2009-11-06 03:15:03 +0000463 if self._parse_job and is_forking and log:
jadmanski10646442008-08-13 14:05:21 +0000464 def wrapper(machine):
mbligh0d0f67d2009-11-06 03:15:03 +0000465 self._parse_job += "/" + machine
466 self._using_parser = True
jadmanski10646442008-08-13 14:05:21 +0000467 self.machines = [machine]
mbligh0d0f67d2009-11-06 03:15:03 +0000468 self.push_execution_context(machine)
jadmanski609a5f42008-08-26 20:52:42 +0000469 os.chdir(self.resultdir)
showard2bab8f42008-11-12 18:15:22 +0000470 utils.write_keyval(self.resultdir, {"hostname": machine})
mbligh4608b002010-01-05 18:22:35 +0000471 self.init_parser()
jadmanski10646442008-08-13 14:05:21 +0000472 result = function(machine)
473 self.cleanup_parser()
474 return result
jadmanski4dd1a002008-09-05 20:27:30 +0000475 elif len(machines) > 1 and log:
jadmanski10646442008-08-13 14:05:21 +0000476 def wrapper(machine):
mbligh0d0f67d2009-11-06 03:15:03 +0000477 self.push_execution_context(machine)
jadmanski609a5f42008-08-26 20:52:42 +0000478 os.chdir(self.resultdir)
mbligh838d82d2009-03-11 17:14:31 +0000479 machine_data = {'hostname' : machine,
mbligh0d0f67d2009-11-06 03:15:03 +0000480 'status_version' : str(self._STATUS_VERSION)}
mbligh838d82d2009-03-11 17:14:31 +0000481 utils.write_keyval(self.resultdir, machine_data)
jadmanski10646442008-08-13 14:05:21 +0000482 result = function(machine)
483 return result
484 else:
485 wrapper = function
mbligh415dc212009-06-15 21:53:34 +0000486 return wrapper
487
488
489 def parallel_simple(self, function, machines, log=True, timeout=None,
490 return_results=False):
491 """
492 Run 'function' using parallel_simple, with an extra wrapper to handle
493 the necessary setup for continuous parsing, if possible. If continuous
494 parsing is already properly initialized then this should just work.
495
496 @param function: A callable to run in parallel given each machine.
497 @param machines: A list of machine names to be passed one per subcommand
498 invocation of function.
499 @param log: If True, output will be written to output in a subdirectory
500 named after each machine.
501 @param timeout: Seconds after which the function call should timeout.
502 @param return_results: If True instead of an AutoServError being raised
503 on any error a list of the results|exceptions from the function
504 called on each arg is returned. [default: False]
505
506 @raises error.AutotestError: If any of the functions failed.
507 """
508 wrapper = self._make_parallel_wrapper(function, machines, log)
509 return subcommand.parallel_simple(wrapper, machines,
510 log=log, timeout=timeout,
511 return_results=return_results)
512
513
514 def parallel_on_machines(self, function, machines, timeout=None):
515 """
showardcd5fac42009-07-06 20:19:43 +0000516 @param function: Called in parallel with one machine as its argument.
mbligh415dc212009-06-15 21:53:34 +0000517 @param machines: A list of machines to call function(machine) on.
518 @param timeout: Seconds after which the function call should timeout.
519
520 @returns A list of machines on which function(machine) returned
521 without raising an exception.
522 """
showardcd5fac42009-07-06 20:19:43 +0000523 results = self.parallel_simple(function, machines, timeout=timeout,
mbligh415dc212009-06-15 21:53:34 +0000524 return_results=True)
525 success_machines = []
526 for result, machine in itertools.izip(results, machines):
527 if not isinstance(result, Exception):
528 success_machines.append(machine)
529 return success_machines
jadmanski10646442008-08-13 14:05:21 +0000530
531
mbligh0d0f67d2009-11-06 03:15:03 +0000532 _USE_TEMP_DIR = object()
mbligh2b92b862008-11-22 13:25:32 +0000533 def run(self, cleanup=False, install_before=False, install_after=False,
jadmanskie432dd22009-01-30 15:04:51 +0000534 collect_crashdumps=True, namespace={}, control=None,
beepscb6f1e22013-06-28 19:14:10 -0700535 control_file_dir=None, verify_job_repo_url=False,
Alex Millerca76bcc2014-04-18 18:47:28 -0700536 only_collect_crashinfo=False, skip_crash_collection=False,
537 job_labels=''):
jadmanskifb9c0fa2009-04-29 17:39:16 +0000538 # for a normal job, make sure the uncollected logs file exists
539 # for a crashinfo-only run it should already exist, bail out otherwise
jadmanski648c39f2010-03-19 17:38:01 +0000540 created_uncollected_logs = False
Alex Miller45554f32013-08-13 16:48:29 -0700541 logging.info("I am PID %s", os.getpid())
mbligh0d0f67d2009-11-06 03:15:03 +0000542 if self.resultdir and not os.path.exists(self._uncollected_log_file):
jadmanskifb9c0fa2009-04-29 17:39:16 +0000543 if only_collect_crashinfo:
544 # if this is a crashinfo-only run, and there were no existing
545 # uncollected logs, just bail out early
546 logging.info("No existing uncollected logs, "
547 "skipping crashinfo collection")
548 return
549 else:
mbligh0d0f67d2009-11-06 03:15:03 +0000550 log_file = open(self._uncollected_log_file, "w")
jadmanskifb9c0fa2009-04-29 17:39:16 +0000551 pickle.dump([], log_file)
552 log_file.close()
jadmanski648c39f2010-03-19 17:38:01 +0000553 created_uncollected_logs = True
jadmanskifb9c0fa2009-04-29 17:39:16 +0000554
jadmanski10646442008-08-13 14:05:21 +0000555 # use a copy so changes don't affect the original dictionary
556 namespace = namespace.copy()
557 machines = self.machines
jadmanskie432dd22009-01-30 15:04:51 +0000558 if control is None:
jadmanski02a3ba22009-11-13 20:47:27 +0000559 if self.control is None:
560 control = ''
561 else:
562 control = self._load_control_file(self.control)
jadmanskie432dd22009-01-30 15:04:51 +0000563 if control_file_dir is None:
564 control_file_dir = self.resultdir
jadmanski10646442008-08-13 14:05:21 +0000565
566 self.aborted = False
Aviv Keshetc5947fa2013-09-04 14:06:29 -0700567 namespace.update(self._make_namespace())
Alex Millerca76bcc2014-04-18 18:47:28 -0700568 namespace.update({'args' : self.args,
569 'job_labels' : job_labels})
jadmanski10646442008-08-13 14:05:21 +0000570 test_start_time = int(time.time())
571
mbligh80e1eba2008-11-19 00:26:18 +0000572 if self.resultdir:
573 os.chdir(self.resultdir)
jadmanski779bd292009-03-19 17:33:33 +0000574 # touch status.log so that the parser knows a job is running here
jadmanski382303a2009-04-21 19:53:39 +0000575 open(self.get_status_log_path(), 'a').close()
mbligh80e1eba2008-11-19 00:26:18 +0000576 self.enable_external_logging()
jadmanskie432dd22009-01-30 15:04:51 +0000577
jadmanskicdd0c402008-09-19 21:21:31 +0000578 collect_crashinfo = True
mblighaebe3b62008-12-22 14:45:40 +0000579 temp_control_file_dir = None
jadmanski10646442008-08-13 14:05:21 +0000580 try:
showardcf8d4922009-10-14 16:08:39 +0000581 try:
582 if install_before and machines:
583 self._execute_code(INSTALL_CONTROL_FILE, namespace)
jadmanskie432dd22009-01-30 15:04:51 +0000584
showardcf8d4922009-10-14 16:08:39 +0000585 if only_collect_crashinfo:
586 return
587
beepscb6f1e22013-06-28 19:14:10 -0700588 # If the verify_job_repo_url option is set but we're unable
589 # to actually verify that the job_repo_url contains the autotest
590 # package, this job will fail.
591 if verify_job_repo_url:
592 self._execute_code(VERIFY_JOB_REPO_URL_CONTROL_FILE,
593 namespace)
594 else:
595 logging.warning('Not checking if job_repo_url contains '
596 'autotest packages on %s', machines)
597
jadmanskidef0c3c2009-03-25 20:07:10 +0000598 # determine the dir to write the control files to
599 cfd_specified = (control_file_dir
mbligh0d0f67d2009-11-06 03:15:03 +0000600 and control_file_dir is not self._USE_TEMP_DIR)
jadmanskidef0c3c2009-03-25 20:07:10 +0000601 if cfd_specified:
602 temp_control_file_dir = None
603 else:
604 temp_control_file_dir = tempfile.mkdtemp(
605 suffix='temp_control_file_dir')
606 control_file_dir = temp_control_file_dir
607 server_control_file = os.path.join(control_file_dir,
mblighe0cbc912010-03-11 18:03:07 +0000608 self._control_filename)
jadmanskidef0c3c2009-03-25 20:07:10 +0000609 client_control_file = os.path.join(control_file_dir,
610 CLIENT_CONTROL_FILENAME)
mbligh0d0f67d2009-11-06 03:15:03 +0000611 if self._client:
jadmanskidef0c3c2009-03-25 20:07:10 +0000612 namespace['control'] = control
613 utils.open_write_close(client_control_file, control)
mblighfeac0102009-04-28 18:31:12 +0000614 shutil.copyfile(CLIENT_WRAPPER_CONTROL_FILE,
615 server_control_file)
jadmanskidef0c3c2009-03-25 20:07:10 +0000616 else:
617 utils.open_write_close(server_control_file, control)
mbligh26f0d882009-06-22 18:30:01 +0000618 logging.info("Processing control file")
jadmanskidef0c3c2009-03-25 20:07:10 +0000619 self._execute_code(server_control_file, namespace)
mbligh26f0d882009-06-22 18:30:01 +0000620 logging.info("Finished processing control file")
jadmanski10646442008-08-13 14:05:21 +0000621
Dan Shib03ea9d2013-08-15 17:13:27 -0700622 # If no device error occured, no need to collect crashinfo.
623 collect_crashinfo = self.failed_with_device_error
Eric Li6f27d4f2010-09-29 10:55:17 -0700624 except Exception, e:
showardcf8d4922009-10-14 16:08:39 +0000625 try:
626 logging.exception(
627 'Exception escaped control file, job aborting:')
Eric Li6f27d4f2010-09-29 10:55:17 -0700628 self.record('INFO', None, None, str(e),
629 {'job_abort_reason': str(e)})
showardcf8d4922009-10-14 16:08:39 +0000630 except:
631 pass # don't let logging exceptions here interfere
632 raise
jadmanski10646442008-08-13 14:05:21 +0000633 finally:
mblighaebe3b62008-12-22 14:45:40 +0000634 if temp_control_file_dir:
jadmanskie432dd22009-01-30 15:04:51 +0000635 # Clean up temp directory used for copies of the control files
mblighaebe3b62008-12-22 14:45:40 +0000636 try:
637 shutil.rmtree(temp_control_file_dir)
638 except Exception, e:
mblighe7d9c602009-07-02 19:02:33 +0000639 logging.warn('Could not remove temp directory %s: %s',
640 temp_control_file_dir, e)
jadmanskie432dd22009-01-30 15:04:51 +0000641
jadmanskicdd0c402008-09-19 21:21:31 +0000642 if machines and (collect_crashdumps or collect_crashinfo):
jadmanski10646442008-08-13 14:05:21 +0000643 namespace['test_start_time'] = test_start_time
Christopher Wileyf594c5e2013-07-03 18:25:30 -0700644 if skip_crash_collection:
645 logging.info('Skipping crash dump/info collection '
646 'as requested.')
647 elif collect_crashinfo:
mbligh084bc172008-10-18 14:02:45 +0000648 # includes crashdumps
649 self._execute_code(CRASHINFO_CONTROL_FILE, namespace)
jadmanskicdd0c402008-09-19 21:21:31 +0000650 else:
mbligh084bc172008-10-18 14:02:45 +0000651 self._execute_code(CRASHDUMPS_CONTROL_FILE, namespace)
jadmanski10646442008-08-13 14:05:21 +0000652 self.disable_external_logging()
showard45ae8192008-11-05 19:32:53 +0000653 if cleanup and machines:
654 self._execute_code(CLEANUP_CONTROL_FILE, namespace)
Chris Sosaf4d43ff2012-10-30 11:21:05 -0700655 if self._uncollected_log_file and created_uncollected_logs:
656 os.remove(self._uncollected_log_file)
jadmanski10646442008-08-13 14:05:21 +0000657 if install_after and machines:
mbligh084bc172008-10-18 14:02:45 +0000658 self._execute_code(INSTALL_CONTROL_FILE, namespace)
jadmanski10646442008-08-13 14:05:21 +0000659
660
661 def run_test(self, url, *args, **dargs):
mbligh2b92b862008-11-22 13:25:32 +0000662 """
663 Summon a test object and run it.
jadmanski10646442008-08-13 14:05:21 +0000664
665 tag
666 tag to add to testname
667 url
668 url of the test to run
669 """
Christopher Wiley8a91f232013-07-09 11:02:27 -0700670 if self._disable_sysinfo:
671 dargs['disable_sysinfo'] = True
672
mblighfc3da5b2010-01-06 18:37:22 +0000673 group, testname = self.pkgmgr.get_package_name(url, 'test')
674 testname, subdir, tag = self._build_tagged_test_name(testname, dargs)
675 outputdir = self._make_test_outputdir(subdir)
jadmanski10646442008-08-13 14:05:21 +0000676
677 def group_func():
678 try:
679 test.runtest(self, url, tag, args, dargs)
680 except error.TestBaseException, e:
681 self.record(e.exit_status, subdir, testname, str(e))
682 raise
683 except Exception, e:
684 info = str(e) + "\n" + traceback.format_exc()
685 self.record('FAIL', subdir, testname, info)
686 raise
687 else:
mbligh2b92b862008-11-22 13:25:32 +0000688 self.record('GOOD', subdir, testname, 'completed successfully')
jadmanskide292df2008-08-26 20:51:14 +0000689
690 result, exc_info = self._run_group(testname, subdir, group_func)
691 if exc_info and isinstance(exc_info[1], error.TestBaseException):
692 return False
693 elif exc_info:
694 raise exc_info[0], exc_info[1], exc_info[2]
695 else:
696 return True
jadmanski10646442008-08-13 14:05:21 +0000697
698
699 def _run_group(self, name, subdir, function, *args, **dargs):
700 """\
701 Underlying method for running something inside of a group.
702 """
jadmanskide292df2008-08-26 20:51:14 +0000703 result, exc_info = None, None
jadmanski10646442008-08-13 14:05:21 +0000704 try:
705 self.record('START', subdir, name)
jadmanski52053632010-06-11 21:08:10 +0000706 result = function(*args, **dargs)
jadmanski10646442008-08-13 14:05:21 +0000707 except error.TestBaseException, e:
jadmanskib88d6dc2009-01-10 00:33:18 +0000708 self.record("END %s" % e.exit_status, subdir, name)
jadmanskide292df2008-08-26 20:51:14 +0000709 exc_info = sys.exc_info()
jadmanski10646442008-08-13 14:05:21 +0000710 except Exception, e:
711 err_msg = str(e) + '\n'
712 err_msg += traceback.format_exc()
713 self.record('END ABORT', subdir, name, err_msg)
714 raise error.JobError(name + ' failed\n' + traceback.format_exc())
715 else:
716 self.record('END GOOD', subdir, name)
717
jadmanskide292df2008-08-26 20:51:14 +0000718 return result, exc_info
jadmanski10646442008-08-13 14:05:21 +0000719
720
721 def run_group(self, function, *args, **dargs):
722 """\
723 function:
724 subroutine to run
725 *args:
726 arguments for the function
727 """
728
729 name = function.__name__
730
731 # Allow the tag for the group to be specified.
732 tag = dargs.pop('tag', None)
733 if tag:
734 name = tag
735
jadmanskide292df2008-08-26 20:51:14 +0000736 return self._run_group(name, None, function, *args, **dargs)[0]
jadmanski10646442008-08-13 14:05:21 +0000737
738
739 def run_reboot(self, reboot_func, get_kernel_func):
740 """\
741 A specialization of run_group meant specifically for handling
742 a reboot. Includes support for capturing the kernel version
743 after the reboot.
744
745 reboot_func: a function that carries out the reboot
746
747 get_kernel_func: a function that returns a string
748 representing the kernel version.
749 """
jadmanski10646442008-08-13 14:05:21 +0000750 try:
751 self.record('START', None, 'reboot')
jadmanski10646442008-08-13 14:05:21 +0000752 reboot_func()
753 except Exception, e:
jadmanski10646442008-08-13 14:05:21 +0000754 err_msg = str(e) + '\n' + traceback.format_exc()
755 self.record('END FAIL', None, 'reboot', err_msg)
jadmanski4b51d542009-04-08 14:17:16 +0000756 raise
jadmanski10646442008-08-13 14:05:21 +0000757 else:
758 kernel = get_kernel_func()
jadmanski10646442008-08-13 14:05:21 +0000759 self.record('END GOOD', None, 'reboot',
Dale Curtis74a314b2011-06-23 14:55:46 -0700760 optional_fields={"kernel": kernel})
jadmanski10646442008-08-13 14:05:21 +0000761
762
jadmanskie432dd22009-01-30 15:04:51 +0000763 def run_control(self, path):
764 """Execute a control file found at path (relative to the autotest
765 path). Intended for executing a control file within a control file,
766 not for running the top-level job control file."""
767 path = os.path.join(self.autodir, path)
768 control_file = self._load_control_file(path)
mbligh0d0f67d2009-11-06 03:15:03 +0000769 self.run(control=control_file, control_file_dir=self._USE_TEMP_DIR)
jadmanskie432dd22009-01-30 15:04:51 +0000770
771
jadmanskic09fc152008-10-15 17:56:59 +0000772 def add_sysinfo_command(self, command, logfile=None, on_every_test=False):
mbligh4395bbd2009-03-25 19:34:17 +0000773 self._add_sysinfo_loggable(sysinfo.command(command, logf=logfile),
jadmanskic09fc152008-10-15 17:56:59 +0000774 on_every_test)
775
776
777 def add_sysinfo_logfile(self, file, on_every_test=False):
778 self._add_sysinfo_loggable(sysinfo.logfile(file), on_every_test)
779
780
781 def _add_sysinfo_loggable(self, loggable, on_every_test):
782 if on_every_test:
783 self.sysinfo.test_loggables.add(loggable)
784 else:
785 self.sysinfo.boot_loggables.add(loggable)
786
787
jadmanski10646442008-08-13 14:05:21 +0000788 def _read_warnings(self):
jadmanskif37df842009-02-11 00:03:26 +0000789 """Poll all the warning loggers and extract any new warnings that have
790 been logged. If the warnings belong to a category that is currently
791 disabled, this method will discard them and they will no longer be
792 retrievable.
793
794 Returns a list of (timestamp, message) tuples, where timestamp is an
795 integer epoch timestamp."""
jadmanski10646442008-08-13 14:05:21 +0000796 warnings = []
797 while True:
798 # pull in a line of output from every logger that has
799 # output ready to be read
mbligh2b92b862008-11-22 13:25:32 +0000800 loggers, _, _ = select.select(self.warning_loggers, [], [], 0)
jadmanski10646442008-08-13 14:05:21 +0000801 closed_loggers = set()
802 for logger in loggers:
803 line = logger.readline()
804 # record any broken pipes (aka line == empty)
805 if len(line) == 0:
806 closed_loggers.add(logger)
807 continue
jadmanskif37df842009-02-11 00:03:26 +0000808 # parse out the warning
809 timestamp, msgtype, msg = line.split('\t', 2)
810 timestamp = int(timestamp)
811 # if the warning is valid, add it to the results
812 if self.warning_manager.is_valid(timestamp, msgtype):
813 warnings.append((timestamp, msg.strip()))
jadmanski10646442008-08-13 14:05:21 +0000814
815 # stop listening to loggers that are closed
816 self.warning_loggers -= closed_loggers
817
818 # stop if none of the loggers have any output left
819 if not loggers:
820 break
821
822 # sort into timestamp order
823 warnings.sort()
824 return warnings
825
826
showardcc929362010-01-25 21:20:41 +0000827 def _unique_subdirectory(self, base_subdirectory_name):
828 """Compute a unique results subdirectory based on the given name.
829
830 Appends base_subdirectory_name with a number as necessary to find a
831 directory name that doesn't already exist.
832 """
833 subdirectory = base_subdirectory_name
834 counter = 1
835 while os.path.exists(os.path.join(self.resultdir, subdirectory)):
836 subdirectory = base_subdirectory_name + '.' + str(counter)
837 counter += 1
838 return subdirectory
839
840
jadmanski52053632010-06-11 21:08:10 +0000841 def get_record_context(self):
842 """Returns an object representing the current job.record context.
843
844 The object returned is an opaque object with a 0-arg restore method
845 which can be called to restore the job.record context (i.e. indentation)
846 to the current level. The intention is that it should be used when
847 something external which generate job.record calls (e.g. an autotest
848 client) can fail catastrophically and the server job record state
849 needs to be reset to its original "known good" state.
850
851 @return: A context object with a 0-arg restore() method."""
852 return self._indenter.get_context()
853
854
showardcc929362010-01-25 21:20:41 +0000855 def record_summary(self, status_code, test_name, reason='', attributes=None,
856 distinguishing_attributes=(), child_test_ids=None):
857 """Record a summary test result.
858
859 @param status_code: status code string, see
860 common_lib.log.is_valid_status()
861 @param test_name: name of the test
862 @param reason: (optional) string providing detailed reason for test
863 outcome
864 @param attributes: (optional) dict of string keyvals to associate with
865 this result
866 @param distinguishing_attributes: (optional) list of attribute names
867 that should be used to distinguish identically-named test
868 results. These attributes should be present in the attributes
869 parameter. This is used to generate user-friendly subdirectory
870 names.
871 @param child_test_ids: (optional) list of test indices for test results
872 used in generating this result.
873 """
874 subdirectory_name_parts = [test_name]
875 for attribute in distinguishing_attributes:
876 assert attributes
877 assert attribute in attributes, '%s not in %s' % (attribute,
878 attributes)
879 subdirectory_name_parts.append(attributes[attribute])
880 base_subdirectory_name = '.'.join(subdirectory_name_parts)
881
882 subdirectory = self._unique_subdirectory(base_subdirectory_name)
883 subdirectory_path = os.path.join(self.resultdir, subdirectory)
884 os.mkdir(subdirectory_path)
885
886 self.record(status_code, subdirectory, test_name,
887 status=reason, optional_fields={'is_summary': True})
888
889 if attributes:
890 utils.write_keyval(subdirectory_path, attributes)
891
892 if child_test_ids:
893 ids_string = ','.join(str(test_id) for test_id in child_test_ids)
894 summary_data = {'child_test_ids': ids_string}
895 utils.write_keyval(os.path.join(subdirectory_path, 'summary_data'),
896 summary_data)
897
898
jadmanski16a7ff72009-04-01 18:19:53 +0000899 def disable_warnings(self, warning_type):
jadmanskif37df842009-02-11 00:03:26 +0000900 self.warning_manager.disable_warnings(warning_type)
jadmanski16a7ff72009-04-01 18:19:53 +0000901 self.record("INFO", None, None,
902 "disabling %s warnings" % warning_type,
903 {"warnings.disable": warning_type})
jadmanskif37df842009-02-11 00:03:26 +0000904
905
jadmanski16a7ff72009-04-01 18:19:53 +0000906 def enable_warnings(self, warning_type):
jadmanskif37df842009-02-11 00:03:26 +0000907 self.warning_manager.enable_warnings(warning_type)
jadmanski16a7ff72009-04-01 18:19:53 +0000908 self.record("INFO", None, None,
909 "enabling %s warnings" % warning_type,
910 {"warnings.enable": warning_type})
jadmanskif37df842009-02-11 00:03:26 +0000911
912
jadmanski779bd292009-03-19 17:33:33 +0000913 def get_status_log_path(self, subdir=None):
914 """Return the path to the job status log.
915
916 @param subdir - Optional paramter indicating that you want the path
917 to a subdirectory status log.
918
919 @returns The path where the status log should be.
920 """
mbligh210bae62009-04-01 18:33:13 +0000921 if self.resultdir:
922 if subdir:
923 return os.path.join(self.resultdir, subdir, "status.log")
924 else:
925 return os.path.join(self.resultdir, "status.log")
jadmanski779bd292009-03-19 17:33:33 +0000926 else:
mbligh210bae62009-04-01 18:33:13 +0000927 return None
jadmanski779bd292009-03-19 17:33:33 +0000928
929
jadmanski6bb32d72009-03-19 20:25:24 +0000930 def _update_uncollected_logs_list(self, update_func):
931 """Updates the uncollected logs list in a multi-process safe manner.
932
933 @param update_func - a function that updates the list of uncollected
934 logs. Should take one parameter, the list to be updated.
935 """
Dan Shi07e09af2013-04-12 09:31:29 -0700936 # Skip log collection if file _uncollected_log_file does not exist.
937 if not (self._uncollected_log_file and
938 os.path.exists(self._uncollected_log_file)):
939 return
mbligh0d0f67d2009-11-06 03:15:03 +0000940 if self._uncollected_log_file:
941 log_file = open(self._uncollected_log_file, "r+")
mbligha788dc42009-03-26 21:10:16 +0000942 fcntl.flock(log_file, fcntl.LOCK_EX)
jadmanski6bb32d72009-03-19 20:25:24 +0000943 try:
944 uncollected_logs = pickle.load(log_file)
945 update_func(uncollected_logs)
946 log_file.seek(0)
947 log_file.truncate()
948 pickle.dump(uncollected_logs, log_file)
jadmanski3bff9092009-04-22 18:09:47 +0000949 log_file.flush()
jadmanski6bb32d72009-03-19 20:25:24 +0000950 finally:
951 fcntl.flock(log_file, fcntl.LOCK_UN)
952 log_file.close()
953
954
955 def add_client_log(self, hostname, remote_path, local_path):
956 """Adds a new set of client logs to the list of uncollected logs,
957 to allow for future log recovery.
958
959 @param host - the hostname of the machine holding the logs
960 @param remote_path - the directory on the remote machine holding logs
961 @param local_path - the local directory to copy the logs into
962 """
963 def update_func(logs_list):
964 logs_list.append((hostname, remote_path, local_path))
965 self._update_uncollected_logs_list(update_func)
966
967
968 def remove_client_log(self, hostname, remote_path, local_path):
969 """Removes a set of client logs from the list of uncollected logs,
970 to allow for future log recovery.
971
972 @param host - the hostname of the machine holding the logs
973 @param remote_path - the directory on the remote machine holding logs
974 @param local_path - the local directory to copy the logs into
975 """
976 def update_func(logs_list):
977 logs_list.remove((hostname, remote_path, local_path))
978 self._update_uncollected_logs_list(update_func)
979
980
mbligh0d0f67d2009-11-06 03:15:03 +0000981 def get_client_logs(self):
982 """Retrieves the list of uncollected logs, if it exists.
983
984 @returns A list of (host, remote_path, local_path) tuples. Returns
985 an empty list if no uncollected logs file exists.
986 """
987 log_exists = (self._uncollected_log_file and
988 os.path.exists(self._uncollected_log_file))
989 if log_exists:
990 return pickle.load(open(self._uncollected_log_file))
991 else:
992 return []
993
994
mbligh084bc172008-10-18 14:02:45 +0000995 def _fill_server_control_namespace(self, namespace, protect=True):
mbligh2b92b862008-11-22 13:25:32 +0000996 """
997 Prepare a namespace to be used when executing server control files.
mbligh084bc172008-10-18 14:02:45 +0000998
999 This sets up the control file API by importing modules and making them
1000 available under the appropriate names within namespace.
1001
1002 For use by _execute_code().
1003
1004 Args:
1005 namespace: The namespace dictionary to fill in.
1006 protect: Boolean. If True (the default) any operation that would
1007 clobber an existing entry in namespace will cause an error.
1008 Raises:
1009 error.AutoservError: When a name would be clobbered by import.
1010 """
1011 def _import_names(module_name, names=()):
mbligh2b92b862008-11-22 13:25:32 +00001012 """
1013 Import a module and assign named attributes into namespace.
mbligh084bc172008-10-18 14:02:45 +00001014
1015 Args:
1016 module_name: The string module name.
1017 names: A limiting list of names to import from module_name. If
1018 empty (the default), all names are imported from the module
1019 similar to a "from foo.bar import *" statement.
1020 Raises:
1021 error.AutoservError: When a name being imported would clobber
1022 a name already in namespace.
1023 """
1024 module = __import__(module_name, {}, {}, names)
1025
1026 # No names supplied? Import * from the lowest level module.
1027 # (Ugh, why do I have to implement this part myself?)
1028 if not names:
1029 for submodule_name in module_name.split('.')[1:]:
1030 module = getattr(module, submodule_name)
1031 if hasattr(module, '__all__'):
1032 names = getattr(module, '__all__')
1033 else:
1034 names = dir(module)
1035
1036 # Install each name into namespace, checking to make sure it
1037 # doesn't override anything that already exists.
1038 for name in names:
1039 # Check for conflicts to help prevent future problems.
1040 if name in namespace and protect:
1041 if namespace[name] is not getattr(module, name):
1042 raise error.AutoservError('importing name '
1043 '%s from %s %r would override %r' %
1044 (name, module_name, getattr(module, name),
1045 namespace[name]))
1046 else:
1047 # Encourage cleanliness and the use of __all__ for a
1048 # more concrete API with less surprises on '*' imports.
1049 warnings.warn('%s (%r) being imported from %s for use '
1050 'in server control files is not the '
1051 'first occurrance of that import.' %
1052 (name, namespace[name], module_name))
1053
1054 namespace[name] = getattr(module, name)
1055
1056
1057 # This is the equivalent of prepending a bunch of import statements to
1058 # the front of the control script.
mbligha2b07dd2009-06-22 18:26:13 +00001059 namespace.update(os=os, sys=sys, logging=logging)
mbligh084bc172008-10-18 14:02:45 +00001060 _import_names('autotest_lib.server',
1061 ('hosts', 'autotest', 'kvm', 'git', 'standalone_profiler',
1062 'source_kernel', 'rpm_kernel', 'deb_kernel', 'git_kernel'))
1063 _import_names('autotest_lib.server.subcommand',
1064 ('parallel', 'parallel_simple', 'subcommand'))
1065 _import_names('autotest_lib.server.utils',
1066 ('run', 'get_tmp_dir', 'sh_escape', 'parse_machine'))
1067 _import_names('autotest_lib.client.common_lib.error')
1068 _import_names('autotest_lib.client.common_lib.barrier', ('barrier',))
1069
1070 # Inject ourself as the job object into other classes within the API.
1071 # (Yuck, this injection is a gross thing be part of a public API. -gps)
1072 #
1073 # XXX Base & SiteAutotest do not appear to use .job. Who does?
1074 namespace['autotest'].Autotest.job = self
1075 # server.hosts.base_classes.Host uses .job.
1076 namespace['hosts'].Host.job = self
Eric Li10222b82010-11-24 09:33:15 -08001077 namespace['hosts'].factory.ssh_user = self._ssh_user
1078 namespace['hosts'].factory.ssh_port = self._ssh_port
1079 namespace['hosts'].factory.ssh_pass = self._ssh_pass
Fang Dengd1c2b732013-08-20 12:59:46 -07001080 namespace['hosts'].factory.ssh_verbosity_flag = (
1081 self._ssh_verbosity_flag)
Aviv Keshetc5947fa2013-09-04 14:06:29 -07001082 namespace['hosts'].factory.ssh_options = self._ssh_options
mbligh084bc172008-10-18 14:02:45 +00001083
1084
1085 def _execute_code(self, code_file, namespace, protect=True):
mbligh2b92b862008-11-22 13:25:32 +00001086 """
1087 Execute code using a copy of namespace as a server control script.
mbligh084bc172008-10-18 14:02:45 +00001088
1089 Unless protect_namespace is explicitly set to False, the dict will not
1090 be modified.
1091
1092 Args:
1093 code_file: The filename of the control file to execute.
1094 namespace: A dict containing names to make available during execution.
1095 protect: Boolean. If True (the default) a copy of the namespace dict
1096 is used during execution to prevent the code from modifying its
1097 contents outside of this function. If False the raw dict is
1098 passed in and modifications will be allowed.
1099 """
1100 if protect:
1101 namespace = namespace.copy()
1102 self._fill_server_control_namespace(namespace, protect=protect)
1103 # TODO: Simplify and get rid of the special cases for only 1 machine.
showard3e66e8c2008-10-27 19:20:51 +00001104 if len(self.machines) > 1:
mbligh084bc172008-10-18 14:02:45 +00001105 machines_text = '\n'.join(self.machines) + '\n'
1106 # Only rewrite the file if it does not match our machine list.
1107 try:
1108 machines_f = open(MACHINES_FILENAME, 'r')
1109 existing_machines_text = machines_f.read()
1110 machines_f.close()
1111 except EnvironmentError:
1112 existing_machines_text = None
1113 if machines_text != existing_machines_text:
1114 utils.open_write_close(MACHINES_FILENAME, machines_text)
1115 execfile(code_file, namespace, namespace)
jadmanski10646442008-08-13 14:05:21 +00001116
1117
jadmanskie29d0e42010-06-17 16:06:52 +00001118 def _parse_status(self, new_line):
mbligh0d0f67d2009-11-06 03:15:03 +00001119 if not self._using_parser:
jadmanski10646442008-08-13 14:05:21 +00001120 return
jadmanskie29d0e42010-06-17 16:06:52 +00001121 new_tests = self.parser.process_lines([new_line])
jadmanski10646442008-08-13 14:05:21 +00001122 for test in new_tests:
1123 self.__insert_test(test)
1124
1125
1126 def __insert_test(self, test):
mbligh2b92b862008-11-22 13:25:32 +00001127 """
1128 An internal method to insert a new test result into the
jadmanski10646442008-08-13 14:05:21 +00001129 database. This method will not raise an exception, even if an
1130 error occurs during the insert, to avoid failing a test
1131 simply because of unexpected database issues."""
showard21baa452008-10-21 00:08:39 +00001132 self.num_tests_run += 1
1133 if status_lib.is_worse_than_or_equal_to(test.status, 'FAIL'):
1134 self.num_tests_failed += 1
jadmanski10646442008-08-13 14:05:21 +00001135 try:
1136 self.results_db.insert_test(self.job_model, test)
1137 except Exception:
1138 msg = ("WARNING: An unexpected error occured while "
1139 "inserting test results into the database. "
1140 "Ignoring error.\n" + traceback.format_exc())
1141 print >> sys.stderr, msg
1142
mblighcaa62c22008-04-07 21:51:17 +00001143
mblighfc3da5b2010-01-06 18:37:22 +00001144 def preprocess_client_state(self):
1145 """
1146 Produce a state file for initializing the state of a client job.
1147
1148 Creates a new client state file with all the current server state, as
1149 well as some pre-set client state.
1150
1151 @returns The path of the file the state was written into.
1152 """
1153 # initialize the sysinfo state
1154 self._state.set('client', 'sysinfo', self.sysinfo.serialize())
1155
1156 # dump the state out to a tempfile
1157 fd, file_path = tempfile.mkstemp(dir=self.tmpdir)
1158 os.close(fd)
mbligha2c99492010-01-27 22:59:50 +00001159
1160 # write_to_file doesn't need locking, we exclusively own file_path
mblighfc3da5b2010-01-06 18:37:22 +00001161 self._state.write_to_file(file_path)
1162 return file_path
1163
1164
1165 def postprocess_client_state(self, state_path):
1166 """
1167 Update the state of this job with the state from a client job.
1168
1169 Updates the state of the server side of a job with the final state
1170 of a client job that was run. Updates the non-client-specific state,
1171 pulls in some specific bits from the client-specific state, and then
1172 discards the rest. Removes the state file afterwards
1173
1174 @param state_file A path to the state file from the client.
1175 """
1176 # update the on-disk state
mblighfc3da5b2010-01-06 18:37:22 +00001177 try:
jadmanskib6e7bdb2010-04-13 16:00:39 +00001178 self._state.read_from_file(state_path)
mblighfc3da5b2010-01-06 18:37:22 +00001179 os.remove(state_path)
mbligha2c99492010-01-27 22:59:50 +00001180 except OSError, e:
mblighfc3da5b2010-01-06 18:37:22 +00001181 # ignore file-not-found errors
1182 if e.errno != errno.ENOENT:
1183 raise
jadmanskib6e7bdb2010-04-13 16:00:39 +00001184 else:
1185 logging.debug('Client state file %s not found', state_path)
mblighfc3da5b2010-01-06 18:37:22 +00001186
1187 # update the sysinfo state
1188 if self._state.has('client', 'sysinfo'):
1189 self.sysinfo.deserialize(self._state.get('client', 'sysinfo'))
1190
1191 # drop all the client-specific state
1192 self._state.discard_namespace('client')
1193
1194
mbligh0a883702010-04-21 01:58:34 +00001195 def clear_all_known_hosts(self):
1196 """Clears known hosts files for all AbstractSSHHosts."""
1197 for host in self.hosts:
1198 if isinstance(host, abstract_ssh.AbstractSSHHost):
1199 host.clear_known_hosts()
1200
1201
jadmanskif37df842009-02-11 00:03:26 +00001202class warning_manager(object):
1203 """Class for controlling warning logs. Manages the enabling and disabling
1204 of warnings."""
1205 def __init__(self):
1206 # a map of warning types to a list of disabled time intervals
1207 self.disabled_warnings = {}
1208
1209
1210 def is_valid(self, timestamp, warning_type):
1211 """Indicates if a warning (based on the time it occured and its type)
1212 is a valid warning. A warning is considered "invalid" if this type of
1213 warning was marked as "disabled" at the time the warning occured."""
1214 disabled_intervals = self.disabled_warnings.get(warning_type, [])
1215 for start, end in disabled_intervals:
1216 if timestamp >= start and (end is None or timestamp < end):
1217 return False
1218 return True
1219
1220
1221 def disable_warnings(self, warning_type, current_time_func=time.time):
1222 """As of now, disables all further warnings of this type."""
1223 intervals = self.disabled_warnings.setdefault(warning_type, [])
1224 if not intervals or intervals[-1][1] is not None:
jadmanski16a7ff72009-04-01 18:19:53 +00001225 intervals.append((int(current_time_func()), None))
jadmanskif37df842009-02-11 00:03:26 +00001226
1227
1228 def enable_warnings(self, warning_type, current_time_func=time.time):
1229 """As of now, enables all further warnings of this type."""
1230 intervals = self.disabled_warnings.get(warning_type, [])
1231 if intervals and intervals[-1][1] is None:
jadmanski16a7ff72009-04-01 18:19:53 +00001232 intervals[-1] = (intervals[-1][0], int(current_time_func()))
Paul Pendlebury57593562011-06-15 10:45:49 -07001233
1234
1235# load up site-specific code for generating site-specific job data
1236get_site_job_data = utils.import_site_function(__file__,
1237 "autotest_lib.server.site_server_job", "get_site_job_data",
1238 _get_site_job_data_dummy)
1239
1240
1241site_server_job = utils.import_site_class(
1242 __file__, "autotest_lib.server.site_server_job", "site_server_job",
1243 base_server_job)
1244
1245
1246class server_job(site_server_job):
Dale Curtis456d3c12011-07-19 11:42:51 -07001247 pass