Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 # Copyright 2015 The LUCI Authors. All rights reserved. | 1 # Copyright 2015 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 """Internal bot API handlers.""" | 5 """Internal bot API handlers.""" |
| 6 | 6 |
| 7 import base64 | 7 import base64 |
| 8 import json | 8 import json |
| 9 import logging | 9 import logging |
| 10 import re | 10 import re |
| (...skipping 614 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 625 self.send_response({}) | 625 self.send_response({}) |
| 626 | 626 |
| 627 | 627 |
| 628 class BotTaskUpdateHandler(_BotApiHandler): | 628 class BotTaskUpdateHandler(_BotApiHandler): |
| 629 """Receives updates from a Bot for a task. | 629 """Receives updates from a Bot for a task. |
| 630 | 630 |
| 631 The handler verifies packets are processed in order and will refuse | 631 The handler verifies packets are processed in order and will refuse |
| 632 out-of-order packets. | 632 out-of-order packets. |
| 633 """ | 633 """ |
| 634 ACCEPTED_KEYS = { | 634 ACCEPTED_KEYS = { |
| 635 u'bot_overhead', u'cipd_stats', u'cost_usd', u'duration', u'exit_code', | 635 u'bot_overhead', u'cipd_pins', u'cipd_stats', u'cost_usd', u'duration', |
| 636 u'hard_timeout', u'id', u'io_timeout', u'isolated_stats', u'output', | 636 u'exit_code', u'hard_timeout', u'id', u'io_timeout', u'isolated_stats', |
| 637 u'output_chunk_start', u'outputs_ref', u'task_id', | 637 u'output', u'output_chunk_start', u'outputs_ref', u'task_id', |
| 638 } | 638 } |
| 639 REQUIRED_KEYS = {u'id', u'task_id'} | 639 REQUIRED_KEYS = {u'id', u'task_id'} |
| 640 | 640 |
| 641 @auth.public # auth happens in bot_auth.validate_bot_id_and_fetch_config() | 641 @auth.public # auth happens in bot_auth.validate_bot_id_and_fetch_config() |
| 642 def post(self, task_id=None): | 642 def post(self, task_id=None): |
| 643 # Unlike handshake and poll, we do not accept invalid keys here. This code | 643 # Unlike handshake and poll, we do not accept invalid keys here. This code |
| 644 # path is much more strict. | 644 # path is much more strict. |
| 645 request = self.parse_body() | 645 request = self.parse_body() |
| 646 msg = log_unexpected_subset_keys( | 646 msg = log_unexpected_subset_keys( |
| 647 self.ACCEPTED_KEYS, self.REQUIRED_KEYS, request, self.request, 'bot', | 647 self.ACCEPTED_KEYS, self.REQUIRED_KEYS, request, self.request, 'bot', |
| 648 'keys') | 648 'keys') |
| 649 if msg: | 649 if msg: |
| 650 self.abort_with_error(400, error=msg) | 650 self.abort_with_error(400, error=msg) |
| 651 | 651 |
| 652 bot_id = request['id'] | 652 bot_id = request['id'] |
| 653 task_id = request['task_id'] | 653 task_id = request['task_id'] |
| 654 | 654 |
| 655 # Make sure bot self-reported ID matches the authentication token. Raises | 655 # Make sure bot self-reported ID matches the authentication token. Raises |
| 656 # auth.AuthorizationError if not. | 656 # auth.AuthorizationError if not. |
| 657 bot_auth.validate_bot_id_and_fetch_config(bot_id) | 657 bot_auth.validate_bot_id_and_fetch_config(bot_id) |
| 658 | 658 |
| 659 bot_overhead = request.get('bot_overhead') | 659 bot_overhead = request.get('bot_overhead') |
| 660 cipd_pins = request.get('cipd_pins') | |
| 661 cipd_stats = request.get('cipd_stats') | |
| 660 cost_usd = request.get('cost_usd', 0) | 662 cost_usd = request.get('cost_usd', 0) |
| 661 duration = request.get('duration') | 663 duration = request.get('duration') |
| 662 exit_code = request.get('exit_code') | 664 exit_code = request.get('exit_code') |
| 663 hard_timeout = request.get('hard_timeout') | 665 hard_timeout = request.get('hard_timeout') |
| 664 io_timeout = request.get('io_timeout') | 666 io_timeout = request.get('io_timeout') |
| 665 isolated_stats = request.get('isolated_stats') | 667 isolated_stats = request.get('isolated_stats') |
| 666 cipd_stats = request.get('cipd_stats') | |
| 667 output = request.get('output') | 668 output = request.get('output') |
| 668 output_chunk_start = request.get('output_chunk_start') | 669 output_chunk_start = request.get('output_chunk_start') |
| 669 outputs_ref = request.get('outputs_ref') | 670 outputs_ref = request.get('outputs_ref') |
| 670 | 671 |
| 671 if (isolated_stats or cipd_stats) and bot_overhead is None: | 672 if (isolated_stats or cipd_stats) and bot_overhead is None: |
| 672 ereporter2.log_request( | 673 ereporter2.log_request( |
| 673 request=self.request, | 674 request=self.request, |
| 674 source='server', | 675 source='server', |
| 675 category='task_failure', | 676 category='task_failure', |
| 676 message='Failed to update task: %s' % task_id) | 677 message='Failed to update task: %s' % task_id) |
| (...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 713 logging.error('Failed to decode output\n%s\n%r', e, output) | 714 logging.error('Failed to decode output\n%s\n%r', e, output) |
| 714 output = output.encode('ascii', 'replace') | 715 output = output.encode('ascii', 'replace') |
| 715 except TypeError as e: | 716 except TypeError as e: |
| 716 # Save the output as-is instead. The error will be logged in ereporter2 | 717 # Save the output as-is instead. The error will be logged in ereporter2 |
| 717 # and returning a HTTP 500 would only force the bot to stay in a retry | 718 # and returning a HTTP 500 would only force the bot to stay in a retry |
| 718 # loop. | 719 # loop. |
| 719 logging.error('Failed to decode output\n%s\n%r', e, output) | 720 logging.error('Failed to decode output\n%s\n%r', e, output) |
| 720 if outputs_ref: | 721 if outputs_ref: |
| 721 outputs_ref = task_request.FilesRef(**outputs_ref) | 722 outputs_ref = task_request.FilesRef(**outputs_ref) |
| 722 | 723 |
| 724 if cipd_pins: | |
| 725 cipd_pins = task_result.CipdPins( | |
| 726 client_package=task_request.CipdPackage( | |
| 727 **cipd_pins['client_package']), | |
|
M-A Ruel
2016/08/26 23:19:51
arguments aligned at +4 (everywhere)
iannucci
2016/08/29 22:08:40
Done
| |
| 728 packages=[ | |
| 729 task_request.CipdPackage(**args) for args in cipd_pins['packages'] ] | |
| 730 ) | |
| 731 | |
| 723 try: | 732 try: |
| 724 state = task_scheduler.bot_update_task( | 733 state = task_scheduler.bot_update_task( |
| 725 run_result_key=run_result_key, | 734 run_result_key=run_result_key, |
| 726 bot_id=bot_id, | 735 bot_id=bot_id, |
| 727 output=output, | 736 output=output, |
| 728 output_chunk_start=output_chunk_start, | 737 output_chunk_start=output_chunk_start, |
| 729 exit_code=exit_code, | 738 exit_code=exit_code, |
| 730 duration=duration, | 739 duration=duration, |
| 731 hard_timeout=hard_timeout, | 740 hard_timeout=hard_timeout, |
| 732 io_timeout=io_timeout, | 741 io_timeout=io_timeout, |
| 733 cost_usd=cost_usd, | 742 cost_usd=cost_usd, |
| 734 outputs_ref=outputs_ref, | 743 outputs_ref=outputs_ref, |
| 744 cipd_pins=cipd_pins, | |
| 735 performance_stats=performance_stats) | 745 performance_stats=performance_stats) |
| 736 if not state: | 746 if not state: |
| 737 logging.info('Failed to update, please retry') | 747 logging.info('Failed to update, please retry') |
| 738 self.abort_with_error(500, error='Failed to update, please retry') | 748 self.abort_with_error(500, error='Failed to update, please retry') |
| 739 | 749 |
| 740 if state in (task_result.State.COMPLETED, task_result.State.TIMED_OUT): | 750 if state in (task_result.State.COMPLETED, task_result.State.TIMED_OUT): |
| 741 action = 'task_completed' | 751 action = 'task_completed' |
| 742 elif state == task_result.State.CANCELED: | 752 elif state == task_result.State.CANCELED: |
| 743 action = 'task_canceled' | 753 action = 'task_canceled' |
| 744 else: | 754 else: |
| (...skipping 95 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 840 ('/swarming/api/v1/bot/poll', BotPollHandler), | 850 ('/swarming/api/v1/bot/poll', BotPollHandler), |
| 841 ('/swarming/api/v1/bot/server_ping', ServerPingHandler), | 851 ('/swarming/api/v1/bot/server_ping', ServerPingHandler), |
| 842 ('/swarming/api/v1/bot/task_update', BotTaskUpdateHandler), | 852 ('/swarming/api/v1/bot/task_update', BotTaskUpdateHandler), |
| 843 ('/swarming/api/v1/bot/task_update/<task_id:[a-f0-9]+>', | 853 ('/swarming/api/v1/bot/task_update/<task_id:[a-f0-9]+>', |
| 844 BotTaskUpdateHandler), | 854 BotTaskUpdateHandler), |
| 845 ('/swarming/api/v1/bot/task_error', BotTaskErrorHandler), | 855 ('/swarming/api/v1/bot/task_error', BotTaskErrorHandler), |
| 846 ('/swarming/api/v1/bot/task_error/<task_id:[a-f0-9]+>', | 856 ('/swarming/api/v1/bot/task_error/<task_id:[a-f0-9]+>', |
| 847 BotTaskErrorHandler), | 857 BotTaskErrorHandler), |
| 848 ] | 858 ] |
| 849 return [webapp2.Route(*i) for i in routes] | 859 return [webapp2.Route(*i) for i in routes] |
| OLD | NEW |