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

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: Rename to cipd_pins 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
« client/cipd.py ('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.5'
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 335 matching lines...) Expand 10 before | Expand all | Expand 10 after
376 # 'items_cold': '<large.pack()>', 376 # 'items_cold': '<large.pack()>',
377 # 'items_hot': '<large.pack()>', 377 # 'items_hot': '<large.pack()>',
378 # }, 378 # },
379 # 'upload': { 379 # 'upload': {
380 # 'duration': 0., 380 # 'duration': 0.,
381 # 'items_cold': '<large.pack()>', 381 # 'items_cold': '<large.pack()>',
382 # 'items_hot': '<large.pack()>', 382 # 'items_hot': '<large.pack()>',
383 # }, 383 # },
384 # }, 384 # },
385 }, 385 },
386 # 'cipd_pins': {
387 # 'packages': [
388 # {'package_name': ..., 'version': ..., 'path': ...},
389 # ...
390 # ],
391 # 'client_package': {'package_name': ..., 'version': ...},
392 # },
386 'outputs_ref': None, 393 'outputs_ref': None,
387 'version': 5, 394 'version': 5,
388 } 395 }
389 396
390 if root_dir: 397 if root_dir:
391 file_path.ensure_tree(root_dir, 0700) 398 file_path.ensure_tree(root_dir, 0700)
392 else: 399 else:
393 root_dir = os.path.dirname(cache.cache_dir) if cache.cache_dir else None 400 root_dir = os.path.dirname(cache.cache_dir) if cache.cache_dir else None
394 # See comment for these constants. 401 # See comment for these constants.
395 run_dir = make_temp_dir(ISOLATED_RUN_DIR, root_dir) 402 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 403 # 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. 404 # as Swarming task can run without an isolate server.
398 out_dir = make_temp_dir(ISOLATED_OUT_DIR, root_dir) if storage else None 405 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) 406 tmp_dir = make_temp_dir(ISOLATED_TMP_DIR, root_dir)
400 cwd = run_dir 407 cwd = run_dir
401 408
402 try: 409 try:
403 cipd_stats = install_packages_fn(run_dir) 410 cipd_info = install_packages_fn(run_dir)
404 if cipd_stats: 411 if cipd_info:
405 result['stats']['cipd'] = cipd_stats 412 result['stats']['cipd'] = cipd_info['stats']
413 result['cipd_pins'] = cipd_info['cipd_pins']
406 414
407 if isolated_hash: 415 if isolated_hash:
408 isolated_stats = result['stats'].setdefault('isolated', {}) 416 isolated_stats = result['stats'].setdefault('isolated', {})
409 bundle, isolated_stats['download'] = fetch_and_map( 417 bundle, isolated_stats['download'] = fetch_and_map(
410 isolated_hash=isolated_hash, 418 isolated_hash=isolated_hash,
411 storage=storage, 419 storage=storage,
412 cache=cache, 420 cache=cache,
413 outdir=run_dir, 421 outdir=run_dir,
414 use_symlinks=use_symlinks) 422 use_symlinks=use_symlinks)
415 if not bundle.command: 423 if not bundle.command:
(...skipping 116 matching lines...) Expand 10 before | Expand all | Expand 10 after
532 for later examination. 540 for later examination.
533 result_json: file path to dump result metadata into. If set, the process 541 result_json: file path to dump result metadata into. If set, the process
534 exit code is always 0 unless an internal error occurred. 542 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 543 root_dir: path to the directory to use to create the temporary directory. If
536 not specified, a random temporary directory is created. 544 not specified, a random temporary directory is created.
537 hard_timeout: kills the process if it lasts more than this amount of 545 hard_timeout: kills the process if it lasts more than this amount of
538 seconds. 546 seconds.
539 grace_period: number of seconds to wait between SIGTERM and SIGKILL. 547 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 548 extra_args: optional arguments to add to the command stated in the .isolate
541 file. Ignored if isolate_hash is empty. 549 file. Ignored if isolate_hash is empty.
542 install_packages_fn: function (dir) => cipd_stats. Installs packages. 550 install_packages_fn: function (dir) => {"stats": cipd_stats, "pins":
551 cipd_pins}. Installs packages.
543 use_symlinks: create tree with symlinks instead of hardlinks. 552 use_symlinks: create tree with symlinks instead of hardlinks.
544 553
545 Returns: 554 Returns:
546 Process exit code that should be used. 555 Process exit code that should be used.
547 """ 556 """
548 assert bool(command) ^ bool(isolated_hash) 557 assert bool(command) ^ bool(isolated_hash)
549 extra_args = extra_args or [] 558 extra_args = extra_args or []
550 559
551 if any(ISOLATED_OUTDIR_PARAMETER in a for a in (command or extra_args)): 560 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' 561 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( 597 print(
589 '[run_isolated_out_hack]%s[/run_isolated_out_hack]' % 598 '[run_isolated_out_hack]%s[/run_isolated_out_hack]' %
590 tools.format_json(data, dense=True)) 599 tools.format_json(data, dense=True))
591 sys.stdout.flush() 600 sys.stdout.flush()
592 return result['exit_code'] or int(bool(result['internal_failure'])) 601 return result['exit_code'] or int(bool(result['internal_failure']))
593 602
594 603
595 def install_packages( 604 def install_packages(
596 run_dir, packages, service_url, client_package_name, 605 run_dir, packages, service_url, client_package_name,
597 client_version, cache_dir=None, timeout=None): 606 client_version, cache_dir=None, timeout=None):
598 """Installs packages. Returns stats. 607 """Installs packages. Returns stats, cipd client info and pins.
608
609 pins and the cipd client info are in the form of:
610 [
611 {
612 "path": path, "package_name": package_name, "version": version,
613 },
614 ...
615 ]
616 (the cipd client info is a single dictionary instead of a list)
617
618 such that they correspond 1:1 to all input package arguments from the command
619 line. These dictionaries make their all the way back to swarming, where they
620 become the arguments of CipdPackage.
599 621
600 Args: 622 Args:
601 run_dir (str): root of installation. 623 run_dir (str): root of installation.
602 packages: packages to install, dict {path: [(package_name, version)]. 624 packages: packages to install, dict
625 {path: [(package_name, version, cmd_line_idx)].
603 service_url (str): CIPD server url, e.g. 626 service_url (str): CIPD server url, e.g.
604 "https://chrome-infra-packages.appspot.com." 627 "https://chrome-infra-packages.appspot.com."
605 client_package_name (str): CIPD package name of CIPD client. 628 client_package_name (str): CIPD package name of CIPD client.
606 client_version (str): Version of CIPD client. 629 client_version (str): Version of CIPD client.
607 cache_dir (str): where to keep cache of cipd clients, packages and tags. 630 cache_dir (str): where to keep cache of cipd clients, packages and tags.
608 timeout: max duration in seconds that this function can take. 631 timeout: max duration in seconds that this function can take.
609 """ 632 """
610 assert cache_dir 633 assert cache_dir
611 if not packages: 634 if not packages:
612 return None 635 return None
613 636
614 timeoutfn = tools.sliding_timeout(timeout) 637 timeoutfn = tools.sliding_timeout(timeout)
615 start = time.time() 638 start = time.time()
616 cache_dir = os.path.abspath(cache_dir) 639 cache_dir = os.path.abspath(cache_dir)
617 640
618 run_dir = os.path.abspath(run_dir) 641 run_dir = os.path.abspath(run_dir)
619 642
643 package_pins = []
644 def insert_pin(path, name, version, idx):
645 if idx > len(package_pins)-1:
646 package_pins.extend([None] * (idx - len(package_pins) + 1))
647 package_pins[idx] = {
648 'package_name': name,
649 'version': version,
650 'path': path,
651 }
652
620 get_client_start = time.time() 653 get_client_start = time.time()
621 client_manager = cipd.get_client( 654 client_manager = cipd.get_client(
622 service_url, client_package_name, client_version, cache_dir, 655 service_url, client_package_name, client_version, cache_dir,
623 timeout=timeoutfn()) 656 timeout=timeoutfn())
624 with client_manager as client: 657 with client_manager as client:
658 client_package = {
659 'package_name': client.package_name,
660 'version': client.instance_id,
661 }
625 get_client_duration = time.time() - get_client_start 662 get_client_duration = time.time() - get_client_start
626 for path, packages in sorted(packages.iteritems()): 663 for path, packages in sorted(packages.iteritems()):
627 site_root = os.path.abspath(os.path.join(run_dir, path)) 664 site_root = os.path.abspath(os.path.join(run_dir, path))
628 if not site_root.startswith(run_dir): 665 if not site_root.startswith(run_dir):
629 raise cipd.Error('Invalid CIPD package path "%s"' % path) 666 raise cipd.Error('Invalid CIPD package path "%s"' % path)
630 667
631 # Do not clean site_root before installation because it may contain other 668 # Do not clean site_root before installation because it may contain other
632 # site roots. 669 # site roots.
633 file_path.ensure_tree(site_root, 0770) 670 file_path.ensure_tree(site_root, 0770)
634 client.ensure( 671 pins = client.ensure(
635 site_root, packages, 672 site_root, [(name, vers) for name, vers, _ in packages],
636 cache_dir=os.path.join(cache_dir, 'cipd_internal'), 673 cache_dir=os.path.join(cache_dir, 'cipd_internal'),
637 timeout=timeoutfn()) 674 timeout=timeoutfn())
675 for i, pin in enumerate(pins):
676 insert_pin(path, pin[0], pin[1], packages[i][2])
638 file_path.make_tree_files_read_only(site_root) 677 file_path.make_tree_files_read_only(site_root)
639 678
640 total_duration = time.time() - start 679 total_duration = time.time() - start
641 logging.info( 680 logging.info(
642 'Installing CIPD client and packages took %d seconds', total_duration) 681 'Installing CIPD client and packages took %d seconds', total_duration)
643 682
683 assert None not in package_pins
684
644 return { 685 return {
645 'duration': total_duration, 686 'stats': {
646 'get_client_duration': get_client_duration, 687 'duration': total_duration,
688 'get_client_duration': get_client_duration,
689 },
690 'cipd_pins': {
691 'packages': package_pins,
692 'client_package': client_package,
693 }
647 } 694 }
648 695
649 696
650 def create_option_parser(): 697 def create_option_parser():
651 parser = logging_utils.OptionParserWithLogging( 698 parser = logging_utils.OptionParserWithLogging(
652 usage='%prog <options> [command to run or extra args]', 699 usage='%prog <options> [command to run or extra args]',
653 version=__version__, 700 version=__version__,
654 log_file=RUN_ISOLATED_LOG_FILE) 701 log_file=RUN_ISOLATED_LOG_FILE)
655 parser.add_option( 702 parser.add_option(
656 '--clean', action='store_true', 703 '--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) 784 '%s in args requires --isolate-server' % ISOLATED_OUTDIR_PARAMETER)
738 785
739 if options.root_dir: 786 if options.root_dir:
740 options.root_dir = unicode(os.path.abspath(options.root_dir)) 787 options.root_dir = unicode(os.path.abspath(options.root_dir))
741 if options.json: 788 if options.json:
742 options.json = unicode(os.path.abspath(options.json)) 789 options.json = unicode(os.path.abspath(options.json))
743 790
744 cipd.validate_cipd_options(parser, options) 791 cipd.validate_cipd_options(parser, options)
745 792
746 install_packages_fn = lambda run_dir: install_packages( 793 install_packages_fn = lambda run_dir: install_packages(
747 run_dir, cipd.parse_package_args(options.cipd_packages), 794 run_dir, cipd.parse_package_args(options.cipd_packages, with_index=True),
748 options.cipd_server, options.cipd_client_package, 795 options.cipd_server, options.cipd_client_package,
749 options.cipd_client_version, cache_dir=options.cipd_cache) 796 options.cipd_client_version, cache_dir=options.cipd_cache)
750 797
751 try: 798 try:
752 command = [] if options.isolated else args 799 command = [] if options.isolated else args
753 if options.isolate_server: 800 if options.isolate_server:
754 storage = isolateserver.get_storage( 801 storage = isolateserver.get_storage(
755 options.isolate_server, options.namespace) 802 options.isolate_server, options.namespace)
756 with storage: 803 with storage:
757 # Hashing schemes used by |storage| and |cache| MUST match. 804 # Hashing schemes used by |storage| and |cache| MUST match.
(...skipping 12 matching lines...) Expand all
770 print >> sys.stderr, ex.message 817 print >> sys.stderr, ex.message
771 return 1 818 return 1
772 819
773 820
774 if __name__ == '__main__': 821 if __name__ == '__main__':
775 subprocess42.inhibit_os_error_reporting() 822 subprocess42.inhibit_os_error_reporting()
776 # Ensure that we are always running with the correct encoding. 823 # Ensure that we are always running with the correct encoding.
777 fix_encoding.fix_encoding() 824 fix_encoding.fix_encoding()
778 file_path.enable_symlink() 825 file_path.enable_symlink()
779 sys.exit(main(sys.argv[1:])) 826 sys.exit(main(sys.argv[1:]))
OLDNEW
« client/cipd.py ('K') | « client/cipd.py ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698