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 611 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
622 self.send_response({}) | 622 self.send_response({}) |
623 | 623 |
624 | 624 |
625 class BotTaskUpdateHandler(_BotApiHandler): | 625 class BotTaskUpdateHandler(_BotApiHandler): |
626 """Receives updates from a Bot for a task. | 626 """Receives updates from a Bot for a task. |
627 | 627 |
628 The handler verifies packets are processed in order and will refuse | 628 The handler verifies packets are processed in order and will refuse |
629 out-of-order packets. | 629 out-of-order packets. |
630 """ | 630 """ |
631 ACCEPTED_KEYS = { | 631 ACCEPTED_KEYS = { |
632 u'bot_overhead', u'cipd_stats', u'cost_usd', u'duration', u'exit_code', | 632 u'bot_overhead', u'cipd_pins', u'cipd_stats', u'cost_usd', u'duration', |
633 u'hard_timeout', u'id', u'io_timeout', u'isolated_stats', u'output', | 633 u'exit_code', u'hard_timeout', u'id', u'io_timeout', u'isolated_stats', |
634 u'output_chunk_start', u'outputs_ref', u'task_id', | 634 u'output', u'output_chunk_start', u'outputs_ref', u'task_id', |
635 } | 635 } |
636 REQUIRED_KEYS = {u'id', u'task_id'} | 636 REQUIRED_KEYS = {u'id', u'task_id'} |
637 | 637 |
638 @auth.public # auth happens in bot_auth.validate_bot_id_and_fetch_config() | 638 @auth.public # auth happens in bot_auth.validate_bot_id_and_fetch_config() |
639 def post(self, task_id=None): | 639 def post(self, task_id=None): |
640 # Unlike handshake and poll, we do not accept invalid keys here. This code | 640 # Unlike handshake and poll, we do not accept invalid keys here. This code |
641 # path is much more strict. | 641 # path is much more strict. |
642 request = self.parse_body() | 642 request = self.parse_body() |
643 msg = log_unexpected_subset_keys( | 643 msg = log_unexpected_subset_keys( |
644 self.ACCEPTED_KEYS, self.REQUIRED_KEYS, request, self.request, 'bot', | 644 self.ACCEPTED_KEYS, self.REQUIRED_KEYS, request, self.request, 'bot', |
645 'keys') | 645 'keys') |
646 if msg: | 646 if msg: |
647 self.abort_with_error(400, error=msg) | 647 self.abort_with_error(400, error=msg) |
648 | 648 |
649 bot_id = request['id'] | 649 bot_id = request['id'] |
650 task_id = request['task_id'] | 650 task_id = request['task_id'] |
651 | 651 |
652 # Make sure bot self-reported ID matches the authentication token. Raises | 652 # Make sure bot self-reported ID matches the authentication token. Raises |
653 # auth.AuthorizationError if not. | 653 # auth.AuthorizationError if not. |
654 bot_auth.validate_bot_id_and_fetch_config(bot_id) | 654 bot_auth.validate_bot_id_and_fetch_config(bot_id) |
655 | 655 |
656 bot_overhead = request.get('bot_overhead') | 656 bot_overhead = request.get('bot_overhead') |
| 657 cipd_pins = request.get('cipd_pins') |
| 658 cipd_stats = request.get('cipd_stats') |
657 cost_usd = request.get('cost_usd', 0) | 659 cost_usd = request.get('cost_usd', 0) |
658 duration = request.get('duration') | 660 duration = request.get('duration') |
659 exit_code = request.get('exit_code') | 661 exit_code = request.get('exit_code') |
660 hard_timeout = request.get('hard_timeout') | 662 hard_timeout = request.get('hard_timeout') |
661 io_timeout = request.get('io_timeout') | 663 io_timeout = request.get('io_timeout') |
662 isolated_stats = request.get('isolated_stats') | 664 isolated_stats = request.get('isolated_stats') |
663 cipd_stats = request.get('cipd_stats') | |
664 output = request.get('output') | 665 output = request.get('output') |
665 output_chunk_start = request.get('output_chunk_start') | 666 output_chunk_start = request.get('output_chunk_start') |
666 outputs_ref = request.get('outputs_ref') | 667 outputs_ref = request.get('outputs_ref') |
667 | 668 |
668 if (isolated_stats or cipd_stats) and bot_overhead is None: | 669 if (isolated_stats or cipd_stats) and bot_overhead is None: |
669 ereporter2.log_request( | 670 ereporter2.log_request( |
670 request=self.request, | 671 request=self.request, |
671 source='server', | 672 source='server', |
672 category='task_failure', | 673 category='task_failure', |
673 message='Failed to update task: %s' % task_id) | 674 message='Failed to update task: %s' % task_id) |
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
710 logging.error('Failed to decode output\n%s\n%r', e, output) | 711 logging.error('Failed to decode output\n%s\n%r', e, output) |
711 output = output.encode('ascii', 'replace') | 712 output = output.encode('ascii', 'replace') |
712 except TypeError as e: | 713 except TypeError as e: |
713 # Save the output as-is instead. The error will be logged in ereporter2 | 714 # Save the output as-is instead. The error will be logged in ereporter2 |
714 # and returning a HTTP 500 would only force the bot to stay in a retry | 715 # and returning a HTTP 500 would only force the bot to stay in a retry |
715 # loop. | 716 # loop. |
716 logging.error('Failed to decode output\n%s\n%r', e, output) | 717 logging.error('Failed to decode output\n%s\n%r', e, output) |
717 if outputs_ref: | 718 if outputs_ref: |
718 outputs_ref = task_request.FilesRef(**outputs_ref) | 719 outputs_ref = task_request.FilesRef(**outputs_ref) |
719 | 720 |
| 721 if cipd_pins: |
| 722 cipd_pins = [task_request.CipdPackage(**pin) for pin in cipd_pins] |
| 723 |
720 try: | 724 try: |
721 state = task_scheduler.bot_update_task( | 725 state = task_scheduler.bot_update_task( |
722 run_result_key=run_result_key, | 726 run_result_key=run_result_key, |
723 bot_id=bot_id, | 727 bot_id=bot_id, |
724 output=output, | 728 output=output, |
725 output_chunk_start=output_chunk_start, | 729 output_chunk_start=output_chunk_start, |
726 exit_code=exit_code, | 730 exit_code=exit_code, |
727 duration=duration, | 731 duration=duration, |
728 hard_timeout=hard_timeout, | 732 hard_timeout=hard_timeout, |
729 io_timeout=io_timeout, | 733 io_timeout=io_timeout, |
730 cost_usd=cost_usd, | 734 cost_usd=cost_usd, |
731 outputs_ref=outputs_ref, | 735 outputs_ref=outputs_ref, |
| 736 cipd_pins=cipd_pins, |
732 performance_stats=performance_stats) | 737 performance_stats=performance_stats) |
733 if not state: | 738 if not state: |
734 logging.info('Failed to update, please retry') | 739 logging.info('Failed to update, please retry') |
735 self.abort_with_error(500, error='Failed to update, please retry') | 740 self.abort_with_error(500, error='Failed to update, please retry') |
736 | 741 |
737 if state in (task_result.State.COMPLETED, task_result.State.TIMED_OUT): | 742 if state in (task_result.State.COMPLETED, task_result.State.TIMED_OUT): |
738 action = 'task_completed' | 743 action = 'task_completed' |
739 elif state == task_result.State.CANCELED: | 744 elif state == task_result.State.CANCELED: |
740 action = 'task_canceled' | 745 action = 'task_canceled' |
741 else: | 746 else: |
(...skipping 95 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
837 ('/swarming/api/v1/bot/poll', BotPollHandler), | 842 ('/swarming/api/v1/bot/poll', BotPollHandler), |
838 ('/swarming/api/v1/bot/server_ping', ServerPingHandler), | 843 ('/swarming/api/v1/bot/server_ping', ServerPingHandler), |
839 ('/swarming/api/v1/bot/task_update', BotTaskUpdateHandler), | 844 ('/swarming/api/v1/bot/task_update', BotTaskUpdateHandler), |
840 ('/swarming/api/v1/bot/task_update/<task_id:[a-f0-9]+>', | 845 ('/swarming/api/v1/bot/task_update/<task_id:[a-f0-9]+>', |
841 BotTaskUpdateHandler), | 846 BotTaskUpdateHandler), |
842 ('/swarming/api/v1/bot/task_error', BotTaskErrorHandler), | 847 ('/swarming/api/v1/bot/task_error', BotTaskErrorHandler), |
843 ('/swarming/api/v1/bot/task_error/<task_id:[a-f0-9]+>', | 848 ('/swarming/api/v1/bot/task_error/<task_id:[a-f0-9]+>', |
844 BotTaskErrorHandler), | 849 BotTaskErrorHandler), |
845 ] | 850 ] |
846 return [webapp2.Route(*i) for i in routes] | 851 return [webapp2.Route(*i) for i in routes] |
OLD | NEW |