blob: 97faeaa099dab8c0b9c9e5a357c1beadd26d71db [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
Dan Shi32649b82015-08-29 20:53:36 -070025from autotest_lib.client.common_lib import error
Dan Shia1ecd5c2013-06-06 11:21:31 -070026from autotest_lib.client.common_lib import global_config
Dan Shi5ddf6a32015-05-02 00:22:01 -070027from autotest_lib.client.common_lib import utils
Dan Shi37bee222015-04-13 15:46:47 -070028from autotest_lib.client.common_lib.cros.graphite import autotest_es
29from autotest_lib.client.common_lib.cros.graphite import autotest_stats
Prashanth Balasubramanianf8b83712014-11-06 15:58:21 -080030try:
31 from autotest_lib.puppylab import results_mocker
32except ImportError:
33 results_mocker = None
34
Dan Shia06f3e22015-09-03 16:15:15 -070035_CONFIG = global_config.global_config
36
37require_atfork = _CONFIG.get_config_value(
mblighcb8cb332009-09-03 21:08:56 +000038 'AUTOSERV', 'require_atfork_module', type=bool, default=True)
39
Dan Shia1ecd5c2013-06-06 11:21:31 -070040
Jakob Jueliche497b552014-09-23 19:11:59 -070041# Number of seconds to wait before returning if testing mode is enabled
Prashanth B6285f6a2014-05-08 18:01:27 -070042TESTING_MODE_SLEEP_SECS = 1
Jakob Jueliche497b552014-09-23 19:11:59 -070043
mblighcb8cb332009-09-03 21:08:56 +000044try:
45 import atfork
46 atfork.monkeypatch_os_fork_functions()
47 import atfork.stdlib_fixer
48 # Fix the Python standard library for threading+fork safety with its
49 # internal locks. http://code.google.com/p/python-atfork/
50 import warnings
51 warnings.filterwarnings('ignore', 'logging module already imported')
52 atfork.stdlib_fixer.fix_logging_module()
53except ImportError, e:
54 from autotest_lib.client.common_lib import global_config
Dan Shia06f3e22015-09-03 16:15:15 -070055 if _CONFIG.get_config_value(
mblighcb8cb332009-09-03 21:08:56 +000056 'AUTOSERV', 'require_atfork_module', type=bool, default=False):
57 print >>sys.stderr, 'Please run utils/build_externals.py'
58 print e
59 sys.exit(1)
mbligh9ff89cd2009-09-03 20:28:17 +000060
Dan Shia1ecd5c2013-06-06 11:21:31 -070061from autotest_lib.server import frontend
showard75cdfee2009-06-10 17:40:41 +000062from autotest_lib.server import server_logging_config
showard043c62a2009-06-10 19:48:57 +000063from autotest_lib.server import server_job, utils, autoserv_parser, autotest
Dan Shia1ecd5c2013-06-06 11:21:31 -070064from autotest_lib.server import utils as server_utils
Dan Shicf4d2032015-03-12 15:04:21 -070065from autotest_lib.site_utils import job_directories
Fang Deng042c1472014-10-23 13:56:41 -070066from autotest_lib.site_utils import job_overhead
Dan Shicf4d2032015-03-12 15:04:21 -070067from autotest_lib.site_utils import lxc
Dan Shi7836d252015-04-27 15:33:58 -070068from autotest_lib.site_utils import lxc_utils
showard75cdfee2009-06-10 17:40:41 +000069from autotest_lib.client.common_lib import pidfile, logging_manager
Gabe Black1e1c41b2015-02-04 23:55:15 -080070from autotest_lib.client.common_lib.cros.graphite import autotest_stats
mbligh92c0fc22008-11-20 16:52:23 +000071
Dan Shicf4d2032015-03-12 15:04:21 -070072# Control segment to stage server-side package.
73STAGE_SERVER_SIDE_PACKAGE_CONTROL_FILE = server_job._control_segment_path(
74 'stage_server_side_package')
75
Dan Shia06f3e22015-09-03 16:15:15 -070076# Command line to start servod in a moblab.
77START_SERVOD_CMD = 'sudo start servod BOARD=%s PORT=%s'
78STOP_SERVOD_CMD = 'sudo stop servod'
79
Alex Millerf1af17e2013-01-09 22:50:32 -080080def log_alarm(signum, frame):
81 logging.error("Received SIGALARM. Ignoring and continuing on.")
Alex Miller0528d6f2013-01-11 10:49:48 -080082 sys.exit(1)
Alex Millerf1af17e2013-01-09 22:50:32 -080083
Dan Shicf4d2032015-03-12 15:04:21 -070084
85def _get_machines(parser):
86 """Get a list of machine names from command line arg -m or a file.
87
88 @param parser: Parser for the command line arguments.
89
90 @return: A list of machine names from command line arg -m or the
91 machines file specified in the command line arg -M.
92 """
93 if parser.options.machines:
94 machines = parser.options.machines.replace(',', ' ').strip().split()
95 else:
96 machines = []
97 machines_file = parser.options.machines_file
98 if machines_file:
99 machines = []
100 for m in open(machines_file, 'r').readlines():
101 # remove comments, spaces
102 m = re.sub('#.*', '', m).strip()
103 if m:
104 machines.append(m)
105 logging.debug('Read list of machines from file: %s', machines_file)
106 logging.debug('Machines: %s', ','.join(machines))
107
108 if machines:
109 for machine in machines:
110 if not machine or re.search('\s', machine):
111 parser.parser.error("Invalid machine: %s" % str(machine))
112 machines = list(set(machines))
113 machines.sort()
114 return machines
115
116
117def _stage_ssp(parser):
118 """Stage server-side package.
119
120 This function calls a control segment to stage server-side package based on
121 the job and autoserv command line option. The detail implementation could
122 be different for each host type. Currently, only CrosHost has
123 stage_server_side_package function defined.
124 The script returns None if no server-side package is available. However,
125 it may raise exception if it failed for reasons other than artifact (the
126 server-side package) not found.
127
128 @param parser: Command line arguments parser passed in the autoserv process.
129
130 @return: url of the staged server-side package. Return None if server-
131 side package is not found for the build.
132 """
Dan Shi36cfd832014-10-10 13:38:51 -0700133 # If test_source_build is not specified, default to use server-side test
134 # code from build specified in --image.
Dan Shicf4d2032015-03-12 15:04:21 -0700135 namespace = {'machines': _get_machines(parser),
Dan Shi36cfd832014-10-10 13:38:51 -0700136 'image': (parser.options.test_source_build or
137 parser.options.image),}
Dan Shicf4d2032015-03-12 15:04:21 -0700138 script_locals = {}
139 execfile(STAGE_SERVER_SIDE_PACKAGE_CONTROL_FILE, namespace, script_locals)
140 return script_locals['ssp_url']
141
142
Dan Shi37befda2015-12-07 13:16:56 -0800143def _run_with_ssp(job, container_name, job_id, results, parser, ssp_url):
Dan Shicf4d2032015-03-12 15:04:21 -0700144 """Run the server job with server-side packaging.
145
Dan Shi37befda2015-12-07 13:16:56 -0800146 @param job: The server job object.
Dan Shicf4d2032015-03-12 15:04:21 -0700147 @param container_name: Name of the container to run the test.
148 @param job_id: ID of the test job.
149 @param results: Folder to store results. This could be different from
150 parser.options.results:
151 parser.options.results can be set to None for results to be
152 stored in a temp folder.
153 results can be None for autoserv run requires no logging.
154 @param parser: Command line parser that contains the options.
155 @param ssp_url: url of the staged server-side package.
156 """
157 bucket = lxc.ContainerBucket()
158 control = (parser.args[0] if len(parser.args) > 0 and parser.args[0] != ''
159 else None)
Dan Shi37befda2015-12-07 13:16:56 -0800160 try:
161 test_container = bucket.setup_test(container_name, job_id, ssp_url,
162 results, control=control)
163 except Exception as e:
164 job.record('FAIL', None, None,
165 'Failed to setup container for test: %s. Check logs in '
166 'ssp_logs folder for more details.' % e)
167 raise
168
Dan Shicf4d2032015-03-12 15:04:21 -0700169 args = sys.argv[:]
170 args.remove('--require-ssp')
Dan Shi77b79a62015-07-29 16:22:05 -0700171 # --parent_job_id is only useful in autoserv running in host, not in
172 # container. Include this argument will cause test to fail for builds before
173 # CL 286265 was merged.
174 if '--parent_job_id' in args:
175 index = args.index('--parent_job_id')
176 args.remove('--parent_job_id')
177 # Remove the actual parent job id in command line arg.
178 del args[index]
Dan Shicf4d2032015-03-12 15:04:21 -0700179
180 # A dictionary of paths to replace in the command line. Key is the path to
181 # be replaced with the one in value.
182 paths_to_replace = {}
183 # Replace the control file path with the one in container.
184 if control:
185 container_control_filename = os.path.join(
186 lxc.CONTROL_TEMP_PATH, os.path.basename(control))
187 paths_to_replace[control] = container_control_filename
188 # Update result directory with the one in container.
189 if parser.options.results:
190 container_result_dir = os.path.join(lxc.RESULT_DIR_FMT % job_id)
191 paths_to_replace[parser.options.results] = container_result_dir
192 # Update parse_job directory with the one in container. The assumption is
193 # that the result folder to be parsed is always the same as the results_dir.
194 if parser.options.parse_job:
195 container_parse_dir = os.path.join(lxc.RESULT_DIR_FMT % job_id)
196 paths_to_replace[parser.options.parse_job] = container_result_dir
197
198 args = [paths_to_replace.get(arg, arg) for arg in args]
199
200 # Apply --use-existing-results, results directory is aready created and
201 # mounted in container. Apply this arg to avoid exception being raised.
202 if not '--use-existing-results' in args:
203 args.append('--use-existing-results')
204
205 # Make sure autoserv running in container using a different pid file.
206 if not '--pidfile-label' in args:
207 args.extend(['--pidfile-label', 'container_autoserv'])
208
Dan Shid1f51232015-04-18 00:29:14 -0700209 cmd_line = ' '.join(["'%s'" % arg if ' ' in arg else arg for arg in args])
Dan Shicf4d2032015-03-12 15:04:21 -0700210 logging.info('Run command in container: %s', cmd_line)
Dan Shi37bee222015-04-13 15:46:47 -0700211 success = False
Dan Shicf4d2032015-03-12 15:04:21 -0700212 try:
213 test_container.attach_run(cmd_line)
Dan Shi37bee222015-04-13 15:46:47 -0700214 success = True
Dan Shicf4d2032015-03-12 15:04:21 -0700215 finally:
Dan Shi37bee222015-04-13 15:46:47 -0700216 counter_key = '%s.%s' % (lxc.STATS_KEY,
217 'success' if success else 'fail')
218 autotest_stats.Counter(counter_key).increment()
219 # metadata is uploaded separately so it can use http to upload.
220 metadata = {'drone': socket.gethostname(),
221 'job_id': job_id,
222 'success': success}
223 autotest_es.post(use_http=True,
224 type_str=lxc.CONTAINER_RUN_TEST_METADB_TYPE,
225 metadata=metadata)
Dan Shicf4d2032015-03-12 15:04:21 -0700226 test_container.destroy()
227
228
Dan Shi3f1b8a52015-04-21 11:11:06 -0700229def correct_results_folder_permission(results):
230 """Make sure the results folder has the right permission settings.
231
232 For tests running with server-side packaging, the results folder has the
233 owner of root. This must be changed to the user running the autoserv
234 process, so parsing job can access the results folder.
235 TODO(dshi): crbug.com/459344 Remove this function when test container can be
236 unprivileged container.
237
238 @param results: Path to the results folder.
239
240 """
241 if not results:
242 return
243
Dan Shi32649b82015-08-29 20:53:36 -0700244 try:
245 utils.run('sudo -n chown -R %s "%s"' % (os.getuid(), results))
246 utils.run('sudo -n chgrp -R %s "%s"' % (os.getgid(), results))
247 except error.CmdError as e:
248 metadata = {'error': str(e),
249 'result_folder': results,
250 'drone': socket.gethostname()}
251 autotest_es.post(use_http=True, type_str='correct_results_folder_failure',
252 metadata=metadata)
253 raise
Dan Shi3f1b8a52015-04-21 11:11:06 -0700254
255
Dan Shia06f3e22015-09-03 16:15:15 -0700256def _start_servod(machine):
257 """Try to start servod in moblab if it's not already running or running with
258 different board or port.
259
260 @param machine: Name of the dut used for test.
261 """
262 if not utils.is_moblab():
263 return
264
Dan Shi1cded882015-09-23 16:52:26 -0700265 logging.debug('Trying to start servod.')
Dan Shia06f3e22015-09-03 16:15:15 -0700266 try:
267 afe = frontend.AFE()
268 board = server_utils.get_board_from_afe(machine, afe)
269 hosts = afe.get_hosts(hostname=machine)
270 servo_host = hosts[0].attributes.get('servo_host', None)
271 servo_port = hosts[0].attributes.get('servo_port', 9999)
272 if not servo_host in ['localhost', '127.0.0.1']:
Dan Shi1cded882015-09-23 16:52:26 -0700273 logging.warn('Starting servod is aborted. The dut\'s servo_host '
274 'attribute is not set to localhost.')
Dan Shia06f3e22015-09-03 16:15:15 -0700275 return
276 except (urllib2.HTTPError, urllib2.URLError):
277 # Ignore error if RPC failed to get board
278 logging.error('Failed to get board name from AFE. Start servod is '
279 'aborted')
280 return
281
282 try:
283 pid = utils.run('pgrep servod').stdout
284 cmd_line = utils.run('ps -fp %s' % pid).stdout
285 if ('--board %s' % board in cmd_line and
286 '--port %s' % servo_port in cmd_line):
287 logging.debug('Servod is already running with given board and port.'
288 ' There is no need to restart servod.')
289 return
290 logging.debug('Servod is running with different board or port. '
291 'Stopping existing servod.')
292 utils.run('sudo stop servod')
293 except error.CmdError:
294 # servod is not running.
295 pass
296
297 try:
298 utils.run(START_SERVOD_CMD % (board, servo_port))
299 logging.debug('Servod is started')
300 except error.CmdError as e:
301 logging.error('Servod failed to be started, error: %s', e)
302
303
Dan Shic68fefb2015-04-07 10:10:52 -0700304def run_autoserv(pid_file_manager, results, parser, ssp_url, use_ssp):
Dan Shicf4d2032015-03-12 15:04:21 -0700305 """Run server job with given options.
306
307 @param pid_file_manager: PidFileManager used to monitor the autoserv process
308 @param results: Folder to store results.
309 @param parser: Parser for the command line arguments.
310 @param ssp_url: Url to server-side package.
Dan Shic68fefb2015-04-07 10:10:52 -0700311 @param use_ssp: Set to True to run with server-side packaging.
Dan Shicf4d2032015-03-12 15:04:21 -0700312 """
Dan Shiec1d47d2015-02-13 11:38:13 -0800313 if parser.options.warn_no_ssp:
Dan Shic68fefb2015-04-07 10:10:52 -0700314 # Post a warning in the log.
Dan Shiec1d47d2015-02-13 11:38:13 -0800315 logging.warn('Autoserv is required to run with server-side packaging. '
316 'However, no drone is found to support server-side '
317 'packaging. The test will be executed in a drone without '
318 'server-side packaging supported.')
319
jadmanski0afbb632008-06-06 21:10:57 +0000320 # send stdin to /dev/null
321 dev_null = os.open(os.devnull, os.O_RDONLY)
322 os.dup2(dev_null, sys.stdin.fileno())
323 os.close(dev_null)
mblighdbf37612007-11-24 19:38:11 +0000324
jadmanski0afbb632008-06-06 21:10:57 +0000325 # Create separate process group
326 os.setpgrp()
mbligh1d42d4e2007-11-05 22:42:00 +0000327
Dan Shicf4d2032015-03-12 15:04:21 -0700328 # Container name is predefined so the container can be destroyed in
329 # handle_sigterm.
330 job_or_task_id = job_directories.get_job_id_or_task_id(
331 parser.options.results)
332 container_name = (lxc.TEST_CONTAINER_NAME_FMT %
Dan Shid68d51c2015-04-21 17:00:42 -0700333 (job_or_task_id, time.time(), os.getpid()))
Dan Shicf4d2032015-03-12 15:04:21 -0700334
jadmanski0afbb632008-06-06 21:10:57 +0000335 # Implement SIGTERM handler
mblighc2299562009-07-02 19:00:36 +0000336 def handle_sigterm(signum, frame):
Simran Basi9d9b7292013-10-16 16:44:07 -0700337 logging.debug('Received SIGTERM')
mblighff7d61f2008-12-22 14:53:35 +0000338 if pid_file_manager:
339 pid_file_manager.close_file(1, signal.SIGTERM)
Simran Basi49e21e62013-10-17 12:40:33 -0700340 logging.debug('Finished writing to pid_file. Killing process.')
Dan Shi3f1b8a52015-04-21 11:11:06 -0700341
342 # Update results folder's file permission. This needs to be done ASAP
343 # before the parsing process tries to access the log.
344 if use_ssp and results:
345 correct_results_folder_permission(results)
346
Simran Basid6b83772014-01-06 16:31:30 -0800347 # TODO (sbasi) - remove the time.sleep when crbug.com/302815 is solved.
348 # This sleep allows the pending output to be logged before the kill
349 # signal is sent.
350 time.sleep(.1)
Dan Shic68fefb2015-04-07 10:10:52 -0700351 if use_ssp:
Dan Shicf4d2032015-03-12 15:04:21 -0700352 logging.debug('Destroy container %s before aborting the autoserv '
353 'process.', container_name)
Dan Shi3f1b8a52015-04-21 11:11:06 -0700354 metadata = {'drone': socket.gethostname(),
355 'job_id': job_or_task_id,
356 'container_name': container_name,
357 'action': 'abort',
358 'success': True}
Dan Shicf4d2032015-03-12 15:04:21 -0700359 try:
360 bucket = lxc.ContainerBucket()
361 container = bucket.get(container_name)
362 if container:
363 container.destroy()
364 else:
Dan Shi3f1b8a52015-04-21 11:11:06 -0700365 metadata['success'] = False
366 metadata['error'] = 'container not found'
Dan Shicf4d2032015-03-12 15:04:21 -0700367 logging.debug('Container %s is not found.', container_name)
368 except:
Dan Shi3f1b8a52015-04-21 11:11:06 -0700369 metadata['success'] = False
370 metadata['error'] = 'Exception: %s' % sys.exc_info()
Dan Shicf4d2032015-03-12 15:04:21 -0700371 # Handle any exception so the autoserv process can be aborted.
372 logging.error('Failed to destroy container %s. Error: %s',
373 container_name, sys.exc_info())
Dan Shi3f1b8a52015-04-21 11:11:06 -0700374 autotest_es.post(use_http=True,
375 type_str=lxc.CONTAINER_RUN_TEST_METADB_TYPE,
376 metadata=metadata)
Dan Shie4a4f9f2015-07-20 09:00:25 -0700377 # Try to correct the result file permission again after the
378 # container is destroyed, as the container might have created some
379 # new files in the result folder.
380 if results:
381 correct_results_folder_permission(results)
Dan Shicf4d2032015-03-12 15:04:21 -0700382
jadmanski0afbb632008-06-06 21:10:57 +0000383 os.killpg(os.getpgrp(), signal.SIGKILL)
mblighfaf0cd42007-11-19 16:00:24 +0000384
jadmanski0afbb632008-06-06 21:10:57 +0000385 # Set signal handler
mblighc2299562009-07-02 19:00:36 +0000386 signal.signal(signal.SIGTERM, handle_sigterm)
mbligha46678d2008-05-01 20:00:01 +0000387
Simran Basid6b83772014-01-06 16:31:30 -0800388 # faulthandler is only needed to debug in the Lab and is not avaliable to
389 # be imported in the chroot as part of VMTest, so Try-Except it.
390 try:
391 import faulthandler
392 faulthandler.register(signal.SIGTERM, all_threads=True, chain=True)
393 logging.debug('faulthandler registered on SIGTERM.')
394 except ImportError:
Christopher Grant4beca022015-06-16 15:14:47 -0400395 sys.exc_clear()
Simran Basid6b83772014-01-06 16:31:30 -0800396
David Rochberg8a60d1e2011-02-01 14:22:07 -0500397 # Ignore SIGTTOU's generated by output from forked children.
398 signal.signal(signal.SIGTTOU, signal.SIG_IGN)
399
Alex Millerf1af17e2013-01-09 22:50:32 -0800400 # If we received a SIGALARM, let's be loud about it.
401 signal.signal(signal.SIGALRM, log_alarm)
402
mbligha5f5e542009-12-30 16:57:49 +0000403 # Server side tests that call shell scripts often depend on $USER being set
404 # but depending on how you launch your autotest scheduler it may not be set.
405 os.environ['USER'] = getpass.getuser()
406
mblighb2bea302008-07-24 20:25:57 +0000407 label = parser.options.label
mbligh374f3412009-05-13 21:29:45 +0000408 group_name = parser.options.group_name
mblighb2bea302008-07-24 20:25:57 +0000409 user = parser.options.user
410 client = parser.options.client
411 server = parser.options.server
jadmanski0afbb632008-06-06 21:10:57 +0000412 install_before = parser.options.install_before
mblighb2bea302008-07-24 20:25:57 +0000413 install_after = parser.options.install_after
414 verify = parser.options.verify
415 repair = parser.options.repair
showard45ae8192008-11-05 19:32:53 +0000416 cleanup = parser.options.cleanup
Alex Millercb79ba72013-05-29 14:43:00 -0700417 provision = parser.options.provision
Dan Shi07e09af2013-04-12 09:31:29 -0700418 reset = parser.options.reset
Alex Miller667b5f22014-02-28 15:33:39 -0800419 job_labels = parser.options.job_labels
mblighb2bea302008-07-24 20:25:57 +0000420 no_tee = parser.options.no_tee
jadmanski0afbb632008-06-06 21:10:57 +0000421 parse_job = parser.options.parse_job
mblighe7d9c602009-07-02 19:02:33 +0000422 execution_tag = parser.options.execution_tag
423 if not execution_tag:
424 execution_tag = parse_job
jadmanski0afbb632008-06-06 21:10:57 +0000425 ssh_user = parser.options.ssh_user
426 ssh_port = parser.options.ssh_port
427 ssh_pass = parser.options.ssh_pass
jadmanskidef0c3c2009-03-25 20:07:10 +0000428 collect_crashinfo = parser.options.collect_crashinfo
mblighe0cbc912010-03-11 18:03:07 +0000429 control_filename = parser.options.control_filename
Scott Zawalski91493c82013-01-25 16:15:20 -0500430 test_retry = parser.options.test_retry
beepscb6f1e22013-06-28 19:14:10 -0700431 verify_job_repo_url = parser.options.verify_job_repo_url
Christopher Wileyf594c5e2013-07-03 18:25:30 -0700432 skip_crash_collection = parser.options.skip_crash_collection
Aviv Keshet18ee3142013-08-12 15:01:51 -0700433 ssh_verbosity = int(parser.options.ssh_verbosity)
Fang Deng6cc20de2013-09-06 15:47:32 -0700434 ssh_options = parser.options.ssh_options
Dan Shib669cbd2013-09-13 11:17:17 -0700435 no_use_packaging = parser.options.no_use_packaging
mbligha46678d2008-05-01 20:00:01 +0000436
mblighb2bea302008-07-24 20:25:57 +0000437 # can't be both a client and a server side test
438 if client and server:
Eric Li861b2d52011-02-04 14:50:35 -0800439 parser.parser.error("Can not specify a test as both server and client!")
mblighb2bea302008-07-24 20:25:57 +0000440
Alex Millercb79ba72013-05-29 14:43:00 -0700441 if provision and client:
442 parser.parser.error("Cannot specify provisioning and client!")
443
444 is_special_task = (verify or repair or cleanup or collect_crashinfo or
Dan Shi07e09af2013-04-12 09:31:29 -0700445 provision or reset)
Alex Millercb79ba72013-05-29 14:43:00 -0700446 if len(parser.args) < 1 and not is_special_task:
Eric Li861b2d52011-02-04 14:50:35 -0800447 parser.parser.error("Missing argument: control file")
mbligha46678d2008-05-01 20:00:01 +0000448
Aviv Keshet18ee3142013-08-12 15:01:51 -0700449 if ssh_verbosity > 0:
450 # ssh_verbosity is an integer between 0 and 3, inclusive
451 ssh_verbosity_flag = '-' + 'v' * ssh_verbosity
Fang Dengd1c2b732013-08-20 12:59:46 -0700452 else:
453 ssh_verbosity_flag = ''
Aviv Keshet18ee3142013-08-12 15:01:51 -0700454
showard45ae8192008-11-05 19:32:53 +0000455 # We have a control file unless it's just a verify/repair/cleanup job
jadmanski0afbb632008-06-06 21:10:57 +0000456 if len(parser.args) > 0:
457 control = parser.args[0]
458 else:
459 control = None
mbligha46678d2008-05-01 20:00:01 +0000460
Dan Shicf4d2032015-03-12 15:04:21 -0700461 machines = _get_machines(parser)
mbligh374f3412009-05-13 21:29:45 +0000462 if group_name and len(machines) < 2:
Dan Shicf4d2032015-03-12 15:04:21 -0700463 parser.parser.error('-G %r may only be supplied with more than one '
464 'machine.' % group_name)
mbligh374f3412009-05-13 21:29:45 +0000465
Christopher Wiley8a91f232013-07-09 11:02:27 -0700466 kwargs = {'group_name': group_name, 'tag': execution_tag,
Dan Shicf4d2032015-03-12 15:04:21 -0700467 'disable_sysinfo': parser.options.disable_sysinfo}
Dan Shi70647ca2015-07-16 22:52:35 -0700468 if parser.options.parent_job_id:
469 kwargs['parent_job_id'] = int(parser.options.parent_job_id)
mblighe0cbc912010-03-11 18:03:07 +0000470 if control_filename:
471 kwargs['control_filename'] = control_filename
jadmanski0afbb632008-06-06 21:10:57 +0000472 job = server_job.server_job(control, parser.args[1:], results, label,
473 user, machines, client, parse_job,
Fang Dengd1c2b732013-08-20 12:59:46 -0700474 ssh_user, ssh_port, ssh_pass,
Aviv Keshetc5947fa2013-09-04 14:06:29 -0700475 ssh_verbosity_flag, ssh_options,
476 test_retry, **kwargs)
Dan Shicf4d2032015-03-12 15:04:21 -0700477
showard75cdfee2009-06-10 17:40:41 +0000478 job.logging.start_logging()
mbligh4608b002010-01-05 18:22:35 +0000479 job.init_parser()
mbligha46678d2008-05-01 20:00:01 +0000480
mbligh161fe6f2008-06-19 16:26:04 +0000481 # perform checks
482 job.precheck()
483
jadmanski0afbb632008-06-06 21:10:57 +0000484 # run the job
485 exit_code = 0
Dan Shic1b8bdd2015-09-14 23:11:24 -0700486 auto_start_servod = _CONFIG.get_config_value(
487 'AUTOSERV', 'auto_start_servod', type=bool, default=False)
jadmanski0afbb632008-06-06 21:10:57 +0000488 try:
mbligh332000a2009-06-08 16:47:28 +0000489 try:
490 if repair:
Dan Shic1b8bdd2015-09-14 23:11:24 -0700491 if auto_start_servod and len(machines) == 1:
492 _start_servod(machines[0])
J. Richard Barnettec2d99cf2015-11-18 12:46:15 -0800493 job.repair(job_labels)
mbligh332000a2009-06-08 16:47:28 +0000494 elif verify:
Alex Miller667b5f22014-02-28 15:33:39 -0800495 job.verify(job_labels)
Alex Millercb79ba72013-05-29 14:43:00 -0700496 elif provision:
Alex Miller667b5f22014-02-28 15:33:39 -0800497 job.provision(job_labels)
Dan Shi07e09af2013-04-12 09:31:29 -0700498 elif reset:
Alex Miller667b5f22014-02-28 15:33:39 -0800499 job.reset(job_labels)
Fang Dengad78aca2014-10-02 18:15:46 -0700500 elif cleanup:
501 job.cleanup(job_labels)
mbligh332000a2009-06-08 16:47:28 +0000502 else:
Dan Shia06f3e22015-09-03 16:15:15 -0700503 if auto_start_servod and len(machines) == 1:
504 _start_servod(machines[0])
Dan Shic68fefb2015-04-07 10:10:52 -0700505 if use_ssp:
Dan Shicf4d2032015-03-12 15:04:21 -0700506 try:
Dan Shi37befda2015-12-07 13:16:56 -0800507 _run_with_ssp(job, container_name, job_or_task_id,
508 results, parser, ssp_url)
Dan Shicf4d2032015-03-12 15:04:21 -0700509 finally:
510 # Update the ownership of files in result folder.
Dan Shi3f1b8a52015-04-21 11:11:06 -0700511 correct_results_folder_permission(results)
Dan Shicf4d2032015-03-12 15:04:21 -0700512 else:
513 job.run(install_before, install_after,
514 verify_job_repo_url=verify_job_repo_url,
515 only_collect_crashinfo=collect_crashinfo,
516 skip_crash_collection=skip_crash_collection,
Dan Shib669cbd2013-09-13 11:17:17 -0700517 job_labels=job_labels,
518 use_packaging=(not no_use_packaging))
mbligh332000a2009-06-08 16:47:28 +0000519 finally:
520 while job.hosts:
521 host = job.hosts.pop()
522 host.close()
jadmanski0afbb632008-06-06 21:10:57 +0000523 except:
jadmanski27b37ea2008-10-29 23:54:31 +0000524 exit_code = 1
jadmanski0afbb632008-06-06 21:10:57 +0000525 traceback.print_exc()
mbligha46678d2008-05-01 20:00:01 +0000526
mblighff7d61f2008-12-22 14:53:35 +0000527 if pid_file_manager:
528 pid_file_manager.num_tests_failed = job.num_tests_failed
529 pid_file_manager.close_file(exit_code)
jadmanskie0dffc32008-12-15 17:30:30 +0000530 job.cleanup_parser()
showard21baa452008-10-21 00:08:39 +0000531
jadmanski27b37ea2008-10-29 23:54:31 +0000532 sys.exit(exit_code)
mbligha46678d2008-05-01 20:00:01 +0000533
534
Fang Deng042c1472014-10-23 13:56:41 -0700535def record_autoserv(options, duration_secs):
536 """Record autoserv end-to-end time in metadata db.
537
538 @param options: parser options.
539 @param duration_secs: How long autoserv has taken, in secs.
540 """
541 # Get machine hostname
542 machines = options.machines.replace(
543 ',', ' ').strip().split() if options.machines else []
544 num_machines = len(machines)
545 if num_machines > 1:
546 # Skip the case where atomic group is used.
547 return
548 elif num_machines == 0:
549 machines.append('hostless')
550
551 # Determine the status that will be reported.
552 s = job_overhead.STATUS
553 task_mapping = {
554 'reset': s.RESETTING, 'verify': s.VERIFYING,
555 'provision': s.PROVISIONING, 'repair': s.REPAIRING,
556 'cleanup': s.CLEANING, 'collect_crashinfo': s.GATHERING}
Dan Shi888cfca2015-07-31 15:49:00 -0700557 match = filter(lambda task: getattr(options, task, False) == True,
558 task_mapping)
Fang Deng042c1472014-10-23 13:56:41 -0700559 status = task_mapping[match[0]] if match else s.RUNNING
560 is_special_task = status not in [s.RUNNING, s.GATHERING]
Dan Shicf4d2032015-03-12 15:04:21 -0700561 job_or_task_id = job_directories.get_job_id_or_task_id(options.results)
Fang Deng042c1472014-10-23 13:56:41 -0700562 job_overhead.record_state_duration(
563 job_or_task_id, machines[0], status, duration_secs,
564 is_special_task=is_special_task)
565
566
mbligha46678d2008-05-01 20:00:01 +0000567def main():
Fang Deng042c1472014-10-23 13:56:41 -0700568 start_time = datetime.datetime.now()
Dan Shia1ecd5c2013-06-06 11:21:31 -0700569 # White list of tests with run time measurement enabled.
Dan Shia06f3e22015-09-03 16:15:15 -0700570 measure_run_time_tests_names = _CONFIG.get_config_value(
571 'AUTOSERV', 'measure_run_time_tests', type=str)
Dan Shia1ecd5c2013-06-06 11:21:31 -0700572 if measure_run_time_tests_names:
573 measure_run_time_tests = [t.strip() for t in
574 measure_run_time_tests_names.split(',')]
575 else:
576 measure_run_time_tests = []
jadmanski0afbb632008-06-06 21:10:57 +0000577 # grab the parser
578 parser = autoserv_parser.autoserv_parser
mbligha5cb4062009-02-17 15:53:39 +0000579 parser.parse_args()
mbligha46678d2008-05-01 20:00:01 +0000580
jadmanski0afbb632008-06-06 21:10:57 +0000581 if len(sys.argv) == 1:
582 parser.parser.print_help()
583 sys.exit(1)
mbligha6f13082008-06-05 23:53:46 +0000584
Dan Shicf4d2032015-03-12 15:04:21 -0700585 # If the job requires to run with server-side package, try to stage server-
586 # side package first. If that fails with error that autotest server package
Dan Shic68fefb2015-04-07 10:10:52 -0700587 # does not exist, fall back to run the job without using server-side
588 # packaging. If option warn_no_ssp is specified, that means autoserv is
589 # running in a drone does not support SSP, thus no need to stage server-side
590 # package.
Dan Shicf4d2032015-03-12 15:04:21 -0700591 ssp_url = None
Dan Shi0b754c52015-04-20 14:20:38 -0700592 ssp_url_warning = False
Dan Shic68fefb2015-04-07 10:10:52 -0700593 if (not parser.options.warn_no_ssp and parser.options.require_ssp):
Dan Shicf4d2032015-03-12 15:04:21 -0700594 ssp_url = _stage_ssp(parser)
Dan Shi0b754c52015-04-20 14:20:38 -0700595 # The build does not have autotest server package. Fall back to not
596 # to use server-side package. Logging is postponed until logging being
597 # set up.
598 ssp_url_warning = not ssp_url
Dan Shicf4d2032015-03-12 15:04:21 -0700599
showard75cdfee2009-06-10 17:40:41 +0000600 if parser.options.no_logging:
601 results = None
602 else:
603 results = parser.options.results
mbligh80e1eba2008-11-19 00:26:18 +0000604 if not results:
605 results = 'results.' + time.strftime('%Y-%m-%d-%H.%M.%S')
606 results = os.path.abspath(results)
showard566d3c02010-01-12 18:57:01 +0000607 resultdir_exists = False
608 for filename in ('control.srv', 'status.log', '.autoserv_execute'):
609 if os.path.exists(os.path.join(results, filename)):
610 resultdir_exists = True
mbligh4608b002010-01-05 18:22:35 +0000611 if not parser.options.use_existing_results and resultdir_exists:
mbligh80e1eba2008-11-19 00:26:18 +0000612 error = "Error: results directory already exists: %s\n" % results
613 sys.stderr.write(error)
614 sys.exit(1)
mbligha788dc42009-03-26 21:10:16 +0000615
616 # Now that we certified that there's no leftover results dir from
617 # previous jobs, lets create the result dir since the logging system
618 # needs to create the log file in there.
619 if not os.path.isdir(results):
620 os.makedirs(results)
showard75cdfee2009-06-10 17:40:41 +0000621
Dan Shic68fefb2015-04-07 10:10:52 -0700622 # Server-side packaging will only be used if it's required and the package
623 # is available. If warn_no_ssp is specified, it means that autoserv is
624 # running in a drone does not have SSP supported and a warning will be logs.
625 # Therefore, it should not run with SSP.
626 use_ssp = (not parser.options.warn_no_ssp and parser.options.require_ssp
627 and ssp_url)
628 if use_ssp:
Dan Shie28de552015-05-06 16:51:58 -0700629 log_dir = os.path.join(results, 'ssp_logs') if results else None
Dan Shicf4d2032015-03-12 15:04:21 -0700630 if log_dir and not os.path.exists(log_dir):
631 os.makedirs(log_dir)
632 else:
633 log_dir = results
Dan Shi3f1b8a52015-04-21 11:11:06 -0700634
showard75cdfee2009-06-10 17:40:41 +0000635 logging_manager.configure_logging(
Dan Shicf4d2032015-03-12 15:04:21 -0700636 server_logging_config.ServerLoggingConfig(),
637 results_dir=log_dir,
showard10d84172009-06-18 23:16:50 +0000638 use_console=not parser.options.no_tee,
639 verbose=parser.options.verbose,
640 no_console_prefix=parser.options.no_console_prefix)
Dan Shicf4d2032015-03-12 15:04:21 -0700641
Dan Shi0b754c52015-04-20 14:20:38 -0700642 if ssp_url_warning:
643 logging.warn(
644 'Autoserv is required to run with server-side packaging. '
645 'However, no server-side package can be found based on '
646 '`--image`, host attribute job_repo_url or host label of '
647 'cros-version. The test will be executed without '
648 'server-side packaging supported.')
649
showard75cdfee2009-06-10 17:40:41 +0000650 if results:
mbligha788dc42009-03-26 21:10:16 +0000651 logging.info("Results placed in %s" % results)
mbligh10717632008-11-19 00:21:57 +0000652
mbligh4608b002010-01-05 18:22:35 +0000653 # wait until now to perform this check, so it get properly logged
Dan Shicf4d2032015-03-12 15:04:21 -0700654 if (parser.options.use_existing_results and not resultdir_exists and
Dan Shiff78f112015-06-12 13:34:02 -0700655 not utils.is_in_container()):
mbligh4608b002010-01-05 18:22:35 +0000656 logging.error("No existing results directory found: %s", results)
657 sys.exit(1)
658
Dan Shicf4d2032015-03-12 15:04:21 -0700659 logging.debug('autoserv is running in drone %s.', socket.gethostname())
Aviv Keshet5c40ec62013-08-20 12:11:12 -0700660 logging.debug('autoserv command was: %s', ' '.join(sys.argv))
mbligh4608b002010-01-05 18:22:35 +0000661
Dan Shicf4d2032015-03-12 15:04:21 -0700662 if parser.options.write_pidfile and results:
mbligh4608b002010-01-05 18:22:35 +0000663 pid_file_manager = pidfile.PidFileManager(parser.options.pidfile_label,
664 results)
jadmanskid5ab8c52008-12-03 16:27:07 +0000665 pid_file_manager.open_file()
mblighff7d61f2008-12-22 14:53:35 +0000666 else:
667 pid_file_manager = None
mbligha46678d2008-05-01 20:00:01 +0000668
jadmanskif22fea82008-11-26 20:57:07 +0000669 autotest.BaseAutotest.set_install_in_tmpdir(
670 parser.options.install_in_tmpdir)
671
Dan Shia1ecd5c2013-06-06 11:21:31 -0700672 timer = None
673 try:
674 # Take the first argument as control file name, get the test name from
675 # the control file. If the test name exists in the list of tests with
676 # run time measurement enabled, start a timer to begin measurement.
677 if (len(parser.args) > 0 and parser.args[0] != '' and
678 parser.options.machines):
Dan Shibbc16132013-07-09 16:23:59 -0700679 try:
680 test_name = control_data.parse_control(parser.args[0],
681 raise_warnings=True).name
682 except control_data.ControlVariableException:
683 logging.debug('Failed to retrieve test name from control file.')
684 test_name = None
Dan Shia1ecd5c2013-06-06 11:21:31 -0700685 if test_name in measure_run_time_tests:
686 machines = parser.options.machines.replace(',', ' '
687 ).strip().split()
Dan Shi8eac5af2014-09-17 00:15:15 -0700688 try:
689 afe = frontend.AFE()
690 board = server_utils.get_board_from_afe(machines[0], afe)
Gabe Black1e1c41b2015-02-04 23:55:15 -0800691 timer = autotest_stats.Timer('autoserv_run_time.%s.%s' %
692 (board, test_name))
Dan Shi8eac5af2014-09-17 00:15:15 -0700693 timer.start()
694 except (urllib2.HTTPError, urllib2.URLError):
695 # Ignore error if RPC failed to get board
696 pass
Dan Shia1ecd5c2013-06-06 11:21:31 -0700697 except control_data.ControlVariableException as e:
698 logging.error(str(e))
jadmanski0afbb632008-06-06 21:10:57 +0000699 exit_code = 0
Prashanth B6285f6a2014-05-08 18:01:27 -0700700 # TODO(beeps): Extend this to cover different failure modes.
701 # Testing exceptions are matched against labels sent to autoserv. Eg,
702 # to allow only the hostless job to run, specify
703 # testing_exceptions: test_suite in the shadow_config. To allow both
704 # the hostless job and dummy_Pass to run, specify
705 # testing_exceptions: test_suite,dummy_Pass. You can figure out
706 # what label autoserv is invoked with by looking through the logs of a test
707 # for the autoserv command's -l option.
Dan Shia06f3e22015-09-03 16:15:15 -0700708 testing_exceptions = _CONFIG.get_config_value(
Prashanth B6285f6a2014-05-08 18:01:27 -0700709 'AUTOSERV', 'testing_exceptions', type=list, default=[])
Dan Shia06f3e22015-09-03 16:15:15 -0700710 test_mode = _CONFIG.get_config_value(
Prashanth B6285f6a2014-05-08 18:01:27 -0700711 'AUTOSERV', 'testing_mode', type=bool, default=False)
Prashanth Balasubramanianf8b83712014-11-06 15:58:21 -0800712 test_mode = (results_mocker and test_mode and not
713 any([ex in parser.options.label
714 for ex in testing_exceptions]))
715 is_task = (parser.options.verify or parser.options.repair or
716 parser.options.provision or parser.options.reset or
717 parser.options.cleanup or parser.options.collect_crashinfo)
jadmanski0afbb632008-06-06 21:10:57 +0000718 try:
719 try:
Prashanth B6285f6a2014-05-08 18:01:27 -0700720 if test_mode:
Prashanth Balasubramanianf8b83712014-11-06 15:58:21 -0800721 # The parser doesn't run on tasks anyway, so we can just return
722 # happy signals without faking results.
723 if not is_task:
724 machine = parser.options.results.split('/')[-1]
725
726 # TODO(beeps): The proper way to do this would be to
727 # refactor job creation so we can invoke job.record
728 # directly. To do that one needs to pipe the test_name
729 # through run_autoserv and bail just before invoking
730 # the server job. See the comment in
731 # puppylab/results_mocker for more context.
732 results_mocker.ResultsMocker(
Prashanth Balasubramanian22dd2262014-11-28 18:19:18 -0800733 test_name if test_name else 'unknown-test',
734 parser.options.results, machine
Prashanth Balasubramanianf8b83712014-11-06 15:58:21 -0800735 ).mock_results()
736 return
Prashanth B6285f6a2014-05-08 18:01:27 -0700737 else:
Dan Shic68fefb2015-04-07 10:10:52 -0700738 run_autoserv(pid_file_manager, results, parser, ssp_url,
739 use_ssp)
Aviv Keshet5c40ec62013-08-20 12:11:12 -0700740 except SystemExit as e:
jadmanski0afbb632008-06-06 21:10:57 +0000741 exit_code = e.code
Aviv Keshet5c40ec62013-08-20 12:11:12 -0700742 if exit_code:
743 logging.exception(e)
744 except Exception as e:
jadmanski0afbb632008-06-06 21:10:57 +0000745 # If we don't know what happened, we'll classify it as
746 # an 'abort' and return 1.
Aviv Keshet5c40ec62013-08-20 12:11:12 -0700747 logging.exception(e)
jadmanski0afbb632008-06-06 21:10:57 +0000748 exit_code = 1
749 finally:
mblighff7d61f2008-12-22 14:53:35 +0000750 if pid_file_manager:
751 pid_file_manager.close_file(exit_code)
Dan Shia1ecd5c2013-06-06 11:21:31 -0700752 if timer:
753 timer.stop()
Fang Deng042c1472014-10-23 13:56:41 -0700754 # Record the autoserv duration time. Must be called
755 # just before the system exits to ensure accuracy.
756 duration_secs = (datetime.datetime.now() - start_time).total_seconds()
757 record_autoserv(parser.options, duration_secs)
jadmanski0afbb632008-06-06 21:10:57 +0000758 sys.exit(exit_code)
mblighfaf0cd42007-11-19 16:00:24 +0000759
mblighbb421852008-03-11 22:36:16 +0000760
mbligha46678d2008-05-01 20:00:01 +0000761if __name__ == '__main__':
jadmanski0afbb632008-06-06 21:10:57 +0000762 main()