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

Side by Side Diff: client/run_isolated.py

Issue 2877483004: Reland "named caches: move instead of symlinking" (Closed)
Patch Set: address 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
« no previous file with comments | « client/named_cache.py ('k') | client/tests/named_cache_test.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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 383 matching lines...) Expand 10 before | Expand all | Expand 10 after
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
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
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
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
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:]))
OLDNEW
« no previous file with comments | « client/named_cache.py ('k') | client/tests/named_cache_test.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698