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 |
11 If input isolated hash is provided, fetches it, creates a tree of hard links, | 11 If input isolated hash is provided, fetches it, creates a tree of hard links, |
12 appends args to the command in the fetched isolated and runs it. | 12 appends args to the command in the fetched isolated and runs it. |
13 To improve performance, keeps a local cache. | 13 To improve performance, keeps a local cache. |
14 The local cache can safely be deleted. | 14 The local cache can safely be deleted. |
15 | 15 |
16 Any ${EXECUTABLE_SUFFIX} on the command line will be replaced with ".exe" string | 16 Any ${EXECUTABLE_SUFFIX} on the command line will be replaced with ".exe" string |
17 on Windows and "" on other platforms. | 17 on Windows and "" on other platforms. |
18 | 18 |
19 Any ${ISOLATED_OUTDIR} on the command line will be replaced by the location of a | 19 Any ${ISOLATED_OUTDIR} on the command line will be replaced by the location of a |
20 temporary directory upon execution of the command specified in the .isolated | 20 temporary directory upon execution of the command specified in the .isolated |
21 file. All content written to this directory will be uploaded upon termination | 21 file. All content written to this directory will be uploaded upon termination |
22 and the .isolated file describing this directory will be printed to stdout. | 22 and the .isolated file describing this directory will be printed to stdout. |
23 | 23 |
24 Any ${SWARMING_BOT_FILE} on the command line will be replaced by the value of | 24 Any ${SWARMING_BOT_FILE} on the command line will be replaced by the value of |
25 the --bot-file parameter. This file is used by a swarming bot to communicate | 25 the --bot-file parameter. This file is used by a swarming bot to communicate |
26 state of the host to tasks. It is written to by the swarming bot's | 26 state of the host to tasks. It is written to by the swarming bot's |
27 on_before_task() hook in the swarming server's custom bot_config.py. | 27 on_before_task() hook in the swarming server's custom bot_config.py. |
28 """ | 28 """ |
29 | 29 |
30 __version__ = '0.8.4' | 30 __version__ = '0.8.4' |
M-A Ruel
2016/08/24 02:40:23
bump too
iannucci
2016/08/24 16:01:11
done
Can we make a PRESUBMIT hook for these?
M-A Ruel
2016/08/24 19:34:07
Please upload again.
Also please test it on stagi
| |
31 | 31 |
32 import base64 | 32 import base64 |
33 import logging | 33 import logging |
34 import optparse | 34 import optparse |
35 import os | 35 import os |
36 import sys | 36 import sys |
37 import tempfile | 37 import tempfile |
38 import time | 38 import time |
39 | 39 |
40 from third_party.depot_tools import fix_encoding | 40 from third_party.depot_tools import fix_encoding |
(...skipping 352 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
393 root_dir = os.path.dirname(cache.cache_dir) if cache.cache_dir else None | 393 root_dir = os.path.dirname(cache.cache_dir) if cache.cache_dir else None |
394 # See comment for these constants. | 394 # See comment for these constants. |
395 run_dir = make_temp_dir(ISOLATED_RUN_DIR, root_dir) | 395 run_dir = make_temp_dir(ISOLATED_RUN_DIR, root_dir) |
396 # storage should be normally set but don't crash if it is not. This can happen | 396 # storage should be normally set but don't crash if it is not. This can happen |
397 # as Swarming task can run without an isolate server. | 397 # as Swarming task can run without an isolate server. |
398 out_dir = make_temp_dir(ISOLATED_OUT_DIR, root_dir) if storage else None | 398 out_dir = make_temp_dir(ISOLATED_OUT_DIR, root_dir) if storage else None |
399 tmp_dir = make_temp_dir(ISOLATED_TMP_DIR, root_dir) | 399 tmp_dir = make_temp_dir(ISOLATED_TMP_DIR, root_dir) |
400 cwd = run_dir | 400 cwd = run_dir |
401 | 401 |
402 try: | 402 try: |
403 cipd_stats = install_packages_fn(run_dir) | 403 cipd_info = install_packages_fn(run_dir) |
404 if cipd_stats: | 404 if cipd_info: |
405 result['stats']['cipd'] = cipd_stats | 405 result['stats']['cipd'] = cipd_info['stats'] |
406 result['cipd_pins'] = cipd_info['pins'] | |
406 | 407 |
407 if isolated_hash: | 408 if isolated_hash: |
408 isolated_stats = result['stats'].setdefault('isolated', {}) | 409 isolated_stats = result['stats'].setdefault('isolated', {}) |
409 bundle, isolated_stats['download'] = fetch_and_map( | 410 bundle, isolated_stats['download'] = fetch_and_map( |
410 isolated_hash=isolated_hash, | 411 isolated_hash=isolated_hash, |
411 storage=storage, | 412 storage=storage, |
412 cache=cache, | 413 cache=cache, |
413 outdir=run_dir, | 414 outdir=run_dir, |
414 use_symlinks=use_symlinks) | 415 use_symlinks=use_symlinks) |
415 if not bundle.command: | 416 if not bundle.command: |
(...skipping 116 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
532 for later examination. | 533 for later examination. |
533 result_json: file path to dump result metadata into. If set, the process | 534 result_json: file path to dump result metadata into. If set, the process |
534 exit code is always 0 unless an internal error occurred. | 535 exit code is always 0 unless an internal error occurred. |
535 root_dir: path to the directory to use to create the temporary directory. If | 536 root_dir: path to the directory to use to create the temporary directory. If |
536 not specified, a random temporary directory is created. | 537 not specified, a random temporary directory is created. |
537 hard_timeout: kills the process if it lasts more than this amount of | 538 hard_timeout: kills the process if it lasts more than this amount of |
538 seconds. | 539 seconds. |
539 grace_period: number of seconds to wait between SIGTERM and SIGKILL. | 540 grace_period: number of seconds to wait between SIGTERM and SIGKILL. |
540 extra_args: optional arguments to add to the command stated in the .isolate | 541 extra_args: optional arguments to add to the command stated in the .isolate |
541 file. Ignored if isolate_hash is empty. | 542 file. Ignored if isolate_hash is empty. |
542 install_packages_fn: function (dir) => cipd_stats. Installs packages. | 543 install_packages_fn: function (dir) => {"stats": cipd_stats, "pins": |
544 cipd_pins}. Installs packages. | |
543 use_symlinks: create tree with symlinks instead of hardlinks. | 545 use_symlinks: create tree with symlinks instead of hardlinks. |
544 | 546 |
545 Returns: | 547 Returns: |
546 Process exit code that should be used. | 548 Process exit code that should be used. |
547 """ | 549 """ |
548 assert bool(command) ^ bool(isolated_hash) | 550 assert bool(command) ^ bool(isolated_hash) |
549 extra_args = extra_args or [] | 551 extra_args = extra_args or [] |
550 | 552 |
551 if any(ISOLATED_OUTDIR_PARAMETER in a for a in (command or extra_args)): | 553 if any(ISOLATED_OUTDIR_PARAMETER in a for a in (command or extra_args)): |
552 assert storage is not None, 'storage is None although outdir is specified' | 554 assert storage is not None, 'storage is None although outdir is specified' |
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
588 print( | 590 print( |
589 '[run_isolated_out_hack]%s[/run_isolated_out_hack]' % | 591 '[run_isolated_out_hack]%s[/run_isolated_out_hack]' % |
590 tools.format_json(data, dense=True)) | 592 tools.format_json(data, dense=True)) |
591 sys.stdout.flush() | 593 sys.stdout.flush() |
592 return result['exit_code'] or int(bool(result['internal_failure'])) | 594 return result['exit_code'] or int(bool(result['internal_failure'])) |
593 | 595 |
594 | 596 |
595 def install_packages( | 597 def install_packages( |
596 run_dir, packages, service_url, client_package_name, | 598 run_dir, packages, service_url, client_package_name, |
597 client_version, cache_dir=None, timeout=None): | 599 client_version, cache_dir=None, timeout=None): |
598 """Installs packages. Returns stats. | 600 """Installs packages. Returns stats and pins. |
601 | |
602 pins are in the form of: | |
603 [ | |
604 { | |
605 "path": path, "package_name": package_name, "version": version, | |
606 }, | |
607 ... | |
608 ] | |
609 | |
610 such that they correspond 1:1 to all input package arguments from the command | |
611 line. These dictionaries make their all the way back to swarming, where they | |
612 become the arguments of CipdPackage. | |
599 | 613 |
600 Args: | 614 Args: |
601 run_dir (str): root of installation. | 615 run_dir (str): root of installation. |
602 packages: packages to install, dict {path: [(package_name, version)]. | 616 packages: packages to install, dict |
617 {path: [(package_name, version, cmd_line_idx)]. | |
603 service_url (str): CIPD server url, e.g. | 618 service_url (str): CIPD server url, e.g. |
604 "https://chrome-infra-packages.appspot.com." | 619 "https://chrome-infra-packages.appspot.com." |
605 client_package_name (str): CIPD package name of CIPD client. | 620 client_package_name (str): CIPD package name of CIPD client. |
606 client_version (str): Version of CIPD client. | 621 client_version (str): Version of CIPD client. |
607 cache_dir (str): where to keep cache of cipd clients, packages and tags. | 622 cache_dir (str): where to keep cache of cipd clients, packages and tags. |
608 timeout: max duration in seconds that this function can take. | 623 timeout: max duration in seconds that this function can take. |
609 """ | 624 """ |
610 assert cache_dir | 625 assert cache_dir |
611 if not packages: | 626 if not packages: |
612 return None | 627 return None |
613 | 628 |
614 timeoutfn = tools.sliding_timeout(timeout) | 629 timeoutfn = tools.sliding_timeout(timeout) |
615 start = time.time() | 630 start = time.time() |
616 cache_dir = os.path.abspath(cache_dir) | 631 cache_dir = os.path.abspath(cache_dir) |
617 | 632 |
618 run_dir = os.path.abspath(run_dir) | 633 run_dir = os.path.abspath(run_dir) |
619 | 634 |
635 all_pins = [] | |
636 def insert_pin(path, name, version, idx): | |
637 if idx > len(all_pins)-1: | |
638 all_pins.extend([None] * (len(all_pins) - idx + 1)) | |
639 all_pins[idx] = { | |
640 'package_name': name, | |
641 'version': version, | |
642 'path': path, | |
643 } | |
644 | |
620 get_client_start = time.time() | 645 get_client_start = time.time() |
621 client_manager = cipd.get_client( | 646 client_manager = cipd.get_client( |
622 service_url, client_package_name, client_version, cache_dir, | 647 service_url, client_package_name, client_version, cache_dir, |
623 timeout=timeoutfn()) | 648 timeout=timeoutfn()) |
624 with client_manager as client: | 649 with client_manager as client: |
625 get_client_duration = time.time() - get_client_start | 650 get_client_duration = time.time() - get_client_start |
626 for path, packages in sorted(packages.iteritems()): | 651 for path, packages in sorted(packages.iteritems()): |
627 site_root = os.path.abspath(os.path.join(run_dir, path)) | 652 site_root = os.path.abspath(os.path.join(run_dir, path)) |
628 if not site_root.startswith(run_dir): | 653 if not site_root.startswith(run_dir): |
629 raise cipd.Error('Invalid CIPD package path "%s"' % path) | 654 raise cipd.Error('Invalid CIPD package path "%s"' % path) |
630 | 655 |
631 # Do not clean site_root before installation because it may contain other | 656 # Do not clean site_root before installation because it may contain other |
632 # site roots. | 657 # site roots. |
633 file_path.ensure_tree(site_root, 0770) | 658 file_path.ensure_tree(site_root, 0770) |
634 client.ensure( | 659 pins = client.ensure( |
635 site_root, packages, | 660 site_root, [(name, vers) for name, vers, _ in packages], |
636 cache_dir=os.path.join(cache_dir, 'cipd_internal'), | 661 cache_dir=os.path.join(cache_dir, 'cipd_internal'), |
637 timeout=timeoutfn()) | 662 timeout=timeoutfn()) |
663 for i, pin in enumerate(pins): | |
664 insert_pin(path, pin[0], pin[1], packages[i]) | |
638 file_path.make_tree_files_read_only(site_root) | 665 file_path.make_tree_files_read_only(site_root) |
639 | 666 |
640 total_duration = time.time() - start | 667 total_duration = time.time() - start |
641 logging.info( | 668 logging.info( |
642 'Installing CIPD client and packages took %d seconds', total_duration) | 669 'Installing CIPD client and packages took %d seconds', total_duration) |
643 | 670 |
671 assert None not in all_pins | |
672 | |
644 return { | 673 return { |
645 'duration': total_duration, | 674 'stats': { |
646 'get_client_duration': get_client_duration, | 675 'duration': total_duration, |
676 'get_client_duration': get_client_duration, | |
677 }, | |
678 'pins': all_pins, | |
647 } | 679 } |
648 | 680 |
649 | 681 |
650 def create_option_parser(): | 682 def create_option_parser(): |
651 parser = logging_utils.OptionParserWithLogging( | 683 parser = logging_utils.OptionParserWithLogging( |
652 usage='%prog <options> [command to run or extra args]', | 684 usage='%prog <options> [command to run or extra args]', |
653 version=__version__, | 685 version=__version__, |
654 log_file=RUN_ISOLATED_LOG_FILE) | 686 log_file=RUN_ISOLATED_LOG_FILE) |
655 parser.add_option( | 687 parser.add_option( |
656 '--clean', action='store_true', | 688 '--clean', action='store_true', |
(...skipping 80 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
737 '%s in args requires --isolate-server' % ISOLATED_OUTDIR_PARAMETER) | 769 '%s in args requires --isolate-server' % ISOLATED_OUTDIR_PARAMETER) |
738 | 770 |
739 if options.root_dir: | 771 if options.root_dir: |
740 options.root_dir = unicode(os.path.abspath(options.root_dir)) | 772 options.root_dir = unicode(os.path.abspath(options.root_dir)) |
741 if options.json: | 773 if options.json: |
742 options.json = unicode(os.path.abspath(options.json)) | 774 options.json = unicode(os.path.abspath(options.json)) |
743 | 775 |
744 cipd.validate_cipd_options(parser, options) | 776 cipd.validate_cipd_options(parser, options) |
745 | 777 |
746 install_packages_fn = lambda run_dir: install_packages( | 778 install_packages_fn = lambda run_dir: install_packages( |
747 run_dir, cipd.parse_package_args(options.cipd_packages), | 779 run_dir, cipd.parse_package_args(options.cipd_packages, with_index=True), |
748 options.cipd_server, options.cipd_client_package, | 780 options.cipd_server, options.cipd_client_package, |
749 options.cipd_client_version, cache_dir=options.cipd_cache) | 781 options.cipd_client_version, cache_dir=options.cipd_cache) |
750 | 782 |
751 try: | 783 try: |
752 command = [] if options.isolated else args | 784 command = [] if options.isolated else args |
753 if options.isolate_server: | 785 if options.isolate_server: |
754 storage = isolateserver.get_storage( | 786 storage = isolateserver.get_storage( |
755 options.isolate_server, options.namespace) | 787 options.isolate_server, options.namespace) |
756 with storage: | 788 with storage: |
757 # Hashing schemes used by |storage| and |cache| MUST match. | 789 # Hashing schemes used by |storage| and |cache| MUST match. |
(...skipping 12 matching lines...) Expand all Loading... | |
770 print >> sys.stderr, ex.message | 802 print >> sys.stderr, ex.message |
771 return 1 | 803 return 1 |
772 | 804 |
773 | 805 |
774 if __name__ == '__main__': | 806 if __name__ == '__main__': |
775 subprocess42.inhibit_os_error_reporting() | 807 subprocess42.inhibit_os_error_reporting() |
776 # Ensure that we are always running with the correct encoding. | 808 # Ensure that we are always running with the correct encoding. |
777 fix_encoding.fix_encoding() | 809 fix_encoding.fix_encoding() |
778 file_path.enable_symlink() | 810 file_path.enable_symlink() |
779 sys.exit(main(sys.argv[1:])) | 811 sys.exit(main(sys.argv[1:])) |
OLD | NEW |