Home | History | Annotate | Download | only in server
      1 #!/usr/bin/python -u
      2 # Copyright 2007-2008 Martin J. Bligh <mbligh (at] google.com>, Google Inc.
      3 # Released under the GPL v2
      4 
      5 """
      6 Run a control file through the server side engine
      7 """
      8 
      9 import datetime
     10 import contextlib
     11 import getpass
     12 import logging
     13 import os
     14 import re
     15 import signal
     16 import socket
     17 import sys
     18 import traceback
     19 import time
     20 import urllib2
     21 
     22 
     23 import common
     24 from autotest_lib.client.common_lib import control_data
     25 from autotest_lib.client.common_lib import error
     26 from autotest_lib.client.common_lib import global_config
     27 from autotest_lib.client.common_lib import utils
     28 
     29 try:
     30     from chromite.lib import metrics
     31 except ImportError:
     32     metrics = utils.metrics_mock
     33 
     34 try:
     35     from autotest_lib.puppylab import results_mocker
     36 except ImportError:
     37     results_mocker = None
     38 
     39 _CONFIG = global_config.global_config
     40 
     41 
     42 # Number of seconds to wait before returning if testing mode is enabled
     43 TESTING_MODE_SLEEP_SECS = 1
     44 
     45 
     46 from autotest_lib.server import frontend
     47 from autotest_lib.server import server_logging_config
     48 from autotest_lib.server import server_job, utils, autoserv_parser, autotest
     49 from autotest_lib.server import utils as server_utils
     50 from autotest_lib.server import site_utils
     51 from autotest_lib.server.cros.dynamic_suite import frontend_wrappers
     52 from autotest_lib.site_utils import job_directories
     53 from autotest_lib.site_utils import job_overhead
     54 from autotest_lib.site_utils import lxc
     55 from autotest_lib.site_utils.lxc import utils as lxc_utils
     56 from autotest_lib.client.common_lib import pidfile, logging_manager
     57 
     58 
     59 # Control segment to stage server-side package.
     60 STAGE_SERVER_SIDE_PACKAGE_CONTROL_FILE = server_job._control_segment_path(
     61         'stage_server_side_package')
     62 
     63 # Command line to start servod in a moblab.
     64 START_SERVOD_CMD = 'sudo start servod BOARD=%s PORT=%s'
     65 STOP_SERVOD_CMD = 'sudo stop servod'
     66 
     67 def log_alarm(signum, frame):
     68     logging.error("Received SIGALARM. Ignoring and continuing on.")
     69     sys.exit(1)
     70 
     71 
     72 def _get_machines(parser):
     73     """Get a list of machine names from command line arg -m or a file.
     74 
     75     @param parser: Parser for the command line arguments.
     76 
     77     @return: A list of machine names from command line arg -m or the
     78              machines file specified in the command line arg -M.
     79     """
     80     if parser.options.machines:
     81         machines = parser.options.machines.replace(',', ' ').strip().split()
     82     else:
     83         machines = []
     84     machines_file = parser.options.machines_file
     85     if machines_file:
     86         machines = []
     87         for m in open(machines_file, 'r').readlines():
     88             # remove comments, spaces
     89             m = re.sub('#.*', '', m).strip()
     90             if m:
     91                 machines.append(m)
     92         logging.debug('Read list of machines from file: %s', machines_file)
     93         logging.debug('Machines: %s', ','.join(machines))
     94 
     95     if machines:
     96         for machine in machines:
     97             if not machine or re.search('\s', machine):
     98                 parser.parser.error("Invalid machine: %s" % str(machine))
     99         machines = list(set(machines))
    100         machines.sort()
    101     return machines
    102 
    103 
    104 def _stage_ssp(parser):
    105     """Stage server-side package.
    106 
    107     This function calls a control segment to stage server-side package based on
    108     the job and autoserv command line option. The detail implementation could
    109     be different for each host type. Currently, only CrosHost has
    110     stage_server_side_package function defined.
    111     The script returns None if no server-side package is available. However,
    112     it may raise exception if it failed for reasons other than artifact (the
    113     server-side package) not found.
    114 
    115     @param parser: Command line arguments parser passed in the autoserv process.
    116 
    117     @return: (ssp_url, error_msg), where
    118               ssp_url is a url to the autotest server-side package. None if
    119               server-side package is not supported.
    120               error_msg is a string indicating the failures. None if server-
    121               side package is staged successfully.
    122     """
    123     machines_list = _get_machines(parser)
    124     machines_list = server_job.get_machine_dicts(
    125             machines_list, parser.options.lab, parser.options.host_attributes)
    126 
    127     # If test_source_build is not specified, default to use server-side test
    128     # code from build specified in --image.
    129     namespace = {'machines': machines_list,
    130                  'image': (parser.options.test_source_build or
    131                            parser.options.image),}
    132     script_locals = {}
    133     execfile(STAGE_SERVER_SIDE_PACKAGE_CONTROL_FILE, namespace, script_locals)
    134     return script_locals['ssp_url'], script_locals['error_msg']
    135 
    136 
    137 def _run_with_ssp(job, container_name, job_id, results, parser, ssp_url,
    138                   job_folder, machines):
    139     """Run the server job with server-side packaging.
    140 
    141     @param job: The server job object.
    142     @param container_name: Name of the container to run the test.
    143     @param job_id: ID of the test job.
    144     @param results: Folder to store results. This could be different from
    145                     parser.options.results:
    146                     parser.options.results  can be set to None for results to be
    147                     stored in a temp folder.
    148                     results can be None for autoserv run requires no logging.
    149     @param parser: Command line parser that contains the options.
    150     @param ssp_url: url of the staged server-side package.
    151     @param job_folder: Name of the job result folder.
    152     @param machines: A list of machines to run the test.
    153     """
    154     bucket = lxc.ContainerBucket()
    155     control = (parser.args[0] if len(parser.args) > 0 and parser.args[0] != ''
    156                else None)
    157     try:
    158         dut_name = machines[0] if len(machines) >= 1 else None
    159         test_container = bucket.setup_test(container_name, job_id, ssp_url,
    160                                            results, control=control,
    161                                            job_folder=job_folder,
    162                                            dut_name=dut_name)
    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 
    169     args = sys.argv[:]
    170     args.remove('--require-ssp')
    171     # --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]
    179 
    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     container_result_dir = os.path.join(lxc.RESULT_DIR_FMT % job_folder)
    190     if parser.options.results:
    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         paths_to_replace[parser.options.parse_job] = container_result_dir
    196 
    197     args = [paths_to_replace.get(arg, arg) for arg in args]
    198 
    199     # Apply --use-existing-results, results directory is aready created and
    200     # mounted in container. Apply this arg to avoid exception being raised.
    201     if not '--use-existing-results' in args:
    202         args.append('--use-existing-results')
    203 
    204     # Make sure autoserv running in container using a different pid file.
    205     if not '--pidfile-label' in args:
    206         args.extend(['--pidfile-label', 'container_autoserv'])
    207 
    208     cmd_line = ' '.join(["'%s'" % arg if ' ' in arg else arg for arg in args])
    209     logging.info('Run command in container: %s', cmd_line)
    210     success = False
    211     try:
    212         test_container.attach_run(cmd_line)
    213         success = True
    214     except Exception as e:
    215         # If the test run inside container fails without generating any log,
    216         # write a message to status.log to help troubleshooting.
    217         debug_files = os.listdir(os.path.join(results, 'debug'))
    218         if not debug_files:
    219             job.record('FAIL', None, None,
    220                        'Failed to run test inside the container: %s. Check '
    221                        'logs in ssp_logs folder for more details.' % e)
    222         raise
    223     finally:
    224         metrics.Counter(
    225             'chromeos/autotest/experimental/execute_job_in_ssp').increment(
    226                 fields={'success': success})
    227         test_container.destroy()
    228 
    229 
    230 def correct_results_folder_permission(results):
    231     """Make sure the results folder has the right permission settings.
    232 
    233     For tests running with server-side packaging, the results folder has the
    234     owner of root. This must be changed to the user running the autoserv
    235     process, so parsing job can access the results folder.
    236     TODO(dshi): crbug.com/459344 Remove this function when test container can be
    237     unprivileged container.
    238 
    239     @param results: Path to the results folder.
    240 
    241     """
    242     if not results:
    243         return
    244 
    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 
    248 
    249 def _start_servod(machine):
    250     """Try to start servod in moblab if it's not already running or running with
    251     different board or port.
    252 
    253     @param machine: Name of the dut used for test.
    254     """
    255     if not utils.is_moblab():
    256         return
    257 
    258     logging.debug('Trying to start servod.')
    259     try:
    260         afe = frontend.AFE()
    261         board = server_utils.get_board_from_afe(machine, afe)
    262         hosts = afe.get_hosts(hostname=machine)
    263         servo_host = hosts[0].attributes.get('servo_host', None)
    264         servo_port = hosts[0].attributes.get('servo_port', 9999)
    265         if not servo_host in ['localhost', '127.0.0.1']:
    266             logging.warn('Starting servod is aborted. The dut\'s servo_host '
    267                          'attribute is not set to localhost.')
    268             return
    269     except (urllib2.HTTPError, urllib2.URLError):
    270         # Ignore error if RPC failed to get board
    271         logging.error('Failed to get board name from AFE. Start servod is '
    272                       'aborted')
    273         return
    274 
    275     try:
    276         pid = utils.run('pgrep servod').stdout
    277         cmd_line = utils.run('ps -fp %s' % pid).stdout
    278         if ('--board %s' % board in cmd_line and
    279             '--port %s' % servo_port in cmd_line):
    280             logging.debug('Servod is already running with given board and port.'
    281                           ' There is no need to restart servod.')
    282             return
    283         logging.debug('Servod is running with different board or port. '
    284                       'Stopping existing servod.')
    285         utils.run('sudo stop servod')
    286     except error.CmdError:
    287         # servod is not running.
    288         pass
    289 
    290     try:
    291         utils.run(START_SERVOD_CMD % (board, servo_port))
    292         logging.debug('Servod is started')
    293     except error.CmdError as e:
    294         logging.error('Servod failed to be started, error: %s', e)
    295 
    296 
    297 def run_autoserv(pid_file_manager, results, parser, ssp_url, use_ssp):
    298     """Run server job with given options.
    299 
    300     @param pid_file_manager: PidFileManager used to monitor the autoserv process
    301     @param results: Folder to store results.
    302     @param parser: Parser for the command line arguments.
    303     @param ssp_url: Url to server-side package.
    304     @param use_ssp: Set to True to run with server-side packaging.
    305     """
    306     if parser.options.warn_no_ssp:
    307         # Post a warning in the log.
    308         logging.warn('Autoserv is required to run with server-side packaging. '
    309                      'However, no drone is found to support server-side '
    310                      'packaging. The test will be executed in a drone without '
    311                      'server-side packaging supported.')
    312 
    313     # send stdin to /dev/null
    314     dev_null = os.open(os.devnull, os.O_RDONLY)
    315     os.dup2(dev_null, sys.stdin.fileno())
    316     os.close(dev_null)
    317 
    318     # Create separate process group if the process is not a process group
    319     # leader. This allows autoserv process to keep running after the caller
    320     # process (drone manager call) exits.
    321     if os.getpid() != os.getpgid(0):
    322         os.setsid()
    323 
    324     # Container name is predefined so the container can be destroyed in
    325     # handle_sigterm.
    326     job_or_task_id = job_directories.get_job_id_or_task_id(
    327             parser.options.results)
    328     container_name = (lxc.TEST_CONTAINER_NAME_FMT %
    329                       (job_or_task_id, time.time(), os.getpid()))
    330     job_folder = job_directories.get_job_folder_name(parser.options.results)
    331 
    332     # Implement SIGTERM handler
    333     def handle_sigterm(signum, frame):
    334         logging.debug('Received SIGTERM')
    335         if pid_file_manager:
    336             pid_file_manager.close_file(1, signal.SIGTERM)
    337         logging.debug('Finished writing to pid_file. Killing process.')
    338 
    339         # Update results folder's file permission. This needs to be done ASAP
    340         # before the parsing process tries to access the log.
    341         if use_ssp and results:
    342             correct_results_folder_permission(results)
    343 
    344         # TODO (sbasi) - remove the time.sleep when crbug.com/302815 is solved.
    345         # This sleep allows the pending output to be logged before the kill
    346         # signal is sent.
    347         time.sleep(.1)
    348         if use_ssp:
    349             logging.debug('Destroy container %s before aborting the autoserv '
    350                           'process.', container_name)
    351             try:
    352                 bucket = lxc.ContainerBucket()
    353                 container = bucket.get(container_name)
    354                 if container:
    355                     container.destroy()
    356                 else:
    357                     logging.debug('Container %s is not found.', container_name)
    358             except:
    359                 # Handle any exception so the autoserv process can be aborted.
    360                 logging.exception('Failed to destroy container %s.',
    361                                   container_name)
    362             # Try to correct the result file permission again after the
    363             # container is destroyed, as the container might have created some
    364             # new files in the result folder.
    365             if results:
    366                 correct_results_folder_permission(results)
    367 
    368         os.killpg(os.getpgrp(), signal.SIGKILL)
    369 
    370     # Set signal handler
    371     signal.signal(signal.SIGTERM, handle_sigterm)
    372 
    373     # faulthandler is only needed to debug in the Lab and is not avaliable to
    374     # be imported in the chroot as part of VMTest, so Try-Except it.
    375     try:
    376         import faulthandler
    377         faulthandler.register(signal.SIGTERM, all_threads=True, chain=True)
    378         logging.debug('faulthandler registered on SIGTERM.')
    379     except ImportError:
    380         sys.exc_clear()
    381 
    382     # Ignore SIGTTOU's generated by output from forked children.
    383     signal.signal(signal.SIGTTOU, signal.SIG_IGN)
    384 
    385     # If we received a SIGALARM, let's be loud about it.
    386     signal.signal(signal.SIGALRM, log_alarm)
    387 
    388     # Server side tests that call shell scripts often depend on $USER being set
    389     # but depending on how you launch your autotest scheduler it may not be set.
    390     os.environ['USER'] = getpass.getuser()
    391 
    392     label = parser.options.label
    393     group_name = parser.options.group_name
    394     user = parser.options.user
    395     client = parser.options.client
    396     server = parser.options.server
    397     install_before = parser.options.install_before
    398     install_after = parser.options.install_after
    399     verify = parser.options.verify
    400     repair = parser.options.repair
    401     cleanup = parser.options.cleanup
    402     provision = parser.options.provision
    403     reset = parser.options.reset
    404     job_labels = parser.options.job_labels
    405     no_tee = parser.options.no_tee
    406     parse_job = parser.options.parse_job
    407     execution_tag = parser.options.execution_tag
    408     if not execution_tag:
    409         execution_tag = parse_job
    410     ssh_user = parser.options.ssh_user
    411     ssh_port = parser.options.ssh_port
    412     ssh_pass = parser.options.ssh_pass
    413     collect_crashinfo = parser.options.collect_crashinfo
    414     control_filename = parser.options.control_filename
    415     test_retry = parser.options.test_retry
    416     verify_job_repo_url = parser.options.verify_job_repo_url
    417     skip_crash_collection = parser.options.skip_crash_collection
    418     ssh_verbosity = int(parser.options.ssh_verbosity)
    419     ssh_options = parser.options.ssh_options
    420     no_use_packaging = parser.options.no_use_packaging
    421     host_attributes = parser.options.host_attributes
    422     in_lab = bool(parser.options.lab)
    423 
    424     # can't be both a client and a server side test
    425     if client and server:
    426         parser.parser.error("Can not specify a test as both server and client!")
    427 
    428     if provision and client:
    429         parser.parser.error("Cannot specify provisioning and client!")
    430 
    431     is_special_task = (verify or repair or cleanup or collect_crashinfo or
    432                        provision or reset)
    433     if len(parser.args) < 1 and not is_special_task:
    434         parser.parser.error("Missing argument: control file")
    435 
    436     if ssh_verbosity > 0:
    437         # ssh_verbosity is an integer between 0 and 3, inclusive
    438         ssh_verbosity_flag = '-' + 'v' * ssh_verbosity
    439     else:
    440         ssh_verbosity_flag = ''
    441 
    442     # We have a control file unless it's just a verify/repair/cleanup job
    443     if len(parser.args) > 0:
    444         control = parser.args[0]
    445     else:
    446         control = None
    447 
    448     machines = _get_machines(parser)
    449     if group_name and len(machines) < 2:
    450         parser.parser.error('-G %r may only be supplied with more than one '
    451                             'machine.' % group_name)
    452 
    453     kwargs = {'group_name': group_name, 'tag': execution_tag,
    454               'disable_sysinfo': parser.options.disable_sysinfo}
    455     if parser.options.parent_job_id:
    456         kwargs['parent_job_id'] = int(parser.options.parent_job_id)
    457     if control_filename:
    458         kwargs['control_filename'] = control_filename
    459     if host_attributes:
    460         kwargs['host_attributes'] = host_attributes
    461     kwargs['in_lab'] = in_lab
    462     job = server_job.server_job(control, parser.args[1:], results, label,
    463                                 user, machines, client, parse_job,
    464                                 ssh_user, ssh_port, ssh_pass,
    465                                 ssh_verbosity_flag, ssh_options,
    466                                 test_retry, **kwargs)
    467 
    468     job.logging.start_logging()
    469     job.init_parser()
    470 
    471     # perform checks
    472     job.precheck()
    473 
    474     # run the job
    475     exit_code = 0
    476     auto_start_servod = _CONFIG.get_config_value(
    477             'AUTOSERV', 'auto_start_servod', type=bool, default=False)
    478 
    479     site_utils.SetupTsMonGlobalState('autoserv', indirect=False,
    480                                      short_lived=True)
    481     try:
    482         try:
    483             if repair:
    484                 if auto_start_servod and len(machines) == 1:
    485                     _start_servod(machines[0])
    486                 job.repair(job_labels)
    487             elif verify:
    488                 job.verify(job_labels)
    489             elif provision:
    490                 job.provision(job_labels)
    491             elif reset:
    492                 job.reset(job_labels)
    493             elif cleanup:
    494                 job.cleanup(job_labels)
    495             else:
    496                 if auto_start_servod and len(machines) == 1:
    497                     _start_servod(machines[0])
    498                 if use_ssp:
    499                     try:
    500                         _run_with_ssp(job, container_name, job_or_task_id,
    501                                         results, parser, ssp_url, job_folder,
    502                                         machines)
    503                     finally:
    504                         # Update the ownership of files in result folder.
    505                         correct_results_folder_permission(results)
    506                 else:
    507                     if collect_crashinfo:
    508                         # Update the ownership of files in result folder. If the
    509                         # job to collect crashinfo was running inside container
    510                         # (SSP) and crashed before correcting folder permission,
    511                         # the result folder might have wrong permission setting.
    512                         try:
    513                             correct_results_folder_permission(results)
    514                         except:
    515                             # Ignore any error as the user may not have root
    516                             # permission to run sudo command.
    517                             pass
    518                     metric_name = ('chromeos/autotest/experimental/'
    519                                    'autoserv_job_run_duration')
    520                     f = {'in_container': utils.is_in_container(),
    521                          'success': False}
    522                     with metrics.SecondsTimer(metric_name, fields=f) as c:
    523                         job.run(install_before, install_after,
    524                                 verify_job_repo_url=verify_job_repo_url,
    525                                 only_collect_crashinfo=collect_crashinfo,
    526                                 skip_crash_collection=skip_crash_collection,
    527                                 job_labels=job_labels,
    528                                 use_packaging=(not no_use_packaging))
    529                         c['success'] = True
    530 
    531         finally:
    532             job.close()
    533             # Special task doesn't run parse, so result summary needs to be
    534             # built here.
    535             if results and (repair or verify or reset or cleanup or provision):
    536                 site_utils.collect_result_sizes(results)
    537     except:
    538         exit_code = 1
    539         traceback.print_exc()
    540     finally:
    541         metrics.Flush()
    542 
    543     if pid_file_manager:
    544         pid_file_manager.num_tests_failed = job.num_tests_failed
    545         pid_file_manager.close_file(exit_code)
    546     job.cleanup_parser()
    547 
    548     sys.exit(exit_code)
    549 
    550 
    551 def record_autoserv(options, duration_secs):
    552     """Record autoserv end-to-end time in metadata db.
    553 
    554     @param options: parser options.
    555     @param duration_secs: How long autoserv has taken, in secs.
    556     """
    557     # Get machine hostname
    558     machines = options.machines.replace(
    559             ',', ' ').strip().split() if options.machines else []
    560     num_machines = len(machines)
    561     if num_machines > 1:
    562         # Skip the case where atomic group is used.
    563         return
    564     elif num_machines == 0:
    565         machines.append('hostless')
    566 
    567     # Determine the status that will be reported.
    568     s = job_overhead.STATUS
    569     task_mapping = {
    570             'reset': s.RESETTING, 'verify': s.VERIFYING,
    571             'provision': s.PROVISIONING, 'repair': s.REPAIRING,
    572             'cleanup': s.CLEANING, 'collect_crashinfo': s.GATHERING}
    573     match = filter(lambda task: getattr(options, task, False) == True,
    574                    task_mapping)
    575     status = task_mapping[match[0]] if match else s.RUNNING
    576     is_special_task = status not in [s.RUNNING, s.GATHERING]
    577     job_or_task_id = job_directories.get_job_id_or_task_id(options.results)
    578     job_overhead.record_state_duration(
    579             job_or_task_id, machines[0], status, duration_secs,
    580             is_special_task=is_special_task)
    581 
    582 
    583 def main():
    584     start_time = datetime.datetime.now()
    585     # grab the parser
    586     parser = autoserv_parser.autoserv_parser
    587     parser.parse_args()
    588 
    589     if len(sys.argv) == 1:
    590         parser.parser.print_help()
    591         sys.exit(1)
    592 
    593     if parser.options.no_logging:
    594         results = None
    595     else:
    596         results = parser.options.results
    597         if not results:
    598             results = 'results.' + time.strftime('%Y-%m-%d-%H.%M.%S')
    599         results = os.path.abspath(results)
    600         resultdir_exists = False
    601         for filename in ('control.srv', 'status.log', '.autoserv_execute'):
    602             if os.path.exists(os.path.join(results, filename)):
    603                 resultdir_exists = True
    604         if not parser.options.use_existing_results and resultdir_exists:
    605             error = "Error: results directory already exists: %s\n" % results
    606             sys.stderr.write(error)
    607             sys.exit(1)
    608 
    609         # Now that we certified that there's no leftover results dir from
    610         # previous jobs, lets create the result dir since the logging system
    611         # needs to create the log file in there.
    612         if not os.path.isdir(results):
    613             os.makedirs(results)
    614 
    615     # If the job requires to run with server-side package, try to stage server-
    616     # side package first. If that fails with error that autotest server package
    617     # does not exist, fall back to run the job without using server-side
    618     # packaging. If option warn_no_ssp is specified, that means autoserv is
    619     # running in a drone does not support SSP, thus no need to stage server-side
    620     # package.
    621     ssp_url = None
    622     ssp_url_warning = False
    623     if (not parser.options.warn_no_ssp and parser.options.require_ssp):
    624         ssp_url, ssp_error_msg = _stage_ssp(parser)
    625         # The build does not have autotest server package. Fall back to not
    626         # to use server-side package. Logging is postponed until logging being
    627         # set up.
    628         ssp_url_warning = not ssp_url
    629 
    630     # Server-side packaging will only be used if it's required and the package
    631     # is available. If warn_no_ssp is specified, it means that autoserv is
    632     # running in a drone does not have SSP supported and a warning will be logs.
    633     # Therefore, it should not run with SSP.
    634     use_ssp = (not parser.options.warn_no_ssp and parser.options.require_ssp
    635                and ssp_url)
    636     if use_ssp:
    637         log_dir = os.path.join(results, 'ssp_logs') if results else None
    638         if log_dir and not os.path.exists(log_dir):
    639             os.makedirs(log_dir)
    640     else:
    641         log_dir = results
    642 
    643     logging_manager.configure_logging(
    644             server_logging_config.ServerLoggingConfig(),
    645             results_dir=log_dir,
    646             use_console=not parser.options.no_tee,
    647             verbose=parser.options.verbose,
    648             no_console_prefix=parser.options.no_console_prefix)
    649 
    650     if ssp_url_warning:
    651         logging.warn(
    652                 'Autoserv is required to run with server-side packaging. '
    653                 'However, no server-side package can be found based on '
    654                 '`--image`, host attribute job_repo_url or host OS version '
    655                 'label. It could be that the build to test is older than the '
    656                 'minimum version that supports server-side packaging. The test '
    657                 'will be executed without using erver-side packaging. '
    658                 'Following is the detailed error:\n%s', ssp_error_msg)
    659 
    660     if results:
    661         logging.info("Results placed in %s" % results)
    662 
    663         # wait until now to perform this check, so it get properly logged
    664         if (parser.options.use_existing_results and not resultdir_exists and
    665             not utils.is_in_container()):
    666             logging.error("No existing results directory found: %s", results)
    667             sys.exit(1)
    668 
    669     logging.debug('autoserv is running in drone %s.', socket.gethostname())
    670     logging.debug('autoserv command was: %s', ' '.join(sys.argv))
    671 
    672     if parser.options.write_pidfile and results:
    673         pid_file_manager = pidfile.PidFileManager(parser.options.pidfile_label,
    674                                                   results)
    675         pid_file_manager.open_file()
    676     else:
    677         pid_file_manager = None
    678 
    679     autotest.BaseAutotest.set_install_in_tmpdir(
    680         parser.options.install_in_tmpdir)
    681 
    682     try:
    683         # Take the first argument as control file name, get the test name from
    684         # the control file.
    685         if (len(parser.args) > 0 and parser.args[0] != '' and
    686             parser.options.machines):
    687             try:
    688                 test_name = control_data.parse_control(parser.args[0],
    689                                                        raise_warnings=True).name
    690             except control_data.ControlVariableException:
    691                 logging.debug('Failed to retrieve test name from control file.')
    692                 test_name = None
    693     except control_data.ControlVariableException as e:
    694         logging.error(str(e))
    695     exit_code = 0
    696     # TODO(beeps): Extend this to cover different failure modes.
    697     # Testing exceptions are matched against labels sent to autoserv. Eg,
    698     # to allow only the hostless job to run, specify
    699     # testing_exceptions: test_suite in the shadow_config. To allow both
    700     # the hostless job and dummy_Pass to run, specify
    701     # testing_exceptions: test_suite,dummy_Pass. You can figure out
    702     # what label autoserv is invoked with by looking through the logs of a test
    703     # for the autoserv command's -l option.
    704     testing_exceptions = _CONFIG.get_config_value(
    705             'AUTOSERV', 'testing_exceptions', type=list, default=[])
    706     test_mode = _CONFIG.get_config_value(
    707             'AUTOSERV', 'testing_mode', type=bool, default=False)
    708     test_mode = (results_mocker and test_mode and not
    709                  any([ex in parser.options.label
    710                       for ex in testing_exceptions]))
    711     is_task = (parser.options.verify or parser.options.repair or
    712                parser.options.provision or parser.options.reset or
    713                parser.options.cleanup or parser.options.collect_crashinfo)
    714     try:
    715         try:
    716             if test_mode:
    717                 # The parser doesn't run on tasks anyway, so we can just return
    718                 # happy signals without faking results.
    719                 if not is_task:
    720                     machine = parser.options.results.split('/')[-1]
    721 
    722                     # TODO(beeps): The proper way to do this would be to
    723                     # refactor job creation so we can invoke job.record
    724                     # directly. To do that one needs to pipe the test_name
    725                     # through run_autoserv and bail just before invoking
    726                     # the server job. See the comment in
    727                     # puppylab/results_mocker for more context.
    728                     results_mocker.ResultsMocker(
    729                             test_name if test_name else 'unknown-test',
    730                             parser.options.results, machine
    731                             ).mock_results()
    732                 return
    733             else:
    734                 run_autoserv(pid_file_manager, results, parser, ssp_url,
    735                              use_ssp)
    736         except SystemExit as e:
    737             exit_code = e.code
    738             if exit_code:
    739                 logging.exception('Uncaught SystemExit with code %s', exit_code)
    740         except Exception:
    741             # If we don't know what happened, we'll classify it as
    742             # an 'abort' and return 1.
    743             logging.exception('Uncaught Exception, exit_code = 1.')
    744             exit_code = 1
    745     finally:
    746         if pid_file_manager:
    747             pid_file_manager.close_file(exit_code)
    748         # Record the autoserv duration time. Must be called
    749         # just before the system exits to ensure accuracy.
    750         duration_secs = (datetime.datetime.now() - start_time).total_seconds()
    751         record_autoserv(parser.options, duration_secs)
    752     sys.exit(exit_code)
    753 
    754 
    755 if __name__ == '__main__':
    756     main()
    757