| OLD | NEW |
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # Copyright 2012 The LUCI Authors. All rights reserved. | 2 # Copyright 2012 The LUCI Authors. All rights reserved. |
| 3 # Use of this source code is governed under the Apache License, Version 2.0 | 3 # Use of this source code is governed under the Apache License, Version 2.0 |
| 4 # that can be found in the LICENSE file. | 4 # that can be found in the LICENSE file. |
| 5 | 5 |
| 6 """Runs a command with optional isolated input/output. | 6 """Runs a command with optional isolated input/output. |
| 7 | 7 |
| 8 Despite name "run_isolated", can run a generic non-isolated command specified as | 8 Despite name "run_isolated", can run a generic non-isolated command specified as |
| 9 args. | 9 args. |
| 10 | 10 |
| (...skipping 383 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 394 logging.exception('Had difficulties removing out_dir %s: %s', out_dir, e) | 394 logging.exception('Had difficulties removing out_dir %s: %s', out_dir, e) |
| 395 stats = { | 395 stats = { |
| 396 'duration': time.time() - start, | 396 'duration': time.time() - start, |
| 397 'items_cold': base64.b64encode(large.pack(cold)), | 397 'items_cold': base64.b64encode(large.pack(cold)), |
| 398 'items_hot': base64.b64encode(large.pack(hot)), | 398 'items_hot': base64.b64encode(large.pack(hot)), |
| 399 } | 399 } |
| 400 return outputs_ref, success, stats | 400 return outputs_ref, success, stats |
| 401 | 401 |
| 402 | 402 |
| 403 def map_and_run( | 403 def map_and_run( |
| 404 command, isolated_hash, storage, isolate_cache, outputs, init_named_caches, | 404 command, isolated_hash, storage, isolate_cache, outputs, |
| 405 leak_temp_dir, root_dir, hard_timeout, grace_period, bot_file, | 405 install_named_caches, leak_temp_dir, root_dir, hard_timeout, grace_period, |
| 406 install_packages_fn, use_symlinks, constant_run_path): | 406 bot_file, install_packages_fn, use_symlinks, constant_run_path): |
| 407 """Runs a command with optional isolated input/output. | 407 """Runs a command with optional isolated input/output. |
| 408 | 408 |
| 409 See run_tha_test for argument documentation. | 409 See run_tha_test for argument documentation. |
| 410 | 410 |
| 411 Returns metadata about the result. | 411 Returns metadata about the result. |
| 412 """ | 412 """ |
| 413 assert isinstance(command, list), command | 413 assert isinstance(command, list), command |
| 414 assert root_dir or root_dir is None | 414 assert root_dir or root_dir is None |
| 415 result = { | 415 result = { |
| 416 'duration': None, | 416 'duration': None, |
| (...skipping 80 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 497 | 497 |
| 498 # If we have an explicit list of files to return, make sure their | 498 # If we have an explicit list of files to return, make sure their |
| 499 # directories exist now. | 499 # directories exist now. |
| 500 if storage and outputs: | 500 if storage and outputs: |
| 501 isolateserver.create_directories(run_dir, outputs) | 501 isolateserver.create_directories(run_dir, outputs) |
| 502 | 502 |
| 503 command = tools.fix_python_path(command) | 503 command = tools.fix_python_path(command) |
| 504 command = process_command(command, out_dir, bot_file) | 504 command = process_command(command, out_dir, bot_file) |
| 505 file_path.ensure_command_has_abs_path(command, cwd) | 505 file_path.ensure_command_has_abs_path(command, cwd) |
| 506 | 506 |
| 507 with init_named_caches(run_dir): | 507 with install_named_caches(run_dir): |
| 508 sys.stdout.flush() | 508 sys.stdout.flush() |
| 509 start = time.time() | 509 start = time.time() |
| 510 try: | 510 try: |
| 511 result['exit_code'], result['had_hard_timeout'] = run_command( | 511 result['exit_code'], result['had_hard_timeout'] = run_command( |
| 512 command, cwd, get_command_env(tmp_dir, cipd_info), | 512 command, cwd, get_command_env(tmp_dir, cipd_info), |
| 513 hard_timeout, grace_period) | 513 hard_timeout, grace_period) |
| 514 finally: | 514 finally: |
| 515 result['duration'] = max(time.time() - start, 0) | 515 result['duration'] = max(time.time() - start, 0) |
| 516 except Exception as e: | 516 except Exception as e: |
| 517 # An internal error occurred. Report accordingly so the swarming task will | 517 # An internal error occurred. Report accordingly so the swarming task will |
| (...skipping 58 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 576 result['exit_code'] = 1 | 576 result['exit_code'] = 1 |
| 577 except Exception as e: | 577 except Exception as e: |
| 578 # Swallow any exception in the main finally clause. | 578 # Swallow any exception in the main finally clause. |
| 579 if out_dir: | 579 if out_dir: |
| 580 logging.exception('Leaking out_dir %s: %s', out_dir, e) | 580 logging.exception('Leaking out_dir %s: %s', out_dir, e) |
| 581 result['internal_failure'] = str(e) | 581 result['internal_failure'] = str(e) |
| 582 return result | 582 return result |
| 583 | 583 |
| 584 | 584 |
| 585 def run_tha_test( | 585 def run_tha_test( |
| 586 command, isolated_hash, storage, isolate_cache, outputs, init_named_caches, | 586 command, isolated_hash, storage, isolate_cache, outputs, |
| 587 leak_temp_dir, result_json, root_dir, hard_timeout, grace_period, bot_file, | 587 install_named_caches, leak_temp_dir, result_json, root_dir, hard_timeout, |
| 588 install_packages_fn, use_symlinks): | 588 grace_period, bot_file, install_packages_fn, use_symlinks): |
| 589 """Runs an executable and records execution metadata. | 589 """Runs an executable and records execution metadata. |
| 590 | 590 |
| 591 Either command or isolated_hash must be specified. | 591 Either command or isolated_hash must be specified. |
| 592 | 592 |
| 593 If isolated_hash is specified, downloads the dependencies in the cache, | 593 If isolated_hash is specified, downloads the dependencies in the cache, |
| 594 hardlinks them into a temporary directory and runs the command specified in | 594 hardlinks them into a temporary directory and runs the command specified in |
| 595 the .isolated. | 595 the .isolated. |
| 596 | 596 |
| 597 A temporary directory is created to hold the output files. The content inside | 597 A temporary directory is created to hold the output files. The content inside |
| 598 this directory will be uploaded back to |storage| packaged as a .isolated | 598 this directory will be uploaded back to |storage| packaged as a .isolated |
| 599 file. | 599 file. |
| 600 | 600 |
| 601 Arguments: | 601 Arguments: |
| 602 command: a list of string; the command to run OR optional arguments to add | 602 command: a list of string; the command to run OR optional arguments to add |
| 603 to the command stated in the .isolated file if a command was | 603 to the command stated in the .isolated file if a command was |
| 604 specified. | 604 specified. |
| 605 isolated_hash: the SHA-1 of the .isolated file that must be retrieved to | 605 isolated_hash: the SHA-1 of the .isolated file that must be retrieved to |
| 606 recreate the tree of files to run the target executable. | 606 recreate the tree of files to run the target executable. |
| 607 The command specified in the .isolated is executed. | 607 The command specified in the .isolated is executed. |
| 608 Mutually exclusive with command argument. | 608 Mutually exclusive with command argument. |
| 609 storage: an isolateserver.Storage object to retrieve remote objects. This | 609 storage: an isolateserver.Storage object to retrieve remote objects. This |
| 610 object has a reference to an isolateserver.StorageApi, which does | 610 object has a reference to an isolateserver.StorageApi, which does |
| 611 the actual I/O. | 611 the actual I/O. |
| 612 isolate_cache: an isolateserver.LocalCache to keep from retrieving the | 612 isolate_cache: an isolateserver.LocalCache to keep from retrieving the |
| 613 same objects constantly by caching the objects retrieved. | 613 same objects constantly by caching the objects retrieved. |
| 614 Can be on-disk or in-memory. | 614 Can be on-disk or in-memory. |
| 615 init_named_caches: a function (run_dir) => context manager that creates | 615 install_named_caches: a function (run_dir) => context manager that installs |
| 616 symlinks for named caches in |run_dir|. | 616 named caches into |run_dir|. |
| 617 leak_temp_dir: if true, the temporary directory will be deliberately leaked | 617 leak_temp_dir: if true, the temporary directory will be deliberately leaked |
| 618 for later examination. | 618 for later examination. |
| 619 result_json: file path to dump result metadata into. If set, the process | 619 result_json: file path to dump result metadata into. If set, the process |
| 620 exit code is always 0 unless an internal error occurred. | 620 exit code is always 0 unless an internal error occurred. |
| 621 root_dir: path to the directory to use to create the temporary directory. If | 621 root_dir: path to the directory to use to create the temporary directory. If |
| 622 not specified, a random temporary directory is created. | 622 not specified, a random temporary directory is created. |
| 623 hard_timeout: kills the process if it lasts more than this amount of | 623 hard_timeout: kills the process if it lasts more than this amount of |
| 624 seconds. | 624 seconds. |
| 625 grace_period: number of seconds to wait between SIGTERM and SIGKILL. | 625 grace_period: number of seconds to wait between SIGTERM and SIGKILL. |
| 626 install_packages_fn: context manager dir => CipdInfo, see | 626 install_packages_fn: context manager dir => CipdInfo, see |
| (...skipping 10 matching lines...) Expand all Loading... |
| 637 'had_hard_timeout': False, | 637 'had_hard_timeout': False, |
| 638 'internal_failure': 'Was terminated before completion', | 638 'internal_failure': 'Was terminated before completion', |
| 639 'outputs_ref': None, | 639 'outputs_ref': None, |
| 640 'version': 5, | 640 'version': 5, |
| 641 } | 641 } |
| 642 tools.write_json(result_json, result, dense=True) | 642 tools.write_json(result_json, result, dense=True) |
| 643 | 643 |
| 644 # run_isolated exit code. Depends on if result_json is used or not. | 644 # run_isolated exit code. Depends on if result_json is used or not. |
| 645 result = map_and_run( | 645 result = map_and_run( |
| 646 command, isolated_hash, storage, isolate_cache, outputs, | 646 command, isolated_hash, storage, isolate_cache, outputs, |
| 647 init_named_caches, leak_temp_dir, root_dir, hard_timeout, grace_period, | 647 install_named_caches, leak_temp_dir, root_dir, hard_timeout, grace_period, |
| 648 bot_file, install_packages_fn, use_symlinks, True) | 648 bot_file, install_packages_fn, use_symlinks, True) |
| 649 logging.info('Result:\n%s', tools.format_json(result, dense=True)) | 649 logging.info('Result:\n%s', tools.format_json(result, dense=True)) |
| 650 | 650 |
| 651 if result_json: | 651 if result_json: |
| 652 # We've found tests to delete 'work' when quitting, causing an exception | 652 # We've found tests to delete 'work' when quitting, causing an exception |
| 653 # here. Try to recreate the directory if necessary. | 653 # here. Try to recreate the directory if necessary. |
| 654 file_path.ensure_tree(os.path.dirname(result_json)) | 654 file_path.ensure_tree(os.path.dirname(result_json)) |
| 655 tools.write_json(result_json, result, dense=True) | 655 tools.write_json(result_json, result, dense=True) |
| 656 # Only return 1 if there was an internal error. | 656 # Only return 1 if there was an internal error. |
| 657 return int(bool(result['internal_failure'])) | 657 return int(bool(result['internal_failure'])) |
| (...skipping 343 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1001 cipd.validate_cipd_options(parser, options) | 1001 cipd.validate_cipd_options(parser, options) |
| 1002 | 1002 |
| 1003 install_packages_fn = noop_install_packages | 1003 install_packages_fn = noop_install_packages |
| 1004 if options.cipd_enabled: | 1004 if options.cipd_enabled: |
| 1005 install_packages_fn = lambda run_dir: install_client_and_packages( | 1005 install_packages_fn = lambda run_dir: install_client_and_packages( |
| 1006 run_dir, cipd.parse_package_args(options.cipd_packages), | 1006 run_dir, cipd.parse_package_args(options.cipd_packages), |
| 1007 options.cipd_server, options.cipd_client_package, | 1007 options.cipd_server, options.cipd_client_package, |
| 1008 options.cipd_client_version, cache_dir=options.cipd_cache) | 1008 options.cipd_client_version, cache_dir=options.cipd_cache) |
| 1009 | 1009 |
| 1010 @contextlib.contextmanager | 1010 @contextlib.contextmanager |
| 1011 def init_named_caches(run_dir): | 1011 def install_named_caches(run_dir): |
| 1012 # WARNING: this function depends on "options" variable defined in the outer | 1012 # WARNING: this function depends on "options" variable defined in the outer |
| 1013 # function. | 1013 # function. |
| 1014 caches = [ |
| 1015 (os.path.join(run_dir, unicode(relpath)), name) |
| 1016 for name, relpath in options.named_caches |
| 1017 ] |
| 1014 with named_cache_manager.open(): | 1018 with named_cache_manager.open(): |
| 1015 named_cache_manager.create_symlinks(run_dir, options.named_caches) | 1019 for path, name in caches: |
| 1020 named_cache_manager.install(path, name) |
| 1016 try: | 1021 try: |
| 1017 yield | 1022 yield |
| 1018 finally: | 1023 finally: |
| 1019 if not options.leak_temp_dir: | 1024 with named_cache_manager.open(): |
| 1020 named_cache_manager.delete_symlinks(run_dir, options.named_caches) | 1025 for path, name in caches: |
| 1026 named_cache_manager.uninstall(path, name) |
| 1021 | 1027 |
| 1022 try: | 1028 try: |
| 1023 if options.isolate_server: | 1029 if options.isolate_server: |
| 1024 storage = isolateserver.get_storage( | 1030 storage = isolateserver.get_storage( |
| 1025 options.isolate_server, options.namespace) | 1031 options.isolate_server, options.namespace) |
| 1026 with storage: | 1032 with storage: |
| 1027 # Hashing schemes used by |storage| and |isolate_cache| MUST match. | 1033 # Hashing schemes used by |storage| and |isolate_cache| MUST match. |
| 1028 assert storage.hash_algo == isolate_cache.hash_algo | 1034 assert storage.hash_algo == isolate_cache.hash_algo |
| 1029 return run_tha_test( | 1035 return run_tha_test( |
| 1030 args, | 1036 args, |
| 1031 options.isolated, | 1037 options.isolated, |
| 1032 storage, | 1038 storage, |
| 1033 isolate_cache, | 1039 isolate_cache, |
| 1034 options.output, | 1040 options.output, |
| 1035 init_named_caches, | 1041 install_named_caches, |
| 1036 options.leak_temp_dir, | 1042 options.leak_temp_dir, |
| 1037 options.json, options.root_dir, | 1043 options.json, options.root_dir, |
| 1038 options.hard_timeout, | 1044 options.hard_timeout, |
| 1039 options.grace_period, | 1045 options.grace_period, |
| 1040 options.bot_file, | 1046 options.bot_file, |
| 1041 install_packages_fn, | 1047 install_packages_fn, |
| 1042 options.use_symlinks) | 1048 options.use_symlinks) |
| 1043 return run_tha_test( | 1049 return run_tha_test( |
| 1044 args, | 1050 args, |
| 1045 options.isolated, | 1051 options.isolated, |
| 1046 None, | 1052 None, |
| 1047 isolate_cache, | 1053 isolate_cache, |
| 1048 options.output, | 1054 options.output, |
| 1049 init_named_caches, | 1055 install_named_caches, |
| 1050 options.leak_temp_dir, | 1056 options.leak_temp_dir, |
| 1051 options.json, | 1057 options.json, |
| 1052 options.root_dir, | 1058 options.root_dir, |
| 1053 options.hard_timeout, | 1059 options.hard_timeout, |
| 1054 options.grace_period, | 1060 options.grace_period, |
| 1055 options.bot_file, | 1061 options.bot_file, |
| 1056 install_packages_fn, | 1062 install_packages_fn, |
| 1057 options.use_symlinks) | 1063 options.use_symlinks) |
| 1058 except (cipd.Error, named_cache.Error) as ex: | 1064 except (cipd.Error, named_cache.Error) as ex: |
| 1059 print >> sys.stderr, ex.message | 1065 print >> sys.stderr, ex.message |
| 1060 return 1 | 1066 return 1 |
| 1061 | 1067 |
| 1062 | 1068 |
| 1063 if __name__ == '__main__': | 1069 if __name__ == '__main__': |
| 1064 subprocess42.inhibit_os_error_reporting() | 1070 subprocess42.inhibit_os_error_reporting() |
| 1065 # Ensure that we are always running with the correct encoding. | 1071 # Ensure that we are always running with the correct encoding. |
| 1066 fix_encoding.fix_encoding() | 1072 fix_encoding.fix_encoding() |
| 1067 file_path.enable_symlink() | 1073 file_path.enable_symlink() |
| 1068 | 1074 |
| 1069 sys.exit(main(sys.argv[1:])) | 1075 sys.exit(main(sys.argv[1:])) |
| OLD | NEW |