blob: aa7212ec5f02766949989c9ac2fd611bf567a71a [file] [log] [blame]
mbligh6203ace2007-10-04 21:54:24 +00001#!/usr/bin/python -u
mbligh1ffd5dc2008-11-25 13:24:05 +00002# Copyright 2007-2008 Martin J. Bligh <mbligh@google.com>, Google Inc.
mbligh82648e52008-11-20 16:54:25 +00003# Released under the GPL v2
mblighdcd57a82007-07-11 23:06:47 +00004
mblighc8949b82007-07-23 16:33:58 +00005"""
Aviv Keshetde6bb192013-01-30 16:17:22 -08006Run a control file through the server side engine
mblighdcd57a82007-07-11 23:06:47 +00007"""
mbligh1ffd5dc2008-11-25 13:24:05 +00008
Fang Deng042c1472014-10-23 13:56:41 -07009import ast
10import datetime
11import getpass
12import logging
13import os
14import re
15import signal
Dan Shicf4d2032015-03-12 15:04:21 -070016import socket
Fang Deng042c1472014-10-23 13:56:41 -070017import sys
18import traceback
19import time
20import urllib2
mbligh1ffd5dc2008-11-25 13:24:05 +000021
mblighf5427bb2008-04-09 15:55:57 +000022import common
mbligh9ff89cd2009-09-03 20:28:17 +000023
Dan Shi7836d252015-04-27 15:33:58 -070024from autotest_lib.client.bin import utils
Dan Shia1ecd5c2013-06-06 11:21:31 -070025from autotest_lib.client.common_lib import control_data
26from autotest_lib.client.common_lib import global_config
Dan Shi37bee222015-04-13 15:46:47 -070027from autotest_lib.client.common_lib.cros.graphite import autotest_es
28from autotest_lib.client.common_lib.cros.graphite import autotest_stats
Prashanth Balasubramanianf8b83712014-11-06 15:58:21 -080029try:
30 from autotest_lib.puppylab import results_mocker
31except ImportError:
32 results_mocker = None
33
Dan Shia1ecd5c2013-06-06 11:21:31 -070034require_atfork = global_config.global_config.get_config_value(
mblighcb8cb332009-09-03 21:08:56 +000035 'AUTOSERV', 'require_atfork_module', type=bool, default=True)
36
Dan Shia1ecd5c2013-06-06 11:21:31 -070037
Jakob Jueliche497b552014-09-23 19:11:59 -070038# Number of seconds to wait before returning if testing mode is enabled
Prashanth B6285f6a2014-05-08 18:01:27 -070039TESTING_MODE_SLEEP_SECS = 1
Jakob Jueliche497b552014-09-23 19:11:59 -070040
mblighcb8cb332009-09-03 21:08:56 +000041try:
42 import atfork
43 atfork.monkeypatch_os_fork_functions()
44 import atfork.stdlib_fixer
45 # Fix the Python standard library for threading+fork safety with its
46 # internal locks. http://code.google.com/p/python-atfork/
47 import warnings
48 warnings.filterwarnings('ignore', 'logging module already imported')
49 atfork.stdlib_fixer.fix_logging_module()
50except ImportError, e:
51 from autotest_lib.client.common_lib import global_config
52 if global_config.global_config.get_config_value(
53 'AUTOSERV', 'require_atfork_module', type=bool, default=False):
54 print >>sys.stderr, 'Please run utils/build_externals.py'
55 print e
56 sys.exit(1)
mbligh9ff89cd2009-09-03 20:28:17 +000057
Dan Shia1ecd5c2013-06-06 11:21:31 -070058from autotest_lib.server import frontend
showard75cdfee2009-06-10 17:40:41 +000059from autotest_lib.server import server_logging_config
showard043c62a2009-06-10 19:48:57 +000060from autotest_lib.server import server_job, utils, autoserv_parser, autotest
Dan Shia1ecd5c2013-06-06 11:21:31 -070061from autotest_lib.server import utils as server_utils
Dan Shicf4d2032015-03-12 15:04:21 -070062from autotest_lib.site_utils import job_directories
Fang Deng042c1472014-10-23 13:56:41 -070063from autotest_lib.site_utils import job_overhead
Dan Shicf4d2032015-03-12 15:04:21 -070064from autotest_lib.site_utils import lxc
Dan Shi7836d252015-04-27 15:33:58 -070065from autotest_lib.site_utils import lxc_utils
showard75cdfee2009-06-10 17:40:41 +000066from autotest_lib.client.common_lib import pidfile, logging_manager
Gabe Black1e1c41b2015-02-04 23:55:15 -080067from autotest_lib.client.common_lib.cros.graphite import autotest_stats
mbligh92c0fc22008-11-20 16:52:23 +000068
Dan Shicf4d2032015-03-12 15:04:21 -070069# Control segment to stage server-side package.
70STAGE_SERVER_SIDE_PACKAGE_CONTROL_FILE = server_job._control_segment_path(
71 'stage_server_side_package')
72
Alex Millerf1af17e2013-01-09 22:50:32 -080073def log_alarm(signum, frame):
74 logging.error("Received SIGALARM. Ignoring and continuing on.")
Alex Miller0528d6f2013-01-11 10:49:48 -080075 sys.exit(1)
Alex Millerf1af17e2013-01-09 22:50:32 -080076
Dan Shicf4d2032015-03-12 15:04:21 -070077
78def _get_machines(parser):
79 """Get a list of machine names from command line arg -m or a file.
80
81 @param parser: Parser for the command line arguments.
82
83 @return: A list of machine names from command line arg -m or the
84 machines file specified in the command line arg -M.
85 """
86 if parser.options.machines:
87 machines = parser.options.machines.replace(',', ' ').strip().split()
88 else:
89 machines = []
90 machines_file = parser.options.machines_file
91 if machines_file:
92 machines = []
93 for m in open(machines_file, 'r').readlines():
94 # remove comments, spaces
95 m = re.sub('#.*', '', m).strip()
96 if m:
97 machines.append(m)
98 logging.debug('Read list of machines from file: %s', machines_file)
99 logging.debug('Machines: %s', ','.join(machines))
100
101 if machines:
102 for machine in machines:
103 if not machine or re.search('\s', machine):
104 parser.parser.error("Invalid machine: %s" % str(machine))
105 machines = list(set(machines))
106 machines.sort()
107 return machines
108
109
110def _stage_ssp(parser):
111 """Stage server-side package.
112
113 This function calls a control segment to stage server-side package based on
114 the job and autoserv command line option. The detail implementation could
115 be different for each host type. Currently, only CrosHost has
116 stage_server_side_package function defined.
117 The script returns None if no server-side package is available. However,
118 it may raise exception if it failed for reasons other than artifact (the
119 server-side package) not found.
120
121 @param parser: Command line arguments parser passed in the autoserv process.
122
123 @return: url of the staged server-side package. Return None if server-
124 side package is not found for the build.
125 """
126 namespace = {'machines': _get_machines(parser),
127 'image': parser.options.image}
128 script_locals = {}
129 execfile(STAGE_SERVER_SIDE_PACKAGE_CONTROL_FILE, namespace, script_locals)
130 return script_locals['ssp_url']
131
132
133def _run_with_ssp(container_name, job_id, results, parser, ssp_url):
134 """Run the server job with server-side packaging.
135
136 @param container_name: Name of the container to run the test.
137 @param job_id: ID of the test job.
138 @param results: Folder to store results. This could be different from
139 parser.options.results:
140 parser.options.results can be set to None for results to be
141 stored in a temp folder.
142 results can be None for autoserv run requires no logging.
143 @param parser: Command line parser that contains the options.
144 @param ssp_url: url of the staged server-side package.
145 """
146 bucket = lxc.ContainerBucket()
147 control = (parser.args[0] if len(parser.args) > 0 and parser.args[0] != ''
148 else None)
149 test_container = bucket.setup_test(container_name, job_id, ssp_url, results,
150 control=control)
151 args = sys.argv[:]
152 args.remove('--require-ssp')
153
154 # A dictionary of paths to replace in the command line. Key is the path to
155 # be replaced with the one in value.
156 paths_to_replace = {}
157 # Replace the control file path with the one in container.
158 if control:
159 container_control_filename = os.path.join(
160 lxc.CONTROL_TEMP_PATH, os.path.basename(control))
161 paths_to_replace[control] = container_control_filename
162 # Update result directory with the one in container.
163 if parser.options.results:
164 container_result_dir = os.path.join(lxc.RESULT_DIR_FMT % job_id)
165 paths_to_replace[parser.options.results] = container_result_dir
166 # Update parse_job directory with the one in container. The assumption is
167 # that the result folder to be parsed is always the same as the results_dir.
168 if parser.options.parse_job:
169 container_parse_dir = os.path.join(lxc.RESULT_DIR_FMT % job_id)
170 paths_to_replace[parser.options.parse_job] = container_result_dir
171
172 args = [paths_to_replace.get(arg, arg) for arg in args]
173
174 # Apply --use-existing-results, results directory is aready created and
175 # mounted in container. Apply this arg to avoid exception being raised.
176 if not '--use-existing-results' in args:
177 args.append('--use-existing-results')
178
179 # Make sure autoserv running in container using a different pid file.
180 if not '--pidfile-label' in args:
181 args.extend(['--pidfile-label', 'container_autoserv'])
182
Dan Shid1f51232015-04-18 00:29:14 -0700183 cmd_line = ' '.join(["'%s'" % arg if ' ' in arg else arg for arg in args])
Dan Shicf4d2032015-03-12 15:04:21 -0700184 logging.info('Run command in container: %s', cmd_line)
Dan Shi37bee222015-04-13 15:46:47 -0700185 success = False
Dan Shicf4d2032015-03-12 15:04:21 -0700186 try:
187 test_container.attach_run(cmd_line)
Dan Shi37bee222015-04-13 15:46:47 -0700188 success = True
Dan Shicf4d2032015-03-12 15:04:21 -0700189 finally:
Dan Shi37bee222015-04-13 15:46:47 -0700190 counter_key = '%s.%s' % (lxc.STATS_KEY,
191 'success' if success else 'fail')
192 autotest_stats.Counter(counter_key).increment()
193 # metadata is uploaded separately so it can use http to upload.
194 metadata = {'drone': socket.gethostname(),
195 'job_id': job_id,
196 'success': success}
197 autotest_es.post(use_http=True,
198 type_str=lxc.CONTAINER_RUN_TEST_METADB_TYPE,
199 metadata=metadata)
Dan Shicf4d2032015-03-12 15:04:21 -0700200 test_container.destroy()
201
202
Dan Shi3f1b8a52015-04-21 11:11:06 -0700203def correct_results_folder_permission(results):
204 """Make sure the results folder has the right permission settings.
205
206 For tests running with server-side packaging, the results folder has the
207 owner of root. This must be changed to the user running the autoserv
208 process, so parsing job can access the results folder.
209 TODO(dshi): crbug.com/459344 Remove this function when test container can be
210 unprivileged container.
211
212 @param results: Path to the results folder.
213
214 """
215 if not results:
216 return
217
Dan Shi7836d252015-04-27 15:33:58 -0700218 utils.run('sudo chown -R %s "%s"' % (os.getuid(), results))
219 utils.run('sudo chgrp -R %s "%s"' % (os.getgid(), results))
Dan Shi3f1b8a52015-04-21 11:11:06 -0700220
221
Dan Shic68fefb2015-04-07 10:10:52 -0700222def run_autoserv(pid_file_manager, results, parser, ssp_url, use_ssp):
Dan Shicf4d2032015-03-12 15:04:21 -0700223 """Run server job with given options.
224
225 @param pid_file_manager: PidFileManager used to monitor the autoserv process
226 @param results: Folder to store results.
227 @param parser: Parser for the command line arguments.
228 @param ssp_url: Url to server-side package.
Dan Shic68fefb2015-04-07 10:10:52 -0700229 @param use_ssp: Set to True to run with server-side packaging.
Dan Shicf4d2032015-03-12 15:04:21 -0700230 """
Dan Shiec1d47d2015-02-13 11:38:13 -0800231 if parser.options.warn_no_ssp:
Dan Shic68fefb2015-04-07 10:10:52 -0700232 # Post a warning in the log.
Dan Shiec1d47d2015-02-13 11:38:13 -0800233 logging.warn('Autoserv is required to run with server-side packaging. '
234 'However, no drone is found to support server-side '
235 'packaging. The test will be executed in a drone without '
236 'server-side packaging supported.')
237
jadmanski0afbb632008-06-06 21:10:57 +0000238 # send stdin to /dev/null
239 dev_null = os.open(os.devnull, os.O_RDONLY)
240 os.dup2(dev_null, sys.stdin.fileno())
241 os.close(dev_null)
mblighdbf37612007-11-24 19:38:11 +0000242
jadmanski0afbb632008-06-06 21:10:57 +0000243 # Create separate process group
244 os.setpgrp()
mbligh1d42d4e2007-11-05 22:42:00 +0000245
Dan Shicf4d2032015-03-12 15:04:21 -0700246 # Container name is predefined so the container can be destroyed in
247 # handle_sigterm.
248 job_or_task_id = job_directories.get_job_id_or_task_id(
249 parser.options.results)
250 container_name = (lxc.TEST_CONTAINER_NAME_FMT %
Dan Shid68d51c2015-04-21 17:00:42 -0700251 (job_or_task_id, time.time(), os.getpid()))
Dan Shicf4d2032015-03-12 15:04:21 -0700252
jadmanski0afbb632008-06-06 21:10:57 +0000253 # Implement SIGTERM handler
mblighc2299562009-07-02 19:00:36 +0000254 def handle_sigterm(signum, frame):
Simran Basi9d9b7292013-10-16 16:44:07 -0700255 logging.debug('Received SIGTERM')
mblighff7d61f2008-12-22 14:53:35 +0000256 if pid_file_manager:
257 pid_file_manager.close_file(1, signal.SIGTERM)
Simran Basi49e21e62013-10-17 12:40:33 -0700258 logging.debug('Finished writing to pid_file. Killing process.')
Dan Shi3f1b8a52015-04-21 11:11:06 -0700259
260 # Update results folder's file permission. This needs to be done ASAP
261 # before the parsing process tries to access the log.
262 if use_ssp and results:
263 correct_results_folder_permission(results)
264
Simran Basid6b83772014-01-06 16:31:30 -0800265 # TODO (sbasi) - remove the time.sleep when crbug.com/302815 is solved.
266 # This sleep allows the pending output to be logged before the kill
267 # signal is sent.
268 time.sleep(.1)
Dan Shic68fefb2015-04-07 10:10:52 -0700269 if use_ssp:
Dan Shicf4d2032015-03-12 15:04:21 -0700270 logging.debug('Destroy container %s before aborting the autoserv '
271 'process.', container_name)
Dan Shi3f1b8a52015-04-21 11:11:06 -0700272 metadata = {'drone': socket.gethostname(),
273 'job_id': job_or_task_id,
274 'container_name': container_name,
275 'action': 'abort',
276 'success': True}
Dan Shicf4d2032015-03-12 15:04:21 -0700277 try:
278 bucket = lxc.ContainerBucket()
279 container = bucket.get(container_name)
280 if container:
281 container.destroy()
282 else:
Dan Shi3f1b8a52015-04-21 11:11:06 -0700283 metadata['success'] = False
284 metadata['error'] = 'container not found'
Dan Shicf4d2032015-03-12 15:04:21 -0700285 logging.debug('Container %s is not found.', container_name)
286 except:
Dan Shi3f1b8a52015-04-21 11:11:06 -0700287 metadata['success'] = False
288 metadata['error'] = 'Exception: %s' % sys.exc_info()
Dan Shicf4d2032015-03-12 15:04:21 -0700289 # Handle any exception so the autoserv process can be aborted.
290 logging.error('Failed to destroy container %s. Error: %s',
291 container_name, sys.exc_info())
Dan Shi3f1b8a52015-04-21 11:11:06 -0700292 autotest_es.post(use_http=True,
293 type_str=lxc.CONTAINER_RUN_TEST_METADB_TYPE,
294 metadata=metadata)
Dan Shicf4d2032015-03-12 15:04:21 -0700295
jadmanski0afbb632008-06-06 21:10:57 +0000296 os.killpg(os.getpgrp(), signal.SIGKILL)
mblighfaf0cd42007-11-19 16:00:24 +0000297
jadmanski0afbb632008-06-06 21:10:57 +0000298 # Set signal handler
mblighc2299562009-07-02 19:00:36 +0000299 signal.signal(signal.SIGTERM, handle_sigterm)
mbligha46678d2008-05-01 20:00:01 +0000300
Simran Basid6b83772014-01-06 16:31:30 -0800301 # faulthandler is only needed to debug in the Lab and is not avaliable to
302 # be imported in the chroot as part of VMTest, so Try-Except it.
303 try:
304 import faulthandler
305 faulthandler.register(signal.SIGTERM, all_threads=True, chain=True)
306 logging.debug('faulthandler registered on SIGTERM.')
307 except ImportError:
308 pass
309
David Rochberg8a60d1e2011-02-01 14:22:07 -0500310 # Ignore SIGTTOU's generated by output from forked children.
311 signal.signal(signal.SIGTTOU, signal.SIG_IGN)
312
Alex Millerf1af17e2013-01-09 22:50:32 -0800313 # If we received a SIGALARM, let's be loud about it.
314 signal.signal(signal.SIGALRM, log_alarm)
315
mbligha5f5e542009-12-30 16:57:49 +0000316 # Server side tests that call shell scripts often depend on $USER being set
317 # but depending on how you launch your autotest scheduler it may not be set.
318 os.environ['USER'] = getpass.getuser()
319
mblighb2bea302008-07-24 20:25:57 +0000320 label = parser.options.label
mbligh374f3412009-05-13 21:29:45 +0000321 group_name = parser.options.group_name
mblighb2bea302008-07-24 20:25:57 +0000322 user = parser.options.user
323 client = parser.options.client
324 server = parser.options.server
jadmanski0afbb632008-06-06 21:10:57 +0000325 install_before = parser.options.install_before
mblighb2bea302008-07-24 20:25:57 +0000326 install_after = parser.options.install_after
327 verify = parser.options.verify
328 repair = parser.options.repair
showard45ae8192008-11-05 19:32:53 +0000329 cleanup = parser.options.cleanup
Alex Millercb79ba72013-05-29 14:43:00 -0700330 provision = parser.options.provision
Dan Shi07e09af2013-04-12 09:31:29 -0700331 reset = parser.options.reset
Alex Miller667b5f22014-02-28 15:33:39 -0800332 job_labels = parser.options.job_labels
mblighb2bea302008-07-24 20:25:57 +0000333 no_tee = parser.options.no_tee
jadmanski0afbb632008-06-06 21:10:57 +0000334 parse_job = parser.options.parse_job
mblighe7d9c602009-07-02 19:02:33 +0000335 execution_tag = parser.options.execution_tag
336 if not execution_tag:
337 execution_tag = parse_job
jadmanskifbc1f0a2008-07-09 14:12:54 +0000338 host_protection = parser.options.host_protection
jadmanski0afbb632008-06-06 21:10:57 +0000339 ssh_user = parser.options.ssh_user
340 ssh_port = parser.options.ssh_port
341 ssh_pass = parser.options.ssh_pass
jadmanskidef0c3c2009-03-25 20:07:10 +0000342 collect_crashinfo = parser.options.collect_crashinfo
mblighe0cbc912010-03-11 18:03:07 +0000343 control_filename = parser.options.control_filename
Scott Zawalski91493c82013-01-25 16:15:20 -0500344 test_retry = parser.options.test_retry
beepscb6f1e22013-06-28 19:14:10 -0700345 verify_job_repo_url = parser.options.verify_job_repo_url
Christopher Wileyf594c5e2013-07-03 18:25:30 -0700346 skip_crash_collection = parser.options.skip_crash_collection
Aviv Keshet18ee3142013-08-12 15:01:51 -0700347 ssh_verbosity = int(parser.options.ssh_verbosity)
Fang Deng6cc20de2013-09-06 15:47:32 -0700348 ssh_options = parser.options.ssh_options
Dan Shib669cbd2013-09-13 11:17:17 -0700349 no_use_packaging = parser.options.no_use_packaging
mbligha46678d2008-05-01 20:00:01 +0000350
mblighb2bea302008-07-24 20:25:57 +0000351 # can't be both a client and a server side test
352 if client and server:
Eric Li861b2d52011-02-04 14:50:35 -0800353 parser.parser.error("Can not specify a test as both server and client!")
mblighb2bea302008-07-24 20:25:57 +0000354
Alex Millercb79ba72013-05-29 14:43:00 -0700355 if provision and client:
356 parser.parser.error("Cannot specify provisioning and client!")
357
358 is_special_task = (verify or repair or cleanup or collect_crashinfo or
Dan Shi07e09af2013-04-12 09:31:29 -0700359 provision or reset)
Alex Millercb79ba72013-05-29 14:43:00 -0700360 if len(parser.args) < 1 and not is_special_task:
Eric Li861b2d52011-02-04 14:50:35 -0800361 parser.parser.error("Missing argument: control file")
mbligha46678d2008-05-01 20:00:01 +0000362
Aviv Keshet18ee3142013-08-12 15:01:51 -0700363 if ssh_verbosity > 0:
364 # ssh_verbosity is an integer between 0 and 3, inclusive
365 ssh_verbosity_flag = '-' + 'v' * ssh_verbosity
Fang Dengd1c2b732013-08-20 12:59:46 -0700366 else:
367 ssh_verbosity_flag = ''
Aviv Keshet18ee3142013-08-12 15:01:51 -0700368
showard45ae8192008-11-05 19:32:53 +0000369 # We have a control file unless it's just a verify/repair/cleanup job
jadmanski0afbb632008-06-06 21:10:57 +0000370 if len(parser.args) > 0:
371 control = parser.args[0]
372 else:
373 control = None
mbligha46678d2008-05-01 20:00:01 +0000374
Dan Shicf4d2032015-03-12 15:04:21 -0700375 machines = _get_machines(parser)
mbligh374f3412009-05-13 21:29:45 +0000376 if group_name and len(machines) < 2:
Dan Shicf4d2032015-03-12 15:04:21 -0700377 parser.parser.error('-G %r may only be supplied with more than one '
378 'machine.' % group_name)
mbligh374f3412009-05-13 21:29:45 +0000379
Christopher Wiley8a91f232013-07-09 11:02:27 -0700380 kwargs = {'group_name': group_name, 'tag': execution_tag,
Dan Shicf4d2032015-03-12 15:04:21 -0700381 'disable_sysinfo': parser.options.disable_sysinfo}
mblighe0cbc912010-03-11 18:03:07 +0000382 if control_filename:
383 kwargs['control_filename'] = control_filename
jadmanski0afbb632008-06-06 21:10:57 +0000384 job = server_job.server_job(control, parser.args[1:], results, label,
385 user, machines, client, parse_job,
Fang Dengd1c2b732013-08-20 12:59:46 -0700386 ssh_user, ssh_port, ssh_pass,
Aviv Keshetc5947fa2013-09-04 14:06:29 -0700387 ssh_verbosity_flag, ssh_options,
388 test_retry, **kwargs)
Dan Shicf4d2032015-03-12 15:04:21 -0700389
showard75cdfee2009-06-10 17:40:41 +0000390 job.logging.start_logging()
mbligh4608b002010-01-05 18:22:35 +0000391 job.init_parser()
mbligha46678d2008-05-01 20:00:01 +0000392
mbligh161fe6f2008-06-19 16:26:04 +0000393 # perform checks
394 job.precheck()
395
jadmanski0afbb632008-06-06 21:10:57 +0000396 # run the job
397 exit_code = 0
398 try:
mbligh332000a2009-06-08 16:47:28 +0000399 try:
400 if repair:
Alex Miller667b5f22014-02-28 15:33:39 -0800401 job.repair(host_protection, job_labels)
mbligh332000a2009-06-08 16:47:28 +0000402 elif verify:
Alex Miller667b5f22014-02-28 15:33:39 -0800403 job.verify(job_labels)
Alex Millercb79ba72013-05-29 14:43:00 -0700404 elif provision:
Alex Miller667b5f22014-02-28 15:33:39 -0800405 job.provision(job_labels)
Dan Shi07e09af2013-04-12 09:31:29 -0700406 elif reset:
Alex Miller667b5f22014-02-28 15:33:39 -0800407 job.reset(job_labels)
Fang Dengad78aca2014-10-02 18:15:46 -0700408 elif cleanup:
409 job.cleanup(job_labels)
mbligh332000a2009-06-08 16:47:28 +0000410 else:
Dan Shic68fefb2015-04-07 10:10:52 -0700411 if use_ssp:
Dan Shicf4d2032015-03-12 15:04:21 -0700412 try:
413 _run_with_ssp(container_name, job_or_task_id, results,
414 parser, ssp_url)
415 finally:
416 # Update the ownership of files in result folder.
Dan Shi3f1b8a52015-04-21 11:11:06 -0700417 correct_results_folder_permission(results)
Dan Shicf4d2032015-03-12 15:04:21 -0700418 else:
419 job.run(install_before, install_after,
420 verify_job_repo_url=verify_job_repo_url,
421 only_collect_crashinfo=collect_crashinfo,
422 skip_crash_collection=skip_crash_collection,
Dan Shib669cbd2013-09-13 11:17:17 -0700423 job_labels=job_labels,
424 use_packaging=(not no_use_packaging))
mbligh332000a2009-06-08 16:47:28 +0000425 finally:
426 while job.hosts:
427 host = job.hosts.pop()
428 host.close()
jadmanski0afbb632008-06-06 21:10:57 +0000429 except:
jadmanski27b37ea2008-10-29 23:54:31 +0000430 exit_code = 1
jadmanski0afbb632008-06-06 21:10:57 +0000431 traceback.print_exc()
mbligha46678d2008-05-01 20:00:01 +0000432
mblighff7d61f2008-12-22 14:53:35 +0000433 if pid_file_manager:
434 pid_file_manager.num_tests_failed = job.num_tests_failed
435 pid_file_manager.close_file(exit_code)
jadmanskie0dffc32008-12-15 17:30:30 +0000436 job.cleanup_parser()
showard21baa452008-10-21 00:08:39 +0000437
jadmanski27b37ea2008-10-29 23:54:31 +0000438 sys.exit(exit_code)
mbligha46678d2008-05-01 20:00:01 +0000439
440
Fang Deng042c1472014-10-23 13:56:41 -0700441def record_autoserv(options, duration_secs):
442 """Record autoserv end-to-end time in metadata db.
443
444 @param options: parser options.
445 @param duration_secs: How long autoserv has taken, in secs.
446 """
447 # Get machine hostname
448 machines = options.machines.replace(
449 ',', ' ').strip().split() if options.machines else []
450 num_machines = len(machines)
451 if num_machines > 1:
452 # Skip the case where atomic group is used.
453 return
454 elif num_machines == 0:
455 machines.append('hostless')
456
457 # Determine the status that will be reported.
458 s = job_overhead.STATUS
459 task_mapping = {
460 'reset': s.RESETTING, 'verify': s.VERIFYING,
461 'provision': s.PROVISIONING, 'repair': s.REPAIRING,
462 'cleanup': s.CLEANING, 'collect_crashinfo': s.GATHERING}
463 # option_dict will be like {'reset': True, 'repair': False, ...}
464 option_dict = ast.literal_eval(str(options))
465 match = filter(lambda task: option_dict.get(task) == True, task_mapping)
466 status = task_mapping[match[0]] if match else s.RUNNING
467 is_special_task = status not in [s.RUNNING, s.GATHERING]
Dan Shicf4d2032015-03-12 15:04:21 -0700468 job_or_task_id = job_directories.get_job_id_or_task_id(options.results)
Fang Deng042c1472014-10-23 13:56:41 -0700469 job_overhead.record_state_duration(
470 job_or_task_id, machines[0], status, duration_secs,
471 is_special_task=is_special_task)
472
473
mbligha46678d2008-05-01 20:00:01 +0000474def main():
Fang Deng042c1472014-10-23 13:56:41 -0700475 start_time = datetime.datetime.now()
Dan Shia1ecd5c2013-06-06 11:21:31 -0700476 # White list of tests with run time measurement enabled.
477 measure_run_time_tests_names = global_config.global_config.get_config_value(
478 'AUTOSERV', 'measure_run_time_tests', type=str)
479 if measure_run_time_tests_names:
480 measure_run_time_tests = [t.strip() for t in
481 measure_run_time_tests_names.split(',')]
482 else:
483 measure_run_time_tests = []
jadmanski0afbb632008-06-06 21:10:57 +0000484 # grab the parser
485 parser = autoserv_parser.autoserv_parser
mbligha5cb4062009-02-17 15:53:39 +0000486 parser.parse_args()
mbligha46678d2008-05-01 20:00:01 +0000487
jadmanski0afbb632008-06-06 21:10:57 +0000488 if len(sys.argv) == 1:
489 parser.parser.print_help()
490 sys.exit(1)
mbligha6f13082008-06-05 23:53:46 +0000491
Dan Shicf4d2032015-03-12 15:04:21 -0700492 # If the job requires to run with server-side package, try to stage server-
493 # side package first. If that fails with error that autotest server package
Dan Shic68fefb2015-04-07 10:10:52 -0700494 # does not exist, fall back to run the job without using server-side
495 # packaging. If option warn_no_ssp is specified, that means autoserv is
496 # running in a drone does not support SSP, thus no need to stage server-side
497 # package.
Dan Shicf4d2032015-03-12 15:04:21 -0700498 ssp_url = None
Dan Shi0b754c52015-04-20 14:20:38 -0700499 ssp_url_warning = False
Dan Shic68fefb2015-04-07 10:10:52 -0700500 if (not parser.options.warn_no_ssp and parser.options.require_ssp):
Dan Shicf4d2032015-03-12 15:04:21 -0700501 ssp_url = _stage_ssp(parser)
Dan Shi0b754c52015-04-20 14:20:38 -0700502 # The build does not have autotest server package. Fall back to not
503 # to use server-side package. Logging is postponed until logging being
504 # set up.
505 ssp_url_warning = not ssp_url
Dan Shicf4d2032015-03-12 15:04:21 -0700506
showard75cdfee2009-06-10 17:40:41 +0000507 if parser.options.no_logging:
508 results = None
509 else:
510 results = parser.options.results
mbligh80e1eba2008-11-19 00:26:18 +0000511 if not results:
512 results = 'results.' + time.strftime('%Y-%m-%d-%H.%M.%S')
513 results = os.path.abspath(results)
showard566d3c02010-01-12 18:57:01 +0000514 resultdir_exists = False
515 for filename in ('control.srv', 'status.log', '.autoserv_execute'):
516 if os.path.exists(os.path.join(results, filename)):
517 resultdir_exists = True
mbligh4608b002010-01-05 18:22:35 +0000518 if not parser.options.use_existing_results and resultdir_exists:
mbligh80e1eba2008-11-19 00:26:18 +0000519 error = "Error: results directory already exists: %s\n" % results
520 sys.stderr.write(error)
521 sys.exit(1)
mbligha788dc42009-03-26 21:10:16 +0000522
523 # Now that we certified that there's no leftover results dir from
524 # previous jobs, lets create the result dir since the logging system
525 # needs to create the log file in there.
526 if not os.path.isdir(results):
527 os.makedirs(results)
showard75cdfee2009-06-10 17:40:41 +0000528
Dan Shic68fefb2015-04-07 10:10:52 -0700529 # Server-side packaging will only be used if it's required and the package
530 # is available. If warn_no_ssp is specified, it means that autoserv is
531 # running in a drone does not have SSP supported and a warning will be logs.
532 # Therefore, it should not run with SSP.
533 use_ssp = (not parser.options.warn_no_ssp and parser.options.require_ssp
534 and ssp_url)
535 if use_ssp:
Dan Shicf4d2032015-03-12 15:04:21 -0700536 log_dir = os.path.join(results, 'wrapper') if results else None
537 if log_dir and not os.path.exists(log_dir):
538 os.makedirs(log_dir)
539 else:
540 log_dir = results
Dan Shi3f1b8a52015-04-21 11:11:06 -0700541 correct_results_folder_permission(results)
542
showard75cdfee2009-06-10 17:40:41 +0000543 logging_manager.configure_logging(
Dan Shicf4d2032015-03-12 15:04:21 -0700544 server_logging_config.ServerLoggingConfig(),
545 results_dir=log_dir,
showard10d84172009-06-18 23:16:50 +0000546 use_console=not parser.options.no_tee,
547 verbose=parser.options.verbose,
548 no_console_prefix=parser.options.no_console_prefix)
Dan Shicf4d2032015-03-12 15:04:21 -0700549
Dan Shi0b754c52015-04-20 14:20:38 -0700550 if ssp_url_warning:
551 logging.warn(
552 'Autoserv is required to run with server-side packaging. '
553 'However, no server-side package can be found based on '
554 '`--image`, host attribute job_repo_url or host label of '
555 'cros-version. The test will be executed without '
556 'server-side packaging supported.')
557
showard75cdfee2009-06-10 17:40:41 +0000558 if results:
mbligha788dc42009-03-26 21:10:16 +0000559 logging.info("Results placed in %s" % results)
mbligh10717632008-11-19 00:21:57 +0000560
mbligh4608b002010-01-05 18:22:35 +0000561 # wait until now to perform this check, so it get properly logged
Dan Shicf4d2032015-03-12 15:04:21 -0700562 if (parser.options.use_existing_results and not resultdir_exists and
Dan Shi7836d252015-04-27 15:33:58 -0700563 not lxc_utils.is_in_container()):
mbligh4608b002010-01-05 18:22:35 +0000564 logging.error("No existing results directory found: %s", results)
565 sys.exit(1)
566
Dan Shicf4d2032015-03-12 15:04:21 -0700567 logging.debug('autoserv is running in drone %s.', socket.gethostname())
Aviv Keshet5c40ec62013-08-20 12:11:12 -0700568 logging.debug('autoserv command was: %s', ' '.join(sys.argv))
mbligh4608b002010-01-05 18:22:35 +0000569
Dan Shicf4d2032015-03-12 15:04:21 -0700570 if parser.options.write_pidfile and results:
mbligh4608b002010-01-05 18:22:35 +0000571 pid_file_manager = pidfile.PidFileManager(parser.options.pidfile_label,
572 results)
jadmanskid5ab8c52008-12-03 16:27:07 +0000573 pid_file_manager.open_file()
mblighff7d61f2008-12-22 14:53:35 +0000574 else:
575 pid_file_manager = None
mbligha46678d2008-05-01 20:00:01 +0000576
jadmanskif22fea82008-11-26 20:57:07 +0000577 autotest.BaseAutotest.set_install_in_tmpdir(
578 parser.options.install_in_tmpdir)
579
Dan Shia1ecd5c2013-06-06 11:21:31 -0700580 timer = None
581 try:
582 # Take the first argument as control file name, get the test name from
583 # the control file. If the test name exists in the list of tests with
584 # run time measurement enabled, start a timer to begin measurement.
585 if (len(parser.args) > 0 and parser.args[0] != '' and
586 parser.options.machines):
Dan Shibbc16132013-07-09 16:23:59 -0700587 try:
588 test_name = control_data.parse_control(parser.args[0],
589 raise_warnings=True).name
590 except control_data.ControlVariableException:
591 logging.debug('Failed to retrieve test name from control file.')
592 test_name = None
Dan Shia1ecd5c2013-06-06 11:21:31 -0700593 if test_name in measure_run_time_tests:
594 machines = parser.options.machines.replace(',', ' '
595 ).strip().split()
Dan Shi8eac5af2014-09-17 00:15:15 -0700596 try:
597 afe = frontend.AFE()
598 board = server_utils.get_board_from_afe(machines[0], afe)
Gabe Black1e1c41b2015-02-04 23:55:15 -0800599 timer = autotest_stats.Timer('autoserv_run_time.%s.%s' %
600 (board, test_name))
Dan Shi8eac5af2014-09-17 00:15:15 -0700601 timer.start()
602 except (urllib2.HTTPError, urllib2.URLError):
603 # Ignore error if RPC failed to get board
604 pass
Dan Shia1ecd5c2013-06-06 11:21:31 -0700605 except control_data.ControlVariableException as e:
606 logging.error(str(e))
jadmanski0afbb632008-06-06 21:10:57 +0000607 exit_code = 0
Prashanth B6285f6a2014-05-08 18:01:27 -0700608 # TODO(beeps): Extend this to cover different failure modes.
609 # Testing exceptions are matched against labels sent to autoserv. Eg,
610 # to allow only the hostless job to run, specify
611 # testing_exceptions: test_suite in the shadow_config. To allow both
612 # the hostless job and dummy_Pass to run, specify
613 # testing_exceptions: test_suite,dummy_Pass. You can figure out
614 # what label autoserv is invoked with by looking through the logs of a test
615 # for the autoserv command's -l option.
616 testing_exceptions = global_config.global_config.get_config_value(
617 'AUTOSERV', 'testing_exceptions', type=list, default=[])
618 test_mode = global_config.global_config.get_config_value(
619 'AUTOSERV', 'testing_mode', type=bool, default=False)
Prashanth Balasubramanianf8b83712014-11-06 15:58:21 -0800620 test_mode = (results_mocker and test_mode and not
621 any([ex in parser.options.label
622 for ex in testing_exceptions]))
623 is_task = (parser.options.verify or parser.options.repair or
624 parser.options.provision or parser.options.reset or
625 parser.options.cleanup or parser.options.collect_crashinfo)
jadmanski0afbb632008-06-06 21:10:57 +0000626 try:
627 try:
Prashanth B6285f6a2014-05-08 18:01:27 -0700628 if test_mode:
Prashanth Balasubramanianf8b83712014-11-06 15:58:21 -0800629 # The parser doesn't run on tasks anyway, so we can just return
630 # happy signals without faking results.
631 if not is_task:
632 machine = parser.options.results.split('/')[-1]
633
634 # TODO(beeps): The proper way to do this would be to
635 # refactor job creation so we can invoke job.record
636 # directly. To do that one needs to pipe the test_name
637 # through run_autoserv and bail just before invoking
638 # the server job. See the comment in
639 # puppylab/results_mocker for more context.
640 results_mocker.ResultsMocker(
Prashanth Balasubramanian22dd2262014-11-28 18:19:18 -0800641 test_name if test_name else 'unknown-test',
642 parser.options.results, machine
Prashanth Balasubramanianf8b83712014-11-06 15:58:21 -0800643 ).mock_results()
644 return
Prashanth B6285f6a2014-05-08 18:01:27 -0700645 else:
Dan Shic68fefb2015-04-07 10:10:52 -0700646 run_autoserv(pid_file_manager, results, parser, ssp_url,
647 use_ssp)
Aviv Keshet5c40ec62013-08-20 12:11:12 -0700648 except SystemExit as e:
jadmanski0afbb632008-06-06 21:10:57 +0000649 exit_code = e.code
Aviv Keshet5c40ec62013-08-20 12:11:12 -0700650 if exit_code:
651 logging.exception(e)
652 except Exception as e:
jadmanski0afbb632008-06-06 21:10:57 +0000653 # If we don't know what happened, we'll classify it as
654 # an 'abort' and return 1.
Aviv Keshet5c40ec62013-08-20 12:11:12 -0700655 logging.exception(e)
jadmanski0afbb632008-06-06 21:10:57 +0000656 exit_code = 1
657 finally:
mblighff7d61f2008-12-22 14:53:35 +0000658 if pid_file_manager:
659 pid_file_manager.close_file(exit_code)
Dan Shia1ecd5c2013-06-06 11:21:31 -0700660 if timer:
661 timer.stop()
Fang Deng042c1472014-10-23 13:56:41 -0700662 # Record the autoserv duration time. Must be called
663 # just before the system exits to ensure accuracy.
664 duration_secs = (datetime.datetime.now() - start_time).total_seconds()
665 record_autoserv(parser.options, duration_secs)
jadmanski0afbb632008-06-06 21:10:57 +0000666 sys.exit(exit_code)
mblighfaf0cd42007-11-19 16:00:24 +0000667
mblighbb421852008-03-11 22:36:16 +0000668
mbligha46678d2008-05-01 20:00:01 +0000669if __name__ == '__main__':
jadmanski0afbb632008-06-06 21:10:57 +0000670 main()