Chromium Code Reviews| OLD | NEW |
|---|---|
| 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 273 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 284 return bundle, { | 284 return bundle, { |
| 285 'duration': time.time() - start, | 285 'duration': time.time() - start, |
| 286 'initial_number_items': cache.initial_number_items, | 286 'initial_number_items': cache.initial_number_items, |
| 287 'initial_size': cache.initial_size, | 287 'initial_size': cache.initial_size, |
| 288 'items_cold': base64.b64encode(large.pack(sorted(cache.added))), | 288 'items_cold': base64.b64encode(large.pack(sorted(cache.added))), |
| 289 'items_hot': base64.b64encode( | 289 'items_hot': base64.b64encode( |
| 290 large.pack(sorted(set(cache.used) - set(cache.added)))), | 290 large.pack(sorted(set(cache.used) - set(cache.added)))), |
| 291 } | 291 } |
| 292 | 292 |
| 293 | 293 |
| 294 def move_outputs_to_outdir(run_dir, out_dir, outputs): | |
|
M-A Ruel
2016/10/27 19:22:19
link_outputs_to_outdir()
otherwise this is confus
aludwin
2016/10/27 22:00:06
Done.
| |
| 295 """Moves any named outputs to out_dir so they can be uploaded. | |
|
M-A Ruel
2016/10/27 19:22:19
add an empty line after
aludwin
2016/10/27 22:00:06
Done.
| |
| 296 Raises an error if the file already exists in that directory. | |
| 297 """ | |
| 298 if not outputs: | |
| 299 return | |
| 300 isolateserver.create_directories(out_dir, outputs) | |
| 301 for o in outputs: | |
| 302 try: | |
| 303 file_path.link_file( | |
| 304 os.path.join(out_dir, o), | |
| 305 os.path.join(run_dir, o), | |
| 306 file_path.HARDLINK_WITH_FALLBACK) | |
| 307 except OSError as e: | |
| 308 sys.stderr.write('<Could not return file %s: %s>' % (o, e)) | |
|
M-A Ruel
2016/10/27 19:22:19
Add a # TODO(aludwin): Surface this error.
I thin
aludwin
2016/10/27 22:00:05
Done.
| |
| 309 | |
| 310 | |
| 294 def delete_and_upload(storage, out_dir, leak_temp_dir): | 311 def delete_and_upload(storage, out_dir, leak_temp_dir): |
| 295 """Deletes the temporary run directory and uploads results back. | 312 """Deletes the temporary run directory and uploads results back. |
| 296 | 313 |
| 297 Returns: | 314 Returns: |
| 298 tuple(outputs_ref, success, stats) | 315 tuple(outputs_ref, success, stats) |
| 299 - outputs_ref: a dict referring to the results archived back to the isolated | 316 - outputs_ref: a dict referring to the results archived back to the isolated |
| 300 server, if applicable. | 317 server, if applicable. |
| 301 - success: False if something occurred that means that the task must | 318 - success: False if something occurred that means that the task must |
| 302 forcibly be considered a failure, e.g. zombie processes were left | 319 forcibly be considered a failure, e.g. zombie processes were left |
| 303 behind. | 320 behind. |
| 304 - stats: uploading stats. | 321 - stats: uploading stats. |
| 305 """ | 322 """ |
| 306 | |
| 307 # Upload out_dir and generate a .isolated file out of this directory. It is | 323 # Upload out_dir and generate a .isolated file out of this directory. It is |
| 308 # only done if files were written in the directory. | 324 # only done if files were written in the directory. |
| 309 outputs_ref = None | 325 outputs_ref = None |
| 310 cold = [] | 326 cold = [] |
| 311 hot = [] | 327 hot = [] |
| 312 start = time.time() | 328 start = time.time() |
| 313 | 329 |
| 314 if fs.isdir(out_dir) and fs.listdir(out_dir): | 330 if fs.isdir(out_dir) and fs.listdir(out_dir): |
| 315 with tools.Profiler('ArchiveOutput'): | 331 with tools.Profiler('ArchiveOutput'): |
| 316 try: | 332 try: |
| (...skipping 29 matching lines...) Expand all Loading... | |
| 346 logging.exception('Had difficulties removing out_dir %s: %s', out_dir, e) | 362 logging.exception('Had difficulties removing out_dir %s: %s', out_dir, e) |
| 347 stats = { | 363 stats = { |
| 348 'duration': time.time() - start, | 364 'duration': time.time() - start, |
| 349 'items_cold': base64.b64encode(large.pack(cold)), | 365 'items_cold': base64.b64encode(large.pack(cold)), |
| 350 'items_hot': base64.b64encode(large.pack(hot)), | 366 'items_hot': base64.b64encode(large.pack(hot)), |
| 351 } | 367 } |
| 352 return outputs_ref, success, stats | 368 return outputs_ref, success, stats |
| 353 | 369 |
| 354 | 370 |
| 355 def map_and_run( | 371 def map_and_run( |
| 356 command, isolated_hash, storage, isolate_cache, init_name_caches, | 372 command, isolated_hash, storage, isolate_cache, outputs, init_name_caches, |
| 357 leak_temp_dir, root_dir, hard_timeout, grace_period, bot_file, extra_args, | 373 leak_temp_dir, root_dir, hard_timeout, grace_period, bot_file, extra_args, |
| 358 install_packages_fn, use_symlinks): | 374 install_packages_fn, use_symlinks): |
| 359 """Runs a command with optional isolated input/output. | 375 """Runs a command with optional isolated input/output. |
| 360 | 376 |
| 361 See run_tha_test for argument documentation. | 377 See run_tha_test for argument documentation. |
| 362 | 378 |
| 363 Returns metadata about the result. | 379 Returns metadata about the result. |
| 364 """ | 380 """ |
| 365 assert root_dir or root_dir is None | 381 assert root_dir or root_dir is None |
| 366 assert bool(command) ^ bool(isolated_hash) | 382 assert bool(command) ^ bool(isolated_hash) |
| (...skipping 67 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 434 if os.environ.get('SWARMING_TASK_ID'): | 450 if os.environ.get('SWARMING_TASK_ID'): |
| 435 # Give an additional hint when running as a swarming task. | 451 # Give an additional hint when running as a swarming task. |
| 436 sys.stderr.write('<This occurs at the \'isolate\' step>\n') | 452 sys.stderr.write('<This occurs at the \'isolate\' step>\n') |
| 437 result['exit_code'] = 1 | 453 result['exit_code'] = 1 |
| 438 return result | 454 return result |
| 439 | 455 |
| 440 change_tree_read_only(run_dir, bundle.read_only) | 456 change_tree_read_only(run_dir, bundle.read_only) |
| 441 cwd = os.path.normpath(os.path.join(cwd, bundle.relative_cwd)) | 457 cwd = os.path.normpath(os.path.join(cwd, bundle.relative_cwd)) |
| 442 command = bundle.command + extra_args | 458 command = bundle.command + extra_args |
| 443 | 459 |
| 460 # If we have an explicit list of files to return, make sure their | |
| 461 # directories exist now. | |
| 462 if storage and outputs: | |
| 463 isolateserver.create_directories(run_dir, outputs) | |
| 464 | |
| 444 command = tools.fix_python_path(command) | 465 command = tools.fix_python_path(command) |
| 445 command = process_command(command, out_dir, bot_file) | 466 command = process_command(command, out_dir, bot_file) |
| 446 file_path.ensure_command_has_abs_path(command, cwd) | 467 file_path.ensure_command_has_abs_path(command, cwd) |
| 447 | 468 |
| 448 init_name_caches(run_dir) | 469 init_name_caches(run_dir) |
| 449 | 470 |
| 450 sys.stdout.flush() | 471 sys.stdout.flush() |
| 451 start = time.time() | 472 start = time.time() |
| 452 try: | 473 try: |
| 453 result['exit_code'], result['had_hard_timeout'] = run_command( | 474 result['exit_code'], result['had_hard_timeout'] = run_command( |
| 454 command, cwd, tmp_dir, hard_timeout, grace_period) | 475 command, cwd, tmp_dir, hard_timeout, grace_period) |
| 455 finally: | 476 finally: |
| 456 result['duration'] = max(time.time() - start, 0) | 477 result['duration'] = max(time.time() - start, 0) |
| 457 except Exception as e: | 478 except Exception as e: |
| 458 # An internal error occurred. Report accordingly so the swarming task will | 479 # An internal error occurred. Report accordingly so the swarming task will |
| 459 # be retried automatically. | 480 # be retried automatically. |
| 460 logging.exception('internal failure: %s', e) | 481 logging.exception('internal failure: %s', e) |
| 461 result['internal_failure'] = str(e) | 482 result['internal_failure'] = str(e) |
| 462 on_error.report(None) | 483 on_error.report(None) |
| 484 | |
| 485 # Clean up | |
| 463 finally: | 486 finally: |
| 464 try: | 487 try: |
| 488 # Try to copy files to the output directory, if specified. | |
|
M-A Ruel
2016/10/27 19:22:20
s/Try to copy/Link/
aludwin
2016/10/27 22:00:05
Done.
| |
| 489 if out_dir: | |
| 490 move_outputs_to_outdir(run_dir, out_dir, outputs) | |
| 491 | |
| 465 success = False | 492 success = False |
| 466 if leak_temp_dir: | 493 if leak_temp_dir: |
| 467 success = True | 494 success = True |
| 468 logging.warning( | 495 logging.warning( |
| 469 'Deliberately leaking %s for later examination', run_dir) | 496 'Deliberately leaking %s for later examination', run_dir) |
| 470 else: | 497 else: |
| 471 # On Windows rmtree(run_dir) call above has a synchronization effect: it | 498 # On Windows rmtree(run_dir) call above has a synchronization effect: it |
| 472 # finishes only when all task child processes terminate (since a running | 499 # finishes only when all task child processes terminate (since a running |
| 473 # process locks *.exe file). Examine out_dir only after that call | 500 # process locks *.exe file). Examine out_dir only after that call |
| 474 # completes (since child processes may write to out_dir too and we need | 501 # 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 Loading... | |
| 511 result['exit_code'] = 1 | 538 result['exit_code'] = 1 |
| 512 except Exception as e: | 539 except Exception as e: |
| 513 # Swallow any exception in the main finally clause. | 540 # Swallow any exception in the main finally clause. |
| 514 if out_dir: | 541 if out_dir: |
| 515 logging.exception('Leaking out_dir %s: %s', out_dir, e) | 542 logging.exception('Leaking out_dir %s: %s', out_dir, e) |
| 516 result['internal_failure'] = str(e) | 543 result['internal_failure'] = str(e) |
| 517 return result | 544 return result |
| 518 | 545 |
| 519 | 546 |
| 520 def run_tha_test( | 547 def run_tha_test( |
| 521 command, isolated_hash, storage, isolate_cache, init_name_caches, | 548 command, isolated_hash, storage, isolate_cache, outputs, init_name_caches, |
| 522 leak_temp_dir, result_json, root_dir, hard_timeout, grace_period, bot_file, | 549 leak_temp_dir, result_json, root_dir, hard_timeout, grace_period, bot_file, |
| 523 extra_args, install_packages_fn, use_symlinks): | 550 extra_args, install_packages_fn, use_symlinks): |
| 524 """Runs an executable and records execution metadata. | 551 """Runs an executable and records execution metadata. |
| 525 | 552 |
| 526 Either command or isolated_hash must be specified. | 553 Either command or isolated_hash must be specified. |
| 527 | 554 |
| 528 If isolated_hash is specified, downloads the dependencies in the cache, | 555 If isolated_hash is specified, downloads the dependencies in the cache, |
| 529 hardlinks them into a temporary directory and runs the command specified in | 556 hardlinks them into a temporary directory and runs the command specified in |
| 530 the .isolated. | 557 the .isolated. |
| 531 | 558 |
| (...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 578 'exit_code': None, | 605 'exit_code': None, |
| 579 'had_hard_timeout': False, | 606 'had_hard_timeout': False, |
| 580 'internal_failure': 'Was terminated before completion', | 607 'internal_failure': 'Was terminated before completion', |
| 581 'outputs_ref': None, | 608 'outputs_ref': None, |
| 582 'version': 5, | 609 'version': 5, |
| 583 } | 610 } |
| 584 tools.write_json(result_json, result, dense=True) | 611 tools.write_json(result_json, result, dense=True) |
| 585 | 612 |
| 586 # run_isolated exit code. Depends on if result_json is used or not. | 613 # run_isolated exit code. Depends on if result_json is used or not. |
| 587 result = map_and_run( | 614 result = map_and_run( |
| 588 command, isolated_hash, storage, isolate_cache, init_name_caches, | 615 command, isolated_hash, storage, isolate_cache, outputs, init_name_caches, |
| 589 leak_temp_dir, root_dir, hard_timeout, grace_period, bot_file, extra_args, | 616 leak_temp_dir, root_dir, hard_timeout, grace_period, bot_file, extra_args, |
| 590 install_packages_fn, use_symlinks) | 617 install_packages_fn, use_symlinks) |
| 591 logging.info('Result:\n%s', tools.format_json(result, dense=True)) | 618 logging.info('Result:\n%s', tools.format_json(result, dense=True)) |
| 592 | 619 |
| 593 if result_json: | 620 if result_json: |
| 594 # We've found tests to delete 'work' when quitting, causing an exception | 621 # We've found tests to delete 'work' when quitting, causing an exception |
| 595 # here. Try to recreate the directory if necessary. | 622 # here. Try to recreate the directory if necessary. |
| 596 file_path.ensure_tree(os.path.dirname(result_json)) | 623 file_path.ensure_tree(os.path.dirname(result_json)) |
| 597 tools.write_json(result_json, result, dense=True) | 624 tools.write_json(result_json, result, dense=True) |
| 598 # Only return 1 if there was an internal error. | 625 # Only return 1 if there was an internal error. |
| (...skipping 158 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 757 parser.add_option( | 784 parser.add_option( |
| 758 '--hard-timeout', type='float', help='Enforce hard timeout in execution') | 785 '--hard-timeout', type='float', help='Enforce hard timeout in execution') |
| 759 parser.add_option( | 786 parser.add_option( |
| 760 '--grace-period', type='float', | 787 '--grace-period', type='float', |
| 761 help='Grace period between SIGTERM and SIGKILL') | 788 help='Grace period between SIGTERM and SIGKILL') |
| 762 parser.add_option( | 789 parser.add_option( |
| 763 '--bot-file', | 790 '--bot-file', |
| 764 help='Path to a file describing the state of the host. The content is ' | 791 help='Path to a file describing the state of the host. The content is ' |
| 765 'defined by on_before_task() in bot_config.') | 792 'defined by on_before_task() in bot_config.') |
| 766 parser.add_option( | 793 parser.add_option( |
| 794 '--output', action='append', | |
| 795 help='Specifies an output to return. If no outputs are specified, all ' | |
| 796 'files located in $(ISOLATED_OUTDIR) will be returned; ' | |
| 797 'otherwise, outputs in both $(ISOLATED_OUTDIR) and those ' | |
| 798 'specified by --output option (there can be multiple) will be ' | |
| 799 'returned. Note that if a file in OUT_DIR has the same path ' | |
| 800 'as an --output option, the --output version will be returned.') | |
| 801 parser.add_option( | |
| 767 '-a', '--argsfile', | 802 '-a', '--argsfile', |
| 768 # This is actually handled in parse_args; it's included here purely so it | 803 # This is actually handled in parse_args; it's included here purely so it |
| 769 # can make it into the help text. | 804 # can make it into the help text. |
| 770 help='Specify a file containing a JSON array of arguments to this ' | 805 help='Specify a file containing a JSON array of arguments to this ' |
| 771 'script. If --argsfile is provided, no other argument may be ' | 806 'script. If --argsfile is provided, no other argument may be ' |
| 772 'provided on the command line.') | 807 'provided on the command line.') |
| 773 data_group = optparse.OptionGroup(parser, 'Data source') | 808 data_group = optparse.OptionGroup(parser, 'Data source') |
| 774 data_group.add_option( | 809 data_group.add_option( |
| 775 '-s', '--isolated', | 810 '-s', '--isolated', |
| 776 help='Hash of the .isolated to grab from the isolate server.') | 811 help='Hash of the .isolated to grab from the isolate server.') |
| (...skipping 108 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 885 storage = isolateserver.get_storage( | 920 storage = isolateserver.get_storage( |
| 886 options.isolate_server, options.namespace) | 921 options.isolate_server, options.namespace) |
| 887 with storage: | 922 with storage: |
| 888 # Hashing schemes used by |storage| and |isolate_cache| MUST match. | 923 # Hashing schemes used by |storage| and |isolate_cache| MUST match. |
| 889 assert storage.hash_algo == isolate_cache.hash_algo | 924 assert storage.hash_algo == isolate_cache.hash_algo |
| 890 return run_tha_test( | 925 return run_tha_test( |
| 891 command, | 926 command, |
| 892 options.isolated, | 927 options.isolated, |
| 893 storage, | 928 storage, |
| 894 isolate_cache, | 929 isolate_cache, |
| 930 options.output, | |
| 895 init_named_caches, | 931 init_named_caches, |
| 896 options.leak_temp_dir, | 932 options.leak_temp_dir, |
| 897 options.json, options.root_dir, | 933 options.json, options.root_dir, |
| 898 options.hard_timeout, | 934 options.hard_timeout, |
| 899 options.grace_period, | 935 options.grace_period, |
| 900 options.bot_file, args, | 936 options.bot_file, args, |
| 901 install_packages_fn, | 937 install_packages_fn, |
| 902 options.use_symlinks) | 938 options.use_symlinks) |
| 903 return run_tha_test( | 939 return run_tha_test( |
| 904 command, | 940 command, |
| 905 options.isolated, | 941 options.isolated, |
| 906 None, | 942 None, |
| 907 isolate_cache, | 943 isolate_cache, |
| 944 options.output, | |
| 908 init_named_caches, | 945 init_named_caches, |
| 909 options.leak_temp_dir, | 946 options.leak_temp_dir, |
| 910 options.json, | 947 options.json, |
| 911 options.root_dir, | 948 options.root_dir, |
| 912 options.hard_timeout, | 949 options.hard_timeout, |
| 913 options.grace_period, | 950 options.grace_period, |
| 914 options.bot_file, args, | 951 options.bot_file, args, |
| 915 install_packages_fn, | 952 install_packages_fn, |
| 916 options.use_symlinks) | 953 options.use_symlinks) |
| 917 except (cipd.Error, named_cache.Error) as ex: | 954 except (cipd.Error, named_cache.Error) as ex: |
| 918 print >> sys.stderr, ex.message | 955 print >> sys.stderr, ex.message |
| 919 return 1 | 956 return 1 |
| 920 | 957 |
| 921 | 958 |
| 922 if __name__ == '__main__': | 959 if __name__ == '__main__': |
| 923 subprocess42.inhibit_os_error_reporting() | 960 subprocess42.inhibit_os_error_reporting() |
| 924 # Ensure that we are always running with the correct encoding. | 961 # Ensure that we are always running with the correct encoding. |
| 925 fix_encoding.fix_encoding() | 962 fix_encoding.fix_encoding() |
| 926 file_path.enable_symlink() | 963 file_path.enable_symlink() |
| 927 | 964 |
| 928 sys.exit(main(sys.argv[1:])) | 965 sys.exit(main(sys.argv[1:])) |
| OLD | NEW |