Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 # Copyright 2013 The LUCI Authors. All rights reserved. | 1 # Copyright 2013 The LUCI Authors. All rights reserved. |
| 2 # Use of this source code is governed under the Apache License, Version 2.0 | 2 # Use of this source code is governed under the Apache License, Version 2.0 |
| 3 # that can be found in the LICENSE file. | 3 # that can be found in the LICENSE file. |
| 4 | 4 |
| 5 """Runs a Swarming task. | 5 """Runs a Swarming task. |
| 6 | 6 |
| 7 Downloads all the necessary files to run the task, executes the command and | 7 Downloads all the necessary files to run the task, executes the command and |
| 8 streams results back to the Swarming server. | 8 streams results back to the Swarming server. |
| 9 | 9 |
| 10 The process exit code is 0 when the task was executed, even if the task itself | 10 The process exit code is 0 when the task was executed, even if the task itself |
| 11 failed. If there's any failure in the setup or teardown, like invalid packet | 11 failed. If there's any failure in the setup or teardown, like invalid packet |
| 12 response, failure to contact the server, etc, a non zero exit code is used. It's | 12 response, failure to contact the server, etc, a non zero exit code is used. It's |
| 13 up to the calling process (bot_main.py) to signal that there was an internal | 13 up to the calling process (bot_main.py) to signal that there was an internal |
| 14 failure and to cancel this task run and ask the server to retry it. | 14 failure and to cancel this task run and ask the server to retry it. |
| 15 """ | 15 """ |
| 16 | 16 |
| 17 import base64 | 17 import base64 |
| 18 import json | 18 import json |
| 19 import logging | 19 import logging |
| 20 import optparse | 20 import optparse |
| 21 import os | 21 import os |
| 22 import signal | 22 import signal |
| 23 import sys | 23 import sys |
| 24 import time | 24 import time |
| 25 | 25 |
| 26 from utils import file_path | |
| 26 from utils import net | 27 from utils import net |
| 27 from utils import on_error | 28 from utils import on_error |
| 28 from utils import subprocess42 | 29 from utils import subprocess42 |
| 29 from utils import zip_package | 30 from utils import zip_package |
| 30 | 31 |
| 31 import bot_auth | 32 import bot_auth |
| 32 import file_reader | 33 import file_reader |
| 33 | 34 |
| 34 | 35 |
| 35 # Path to this file or the zip containing this file. | 36 # Path to this file or the zip containing this file. |
| (...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 74 | 75 |
| 75 def get_run_isolated(): | 76 def get_run_isolated(): |
| 76 """Returns the path to itself to run run_isolated. | 77 """Returns the path to itself to run run_isolated. |
| 77 | 78 |
| 78 Mocked in test to point to the real run_isolated.py script. | 79 Mocked in test to point to the real run_isolated.py script. |
| 79 """ | 80 """ |
| 80 return [sys.executable, THIS_FILE, 'run_isolated'] | 81 return [sys.executable, THIS_FILE, 'run_isolated'] |
| 81 | 82 |
| 82 | 83 |
| 83 def get_isolated_cmd( | 84 def get_isolated_cmd( |
| 84 work_dir, task_details, isolated_result, min_free_space, bot_file): | 85 work_dir, task_details, isolated_result, bot_file, package_list, |
| 86 min_free_space): | |
| 85 """Returns the command to call run_isolated. Mocked in tests.""" | 87 """Returns the command to call run_isolated. Mocked in tests.""" |
| 86 assert (bool(task_details.command) != | 88 assert (bool(task_details.command) != |
| 87 bool(task_details.isolated and task_details.isolated.get('input'))) | 89 bool(task_details.isolated and task_details.isolated.get('input'))) |
| 88 bot_dir = os.path.dirname(work_dir) | 90 bot_dir = os.path.dirname(work_dir) |
| 89 if os.path.isfile(isolated_result): | 91 if os.path.isfile(isolated_result): |
| 90 os.remove(isolated_result) | 92 os.remove(isolated_result) |
| 91 cmd = get_run_isolated() | 93 cmd = get_run_isolated() |
| 92 | 94 |
| 93 if task_details.isolated: | 95 if task_details.isolated: |
| 94 cmd.extend( | 96 cmd.extend( |
| 95 [ | 97 [ |
| 96 '-I', task_details.isolated['server'].encode('utf-8'), | 98 '-I', task_details.isolated['server'].encode('utf-8'), |
| 97 '--namespace', task_details.isolated['namespace'].encode('utf-8'), | 99 '--namespace', task_details.isolated['namespace'].encode('utf-8'), |
| 98 ]) | 100 ]) |
| 99 isolated_input = task_details.isolated.get('input') | 101 isolated_input = task_details.isolated.get('input') |
| 100 if isolated_input: | 102 if isolated_input: |
| 101 cmd.extend( | 103 cmd.extend( |
| 102 [ | 104 [ |
| 103 '--isolated', isolated_input, | 105 '--isolated', isolated_input, |
| 104 ]) | 106 ]) |
| 105 | 107 |
| 106 if task_details.cipd_input and task_details.cipd_input.get('packages'): | 108 if task_details.cipd_input and task_details.cipd_input.get('packages'): |
| 107 to_pkg = lambda p: '%s:%s' % (p['package_name'], p['version']) | 109 package_json = { |
| 110 # Package JSON object format matches. | |
|
M-A Ruel
2016/06/15 17:28:27
Can't parse this sentence
nodir
2016/06/15 17:53:42
Done.
| |
| 111 'packages': task_details.cipd_input['packages'], | |
| 112 } | |
| 113 with open(package_list, 'w') as f: | |
|
M-A Ruel
2016/06/15 17:28:27
'wb'
nodir
2016/06/15 17:53:42
Done.
| |
| 114 json.dump(package_json, f) | |
| 108 cmd.extend( | 115 cmd.extend( |
| 109 [ | 116 [ |
| 110 '--cipd-cache', os.path.join(bot_dir, 'cipd_cache'), | 117 '--cipd-cache', os.path.join(bot_dir, 'cipd_cache'), |
| 111 '--cipd-client-package', | 118 '--cipd-client-package', |
| 112 to_pkg(task_details.cipd_input.get('client_package')), | 119 task_details.cipd_input['client_package']['package_name'], |
| 120 '--cipd-client-version', | |
| 121 task_details.cipd_input['client_package']['version'], | |
| 122 '--cipd-package-list', package_list, | |
| 113 '--cipd-server', task_details.cipd_input.get('server'), | 123 '--cipd-server', task_details.cipd_input.get('server'), |
| 114 ]) | 124 ]) |
| 115 for p in task_details.cipd_input['packages']: | |
| 116 cmd.extend(['--cipd-package', to_pkg(p)]) | |
| 117 | 125 |
| 118 cmd.extend( | 126 cmd.extend( |
| 119 [ | 127 [ |
| 120 '--json', isolated_result, | 128 '--json', isolated_result, |
| 121 '--log-file', os.path.join(bot_dir, 'logs', 'run_isolated.log'), | 129 '--log-file', os.path.join(bot_dir, 'logs', 'run_isolated.log'), |
| 122 '--cache', os.path.join(bot_dir, 'isolated_cache'), | 130 '--cache', os.path.join(bot_dir, 'isolated_cache'), |
| 123 '--root-dir', os.path.join(work_dir, 'isolated'), | 131 '--root-dir', os.path.join(work_dir, 'isolated'), |
| 124 ]) | 132 ]) |
| 125 if min_free_space: | 133 if min_free_space: |
| 126 cmd.extend(('--min-free-space', str(min_free_space))) | 134 cmd.extend(('--min-free-space', str(min_free_space))) |
| (...skipping 235 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 362 # Signal the command is about to be started. | 370 # Signal the command is about to be started. |
| 363 last_packet = start = now = monotonic_time() | 371 last_packet = start = now = monotonic_time() |
| 364 params = { | 372 params = { |
| 365 'cost_usd': cost_usd_hour * (now - task_start) / 60. / 60., | 373 'cost_usd': cost_usd_hour * (now - task_start) / 60. / 60., |
| 366 'id': task_details.bot_id, | 374 'id': task_details.bot_id, |
| 367 'task_id': task_details.task_id, | 375 'task_id': task_details.task_id, |
| 368 } | 376 } |
| 369 post_update(swarming_server, headers_cb(), params, None, '', 0) | 377 post_update(swarming_server, headers_cb(), params, None, '', 0) |
| 370 | 378 |
| 371 isolated_result = os.path.join(work_dir, 'isolated_result.json') | 379 isolated_result = os.path.join(work_dir, 'isolated_result.json') |
| 380 package_list = os.path.join(work_dir, 'package_list.json') | |
| 372 cmd = get_isolated_cmd( | 381 cmd = get_isolated_cmd( |
| 373 work_dir, task_details, isolated_result, min_free_space, bot_file) | 382 work_dir, task_details, isolated_result, bot_file, package_list, |
| 383 min_free_space) | |
| 374 # Hard timeout enforcement is deferred to run_isolated. Grace is doubled to | 384 # Hard timeout enforcement is deferred to run_isolated. Grace is doubled to |
| 375 # give one 'grace_period' slot to the child process and one slot to upload | 385 # give one 'grace_period' slot to the child process and one slot to upload |
| 376 # the results back. | 386 # the results back. |
| 377 task_details.hard_timeout = 0 | 387 task_details.hard_timeout = 0 |
| 378 if task_details.grace_period: | 388 if task_details.grace_period: |
| 379 task_details.grace_period *= 2 | 389 task_details.grace_period *= 2 |
| 380 | 390 |
| 381 try: | 391 try: |
| 382 # TODO(maruel): Support both channels independently and display stderr in | 392 # TODO(maruel): Support both channels independently and display stderr in |
| 383 # red. | 393 # red. |
| (...skipping 173 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 557 swarming_server, headers_cb(), params, exit_code, | 567 swarming_server, headers_cb(), params, exit_code, |
| 558 stdout, output_chunk_start) | 568 stdout, output_chunk_start) |
| 559 return { | 569 return { |
| 560 u'exit_code': exit_code, | 570 u'exit_code': exit_code, |
| 561 u'hard_timeout': had_hard_timeout, | 571 u'hard_timeout': had_hard_timeout, |
| 562 u'io_timeout': had_io_timeout, | 572 u'io_timeout': had_io_timeout, |
| 563 u'must_signal_internal_failure': must_signal_internal_failure, | 573 u'must_signal_internal_failure': must_signal_internal_failure, |
| 564 u'version': OUT_VERSION, | 574 u'version': OUT_VERSION, |
| 565 } | 575 } |
| 566 finally: | 576 finally: |
| 567 try: | 577 file_path.try_remove(unicode(isolated_result)) |
| 568 os.remove(isolated_result) | 578 file_path.try_remove(unicode(package_list)) |
| 569 except OSError: | |
| 570 pass | |
| 571 stop_headers_reader() | 579 stop_headers_reader() |
| 572 | 580 |
| 573 | 581 |
| 574 def main(args): | 582 def main(args): |
| 575 subprocess42.inhibit_os_error_reporting() | 583 subprocess42.inhibit_os_error_reporting() |
| 576 parser = optparse.OptionParser(description=sys.modules[__name__].__doc__) | 584 parser = optparse.OptionParser(description=sys.modules[__name__].__doc__) |
| 577 parser.add_option('--in-file', help='Name of the request file') | 585 parser.add_option('--in-file', help='Name of the request file') |
| 578 parser.add_option( | 586 parser.add_option( |
| 579 '--out-file', help='Name of the JSON file to write a task summary to') | 587 '--out-file', help='Name of the JSON file to write a task summary to') |
| 580 parser.add_option( | 588 parser.add_option( |
| (...skipping 19 matching lines...) Expand all Loading... | |
| 600 options.start = now | 608 options.start = now |
| 601 | 609 |
| 602 try: | 610 try: |
| 603 load_and_run( | 611 load_and_run( |
| 604 options.in_file, options.swarming_server, options.cost_usd_hour, | 612 options.in_file, options.swarming_server, options.cost_usd_hour, |
| 605 options.start, options.out_file, options.min_free_space, | 613 options.start, options.out_file, options.min_free_space, |
| 606 options.bot_file) | 614 options.bot_file) |
| 607 return 0 | 615 return 0 |
| 608 finally: | 616 finally: |
| 609 logging.info('quitting') | 617 logging.info('quitting') |
| OLD | NEW |