blob: 16c84220f7cceacd80cc1dae751309828c786cf7 [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 Shi5ddf6a32015-05-02 00:22:01 -070026from autotest_lib.client.common_lib import utils
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 Shie28de552015-05-06 16:51:58 -0700536 log_dir = os.path.join(results, 'ssp_logs') if results else None
Dan Shicf4d2032015-03-12 15:04:21 -0700537 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
showard75cdfee2009-06-10 17:40:41 +0000542 logging_manager.configure_logging(
Dan Shicf4d2032015-03-12 15:04:21 -0700543 server_logging_config.ServerLoggingConfig(),
544 results_dir=log_dir,
showard10d84172009-06-18 23:16:50 +0000545 use_console=not parser.options.no_tee,
546 verbose=parser.options.verbose,
547 no_console_prefix=parser.options.no_console_prefix)
Dan Shicf4d2032015-03-12 15:04:21 -0700548
Dan Shi0b754c52015-04-20 14:20:38 -0700549 if ssp_url_warning:
550 logging.warn(
551 'Autoserv is required to run with server-side packaging. '
552 'However, no server-side package can be found based on '
553 '`--image`, host attribute job_repo_url or host label of '
554 'cros-version. The test will be executed without '
555 'server-side packaging supported.')
556
showard75cdfee2009-06-10 17:40:41 +0000557 if results:
mbligha788dc42009-03-26 21:10:16 +0000558 logging.info("Results placed in %s" % results)
mbligh10717632008-11-19 00:21:57 +0000559
mbligh4608b002010-01-05 18:22:35 +0000560 # wait until now to perform this check, so it get properly logged
Dan Shicf4d2032015-03-12 15:04:21 -0700561 if (parser.options.use_existing_results and not resultdir_exists and
Dan Shi7836d252015-04-27 15:33:58 -0700562 not lxc_utils.is_in_container()):
mbligh4608b002010-01-05 18:22:35 +0000563 logging.error("No existing results directory found: %s", results)
564 sys.exit(1)
565
Dan Shicf4d2032015-03-12 15:04:21 -0700566 logging.debug('autoserv is running in drone %s.', socket.gethostname())
Aviv Keshet5c40ec62013-08-20 12:11:12 -0700567 logging.debug('autoserv command was: %s', ' '.join(sys.argv))
mbligh4608b002010-01-05 18:22:35 +0000568
Dan Shicf4d2032015-03-12 15:04:21 -0700569 if parser.options.write_pidfile and results:
mbligh4608b002010-01-05 18:22:35 +0000570 pid_file_manager = pidfile.PidFileManager(parser.options.pidfile_label,
571 results)
jadmanskid5ab8c52008-12-03 16:27:07 +0000572 pid_file_manager.open_file()
mblighff7d61f2008-12-22 14:53:35 +0000573 else:
574 pid_file_manager = None
mbligha46678d2008-05-01 20:00:01 +0000575
jadmanskif22fea82008-11-26 20:57:07 +0000576 autotest.BaseAutotest.set_install_in_tmpdir(
577 parser.options.install_in_tmpdir)
578
Dan Shia1ecd5c2013-06-06 11:21:31 -0700579 timer = None
580 try:
581 # Take the first argument as control file name, get the test name from
582 # the control file. If the test name exists in the list of tests with
583 # run time measurement enabled, start a timer to begin measurement.
584 if (len(parser.args) > 0 and parser.args[0] != '' and
585 parser.options.machines):
Dan Shibbc16132013-07-09 16:23:59 -0700586 try:
587 test_name = control_data.parse_control(parser.args[0],
588 raise_warnings=True).name
589 except control_data.ControlVariableException:
590 logging.debug('Failed to retrieve test name from control file.')
591 test_name = None
Dan Shia1ecd5c2013-06-06 11:21:31 -0700592 if test_name in measure_run_time_tests:
593 machines = parser.options.machines.replace(',', ' '
594 ).strip().split()
Dan Shi8eac5af2014-09-17 00:15:15 -0700595 try:
596 afe = frontend.AFE()
597 board = server_utils.get_board_from_afe(machines[0], afe)
Gabe Black1e1c41b2015-02-04 23:55:15 -0800598 timer = autotest_stats.Timer('autoserv_run_time.%s.%s' %
599 (board, test_name))
Dan Shi8eac5af2014-09-17 00:15:15 -0700600 timer.start()
601 except (urllib2.HTTPError, urllib2.URLError):
602 # Ignore error if RPC failed to get board
603 pass
Dan Shia1ecd5c2013-06-06 11:21:31 -0700604 except control_data.ControlVariableException as e:
605 logging.error(str(e))
jadmanski0afbb632008-06-06 21:10:57 +0000606 exit_code = 0
Prashanth B6285f6a2014-05-08 18:01:27 -0700607 # TODO(beeps): Extend this to cover different failure modes.
608 # Testing exceptions are matched against labels sent to autoserv. Eg,
609 # to allow only the hostless job to run, specify
610 # testing_exceptions: test_suite in the shadow_config. To allow both
611 # the hostless job and dummy_Pass to run, specify
612 # testing_exceptions: test_suite,dummy_Pass. You can figure out
613 # what label autoserv is invoked with by looking through the logs of a test
614 # for the autoserv command's -l option.
615 testing_exceptions = global_config.global_config.get_config_value(
616 'AUTOSERV', 'testing_exceptions', type=list, default=[])
617 test_mode = global_config.global_config.get_config_value(
618 'AUTOSERV', 'testing_mode', type=bool, default=False)
Prashanth Balasubramanianf8b83712014-11-06 15:58:21 -0800619 test_mode = (results_mocker and test_mode and not
620 any([ex in parser.options.label
621 for ex in testing_exceptions]))
622 is_task = (parser.options.verify or parser.options.repair or
623 parser.options.provision or parser.options.reset or
624 parser.options.cleanup or parser.options.collect_crashinfo)
jadmanski0afbb632008-06-06 21:10:57 +0000625 try:
626 try:
Prashanth B6285f6a2014-05-08 18:01:27 -0700627 if test_mode:
Prashanth Balasubramanianf8b83712014-11-06 15:58:21 -0800628 # The parser doesn't run on tasks anyway, so we can just return
629 # happy signals without faking results.
630 if not is_task:
631 machine = parser.options.results.split('/')[-1]
632
633 # TODO(beeps): The proper way to do this would be to
634 # refactor job creation so we can invoke job.record
635 # directly. To do that one needs to pipe the test_name
636 # through run_autoserv and bail just before invoking
637 # the server job. See the comment in
638 # puppylab/results_mocker for more context.
639 results_mocker.ResultsMocker(
Prashanth Balasubramanian22dd2262014-11-28 18:19:18 -0800640 test_name if test_name else 'unknown-test',
641 parser.options.results, machine
Prashanth Balasubramanianf8b83712014-11-06 15:58:21 -0800642 ).mock_results()
643 return
Prashanth B6285f6a2014-05-08 18:01:27 -0700644 else:
Dan Shic68fefb2015-04-07 10:10:52 -0700645 run_autoserv(pid_file_manager, results, parser, ssp_url,
646 use_ssp)
Aviv Keshet5c40ec62013-08-20 12:11:12 -0700647 except SystemExit as e:
jadmanski0afbb632008-06-06 21:10:57 +0000648 exit_code = e.code
Aviv Keshet5c40ec62013-08-20 12:11:12 -0700649 if exit_code:
650 logging.exception(e)
651 except Exception as e:
jadmanski0afbb632008-06-06 21:10:57 +0000652 # If we don't know what happened, we'll classify it as
653 # an 'abort' and return 1.
Aviv Keshet5c40ec62013-08-20 12:11:12 -0700654 logging.exception(e)
jadmanski0afbb632008-06-06 21:10:57 +0000655 exit_code = 1
656 finally:
mblighff7d61f2008-12-22 14:53:35 +0000657 if pid_file_manager:
658 pid_file_manager.close_file(exit_code)
Dan Shia1ecd5c2013-06-06 11:21:31 -0700659 if timer:
660 timer.stop()
Fang Deng042c1472014-10-23 13:56:41 -0700661 # Record the autoserv duration time. Must be called
662 # just before the system exits to ensure accuracy.
663 duration_secs = (datetime.datetime.now() - start_time).total_seconds()
664 record_autoserv(parser.options, duration_secs)
jadmanski0afbb632008-06-06 21:10:57 +0000665 sys.exit(exit_code)
mblighfaf0cd42007-11-19 16:00:24 +0000666
mblighbb421852008-03-11 22:36:16 +0000667
mbligha46678d2008-05-01 20:00:01 +0000668if __name__ == '__main__':
jadmanski0afbb632008-06-06 21:10:57 +0000669 main()