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

Side by Side Diff: client/run_isolated.py

Issue 2267363004: Add CIPD pin reporting to swarming. (Closed) Base URL: https://chromium.googlesource.com/external/github.com/luci/luci-py@master
Patch Set: comments and some tests Created 4 years, 3 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
« appengine/swarming/templates/user_task.html ('K') | « client/cipd.py ('k') | no next file » | 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
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
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
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
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
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
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:]))
OLDNEW
« appengine/swarming/templates/user_task.html ('K') | « client/cipd.py ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698