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 |