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

Side by Side Diff: client/run_isolated.py

Issue 2440353004: Return specific files, not just those in $(ISOLATED_OUTDIR) (Closed)
Patch Set: Return files specified by --output in addition to those in output dir Created 4 years, 1 month 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 18 matching lines...) Expand all
29 29
30 __version__ = '0.8.6' 30 __version__ = '0.8.6'
31 31
32 import argparse 32 import argparse
33 import base64 33 import base64
34 import collections 34 import collections
35 import json 35 import json
36 import logging 36 import logging
37 import optparse 37 import optparse
38 import os 38 import os
39 import shutil
39 import sys 40 import sys
40 import tempfile 41 import tempfile
41 import time 42 import time
42 43
43 from third_party.depot_tools import fix_encoding 44 from third_party.depot_tools import fix_encoding
44 45
45 from utils import file_path 46 from utils import file_path
46 from utils import fs 47 from utils import fs
47 from utils import large 48 from utils import large
48 from utils import logging_utils 49 from utils import logging_utils
(...skipping 233 matching lines...) Expand 10 before | Expand all | Expand 10 after
282 return bundle, { 283 return bundle, {
283 'duration': time.time() - start, 284 'duration': time.time() - start,
284 'initial_number_items': cache.initial_number_items, 285 'initial_number_items': cache.initial_number_items,
285 'initial_size': cache.initial_size, 286 'initial_size': cache.initial_size,
286 'items_cold': base64.b64encode(large.pack(sorted(cache.added))), 287 'items_cold': base64.b64encode(large.pack(sorted(cache.added))),
287 'items_hot': base64.b64encode( 288 'items_hot': base64.b64encode(
288 large.pack(sorted(set(cache.used) - set(cache.added)))), 289 large.pack(sorted(set(cache.used) - set(cache.added)))),
289 } 290 }
290 291
291 292
293 def move_outputs_to_outdir(run_dir, out_dir, outputs):
294 """ Moves/overwrites any named outputs to out_dir so they can be uploaded."""
M-A Ruel 2016/10/26 15:43:40 """Moves/overwrites ...
aludwin 2016/10/26 17:32:14 Done.
295 if outputs == None:
M-A Ruel 2016/10/26 15:43:40 if not outputs:
aludwin 2016/10/26 17:32:14 Done.
296 return
297 isolateserver.create_directories(out_dir, outputs)
298 for o in outputs:
299 shutil.copyfile(
M-A Ruel 2016/10/26 15:43:40 this doesn't fit a line? please use instead: file
aludwin 2016/10/26 17:32:14 Done.
300 os.path.join(run_dir, o),
301 os.path.join(out_dir, o))
302
303
292 def delete_and_upload(storage, out_dir, leak_temp_dir): 304 def delete_and_upload(storage, out_dir, leak_temp_dir):
293 """Deletes the temporary run directory and uploads results back. 305 """Deletes the temporary run directory and uploads results back.
294 306
295 Returns: 307 Returns:
296 tuple(outputs_ref, success, stats) 308 tuple(outputs_ref, success, stats)
297 - outputs_ref: a dict referring to the results archived back to the isolated 309 - outputs_ref: a dict referring to the results archived back to the isolated
298 server, if applicable. 310 server, if applicable.
299 - success: False if something occurred that means that the task must 311 - success: False if something occurred that means that the task must
300 forcibly be considered a failure, e.g. zombie processes were left 312 forcibly be considered a failure, e.g. zombie processes were left
301 behind. 313 behind.
302 - stats: uploading stats. 314 - stats: uploading stats.
303 """ 315 """
304
305 # Upload out_dir and generate a .isolated file out of this directory. It is 316 # Upload out_dir and generate a .isolated file out of this directory. It is
306 # only done if files were written in the directory. 317 # only done if files were written in the directory.
307 outputs_ref = None 318 outputs_ref = None
308 cold = [] 319 cold = []
309 hot = [] 320 hot = []
310 start = time.time() 321 start = time.time()
311 322
312 if fs.isdir(out_dir) and fs.listdir(out_dir): 323 if fs.isdir(out_dir) and fs.listdir(out_dir):
313 with tools.Profiler('ArchiveOutput'): 324 with tools.Profiler('ArchiveOutput'):
314 try: 325 try:
(...skipping 29 matching lines...) Expand all
344 logging.exception('Had difficulties removing out_dir %s: %s', out_dir, e) 355 logging.exception('Had difficulties removing out_dir %s: %s', out_dir, e)
345 stats = { 356 stats = {
346 'duration': time.time() - start, 357 'duration': time.time() - start,
347 'items_cold': base64.b64encode(large.pack(cold)), 358 'items_cold': base64.b64encode(large.pack(cold)),
348 'items_hot': base64.b64encode(large.pack(hot)), 359 'items_hot': base64.b64encode(large.pack(hot)),
349 } 360 }
350 return outputs_ref, success, stats 361 return outputs_ref, success, stats
351 362
352 363
353 def map_and_run( 364 def map_and_run(
354 command, isolated_hash, storage, isolate_cache, leak_temp_dir, root_dir, 365 command, isolated_hash, storage, isolate_cache, outputs, leak_temp_dir,
355 hard_timeout, grace_period, bot_file, extra_args, install_packages_fn, 366 root_dir, hard_timeout, grace_period, bot_file, extra_args,
356 use_symlinks): 367 install_packages_fn, use_symlinks):
357 """Runs a command with optional isolated input/output. 368 """Runs a command with optional isolated input/output.
358 369
359 See run_tha_test for argument documentation. 370 See run_tha_test for argument documentation.
360 371
361 Returns metadata about the result. 372 Returns metadata about the result.
362 """ 373 """
363 assert root_dir or root_dir is None 374 assert root_dir or root_dir is None
364 assert bool(command) ^ bool(isolated_hash) 375 assert bool(command) ^ bool(isolated_hash)
365 result = { 376 result = {
366 'duration': None, 377 'duration': None,
(...skipping 65 matching lines...) Expand 10 before | Expand all | Expand 10 after
432 if os.environ.get('SWARMING_TASK_ID'): 443 if os.environ.get('SWARMING_TASK_ID'):
433 # Give an additional hint when running as a swarming task. 444 # Give an additional hint when running as a swarming task.
434 sys.stderr.write('<This occurs at the \'isolate\' step>\n') 445 sys.stderr.write('<This occurs at the \'isolate\' step>\n')
435 result['exit_code'] = 1 446 result['exit_code'] = 1
436 return result 447 return result
437 448
438 change_tree_read_only(run_dir, bundle.read_only) 449 change_tree_read_only(run_dir, bundle.read_only)
439 cwd = os.path.normpath(os.path.join(cwd, bundle.relative_cwd)) 450 cwd = os.path.normpath(os.path.join(cwd, bundle.relative_cwd))
440 command = bundle.command + extra_args 451 command = bundle.command + extra_args
441 452
453 # If we have an explicit list of files to return, make sure their
454 # directories exist now
M-A Ruel 2016/10/26 15:43:39 add period.
aludwin 2016/10/26 17:32:14 Done.
455 if storage and outputs != None:
M-A Ruel 2016/10/26 15:43:39 if storage and outputs:
aludwin 2016/10/26 17:32:14 Done.
456 isolateserver.create_directories(run_dir, outputs)
457
442 command = tools.fix_python_path(command) 458 command = tools.fix_python_path(command)
443 command = process_command(command, out_dir, bot_file) 459 command = process_command(command, out_dir, bot_file)
444 file_path.ensure_command_has_abs_path(command, cwd) 460 file_path.ensure_command_has_abs_path(command, cwd)
445 461
446 sys.stdout.flush() 462 sys.stdout.flush()
447 start = time.time() 463 start = time.time()
448 try: 464 try:
449 result['exit_code'], result['had_hard_timeout'] = run_command( 465 result['exit_code'], result['had_hard_timeout'] = run_command(
450 command, cwd, tmp_dir, hard_timeout, grace_period) 466 command, cwd, tmp_dir, hard_timeout, grace_period)
451 finally: 467 finally:
452 result['duration'] = max(time.time() - start, 0) 468 result['duration'] = max(time.time() - start, 0)
453 except Exception as e: 469 except Exception as e:
454 # An internal error occurred. Report accordingly so the swarming task will 470 # An internal error occurred. Report accordingly so the swarming task will
455 # be retried automatically. 471 # be retried automatically.
456 logging.exception('internal failure: %s', e) 472 logging.exception('internal failure: %s', e)
457 result['internal_failure'] = str(e) 473 result['internal_failure'] = str(e)
458 on_error.report(None) 474 on_error.report(None)
475
476 # Try to copy files to the output directory, if specified. They must *all*
477 # exist; we'll treat this as a task failure if we can't copy the files for
M-A Ruel 2016/10/26 15:43:40 can the outputs be directories? I feel this should
aludwin 2016/10/26 17:32:14 Currently, Bazel doesn't support this - but it wil
478 # some reason.
479 try:
480 if out_dir:
481 move_outputs_to_outdir(run_dir, out_dir, outputs)
482 except Exception as e:
M-A Ruel 2016/10/26 15:43:40 Please catch the exceptions at move_outputs_to_out
aludwin 2016/10/26 17:32:14 Done.
483 sys.stderr.write('<Could not return requested files: %s>' % e)
484 result['exit_code'] = 1
485 return result
486
487 # Clean up
459 finally: 488 finally:
460 try: 489 try:
461 if leak_temp_dir: 490 if leak_temp_dir:
462 logging.warning( 491 logging.warning(
463 'Deliberately leaking %s for later examination', run_dir) 492 'Deliberately leaking %s for later examination', run_dir)
464 else: 493 else:
465 # On Windows rmtree(run_dir) call above has a synchronization effect: it 494 # On Windows rmtree(run_dir) call above has a synchronization effect: it
466 # finishes only when all task child processes terminate (since a running 495 # finishes only when all task child processes terminate (since a running
467 # process locks *.exe file). Examine out_dir only after that call 496 # process locks *.exe file). Examine out_dir only after that call
468 # completes (since child processes may write to out_dir too and we need 497 # completes (since child processes may write to out_dir too and we need
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after
505 result['exit_code'] = 1 534 result['exit_code'] = 1
506 except Exception as e: 535 except Exception as e:
507 # Swallow any exception in the main finally clause. 536 # Swallow any exception in the main finally clause.
508 if out_dir: 537 if out_dir:
509 logging.exception('Leaking out_dir %s: %s', out_dir, e) 538 logging.exception('Leaking out_dir %s: %s', out_dir, e)
510 result['internal_failure'] = str(e) 539 result['internal_failure'] = str(e)
511 return result 540 return result
512 541
513 542
514 def run_tha_test( 543 def run_tha_test(
515 command, isolated_hash, storage, isolate_cache, leak_temp_dir, result_json, 544 command, isolated_hash, storage, isolate_cache, outputs,
516 root_dir, hard_timeout, grace_period, bot_file, extra_args, 545 leak_temp_dir, result_json, root_dir, hard_timeout, grace_period,
517 install_packages_fn, use_symlinks): 546 bot_file, extra_args, install_packages_fn, use_symlinks):
518 """Runs an executable and records execution metadata. 547 """Runs an executable and records execution metadata.
519 548
520 Either command or isolated_hash must be specified. 549 Either command or isolated_hash must be specified.
521 550
522 If isolated_hash is specified, downloads the dependencies in the cache, 551 If isolated_hash is specified, downloads the dependencies in the cache,
523 hardlinks them into a temporary directory and runs the command specified in 552 hardlinks them into a temporary directory and runs the command specified in
524 the .isolated. 553 the .isolated.
525 554
526 A temporary directory is created to hold the output files. The content inside 555 A temporary directory is created to hold the output files. The content inside
527 this directory will be uploaded back to |storage| packaged as a .isolated 556 this directory will be uploaded back to |storage| packaged as a .isolated
(...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after
570 'exit_code': None, 599 'exit_code': None,
571 'had_hard_timeout': False, 600 'had_hard_timeout': False,
572 'internal_failure': 'Was terminated before completion', 601 'internal_failure': 'Was terminated before completion',
573 'outputs_ref': None, 602 'outputs_ref': None,
574 'version': 5, 603 'version': 5,
575 } 604 }
576 tools.write_json(result_json, result, dense=True) 605 tools.write_json(result_json, result, dense=True)
577 606
578 # run_isolated exit code. Depends on if result_json is used or not. 607 # run_isolated exit code. Depends on if result_json is used or not.
579 result = map_and_run( 608 result = map_and_run(
580 command, isolated_hash, storage, isolate_cache, leak_temp_dir, root_dir, 609 command, isolated_hash, storage, isolate_cache, outputs,
581 hard_timeout, grace_period, bot_file, extra_args, install_packages_fn, 610 leak_temp_dir, root_dir, hard_timeout, grace_period, bot_file,
582 use_symlinks) 611 extra_args, install_packages_fn, use_symlinks)
583 logging.info('Result:\n%s', tools.format_json(result, dense=True)) 612 logging.info('Result:\n%s', tools.format_json(result, dense=True))
584 613
585 if result_json: 614 if result_json:
586 # We've found tests to delete 'work' when quitting, causing an exception 615 # We've found tests to delete 'work' when quitting, causing an exception
587 # here. Try to recreate the directory if necessary. 616 # here. Try to recreate the directory if necessary.
588 file_path.ensure_tree(os.path.dirname(result_json)) 617 file_path.ensure_tree(os.path.dirname(result_json))
589 tools.write_json(result_json, result, dense=True) 618 tools.write_json(result_json, result, dense=True)
590 # Only return 1 if there was an internal error. 619 # Only return 1 if there was an internal error.
591 return int(bool(result['internal_failure'])) 620 return int(bool(result['internal_failure']))
592 621
(...skipping 134 matching lines...) Expand 10 before | Expand all | Expand 10 after
727 parser.add_option( 756 parser.add_option(
728 '--hard-timeout', type='float', help='Enforce hard timeout in execution') 757 '--hard-timeout', type='float', help='Enforce hard timeout in execution')
729 parser.add_option( 758 parser.add_option(
730 '--grace-period', type='float', 759 '--grace-period', type='float',
731 help='Grace period between SIGTERM and SIGKILL') 760 help='Grace period between SIGTERM and SIGKILL')
732 parser.add_option( 761 parser.add_option(
733 '--bot-file', 762 '--bot-file',
734 help='Path to a file describing the state of the host. The content is ' 763 help='Path to a file describing the state of the host. The content is '
735 'defined by on_before_task() in bot_config.') 764 'defined by on_before_task() in bot_config.')
736 parser.add_option( 765 parser.add_option(
766 '--output', action='append',
767 help='Specifies an output to return. If no outputs are specified, all '
768 'files located in $(ISOLATED_OUT_DIR) will be returned; '
769 'otherwise, outputs in both $(ISOLATED_OUT_DIR) and those '
770 'specified by --output option (there can be multiple) will be '
771 'returned. Note that if a file in OUT_DIR has the same path '
772 'as an --output option, the --output version will be returned.')
773 parser.add_option(
737 '-a', '--argsfile', 774 '-a', '--argsfile',
738 # This is actually handled in parse_args; it's included here purely so it 775 # This is actually handled in parse_args; it's included here purely so it
739 # can make it into the help text. 776 # can make it into the help text.
740 help='Specify a file containing a JSON array of arguments to this ' 777 help='Specify a file containing a JSON array of arguments to this '
741 'script. If --argsfile is provided, no other argument may be ' 778 'script. If --argsfile is provided, no other argument may be '
742 'provided on the command line.') 779 'provided on the command line.')
743 data_group = optparse.OptionGroup(parser, 'Data source') 780 data_group = optparse.OptionGroup(parser, 'Data source')
744 data_group.add_option( 781 data_group.add_option(
745 '-s', '--isolated', 782 '-s', '--isolated',
746 help='Hash of the .isolated to grab from the isolate server.') 783 help='Hash of the .isolated to grab from the isolate server.')
(...skipping 92 matching lines...) Expand 10 before | Expand all | Expand 10 after
839 876
840 try: 877 try:
841 command = [] if options.isolated else args 878 command = [] if options.isolated else args
842 if options.isolate_server: 879 if options.isolate_server:
843 storage = isolateserver.get_storage( 880 storage = isolateserver.get_storage(
844 options.isolate_server, options.namespace) 881 options.isolate_server, options.namespace)
845 with storage: 882 with storage:
846 # Hashing schemes used by |storage| and |isolated_cache| MUST match. 883 # Hashing schemes used by |storage| and |isolated_cache| MUST match.
847 assert storage.hash_algo == isolated_cache.hash_algo 884 assert storage.hash_algo == isolated_cache.hash_algo
848 return run_tha_test( 885 return run_tha_test(
849 command, options.isolated, storage, isolated_cache, 886 command, options.isolated, storage, isolated_cache, options.output,
850 options.leak_temp_dir, options.json, options.root_dir, 887 options.leak_temp_dir, options.json, options.root_dir,
851 options.hard_timeout, options.grace_period, options.bot_file, args, 888 options.hard_timeout, options.grace_period, options.bot_file, args,
852 install_packages_fn, options.use_symlinks) 889 install_packages_fn, options.use_symlinks)
853 return run_tha_test( 890 return run_tha_test(
854 command, options.isolated, None, isolated_cache, options.leak_temp_dir, 891 command, options.isolated, None, isolated_cache, options.output,
855 options.json, options.root_dir, options.hard_timeout, 892 options.leak_temp_dir, options.json, options.root_dir,
856 options.grace_period, options.bot_file, args, install_packages_fn, 893 options.hard_timeout, options.grace_period, options.bot_file,
857 options.use_symlinks) 894 args, install_packages_fn, options.use_symlinks)
858 except cipd.Error as ex: 895 except cipd.Error as ex:
859 print >> sys.stderr, ex.message 896 print >> sys.stderr, ex.message
860 return 1 897 return 1
861 898
862 899
863 if __name__ == '__main__': 900 if __name__ == '__main__':
864 subprocess42.inhibit_os_error_reporting() 901 subprocess42.inhibit_os_error_reporting()
865 # Ensure that we are always running with the correct encoding. 902 # Ensure that we are always running with the correct encoding.
866 fix_encoding.fix_encoding() 903 fix_encoding.fix_encoding()
867 file_path.enable_symlink() 904 file_path.enable_symlink()
868 905
869 sys.exit(main(sys.argv[1:])) 906 sys.exit(main(sys.argv[1:]))
OLDNEW
« no previous file with comments | « appengine/swarming/swarming_bot/bot_code/task_runner.py ('k') | client/tests/run_isolated_test.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698