| OLD | NEW |
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # coding: utf-8 | 2 # coding: utf-8 |
| 3 # Copyright 2015 The Swarming Authors. All rights reserved. | 3 # Copyright 2015 The Swarming Authors. All rights reserved. |
| 4 # Use of this source code is governed by the Apache v2.0 license that can be | 4 # Use of this source code is governed by the Apache v2.0 license that can be |
| 5 # found in the LICENSE file. | 5 # found in the LICENSE file. |
| 6 | 6 |
| 7 import base64 | 7 import base64 |
| 8 import datetime | 8 import datetime |
| 9 import logging | 9 import logging |
| 10 import os | 10 import os |
| 11 import random | 11 import random |
| 12 import StringIO | 12 import StringIO |
| 13 import sys | 13 import sys |
| 14 import unittest | 14 import unittest |
| 15 import zipfile | 15 import zipfile |
| 16 | 16 |
| 17 # Setups environment. | 17 # Setups environment. |
| 18 import test_env_handlers | 18 import test_env_handlers |
| 19 | 19 |
| 20 from google.appengine.api import datastore_errors | 20 from google.appengine.api import datastore_errors |
| 21 from google.appengine.ext import ndb | 21 from google.appengine.ext import ndb |
| 22 | 22 |
| 23 import webapp2 | 23 import webapp2 |
| 24 import webtest | 24 import webtest |
| 25 | 25 |
| 26 import handlers_api | |
| 27 import handlers_bot | 26 import handlers_bot |
| 28 from components import ereporter2 | 27 from components import ereporter2 |
| 29 from components import utils | |
| 30 from server import bot_archive | 28 from server import bot_archive |
| 31 from server import bot_management | 29 from server import bot_management |
| 32 from server import task_result | 30 from server import task_result |
| 33 | 31 |
| 34 | 32 |
| 33 DATETIME_FORMAT = u'%Y-%m-%dT%H:%M:%S' |
| 34 |
| 35 |
| 35 class BotApiTest(test_env_handlers.AppTestBase): | 36 class BotApiTest(test_env_handlers.AppTestBase): |
| 36 def setUp(self): | 37 def setUp(self): |
| 37 super(BotApiTest, self).setUp() | 38 super(BotApiTest, self).setUp() |
| 38 # By default requests in tests are coming from bot with fake IP. | 39 # By default requests in tests are coming from bot with fake IP. |
| 39 routes = handlers_bot.get_routes() + handlers_api.get_routes() | 40 routes = handlers_bot.get_routes() |
| 40 app = webapp2.WSGIApplication(routes, debug=True) | 41 app = webapp2.WSGIApplication(routes, debug=True) |
| 41 self.app = webtest.TestApp( | 42 self.app = webtest.TestApp( |
| 42 app, | 43 app, |
| 43 extra_environ={ | 44 extra_environ={ |
| 44 'REMOTE_ADDR': self.source_ip, | 45 'REMOTE_ADDR': self.source_ip, |
| 45 'SERVER_SOFTWARE': os.environ['SERVER_SOFTWARE'], | 46 'SERVER_SOFTWARE': os.environ['SERVER_SOFTWARE'], |
| 46 }) | 47 }) |
| 47 self.mock( | 48 self.mock( |
| 48 ereporter2, 'log_request', | 49 ereporter2, 'log_request', |
| 49 lambda *args, **kwargs: self.fail('%s, %s' % (args, kwargs))) | 50 lambda *args, **kwargs: self.fail('%s, %s' % (args, kwargs))) |
| (...skipping 167 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 217 u'cmd': u'restart', | 218 u'cmd': u'restart', |
| 218 u'message': 'Mocked restart message', | 219 u'message': 'Mocked restart message', |
| 219 } | 220 } |
| 220 self.assertEqual(expected, response) | 221 self.assertEqual(expected, response) |
| 221 | 222 |
| 222 def test_poll_task_raw(self): | 223 def test_poll_task_raw(self): |
| 223 # Successfully poll a task. | 224 # Successfully poll a task. |
| 224 self.mock(random, 'getrandbits', lambda _: 0x88) | 225 self.mock(random, 'getrandbits', lambda _: 0x88) |
| 225 now = datetime.datetime(2010, 1, 2, 3, 4, 5) | 226 now = datetime.datetime(2010, 1, 2, 3, 4, 5) |
| 226 self.mock_now(now) | 227 self.mock_now(now) |
| 227 str_now = unicode(now.strftime(utils.DATETIME_FORMAT)) | 228 str_now = unicode(now.strftime(DATETIME_FORMAT)) |
| 228 # A bot polls, gets a task, updates it, completes it. | 229 # A bot polls, gets a task, updates it, completes it. |
| 229 token, params = self.get_bot_token() | 230 token, params = self.get_bot_token() |
| 230 # Enqueue a task. | 231 # Enqueue a task. |
| 231 _, task_id = self.client_create_task_raw() | 232 _, task_id = self.client_create_task_raw() |
| 232 self.assertEqual('0', task_id[-1]) | 233 self.assertEqual('0', task_id[-1]) |
| 233 # Convert TaskResultSummary reference to TaskRunResult. | 234 # Convert TaskResultSummary reference to TaskRunResult. |
| 234 task_id = task_id[:-1] + '1' | 235 task_id = task_id[:-1] + '1' |
| 235 response = self.post_with_token('/swarming/api/v1/bot/poll', params, token) | 236 response = self.post_with_token('/swarming/api/v1/bot/poll', params, token) |
| 236 expected = { | 237 expected = { |
| 237 u'cmd': u'run', | 238 u'cmd': u'run', |
| 238 u'manifest': { | 239 u'manifest': { |
| 239 u'bot_id': u'bot1', | 240 u'bot_id': u'bot1', |
| 240 u'command': [u'python', u'run_test.py'], | 241 u'command': [u'python', u'run_test.py'], |
| 241 u'data': [], | 242 u'data': [], |
| 242 u'dimensions': {u'os': u'Amiga'}, | 243 u'dimensions': {u'os': u'Amiga'}, |
| 243 u'env': {}, | 244 u'env': {}, |
| 244 u'extra_args': [], | 245 u'extra_args': [], |
| 245 u'grace_period': 30, | 246 u'grace_period': 30, |
| 246 u'hard_timeout': 3600, | 247 u'hard_timeout': 3600, |
| 247 u'host': u'http://localhost:8080', | 248 u'host': u'http://localhost:8080', |
| 248 u'inputs_ref': None, | 249 u'inputs_ref': None, |
| 249 u'io_timeout': 1200, | 250 u'io_timeout': 1200, |
| 250 u'task_id': task_id, | 251 u'task_id': task_id, |
| 251 }, | 252 }, |
| 252 } | 253 } |
| 253 self.assertEqual(expected, response) | 254 self.assertEqual(expected, response) |
| 254 response = self.client_get_results(task_id) | 255 response = self.client_get_results(task_id) |
| 255 expected = { | 256 expected = { |
| 256 u'abandoned_ts': None, | 257 u'bot_dimensions': [ |
| 257 u'bot_dimensions': {u'id': [u'bot1'], u'os': [u'Amiga']}, | 258 {u'key': u'id', u'value': [u'bot1']}, |
| 259 {u'key': u'os', u'value': [u'Amiga']}, |
| 260 ], |
| 258 u'bot_id': u'bot1', | 261 u'bot_id': u'bot1', |
| 259 u'bot_version': self.bot_version, | 262 u'bot_version': self.bot_version, |
| 260 u'children_task_ids': [], | 263 u'costs_usd': [0.], |
| 261 u'completed_ts': None, | 264 u'created_ts': str_now, |
| 262 u'cost_usd': 0., | |
| 263 u'durations': [], | |
| 264 u'exit_codes': [], | |
| 265 u'failure': False, | 265 u'failure': False, |
| 266 u'id': u'5cee488008811', | |
| 267 u'internal_failure': False, | 266 u'internal_failure': False, |
| 268 u'modified_ts': str_now, | 267 u'modified_ts': str_now, |
| 269 u'outputs_ref': None, | 268 u'name': u'hi', |
| 270 u'server_versions': [u'v1a'], | 269 u'server_versions': [u'v1a'], |
| 271 u'started_ts': str_now, | 270 u'started_ts': str_now, |
| 272 u'state': task_result.State.RUNNING, | 271 u'state': u'RUNNING', |
| 273 u'try_number': 1, | 272 u'task_id': u'5cee488008811', |
| 273 u'try_number': u'1', |
| 274 } | 274 } |
| 275 self.assertEqual(expected, response) | 275 self.assertEqual(expected, response) |
| 276 | 276 |
| 277 def test_poll_task_isolated(self): | 277 def test_poll_task_isolated(self): |
| 278 # Successfully poll a task. | 278 # Successfully poll a task. |
| 279 self.mock(random, 'getrandbits', lambda _: 0x88) | 279 self.mock(random, 'getrandbits', lambda _: 0x88) |
| 280 now = datetime.datetime(2010, 1, 2, 3, 4, 5) | 280 now = datetime.datetime(2010, 1, 2, 3, 4, 5) |
| 281 self.mock_now(now) | 281 self.mock_now(now) |
| 282 str_now = unicode(now.strftime(utils.DATETIME_FORMAT)) | 282 str_now = unicode(now.strftime(DATETIME_FORMAT)) |
| 283 # A bot polls, gets a task, updates it, completes it. | 283 # A bot polls, gets a task, updates it, completes it. |
| 284 token, params = self.get_bot_token() | 284 token, params = self.get_bot_token() |
| 285 # Enqueue a task. | 285 # Enqueue a task. |
| 286 _, task_id = self.client_create_task_isolated() | 286 _, task_id = self.client_create_task_isolated() |
| 287 self.assertEqual('0', task_id[-1]) | 287 self.assertEqual('0', task_id[-1]) |
| 288 # Convert TaskResultSummary reference to TaskRunResult. | 288 # Convert TaskResultSummary reference to TaskRunResult. |
| 289 task_id = task_id[:-1] + '1' | 289 task_id = task_id[:-1] + '1' |
| 290 response = self.post_with_token('/swarming/api/v1/bot/poll', params, token) | 290 response = self.post_with_token('/swarming/api/v1/bot/poll', params, token) |
| 291 expected = { | 291 expected = { |
| 292 u'cmd': u'run', | 292 u'cmd': u'run', |
| (...skipping 12 matching lines...) Expand all Loading... |
| 305 u'isolatedserver': u'http://localhost:1', | 305 u'isolatedserver': u'http://localhost:1', |
| 306 u'namespace': u'default-gzip', | 306 u'namespace': u'default-gzip', |
| 307 }, | 307 }, |
| 308 u'io_timeout': 1200, | 308 u'io_timeout': 1200, |
| 309 u'task_id': task_id, | 309 u'task_id': task_id, |
| 310 }, | 310 }, |
| 311 } | 311 } |
| 312 self.assertEqual(expected, response) | 312 self.assertEqual(expected, response) |
| 313 response = self.client_get_results(task_id) | 313 response = self.client_get_results(task_id) |
| 314 expected = { | 314 expected = { |
| 315 u'abandoned_ts': None, | 315 u'bot_dimensions': [ |
| 316 u'bot_dimensions': {u'id': [u'bot1'], u'os': [u'Amiga']}, | 316 {u'key': u'id', u'value': [u'bot1']}, |
| 317 {u'key': u'os', u'value': [u'Amiga']}, |
| 318 ], |
| 317 u'bot_id': u'bot1', | 319 u'bot_id': u'bot1', |
| 318 u'bot_version': self.bot_version, | 320 u'bot_version': self.bot_version, |
| 319 u'children_task_ids': [], | 321 u'costs_usd': [0.], |
| 320 u'completed_ts': None, | 322 u'created_ts': str_now, |
| 321 u'cost_usd': 0., | |
| 322 u'durations': [], | |
| 323 u'exit_codes': [], | |
| 324 u'failure': False, | 323 u'failure': False, |
| 325 u'id': u'5cee488008811', | |
| 326 u'internal_failure': False, | 324 u'internal_failure': False, |
| 327 u'modified_ts': str_now, | 325 u'modified_ts': str_now, |
| 328 u'outputs_ref': None, | 326 u'name': u'hi', |
| 329 u'server_versions': [u'v1a'], | 327 u'server_versions': [u'v1a'], |
| 330 u'started_ts': str_now, | 328 u'started_ts': str_now, |
| 331 u'state': task_result.State.RUNNING, | 329 u'state': u'RUNNING', |
| 332 u'try_number': 1, | 330 u'task_id': u'5cee488008811', |
| 331 u'try_number': u'1', |
| 333 } | 332 } |
| 334 self.assertEqual(expected, response) | 333 self.assertEqual(expected, response) |
| 335 | 334 |
| 336 def test_bot_ereporter2_error(self): | 335 def test_bot_ereporter2_error(self): |
| 337 # ereporter2's //client/utils/on_error.py traps unhandled exceptions | 336 # ereporter2's //client/utils/on_error.py traps unhandled exceptions |
| 338 # automatically. | 337 # automatically. |
| 339 self.mock(random, 'getrandbits', lambda _: 0x88) | 338 self.mock(random, 'getrandbits', lambda _: 0x88) |
| 340 errors = [] | 339 errors = [] |
| 341 self.mock( | 340 self.mock( |
| 342 ereporter2, 'log_request', | 341 ereporter2, 'log_request', |
| (...skipping 143 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 486 'version': u'123', | 485 'version': u'123', |
| 487 }, | 486 }, |
| 488 ] | 487 ] |
| 489 self.assertEqual(expected, actual) | 488 self.assertEqual(expected, actual) |
| 490 | 489 |
| 491 def test_task_complete(self): | 490 def test_task_complete(self): |
| 492 # Runs a task with 2 commands up to completion. | 491 # Runs a task with 2 commands up to completion. |
| 493 self.mock(random, 'getrandbits', lambda _: 0x88) | 492 self.mock(random, 'getrandbits', lambda _: 0x88) |
| 494 now = datetime.datetime(2010, 1, 2, 3, 4, 5) | 493 now = datetime.datetime(2010, 1, 2, 3, 4, 5) |
| 495 self.mock_now(now) | 494 self.mock_now(now) |
| 496 str_now = unicode(now.strftime(utils.DATETIME_FORMAT)) | 495 str_now = unicode(now.strftime(DATETIME_FORMAT)) |
| 497 token, params = self.get_bot_token() | 496 token, params = self.get_bot_token() |
| 498 self.client_create_task_raw( | 497 self.client_create_task_raw( |
| 499 properties=dict(commands=[['python', 'runtest.py']])) | 498 properties=dict(command=['python', 'runtest.py'])) |
| 500 | 499 |
| 501 def _params(**kwargs): | 500 def _params(**kwargs): |
| 502 out = { | 501 out = { |
| 503 'cost_usd': 0.1, | 502 'cost_usd': 0.1, |
| 504 'duration': None, | 503 'duration': None, |
| 505 'exit_code': None, | 504 'exit_code': None, |
| 506 'id': 'bot1', | 505 'id': 'bot1', |
| 507 'output': None, | 506 'output': None, |
| 508 'output_chunk_start': 0, | 507 'output_chunk_start': 0, |
| 509 'task_id': task_id, | 508 'task_id': task_id, |
| 510 } | 509 } |
| 511 out.update(**kwargs) | 510 out.update(**kwargs) |
| 512 return out | 511 return out |
| 513 | 512 |
| 514 def _expected(**kwargs): | 513 def _expected(**kwargs): |
| 515 out = { | 514 out = { |
| 516 u'abandoned_ts': None, | 515 u'bot_dimensions': [ |
| 517 u'bot_dimensions': {u'id': [u'bot1'], u'os': [u'Amiga']}, | 516 {u'key': u'id', u'value': [u'bot1']}, |
| 517 {u'key': u'os', u'value': [u'Amiga']}, |
| 518 ], |
| 518 u'bot_id': u'bot1', | 519 u'bot_id': u'bot1', |
| 519 u'bot_version': self.bot_version, | 520 u'bot_version': self.bot_version, |
| 520 u'children_task_ids': [], | 521 u'costs_usd': [0.1], |
| 521 u'completed_ts': None, | 522 u'created_ts': str_now, |
| 522 u'cost_usd': 0.1, | |
| 523 u'durations': [], | |
| 524 u'exit_codes': [], | |
| 525 u'failure': False, | 523 u'failure': False, |
| 526 u'id': u'5cee488008811', | |
| 527 u'internal_failure': False, | 524 u'internal_failure': False, |
| 528 u'modified_ts': str_now, | 525 u'modified_ts': str_now, |
| 529 u'outputs_ref': None, | 526 u'name': u'hi', |
| 530 u'server_versions': [u'v1a'], | 527 u'server_versions': [u'v1a'], |
| 531 u'started_ts': str_now, | 528 u'started_ts': str_now, |
| 532 u'state': task_result.State.RUNNING, | 529 u'state': u'RUNNING', |
| 533 u'try_number': 1, | 530 u'task_id': u'5cee488008811', |
| 531 u'try_number': u'1', |
| 534 } | 532 } |
| 535 out.update(**kwargs) | 533 out.update(**kwargs) |
| 536 return out | 534 return out |
| 537 | 535 |
| 538 def _cycle(params, expected): | 536 def _cycle(params, expected): |
| 539 response = self.post_with_token( | 537 response = self.post_with_token( |
| 540 '/swarming/api/v1/bot/task_update', params, token) | 538 '/swarming/api/v1/bot/task_update', params, token) |
| 541 self.assertEqual({u'ok': True}, response) | 539 self.assertEqual({u'ok': True}, response) |
| 542 response = self.client_get_results(task_id) | 540 response = self.client_get_results(task_id) |
| 543 self.assertEqual(expected, response) | 541 self.assertEqual(expected, response) |
| (...skipping 16 matching lines...) Expand all Loading... |
| 560 # 3. Task update with some more output. | 558 # 3. Task update with some more output. |
| 561 params = _params(output=base64.b64encode('hi'), output_chunk_start=3) | 559 params = _params(output=base64.b64encode('hi'), output_chunk_start=3) |
| 562 expected = _expected() | 560 expected = _expected() |
| 563 _cycle(params, expected) | 561 _cycle(params, expected) |
| 564 | 562 |
| 565 # 4. Task update with completion of the command. | 563 # 4. Task update with completion of the command. |
| 566 params = _params( | 564 params = _params( |
| 567 duration=0.1, exit_code=23, output=base64.b64encode('Ahahah')) | 565 duration=0.1, exit_code=23, output=base64.b64encode('Ahahah')) |
| 568 expected = _expected( | 566 expected = _expected( |
| 569 completed_ts=str_now, | 567 completed_ts=str_now, |
| 570 durations=[0.1], | 568 duration=0.1, |
| 571 exit_codes=[23], | 569 exit_code=u'23', |
| 572 failure=True, | 570 failure=True, |
| 573 state=task_result.State.COMPLETED) | 571 state=u'COMPLETED') |
| 574 _cycle(params, expected) | 572 _cycle(params, expected) |
| 575 | 573 |
| 576 def test_task_update_db_failure(self): | 574 def test_task_update_db_failure(self): |
| 577 # The error is caught in task_scheduler.bot_update_task(). | 575 # The error is caught in task_scheduler.bot_update_task(). |
| 578 self.client_create_task_raw( | 576 self.client_create_task_raw( |
| 579 properties=dict(commands=[['python', 'runtest.py']])) | 577 properties=dict(command=['python', 'runtest.py'])) |
| 580 | 578 |
| 581 token, params = self.get_bot_token() | 579 token, params = self.get_bot_token() |
| 582 response = self.post_with_token( | 580 response = self.post_with_token( |
| 583 '/swarming/api/v1/bot/poll', params, token) | 581 '/swarming/api/v1/bot/poll', params, token) |
| 584 task_id = response['manifest']['task_id'] | 582 task_id = response['manifest']['task_id'] |
| 585 | 583 |
| 586 def r(*_): | 584 def r(*_): |
| 587 raise datastore_errors.Timeout('Sorry!') | 585 raise datastore_errors.Timeout('Sorry!') |
| 588 self.mock(ndb, 'put_multi', r) | 586 self.mock(ndb, 'put_multi', r) |
| 589 params = { | 587 params = { |
| 590 'cost_usd': 0.1, | 588 'cost_usd': 0.1, |
| 591 'duration': 0.1, | 589 'duration': 0.1, |
| 592 'exit_code': 0, | 590 'exit_code': 0, |
| 593 'id': 'bot1', | 591 'id': 'bot1', |
| 594 'output': base64.b64encode('result string'), | 592 'output': base64.b64encode('result string'), |
| 595 'output_chunk_start': 0, | 593 'output_chunk_start': 0, |
| 596 'task_id': task_id, | 594 'task_id': task_id, |
| 597 } | 595 } |
| 598 response = self.post_with_token( | 596 response = self.post_with_token( |
| 599 '/swarming/api/v1/bot/task_update', params, token, status=500) | 597 '/swarming/api/v1/bot/task_update', params, token, status=500) |
| 600 self.assertEqual({u'error': u'Failed to update, please retry'}, response) | 598 self.assertEqual({u'error': u'Failed to update, please retry'}, response) |
| 601 | 599 |
| 602 def test_task_update_failure(self): | 600 def test_task_update_failure(self): |
| 603 # The error is caught in handlers_api.BotTaskUpdateHandler.post(). | |
| 604 self.client_create_task_raw( | 601 self.client_create_task_raw( |
| 605 properties=dict(commands=[['python', 'runtest.py']])) | 602 properties=dict(command=['python', 'runtest.py'])) |
| 606 | 603 |
| 607 token, params = self.get_bot_token() | 604 token, params = self.get_bot_token() |
| 608 response = self.post_with_token( | 605 response = self.post_with_token( |
| 609 '/swarming/api/v1/bot/poll', params, token) | 606 '/swarming/api/v1/bot/poll', params, token) |
| 610 task_id = response['manifest']['task_id'] | 607 task_id = response['manifest']['task_id'] |
| 611 | 608 |
| 612 class NewError(Exception): | 609 class NewError(Exception): |
| 613 pass | 610 pass |
| 614 | 611 |
| 615 def r(*_): | 612 def r(*_): |
| 616 raise NewError('Sorry!') | 613 raise NewError('Sorry!') |
| 617 self.mock(ndb, 'put_multi', r) | 614 self.mock(ndb, 'put_multi', r) |
| 618 params = { | 615 params = { |
| 619 'cost_usd': 0.1, | 616 'cost_usd': 0.1, |
| 620 'duration': 0.1, | 617 'duration': 0.1, |
| 621 'exit_code': 0, | 618 'exit_code': 0, |
| 622 'id': 'bot1', | 619 'id': 'bot1', |
| 623 'output': base64.b64encode('result string'), | 620 'output': base64.b64encode('result string'), |
| 624 'output_chunk_start': 0, | 621 'output_chunk_start': 0, |
| 625 'task_id': task_id, | 622 'task_id': task_id, |
| 626 } | 623 } |
| 627 response = self.post_with_token( | 624 response = self.post_with_token( |
| 628 '/swarming/api/v1/bot/task_update', params, token, status=500) | 625 '/swarming/api/v1/bot/task_update', params, token, status=500) |
| 629 self.assertEqual({u'error': u'Sorry!'}, response) | 626 self.assertEqual({u'error': u'Sorry!'}, response) |
| 630 | 627 |
| 631 def test_task_failure(self): | 628 def test_task_failure(self): |
| 632 self.mock(random, 'getrandbits', lambda _: 0x88) | 629 self.mock(random, 'getrandbits', lambda _: 0x88) |
| 633 now = datetime.datetime(2010, 1, 2, 3, 4, 5) | 630 now = datetime.datetime(2010, 1, 2, 3, 4, 5) |
| 634 self.mock_now(now) | 631 self.mock_now(now) |
| 635 str_now = unicode(now.strftime(utils.DATETIME_FORMAT)) | 632 str_now = unicode(now.strftime(DATETIME_FORMAT)) |
| 636 token, params = self.get_bot_token() | 633 token, params = self.get_bot_token() |
| 637 self.client_create_task_raw() | 634 self.client_create_task_raw() |
| 638 response = self.post_with_token( | 635 response = self.post_with_token( |
| 639 '/swarming/api/v1/bot/poll', params, token) | 636 '/swarming/api/v1/bot/poll', params, token) |
| 640 task_id = response['manifest']['task_id'] | 637 task_id = response['manifest']['task_id'] |
| 641 | 638 |
| 642 params = { | 639 params = { |
| 643 'cost_usd': 0.1, | 640 'cost_usd': 0.1, |
| 644 'duration': 0.1, | 641 'duration': 0.1, |
| 645 'exit_code': 1, | 642 'exit_code': 1, |
| 646 'id': 'bot1', | 643 'id': 'bot1', |
| 647 'output': base64.b64encode('result string'), | 644 'output': base64.b64encode('result string'), |
| 648 'output_chunk_start': 0, | 645 'output_chunk_start': 0, |
| 649 'task_id': task_id, | 646 'task_id': task_id, |
| 650 } | 647 } |
| 651 response = self.post_with_token( | 648 response = self.post_with_token( |
| 652 '/swarming/api/v1/bot/task_update', params, token) | 649 '/swarming/api/v1/bot/task_update', params, token) |
| 653 response = self.client_get_results(task_id) | 650 response = self.client_get_results(task_id) |
| 654 expected = { | 651 expected = { |
| 655 u'abandoned_ts': None, | 652 u'bot_dimensions': [ |
| 656 u'bot_dimensions': {u'id': [u'bot1'], u'os': [u'Amiga']}, | 653 {u'key': u'id', u'value': [u'bot1']}, |
| 654 {u'key': u'os', u'value': [u'Amiga']}, |
| 655 ], |
| 657 u'bot_id': u'bot1', | 656 u'bot_id': u'bot1', |
| 658 u'bot_version': self.bot_version, | 657 u'bot_version': self.bot_version, |
| 659 u'children_task_ids': [], | |
| 660 u'completed_ts': str_now, | 658 u'completed_ts': str_now, |
| 661 u'cost_usd': 0.1, | 659 u'costs_usd': [0.1], |
| 662 u'durations': [0.1], | 660 u'created_ts': str_now, |
| 663 u'exit_codes': [1], | 661 u'duration': 0.1, |
| 662 u'exit_code': u'1', |
| 664 u'failure': True, | 663 u'failure': True, |
| 665 u'id': u'5cee488008811', | |
| 666 u'internal_failure': False, | 664 u'internal_failure': False, |
| 667 u'modified_ts': str_now, | 665 u'modified_ts': str_now, |
| 668 u'outputs_ref': None, | 666 u'name': u'hi', |
| 669 u'server_versions': [u'v1a'], | 667 u'server_versions': [u'v1a'], |
| 670 u'started_ts': str_now, | 668 u'started_ts': str_now, |
| 671 u'state': task_result.State.COMPLETED, | 669 u'state': u'COMPLETED', |
| 672 u'try_number': 1, | 670 u'task_id': u'5cee488008811', |
| 671 u'try_number': u'1', |
| 673 } | 672 } |
| 674 self.assertEqual(expected, response) | 673 self.assertEqual(expected, response) |
| 675 | 674 |
| 676 def test_task_internal_failure(self): | 675 def test_task_internal_failure(self): |
| 677 # E.g. task_runner blew up. | 676 # E.g. task_runner blew up. |
| 678 self.mock(random, 'getrandbits', lambda _: 0x88) | 677 self.mock(random, 'getrandbits', lambda _: 0x88) |
| 679 now = datetime.datetime(2010, 1, 2, 3, 4, 5) | 678 now = datetime.datetime(2010, 1, 2, 3, 4, 5) |
| 680 self.mock_now(now) | 679 self.mock_now(now) |
| 681 str_now = unicode(now.strftime(utils.DATETIME_FORMAT)) | 680 str_now = unicode(now.strftime(DATETIME_FORMAT)) |
| 682 errors = [] | 681 errors = [] |
| 683 self.mock( | 682 self.mock( |
| 684 ereporter2, 'log_request', | 683 ereporter2, 'log_request', |
| 685 lambda *args, **kwargs: errors.append((args, kwargs))) | 684 lambda *args, **kwargs: errors.append((args, kwargs))) |
| 686 token, params = self.get_bot_token() | 685 token, params = self.get_bot_token() |
| 687 self.client_create_task_raw() | 686 self.client_create_task_raw() |
| 688 response = self.post_with_token( | 687 response = self.post_with_token( |
| 689 '/swarming/api/v1/bot/poll', params, token) | 688 '/swarming/api/v1/bot/poll', params, token) |
| 690 task_id = response['manifest']['task_id'] | 689 task_id = response['manifest']['task_id'] |
| 691 | 690 |
| 692 # Let's say it failed to start task_runner because the new bot code is | 691 # Let's say it failed to start task_runner because the new bot code is |
| 693 # broken. The end result is still BOT_DIED. The big change is that it | 692 # broken. The end result is still BOT_DIED. The big change is that it |
| 694 # doesn't need to wait for a cron job to set this status. | 693 # doesn't need to wait for a cron job to set this status. |
| 695 params = { | 694 params = { |
| 696 'id': params['dimensions']['id'][0], | 695 'id': params['dimensions']['id'][0], |
| 697 'message': 'Oh', | 696 'message': 'Oh', |
| 698 'task_id': task_id, | 697 'task_id': task_id, |
| 699 } | 698 } |
| 700 response = self.post_with_token( | 699 response = self.post_with_token( |
| 701 '/swarming/api/v1/bot/task_error', params, token) | 700 '/swarming/api/v1/bot/task_error', params, token) |
| 702 | 701 |
| 703 response = self.client_get_results(task_id) | 702 response = self.client_get_results(task_id) |
| 704 expected = { | 703 expected = { |
| 705 u'abandoned_ts': str_now, | 704 u'abandoned_ts': str_now, |
| 706 u'bot_dimensions': {u'id': [u'bot1'], u'os': [u'Amiga']}, | 705 u'bot_dimensions': [ |
| 706 {u'key': u'id', u'value': [u'bot1']}, |
| 707 {u'key': u'os', u'value': [u'Amiga']}, |
| 708 ], |
| 707 u'bot_id': u'bot1', | 709 u'bot_id': u'bot1', |
| 708 u'bot_version': self.bot_version, | 710 u'bot_version': self.bot_version, |
| 709 u'children_task_ids': [], | 711 u'costs_usd': [0.], |
| 710 u'completed_ts': None, | 712 u'created_ts': str_now, |
| 711 u'cost_usd': 0., | |
| 712 u'durations': [], | |
| 713 u'exit_codes': [], | |
| 714 u'failure': False, | 713 u'failure': False, |
| 715 u'id': u'5cee488008811', | |
| 716 u'internal_failure': True, | 714 u'internal_failure': True, |
| 717 u'modified_ts': str_now, | 715 u'modified_ts': str_now, |
| 718 u'outputs_ref': None, | 716 u'name': u'hi', |
| 719 u'server_versions': [u'v1a'], | 717 u'server_versions': [u'v1a'], |
| 720 u'started_ts': str_now, | 718 u'started_ts': str_now, |
| 721 u'state': task_result.State.BOT_DIED, | 719 u'state': u'BOT_DIED', |
| 722 u'try_number': 1, | 720 u'task_id': u'5cee488008811', |
| 721 u'try_number': u'1', |
| 723 } | 722 } |
| 724 self.assertEqual(expected, response) | 723 self.assertEqual(expected, response) |
| 725 self.assertEqual(1, len(errors)) | 724 self.assertEqual(1, len(errors)) |
| 726 expected = [ | 725 expected = [ |
| 727 { | 726 { |
| 728 'message': | 727 'message': |
| 729 u'Bot: https://None/restricted/bot/bot1\n' | 728 u'Bot: https://None/restricted/bot/bot1\n' |
| 730 'Task failed: https://None/user/task/5cee488008811\nOh', | 729 'Task failed: https://None/user/task/5cee488008811\nOh', |
| 731 'source': 'bot', | 730 'source': 'bot', |
| 732 }, | 731 }, |
| 733 ] | 732 ] |
| 734 self.assertEqual(expected, [e[1] for e in errors]) | 733 self.assertEqual(expected, [e[1] for e in errors]) |
| 735 | 734 |
| 736 def test_bot_code(self): | 735 def test_bot_code(self): |
| 737 code = self.app.get('/bot_code') | 736 code = self.app.get('/bot_code') |
| 738 expected = {'config/bot_config.py', 'config/config.json'}.union( | 737 expected = {'config/bot_config.py', 'config/config.json'}.union( |
| 739 bot_archive.FILES) | 738 bot_archive.FILES) |
| 740 with zipfile.ZipFile(StringIO.StringIO(code.body), 'r') as z: | 739 with zipfile.ZipFile(StringIO.StringIO(code.body), 'r') as z: |
| 741 self.assertEqual(expected, set(z.namelist())) | 740 self.assertEqual(expected, set(z.namelist())) |
| 742 | 741 |
| 743 | 742 |
| 744 if __name__ == '__main__': | 743 if __name__ == '__main__': |
| 745 if '-v' in sys.argv: | 744 if '-v' in sys.argv: |
| 746 unittest.TestCase.maxDiff = None | 745 unittest.TestCase.maxDiff = None |
| 747 logging.basicConfig( | 746 logging.basicConfig( |
| 748 level=logging.DEBUG if '-v' in sys.argv else logging.CRITICAL, | 747 level=logging.DEBUG if '-v' in sys.argv else logging.CRITICAL, |
| 749 format='%(levelname)-7s %(filename)s:%(lineno)3d %(message)s') | 748 format='%(levelname)-7s %(filename)s:%(lineno)3d %(message)s') |
| 750 unittest.main() | 749 unittest.main() |
| OLD | NEW |