Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(202)

Side by Side Diff: client/run_isolated.py

Issue 2847153002: Cache/retrieve extracted CIPD packages in local isolate cache (Closed)
Patch Set: Address more comments Created 3 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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 75 matching lines...) Expand 10 before | Expand all | Expand 10 after
86 RUN_ISOLATED_LOG_FILE = 'run_isolated.log' 86 RUN_ISOLATED_LOG_FILE = 'run_isolated.log'
87 87
88 88
89 # The name of the log to use for the run_test_cases.py command 89 # The name of the log to use for the run_test_cases.py command
90 RUN_TEST_CASES_LOG = 'run_test_cases.log' 90 RUN_TEST_CASES_LOG = 'run_test_cases.log'
91 91
92 92
93 # Use short names for temporary directories. This is driven by Windows, which 93 # Use short names for temporary directories. This is driven by Windows, which
94 # imposes a relatively short maximum path length of 260 characters, often 94 # imposes a relatively short maximum path length of 260 characters, often
95 # referred to as MAX_PATH. It is relatively easy to create files with longer 95 # referred to as MAX_PATH. It is relatively easy to create files with longer
96 # path length. A use case is with recursive depedency treesV like npm packages. 96 # path length. A use case is with recursive dependency trees like npm packages.
97 # 97 #
98 # It is recommended to start the script with a `root_dir` as short as 98 # It is recommended to start the script with a `root_dir` as short as
99 # possible. 99 # possible.
100 # - ir stands for isolated_run 100 # - ir stands for isolated_run
101 # - io stands for isolated_out 101 # - io stands for isolated_out
102 # - it stands for isolated_tmp 102 # - it stands for isolated_tmp
103 ISOLATED_RUN_DIR = u'ir' 103 ISOLATED_RUN_DIR = u'ir'
104 ISOLATED_OUT_DIR = u'io' 104 ISOLATED_OUT_DIR = u'io'
105 ISOLATED_TMP_DIR = u'it' 105 ISOLATED_TMP_DIR = u'it'
106 106
107 107
108 def get_as_zip_package(executable=True): 108 def get_as_zip_package(executable=True):
109 """Returns ZipPackage with this module and all its dependencies. 109 """Returns ZipPackage with this module and all its dependencies.
110 110
111 If |executable| is True will store run_isolated.py as __main__.py so that 111 If |executable| is True will store run_isolated.py as __main__.py so that
112 zip package is directly executable be python. 112 zip package is directly executable be python.
113 """ 113 """
114 # Building a zip package when running from another zip package is 114 # Building a zip package when running from another zip package is
115 # unsupported and probably unneeded. 115 # unsupported and probably unneeded.
116 assert not zip_package.is_zipped_module(sys.modules[__name__]) 116 assert not zip_package.is_zipped_module(sys.modules[__name__])
117 assert THIS_FILE_PATH 117 assert THIS_FILE_PATH
118 assert BASE_DIR 118 assert BASE_DIR
119 package = zip_package.ZipPackage(root=BASE_DIR) 119 package = zip_package.ZipPackage(root=BASE_DIR)
120 package.add_python_file(THIS_FILE_PATH, '__main__.py' if executable else None) 120 package.add_python_file(THIS_FILE_PATH, '__main__.py' if executable else None)
121 package.add_python_file(os.path.join(BASE_DIR, 'isolate_storage.py')) 121 package.add_python_file(os.path.join(BASE_DIR, 'isolate_storage.py'))
122 package.add_python_file(os.path.join(BASE_DIR, 'isolated_format.py')) 122 package.add_python_file(os.path.join(BASE_DIR, 'isolated_format.py'))
123 package.add_python_file(os.path.join(BASE_DIR, 'isolateserver.py')) 123 package.add_python_file(os.path.join(BASE_DIR, 'isolateserver.py'))
124 package.add_python_file(os.path.join(BASE_DIR, 'isolate_format.py'))
124 package.add_python_file(os.path.join(BASE_DIR, 'auth.py')) 125 package.add_python_file(os.path.join(BASE_DIR, 'auth.py'))
125 package.add_python_file(os.path.join(BASE_DIR, 'cipd.py')) 126 package.add_python_file(os.path.join(BASE_DIR, 'cipd.py'))
126 package.add_python_file(os.path.join(BASE_DIR, 'named_cache.py')) 127 package.add_python_file(os.path.join(BASE_DIR, 'named_cache.py'))
128 package.add_python_file(os.path.join(BASE_DIR, 'run_isolated.py'))
Vadim Sh. 2017/05/11 03:09:09 note that this file may already be included in the
127 package.add_directory(os.path.join(BASE_DIR, 'libs')) 129 package.add_directory(os.path.join(BASE_DIR, 'libs'))
128 package.add_directory(os.path.join(BASE_DIR, 'third_party')) 130 package.add_directory(os.path.join(BASE_DIR, 'third_party'))
129 package.add_directory(os.path.join(BASE_DIR, 'utils')) 131 package.add_directory(os.path.join(BASE_DIR, 'utils'))
130 return package 132 return package
131 133
132 134
133 def make_temp_dir(prefix, root_dir): 135 def make_temp_dir(prefix, root_dir):
134 """Returns a new unique temporary directory.""" 136 """Returns a new unique temporary directory."""
135 return unicode(tempfile.mkdtemp(prefix=prefix, dir=root_dir)) 137 return unicode(tempfile.mkdtemp(prefix=prefix, dir=root_dir))
136 138
(...skipping 138 matching lines...) Expand 10 before | Expand all | Expand 10 after
275 exit_code = proc.wait(grace_period or None) 277 exit_code = proc.wait(grace_period or None)
276 except subprocess42.TimeoutExpired: 278 except subprocess42.TimeoutExpired:
277 # Now kill for real. The user can distinguish between the 279 # Now kill for real. The user can distinguish between the
278 # following states: 280 # following states:
279 # - signal but process exited within grace period, 281 # - signal but process exited within grace period,
280 # hard_timed_out will be set but the process exit code will be 282 # hard_timed_out will be set but the process exit code will be
281 # script provided. 283 # script provided.
282 # - processed exited late, exit code will be -9 on posix. 284 # - processed exited late, exit code will be -9 on posix.
283 logging.warning('Grace exhausted; sending SIGKILL') 285 logging.warning('Grace exhausted; sending SIGKILL')
284 proc.kill() 286 proc.kill()
285 logging.info('Waiting for proces exit') 287 logging.info('Waiting for process exit')
286 exit_code = proc.wait() 288 exit_code = proc.wait()
287 except OSError: 289 except OSError:
288 # This is not considered to be an internal error. The executable simply 290 # This is not considered to be an internal error. The executable simply
289 # does not exit. 291 # does not exit.
290 sys.stderr.write( 292 sys.stderr.write(
291 '<The executable does not exist or a dependent library is missing>\n' 293 '<The executable does not exist or a dependent library is missing>\n'
292 '<Check for missing .so/.dll in the .isolate or GN file>\n' 294 '<Check for missing .so/.dll in the .isolate or GN file>\n'
293 '<Command: %s>\n' % command) 295 '<Command: %s>\n' % command)
294 if os.environ.get('SWARMING_TASK_ID'): 296 if os.environ.get('SWARMING_TASK_ID'):
295 # Give an additional hint when running as a swarming task. 297 # Give an additional hint when running as a swarming task.
(...skipping 383 matching lines...) Expand 10 before | Expand all | Expand 10 after
679 'pins', # dict with installed cipd pins to return to the server 681 'pins', # dict with installed cipd pins to return to the server
680 ]) 682 ])
681 683
682 684
683 @contextlib.contextmanager 685 @contextlib.contextmanager
684 def noop_install_packages(_run_dir): 686 def noop_install_packages(_run_dir):
685 """Placeholder for 'install_client_and_packages' if cipd is disabled.""" 687 """Placeholder for 'install_client_and_packages' if cipd is disabled."""
686 yield None 688 yield None
687 689
688 690
689 def _install_packages(run_dir, cipd_cache_dir, client, packages, timeout): 691 def _install_packages(run_dir, cipd_cache_dir, client, packages, timeout,
692 isolate_cache=None):
Vadim Sh. 2017/05/11 03:09:09 please document in Args, at least its type (in all
690 """Calls 'cipd ensure' for packages. 693 """Calls 'cipd ensure' for packages.
691 694
692 Args: 695 Args:
693 run_dir (str): root of installation. 696 run_dir (str): root of installation.
694 cipd_cache_dir (str): the directory to use for the cipd package cache. 697 cipd_cache_dir (str): the directory to use for the cipd package cache.
695 client (CipdClient): the cipd client to use 698 client (CipdClient): the cipd client to use
696 packages: packages to install, list [(path, package_name, version), ...]. 699 packages: packages to install, list [(path, package_name, version), ...].
697 timeout: max duration in seconds that this function can take. 700 timeout: max duration in seconds that this function can take.
698 701
699 Returns: list of pinned packages. Looks like [ 702 Returns: list of pinned packages. Looks like [
(...skipping 22 matching lines...) Expand all
722 by_path[path].append((name, version, i)) 725 by_path[path].append((name, version, i))
723 726
724 pins = client.ensure( 727 pins = client.ensure(
725 run_dir, 728 run_dir,
726 { 729 {
727 subdir: [(name, vers) for name, vers, _ in pkgs] 730 subdir: [(name, vers) for name, vers, _ in pkgs]
728 for subdir, pkgs in by_path.iteritems() 731 for subdir, pkgs in by_path.iteritems()
729 }, 732 },
730 cache_dir=cipd_cache_dir, 733 cache_dir=cipd_cache_dir,
731 timeout=timeout, 734 timeout=timeout,
735 isolate_cache=isolate_cache,
732 ) 736 )
733 737
734 for subdir, pin_list in sorted(pins.iteritems()): 738 for subdir, pin_list in sorted(pins.iteritems()):
735 this_subdir = by_path[subdir] 739 this_subdir = by_path[subdir]
736 for i, (name, version) in enumerate(pin_list): 740 for i, (name, version) in enumerate(pin_list):
737 insert_pin(subdir, name, version, this_subdir[i][2]) 741 insert_pin(subdir, name, version, this_subdir[i][2])
738 742
739 assert None not in package_pins 743 assert None not in package_pins
740 744
741 return package_pins 745 return package_pins
742 746
743 747
744 @contextlib.contextmanager 748 @contextlib.contextmanager
745 def install_client_and_packages( 749 def install_client_and_packages(
746 run_dir, packages, service_url, client_package_name, 750 run_dir, packages, service_url, client_package_name,
747 client_version, cache_dir, timeout=None): 751 client_version, cache_dir, timeout=None, isolate_cache=None):
748 """Bootstraps CIPD client and installs CIPD packages. 752 """Bootstraps CIPD client and installs CIPD packages.
749 753
750 Yields CipdClient, stats, client info and pins (as single CipdInfo object). 754 Yields CipdClient, stats, client info and pins (as single CipdInfo object).
751 755
752 Pins and the CIPD client info are in the form of: 756 Pins and the CIPD client info are in the form of:
753 [ 757 [
754 { 758 {
755 "path": path, "package_name": package_name, "version": version, 759 "path": path, "package_name": package_name, "version": version,
756 }, 760 },
757 ... 761 ...
(...skipping 20 matching lines...) Expand all
778 cache_dir (str): where to keep cache of cipd clients, packages and tags. 782 cache_dir (str): where to keep cache of cipd clients, packages and tags.
779 timeout: max duration in seconds that this function can take. 783 timeout: max duration in seconds that this function can take.
780 """ 784 """
781 assert cache_dir 785 assert cache_dir
782 786
783 timeoutfn = tools.sliding_timeout(timeout) 787 timeoutfn = tools.sliding_timeout(timeout)
784 start = time.time() 788 start = time.time()
785 789
786 cache_dir = os.path.abspath(cache_dir) 790 cache_dir = os.path.abspath(cache_dir)
787 cipd_cache_dir = os.path.join(cache_dir, 'cache') # tag and instance caches 791 cipd_cache_dir = os.path.join(cache_dir, 'cache') # tag and instance caches
792 file_path.ensure_tree(unicode(cipd_cache_dir))
793
788 run_dir = os.path.abspath(run_dir) 794 run_dir = os.path.abspath(run_dir)
789 packages = packages or [] 795 packages = packages or []
790 796
791 get_client_start = time.time() 797 get_client_start = time.time()
792 client_manager = cipd.get_client( 798 client_manager = cipd.get_client(
793 service_url, client_package_name, client_version, cache_dir, 799 service_url, client_package_name, client_version, cache_dir,
794 timeout=timeoutfn()) 800 timeout=timeoutfn())
795 801
796 with client_manager as client: 802 with client_manager as client:
797 get_client_duration = time.time() - get_client_start 803 get_client_duration = time.time() - get_client_start
798 804
799 package_pins = [] 805 package_pins = []
800 if packages: 806 if packages:
801 package_pins = _install_packages( 807 package_pins = _install_packages(
802 run_dir, cipd_cache_dir, client, packages, timeoutfn()) 808 run_dir, cipd_cache_dir, client, packages, timeoutfn(), isolate_cache)
803 809
804 file_path.make_tree_files_read_only(run_dir) 810 file_path.make_tree_files_read_only(run_dir)
805 811
806 total_duration = time.time() - start 812 total_duration = time.time() - start
807 logging.info( 813 logging.info(
808 'Installing CIPD client and packages took %d seconds', total_duration) 814 'Installing CIPD client and packages took %d seconds', total_duration)
809 815
810 yield CipdInfo( 816 yield CipdInfo(
811 client=client, 817 client=client,
812 cache_dir=cipd_cache_dir, 818 cache_dir=cipd_cache_dir,
(...skipping 185 matching lines...) Expand 10 before | Expand all | Expand 10 after
998 if options.json: 1004 if options.json:
999 options.json = unicode(os.path.abspath(options.json)) 1005 options.json = unicode(os.path.abspath(options.json))
1000 1006
1001 cipd.validate_cipd_options(parser, options) 1007 cipd.validate_cipd_options(parser, options)
1002 1008
1003 install_packages_fn = noop_install_packages 1009 install_packages_fn = noop_install_packages
1004 if options.cipd_enabled: 1010 if options.cipd_enabled:
1005 install_packages_fn = lambda run_dir: install_client_and_packages( 1011 install_packages_fn = lambda run_dir: install_client_and_packages(
1006 run_dir, cipd.parse_package_args(options.cipd_packages), 1012 run_dir, cipd.parse_package_args(options.cipd_packages),
1007 options.cipd_server, options.cipd_client_package, 1013 options.cipd_server, options.cipd_client_package,
1008 options.cipd_client_version, cache_dir=options.cipd_cache) 1014 options.cipd_client_version, cache_dir=options.cipd_cache,
1015 isolate_cache=isolate_cache)
1009 1016
1010 @contextlib.contextmanager 1017 @contextlib.contextmanager
1011 def init_named_caches(run_dir): 1018 def init_named_caches(run_dir):
1012 # WARNING: this function depends on "options" variable defined in the outer 1019 # WARNING: this function depends on "options" variable defined in the outer
1013 # function. 1020 # function.
1014 with named_cache_manager.open(): 1021 with named_cache_manager.open():
1015 named_cache_manager.create_symlinks(run_dir, options.named_caches) 1022 named_cache_manager.create_symlinks(run_dir, options.named_caches)
1016 try: 1023 try:
1017 yield 1024 yield
1018 finally: 1025 finally:
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after
1060 return 1 1067 return 1
1061 1068
1062 1069
1063 if __name__ == '__main__': 1070 if __name__ == '__main__':
1064 subprocess42.inhibit_os_error_reporting() 1071 subprocess42.inhibit_os_error_reporting()
1065 # Ensure that we are always running with the correct encoding. 1072 # Ensure that we are always running with the correct encoding.
1066 fix_encoding.fix_encoding() 1073 fix_encoding.fix_encoding()
1067 file_path.enable_symlink() 1074 file_path.enable_symlink()
1068 1075
1069 sys.exit(main(sys.argv[1:])) 1076 sys.exit(main(sys.argv[1:]))
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698