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

Side by Side Diff: client/run_isolated.py

Issue 2440353004: Return specific files, not just those in $(ISOLATED_OUTDIR) (Closed)
Patch Set: Fix merge conflicts with 8ccc159 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 273 matching lines...) Expand 10 before | Expand all | Expand 10 after
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
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
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
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
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
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
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:]))
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