blob: e3483adcc19f756fee4defaf438c3aa39116bc52 [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.
Dan Shi65374e22016-09-15 16:14:05 -0700209 container_result_dir = os.path.join(lxc.RESULT_DIR_FMT % job_folder)
Dan Shicf4d2032015-03-12 15:04:21 -0700210 if parser.options.results:
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 Shicf4d2032015-03-12 15:04:21 -0700215 paths_to_replace[parser.options.parse_job] = container_result_dir
216
217 args = [paths_to_replace.get(arg, arg) for arg in args]
218
219 # Apply --use-existing-results, results directory is aready created and
220 # mounted in container. Apply this arg to avoid exception being raised.
221 if not '--use-existing-results' in args:
222 args.append('--use-existing-results')
223
224 # Make sure autoserv running in container using a different pid file.
225 if not '--pidfile-label' in args:
226 args.extend(['--pidfile-label', 'container_autoserv'])
227
Dan Shid1f51232015-04-18 00:29:14 -0700228 cmd_line = ' '.join(["'%s'" % arg if ' ' in arg else arg for arg in args])
Dan Shicf4d2032015-03-12 15:04:21 -0700229 logging.info('Run command in container: %s', cmd_line)
Dan Shi37bee222015-04-13 15:46:47 -0700230 success = False
Dan Shicf4d2032015-03-12 15:04:21 -0700231 try:
232 test_container.attach_run(cmd_line)
Dan Shi37bee222015-04-13 15:46:47 -0700233 success = True
Dan Shi9d3454e2015-12-08 09:16:08 -0800234 except Exception as e:
235 # If the test run inside container fails without generating any log,
236 # write a message to status.log to help troubleshooting.
237 debug_files = os.listdir(os.path.join(results, 'debug'))
238 if not debug_files:
239 job.record('FAIL', None, None,
240 'Failed to run test inside the container: %s. Check '
241 'logs in ssp_logs folder for more details.' % e)
242 raise
Dan Shicf4d2032015-03-12 15:04:21 -0700243 finally:
Dan Shi37bee222015-04-13 15:46:47 -0700244 counter_key = '%s.%s' % (lxc.STATS_KEY,
245 'success' if success else 'fail')
246 autotest_stats.Counter(counter_key).increment()
247 # metadata is uploaded separately so it can use http to upload.
248 metadata = {'drone': socket.gethostname(),
249 'job_id': job_id,
250 'success': success}
251 autotest_es.post(use_http=True,
252 type_str=lxc.CONTAINER_RUN_TEST_METADB_TYPE,
253 metadata=metadata)
Dan Shicf4d2032015-03-12 15:04:21 -0700254 test_container.destroy()
255
256
Dan Shi3f1b8a52015-04-21 11:11:06 -0700257def correct_results_folder_permission(results):
258 """Make sure the results folder has the right permission settings.
259
260 For tests running with server-side packaging, the results folder has the
261 owner of root. This must be changed to the user running the autoserv
262 process, so parsing job can access the results folder.
263 TODO(dshi): crbug.com/459344 Remove this function when test container can be
264 unprivileged container.
265
266 @param results: Path to the results folder.
267
268 """
269 if not results:
270 return
271
Dan Shi32649b82015-08-29 20:53:36 -0700272 try:
273 utils.run('sudo -n chown -R %s "%s"' % (os.getuid(), results))
274 utils.run('sudo -n chgrp -R %s "%s"' % (os.getgid(), results))
275 except error.CmdError as e:
276 metadata = {'error': str(e),
277 'result_folder': results,
278 'drone': socket.gethostname()}
279 autotest_es.post(use_http=True, type_str='correct_results_folder_failure',
280 metadata=metadata)
281 raise
Dan Shi3f1b8a52015-04-21 11:11:06 -0700282
283
Dan Shia06f3e22015-09-03 16:15:15 -0700284def _start_servod(machine):
285 """Try to start servod in moblab if it's not already running or running with
286 different board or port.
287
288 @param machine: Name of the dut used for test.
289 """
290 if not utils.is_moblab():
291 return
292
Dan Shi1cded882015-09-23 16:52:26 -0700293 logging.debug('Trying to start servod.')
Dan Shia06f3e22015-09-03 16:15:15 -0700294 try:
Kevin Cheng9b6930f2016-07-20 14:57:15 -0700295 afe = frontend.AFE()
Dan Shia06f3e22015-09-03 16:15:15 -0700296 board = server_utils.get_board_from_afe(machine, afe)
297 hosts = afe.get_hosts(hostname=machine)
298 servo_host = hosts[0].attributes.get('servo_host', None)
299 servo_port = hosts[0].attributes.get('servo_port', 9999)
300 if not servo_host in ['localhost', '127.0.0.1']:
Dan Shi1cded882015-09-23 16:52:26 -0700301 logging.warn('Starting servod is aborted. The dut\'s servo_host '
302 'attribute is not set to localhost.')
Dan Shia06f3e22015-09-03 16:15:15 -0700303 return
304 except (urllib2.HTTPError, urllib2.URLError):
305 # Ignore error if RPC failed to get board
306 logging.error('Failed to get board name from AFE. Start servod is '
307 'aborted')
308 return
309
310 try:
311 pid = utils.run('pgrep servod').stdout
312 cmd_line = utils.run('ps -fp %s' % pid).stdout
313 if ('--board %s' % board in cmd_line and
314 '--port %s' % servo_port in cmd_line):
315 logging.debug('Servod is already running with given board and port.'
316 ' There is no need to restart servod.')
317 return
318 logging.debug('Servod is running with different board or port. '
319 'Stopping existing servod.')
320 utils.run('sudo stop servod')
321 except error.CmdError:
322 # servod is not running.
323 pass
324
325 try:
326 utils.run(START_SERVOD_CMD % (board, servo_port))
327 logging.debug('Servod is started')
328 except error.CmdError as e:
329 logging.error('Servod failed to be started, error: %s', e)
330
331
Dan Shic68fefb2015-04-07 10:10:52 -0700332def run_autoserv(pid_file_manager, results, parser, ssp_url, use_ssp):
Dan Shicf4d2032015-03-12 15:04:21 -0700333 """Run server job with given options.
334
335 @param pid_file_manager: PidFileManager used to monitor the autoserv process
336 @param results: Folder to store results.
337 @param parser: Parser for the command line arguments.
338 @param ssp_url: Url to server-side package.
Dan Shic68fefb2015-04-07 10:10:52 -0700339 @param use_ssp: Set to True to run with server-side packaging.
Dan Shicf4d2032015-03-12 15:04:21 -0700340 """
Dan Shiec1d47d2015-02-13 11:38:13 -0800341 if parser.options.warn_no_ssp:
Dan Shic68fefb2015-04-07 10:10:52 -0700342 # Post a warning in the log.
Dan Shiec1d47d2015-02-13 11:38:13 -0800343 logging.warn('Autoserv is required to run with server-side packaging. '
344 'However, no drone is found to support server-side '
345 'packaging. The test will be executed in a drone without '
346 'server-side packaging supported.')
347
jadmanski0afbb632008-06-06 21:10:57 +0000348 # send stdin to /dev/null
349 dev_null = os.open(os.devnull, os.O_RDONLY)
350 os.dup2(dev_null, sys.stdin.fileno())
351 os.close(dev_null)
mblighdbf37612007-11-24 19:38:11 +0000352
Dan Shie8aeb662016-06-30 11:22:03 -0700353 # Create separate process group if the process is not a process group
354 # leader. This allows autoserv process to keep running after the caller
355 # process (drone manager call) exits.
356 if os.getpid() != os.getpgid(0):
357 os.setsid()
mbligh1d42d4e2007-11-05 22:42:00 +0000358
Dan Shicf4d2032015-03-12 15:04:21 -0700359 # Container name is predefined so the container can be destroyed in
360 # handle_sigterm.
361 job_or_task_id = job_directories.get_job_id_or_task_id(
362 parser.options.results)
363 container_name = (lxc.TEST_CONTAINER_NAME_FMT %
Dan Shid68d51c2015-04-21 17:00:42 -0700364 (job_or_task_id, time.time(), os.getpid()))
Dan Shiafa63872016-02-23 15:32:31 -0800365 job_folder = job_directories.get_job_folder_name(parser.options.results)
Dan Shicf4d2032015-03-12 15:04:21 -0700366
jadmanski0afbb632008-06-06 21:10:57 +0000367 # Implement SIGTERM handler
mblighc2299562009-07-02 19:00:36 +0000368 def handle_sigterm(signum, frame):
Simran Basi9d9b7292013-10-16 16:44:07 -0700369 logging.debug('Received SIGTERM')
mblighff7d61f2008-12-22 14:53:35 +0000370 if pid_file_manager:
371 pid_file_manager.close_file(1, signal.SIGTERM)
Simran Basi49e21e62013-10-17 12:40:33 -0700372 logging.debug('Finished writing to pid_file. Killing process.')
Dan Shi3f1b8a52015-04-21 11:11:06 -0700373
374 # Update results folder's file permission. This needs to be done ASAP
375 # before the parsing process tries to access the log.
376 if use_ssp and results:
377 correct_results_folder_permission(results)
378
Simran Basid6b83772014-01-06 16:31:30 -0800379 # TODO (sbasi) - remove the time.sleep when crbug.com/302815 is solved.
380 # This sleep allows the pending output to be logged before the kill
381 # signal is sent.
382 time.sleep(.1)
Dan Shic68fefb2015-04-07 10:10:52 -0700383 if use_ssp:
Dan Shicf4d2032015-03-12 15:04:21 -0700384 logging.debug('Destroy container %s before aborting the autoserv '
385 'process.', container_name)
Dan Shi3f1b8a52015-04-21 11:11:06 -0700386 metadata = {'drone': socket.gethostname(),
387 'job_id': job_or_task_id,
388 'container_name': container_name,
389 'action': 'abort',
390 'success': True}
Dan Shicf4d2032015-03-12 15:04:21 -0700391 try:
392 bucket = lxc.ContainerBucket()
393 container = bucket.get(container_name)
394 if container:
395 container.destroy()
396 else:
Dan Shi3f1b8a52015-04-21 11:11:06 -0700397 metadata['success'] = False
398 metadata['error'] = 'container not found'
Dan Shicf4d2032015-03-12 15:04:21 -0700399 logging.debug('Container %s is not found.', container_name)
400 except:
Dan Shi3f1b8a52015-04-21 11:11:06 -0700401 metadata['success'] = False
Dan Shi65374e22016-09-15 16:14:05 -0700402 metadata['error'] = 'Exception: %s' % str(sys.exc_info())
Dan Shicf4d2032015-03-12 15:04:21 -0700403 # Handle any exception so the autoserv process can be aborted.
Dan Shi65374e22016-09-15 16:14:05 -0700404 logging.exception('Failed to destroy container %s.',
405 container_name)
Dan Shi3f1b8a52015-04-21 11:11:06 -0700406 autotest_es.post(use_http=True,
407 type_str=lxc.CONTAINER_RUN_TEST_METADB_TYPE,
408 metadata=metadata)
Dan Shie4a4f9f2015-07-20 09:00:25 -0700409 # Try to correct the result file permission again after the
410 # container is destroyed, as the container might have created some
411 # new files in the result folder.
412 if results:
413 correct_results_folder_permission(results)
Dan Shicf4d2032015-03-12 15:04:21 -0700414
jadmanski0afbb632008-06-06 21:10:57 +0000415 os.killpg(os.getpgrp(), signal.SIGKILL)
mblighfaf0cd42007-11-19 16:00:24 +0000416
jadmanski0afbb632008-06-06 21:10:57 +0000417 # Set signal handler
mblighc2299562009-07-02 19:00:36 +0000418 signal.signal(signal.SIGTERM, handle_sigterm)
mbligha46678d2008-05-01 20:00:01 +0000419
Simran Basid6b83772014-01-06 16:31:30 -0800420 # faulthandler is only needed to debug in the Lab and is not avaliable to
421 # be imported in the chroot as part of VMTest, so Try-Except it.
422 try:
423 import faulthandler
424 faulthandler.register(signal.SIGTERM, all_threads=True, chain=True)
425 logging.debug('faulthandler registered on SIGTERM.')
426 except ImportError:
Christopher Grant4beca022015-06-16 15:14:47 -0400427 sys.exc_clear()
Simran Basid6b83772014-01-06 16:31:30 -0800428
David Rochberg8a60d1e2011-02-01 14:22:07 -0500429 # Ignore SIGTTOU's generated by output from forked children.
430 signal.signal(signal.SIGTTOU, signal.SIG_IGN)
431
Alex Millerf1af17e2013-01-09 22:50:32 -0800432 # If we received a SIGALARM, let's be loud about it.
433 signal.signal(signal.SIGALRM, log_alarm)
434
mbligha5f5e542009-12-30 16:57:49 +0000435 # Server side tests that call shell scripts often depend on $USER being set
436 # but depending on how you launch your autotest scheduler it may not be set.
437 os.environ['USER'] = getpass.getuser()
438
mblighb2bea302008-07-24 20:25:57 +0000439 label = parser.options.label
mbligh374f3412009-05-13 21:29:45 +0000440 group_name = parser.options.group_name
mblighb2bea302008-07-24 20:25:57 +0000441 user = parser.options.user
442 client = parser.options.client
443 server = parser.options.server
jadmanski0afbb632008-06-06 21:10:57 +0000444 install_before = parser.options.install_before
mblighb2bea302008-07-24 20:25:57 +0000445 install_after = parser.options.install_after
446 verify = parser.options.verify
447 repair = parser.options.repair
showard45ae8192008-11-05 19:32:53 +0000448 cleanup = parser.options.cleanup
Alex Millercb79ba72013-05-29 14:43:00 -0700449 provision = parser.options.provision
Dan Shi07e09af2013-04-12 09:31:29 -0700450 reset = parser.options.reset
Alex Miller667b5f22014-02-28 15:33:39 -0800451 job_labels = parser.options.job_labels
mblighb2bea302008-07-24 20:25:57 +0000452 no_tee = parser.options.no_tee
jadmanski0afbb632008-06-06 21:10:57 +0000453 parse_job = parser.options.parse_job
mblighe7d9c602009-07-02 19:02:33 +0000454 execution_tag = parser.options.execution_tag
455 if not execution_tag:
456 execution_tag = parse_job
jadmanski0afbb632008-06-06 21:10:57 +0000457 ssh_user = parser.options.ssh_user
458 ssh_port = parser.options.ssh_port
459 ssh_pass = parser.options.ssh_pass
jadmanskidef0c3c2009-03-25 20:07:10 +0000460 collect_crashinfo = parser.options.collect_crashinfo
mblighe0cbc912010-03-11 18:03:07 +0000461 control_filename = parser.options.control_filename
Scott Zawalski91493c82013-01-25 16:15:20 -0500462 test_retry = parser.options.test_retry
beepscb6f1e22013-06-28 19:14:10 -0700463 verify_job_repo_url = parser.options.verify_job_repo_url
Christopher Wileyf594c5e2013-07-03 18:25:30 -0700464 skip_crash_collection = parser.options.skip_crash_collection
Aviv Keshet18ee3142013-08-12 15:01:51 -0700465 ssh_verbosity = int(parser.options.ssh_verbosity)
Fang Deng6cc20de2013-09-06 15:47:32 -0700466 ssh_options = parser.options.ssh_options
Dan Shib669cbd2013-09-13 11:17:17 -0700467 no_use_packaging = parser.options.no_use_packaging
Simran Basi1bf60eb2015-12-01 16:39:29 -0800468 host_attributes = parser.options.host_attributes
469 in_lab = bool(parser.options.lab)
mbligha46678d2008-05-01 20:00:01 +0000470
mblighb2bea302008-07-24 20:25:57 +0000471 # can't be both a client and a server side test
472 if client and server:
Eric Li861b2d52011-02-04 14:50:35 -0800473 parser.parser.error("Can not specify a test as both server and client!")
mblighb2bea302008-07-24 20:25:57 +0000474
Alex Millercb79ba72013-05-29 14:43:00 -0700475 if provision and client:
476 parser.parser.error("Cannot specify provisioning and client!")
477
478 is_special_task = (verify or repair or cleanup or collect_crashinfo or
Dan Shi07e09af2013-04-12 09:31:29 -0700479 provision or reset)
Alex Millercb79ba72013-05-29 14:43:00 -0700480 if len(parser.args) < 1 and not is_special_task:
Eric Li861b2d52011-02-04 14:50:35 -0800481 parser.parser.error("Missing argument: control file")
mbligha46678d2008-05-01 20:00:01 +0000482
Aviv Keshet18ee3142013-08-12 15:01:51 -0700483 if ssh_verbosity > 0:
484 # ssh_verbosity is an integer between 0 and 3, inclusive
485 ssh_verbosity_flag = '-' + 'v' * ssh_verbosity
Fang Dengd1c2b732013-08-20 12:59:46 -0700486 else:
487 ssh_verbosity_flag = ''
Aviv Keshet18ee3142013-08-12 15:01:51 -0700488
showard45ae8192008-11-05 19:32:53 +0000489 # We have a control file unless it's just a verify/repair/cleanup job
jadmanski0afbb632008-06-06 21:10:57 +0000490 if len(parser.args) > 0:
491 control = parser.args[0]
492 else:
493 control = None
mbligha46678d2008-05-01 20:00:01 +0000494
Dan Shicf4d2032015-03-12 15:04:21 -0700495 machines = _get_machines(parser)
mbligh374f3412009-05-13 21:29:45 +0000496 if group_name and len(machines) < 2:
Dan Shicf4d2032015-03-12 15:04:21 -0700497 parser.parser.error('-G %r may only be supplied with more than one '
498 'machine.' % group_name)
mbligh374f3412009-05-13 21:29:45 +0000499
Christopher Wiley8a91f232013-07-09 11:02:27 -0700500 kwargs = {'group_name': group_name, 'tag': execution_tag,
Dan Shicf4d2032015-03-12 15:04:21 -0700501 'disable_sysinfo': parser.options.disable_sysinfo}
Dan Shi70647ca2015-07-16 22:52:35 -0700502 if parser.options.parent_job_id:
503 kwargs['parent_job_id'] = int(parser.options.parent_job_id)
mblighe0cbc912010-03-11 18:03:07 +0000504 if control_filename:
505 kwargs['control_filename'] = control_filename
Simran Basi1bf60eb2015-12-01 16:39:29 -0800506 if host_attributes:
507 kwargs['host_attributes'] = host_attributes
508 kwargs['in_lab'] = in_lab
jadmanski0afbb632008-06-06 21:10:57 +0000509 job = server_job.server_job(control, parser.args[1:], results, label,
510 user, machines, client, parse_job,
Fang Dengd1c2b732013-08-20 12:59:46 -0700511 ssh_user, ssh_port, ssh_pass,
Aviv Keshetc5947fa2013-09-04 14:06:29 -0700512 ssh_verbosity_flag, ssh_options,
513 test_retry, **kwargs)
Dan Shicf4d2032015-03-12 15:04:21 -0700514
showard75cdfee2009-06-10 17:40:41 +0000515 job.logging.start_logging()
mbligh4608b002010-01-05 18:22:35 +0000516 job.init_parser()
mbligha46678d2008-05-01 20:00:01 +0000517
mbligh161fe6f2008-06-19 16:26:04 +0000518 # perform checks
519 job.precheck()
520
jadmanski0afbb632008-06-06 21:10:57 +0000521 # run the job
522 exit_code = 0
Dan Shic1b8bdd2015-09-14 23:11:24 -0700523 auto_start_servod = _CONFIG.get_config_value(
524 'AUTOSERV', 'auto_start_servod', type=bool, default=False)
jadmanski0afbb632008-06-06 21:10:57 +0000525 try:
mbligh332000a2009-06-08 16:47:28 +0000526 try:
527 if repair:
Dan Shic1b8bdd2015-09-14 23:11:24 -0700528 if auto_start_servod and len(machines) == 1:
529 _start_servod(machines[0])
J. Richard Barnettec2d99cf2015-11-18 12:46:15 -0800530 job.repair(job_labels)
mbligh332000a2009-06-08 16:47:28 +0000531 elif verify:
Alex Miller667b5f22014-02-28 15:33:39 -0800532 job.verify(job_labels)
Alex Millercb79ba72013-05-29 14:43:00 -0700533 elif provision:
Alex Miller667b5f22014-02-28 15:33:39 -0800534 job.provision(job_labels)
Dan Shi07e09af2013-04-12 09:31:29 -0700535 elif reset:
Alex Miller667b5f22014-02-28 15:33:39 -0800536 job.reset(job_labels)
Fang Dengad78aca2014-10-02 18:15:46 -0700537 elif cleanup:
538 job.cleanup(job_labels)
mbligh332000a2009-06-08 16:47:28 +0000539 else:
Dan Shia06f3e22015-09-03 16:15:15 -0700540 if auto_start_servod and len(machines) == 1:
541 _start_servod(machines[0])
Dan Shic68fefb2015-04-07 10:10:52 -0700542 if use_ssp:
Dan Shicf4d2032015-03-12 15:04:21 -0700543 try:
Dan Shi37befda2015-12-07 13:16:56 -0800544 _run_with_ssp(job, container_name, job_or_task_id,
Dan Shi3be35af2016-08-25 23:22:40 -0700545 results, parser, ssp_url, job_folder,
546 machines)
Dan Shicf4d2032015-03-12 15:04:21 -0700547 finally:
548 # Update the ownership of files in result folder.
Dan Shi3f1b8a52015-04-21 11:11:06 -0700549 correct_results_folder_permission(results)
Dan Shicf4d2032015-03-12 15:04:21 -0700550 else:
Dan Shiafa63872016-02-23 15:32:31 -0800551 if collect_crashinfo:
552 # Update the ownership of files in result folder. If the
553 # job to collect crashinfo was running inside container
554 # (SSP) and crashed before correcting folder permission,
555 # the result folder might have wrong permission setting.
556 try:
557 correct_results_folder_permission(results)
558 except:
559 # Ignore any error as the user may not have root
560 # permission to run sudo command.
561 pass
Dan Shicf4d2032015-03-12 15:04:21 -0700562 job.run(install_before, install_after,
563 verify_job_repo_url=verify_job_repo_url,
564 only_collect_crashinfo=collect_crashinfo,
565 skip_crash_collection=skip_crash_collection,
Dan Shib669cbd2013-09-13 11:17:17 -0700566 job_labels=job_labels,
567 use_packaging=(not no_use_packaging))
mbligh332000a2009-06-08 16:47:28 +0000568 finally:
569 while job.hosts:
570 host = job.hosts.pop()
571 host.close()
jadmanski0afbb632008-06-06 21:10:57 +0000572 except:
jadmanski27b37ea2008-10-29 23:54:31 +0000573 exit_code = 1
jadmanski0afbb632008-06-06 21:10:57 +0000574 traceback.print_exc()
mbligha46678d2008-05-01 20:00:01 +0000575
mblighff7d61f2008-12-22 14:53:35 +0000576 if pid_file_manager:
577 pid_file_manager.num_tests_failed = job.num_tests_failed
578 pid_file_manager.close_file(exit_code)
jadmanskie0dffc32008-12-15 17:30:30 +0000579 job.cleanup_parser()
showard21baa452008-10-21 00:08:39 +0000580
jadmanski27b37ea2008-10-29 23:54:31 +0000581 sys.exit(exit_code)
mbligha46678d2008-05-01 20:00:01 +0000582
583
Fang Deng042c1472014-10-23 13:56:41 -0700584def record_autoserv(options, duration_secs):
585 """Record autoserv end-to-end time in metadata db.
586
587 @param options: parser options.
588 @param duration_secs: How long autoserv has taken, in secs.
589 """
590 # Get machine hostname
591 machines = options.machines.replace(
592 ',', ' ').strip().split() if options.machines else []
593 num_machines = len(machines)
594 if num_machines > 1:
595 # Skip the case where atomic group is used.
596 return
597 elif num_machines == 0:
598 machines.append('hostless')
599
600 # Determine the status that will be reported.
601 s = job_overhead.STATUS
602 task_mapping = {
603 'reset': s.RESETTING, 'verify': s.VERIFYING,
604 'provision': s.PROVISIONING, 'repair': s.REPAIRING,
605 'cleanup': s.CLEANING, 'collect_crashinfo': s.GATHERING}
Dan Shi888cfca2015-07-31 15:49:00 -0700606 match = filter(lambda task: getattr(options, task, False) == True,
607 task_mapping)
Fang Deng042c1472014-10-23 13:56:41 -0700608 status = task_mapping[match[0]] if match else s.RUNNING
609 is_special_task = status not in [s.RUNNING, s.GATHERING]
Dan Shicf4d2032015-03-12 15:04:21 -0700610 job_or_task_id = job_directories.get_job_id_or_task_id(options.results)
Fang Deng042c1472014-10-23 13:56:41 -0700611 job_overhead.record_state_duration(
612 job_or_task_id, machines[0], status, duration_secs,
613 is_special_task=is_special_task)
614
615
mbligha46678d2008-05-01 20:00:01 +0000616def main():
Fang Deng042c1472014-10-23 13:56:41 -0700617 start_time = datetime.datetime.now()
Dan Shia1ecd5c2013-06-06 11:21:31 -0700618 # White list of tests with run time measurement enabled.
Dan Shia06f3e22015-09-03 16:15:15 -0700619 measure_run_time_tests_names = _CONFIG.get_config_value(
620 'AUTOSERV', 'measure_run_time_tests', type=str)
Dan Shia1ecd5c2013-06-06 11:21:31 -0700621 if measure_run_time_tests_names:
622 measure_run_time_tests = [t.strip() for t in
623 measure_run_time_tests_names.split(',')]
624 else:
625 measure_run_time_tests = []
jadmanski0afbb632008-06-06 21:10:57 +0000626 # grab the parser
627 parser = autoserv_parser.autoserv_parser
mbligha5cb4062009-02-17 15:53:39 +0000628 parser.parse_args()
mbligha46678d2008-05-01 20:00:01 +0000629
jadmanski0afbb632008-06-06 21:10:57 +0000630 if len(sys.argv) == 1:
631 parser.parser.print_help()
632 sys.exit(1)
mbligha6f13082008-06-05 23:53:46 +0000633
Dan Shicf4d2032015-03-12 15:04:21 -0700634 # If the job requires to run with server-side package, try to stage server-
635 # side package first. If that fails with error that autotest server package
Dan Shic68fefb2015-04-07 10:10:52 -0700636 # does not exist, fall back to run the job without using server-side
637 # packaging. If option warn_no_ssp is specified, that means autoserv is
638 # running in a drone does not support SSP, thus no need to stage server-side
639 # package.
Dan Shicf4d2032015-03-12 15:04:21 -0700640 ssp_url = None
Dan Shi0b754c52015-04-20 14:20:38 -0700641 ssp_url_warning = False
Dan Shic68fefb2015-04-07 10:10:52 -0700642 if (not parser.options.warn_no_ssp and parser.options.require_ssp):
Dan Shi14de7622016-08-22 11:09:06 -0700643 ssp_url, ssp_error_msg = _stage_ssp(parser)
Dan Shi0b754c52015-04-20 14:20:38 -0700644 # The build does not have autotest server package. Fall back to not
645 # to use server-side package. Logging is postponed until logging being
646 # set up.
647 ssp_url_warning = not ssp_url
Dan Shicf4d2032015-03-12 15:04:21 -0700648
showard75cdfee2009-06-10 17:40:41 +0000649 if parser.options.no_logging:
650 results = None
651 else:
652 results = parser.options.results
mbligh80e1eba2008-11-19 00:26:18 +0000653 if not results:
654 results = 'results.' + time.strftime('%Y-%m-%d-%H.%M.%S')
Dan Shi14de7622016-08-22 11:09:06 -0700655 results = os.path.abspath(results)
showard566d3c02010-01-12 18:57:01 +0000656 resultdir_exists = False
657 for filename in ('control.srv', 'status.log', '.autoserv_execute'):
658 if os.path.exists(os.path.join(results, filename)):
659 resultdir_exists = True
mbligh4608b002010-01-05 18:22:35 +0000660 if not parser.options.use_existing_results and resultdir_exists:
mbligh80e1eba2008-11-19 00:26:18 +0000661 error = "Error: results directory already exists: %s\n" % results
662 sys.stderr.write(error)
663 sys.exit(1)
mbligha788dc42009-03-26 21:10:16 +0000664
665 # Now that we certified that there's no leftover results dir from
666 # previous jobs, lets create the result dir since the logging system
667 # needs to create the log file in there.
668 if not os.path.isdir(results):
669 os.makedirs(results)
showard75cdfee2009-06-10 17:40:41 +0000670
Dan Shic68fefb2015-04-07 10:10:52 -0700671 # Server-side packaging will only be used if it's required and the package
672 # is available. If warn_no_ssp is specified, it means that autoserv is
673 # running in a drone does not have SSP supported and a warning will be logs.
674 # Therefore, it should not run with SSP.
675 use_ssp = (not parser.options.warn_no_ssp and parser.options.require_ssp
676 and ssp_url)
677 if use_ssp:
Dan Shie28de552015-05-06 16:51:58 -0700678 log_dir = os.path.join(results, 'ssp_logs') if results else None
Dan Shicf4d2032015-03-12 15:04:21 -0700679 if log_dir and not os.path.exists(log_dir):
680 os.makedirs(log_dir)
681 else:
682 log_dir = results
Dan Shi3f1b8a52015-04-21 11:11:06 -0700683
showard75cdfee2009-06-10 17:40:41 +0000684 logging_manager.configure_logging(
Dan Shicf4d2032015-03-12 15:04:21 -0700685 server_logging_config.ServerLoggingConfig(),
686 results_dir=log_dir,
showard10d84172009-06-18 23:16:50 +0000687 use_console=not parser.options.no_tee,
688 verbose=parser.options.verbose,
689 no_console_prefix=parser.options.no_console_prefix)
Dan Shicf4d2032015-03-12 15:04:21 -0700690
Dan Shi0b754c52015-04-20 14:20:38 -0700691 if ssp_url_warning:
692 logging.warn(
693 'Autoserv is required to run with server-side packaging. '
694 'However, no server-side package can be found based on '
Dan Shi6450e142016-03-11 11:52:20 -0800695 '`--image`, host attribute job_repo_url or host OS version '
696 'label. It could be that the build to test is older than the '
697 'minimum version that supports server-side packaging. The test '
Dan Shi14de7622016-08-22 11:09:06 -0700698 'will be executed without using erver-side packaging. '
699 'Following is the detailed error:\n%s', ssp_error_msg)
Dan Shi0b754c52015-04-20 14:20:38 -0700700
showard75cdfee2009-06-10 17:40:41 +0000701 if results:
mbligha788dc42009-03-26 21:10:16 +0000702 logging.info("Results placed in %s" % results)
mbligh10717632008-11-19 00:21:57 +0000703
mbligh4608b002010-01-05 18:22:35 +0000704 # wait until now to perform this check, so it get properly logged
Dan Shicf4d2032015-03-12 15:04:21 -0700705 if (parser.options.use_existing_results and not resultdir_exists and
Dan Shiff78f112015-06-12 13:34:02 -0700706 not utils.is_in_container()):
mbligh4608b002010-01-05 18:22:35 +0000707 logging.error("No existing results directory found: %s", results)
708 sys.exit(1)
709
Dan Shicf4d2032015-03-12 15:04:21 -0700710 logging.debug('autoserv is running in drone %s.', socket.gethostname())
Aviv Keshet5c40ec62013-08-20 12:11:12 -0700711 logging.debug('autoserv command was: %s', ' '.join(sys.argv))
mbligh4608b002010-01-05 18:22:35 +0000712
Dan Shicf4d2032015-03-12 15:04:21 -0700713 if parser.options.write_pidfile and results:
mbligh4608b002010-01-05 18:22:35 +0000714 pid_file_manager = pidfile.PidFileManager(parser.options.pidfile_label,
715 results)
jadmanskid5ab8c52008-12-03 16:27:07 +0000716 pid_file_manager.open_file()
mblighff7d61f2008-12-22 14:53:35 +0000717 else:
718 pid_file_manager = None
mbligha46678d2008-05-01 20:00:01 +0000719
jadmanskif22fea82008-11-26 20:57:07 +0000720 autotest.BaseAutotest.set_install_in_tmpdir(
721 parser.options.install_in_tmpdir)
722
Dan Shia1ecd5c2013-06-06 11:21:31 -0700723 timer = None
724 try:
725 # Take the first argument as control file name, get the test name from
726 # the control file. If the test name exists in the list of tests with
727 # run time measurement enabled, start a timer to begin measurement.
728 if (len(parser.args) > 0 and parser.args[0] != '' and
729 parser.options.machines):
Dan Shibbc16132013-07-09 16:23:59 -0700730 try:
731 test_name = control_data.parse_control(parser.args[0],
732 raise_warnings=True).name
733 except control_data.ControlVariableException:
734 logging.debug('Failed to retrieve test name from control file.')
735 test_name = None
Dan Shia1ecd5c2013-06-06 11:21:31 -0700736 if test_name in measure_run_time_tests:
737 machines = parser.options.machines.replace(',', ' '
738 ).strip().split()
Dan Shi8eac5af2014-09-17 00:15:15 -0700739 try:
Kevin Cheng9b6930f2016-07-20 14:57:15 -0700740 afe = frontend.AFE()
Dan Shi8eac5af2014-09-17 00:15:15 -0700741 board = server_utils.get_board_from_afe(machines[0], afe)
Gabe Black1e1c41b2015-02-04 23:55:15 -0800742 timer = autotest_stats.Timer('autoserv_run_time.%s.%s' %
743 (board, test_name))
Dan Shi8eac5af2014-09-17 00:15:15 -0700744 timer.start()
745 except (urllib2.HTTPError, urllib2.URLError):
746 # Ignore error if RPC failed to get board
747 pass
Dan Shia1ecd5c2013-06-06 11:21:31 -0700748 except control_data.ControlVariableException as e:
749 logging.error(str(e))
jadmanski0afbb632008-06-06 21:10:57 +0000750 exit_code = 0
Prashanth B6285f6a2014-05-08 18:01:27 -0700751 # TODO(beeps): Extend this to cover different failure modes.
752 # Testing exceptions are matched against labels sent to autoserv. Eg,
753 # to allow only the hostless job to run, specify
754 # testing_exceptions: test_suite in the shadow_config. To allow both
755 # the hostless job and dummy_Pass to run, specify
756 # testing_exceptions: test_suite,dummy_Pass. You can figure out
757 # what label autoserv is invoked with by looking through the logs of a test
758 # for the autoserv command's -l option.
Dan Shia06f3e22015-09-03 16:15:15 -0700759 testing_exceptions = _CONFIG.get_config_value(
Prashanth B6285f6a2014-05-08 18:01:27 -0700760 'AUTOSERV', 'testing_exceptions', type=list, default=[])
Dan Shia06f3e22015-09-03 16:15:15 -0700761 test_mode = _CONFIG.get_config_value(
Prashanth B6285f6a2014-05-08 18:01:27 -0700762 'AUTOSERV', 'testing_mode', type=bool, default=False)
Prashanth Balasubramanianf8b83712014-11-06 15:58:21 -0800763 test_mode = (results_mocker and test_mode and not
764 any([ex in parser.options.label
765 for ex in testing_exceptions]))
766 is_task = (parser.options.verify or parser.options.repair or
767 parser.options.provision or parser.options.reset or
768 parser.options.cleanup or parser.options.collect_crashinfo)
jadmanski0afbb632008-06-06 21:10:57 +0000769 try:
770 try:
Prashanth B6285f6a2014-05-08 18:01:27 -0700771 if test_mode:
Prashanth Balasubramanianf8b83712014-11-06 15:58:21 -0800772 # The parser doesn't run on tasks anyway, so we can just return
773 # happy signals without faking results.
774 if not is_task:
775 machine = parser.options.results.split('/')[-1]
776
777 # TODO(beeps): The proper way to do this would be to
778 # refactor job creation so we can invoke job.record
779 # directly. To do that one needs to pipe the test_name
780 # through run_autoserv and bail just before invoking
781 # the server job. See the comment in
782 # puppylab/results_mocker for more context.
783 results_mocker.ResultsMocker(
Prashanth Balasubramanian22dd2262014-11-28 18:19:18 -0800784 test_name if test_name else 'unknown-test',
785 parser.options.results, machine
Prashanth Balasubramanianf8b83712014-11-06 15:58:21 -0800786 ).mock_results()
787 return
Prashanth B6285f6a2014-05-08 18:01:27 -0700788 else:
Dan Shic68fefb2015-04-07 10:10:52 -0700789 run_autoserv(pid_file_manager, results, parser, ssp_url,
790 use_ssp)
Aviv Keshet5c40ec62013-08-20 12:11:12 -0700791 except SystemExit as e:
jadmanski0afbb632008-06-06 21:10:57 +0000792 exit_code = e.code
Aviv Keshet5c40ec62013-08-20 12:11:12 -0700793 if exit_code:
794 logging.exception(e)
795 except Exception as e:
jadmanski0afbb632008-06-06 21:10:57 +0000796 # If we don't know what happened, we'll classify it as
797 # an 'abort' and return 1.
Aviv Keshet5c40ec62013-08-20 12:11:12 -0700798 logging.exception(e)
jadmanski0afbb632008-06-06 21:10:57 +0000799 exit_code = 1
800 finally:
mblighff7d61f2008-12-22 14:53:35 +0000801 if pid_file_manager:
802 pid_file_manager.close_file(exit_code)
Dan Shia1ecd5c2013-06-06 11:21:31 -0700803 if timer:
804 timer.stop()
Fang Deng042c1472014-10-23 13:56:41 -0700805 # Record the autoserv duration time. Must be called
806 # just before the system exits to ensure accuracy.
807 duration_secs = (datetime.datetime.now() - start_time).total_seconds()
808 record_autoserv(parser.options, duration_secs)
jadmanski0afbb632008-06-06 21:10:57 +0000809 sys.exit(exit_code)
mblighfaf0cd42007-11-19 16:00:24 +0000810
mblighbb421852008-03-11 22:36:16 +0000811
mbligha46678d2008-05-01 20:00:01 +0000812if __name__ == '__main__':
jadmanski0afbb632008-06-06 21:10:57 +0000813 main()