1#!/usr/bin/python2 -u 2# Copyright 2007-2008 Martin J. Bligh <mbligh@google.com>, Google Inc. 3# Released under the GPL v2 4 5""" 6Run a control file through the server side engine 7""" 8 9import datetime 10import contextlib 11import getpass 12import logging 13import os 14import re 15import shutil 16import signal 17import socket 18import sys 19import traceback 20import time 21import six 22from six.moves import urllib 23 24import common 25from autotest_lib.client.bin.result_tools import utils as result_utils 26from autotest_lib.client.bin.result_tools import view as result_view 27from autotest_lib.client.common_lib import control_data 28from autotest_lib.client.common_lib import autotest_enum 29from autotest_lib.client.common_lib import error 30from autotest_lib.client.common_lib import global_config 31from autotest_lib.client.common_lib import host_queue_entry_states 32from autotest_lib.client.common_lib import host_states 33from autotest_lib.client.common_lib import seven 34from autotest_lib.server.cros.dynamic_suite import suite 35 36try: 37 from chromite.lib import metrics 38 from chromite.lib import cloud_trace 39except ImportError: 40 from autotest_lib.client.common_lib import utils as common_utils 41 metrics = common_utils.metrics_mock 42 import mock 43 cloud_trace = mock.MagicMock() 44 45_CONFIG = global_config.global_config 46 47# Number of seconds to wait before returning if testing mode is enabled 48TESTING_MODE_SLEEP_SECS = 1 49 50 51from autotest_lib.server import frontend 52from autotest_lib.server import server_logging_config 53from autotest_lib.server import server_job, utils, autoserv_parser, autotest 54from autotest_lib.server import utils as server_utils 55from autotest_lib.server import site_utils 56from autotest_lib.server.cros.dynamic_suite import frontend_wrappers 57from autotest_lib.site_utils import job_directories 58from autotest_lib.site_utils import lxc 59from autotest_lib.site_utils.lxc import utils as lxc_utils 60from autotest_lib.client.common_lib import pidfile, logging_manager 61 62 63# Control segment to stage server-side package. 64STAGE_SERVER_SIDE_PACKAGE_CONTROL_FILE = server_job._control_segment_path( 65 'stage_server_side_package') 66 67# Command line to start servod in a moblab. 68START_SERVOD_CMD = 'sudo start servod BOARD=%s PORT=%s' 69STOP_SERVOD_CMD = 'sudo stop servod' 70 71_AUTOTEST_ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__), '..')) 72_CONTROL_FILE_FROM_CONTROL_NAME = 'control.from_control_name' 73 74_LXC_JOB_FOLDER = 'lxc_job_folder' 75 76def log_alarm(signum, frame): 77 logging.error("Received SIGALARM. Ignoring and continuing on.") 78 sys.exit(1) 79 80 81def _get_machines(parser): 82 """Get a list of machine names from command line arg -m or a file. 83 84 @param parser: Parser for the command line arguments. 85 86 @return: A list of machine names from command line arg -m or the 87 machines file specified in the command line arg -M. 88 """ 89 if parser.options.machines: 90 machines = parser.options.machines.replace(',', ' ').strip().split() 91 else: 92 machines = [] 93 machines_file = parser.options.machines_file 94 if machines_file: 95 machines = [] 96 for m in open(machines_file, 'r').readlines(): 97 # remove comments, spaces 98 m = re.sub('#.*', '', m).strip() 99 if m: 100 machines.append(m) 101 logging.debug('Read list of machines from file: %s', machines_file) 102 logging.debug('Machines: %s', ','.join(machines)) 103 104 if machines: 105 for machine in machines: 106 if not machine or re.search('\s', machine): 107 parser.parser.error("Invalid machine: %s" % str(machine)) 108 machines = list(set(machines)) 109 machines.sort() 110 return machines 111 112 113def _stage_ssp(parser, resultsdir): 114 """Stage server-side package. 115 116 This function calls a control segment to stage server-side package based on 117 the job and autoserv command line option. The detail implementation could 118 be different for each host type. Currently, only CrosHost has 119 stage_server_side_package function defined. 120 The script returns None if no server-side package is available. However, 121 it may raise exception if it failed for reasons other than artifact (the 122 server-side package) not found. 123 124 @param parser: Command line arguments parser passed in the autoserv process. 125 @param resultsdir: Folder to store results. This could be different from 126 parser.options.results: parser.options.results can be set to None 127 for results to be stored in a temp folder. resultsdir can be None 128 for autoserv run requires no logging. 129 130 @return: url to the autotest server-side package. None in case of errors. 131 """ 132 machines_list = _get_machines(parser) 133 machines_list = server_job.get_machine_dicts( 134 machine_names=machines_list, 135 store_dir=os.path.join(resultsdir, parser.options.host_info_subdir), 136 in_lab=parser.options.lab, 137 use_shadow_store=not parser.options.local_only_host_info, 138 host_attributes=parser.options.host_attributes, 139 ) 140 141 namespace = {'machines': machines_list, 142 'isolate_hash': parser.options.isolate, 143 'image': parser.options.test_source_build} 144 script_locals = {} 145 146 seven.exec_file( 147 STAGE_SERVER_SIDE_PACKAGE_CONTROL_FILE, 148 globals_=namespace, 149 locals_=script_locals, 150 ) 151 ssp_url = script_locals['ssp_url'] 152 if not ssp_url: 153 logging.error('Failed to stage SSP package: %s', 154 script_locals['error_msg']) 155 logging.error('This job will fail later, when attempting to run with' 156 ' SSP') 157 return ssp_url 158 159 160def _run_with_ssp(job, container_id, job_id, results, parser, ssp_url, 161 machines): 162 """Run the server job with server-side packaging. 163 164 @param job: The server job object. 165 @param container_id: ID of the container to run the test. 166 @param job_id: ID of the test job. 167 @param results: Folder to store results. This could be different from 168 parser.options.results: 169 parser.options.results can be set to None for results to be 170 stored in a temp folder. 171 results can be None if the autoserv run requires no logging. 172 @param parser: Command line parser that contains the options. 173 @param ssp_url: url of the staged server-side package. 174 @param machines: A list of machines to run the test. 175 """ 176 if not ssp_url: 177 job.record('FAIL', None, None, 178 'Failed to stage server-side package') 179 raise error.AutoservError('Failed to stage server-side package') 180 181 bucket = lxc.ContainerBucket( 182 base_name=_ssp_base_image_name_or_default(parser.options)) 183 control = (parser.args[0] if len(parser.args) > 0 and parser.args[0] != '' 184 else None) 185 try: 186 dut_name = machines[0] if len(machines) >= 1 else None 187 test_container = bucket.setup_test(container_id, job_id, ssp_url, 188 results, control=control, 189 job_folder=_LXC_JOB_FOLDER, 190 dut_name=dut_name, 191 isolate_hash=parser.options.isolate) 192 except Exception as e: 193 job.record('FAIL', None, None, 194 'Failed to setup container for test: %s. Check logs in ' 195 'ssp_logs folder for more details.' % e) 196 raise 197 198 args = sys.argv[:] 199 args.remove('--require-ssp') 200 # --parent_job_id is only useful in autoserv running in host, not in 201 # container. Include this argument will cause test to fail for builds before 202 # CL 286265 was merged. 203 if '--parent_job_id' in args: 204 index = args.index('--parent_job_id') 205 args.remove('--parent_job_id') 206 # Remove the actual parent job id in command line arg. 207 del args[index] 208 209 # A dictionary of paths to replace in the command line. Key is the path to 210 # be replaced with the one in value. 211 paths_to_replace = {} 212 # Replace the control file path with the one in container. 213 if control: 214 container_control_filename = os.path.join( 215 lxc.CONTROL_TEMP_PATH, os.path.basename(control)) 216 paths_to_replace[control] = container_control_filename 217 # Update result directory with the one in container. 218 container_result_dir = os.path.join(lxc.RESULT_DIR_FMT % _LXC_JOB_FOLDER) 219 if parser.options.results: 220 paths_to_replace[parser.options.results] = container_result_dir 221 args = [paths_to_replace.get(arg, arg) for arg in args] 222 223 # Apply --use-existing-results, results directory is aready created and 224 # mounted in container. Apply this arg to avoid exception being raised. 225 if not '--use-existing-results' in args: 226 args.append('--use-existing-results') 227 228 # Make sure autoserv running in container using a different pid file. 229 if not '--pidfile-label' in args: 230 args.extend(['--pidfile-label', 'container_autoserv']) 231 232 cmd_line = ' '.join(["'%s'" % arg if ' ' in arg else arg for arg in args]) 233 logging.info('Run command in container: %s', cmd_line) 234 success = False 235 try: 236 test_container.attach_run(cmd_line) 237 success = True 238 except Exception as e: 239 # If the test run inside container fails without generating any log, 240 # write a message to status.log to help troubleshooting. 241 debug_files = os.listdir(os.path.join(results, 'debug')) 242 if not debug_files: 243 job.record('FAIL', None, None, 244 'Failed to run test inside the container: %s. Check ' 245 'logs in ssp_logs folder for more details.' % e) 246 raise 247 finally: 248 metrics.Counter( 249 'chromeos/autotest/experimental/execute_job_in_ssp').increment( 250 fields={'success': success}) 251 test_container.destroy() 252 253 254def correct_results_folder_permission(results): 255 """Make sure the results folder has the right permission settings. 256 257 For tests running with server-side packaging, the results folder has the 258 owner of root. This must be changed to the user running the autoserv 259 process, so parsing job can access the results folder. 260 TODO(dshi): crbug.com/459344 Remove this function when test container can be 261 unprivileged container. 262 263 @param results: Path to the results folder. 264 265 """ 266 if not results: 267 return 268 269 utils.run('sudo -n chown -R %s "%s"' % (os.getuid(), results)) 270 utils.run('sudo -n chgrp -R %s "%s"' % (os.getgid(), results)) 271 272 273def _start_servod(machine): 274 """Try to start servod in moblab if it's not already running or running with 275 different board or port. 276 277 @param machine: Name of the dut used for test. 278 """ 279 if not utils.is_moblab(): 280 return 281 282 logging.debug('Trying to start servod.') 283 try: 284 afe = frontend.AFE() 285 board = server_utils.get_board_from_afe(machine, afe) 286 hosts = afe.get_hosts(hostname=machine) 287 servo_host = hosts[0].attributes.get('servo_host', None) 288 servo_port = hosts[0].attributes.get('servo_port', 9999) 289 if not servo_host in ['localhost', '127.0.0.1']: 290 logging.warn('Starting servod is aborted. The dut\'s servo_host ' 291 'attribute is not set to localhost.') 292 return 293 except (urllib.error.HTTPError, urllib.error.URLError): 294 # Ignore error if RPC failed to get board 295 logging.error('Failed to get board name from AFE. Start servod is ' 296 'aborted') 297 return 298 299 try: 300 pid = utils.run('pgrep servod').stdout 301 cmd_line = utils.run('ps -fp %s' % pid).stdout 302 if ('--board %s' % board in cmd_line and 303 '--port %s' % servo_port in cmd_line): 304 logging.debug('Servod is already running with given board and port.' 305 ' There is no need to restart servod.') 306 return 307 logging.debug('Servod is running with different board or port. ' 308 'Stopping existing servod.') 309 utils.run('sudo stop servod') 310 except error.CmdError: 311 # servod is not running. 312 pass 313 314 try: 315 utils.run(START_SERVOD_CMD % (board, servo_port)) 316 logging.debug('Servod is started') 317 except error.CmdError as e: 318 logging.error('Servod failed to be started, error: %s', e) 319 320 321def _control_path_on_disk(control_name): 322 """Find the control file corresponding to the given control name, on disk. 323 324 @param control_name: NAME attribute of the control file to fetch. 325 @return: Path to the control file. 326 """ 327 cf_getter = suite.create_fs_getter(_AUTOTEST_ROOT) 328 control_name_predicate = suite.test_name_matches_pattern_predicate( 329 '^%s$' % control_name) 330 tests = suite.find_and_parse_tests(cf_getter, control_name_predicate) 331 if not tests: 332 raise error.AutoservError( 333 'Failed to find any control files with NAME %s' % control_name) 334 if len(tests) > 1: 335 logging.error('Found more than one control file with NAME %s: %s', 336 control_name, [t.path for t in tests]) 337 raise error.AutoservError( 338 'Found more than one control file with NAME %s' % control_name) 339 return tests[0].path 340 341 342def _stage_control_file(control_name, results_dir): 343 """Stage the control file to execute from local autotest checkout. 344 345 @param control_name: Name of the control file to stage. 346 @param results_dir: Results directory to stage the control file into. 347 @return: Absolute path to the staged control file. 348 """ 349 control_path = _control_path_on_disk(control_name) 350 new_control = os.path.join(results_dir, _CONTROL_FILE_FROM_CONTROL_NAME) 351 shutil.copy2(control_path, new_control) 352 return new_control 353 354 355def run_autoserv(pid_file_manager, results, parser, ssp_url, use_ssp): 356 """Run server job with given options. 357 358 @param pid_file_manager: PidFileManager used to monitor the autoserv process 359 @param results: Folder to store results. 360 @param parser: Parser for the command line arguments. 361 @param ssp_url: Url to server-side package. 362 @param use_ssp: Set to True to run with server-side packaging. 363 """ 364 # send stdin to /dev/null 365 dev_null = os.open(os.devnull, os.O_RDONLY) 366 os.dup2(dev_null, sys.stdin.fileno()) 367 os.close(dev_null) 368 369 # Create separate process group if the process is not a process group 370 # leader. This allows autoserv process to keep running after the caller 371 # process (drone manager call) exits. 372 if os.getpid() != os.getpgid(0): 373 os.setsid() 374 375 # Container name is predefined so the container can be destroyed in 376 # handle_sigterm. 377 job_or_task_id = job_directories.get_job_id_or_task_id( 378 parser.options.results) 379 container_id = lxc.ContainerId(job_or_task_id, time.time(), os.getpid()) 380 381 # Implement SIGTERM handler 382 def handle_sigterm(signum, frame): 383 logging.debug('Received SIGTERM') 384 if pid_file_manager: 385 pid_file_manager.close_file(1, signal.SIGTERM) 386 logging.debug('Finished writing to pid_file. Killing process.') 387 388 # Update results folder's file permission. This needs to be done ASAP 389 # before the parsing process tries to access the log. 390 if use_ssp and results: 391 correct_results_folder_permission(results) 392 393 # This sleep allows the pending output to be logged before the kill 394 # signal is sent. 395 time.sleep(.1) 396 if use_ssp: 397 logging.debug('Destroy container %s before aborting the autoserv ' 398 'process.', container_id) 399 try: 400 bucket = lxc.ContainerBucket( 401 base_name=_ssp_base_image_name_or_default( 402 parser.options)) 403 container = bucket.get_container(container_id) 404 if container: 405 container.destroy() 406 logging.debug("Container %s destroyed.", container_id) 407 else: 408 logging.debug('Container %s is not found.', container_id) 409 bucket.scrub_container_location(container_id) 410 except: 411 # Handle any exception so the autoserv process can be aborted. 412 logging.exception('Failed to destroy container %s.', 413 container_id) 414 # Try to correct the result file permission again after the 415 # container is destroyed, as the container might have created some 416 # new files in the result folder. 417 if results: 418 correct_results_folder_permission(results) 419 420 os.killpg(os.getpgrp(), signal.SIGKILL) 421 422 # Set signal handler 423 signal.signal(signal.SIGTERM, handle_sigterm) 424 425 # faulthandler is only needed to debug in the Lab and is not avaliable to 426 # be imported in the chroot as part of VMTest, so Try-Except it. 427 try: 428 import faulthandler 429 faulthandler.register(signal.SIGTERM, all_threads=True, chain=True) 430 logging.debug('faulthandler registered on SIGTERM.') 431 except ImportError: 432 # exc_clear() doesn't exist (nor is needed) in python3 433 if six.PY2: 434 sys.exc_clear() 435 436 # Ignore SIGTTOU's generated by output from forked children. 437 signal.signal(signal.SIGTTOU, signal.SIG_IGN) 438 439 # If we received a SIGALARM, let's be loud about it. 440 signal.signal(signal.SIGALRM, log_alarm) 441 442 # Server side tests that call shell scripts often depend on $USER being set 443 # but depending on how you launch your autotest scheduler it may not be set. 444 os.environ['USER'] = getpass.getuser() 445 446 label = parser.options.label 447 group_name = parser.options.group_name 448 user = parser.options.user 449 client = parser.options.client 450 server = parser.options.server 451 verify = parser.options.verify 452 repair = parser.options.repair 453 cleanup = parser.options.cleanup 454 provision = parser.options.provision 455 reset = parser.options.reset 456 job_labels = parser.options.job_labels 457 no_tee = parser.options.no_tee 458 execution_tag = parser.options.execution_tag 459 ssh_user = parser.options.ssh_user 460 ssh_port = parser.options.ssh_port 461 ssh_pass = parser.options.ssh_pass 462 collect_crashinfo = parser.options.collect_crashinfo 463 control_filename = parser.options.control_filename 464 verify_job_repo_url = parser.options.verify_job_repo_url 465 skip_crash_collection = parser.options.skip_crash_collection 466 ssh_verbosity = int(parser.options.ssh_verbosity) 467 ssh_options = parser.options.ssh_options 468 no_use_packaging = parser.options.no_use_packaging 469 in_lab = bool(parser.options.lab) 470 471 # can't be both a client and a server side test 472 if client and server: 473 parser.parser.error("Can not specify a test as both server and client!") 474 475 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 479 provision or reset) 480 use_client_trampoline = False 481 if parser.options.control_name: 482 if use_ssp: 483 # When use_ssp is True, autoserv will be re-executed inside a 484 # container preserving the --control-name argument. Control file 485 # will be staged inside the rexecuted autoserv. 486 control = None 487 else: 488 try: 489 control = _stage_control_file(parser.options.control_name, 490 results) 491 except error.AutoservError as e: 492 logging.info("Using client trampoline because of: %s", e) 493 control = parser.options.control_name 494 use_client_trampoline = True 495 496 elif parser.args: 497 control = parser.args[0] 498 else: 499 if not is_special_task: 500 parser.parser.error("Missing argument: control file") 501 control = None 502 503 if ssh_verbosity > 0: 504 # ssh_verbosity is an integer between 0 and 3, inclusive 505 ssh_verbosity_flag = '-' + 'v' * ssh_verbosity 506 else: 507 ssh_verbosity_flag = '' 508 509 machines = _get_machines(parser) 510 if group_name and len(machines) < 2: 511 parser.parser.error('-G %r may only be supplied with more than one ' 512 'machine.' % group_name) 513 514 logging.debug("Parser.args is %r", parser.args) 515 try: 516 logging.debug("Parser.options.args is %r", parser.options.args) 517 except AttributeError: 518 logging.debug("No Parser.options.args.") 519 520 try: 521 logging.debug("Parser.options is %r", parser.options) 522 except AttributeError: 523 logging.debug("No Parser.options.") 524 job_kwargs = { 525 'control': control, 526 'args': parser.args[1:], 527 'resultdir': results, 528 'label': label, 529 'user': user, 530 'machines': machines, 531 'machine_dict_list': server_job.get_machine_dicts( 532 machine_names=machines, 533 store_dir=os.path.join(results, 534 parser.options.host_info_subdir), 535 in_lab=in_lab, 536 use_shadow_store=not parser.options.local_only_host_info, 537 host_attributes=parser.options.host_attributes, 538 ), 539 'client': client, 540 'ssh_user': ssh_user, 541 'ssh_port': ssh_port, 542 'ssh_pass': ssh_pass, 543 'ssh_verbosity_flag': ssh_verbosity_flag, 544 'ssh_options': ssh_options, 545 'group_name': group_name, 546 'tag': execution_tag, 547 'disable_sysinfo': parser.options.disable_sysinfo, 548 'in_lab': in_lab, 549 'use_client_trampoline': use_client_trampoline, 550 'sync_offload_dir': parser.options.sync_offload_dir, 551 } 552 if parser.options.parent_job_id: 553 job_kwargs['parent_job_id'] = int(parser.options.parent_job_id) 554 if control_filename: 555 job_kwargs['control_filename'] = control_filename 556 job = server_job.server_job(**job_kwargs) 557 558 job.logging.start_logging() 559 560 # perform checks 561 job.precheck() 562 563 # run the job 564 exit_code = 0 565 auto_start_servod = _CONFIG.get_config_value( 566 'AUTOSERV', 'auto_start_servod', type=bool, default=False) 567 568 if not utils.is_in_container(): 569 # crbug.com/1054522 -- ts_mon setup is broken inside the SSP container 570 # due to a problem in the installed python packages. 571 # Trying to clean up an incorrectly initialized ts_mon state adds a 5 572 # second overhead in process teardown, so avoid setting up ts_mon 573 # entirely inside the SSP container. 574 site_utils.SetupTsMonGlobalState('autoserv', indirect=False, 575 short_lived=True) 576 try: 577 try: 578 if repair: 579 if auto_start_servod and len(machines) == 1: 580 _start_servod(machines[0]) 581 job.repair(job_labels) 582 elif verify: 583 job.verify(job_labels) 584 elif provision: 585 job.provision(job_labels) 586 elif reset: 587 job.reset(job_labels) 588 elif cleanup: 589 job.cleanup(job_labels) 590 else: 591 if auto_start_servod and len(machines) == 1: 592 _start_servod(machines[0]) 593 if use_ssp: 594 try: 595 _run_with_ssp(job, container_id, job_or_task_id, 596 results, parser, ssp_url, machines) 597 finally: 598 # Update the ownership of files in result folder. 599 correct_results_folder_permission(results) 600 else: 601 if collect_crashinfo: 602 # Update the ownership of files in result folder. If the 603 # job to collect crashinfo was running inside container 604 # (SSP) and crashed before correcting folder permission, 605 # the result folder might have wrong permission setting. 606 try: 607 correct_results_folder_permission(results) 608 except: 609 # Ignore any error as the user may not have root 610 # permission to run sudo command. 611 pass 612 metric_name = ('chromeos/autotest/experimental/' 613 'autoserv_job_run_duration') 614 f = {'in_container': utils.is_in_container(), 615 'success': False} 616 with metrics.SecondsTimer(metric_name, fields=f) as c: 617 job.run(verify_job_repo_url=verify_job_repo_url, 618 only_collect_crashinfo=collect_crashinfo, 619 skip_crash_collection=skip_crash_collection, 620 job_labels=job_labels, 621 use_packaging=(not no_use_packaging)) 622 c['success'] = True 623 624 finally: 625 job.close() 626 except: 627 exit_code = 1 628 traceback.print_exc() 629 finally: 630 metrics.Flush() 631 632 sys.exit(exit_code) 633 634 635# Job breakdown statuses 636_hs = host_states.Status 637_qs = host_queue_entry_states.Status 638_status_list = [ 639 _qs.QUEUED, _qs.RESETTING, _qs.VERIFYING, 640 _qs.PROVISIONING, _hs.REPAIRING, _qs.CLEANING, 641 _qs.RUNNING, _qs.GATHERING, _qs.PARSING] 642_JOB_OVERHEAD_STATUS = autotest_enum.AutotestEnum(*_status_list, 643 string_values=True) 644 645 646def get_job_status(options): 647 """Returns the HQE Status for this run. 648 649 @param options: parser options. 650 """ 651 s = _JOB_OVERHEAD_STATUS 652 task_mapping = { 653 'reset': s.RESETTING, 'verify': s.VERIFYING, 654 'provision': s.PROVISIONING, 'repair': s.REPAIRING, 655 'cleanup': s.CLEANING, 'collect_crashinfo': s.GATHERING} 656 match = [task for task in task_mapping if getattr(options, task, False)] 657 return task_mapping[match[0]] if match else s.RUNNING 658 659 660def _require_ssp_from_control(control_name): 661 """Read the value of REQUIRE_SSP from test control file. 662 663 This reads the control file from the prod checkout of autotest and uses that 664 to determine whether to even stage the SSP package on a devserver. 665 666 This means: 667 [1] Any change in REQUIRE_SSP directive in a test requires a prod-push to go 668 live. 669 [2] This function may find that the control file does not exist but the SSP 670 package may contain the test file. This function conservatively returns True 671 in that case. 672 673 This function is called very early in autoserv, before logging is setup. 674 """ 675 if not control_name: 676 return True 677 try: 678 path = _control_path_on_disk(control_name) 679 except error.AutoservError as e: 680 sys.stderr.write("autoserv: Could not determine control file path," 681 " assuming we need SSP: %s\n" % e) 682 sys.stderr.flush() 683 return True 684 if not os.path.isfile(path): 685 return True 686 control = control_data.parse_control(path) 687 # There must be explicit directive in the control file to disable SSP. 688 if not control or control.require_ssp is None: 689 return True 690 return control.require_ssp 691 692 693def _ssp_base_image_name_or_default(options): 694 """Extract base image name from autoserv options or the global config.""" 695 if options.ssp_base_image_name: 696 return options.ssp_base_image_name 697 return global_config.global_config.get_config_value('AUTOSERV', 698 'container_base_name') 699 700 701def main(): 702 start_time = datetime.datetime.now() 703 parser = autoserv_parser.autoserv_parser 704 parser.parse_args() 705 706 if len(sys.argv) == 1: 707 parser.parser.print_help() 708 sys.exit(1) 709 710 if parser.options.no_logging: 711 results = None 712 else: 713 results = parser.options.results 714 if not results: 715 results = 'results.' + time.strftime('%Y-%m-%d-%H.%M.%S') 716 results = os.path.abspath(results) 717 resultdir_exists = False 718 for filename in ('control.srv', 'status.log', '.autoserv_execute'): 719 if os.path.exists(os.path.join(results, filename)): 720 resultdir_exists = True 721 if not parser.options.use_existing_results and resultdir_exists: 722 error = "Error: results directory already exists: %s\n" % results 723 sys.stderr.write(error) 724 sys.exit(1) 725 726 # Now that we certified that there's no leftover results dir from 727 # previous jobs, lets create the result dir since the logging system 728 # needs to create the log file in there. 729 if not os.path.isdir(results): 730 os.makedirs(results) 731 732 if parser.options.require_ssp: 733 # This is currently only used for skylab (i.e., when --control-name is 734 # used). 735 use_ssp = _require_ssp_from_control(parser.options.control_name) 736 else: 737 use_ssp = False 738 739 740 if use_ssp: 741 log_dir = os.path.join(results, 'ssp_logs') if results else None 742 if log_dir and not os.path.exists(log_dir): 743 os.makedirs(log_dir) 744 else: 745 log_dir = results 746 747 logging_manager.configure_logging( 748 server_logging_config.ServerLoggingConfig(), 749 results_dir=log_dir, 750 use_console=not parser.options.no_tee, 751 verbose=parser.options.verbose, 752 no_console_prefix=parser.options.no_console_prefix) 753 754 logging.debug('autoserv is running in drone %s.', socket.gethostname()) 755 logging.debug('autoserv environment: %r', os.environ) 756 logging.debug('autoserv command was: %s', ' '.join(sys.argv)) 757 logging.debug('autoserv parsed options: %s', parser.options) 758 759 if use_ssp: 760 ssp_url = _stage_ssp(parser, results) 761 else: 762 ssp_url = None 763 764 if results: 765 logging.info("Results placed in %s" % results) 766 767 # wait until now to perform this check, so it get properly logged 768 if (parser.options.use_existing_results and not resultdir_exists and 769 not utils.is_in_container()): 770 logging.error("No existing results directory found: %s", results) 771 sys.exit(1) 772 773 if parser.options.write_pidfile and results: 774 pid_file_manager = pidfile.PidFileManager(parser.options.pidfile_label, 775 results) 776 pid_file_manager.open_file() 777 else: 778 pid_file_manager = None 779 780 autotest.Autotest.set_install_in_tmpdir( 781 parser.options.install_in_tmpdir) 782 783 exit_code = 0 784 is_task = (parser.options.verify or parser.options.repair or 785 parser.options.provision or parser.options.reset or 786 parser.options.cleanup or parser.options.collect_crashinfo) 787 788 trace_labels = { 789 'job_id': job_directories.get_job_id_or_task_id( 790 parser.options.results) 791 } 792 trace = cloud_trace.SpanStack( 793 labels=trace_labels, 794 global_context=parser.options.cloud_trace_context) 795 trace.enabled = parser.options.cloud_trace_context_enabled == 'True' 796 try: 797 try: 798 with trace.Span(get_job_status(parser.options)): 799 run_autoserv(pid_file_manager, results, parser, ssp_url, 800 use_ssp) 801 except SystemExit as e: 802 exit_code = e.code 803 if exit_code: 804 logging.exception('Uncaught SystemExit with code %s', exit_code) 805 except Exception: 806 # If we don't know what happened, we'll classify it as 807 # an 'abort' and return 1. 808 logging.exception('Uncaught Exception, exit_code = 1.') 809 exit_code = 1 810 finally: 811 if pid_file_manager: 812 pid_file_manager.close_file(exit_code) 813 sys.exit(exit_code) 814 815 816if __name__ == '__main__': 817 main() 818