blob: 46728e12e4b95af444eaa1f7a583fd2bd3525765 [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
Kevin Cheng9b6930f2016-07-20 14:57:15 -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
Kevin Chengadc99f92016-07-20 08:21:58 -070065from autotest_lib.server.cros.dynamic_suite import frontend_wrappers
Dan Shicf4d2032015-03-12 15:04:21 -070066from autotest_lib.site_utils import job_directories
Fang Deng042c1472014-10-23 13:56:41 -070067from autotest_lib.site_utils import job_overhead
Dan Shicf4d2032015-03-12 15:04:21 -070068from autotest_lib.site_utils import lxc
Dan Shi7836d252015-04-27 15:33:58 -070069from autotest_lib.site_utils import lxc_utils
showard75cdfee2009-06-10 17:40:41 +000070from autotest_lib.client.common_lib import pidfile, logging_manager
Gabe Black1e1c41b2015-02-04 23:55:15 -080071from autotest_lib.client.common_lib.cros.graphite import autotest_stats
mbligh92c0fc22008-11-20 16:52:23 +000072
Dan Shicf4d2032015-03-12 15:04:21 -070073# Control segment to stage server-side package.
74STAGE_SERVER_SIDE_PACKAGE_CONTROL_FILE = server_job._control_segment_path(
75 'stage_server_side_package')
76
Dan Shia06f3e22015-09-03 16:15:15 -070077# Command line to start servod in a moblab.
78START_SERVOD_CMD = 'sudo start servod BOARD=%s PORT=%s'
79STOP_SERVOD_CMD = 'sudo stop servod'
80
Alex Millerf1af17e2013-01-09 22:50:32 -080081def log_alarm(signum, frame):
82 logging.error("Received SIGALARM. Ignoring and continuing on.")
Alex Miller0528d6f2013-01-11 10:49:48 -080083 sys.exit(1)
Alex Millerf1af17e2013-01-09 22:50:32 -080084
Dan Shicf4d2032015-03-12 15:04:21 -070085
86def _get_machines(parser):
87 """Get a list of machine names from command line arg -m or a file.
88
89 @param parser: Parser for the command line arguments.
90
91 @return: A list of machine names from command line arg -m or the
92 machines file specified in the command line arg -M.
93 """
94 if parser.options.machines:
95 machines = parser.options.machines.replace(',', ' ').strip().split()
96 else:
97 machines = []
98 machines_file = parser.options.machines_file
99 if machines_file:
100 machines = []
101 for m in open(machines_file, 'r').readlines():
102 # remove comments, spaces
103 m = re.sub('#.*', '', m).strip()
104 if m:
105 machines.append(m)
106 logging.debug('Read list of machines from file: %s', machines_file)
107 logging.debug('Machines: %s', ','.join(machines))
108
109 if machines:
110 for machine in machines:
111 if not machine or re.search('\s', machine):
112 parser.parser.error("Invalid machine: %s" % str(machine))
113 machines = list(set(machines))
114 machines.sort()
115 return machines
116
117
118def _stage_ssp(parser):
119 """Stage server-side package.
120
121 This function calls a control segment to stage server-side package based on
122 the job and autoserv command line option. The detail implementation could
123 be different for each host type. Currently, only CrosHost has
124 stage_server_side_package function defined.
125 The script returns None if no server-side package is available. However,
126 it may raise exception if it failed for reasons other than artifact (the
127 server-side package) not found.
128
129 @param parser: Command line arguments parser passed in the autoserv process.
130
Dan Shi14de7622016-08-22 11:09:06 -0700131 @return: (ssp_url, error_msg), where
132 ssp_url is a url to the autotest server-side package. None if
133 server-side package is not supported.
134 error_msg is a string indicating the failures. None if server-
135 side package is staged successfully.
Dan Shicf4d2032015-03-12 15:04:21 -0700136 """
Kevin Chengadc99f92016-07-20 08:21:58 -0700137 machines_list = _get_machines(parser)
138 if bool(parser.options.lab):
139 machine_dict_list = []
140 afe = frontend_wrappers.RetryingAFE(timeout_min=5, delay_sec=10)
141 for machine in machines_list:
Dan Shi14de7622016-08-22 11:09:06 -0700142 afe_host = afe.get_hosts(hostname=machine)[0]
143 machine_dict_list.append({'hostname': machine,
144 'afe_host': afe_host})
Kevin Chengadc99f92016-07-20 08:21:58 -0700145 machines_list = machine_dict_list
146
Dan Shi36cfd832014-10-10 13:38:51 -0700147 # If test_source_build is not specified, default to use server-side test
148 # code from build specified in --image.
Kevin Chengadc99f92016-07-20 08:21:58 -0700149 namespace = {'machines': machines_list,
Dan Shi36cfd832014-10-10 13:38:51 -0700150 'image': (parser.options.test_source_build or
151 parser.options.image),}
Dan Shicf4d2032015-03-12 15:04:21 -0700152 script_locals = {}
153 execfile(STAGE_SERVER_SIDE_PACKAGE_CONTROL_FILE, namespace, script_locals)
Dan Shi14de7622016-08-22 11:09:06 -0700154 return script_locals['ssp_url'], script_locals['error_msg']
Dan Shicf4d2032015-03-12 15:04:21 -0700155
156
Dan Shiafa63872016-02-23 15:32:31 -0800157def _run_with_ssp(job, container_name, job_id, results, parser, ssp_url,
Dan Shi3be35af2016-08-25 23:22:40 -0700158 job_folder, machines):
Dan Shicf4d2032015-03-12 15:04:21 -0700159 """Run the server job with server-side packaging.
160
Dan Shi37befda2015-12-07 13:16:56 -0800161 @param job: The server job object.
Dan Shicf4d2032015-03-12 15:04:21 -0700162 @param container_name: Name of the container to run the test.
163 @param job_id: ID of the test job.
164 @param results: Folder to store results. This could be different from
165 parser.options.results:
166 parser.options.results can be set to None for results to be
167 stored in a temp folder.
168 results can be None for autoserv run requires no logging.
169 @param parser: Command line parser that contains the options.
170 @param ssp_url: url of the staged server-side package.
Dan Shiafa63872016-02-23 15:32:31 -0800171 @param job_folder: Name of the job result folder.
Dan Shi3be35af2016-08-25 23:22:40 -0700172 @param machines: A list of machines to run the test.
Dan Shicf4d2032015-03-12 15:04:21 -0700173 """
174 bucket = lxc.ContainerBucket()
175 control = (parser.args[0] if len(parser.args) > 0 and parser.args[0] != ''
176 else None)
Dan Shi37befda2015-12-07 13:16:56 -0800177 try:
Dan Shi3be35af2016-08-25 23:22:40 -0700178 dut_name = machines[0] if len(machines) >= 1 else None
Dan Shi37befda2015-12-07 13:16:56 -0800179 test_container = bucket.setup_test(container_name, job_id, ssp_url,
Dan Shiafa63872016-02-23 15:32:31 -0800180 results, control=control,
Dan Shi3be35af2016-08-25 23:22:40 -0700181 job_folder=job_folder,
182 dut_name=dut_name)
Dan Shi37befda2015-12-07 13:16:56 -0800183 except Exception as e:
184 job.record('FAIL', None, None,
185 'Failed to setup container for test: %s. Check logs in '
186 'ssp_logs folder for more details.' % e)
187 raise
188
Dan Shicf4d2032015-03-12 15:04:21 -0700189 args = sys.argv[:]
190 args.remove('--require-ssp')
Dan Shi77b79a62015-07-29 16:22:05 -0700191 # --parent_job_id is only useful in autoserv running in host, not in
192 # container. Include this argument will cause test to fail for builds before
193 # CL 286265 was merged.
194 if '--parent_job_id' in args:
195 index = args.index('--parent_job_id')
196 args.remove('--parent_job_id')
197 # Remove the actual parent job id in command line arg.
198 del args[index]
Dan Shicf4d2032015-03-12 15:04:21 -0700199
200 # A dictionary of paths to replace in the command line. Key is the path to
201 # be replaced with the one in value.
202 paths_to_replace = {}
203 # Replace the control file path with the one in container.
204 if control:
205 container_control_filename = os.path.join(
206 lxc.CONTROL_TEMP_PATH, os.path.basename(control))
207 paths_to_replace[control] = container_control_filename
208 # Update result directory with the one in container.
209 if parser.options.results:
Dan Shiafa63872016-02-23 15:32:31 -0800210 container_result_dir = os.path.join(lxc.RESULT_DIR_FMT % job_folder)
Dan Shicf4d2032015-03-12 15:04:21 -0700211 paths_to_replace[parser.options.results] = container_result_dir
212 # Update parse_job directory with the one in container. The assumption is
213 # that the result folder to be parsed is always the same as the results_dir.
214 if parser.options.parse_job:
Dan Shiafa63872016-02-23 15:32:31 -0800215 container_parse_dir = os.path.join(lxc.RESULT_DIR_FMT % job_folder)
Dan Shicf4d2032015-03-12 15:04:21 -0700216 paths_to_replace[parser.options.parse_job] = container_result_dir
217
218 args = [paths_to_replace.get(arg, arg) for arg in args]
219
220 # Apply --use-existing-results, results directory is aready created and
221 # mounted in container. Apply this arg to avoid exception being raised.
222 if not '--use-existing-results' in args:
223 args.append('--use-existing-results')
224
225 # Make sure autoserv running in container using a different pid file.
226 if not '--pidfile-label' in args:
227 args.extend(['--pidfile-label', 'container_autoserv'])
228
Dan Shid1f51232015-04-18 00:29:14 -0700229 cmd_line = ' '.join(["'%s'" % arg if ' ' in arg else arg for arg in args])
Dan Shicf4d2032015-03-12 15:04:21 -0700230 logging.info('Run command in container: %s', cmd_line)
Dan Shi37bee222015-04-13 15:46:47 -0700231 success = False
Dan Shicf4d2032015-03-12 15:04:21 -0700232 try:
233 test_container.attach_run(cmd_line)
Dan Shi37bee222015-04-13 15:46:47 -0700234 success = True
Dan Shi9d3454e2015-12-08 09:16:08 -0800235 except Exception as e:
236 # If the test run inside container fails without generating any log,
237 # write a message to status.log to help troubleshooting.
238 debug_files = os.listdir(os.path.join(results, 'debug'))
239 if not debug_files:
240 job.record('FAIL', None, None,
241 'Failed to run test inside the container: %s. Check '
242 'logs in ssp_logs folder for more details.' % e)
243 raise
Dan Shicf4d2032015-03-12 15:04:21 -0700244 finally:
Dan Shi37bee222015-04-13 15:46:47 -0700245 counter_key = '%s.%s' % (lxc.STATS_KEY,
246 'success' if success else 'fail')
247 autotest_stats.Counter(counter_key).increment()
248 # metadata is uploaded separately so it can use http to upload.
249 metadata = {'drone': socket.gethostname(),
250 'job_id': job_id,
251 'success': success}
252 autotest_es.post(use_http=True,
253 type_str=lxc.CONTAINER_RUN_TEST_METADB_TYPE,
254 metadata=metadata)
Dan Shicf4d2032015-03-12 15:04:21 -0700255 test_container.destroy()
256
257
Dan Shi3f1b8a52015-04-21 11:11:06 -0700258def correct_results_folder_permission(results):
259 """Make sure the results folder has the right permission settings.
260
261 For tests running with server-side packaging, the results folder has the
262 owner of root. This must be changed to the user running the autoserv
263 process, so parsing job can access the results folder.
264 TODO(dshi): crbug.com/459344 Remove this function when test container can be
265 unprivileged container.
266
267 @param results: Path to the results folder.
268
269 """
270 if not results:
271 return
272
Dan Shi32649b82015-08-29 20:53:36 -0700273 try:
274 utils.run('sudo -n chown -R %s "%s"' % (os.getuid(), results))
275 utils.run('sudo -n chgrp -R %s "%s"' % (os.getgid(), results))
276 except error.CmdError as e:
277 metadata = {'error': str(e),
278 'result_folder': results,
279 'drone': socket.gethostname()}
280 autotest_es.post(use_http=True, type_str='correct_results_folder_failure',
281 metadata=metadata)
282 raise
Dan Shi3f1b8a52015-04-21 11:11:06 -0700283
284
Dan Shia06f3e22015-09-03 16:15:15 -0700285def _start_servod(machine):
286 """Try to start servod in moblab if it's not already running or running with
287 different board or port.
288
289 @param machine: Name of the dut used for test.
290 """
291 if not utils.is_moblab():
292 return
293
Dan Shi1cded882015-09-23 16:52:26 -0700294 logging.debug('Trying to start servod.')
Dan Shia06f3e22015-09-03 16:15:15 -0700295 try:
Kevin Cheng9b6930f2016-07-20 14:57:15 -0700296 afe = frontend.AFE()
Dan Shia06f3e22015-09-03 16:15:15 -0700297 board = server_utils.get_board_from_afe(machine, afe)
298 hosts = afe.get_hosts(hostname=machine)
299 servo_host = hosts[0].attributes.get('servo_host', None)
300 servo_port = hosts[0].attributes.get('servo_port', 9999)
301 if not servo_host in ['localhost', '127.0.0.1']:
Dan Shi1cded882015-09-23 16:52:26 -0700302 logging.warn('Starting servod is aborted. The dut\'s servo_host '
303 'attribute is not set to localhost.')
Dan Shia06f3e22015-09-03 16:15:15 -0700304 return
305 except (urllib2.HTTPError, urllib2.URLError):
306 # Ignore error if RPC failed to get board
307 logging.error('Failed to get board name from AFE. Start servod is '
308 'aborted')
309 return
310
311 try:
312 pid = utils.run('pgrep servod').stdout
313 cmd_line = utils.run('ps -fp %s' % pid).stdout
314 if ('--board %s' % board in cmd_line and
315 '--port %s' % servo_port in cmd_line):
316 logging.debug('Servod is already running with given board and port.'
317 ' There is no need to restart servod.')
318 return
319 logging.debug('Servod is running with different board or port. '
320 'Stopping existing servod.')
321 utils.run('sudo stop servod')
322 except error.CmdError:
323 # servod is not running.
324 pass
325
326 try:
327 utils.run(START_SERVOD_CMD % (board, servo_port))
328 logging.debug('Servod is started')
329 except error.CmdError as e:
330 logging.error('Servod failed to be started, error: %s', e)
331
332
Dan Shic68fefb2015-04-07 10:10:52 -0700333def run_autoserv(pid_file_manager, results, parser, ssp_url, use_ssp):
Dan Shicf4d2032015-03-12 15:04:21 -0700334 """Run server job with given options.
335
336 @param pid_file_manager: PidFileManager used to monitor the autoserv process
337 @param results: Folder to store results.
338 @param parser: Parser for the command line arguments.
339 @param ssp_url: Url to server-side package.
Dan Shic68fefb2015-04-07 10:10:52 -0700340 @param use_ssp: Set to True to run with server-side packaging.
Dan Shicf4d2032015-03-12 15:04:21 -0700341 """
Dan Shiec1d47d2015-02-13 11:38:13 -0800342 if parser.options.warn_no_ssp:
Dan Shic68fefb2015-04-07 10:10:52 -0700343 # Post a warning in the log.
Dan Shiec1d47d2015-02-13 11:38:13 -0800344 logging.warn('Autoserv is required to run with server-side packaging. '
345 'However, no drone is found to support server-side '
346 'packaging. The test will be executed in a drone without '
347 'server-side packaging supported.')
348
jadmanski0afbb632008-06-06 21:10:57 +0000349 # send stdin to /dev/null
350 dev_null = os.open(os.devnull, os.O_RDONLY)
351 os.dup2(dev_null, sys.stdin.fileno())
352 os.close(dev_null)
mblighdbf37612007-11-24 19:38:11 +0000353
Dan Shie8aeb662016-06-30 11:22:03 -0700354 # Create separate process group if the process is not a process group
355 # leader. This allows autoserv process to keep running after the caller
356 # process (drone manager call) exits.
357 if os.getpid() != os.getpgid(0):
358 os.setsid()
mbligh1d42d4e2007-11-05 22:42:00 +0000359
Dan Shicf4d2032015-03-12 15:04:21 -0700360 # Container name is predefined so the container can be destroyed in
361 # handle_sigterm.
362 job_or_task_id = job_directories.get_job_id_or_task_id(
363 parser.options.results)
364 container_name = (lxc.TEST_CONTAINER_NAME_FMT %
Dan Shid68d51c2015-04-21 17:00:42 -0700365 (job_or_task_id, time.time(), os.getpid()))
Dan Shiafa63872016-02-23 15:32:31 -0800366 job_folder = job_directories.get_job_folder_name(parser.options.results)
Dan Shicf4d2032015-03-12 15:04:21 -0700367
jadmanski0afbb632008-06-06 21:10:57 +0000368 # Implement SIGTERM handler
mblighc2299562009-07-02 19:00:36 +0000369 def handle_sigterm(signum, frame):
Simran Basi9d9b7292013-10-16 16:44:07 -0700370 logging.debug('Received SIGTERM')
mblighff7d61f2008-12-22 14:53:35 +0000371 if pid_file_manager:
372 pid_file_manager.close_file(1, signal.SIGTERM)
Simran Basi49e21e62013-10-17 12:40:33 -0700373 logging.debug('Finished writing to pid_file. Killing process.')
Dan Shi3f1b8a52015-04-21 11:11:06 -0700374
375 # Update results folder's file permission. This needs to be done ASAP
376 # before the parsing process tries to access the log.
377 if use_ssp and results:
378 correct_results_folder_permission(results)
379
Simran Basid6b83772014-01-06 16:31:30 -0800380 # TODO (sbasi) - remove the time.sleep when crbug.com/302815 is solved.
381 # This sleep allows the pending output to be logged before the kill
382 # signal is sent.
383 time.sleep(.1)
Dan Shic68fefb2015-04-07 10:10:52 -0700384 if use_ssp:
Dan Shicf4d2032015-03-12 15:04:21 -0700385 logging.debug('Destroy container %s before aborting the autoserv '
386 'process.', container_name)
Dan Shi3f1b8a52015-04-21 11:11:06 -0700387 metadata = {'drone': socket.gethostname(),
388 'job_id': job_or_task_id,
389 'container_name': container_name,
390 'action': 'abort',
391 'success': True}
Dan Shicf4d2032015-03-12 15:04:21 -0700392 try:
393 bucket = lxc.ContainerBucket()
394 container = bucket.get(container_name)
395 if container:
396 container.destroy()
397 else:
Dan Shi3f1b8a52015-04-21 11:11:06 -0700398 metadata['success'] = False
399 metadata['error'] = 'container not found'
Dan Shicf4d2032015-03-12 15:04:21 -0700400 logging.debug('Container %s is not found.', container_name)
401 except:
Dan Shi3f1b8a52015-04-21 11:11:06 -0700402 metadata['success'] = False
403 metadata['error'] = 'Exception: %s' % sys.exc_info()
Dan Shicf4d2032015-03-12 15:04:21 -0700404 # Handle any exception so the autoserv process can be aborted.
405 logging.error('Failed to destroy container %s. Error: %s',
406 container_name, sys.exc_info())
Dan Shi3f1b8a52015-04-21 11:11:06 -0700407 autotest_es.post(use_http=True,
408 type_str=lxc.CONTAINER_RUN_TEST_METADB_TYPE,
409 metadata=metadata)
Dan Shie4a4f9f2015-07-20 09:00:25 -0700410 # Try to correct the result file permission again after the
411 # container is destroyed, as the container might have created some
412 # new files in the result folder.
413 if results:
414 correct_results_folder_permission(results)
Dan Shicf4d2032015-03-12 15:04:21 -0700415
jadmanski0afbb632008-06-06 21:10:57 +0000416 os.killpg(os.getpgrp(), signal.SIGKILL)
mblighfaf0cd42007-11-19 16:00:24 +0000417
jadmanski0afbb632008-06-06 21:10:57 +0000418 # Set signal handler
mblighc2299562009-07-02 19:00:36 +0000419 signal.signal(signal.SIGTERM, handle_sigterm)
mbligha46678d2008-05-01 20:00:01 +0000420
Simran Basid6b83772014-01-06 16:31:30 -0800421 # faulthandler is only needed to debug in the Lab and is not avaliable to
422 # be imported in the chroot as part of VMTest, so Try-Except it.
423 try:
424 import faulthandler
425 faulthandler.register(signal.SIGTERM, all_threads=True, chain=True)
426 logging.debug('faulthandler registered on SIGTERM.')
427 except ImportError:
Christopher Grant4beca022015-06-16 15:14:47 -0400428 sys.exc_clear()
Simran Basid6b83772014-01-06 16:31:30 -0800429
David Rochberg8a60d1e2011-02-01 14:22:07 -0500430 # Ignore SIGTTOU's generated by output from forked children.
431 signal.signal(signal.SIGTTOU, signal.SIG_IGN)
432
Alex Millerf1af17e2013-01-09 22:50:32 -0800433 # If we received a SIGALARM, let's be loud about it.
434 signal.signal(signal.SIGALRM, log_alarm)
435
mbligha5f5e542009-12-30 16:57:49 +0000436 # Server side tests that call shell scripts often depend on $USER being set
437 # but depending on how you launch your autotest scheduler it may not be set.
438 os.environ['USER'] = getpass.getuser()
439
mblighb2bea302008-07-24 20:25:57 +0000440 label = parser.options.label
mbligh374f3412009-05-13 21:29:45 +0000441 group_name = parser.options.group_name
mblighb2bea302008-07-24 20:25:57 +0000442 user = parser.options.user
443 client = parser.options.client
444 server = parser.options.server
jadmanski0afbb632008-06-06 21:10:57 +0000445 install_before = parser.options.install_before
mblighb2bea302008-07-24 20:25:57 +0000446 install_after = parser.options.install_after
447 verify = parser.options.verify
448 repair = parser.options.repair
showard45ae8192008-11-05 19:32:53 +0000449 cleanup = parser.options.cleanup
Alex Millercb79ba72013-05-29 14:43:00 -0700450 provision = parser.options.provision
Dan Shi07e09af2013-04-12 09:31:29 -0700451 reset = parser.options.reset
Alex Miller667b5f22014-02-28 15:33:39 -0800452 job_labels = parser.options.job_labels
mblighb2bea302008-07-24 20:25:57 +0000453 no_tee = parser.options.no_tee
jadmanski0afbb632008-06-06 21:10:57 +0000454 parse_job = parser.options.parse_job
mblighe7d9c602009-07-02 19:02:33 +0000455 execution_tag = parser.options.execution_tag
456 if not execution_tag:
457 execution_tag = parse_job
jadmanski0afbb632008-06-06 21:10:57 +0000458 ssh_user = parser.options.ssh_user
459 ssh_port = parser.options.ssh_port
460 ssh_pass = parser.options.ssh_pass
jadmanskidef0c3c2009-03-25 20:07:10 +0000461 collect_crashinfo = parser.options.collect_crashinfo
mblighe0cbc912010-03-11 18:03:07 +0000462 control_filename = parser.options.control_filename
Scott Zawalski91493c82013-01-25 16:15:20 -0500463 test_retry = parser.options.test_retry
beepscb6f1e22013-06-28 19:14:10 -0700464 verify_job_repo_url = parser.options.verify_job_repo_url
Christopher Wileyf594c5e2013-07-03 18:25:30 -0700465 skip_crash_collection = parser.options.skip_crash_collection
Aviv Keshet18ee3142013-08-12 15:01:51 -0700466 ssh_verbosity = int(parser.options.ssh_verbosity)
Fang Deng6cc20de2013-09-06 15:47:32 -0700467 ssh_options = parser.options.ssh_options
Dan Shib669cbd2013-09-13 11:17:17 -0700468 no_use_packaging = parser.options.no_use_packaging
Simran Basi1bf60eb2015-12-01 16:39:29 -0800469 host_attributes = parser.options.host_attributes
470 in_lab = bool(parser.options.lab)
mbligha46678d2008-05-01 20:00:01 +0000471
mblighb2bea302008-07-24 20:25:57 +0000472 # can't be both a client and a server side test
473 if client and server:
Eric Li861b2d52011-02-04 14:50:35 -0800474 parser.parser.error("Can not specify a test as both server and client!")
mblighb2bea302008-07-24 20:25:57 +0000475
Alex Millercb79ba72013-05-29 14:43:00 -0700476 if provision and client:
477 parser.parser.error("Cannot specify provisioning and client!")
478
479 is_special_task = (verify or repair or cleanup or collect_crashinfo or
Dan Shi07e09af2013-04-12 09:31:29 -0700480 provision or reset)
Alex Millercb79ba72013-05-29 14:43:00 -0700481 if len(parser.args) < 1 and not is_special_task:
Eric Li861b2d52011-02-04 14:50:35 -0800482 parser.parser.error("Missing argument: control file")
mbligha46678d2008-05-01 20:00:01 +0000483
Aviv Keshet18ee3142013-08-12 15:01:51 -0700484 if ssh_verbosity > 0:
485 # ssh_verbosity is an integer between 0 and 3, inclusive
486 ssh_verbosity_flag = '-' + 'v' * ssh_verbosity
Fang Dengd1c2b732013-08-20 12:59:46 -0700487 else:
488 ssh_verbosity_flag = ''
Aviv Keshet18ee3142013-08-12 15:01:51 -0700489
showard45ae8192008-11-05 19:32:53 +0000490 # We have a control file unless it's just a verify/repair/cleanup job
jadmanski0afbb632008-06-06 21:10:57 +0000491 if len(parser.args) > 0:
492 control = parser.args[0]
493 else:
494 control = None
mbligha46678d2008-05-01 20:00:01 +0000495
Dan Shicf4d2032015-03-12 15:04:21 -0700496 machines = _get_machines(parser)
mbligh374f3412009-05-13 21:29:45 +0000497 if group_name and len(machines) < 2:
Dan Shicf4d2032015-03-12 15:04:21 -0700498 parser.parser.error('-G %r may only be supplied with more than one '
499 'machine.' % group_name)
mbligh374f3412009-05-13 21:29:45 +0000500
Christopher Wiley8a91f232013-07-09 11:02:27 -0700501 kwargs = {'group_name': group_name, 'tag': execution_tag,
Dan Shicf4d2032015-03-12 15:04:21 -0700502 'disable_sysinfo': parser.options.disable_sysinfo}
Dan Shi70647ca2015-07-16 22:52:35 -0700503 if parser.options.parent_job_id:
504 kwargs['parent_job_id'] = int(parser.options.parent_job_id)
mblighe0cbc912010-03-11 18:03:07 +0000505 if control_filename:
506 kwargs['control_filename'] = control_filename
Simran Basi1bf60eb2015-12-01 16:39:29 -0800507 if host_attributes:
508 kwargs['host_attributes'] = host_attributes
509 kwargs['in_lab'] = in_lab
jadmanski0afbb632008-06-06 21:10:57 +0000510 job = server_job.server_job(control, parser.args[1:], results, label,
511 user, machines, client, parse_job,
Fang Dengd1c2b732013-08-20 12:59:46 -0700512 ssh_user, ssh_port, ssh_pass,
Aviv Keshetc5947fa2013-09-04 14:06:29 -0700513 ssh_verbosity_flag, ssh_options,
514 test_retry, **kwargs)
Dan Shicf4d2032015-03-12 15:04:21 -0700515
showard75cdfee2009-06-10 17:40:41 +0000516 job.logging.start_logging()
mbligh4608b002010-01-05 18:22:35 +0000517 job.init_parser()
mbligha46678d2008-05-01 20:00:01 +0000518
mbligh161fe6f2008-06-19 16:26:04 +0000519 # perform checks
520 job.precheck()
521
jadmanski0afbb632008-06-06 21:10:57 +0000522 # run the job
523 exit_code = 0
Dan Shic1b8bdd2015-09-14 23:11:24 -0700524 auto_start_servod = _CONFIG.get_config_value(
525 'AUTOSERV', 'auto_start_servod', type=bool, default=False)
jadmanski0afbb632008-06-06 21:10:57 +0000526 try:
mbligh332000a2009-06-08 16:47:28 +0000527 try:
528 if repair:
Dan Shic1b8bdd2015-09-14 23:11:24 -0700529 if auto_start_servod and len(machines) == 1:
530 _start_servod(machines[0])
J. Richard Barnettec2d99cf2015-11-18 12:46:15 -0800531 job.repair(job_labels)
mbligh332000a2009-06-08 16:47:28 +0000532 elif verify:
Alex Miller667b5f22014-02-28 15:33:39 -0800533 job.verify(job_labels)
Alex Millercb79ba72013-05-29 14:43:00 -0700534 elif provision:
Alex Miller667b5f22014-02-28 15:33:39 -0800535 job.provision(job_labels)
Dan Shi07e09af2013-04-12 09:31:29 -0700536 elif reset:
Alex Miller667b5f22014-02-28 15:33:39 -0800537 job.reset(job_labels)
Fang Dengad78aca2014-10-02 18:15:46 -0700538 elif cleanup:
539 job.cleanup(job_labels)
mbligh332000a2009-06-08 16:47:28 +0000540 else:
Dan Shia06f3e22015-09-03 16:15:15 -0700541 if auto_start_servod and len(machines) == 1:
542 _start_servod(machines[0])
Dan Shic68fefb2015-04-07 10:10:52 -0700543 if use_ssp:
Dan Shicf4d2032015-03-12 15:04:21 -0700544 try:
Dan Shi37befda2015-12-07 13:16:56 -0800545 _run_with_ssp(job, container_name, job_or_task_id,
Dan Shi3be35af2016-08-25 23:22:40 -0700546 results, parser, ssp_url, job_folder,
547 machines)
Dan Shicf4d2032015-03-12 15:04:21 -0700548 finally:
549 # Update the ownership of files in result folder.
Dan Shi3f1b8a52015-04-21 11:11:06 -0700550 correct_results_folder_permission(results)
Dan Shicf4d2032015-03-12 15:04:21 -0700551 else:
Dan Shiafa63872016-02-23 15:32:31 -0800552 if collect_crashinfo:
553 # Update the ownership of files in result folder. If the
554 # job to collect crashinfo was running inside container
555 # (SSP) and crashed before correcting folder permission,
556 # the result folder might have wrong permission setting.
557 try:
558 correct_results_folder_permission(results)
559 except:
560 # Ignore any error as the user may not have root
561 # permission to run sudo command.
562 pass
Dan Shicf4d2032015-03-12 15:04:21 -0700563 job.run(install_before, install_after,
564 verify_job_repo_url=verify_job_repo_url,
565 only_collect_crashinfo=collect_crashinfo,
566 skip_crash_collection=skip_crash_collection,
Dan Shib669cbd2013-09-13 11:17:17 -0700567 job_labels=job_labels,
568 use_packaging=(not no_use_packaging))
mbligh332000a2009-06-08 16:47:28 +0000569 finally:
570 while job.hosts:
571 host = job.hosts.pop()
572 host.close()
jadmanski0afbb632008-06-06 21:10:57 +0000573 except:
jadmanski27b37ea2008-10-29 23:54:31 +0000574 exit_code = 1
jadmanski0afbb632008-06-06 21:10:57 +0000575 traceback.print_exc()
mbligha46678d2008-05-01 20:00:01 +0000576
mblighff7d61f2008-12-22 14:53:35 +0000577 if pid_file_manager:
578 pid_file_manager.num_tests_failed = job.num_tests_failed
579 pid_file_manager.close_file(exit_code)
jadmanskie0dffc32008-12-15 17:30:30 +0000580 job.cleanup_parser()
showard21baa452008-10-21 00:08:39 +0000581
jadmanski27b37ea2008-10-29 23:54:31 +0000582 sys.exit(exit_code)
mbligha46678d2008-05-01 20:00:01 +0000583
584
Fang Deng042c1472014-10-23 13:56:41 -0700585def record_autoserv(options, duration_secs):
586 """Record autoserv end-to-end time in metadata db.
587
588 @param options: parser options.
589 @param duration_secs: How long autoserv has taken, in secs.
590 """
591 # Get machine hostname
592 machines = options.machines.replace(
593 ',', ' ').strip().split() if options.machines else []
594 num_machines = len(machines)
595 if num_machines > 1:
596 # Skip the case where atomic group is used.
597 return
598 elif num_machines == 0:
599 machines.append('hostless')
600
601 # Determine the status that will be reported.
602 s = job_overhead.STATUS
603 task_mapping = {
604 'reset': s.RESETTING, 'verify': s.VERIFYING,
605 'provision': s.PROVISIONING, 'repair': s.REPAIRING,
606 'cleanup': s.CLEANING, 'collect_crashinfo': s.GATHERING}
Dan Shi888cfca2015-07-31 15:49:00 -0700607 match = filter(lambda task: getattr(options, task, False) == True,
608 task_mapping)
Fang Deng042c1472014-10-23 13:56:41 -0700609 status = task_mapping[match[0]] if match else s.RUNNING
610 is_special_task = status not in [s.RUNNING, s.GATHERING]
Dan Shicf4d2032015-03-12 15:04:21 -0700611 job_or_task_id = job_directories.get_job_id_or_task_id(options.results)
Fang Deng042c1472014-10-23 13:56:41 -0700612 job_overhead.record_state_duration(
613 job_or_task_id, machines[0], status, duration_secs,
614 is_special_task=is_special_task)
615
616
mbligha46678d2008-05-01 20:00:01 +0000617def main():
Fang Deng042c1472014-10-23 13:56:41 -0700618 start_time = datetime.datetime.now()
Dan Shia1ecd5c2013-06-06 11:21:31 -0700619 # White list of tests with run time measurement enabled.
Dan Shia06f3e22015-09-03 16:15:15 -0700620 measure_run_time_tests_names = _CONFIG.get_config_value(
621 'AUTOSERV', 'measure_run_time_tests', type=str)
Dan Shia1ecd5c2013-06-06 11:21:31 -0700622 if measure_run_time_tests_names:
623 measure_run_time_tests = [t.strip() for t in
624 measure_run_time_tests_names.split(',')]
625 else:
626 measure_run_time_tests = []
jadmanski0afbb632008-06-06 21:10:57 +0000627 # grab the parser
628 parser = autoserv_parser.autoserv_parser
mbligha5cb4062009-02-17 15:53:39 +0000629 parser.parse_args()
mbligha46678d2008-05-01 20:00:01 +0000630
jadmanski0afbb632008-06-06 21:10:57 +0000631 if len(sys.argv) == 1:
632 parser.parser.print_help()
633 sys.exit(1)
mbligha6f13082008-06-05 23:53:46 +0000634
Dan Shicf4d2032015-03-12 15:04:21 -0700635 # If the job requires to run with server-side package, try to stage server-
636 # side package first. If that fails with error that autotest server package
Dan Shic68fefb2015-04-07 10:10:52 -0700637 # does not exist, fall back to run the job without using server-side
638 # packaging. If option warn_no_ssp is specified, that means autoserv is
639 # running in a drone does not support SSP, thus no need to stage server-side
640 # package.
Dan Shicf4d2032015-03-12 15:04:21 -0700641 ssp_url = None
Dan Shi0b754c52015-04-20 14:20:38 -0700642 ssp_url_warning = False
Dan Shic68fefb2015-04-07 10:10:52 -0700643 if (not parser.options.warn_no_ssp and parser.options.require_ssp):
Dan Shi14de7622016-08-22 11:09:06 -0700644 ssp_url, ssp_error_msg = _stage_ssp(parser)
Dan Shi0b754c52015-04-20 14:20:38 -0700645 # The build does not have autotest server package. Fall back to not
646 # to use server-side package. Logging is postponed until logging being
647 # set up.
648 ssp_url_warning = not ssp_url
Dan Shicf4d2032015-03-12 15:04:21 -0700649
showard75cdfee2009-06-10 17:40:41 +0000650 if parser.options.no_logging:
651 results = None
652 else:
653 results = parser.options.results
mbligh80e1eba2008-11-19 00:26:18 +0000654 if not results:
655 results = 'results.' + time.strftime('%Y-%m-%d-%H.%M.%S')
Dan Shi14de7622016-08-22 11:09:06 -0700656 results = os.path.abspath(results)
showard566d3c02010-01-12 18:57:01 +0000657 resultdir_exists = False
658 for filename in ('control.srv', 'status.log', '.autoserv_execute'):
659 if os.path.exists(os.path.join(results, filename)):
660 resultdir_exists = True
mbligh4608b002010-01-05 18:22:35 +0000661 if not parser.options.use_existing_results and resultdir_exists:
mbligh80e1eba2008-11-19 00:26:18 +0000662 error = "Error: results directory already exists: %s\n" % results
663 sys.stderr.write(error)
664 sys.exit(1)
mbligha788dc42009-03-26 21:10:16 +0000665
666 # Now that we certified that there's no leftover results dir from
667 # previous jobs, lets create the result dir since the logging system
668 # needs to create the log file in there.
669 if not os.path.isdir(results):
670 os.makedirs(results)
showard75cdfee2009-06-10 17:40:41 +0000671
Dan Shic68fefb2015-04-07 10:10:52 -0700672 # Server-side packaging will only be used if it's required and the package
673 # is available. If warn_no_ssp is specified, it means that autoserv is
674 # running in a drone does not have SSP supported and a warning will be logs.
675 # Therefore, it should not run with SSP.
676 use_ssp = (not parser.options.warn_no_ssp and parser.options.require_ssp
677 and ssp_url)
678 if use_ssp:
Dan Shie28de552015-05-06 16:51:58 -0700679 log_dir = os.path.join(results, 'ssp_logs') if results else None
Dan Shicf4d2032015-03-12 15:04:21 -0700680 if log_dir and not os.path.exists(log_dir):
681 os.makedirs(log_dir)
682 else:
683 log_dir = results
Dan Shi3f1b8a52015-04-21 11:11:06 -0700684
showard75cdfee2009-06-10 17:40:41 +0000685 logging_manager.configure_logging(
Dan Shicf4d2032015-03-12 15:04:21 -0700686 server_logging_config.ServerLoggingConfig(),
687 results_dir=log_dir,
showard10d84172009-06-18 23:16:50 +0000688 use_console=not parser.options.no_tee,
689 verbose=parser.options.verbose,
690 no_console_prefix=parser.options.no_console_prefix)
Dan Shicf4d2032015-03-12 15:04:21 -0700691
Dan Shi0b754c52015-04-20 14:20:38 -0700692 if ssp_url_warning:
693 logging.warn(
694 'Autoserv is required to run with server-side packaging. '
695 'However, no server-side package can be found based on '
Dan Shi6450e142016-03-11 11:52:20 -0800696 '`--image`, host attribute job_repo_url or host OS version '
697 'label. It could be that the build to test is older than the '
698 'minimum version that supports server-side packaging. The test '
Dan Shi14de7622016-08-22 11:09:06 -0700699 'will be executed without using erver-side packaging. '
700 'Following is the detailed error:\n%s', ssp_error_msg)
Dan Shi0b754c52015-04-20 14:20:38 -0700701
showard75cdfee2009-06-10 17:40:41 +0000702 if results:
mbligha788dc42009-03-26 21:10:16 +0000703 logging.info("Results placed in %s" % results)
mbligh10717632008-11-19 00:21:57 +0000704
mbligh4608b002010-01-05 18:22:35 +0000705 # wait until now to perform this check, so it get properly logged
Dan Shicf4d2032015-03-12 15:04:21 -0700706 if (parser.options.use_existing_results and not resultdir_exists and
Dan Shiff78f112015-06-12 13:34:02 -0700707 not utils.is_in_container()):
mbligh4608b002010-01-05 18:22:35 +0000708 logging.error("No existing results directory found: %s", results)
709 sys.exit(1)
710
Dan Shicf4d2032015-03-12 15:04:21 -0700711 logging.debug('autoserv is running in drone %s.', socket.gethostname())
Aviv Keshet5c40ec62013-08-20 12:11:12 -0700712 logging.debug('autoserv command was: %s', ' '.join(sys.argv))
mbligh4608b002010-01-05 18:22:35 +0000713
Dan Shicf4d2032015-03-12 15:04:21 -0700714 if parser.options.write_pidfile and results:
mbligh4608b002010-01-05 18:22:35 +0000715 pid_file_manager = pidfile.PidFileManager(parser.options.pidfile_label,
716 results)
jadmanskid5ab8c52008-12-03 16:27:07 +0000717 pid_file_manager.open_file()
mblighff7d61f2008-12-22 14:53:35 +0000718 else:
719 pid_file_manager = None
mbligha46678d2008-05-01 20:00:01 +0000720
jadmanskif22fea82008-11-26 20:57:07 +0000721 autotest.BaseAutotest.set_install_in_tmpdir(
722 parser.options.install_in_tmpdir)
723
Dan Shia1ecd5c2013-06-06 11:21:31 -0700724 timer = None
725 try:
726 # Take the first argument as control file name, get the test name from
727 # the control file. If the test name exists in the list of tests with
728 # run time measurement enabled, start a timer to begin measurement.
729 if (len(parser.args) > 0 and parser.args[0] != '' and
730 parser.options.machines):
Dan Shibbc16132013-07-09 16:23:59 -0700731 try:
732 test_name = control_data.parse_control(parser.args[0],
733 raise_warnings=True).name
734 except control_data.ControlVariableException:
735 logging.debug('Failed to retrieve test name from control file.')
736 test_name = None
Dan Shia1ecd5c2013-06-06 11:21:31 -0700737 if test_name in measure_run_time_tests:
738 machines = parser.options.machines.replace(',', ' '
739 ).strip().split()
Dan Shi8eac5af2014-09-17 00:15:15 -0700740 try:
Kevin Cheng9b6930f2016-07-20 14:57:15 -0700741 afe = frontend.AFE()
Dan Shi8eac5af2014-09-17 00:15:15 -0700742 board = server_utils.get_board_from_afe(machines[0], afe)
Gabe Black1e1c41b2015-02-04 23:55:15 -0800743 timer = autotest_stats.Timer('autoserv_run_time.%s.%s' %
744 (board, test_name))
Dan Shi8eac5af2014-09-17 00:15:15 -0700745 timer.start()
746 except (urllib2.HTTPError, urllib2.URLError):
747 # Ignore error if RPC failed to get board
748 pass
Dan Shia1ecd5c2013-06-06 11:21:31 -0700749 except control_data.ControlVariableException as e:
750 logging.error(str(e))
jadmanski0afbb632008-06-06 21:10:57 +0000751 exit_code = 0
Prashanth B6285f6a2014-05-08 18:01:27 -0700752 # TODO(beeps): Extend this to cover different failure modes.
753 # Testing exceptions are matched against labels sent to autoserv. Eg,
754 # to allow only the hostless job to run, specify
755 # testing_exceptions: test_suite in the shadow_config. To allow both
756 # the hostless job and dummy_Pass to run, specify
757 # testing_exceptions: test_suite,dummy_Pass. You can figure out
758 # what label autoserv is invoked with by looking through the logs of a test
759 # for the autoserv command's -l option.
Dan Shia06f3e22015-09-03 16:15:15 -0700760 testing_exceptions = _CONFIG.get_config_value(
Prashanth B6285f6a2014-05-08 18:01:27 -0700761 'AUTOSERV', 'testing_exceptions', type=list, default=[])
Dan Shia06f3e22015-09-03 16:15:15 -0700762 test_mode = _CONFIG.get_config_value(
Prashanth B6285f6a2014-05-08 18:01:27 -0700763 'AUTOSERV', 'testing_mode', type=bool, default=False)
Prashanth Balasubramanianf8b83712014-11-06 15:58:21 -0800764 test_mode = (results_mocker and test_mode and not
765 any([ex in parser.options.label
766 for ex in testing_exceptions]))
767 is_task = (parser.options.verify or parser.options.repair or
768 parser.options.provision or parser.options.reset or
769 parser.options.cleanup or parser.options.collect_crashinfo)
jadmanski0afbb632008-06-06 21:10:57 +0000770 try:
771 try:
Prashanth B6285f6a2014-05-08 18:01:27 -0700772 if test_mode:
Prashanth Balasubramanianf8b83712014-11-06 15:58:21 -0800773 # The parser doesn't run on tasks anyway, so we can just return
774 # happy signals without faking results.
775 if not is_task:
776 machine = parser.options.results.split('/')[-1]
777
778 # TODO(beeps): The proper way to do this would be to
779 # refactor job creation so we can invoke job.record
780 # directly. To do that one needs to pipe the test_name
781 # through run_autoserv and bail just before invoking
782 # the server job. See the comment in
783 # puppylab/results_mocker for more context.
784 results_mocker.ResultsMocker(
Prashanth Balasubramanian22dd2262014-11-28 18:19:18 -0800785 test_name if test_name else 'unknown-test',
786 parser.options.results, machine
Prashanth Balasubramanianf8b83712014-11-06 15:58:21 -0800787 ).mock_results()
788 return
Prashanth B6285f6a2014-05-08 18:01:27 -0700789 else:
Dan Shic68fefb2015-04-07 10:10:52 -0700790 run_autoserv(pid_file_manager, results, parser, ssp_url,
791 use_ssp)
Aviv Keshet5c40ec62013-08-20 12:11:12 -0700792 except SystemExit as e:
jadmanski0afbb632008-06-06 21:10:57 +0000793 exit_code = e.code
Aviv Keshet5c40ec62013-08-20 12:11:12 -0700794 if exit_code:
795 logging.exception(e)
796 except Exception as e:
jadmanski0afbb632008-06-06 21:10:57 +0000797 # If we don't know what happened, we'll classify it as
798 # an 'abort' and return 1.
Aviv Keshet5c40ec62013-08-20 12:11:12 -0700799 logging.exception(e)
jadmanski0afbb632008-06-06 21:10:57 +0000800 exit_code = 1
801 finally:
mblighff7d61f2008-12-22 14:53:35 +0000802 if pid_file_manager:
803 pid_file_manager.close_file(exit_code)
Dan Shia1ecd5c2013-06-06 11:21:31 -0700804 if timer:
805 timer.stop()
Fang Deng042c1472014-10-23 13:56:41 -0700806 # Record the autoserv duration time. Must be called
807 # just before the system exits to ensure accuracy.
808 duration_secs = (datetime.datetime.now() - start_time).total_seconds()
809 record_autoserv(parser.options, duration_secs)
jadmanski0afbb632008-06-06 21:10:57 +0000810 sys.exit(exit_code)
mblighfaf0cd42007-11-19 16:00:24 +0000811
mblighbb421852008-03-11 22:36:16 +0000812
mbligha46678d2008-05-01 20:00:01 +0000813if __name__ == '__main__':
jadmanski0afbb632008-06-06 21:10:57 +0000814 main()