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 |