| OLD | NEW |
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # Copyright 2014 The LUCI Authors. All rights reserved. | 2 # Copyright 2014 The LUCI Authors. All rights reserved. |
| 3 # Use of this source code is governed under the Apache License, Version 2.0 | 3 # Use of this source code is governed under the Apache License, Version 2.0 |
| 4 # that can be found in the LICENSE file. | 4 # that can be found in the LICENSE file. |
| 5 | 5 |
| 6 """Integration test for the Swarming server, Swarming bot and Swarming client. | 6 """Integration test for the Swarming server, Swarming bot and Swarming client. |
| 7 | 7 |
| 8 It starts both a Swarming server and a Swarming bot and triggers tasks with the | 8 It starts both a Swarming server and a Swarming bot and triggers tasks with the |
| 9 Swarming client to ensure the system works end to end. | 9 Swarming client to ensure the system works end to end. |
| 10 """ | 10 """ |
| (...skipping 56 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 67 | 67 |
| 68 RESULT_HEY_OUTPUTS_REF = { | 68 RESULT_HEY_OUTPUTS_REF = { |
| 69 u'isolated': u'5c64883277eb00bceafe3659f182e194cffc5d96', | 69 u'isolated': u'5c64883277eb00bceafe3659f182e194cffc5d96', |
| 70 u'isolatedserver': u'http://localhost:10050', | 70 u'isolatedserver': u'http://localhost:10050', |
| 71 u'namespace': u'default-gzip', | 71 u'namespace': u'default-gzip', |
| 72 u'view_url': | 72 u'view_url': |
| 73 u'http://localhost:10050/browse?namespace=default-gzip' | 73 u'http://localhost:10050/browse?namespace=default-gzip' |
| 74 '&hash=5c64883277eb00bceafe3659f182e194cffc5d96', | 74 '&hash=5c64883277eb00bceafe3659f182e194cffc5d96', |
| 75 } | 75 } |
| 76 | 76 |
| 77 RESULT_HEY2_ISOLATED_OUT = { |
| 78 u'isolated': u'0616f86b24065a0595e58088925567ae54a8157c', |
| 79 u'isolatedserver': u'http://localhost:10050', |
| 80 u'namespace': u'default-gzip', |
| 81 u'view_url': |
| 82 u'http://localhost:10050/browse?namespace=default-gzip' |
| 83 '&hash=0616f86b24065a0595e58088925567ae54a8157c', |
| 84 } |
| 85 |
| 86 RESULT_HEY2_OUTPUTS_REF = { |
| 87 u'isolated': u'0616f86b24065a0595e58088925567ae54a8157c', |
| 88 u'isolatedserver': u'http://localhost:10050', |
| 89 u'namespace': u'default-gzip', |
| 90 u'view_url': |
| 91 u'http://localhost:10050/browse?namespace=default-gzip' |
| 92 '&hash=0616f86b24065a0595e58088925567ae54a8157c', |
| 93 } |
| 94 |
| 77 RESULT_SECRET_OUTPUT = { | 95 RESULT_SECRET_OUTPUT = { |
| 78 u'isolated': u'd2eca4d860e4f1728272f6a736fd1c9ac6e98c4f', | 96 u'isolated': u'd2eca4d860e4f1728272f6a736fd1c9ac6e98c4f', |
| 79 u'isolatedserver': u'http://localhost:10050', | 97 u'isolatedserver': u'http://localhost:10050', |
| 80 u'namespace': u'default-gzip', | 98 u'namespace': u'default-gzip', |
| 81 u'view_url': | 99 u'view_url': |
| 82 u'http://localhost:10050/browse?namespace=default-gzip' | 100 u'http://localhost:10050/browse?namespace=default-gzip' |
| 83 '&hash=d2eca4d860e4f1728272f6a736fd1c9ac6e98c4f', | 101 '&hash=d2eca4d860e4f1728272f6a736fd1c9ac6e98c4f', |
| 84 } | 102 } |
| 85 | 103 |
| 86 | 104 |
| (...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 129 h, tmp = tempfile.mkstemp(prefix='swarming_smoke_test', suffix='.json') | 147 h, tmp = tempfile.mkstemp(prefix='swarming_smoke_test', suffix='.json') |
| 130 os.close(h) | 148 os.close(h) |
| 131 try: | 149 try: |
| 132 cmd = [ | 150 cmd = [ |
| 133 '--user', 'joe@localhost', | 151 '--user', 'joe@localhost', |
| 134 '-d', 'pool', 'default', | 152 '-d', 'pool', 'default', |
| 135 '--dump-json', tmp, | 153 '--dump-json', tmp, |
| 136 '--task-name', name, | 154 '--task-name', name, |
| 137 '-I', self._isolate_server, | 155 '-I', self._isolate_server, |
| 138 '--namespace', 'default-gzip', | 156 '--namespace', 'default-gzip', |
| 139 isolated_hash, | 157 '-s', isolated_hash, |
| 140 ] | 158 ] |
| 141 if extra: | 159 if extra: |
| 142 cmd.extend(extra) | 160 cmd.extend(extra) |
| 143 assert not self._run('trigger', cmd) | 161 assert not self._run('trigger', cmd) |
| 144 with open(tmp, 'rb') as f: | 162 with open(tmp, 'rb') as f: |
| 145 data = json.load(f) | 163 data = json.load(f) |
| 146 task_id = data['tasks'].popitem()[1]['task_id'] | 164 task_id = data['tasks'].popitem()[1]['task_id'] |
| 147 logging.debug('task_id = %s', task_id) | 165 logging.debug('task_id = %s', task_id) |
| 148 return task_id | 166 return task_id |
| 149 finally: | 167 finally: |
| (...skipping 117 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 267 bot = None | 285 bot = None |
| 268 | 286 |
| 269 @classmethod | 287 @classmethod |
| 270 def setUpClass(cls): | 288 def setUpClass(cls): |
| 271 cls.dimensions = os_utilities.get_dimensions() | 289 cls.dimensions = os_utilities.get_dimensions() |
| 272 | 290 |
| 273 def setUp(self): | 291 def setUp(self): |
| 274 super(Test, self).setUp() | 292 super(Test, self).setUp() |
| 275 # Reset the bot's cache at the start of each task, so that the cache reuse | 293 # Reset the bot's cache at the start of each task, so that the cache reuse |
| 276 # data becomes deterministic. | 294 # data becomes deterministic. |
| 295 # Main caveat is 'isolated_upload' as the isolate server is not cleared. |
| 277 self.bot.wipe_cache() | 296 self.bot.wipe_cache() |
| 278 | 297 |
| 279 def gen_expected(self, **kwargs): | 298 def gen_expected(self, **kwargs): |
| 280 return gen_expected(bot_dimensions=self.dimensions, **kwargs) | 299 return gen_expected(bot_dimensions=self.dimensions, **kwargs) |
| 281 | 300 |
| 282 def test_raw_bytes(self): | 301 def test_raw_bytes(self): |
| 283 # A string of a letter 'A', UTF-8 BOM then UTF-16 BOM then UTF-EDBCDIC then | 302 # A string of a letter 'A', UTF-8 BOM then UTF-16 BOM then UTF-EDBCDIC then |
| 284 # invalid UTF-8 and the letter 'B'. It is double escaped so it can be passed | 303 # invalid UTF-8 and the letter 'B'. It is double escaped so it can be passed |
| 285 # down the shell. | 304 # down the shell. |
| 286 invalid_bytes = 'A\\xEF\\xBB\\xBF\\xFE\\xFF\\xDD\\x73\\x66\\x73\\xc3\\x28B' | 305 invalid_bytes = 'A\\xEF\\xBB\\xBF\\xFE\\xFF\\xDD\\x73\\x66\\x73\\xc3\\x28B' |
| (...skipping 114 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 401 'print(\'hi\')', | 420 'print(\'hi\')', |
| 402 'with open(os.path.join(sys.argv[1], \'result.txt\'), \'wb\') as f:', | 421 'with open(os.path.join(sys.argv[1], \'result.txt\'), \'wb\') as f:', |
| 403 ' f.write(\'hey\')')) | 422 ' f.write(\'hey\')')) |
| 404 expected_summary = self.gen_expected( | 423 expected_summary = self.gen_expected( |
| 405 name=u'isolated_task', | 424 name=u'isolated_task', |
| 406 isolated_out=RESULT_HEY_ISOLATED_OUT, | 425 isolated_out=RESULT_HEY_ISOLATED_OUT, |
| 407 performance_stats={ | 426 performance_stats={ |
| 408 u'isolated_download': { | 427 u'isolated_download': { |
| 409 u'initial_number_items': u'0', | 428 u'initial_number_items': u'0', |
| 410 u'initial_size': u'0', | 429 u'initial_size': u'0', |
| 411 u'items_cold': [112, 200], | 430 u'items_cold': sorted([len(hello_world), 200]), |
| 412 u'items_hot': [], | 431 u'items_hot': [], |
| 413 }, | 432 }, |
| 414 u'isolated_upload': { | 433 u'isolated_upload': { |
| 415 u'items_cold': [3, 118], | 434 u'items_cold': [3, 118], |
| 416 u'items_hot': [], | 435 u'items_hot': [], |
| 417 }, | 436 }, |
| 418 }, | 437 }, |
| 419 outputs=[u'hi\n'], | 438 outputs=[u'hi\n'], |
| 420 outputs_ref=RESULT_HEY_OUTPUTS_REF) | 439 outputs_ref=RESULT_HEY_OUTPUTS_REF) |
| 421 expected_files = {os.path.join('0', 'result.txt'): 'hey'} | 440 expected_files = {os.path.join('0', 'result.txt'): 'hey'} |
| 422 self._run_isolated( | 441 self._run_isolated( |
| 423 hello_world, 'isolated_task', ['--', '${ISOLATED_OUTDIR}'], | 442 hello_world, 'isolated_task', ['--', '${ISOLATED_OUTDIR}'], |
| 424 expected_summary, expected_files) | 443 expected_summary, expected_files) |
| 425 | 444 |
| 445 def test_isolated_command(self): |
| 446 # Command is specified in Swarming task, still with isolated file. |
| 447 hello_world = '\n'.join(( |
| 448 'import os', |
| 449 'import sys', |
| 450 'print(\'hi\')', |
| 451 'with open(os.path.join(sys.argv[1], \'result.txt\'), \'wb\') as f:', |
| 452 ' f.write(\'hey2\')')) |
| 453 expected_summary = self.gen_expected( |
| 454 name=u'separate_cmd', |
| 455 isolated_out=RESULT_HEY2_ISOLATED_OUT, |
| 456 performance_stats={ |
| 457 u'isolated_download': { |
| 458 u'initial_number_items': u'0', |
| 459 u'initial_size': u'0', |
| 460 u'items_cold': sorted([len(hello_world), 157]), |
| 461 u'items_hot': [], |
| 462 }, |
| 463 u'isolated_upload': { |
| 464 u'items_cold': [4, 118], |
| 465 u'items_hot': [], |
| 466 }, |
| 467 }, |
| 468 outputs=[u'hi\n'], |
| 469 outputs_ref=RESULT_HEY2_OUTPUTS_REF) |
| 470 expected_files = {os.path.join('0', 'result.txt'): 'hey2'} |
| 471 self._run_isolated( |
| 472 hello_world, 'separate_cmd', |
| 473 ['--raw-cmd', '--', 'python', 'hello_world.py', '${ISOLATED_OUTDIR}'], |
| 474 expected_summary, expected_files, |
| 475 isolated_content={'variables': {'files': ['hello_world.py']}}) |
| 476 |
| 426 def test_isolated_hard_timeout(self): | 477 def test_isolated_hard_timeout(self): |
| 427 # Make an isolated file, archive it, have it time out. Similar to | 478 # Make an isolated file, archive it, have it time out. Similar to |
| 428 # test_hard_timeout. The script doesn't handle signal so it failed the grace | 479 # test_hard_timeout. The script doesn't handle signal so it failed the grace |
| 429 # period. | 480 # period. |
| 430 hello_world = '\n'.join(( | 481 hello_world = '\n'.join(( |
| 431 'import os', | 482 'import os', |
| 432 'import sys', | 483 'import sys', |
| 433 'import time', | 484 'import time', |
| 434 'sys.stdout.write(\'hi\\n\')', | 485 'sys.stdout.write(\'hi\\n\')', |
| 435 'sys.stdout.flush()', | 486 'sys.stdout.flush()', |
| 436 'time.sleep(120)', | 487 'time.sleep(120)', |
| 437 'with open(os.path.join(sys.argv[1], \'result.txt\'), \'wb\') as f:', | 488 'with open(os.path.join(sys.argv[1], \'result.txt\'), \'wb\') as f:', |
| 438 ' f.write(\'hey\')')) | 489 ' f.write(\'hey\')')) |
| 439 expected_summary = self.gen_expected( | 490 expected_summary = self.gen_expected( |
| 440 name=u'isolated_hard_timeout', | 491 name=u'isolated_hard_timeout', |
| 441 exit_codes=[SIGNAL_TERM], | 492 exit_codes=[SIGNAL_TERM], |
| 442 failure=True, | 493 failure=True, |
| 443 performance_stats={ | 494 performance_stats={ |
| 444 u'isolated_download': { | 495 u'isolated_download': { |
| 445 u'initial_number_items': u'0', | 496 u'initial_number_items': u'0', |
| 446 u'initial_size': u'0', | 497 u'initial_size': u'0', |
| 447 u'items_cold': [172, 200], | 498 u'items_cold': sorted([len(hello_world), 200]), |
| 448 u'items_hot': [], | 499 u'items_hot': [], |
| 449 }, | 500 }, |
| 450 u'isolated_upload': { | 501 u'isolated_upload': { |
| 451 u'items_cold': [], | 502 u'items_cold': [], |
| 452 u'items_hot': [], | 503 u'items_hot': [], |
| 453 }, | 504 }, |
| 454 }, | 505 }, |
| 455 state=0x40) # task_result.State.TIMED_OUT | 506 state=0x40) # task_result.State.TIMED_OUT |
| 456 # Hard timeout is enforced by run_isolated, I/O timeout by task_runner. | 507 # Hard timeout is enforced by run_isolated, I/O timeout by task_runner. |
| 457 self._run_isolated( | 508 self._run_isolated( |
| (...skipping 25 matching lines...) Expand all Loading... |
| 483 ' print(\'ioerror\')', | 534 ' print(\'ioerror\')', |
| 484 'with open(os.path.join(sys.argv[1], \'result.txt\'), \'wb\') as f:', | 535 'with open(os.path.join(sys.argv[1], \'result.txt\'), \'wb\') as f:', |
| 485 ' f.write(\'hey\')')) | 536 ' f.write(\'hey\')')) |
| 486 expected_summary = self.gen_expected( | 537 expected_summary = self.gen_expected( |
| 487 name=u'isolated_hard_timeout_grace', | 538 name=u'isolated_hard_timeout_grace', |
| 488 isolated_out=RESULT_HEY_ISOLATED_OUT, | 539 isolated_out=RESULT_HEY_ISOLATED_OUT, |
| 489 performance_stats={ | 540 performance_stats={ |
| 490 u'isolated_download': { | 541 u'isolated_download': { |
| 491 u'initial_number_items': u'0', | 542 u'initial_number_items': u'0', |
| 492 u'initial_size': u'0', | 543 u'initial_size': u'0', |
| 493 u'items_cold': [200, 407], | 544 u'items_cold': sorted([200, len(hello_world)]), |
| 494 u'items_hot': [], | 545 u'items_hot': [], |
| 495 }, | 546 }, |
| 496 u'isolated_upload': { | 547 u'isolated_upload': { |
| 497 u'items_cold': [], | 548 u'items_cold': [], |
| 498 u'items_hot': [3, 118], | 549 u'items_hot': [3, 118], |
| 499 }, | 550 }, |
| 500 }, | 551 }, |
| 501 outputs=[u'hi\ngot signal 15\n'], | 552 outputs=[u'hi\ngot signal 15\n'], |
| 502 outputs_ref=RESULT_HEY_OUTPUTS_REF, | 553 outputs_ref=RESULT_HEY_OUTPUTS_REF, |
| 503 failure=True, | 554 failure=True, |
| 504 state=0x40) # task_result.State.TIMED_OUT | 555 state=0x40) # task_result.State.TIMED_OUT |
| 505 expected_files = {os.path.join('0', 'result.txt'): 'hey'} | 556 expected_files = {os.path.join('0', 'result.txt'): 'hey'} |
| 506 # Hard timeout is enforced by run_isolated, I/O timeout by task_runner. | 557 # Hard timeout is enforced by run_isolated, I/O timeout by task_runner. |
| 507 self._run_isolated( | 558 self._run_isolated( |
| 508 hello_world, 'isolated_hard_timeout_grace', | 559 hello_world, 'isolated_hard_timeout_grace', |
| 509 ['--hard-timeout', '1', '--', '${ISOLATED_OUTDIR}'], | 560 ['--hard-timeout', '1', '--', '${ISOLATED_OUTDIR}'], |
| 510 expected_summary, expected_files) | 561 expected_summary, expected_files) |
| 511 | 562 |
| 512 def test_idempotent_reuse(self): | 563 def test_idempotent_reuse(self): |
| 513 hello_world = 'print "hi"\n' | 564 hello_world = 'print "hi"\n' |
| 514 expected_summary = self.gen_expected( | 565 expected_summary = self.gen_expected( |
| 515 name=u'idempotent_reuse', | 566 name=u'idempotent_reuse', |
| 516 performance_stats={ | 567 performance_stats={ |
| 517 u'isolated_download': { | 568 u'isolated_download': { |
| 518 u'initial_number_items': u'0', | 569 u'initial_number_items': u'0', |
| 519 u'initial_size': u'0', | 570 u'initial_size': u'0', |
| 520 u'items_cold': [11, 199], | 571 u'items_cold': sorted([len(hello_world), 199]), |
| 521 u'items_hot': [], | 572 u'items_hot': [], |
| 522 }, | 573 }, |
| 523 u'isolated_upload': { | 574 u'isolated_upload': { |
| 524 u'items_cold': [], | 575 u'items_cold': [], |
| 525 u'items_hot': [], | 576 u'items_hot': [], |
| 526 }, | 577 }, |
| 527 }, | 578 }, |
| 528 properties_hash = | 579 properties_hash = |
| 529 u'082928de84d0a65839d227dcea2f5a947898929c77c1602b68c46d7d4588c1f5', | 580 u'082928de84d0a65839d227dcea2f5a947898929c77c1602b68c46d7d4588c1f5', |
| 530 ) | 581 ) |
| (...skipping 23 matching lines...) Expand all Loading... |
| 554 with open(os.path.join(sys.argv[1], 'sekret'), 'w') as f: | 605 with open(os.path.join(sys.argv[1], 'sekret'), 'w') as f: |
| 555 print >> f, data['swarming']['secret_bytes'].decode('base64') | 606 print >> f, data['swarming']['secret_bytes'].decode('base64') |
| 556 """) | 607 """) |
| 557 expected_summary = self.gen_expected( | 608 expected_summary = self.gen_expected( |
| 558 name=u'secret_bytes', | 609 name=u'secret_bytes', |
| 559 isolated_out=RESULT_SECRET_OUTPUT, | 610 isolated_out=RESULT_SECRET_OUTPUT, |
| 560 performance_stats={ | 611 performance_stats={ |
| 561 u'isolated_download': { | 612 u'isolated_download': { |
| 562 u'initial_number_items': u'0', | 613 u'initial_number_items': u'0', |
| 563 u'initial_size': u'0', | 614 u'initial_size': u'0', |
| 564 u'items_cold': [200, 241], | 615 u'items_cold': sorted([200, len(hello_world)]), |
| 565 u'items_hot': [], | 616 u'items_hot': [], |
| 566 }, | 617 }, |
| 567 u'isolated_upload': { | 618 u'isolated_upload': { |
| 568 u'items_cold': [7, 114], | 619 u'items_cold': [7, 114], |
| 569 u'items_hot': [], | 620 u'items_hot': [], |
| 570 }, | 621 }, |
| 571 }, | 622 }, |
| 572 outputs_ref=RESULT_SECRET_OUTPUT, | 623 outputs_ref=RESULT_SECRET_OUTPUT, |
| 573 ) | 624 ) |
| 574 h, tmp = tempfile.mkstemp(prefix='swarming_smoke_test_secret') | 625 h, tmp = tempfile.mkstemp(prefix='swarming_smoke_test_secret') |
| (...skipping 69 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 644 }, | 695 }, |
| 645 }, | 696 }, |
| 646 ) | 697 ) |
| 647 self._run_isolated( | 698 self._run_isolated( |
| 648 script, 'cache_second', | 699 script, 'cache_second', |
| 649 ['--named-cache', 'fuu', 'p/b', '--', '${ISOLATED_OUTDIR}/yo'], | 700 ['--named-cache', 'fuu', 'p/b', '--', '${ISOLATED_OUTDIR}/yo'], |
| 650 expected_summary, | 701 expected_summary, |
| 651 {'0/yo': 'Yo!'}) | 702 {'0/yo': 'Yo!'}) |
| 652 | 703 |
| 653 def _run_isolated(self, hello_world, name, args, expected_summary, | 704 def _run_isolated(self, hello_world, name, args, expected_summary, |
| 654 expected_files, deduped=False): | 705 expected_files, deduped=False, isolated_content=None): |
| 706 """Runs hello_world.py as an isolated file.""" |
| 655 # Shared code for all test_isolated_* test cases. | 707 # Shared code for all test_isolated_* test cases. |
| 656 tmpdir = tempfile.mkdtemp(prefix='swarming_smoke') | 708 tmpdir = tempfile.mkdtemp(prefix='swarming_smoke') |
| 657 try: | 709 try: |
| 658 isolate_path = os.path.join(tmpdir, 'i.isolate') | 710 isolate_path = os.path.join(tmpdir, 'i.isolate') |
| 659 isolated_path = os.path.join(tmpdir, 'i.isolated') | 711 isolated_path = os.path.join(tmpdir, 'i.isolated') |
| 660 with open(isolate_path, 'wb') as f: | 712 with open(isolate_path, 'wb') as f: |
| 661 json.dump(ISOLATE_HELLO_WORLD, f) | 713 json.dump(isolated_content or ISOLATE_HELLO_WORLD, f) |
| 662 with open(os.path.join(tmpdir, 'hello_world.py'), 'wb') as f: | 714 with open(os.path.join(tmpdir, 'hello_world.py'), 'wb') as f: |
| 663 f.write(hello_world) | 715 f.write(hello_world) |
| 664 isolated_hash = self.client.isolate(isolate_path, isolated_path) | 716 isolated_hash = self.client.isolate(isolate_path, isolated_path) |
| 665 task_id = self.client.task_trigger_isolated( | 717 task_id = self.client.task_trigger_isolated( |
| 666 name, isolated_hash, extra=args) | 718 name, isolated_hash, extra=args) |
| 667 actual_summary, actual_files = self.client.task_collect(task_id) | 719 actual_summary, actual_files = self.client.task_collect(task_id) |
| 668 self.assertResults(expected_summary, actual_summary, deduped=deduped) | 720 self.assertResults(expected_summary, actual_summary, deduped=deduped) |
| 669 actual_files.pop('summary.json') | 721 actual_files.pop('summary.json') |
| 670 self.assertEqual(expected_files, actual_files) | 722 self.assertEqual(expected_files, actual_files) |
| 671 return task_id | 723 return task_id |
| (...skipping 149 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 821 if bot is not None and bot.poll() is None: | 873 if bot is not None and bot.poll() is None: |
| 822 bot.kill() | 874 bot.kill() |
| 823 bot.wait() | 875 bot.wait() |
| 824 finally: | 876 finally: |
| 825 cleanup(bot, client, servers, failed or verbose, leak) | 877 cleanup(bot, client, servers, failed or verbose, leak) |
| 826 return int(failed) | 878 return int(failed) |
| 827 | 879 |
| 828 | 880 |
| 829 if __name__ == '__main__': | 881 if __name__ == '__main__': |
| 830 sys.exit(main()) | 882 sys.exit(main()) |
| OLD | NEW |