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 514 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 525 self.send_response({}) | 525 self.send_response({}) |
| 526 | 526 |
| 527 | 527 |
| 528 class BotTaskUpdateHandler(_BotApiHandler): | 528 class BotTaskUpdateHandler(_BotApiHandler): |
| 529 """Receives updates from a Bot for a task. | 529 """Receives updates from a Bot for a task. |
| 530 | 530 |
| 531 The handler verifies packets are processed in order and will refuse | 531 The handler verifies packets are processed in order and will refuse |
| 532 out-of-order packets. | 532 out-of-order packets. |
| 533 """ | 533 """ |
| 534 ACCEPTED_KEYS = { | 534 ACCEPTED_KEYS = { |
| 535 u'bot_overhead', u'cost_usd', u'duration', u'exit_code', | 535 u'bot_overhead', u'cipd_stats', u'cost_usd', u'duration', u'exit_code', |
| 536 u'hard_timeout', u'id', u'io_timeout', u'isolated_stats', u'output', | 536 u'hard_timeout', u'id', u'io_timeout', u'isolated_stats', u'output', |
| 537 u'output_chunk_start', u'outputs_ref', u'task_id', | 537 u'output_chunk_start', u'outputs_ref', u'task_id', |
| 538 } | 538 } |
| 539 REQUIRED_KEYS = {u'id', u'task_id'} | 539 REQUIRED_KEYS = {u'id', u'task_id'} |
| 540 | 540 |
| 541 @auth.require(acl.is_bot) | 541 @auth.require(acl.is_bot) |
| 542 def post(self, task_id=None): | 542 def post(self, task_id=None): |
| 543 # Unlike handshake and poll, we do not accept invalid keys here. This code | 543 # Unlike handshake and poll, we do not accept invalid keys here. This code |
| 544 # path is much more strict. | 544 # path is much more strict. |
| 545 request = self.parse_body() | 545 request = self.parse_body() |
| 546 msg = log_unexpected_subset_keys( | 546 msg = log_unexpected_subset_keys( |
| 547 self.ACCEPTED_KEYS, self.REQUIRED_KEYS, request, self.request, 'bot', | 547 self.ACCEPTED_KEYS, self.REQUIRED_KEYS, request, self.request, 'bot', |
| 548 'keys') | 548 'keys') |
| 549 if msg: | 549 if msg: |
| 550 self.abort_with_error(400, error=msg) | 550 self.abort_with_error(400, error=msg) |
| 551 | 551 |
| 552 bot_id = request['id'] | 552 bot_id = request['id'] |
| 553 cost_usd = request['cost_usd'] | 553 cost_usd = request['cost_usd'] |
| 554 task_id = request['task_id'] | 554 task_id = request['task_id'] |
| 555 | 555 |
| 556 # Make sure bot self-reported ID matches the authentication token. | 556 # Make sure bot self-reported ID matches the authentication token. |
| 557 bot_auth.validate_bot_id(bot_id) | 557 bot_auth.validate_bot_id(bot_id) |
| 558 | 558 |
| 559 bot_overhead = request.get('bot_overhead') | 559 bot_overhead = request.get('bot_overhead') |
| 560 duration = request.get('duration') | 560 duration = request.get('duration') |
| 561 exit_code = request.get('exit_code') | 561 exit_code = request.get('exit_code') |
| 562 hard_timeout = request.get('hard_timeout') | 562 hard_timeout = request.get('hard_timeout') |
| 563 io_timeout = request.get('io_timeout') | 563 io_timeout = request.get('io_timeout') |
| 564 isolated_stats = request.get('isolated_stats') | 564 isolated_stats = request.get('isolated_stats') |
| 565 cipd_stats = request.get('cipd_stats') | |
| 565 output = request.get('output') | 566 output = request.get('output') |
| 566 output_chunk_start = request.get('output_chunk_start') | 567 output_chunk_start = request.get('output_chunk_start') |
| 567 outputs_ref = request.get('outputs_ref') | 568 outputs_ref = request.get('outputs_ref') |
| 568 | 569 |
| 569 if isolated_stats and bot_overhead is None: | 570 if (isolated_stats or cipd_stats) and bot_overhead is None: |
| 570 ereporter2.log_request( | 571 ereporter2.log_request( |
| 571 request=self.request, | 572 request=self.request, |
| 572 source='server', | 573 source='server', |
| 573 category='task_failure', | 574 category='task_failure', |
| 574 message='Failed to update task: %s' % task_id) | 575 message='Failed to update task: %s' % task_id) |
| 575 self.abort_with_error( | 576 self.abort_with_error( |
| 576 400, | 577 400, |
| 577 error='isolated_stats requires bot_overhead to be set' | 578 error='isolated_stats and cipd_stats require bot_overhead to be set' |
| 578 '\nbot_overhead: %s\nisolated_stats: %s' % | 579 '\nbot_overhead: %s\nisolate_stats: %s' % |
| 579 (bot_overhead, isolated_stats)) | 580 (bot_overhead, isolated_stats)) |
| 580 | 581 |
| 581 run_result_key = task_pack.unpack_run_result_key(task_id) | 582 run_result_key = task_pack.unpack_run_result_key(task_id) |
| 582 performance_stats = None | 583 performance_stats = None |
| 583 if bot_overhead: | 584 if bot_overhead: |
| 584 performance_stats = task_result.PerformanceStats( | 585 performance_stats = task_result.PerformanceStats( |
| 585 bot_overhead=bot_overhead) | 586 bot_overhead=bot_overhead) |
| 586 if isolated_stats: | 587 if isolated_stats: |
| 587 download = isolated_stats.get('download') or {} | 588 download = isolated_stats.get('download') or {} |
| 588 upload = isolated_stats.get('upload') or {} | 589 upload = isolated_stats.get('upload') or {} |
| 589 def unpack_base64(d, k): | 590 def unpack_base64(d, k): |
| 590 x = d.get(k) | 591 x = d.get(k) |
| 591 if x: | 592 if x: |
| 592 return base64.b64decode(x) | 593 return base64.b64decode(x) |
| 593 performance_stats.isolated_download = task_result.OperationStats( | 594 performance_stats.isolated_download = task_result.OperationStats( |
| 594 duration=download.get('duration'), | 595 duration=download.get('duration'), |
| 595 initial_number_items=download.get('initial_number_items'), | 596 initial_number_items=download.get('initial_number_items'), |
| 596 initial_size=download.get('initial_size'), | 597 initial_size=download.get('initial_size'), |
| 597 items_cold=unpack_base64(download, 'items_cold'), | 598 items_cold=unpack_base64(download, 'items_cold'), |
| 598 items_hot=unpack_base64(download, 'items_hot')) | 599 items_hot=unpack_base64(download, 'items_hot')) |
| 599 performance_stats.isolated_upload = task_result.OperationStats( | 600 performance_stats.isolated_upload = task_result.OperationStats( |
| 600 duration=upload.get('duration'), | 601 duration=upload.get('duration'), |
| 601 items_cold=unpack_base64(upload, 'items_cold'), | 602 items_cold=unpack_base64(upload, 'items_cold'), |
| 602 items_hot=unpack_base64(upload, 'items_hot')) | 603 items_hot=unpack_base64(upload, 'items_hot')) |
| 604 if cipd_stats: | |
| 605 performance_stats.package_installation = task_result.OperationStats( | |
| 606 duration=cipd_stats.get('duration')) | |
|
M-A Ruel
2016/06/10 02:06:42
and client?
nodir
2016/06/10 15:46:32
so OperationStats does not have a place to put get
| |
| 603 | 607 |
| 604 if output is not None: | 608 if output is not None: |
| 605 try: | 609 try: |
| 606 output = base64.b64decode(output) | 610 output = base64.b64decode(output) |
| 607 except UnicodeEncodeError as e: | 611 except UnicodeEncodeError as e: |
| 608 logging.error('Failed to decode output\n%s\n%r', e, output) | 612 logging.error('Failed to decode output\n%s\n%r', e, output) |
| 609 output = output.encode('ascii', 'replace') | 613 output = output.encode('ascii', 'replace') |
| 610 except TypeError as e: | 614 except TypeError as e: |
| 611 # Save the output as-is instead. The error will be logged in ereporter2 | 615 # Save the output as-is instead. The error will be logged in ereporter2 |
| 612 # and returning a HTTP 500 would only force the bot to stay in a retry | 616 # and returning a HTTP 500 would only force the bot to stay in a retry |
| (...skipping 120 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 733 ('/swarming/api/v1/bot/poll', BotPollHandler), | 737 ('/swarming/api/v1/bot/poll', BotPollHandler), |
| 734 ('/swarming/api/v1/bot/server_ping', ServerPingHandler), | 738 ('/swarming/api/v1/bot/server_ping', ServerPingHandler), |
| 735 ('/swarming/api/v1/bot/task_update', BotTaskUpdateHandler), | 739 ('/swarming/api/v1/bot/task_update', BotTaskUpdateHandler), |
| 736 ('/swarming/api/v1/bot/task_update/<task_id:[a-f0-9]+>', | 740 ('/swarming/api/v1/bot/task_update/<task_id:[a-f0-9]+>', |
| 737 BotTaskUpdateHandler), | 741 BotTaskUpdateHandler), |
| 738 ('/swarming/api/v1/bot/task_error', BotTaskErrorHandler), | 742 ('/swarming/api/v1/bot/task_error', BotTaskErrorHandler), |
| 739 ('/swarming/api/v1/bot/task_error/<task_id:[a-f0-9]+>', | 743 ('/swarming/api/v1/bot/task_error/<task_id:[a-f0-9]+>', |
| 740 BotTaskErrorHandler), | 744 BotTaskErrorHandler), |
| 741 ] | 745 ] |
| 742 return [webapp2.Route(*i) for i in routes] | 746 return [webapp2.Route(*i) for i in routes] |
| OLD | NEW |