blob: 60ee3d41e4dd5dabd26c0dff4420840dfca7e60f [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 Shia1ecd5c2013-06-06 11:21:31 -070024from autotest_lib.client.common_lib import control_data
25from autotest_lib.client.common_lib import global_config
Dan Shi37bee222015-04-13 15:46:47 -070026from autotest_lib.client.common_lib.cros.graphite import autotest_es
27from autotest_lib.client.common_lib.cros.graphite import autotest_stats
Prashanth Balasubramanianf8b83712014-11-06 15:58:21 -080028try:
29 from autotest_lib.puppylab import results_mocker
30except ImportError:
31 results_mocker = None
32
Dan Shia1ecd5c2013-06-06 11:21:31 -070033require_atfork = global_config.global_config.get_config_value(
mblighcb8cb332009-09-03 21:08:56 +000034 'AUTOSERV', 'require_atfork_module', type=bool, default=True)
35
Dan Shia1ecd5c2013-06-06 11:21:31 -070036
Jakob Jueliche497b552014-09-23 19:11:59 -070037# Number of seconds to wait before returning if testing mode is enabled
Prashanth B6285f6a2014-05-08 18:01:27 -070038TESTING_MODE_SLEEP_SECS = 1
Jakob Jueliche497b552014-09-23 19:11:59 -070039
mblighcb8cb332009-09-03 21:08:56 +000040try:
41 import atfork
42 atfork.monkeypatch_os_fork_functions()
43 import atfork.stdlib_fixer
44 # Fix the Python standard library for threading+fork safety with its
45 # internal locks. http://code.google.com/p/python-atfork/
46 import warnings
47 warnings.filterwarnings('ignore', 'logging module already imported')
48 atfork.stdlib_fixer.fix_logging_module()
49except ImportError, e:
50 from autotest_lib.client.common_lib import global_config
51 if global_config.global_config.get_config_value(
52 'AUTOSERV', 'require_atfork_module', type=bool, default=False):
53 print >>sys.stderr, 'Please run utils/build_externals.py'
54 print e
55 sys.exit(1)
mbligh9ff89cd2009-09-03 20:28:17 +000056
Dan Shia1ecd5c2013-06-06 11:21:31 -070057from autotest_lib.server import frontend
showard75cdfee2009-06-10 17:40:41 +000058from autotest_lib.server import server_logging_config
showard043c62a2009-06-10 19:48:57 +000059from autotest_lib.server import server_job, utils, autoserv_parser, autotest
Dan Shia1ecd5c2013-06-06 11:21:31 -070060from autotest_lib.server import utils as server_utils
Dan Shicf4d2032015-03-12 15:04:21 -070061from autotest_lib.site_utils import job_directories
Fang Deng042c1472014-10-23 13:56:41 -070062from autotest_lib.site_utils import job_overhead
Dan Shicf4d2032015-03-12 15:04:21 -070063from autotest_lib.site_utils import lxc
showard75cdfee2009-06-10 17:40:41 +000064from autotest_lib.client.common_lib import pidfile, logging_manager
Gabe Black1e1c41b2015-02-04 23:55:15 -080065from autotest_lib.client.common_lib.cros.graphite import autotest_stats
mbligh92c0fc22008-11-20 16:52:23 +000066
Dan Shicf4d2032015-03-12 15:04:21 -070067# Control segment to stage server-side package.
68STAGE_SERVER_SIDE_PACKAGE_CONTROL_FILE = server_job._control_segment_path(
69 'stage_server_side_package')
70
Alex Millerf1af17e2013-01-09 22:50:32 -080071def log_alarm(signum, frame):
72 logging.error("Received SIGALARM. Ignoring and continuing on.")
Alex Miller0528d6f2013-01-11 10:49:48 -080073 sys.exit(1)
Alex Millerf1af17e2013-01-09 22:50:32 -080074
Dan Shicf4d2032015-03-12 15:04:21 -070075
76def _get_machines(parser):
77 """Get a list of machine names from command line arg -m or a file.
78
79 @param parser: Parser for the command line arguments.
80
81 @return: A list of machine names from command line arg -m or the
82 machines file specified in the command line arg -M.
83 """
84 if parser.options.machines:
85 machines = parser.options.machines.replace(',', ' ').strip().split()
86 else:
87 machines = []
88 machines_file = parser.options.machines_file
89 if machines_file:
90 machines = []
91 for m in open(machines_file, 'r').readlines():
92 # remove comments, spaces
93 m = re.sub('#.*', '', m).strip()
94 if m:
95 machines.append(m)
96 logging.debug('Read list of machines from file: %s', machines_file)
97 logging.debug('Machines: %s', ','.join(machines))
98
99 if machines:
100 for machine in machines:
101 if not machine or re.search('\s', machine):
102 parser.parser.error("Invalid machine: %s" % str(machine))
103 machines = list(set(machines))
104 machines.sort()
105 return machines
106
107
108def _stage_ssp(parser):
109 """Stage server-side package.
110
111 This function calls a control segment to stage server-side package based on
112 the job and autoserv command line option. The detail implementation could
113 be different for each host type. Currently, only CrosHost has
114 stage_server_side_package function defined.
115 The script returns None if no server-side package is available. However,
116 it may raise exception if it failed for reasons other than artifact (the
117 server-side package) not found.
118
119 @param parser: Command line arguments parser passed in the autoserv process.
120
121 @return: url of the staged server-side package. Return None if server-
122 side package is not found for the build.
123 """
124 namespace = {'machines': _get_machines(parser),
125 'image': parser.options.image}
126 script_locals = {}
127 execfile(STAGE_SERVER_SIDE_PACKAGE_CONTROL_FILE, namespace, script_locals)
128 return script_locals['ssp_url']
129
130
131def _run_with_ssp(container_name, job_id, results, parser, ssp_url):
132 """Run the server job with server-side packaging.
133
134 @param container_name: Name of the container to run the test.
135 @param job_id: ID of the test job.
136 @param results: Folder to store results. This could be different from
137 parser.options.results:
138 parser.options.results can be set to None for results to be
139 stored in a temp folder.
140 results can be None for autoserv run requires no logging.
141 @param parser: Command line parser that contains the options.
142 @param ssp_url: url of the staged server-side package.
143 """
144 bucket = lxc.ContainerBucket()
145 control = (parser.args[0] if len(parser.args) > 0 and parser.args[0] != ''
146 else None)
147 test_container = bucket.setup_test(container_name, job_id, ssp_url, results,
148 control=control)
149 args = sys.argv[:]
150 args.remove('--require-ssp')
151
152 # A dictionary of paths to replace in the command line. Key is the path to
153 # be replaced with the one in value.
154 paths_to_replace = {}
155 # Replace the control file path with the one in container.
156 if control:
157 container_control_filename = os.path.join(
158 lxc.CONTROL_TEMP_PATH, os.path.basename(control))
159 paths_to_replace[control] = container_control_filename
160 # Update result directory with the one in container.
161 if parser.options.results:
162 container_result_dir = os.path.join(lxc.RESULT_DIR_FMT % job_id)
163 paths_to_replace[parser.options.results] = container_result_dir
164 # Update parse_job directory with the one in container. The assumption is
165 # that the result folder to be parsed is always the same as the results_dir.
166 if parser.options.parse_job:
167 container_parse_dir = os.path.join(lxc.RESULT_DIR_FMT % job_id)
168 paths_to_replace[parser.options.parse_job] = container_result_dir
169
170 args = [paths_to_replace.get(arg, arg) for arg in args]
171
172 # Apply --use-existing-results, results directory is aready created and
173 # mounted in container. Apply this arg to avoid exception being raised.
174 if not '--use-existing-results' in args:
175 args.append('--use-existing-results')
176
177 # Make sure autoserv running in container using a different pid file.
178 if not '--pidfile-label' in args:
179 args.extend(['--pidfile-label', 'container_autoserv'])
180
Dan Shid1f51232015-04-18 00:29:14 -0700181 cmd_line = ' '.join(["'%s'" % arg if ' ' in arg else arg for arg in args])
Dan Shicf4d2032015-03-12 15:04:21 -0700182 logging.info('Run command in container: %s', cmd_line)
Dan Shi37bee222015-04-13 15:46:47 -0700183 success = False
Dan Shicf4d2032015-03-12 15:04:21 -0700184 try:
185 test_container.attach_run(cmd_line)
Dan Shi37bee222015-04-13 15:46:47 -0700186 success = True
Dan Shicf4d2032015-03-12 15:04:21 -0700187 finally:
Dan Shi37bee222015-04-13 15:46:47 -0700188 counter_key = '%s.%s' % (lxc.STATS_KEY,
189 'success' if success else 'fail')
190 autotest_stats.Counter(counter_key).increment()
191 # metadata is uploaded separately so it can use http to upload.
192 metadata = {'drone': socket.gethostname(),
193 'job_id': job_id,
194 'success': success}
195 autotest_es.post(use_http=True,
196 type_str=lxc.CONTAINER_RUN_TEST_METADB_TYPE,
197 metadata=metadata)
Dan Shicf4d2032015-03-12 15:04:21 -0700198 test_container.destroy()
199
200
Dan Shi3f1b8a52015-04-21 11:11:06 -0700201def correct_results_folder_permission(results):
202 """Make sure the results folder has the right permission settings.
203
204 For tests running with server-side packaging, the results folder has the
205 owner of root. This must be changed to the user running the autoserv
206 process, so parsing job can access the results folder.
207 TODO(dshi): crbug.com/459344 Remove this function when test container can be
208 unprivileged container.
209
210 @param results: Path to the results folder.
211
212 """
213 if not results:
214 return
215
216 lxc.run('chown -R %s "%s"' % (os.getuid(), results))
217 lxc.run('chgrp -R %s "%s"' % (os.getgid(), results))
218
219
Dan Shic68fefb2015-04-07 10:10:52 -0700220def run_autoserv(pid_file_manager, results, parser, ssp_url, use_ssp):
Dan Shicf4d2032015-03-12 15:04:21 -0700221 """Run server job with given options.
222
223 @param pid_file_manager: PidFileManager used to monitor the autoserv process
224 @param results: Folder to store results.
225 @param parser: Parser for the command line arguments.
226 @param ssp_url: Url to server-side package.
Dan Shic68fefb2015-04-07 10:10:52 -0700227 @param use_ssp: Set to True to run with server-side packaging.
Dan Shicf4d2032015-03-12 15:04:21 -0700228 """
Dan Shiec1d47d2015-02-13 11:38:13 -0800229 if parser.options.warn_no_ssp:
Dan Shic68fefb2015-04-07 10:10:52 -0700230 # Post a warning in the log.
Dan Shiec1d47d2015-02-13 11:38:13 -0800231 logging.warn('Autoserv is required to run with server-side packaging. '
232 'However, no drone is found to support server-side '
233 'packaging. The test will be executed in a drone without '
234 'server-side packaging supported.')
235
jadmanski0afbb632008-06-06 21:10:57 +0000236 # send stdin to /dev/null
237 dev_null = os.open(os.devnull, os.O_RDONLY)
238 os.dup2(dev_null, sys.stdin.fileno())
239 os.close(dev_null)
mblighdbf37612007-11-24 19:38:11 +0000240
jadmanski0afbb632008-06-06 21:10:57 +0000241 # Create separate process group
242 os.setpgrp()
mbligh1d42d4e2007-11-05 22:42:00 +0000243
Dan Shicf4d2032015-03-12 15:04:21 -0700244 # Container name is predefined so the container can be destroyed in
245 # handle_sigterm.
246 job_or_task_id = job_directories.get_job_id_or_task_id(
247 parser.options.results)
248 container_name = (lxc.TEST_CONTAINER_NAME_FMT %
Dan Shid68d51c2015-04-21 17:00:42 -0700249 (job_or_task_id, time.time(), os.getpid()))
Dan Shicf4d2032015-03-12 15:04:21 -0700250
jadmanski0afbb632008-06-06 21:10:57 +0000251 # Implement SIGTERM handler
mblighc2299562009-07-02 19:00:36 +0000252 def handle_sigterm(signum, frame):
Simran Basi9d9b7292013-10-16 16:44:07 -0700253 logging.debug('Received SIGTERM')
mblighff7d61f2008-12-22 14:53:35 +0000254 if pid_file_manager:
255 pid_file_manager.close_file(1, signal.SIGTERM)
Simran Basi49e21e62013-10-17 12:40:33 -0700256 logging.debug('Finished writing to pid_file. Killing process.')
Dan Shi3f1b8a52015-04-21 11:11:06 -0700257
258 # Update results folder's file permission. This needs to be done ASAP
259 # before the parsing process tries to access the log.
260 if use_ssp and results:
261 correct_results_folder_permission(results)
262
Simran Basid6b83772014-01-06 16:31:30 -0800263 # TODO (sbasi) - remove the time.sleep when crbug.com/302815 is solved.
264 # This sleep allows the pending output to be logged before the kill
265 # signal is sent.
266 time.sleep(.1)
Dan Shic68fefb2015-04-07 10:10:52 -0700267 if use_ssp:
Dan Shicf4d2032015-03-12 15:04:21 -0700268 logging.debug('Destroy container %s before aborting the autoserv '
269 'process.', container_name)
Dan Shi3f1b8a52015-04-21 11:11:06 -0700270 metadata = {'drone': socket.gethostname(),
271 'job_id': job_or_task_id,
272 'container_name': container_name,
273 'action': 'abort',
274 'success': True}
Dan Shicf4d2032015-03-12 15:04:21 -0700275 try:
276 bucket = lxc.ContainerBucket()
277 container = bucket.get(container_name)
278 if container:
279 container.destroy()
280 else:
Dan Shi3f1b8a52015-04-21 11:11:06 -0700281 metadata['success'] = False
282 metadata['error'] = 'container not found'
Dan Shicf4d2032015-03-12 15:04:21 -0700283 logging.debug('Container %s is not found.', container_name)
284 except:
Dan Shi3f1b8a52015-04-21 11:11:06 -0700285 metadata['success'] = False
286 metadata['error'] = 'Exception: %s' % sys.exc_info()
Dan Shicf4d2032015-03-12 15:04:21 -0700287 # Handle any exception so the autoserv process can be aborted.
288 logging.error('Failed to destroy container %s. Error: %s',
289 container_name, sys.exc_info())
Dan Shi3f1b8a52015-04-21 11:11:06 -0700290 autotest_es.post(use_http=True,
291 type_str=lxc.CONTAINER_RUN_TEST_METADB_TYPE,
292 metadata=metadata)
Dan Shicf4d2032015-03-12 15:04:21 -0700293
jadmanski0afbb632008-06-06 21:10:57 +0000294 os.killpg(os.getpgrp(), signal.SIGKILL)
mblighfaf0cd42007-11-19 16:00:24 +0000295
jadmanski0afbb632008-06-06 21:10:57 +0000296 # Set signal handler
mblighc2299562009-07-02 19:00:36 +0000297 signal.signal(signal.SIGTERM, handle_sigterm)
mbligha46678d2008-05-01 20:00:01 +0000298
Simran Basid6b83772014-01-06 16:31:30 -0800299 # faulthandler is only needed to debug in the Lab and is not avaliable to
300 # be imported in the chroot as part of VMTest, so Try-Except it.
301 try:
302 import faulthandler
303 faulthandler.register(signal.SIGTERM, all_threads=True, chain=True)
304 logging.debug('faulthandler registered on SIGTERM.')
305 except ImportError:
306 pass
307
David Rochberg8a60d1e2011-02-01 14:22:07 -0500308 # Ignore SIGTTOU's generated by output from forked children.
309 signal.signal(signal.SIGTTOU, signal.SIG_IGN)
310
Alex Millerf1af17e2013-01-09 22:50:32 -0800311 # If we received a SIGALARM, let's be loud about it.
312 signal.signal(signal.SIGALRM, log_alarm)
313
mbligha5f5e542009-12-30 16:57:49 +0000314 # Server side tests that call shell scripts often depend on $USER being set
315 # but depending on how you launch your autotest scheduler it may not be set.
316 os.environ['USER'] = getpass.getuser()
317
mblighb2bea302008-07-24 20:25:57 +0000318 label = parser.options.label
mbligh374f3412009-05-13 21:29:45 +0000319 group_name = parser.options.group_name
mblighb2bea302008-07-24 20:25:57 +0000320 user = parser.options.user
321 client = parser.options.client
322 server = parser.options.server
jadmanski0afbb632008-06-06 21:10:57 +0000323 install_before = parser.options.install_before
mblighb2bea302008-07-24 20:25:57 +0000324 install_after = parser.options.install_after
325 verify = parser.options.verify
326 repair = parser.options.repair
showard45ae8192008-11-05 19:32:53 +0000327 cleanup = parser.options.cleanup
Alex Millercb79ba72013-05-29 14:43:00 -0700328 provision = parser.options.provision
Dan Shi07e09af2013-04-12 09:31:29 -0700329 reset = parser.options.reset
Alex Miller667b5f22014-02-28 15:33:39 -0800330 job_labels = parser.options.job_labels
mblighb2bea302008-07-24 20:25:57 +0000331 no_tee = parser.options.no_tee
jadmanski0afbb632008-06-06 21:10:57 +0000332 parse_job = parser.options.parse_job
mblighe7d9c602009-07-02 19:02:33 +0000333 execution_tag = parser.options.execution_tag
334 if not execution_tag:
335 execution_tag = parse_job
jadmanskifbc1f0a2008-07-09 14:12:54 +0000336 host_protection = parser.options.host_protection
jadmanski0afbb632008-06-06 21:10:57 +0000337 ssh_user = parser.options.ssh_user
338 ssh_port = parser.options.ssh_port
339 ssh_pass = parser.options.ssh_pass
jadmanskidef0c3c2009-03-25 20:07:10 +0000340 collect_crashinfo = parser.options.collect_crashinfo
mblighe0cbc912010-03-11 18:03:07 +0000341 control_filename = parser.options.control_filename
Scott Zawalski91493c82013-01-25 16:15:20 -0500342 test_retry = parser.options.test_retry
beepscb6f1e22013-06-28 19:14:10 -0700343 verify_job_repo_url = parser.options.verify_job_repo_url
Christopher Wileyf594c5e2013-07-03 18:25:30 -0700344 skip_crash_collection = parser.options.skip_crash_collection
Aviv Keshet18ee3142013-08-12 15:01:51 -0700345 ssh_verbosity = int(parser.options.ssh_verbosity)
Fang Deng6cc20de2013-09-06 15:47:32 -0700346 ssh_options = parser.options.ssh_options
Dan Shib669cbd2013-09-13 11:17:17 -0700347 no_use_packaging = parser.options.no_use_packaging
mbligha46678d2008-05-01 20:00:01 +0000348
mblighb2bea302008-07-24 20:25:57 +0000349 # can't be both a client and a server side test
350 if client and server:
Eric Li861b2d52011-02-04 14:50:35 -0800351 parser.parser.error("Can not specify a test as both server and client!")
mblighb2bea302008-07-24 20:25:57 +0000352
Alex Millercb79ba72013-05-29 14:43:00 -0700353 if provision and client:
354 parser.parser.error("Cannot specify provisioning and client!")
355
356 is_special_task = (verify or repair or cleanup or collect_crashinfo or
Dan Shi07e09af2013-04-12 09:31:29 -0700357 provision or reset)
Alex Millercb79ba72013-05-29 14:43:00 -0700358 if len(parser.args) < 1 and not is_special_task:
Eric Li861b2d52011-02-04 14:50:35 -0800359 parser.parser.error("Missing argument: control file")
mbligha46678d2008-05-01 20:00:01 +0000360
Aviv Keshet18ee3142013-08-12 15:01:51 -0700361 if ssh_verbosity > 0:
362 # ssh_verbosity is an integer between 0 and 3, inclusive
363 ssh_verbosity_flag = '-' + 'v' * ssh_verbosity
Fang Dengd1c2b732013-08-20 12:59:46 -0700364 else:
365 ssh_verbosity_flag = ''
Aviv Keshet18ee3142013-08-12 15:01:51 -0700366
showard45ae8192008-11-05 19:32:53 +0000367 # We have a control file unless it's just a verify/repair/cleanup job
jadmanski0afbb632008-06-06 21:10:57 +0000368 if len(parser.args) > 0:
369 control = parser.args[0]
370 else:
371 control = None
mbligha46678d2008-05-01 20:00:01 +0000372
Dan Shicf4d2032015-03-12 15:04:21 -0700373 machines = _get_machines(parser)
mbligh374f3412009-05-13 21:29:45 +0000374 if group_name and len(machines) < 2:
Dan Shicf4d2032015-03-12 15:04:21 -0700375 parser.parser.error('-G %r may only be supplied with more than one '
376 'machine.' % group_name)
mbligh374f3412009-05-13 21:29:45 +0000377
Christopher Wiley8a91f232013-07-09 11:02:27 -0700378 kwargs = {'group_name': group_name, 'tag': execution_tag,
Dan Shicf4d2032015-03-12 15:04:21 -0700379 'disable_sysinfo': parser.options.disable_sysinfo}
mblighe0cbc912010-03-11 18:03:07 +0000380 if control_filename:
381 kwargs['control_filename'] = control_filename
jadmanski0afbb632008-06-06 21:10:57 +0000382 job = server_job.server_job(control, parser.args[1:], results, label,
383 user, machines, client, parse_job,
Fang Dengd1c2b732013-08-20 12:59:46 -0700384 ssh_user, ssh_port, ssh_pass,
Aviv Keshetc5947fa2013-09-04 14:06:29 -0700385 ssh_verbosity_flag, ssh_options,
386 test_retry, **kwargs)
Dan Shicf4d2032015-03-12 15:04:21 -0700387
showard75cdfee2009-06-10 17:40:41 +0000388 job.logging.start_logging()
mbligh4608b002010-01-05 18:22:35 +0000389 job.init_parser()
mbligha46678d2008-05-01 20:00:01 +0000390
mbligh161fe6f2008-06-19 16:26:04 +0000391 # perform checks
392 job.precheck()
393
jadmanski0afbb632008-06-06 21:10:57 +0000394 # run the job
395 exit_code = 0
396 try:
mbligh332000a2009-06-08 16:47:28 +0000397 try:
398 if repair:
Alex Miller667b5f22014-02-28 15:33:39 -0800399 job.repair(host_protection, job_labels)
mbligh332000a2009-06-08 16:47:28 +0000400 elif verify:
Alex Miller667b5f22014-02-28 15:33:39 -0800401 job.verify(job_labels)
Alex Millercb79ba72013-05-29 14:43:00 -0700402 elif provision:
Alex Miller667b5f22014-02-28 15:33:39 -0800403 job.provision(job_labels)
Dan Shi07e09af2013-04-12 09:31:29 -0700404 elif reset:
Alex Miller667b5f22014-02-28 15:33:39 -0800405 job.reset(job_labels)
Fang Dengad78aca2014-10-02 18:15:46 -0700406 elif cleanup:
407 job.cleanup(job_labels)
mbligh332000a2009-06-08 16:47:28 +0000408 else:
Dan Shic68fefb2015-04-07 10:10:52 -0700409 if use_ssp:
Dan Shicf4d2032015-03-12 15:04:21 -0700410 try:
411 _run_with_ssp(container_name, job_or_task_id, results,
412 parser, ssp_url)
413 finally:
414 # Update the ownership of files in result folder.
Dan Shi3f1b8a52015-04-21 11:11:06 -0700415 correct_results_folder_permission(results)
Dan Shicf4d2032015-03-12 15:04:21 -0700416 else:
417 job.run(install_before, install_after,
418 verify_job_repo_url=verify_job_repo_url,
419 only_collect_crashinfo=collect_crashinfo,
420 skip_crash_collection=skip_crash_collection,
Dan Shib669cbd2013-09-13 11:17:17 -0700421 job_labels=job_labels,
422 use_packaging=(not no_use_packaging))
mbligh332000a2009-06-08 16:47:28 +0000423 finally:
424 while job.hosts:
425 host = job.hosts.pop()
426 host.close()
jadmanski0afbb632008-06-06 21:10:57 +0000427 except:
jadmanski27b37ea2008-10-29 23:54:31 +0000428 exit_code = 1
jadmanski0afbb632008-06-06 21:10:57 +0000429 traceback.print_exc()
mbligha46678d2008-05-01 20:00:01 +0000430
mblighff7d61f2008-12-22 14:53:35 +0000431 if pid_file_manager:
432 pid_file_manager.num_tests_failed = job.num_tests_failed
433 pid_file_manager.close_file(exit_code)
jadmanskie0dffc32008-12-15 17:30:30 +0000434 job.cleanup_parser()
showard21baa452008-10-21 00:08:39 +0000435
jadmanski27b37ea2008-10-29 23:54:31 +0000436 sys.exit(exit_code)
mbligha46678d2008-05-01 20:00:01 +0000437
438
Fang Deng042c1472014-10-23 13:56:41 -0700439def record_autoserv(options, duration_secs):
440 """Record autoserv end-to-end time in metadata db.
441
442 @param options: parser options.
443 @param duration_secs: How long autoserv has taken, in secs.
444 """
445 # Get machine hostname
446 machines = options.machines.replace(
447 ',', ' ').strip().split() if options.machines else []
448 num_machines = len(machines)
449 if num_machines > 1:
450 # Skip the case where atomic group is used.
451 return
452 elif num_machines == 0:
453 machines.append('hostless')
454
455 # Determine the status that will be reported.
456 s = job_overhead.STATUS
457 task_mapping = {
458 'reset': s.RESETTING, 'verify': s.VERIFYING,
459 'provision': s.PROVISIONING, 'repair': s.REPAIRING,
460 'cleanup': s.CLEANING, 'collect_crashinfo': s.GATHERING}
461 # option_dict will be like {'reset': True, 'repair': False, ...}
462 option_dict = ast.literal_eval(str(options))
463 match = filter(lambda task: option_dict.get(task) == True, task_mapping)
464 status = task_mapping[match[0]] if match else s.RUNNING
465 is_special_task = status not in [s.RUNNING, s.GATHERING]
Dan Shicf4d2032015-03-12 15:04:21 -0700466 job_or_task_id = job_directories.get_job_id_or_task_id(options.results)
Fang Deng042c1472014-10-23 13:56:41 -0700467 job_overhead.record_state_duration(
468 job_or_task_id, machines[0], status, duration_secs,
469 is_special_task=is_special_task)
470
471
mbligha46678d2008-05-01 20:00:01 +0000472def main():
Fang Deng042c1472014-10-23 13:56:41 -0700473 start_time = datetime.datetime.now()
Dan Shia1ecd5c2013-06-06 11:21:31 -0700474 # White list of tests with run time measurement enabled.
475 measure_run_time_tests_names = global_config.global_config.get_config_value(
476 'AUTOSERV', 'measure_run_time_tests', type=str)
477 if measure_run_time_tests_names:
478 measure_run_time_tests = [t.strip() for t in
479 measure_run_time_tests_names.split(',')]
480 else:
481 measure_run_time_tests = []
jadmanski0afbb632008-06-06 21:10:57 +0000482 # grab the parser
483 parser = autoserv_parser.autoserv_parser
mbligha5cb4062009-02-17 15:53:39 +0000484 parser.parse_args()
mbligha46678d2008-05-01 20:00:01 +0000485
jadmanski0afbb632008-06-06 21:10:57 +0000486 if len(sys.argv) == 1:
487 parser.parser.print_help()
488 sys.exit(1)
mbligha6f13082008-06-05 23:53:46 +0000489
Dan Shicf4d2032015-03-12 15:04:21 -0700490 # If the job requires to run with server-side package, try to stage server-
491 # side package first. If that fails with error that autotest server package
Dan Shic68fefb2015-04-07 10:10:52 -0700492 # does not exist, fall back to run the job without using server-side
493 # packaging. If option warn_no_ssp is specified, that means autoserv is
494 # running in a drone does not support SSP, thus no need to stage server-side
495 # package.
Dan Shicf4d2032015-03-12 15:04:21 -0700496 ssp_url = None
Dan Shi0b754c52015-04-20 14:20:38 -0700497 ssp_url_warning = False
Dan Shic68fefb2015-04-07 10:10:52 -0700498 if (not parser.options.warn_no_ssp and parser.options.require_ssp):
Dan Shicf4d2032015-03-12 15:04:21 -0700499 ssp_url = _stage_ssp(parser)
Dan Shi0b754c52015-04-20 14:20:38 -0700500 # The build does not have autotest server package. Fall back to not
501 # to use server-side package. Logging is postponed until logging being
502 # set up.
503 ssp_url_warning = not ssp_url
Dan Shicf4d2032015-03-12 15:04:21 -0700504
showard75cdfee2009-06-10 17:40:41 +0000505 if parser.options.no_logging:
506 results = None
507 else:
508 results = parser.options.results
mbligh80e1eba2008-11-19 00:26:18 +0000509 if not results:
510 results = 'results.' + time.strftime('%Y-%m-%d-%H.%M.%S')
511 results = os.path.abspath(results)
showard566d3c02010-01-12 18:57:01 +0000512 resultdir_exists = False
513 for filename in ('control.srv', 'status.log', '.autoserv_execute'):
514 if os.path.exists(os.path.join(results, filename)):
515 resultdir_exists = True
mbligh4608b002010-01-05 18:22:35 +0000516 if not parser.options.use_existing_results and resultdir_exists:
mbligh80e1eba2008-11-19 00:26:18 +0000517 error = "Error: results directory already exists: %s\n" % results
518 sys.stderr.write(error)
519 sys.exit(1)
mbligha788dc42009-03-26 21:10:16 +0000520
521 # Now that we certified that there's no leftover results dir from
522 # previous jobs, lets create the result dir since the logging system
523 # needs to create the log file in there.
524 if not os.path.isdir(results):
525 os.makedirs(results)
showard75cdfee2009-06-10 17:40:41 +0000526
Dan Shic68fefb2015-04-07 10:10:52 -0700527 # Server-side packaging will only be used if it's required and the package
528 # is available. If warn_no_ssp is specified, it means that autoserv is
529 # running in a drone does not have SSP supported and a warning will be logs.
530 # Therefore, it should not run with SSP.
531 use_ssp = (not parser.options.warn_no_ssp and parser.options.require_ssp
532 and ssp_url)
533 if use_ssp:
Dan Shicf4d2032015-03-12 15:04:21 -0700534 log_dir = os.path.join(results, 'wrapper') if results else None
535 if log_dir and not os.path.exists(log_dir):
536 os.makedirs(log_dir)
537 else:
538 log_dir = results
Dan Shi3f1b8a52015-04-21 11:11:06 -0700539 correct_results_folder_permission(results)
540
showard75cdfee2009-06-10 17:40:41 +0000541 logging_manager.configure_logging(
Dan Shicf4d2032015-03-12 15:04:21 -0700542 server_logging_config.ServerLoggingConfig(),
543 results_dir=log_dir,
showard10d84172009-06-18 23:16:50 +0000544 use_console=not parser.options.no_tee,
545 verbose=parser.options.verbose,
546 no_console_prefix=parser.options.no_console_prefix)
Dan Shicf4d2032015-03-12 15:04:21 -0700547
Dan Shi0b754c52015-04-20 14:20:38 -0700548 if ssp_url_warning:
549 logging.warn(
550 'Autoserv is required to run with server-side packaging. '
551 'However, no server-side package can be found based on '
552 '`--image`, host attribute job_repo_url or host label of '
553 'cros-version. The test will be executed without '
554 'server-side packaging supported.')
555
showard75cdfee2009-06-10 17:40:41 +0000556 if results:
mbligha788dc42009-03-26 21:10:16 +0000557 logging.info("Results placed in %s" % results)
mbligh10717632008-11-19 00:21:57 +0000558
mbligh4608b002010-01-05 18:22:35 +0000559 # wait until now to perform this check, so it get properly logged
Dan Shicf4d2032015-03-12 15:04:21 -0700560 if (parser.options.use_existing_results and not resultdir_exists and
561 not lxc.is_in_container()):
mbligh4608b002010-01-05 18:22:35 +0000562 logging.error("No existing results directory found: %s", results)
563 sys.exit(1)
564
Dan Shicf4d2032015-03-12 15:04:21 -0700565 logging.debug('autoserv is running in drone %s.', socket.gethostname())
Aviv Keshet5c40ec62013-08-20 12:11:12 -0700566 logging.debug('autoserv command was: %s', ' '.join(sys.argv))
mbligh4608b002010-01-05 18:22:35 +0000567
Dan Shicf4d2032015-03-12 15:04:21 -0700568 if parser.options.write_pidfile and results:
mbligh4608b002010-01-05 18:22:35 +0000569 pid_file_manager = pidfile.PidFileManager(parser.options.pidfile_label,
570 results)
jadmanskid5ab8c52008-12-03 16:27:07 +0000571 pid_file_manager.open_file()
mblighff7d61f2008-12-22 14:53:35 +0000572 else:
573 pid_file_manager = None
mbligha46678d2008-05-01 20:00:01 +0000574
jadmanskif22fea82008-11-26 20:57:07 +0000575 autotest.BaseAutotest.set_install_in_tmpdir(
576 parser.options.install_in_tmpdir)
577
Dan Shia1ecd5c2013-06-06 11:21:31 -0700578 timer = None
579 try:
580 # Take the first argument as control file name, get the test name from
581 # the control file. If the test name exists in the list of tests with
582 # run time measurement enabled, start a timer to begin measurement.
583 if (len(parser.args) > 0 and parser.args[0] != '' and
584 parser.options.machines):
Dan Shibbc16132013-07-09 16:23:59 -0700585 try:
586 test_name = control_data.parse_control(parser.args[0],
587 raise_warnings=True).name
588 except control_data.ControlVariableException:
589 logging.debug('Failed to retrieve test name from control file.')
590 test_name = None
Dan Shia1ecd5c2013-06-06 11:21:31 -0700591 if test_name in measure_run_time_tests:
592 machines = parser.options.machines.replace(',', ' '
593 ).strip().split()
Dan Shi8eac5af2014-09-17 00:15:15 -0700594 try:
595 afe = frontend.AFE()
596 board = server_utils.get_board_from_afe(machines[0], afe)
Gabe Black1e1c41b2015-02-04 23:55:15 -0800597 timer = autotest_stats.Timer('autoserv_run_time.%s.%s' %
598 (board, test_name))
Dan Shi8eac5af2014-09-17 00:15:15 -0700599 timer.start()
600 except (urllib2.HTTPError, urllib2.URLError):
601 # Ignore error if RPC failed to get board
602 pass
Dan Shia1ecd5c2013-06-06 11:21:31 -0700603 except control_data.ControlVariableException as e:
604 logging.error(str(e))
jadmanski0afbb632008-06-06 21:10:57 +0000605 exit_code = 0
Prashanth B6285f6a2014-05-08 18:01:27 -0700606 # TODO(beeps): Extend this to cover different failure modes.
607 # Testing exceptions are matched against labels sent to autoserv. Eg,
608 # to allow only the hostless job to run, specify
609 # testing_exceptions: test_suite in the shadow_config. To allow both
610 # the hostless job and dummy_Pass to run, specify
611 # testing_exceptions: test_suite,dummy_Pass. You can figure out
612 # what label autoserv is invoked with by looking through the logs of a test
613 # for the autoserv command's -l option.
614 testing_exceptions = global_config.global_config.get_config_value(
615 'AUTOSERV', 'testing_exceptions', type=list, default=[])
616 test_mode = global_config.global_config.get_config_value(
617 'AUTOSERV', 'testing_mode', type=bool, default=False)
Prashanth Balasubramanianf8b83712014-11-06 15:58:21 -0800618 test_mode = (results_mocker and test_mode and not
619 any([ex in parser.options.label
620 for ex in testing_exceptions]))
621 is_task = (parser.options.verify or parser.options.repair or
622 parser.options.provision or parser.options.reset or
623 parser.options.cleanup or parser.options.collect_crashinfo)
jadmanski0afbb632008-06-06 21:10:57 +0000624 try:
625 try:
Prashanth B6285f6a2014-05-08 18:01:27 -0700626 if test_mode:
Prashanth Balasubramanianf8b83712014-11-06 15:58:21 -0800627 # The parser doesn't run on tasks anyway, so we can just return
628 # happy signals without faking results.
629 if not is_task:
630 machine = parser.options.results.split('/')[-1]
631
632 # TODO(beeps): The proper way to do this would be to
633 # refactor job creation so we can invoke job.record
634 # directly. To do that one needs to pipe the test_name
635 # through run_autoserv and bail just before invoking
636 # the server job. See the comment in
637 # puppylab/results_mocker for more context.
638 results_mocker.ResultsMocker(
Prashanth Balasubramanian22dd2262014-11-28 18:19:18 -0800639 test_name if test_name else 'unknown-test',
640 parser.options.results, machine
Prashanth Balasubramanianf8b83712014-11-06 15:58:21 -0800641 ).mock_results()
642 return
Prashanth B6285f6a2014-05-08 18:01:27 -0700643 else:
Dan Shic68fefb2015-04-07 10:10:52 -0700644 run_autoserv(pid_file_manager, results, parser, ssp_url,
645 use_ssp)
Aviv Keshet5c40ec62013-08-20 12:11:12 -0700646 except SystemExit as e:
jadmanski0afbb632008-06-06 21:10:57 +0000647 exit_code = e.code
Aviv Keshet5c40ec62013-08-20 12:11:12 -0700648 if exit_code:
649 logging.exception(e)
650 except Exception as e:
jadmanski0afbb632008-06-06 21:10:57 +0000651 # If we don't know what happened, we'll classify it as
652 # an 'abort' and return 1.
Aviv Keshet5c40ec62013-08-20 12:11:12 -0700653 logging.exception(e)
jadmanski0afbb632008-06-06 21:10:57 +0000654 exit_code = 1
655 finally:
mblighff7d61f2008-12-22 14:53:35 +0000656 if pid_file_manager:
657 pid_file_manager.close_file(exit_code)
Dan Shia1ecd5c2013-06-06 11:21:31 -0700658 if timer:
659 timer.stop()
Fang Deng042c1472014-10-23 13:56:41 -0700660 # Record the autoserv duration time. Must be called
661 # just before the system exits to ensure accuracy.
662 duration_secs = (datetime.datetime.now() - start_time).total_seconds()
663 record_autoserv(parser.options, duration_secs)
jadmanski0afbb632008-06-06 21:10:57 +0000664 sys.exit(exit_code)
mblighfaf0cd42007-11-19 16:00:24 +0000665
mblighbb421852008-03-11 22:36:16 +0000666
mbligha46678d2008-05-01 20:00:01 +0000667if __name__ == '__main__':
jadmanski0afbb632008-06-06 21:10:57 +0000668 main()