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

Side by Side Diff: client/run_isolated.py

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