| 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 """ |
| 11 | 11 |
| 12 import base64 | 12 import base64 |
| 13 import json | 13 import json |
| 14 import logging | 14 import logging |
| 15 import os | 15 import os |
| 16 import re | 16 import re |
| 17 import signal | 17 import signal |
| 18 import socket | 18 import socket |
| 19 import sys | 19 import sys |
| 20 import tempfile | 20 import tempfile |
| 21 import textwrap | 21 import textwrap |
| 22 import time |
| 22 import unittest | 23 import unittest |
| 23 import urllib | 24 import urllib |
| 24 | 25 |
| 25 APP_DIR = os.path.dirname(os.path.abspath(__file__)) | 26 APP_DIR = os.path.dirname(os.path.abspath(__file__)) |
| 26 BOT_DIR = os.path.join(APP_DIR, 'swarming_bot') | 27 BOT_DIR = os.path.join(APP_DIR, 'swarming_bot') |
| 27 CLIENT_DIR = os.path.join(APP_DIR, '..', '..', 'client') | 28 CLIENT_DIR = os.path.join(APP_DIR, '..', '..', 'client') |
| 28 | 29 |
| 29 from tools import start_bot | 30 from tools import start_bot |
| 30 from tools import start_servers | 31 from tools import start_servers |
| 31 | 32 |
| (...skipping 169 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 201 os.remove(tmp) | 202 os.remove(tmp) |
| 202 | 203 |
| 203 def terminate(self, bot_id): | 204 def terminate(self, bot_id): |
| 204 return self._run('terminate', ['--wait', bot_id]) | 205 return self._run('terminate', ['--wait', bot_id]) |
| 205 | 206 |
| 206 def cleanup(self): | 207 def cleanup(self): |
| 207 if self._tmpdir: | 208 if self._tmpdir: |
| 208 file_path.rmtree(self._tmpdir) | 209 file_path.rmtree(self._tmpdir) |
| 209 self._tmpdir = None | 210 self._tmpdir = None |
| 210 | 211 |
| 212 def query_bot(self): |
| 213 """Returns the bot's properties.""" |
| 214 data = json.loads(self._capture('query', ['bots/list', '--limit', '10'])) |
| 215 if not data.get('items'): |
| 216 return None |
| 217 assert len(data['items']) == 1 |
| 218 return data['items'][0] |
| 219 |
| 211 def dump_log(self): | 220 def dump_log(self): |
| 212 print >> sys.stderr, '-' * 60 | 221 print >> sys.stderr, '-' * 60 |
| 213 print >> sys.stderr, 'Client calls' | 222 print >> sys.stderr, 'Client calls' |
| 214 print >> sys.stderr, '-' * 60 | 223 print >> sys.stderr, '-' * 60 |
| 215 for i in xrange(self._index): | 224 for i in xrange(self._index): |
| 216 with open(os.path.join(self._tmpdir, 'client_%d.log' % i), 'rb') as f: | 225 with open(os.path.join(self._tmpdir, 'client_%d.log' % i), 'rb') as f: |
| 217 log = f.read().strip('\n') | 226 log = f.read().strip('\n') |
| 218 for l in log.splitlines(): | 227 for l in log.splitlines(): |
| 219 sys.stderr.write(' %s\n' % l) | 228 sys.stderr.write(' %s\n' % l) |
| 220 | 229 |
| (...skipping 13 matching lines...) Expand all Loading... |
| 234 '--verbose', | 243 '--verbose', |
| 235 ] + args | 244 ] + args |
| 236 with open(name, 'wb') as f: | 245 with open(name, 'wb') as f: |
| 237 f.write('\nRunning: %s\n' % ' '.join(cmd)) | 246 f.write('\nRunning: %s\n' % ' '.join(cmd)) |
| 238 f.flush() | 247 f.flush() |
| 239 p = subprocess42.Popen( | 248 p = subprocess42.Popen( |
| 240 cmd, stdout=f, stderr=subprocess42.STDOUT, cwd=CLIENT_DIR) | 249 cmd, stdout=f, stderr=subprocess42.STDOUT, cwd=CLIENT_DIR) |
| 241 p.communicate() | 250 p.communicate() |
| 242 return p.returncode | 251 return p.returncode |
| 243 | 252 |
| 253 def _capture(self, command, args): |
| 254 cmd = [ |
| 255 sys.executable, 'swarming.py', command, '-S', self._swarming_server, |
| 256 ] + args |
| 257 p = subprocess42.Popen(cmd, stdout=subprocess42.PIPE, cwd=CLIENT_DIR) |
| 258 return p.communicate()[0] |
| 259 |
| 244 | 260 |
| 245 def gen_expected(**kwargs): | 261 def gen_expected(**kwargs): |
| 246 expected = { | 262 expected = { |
| 247 u'abandoned_ts': None, | 263 u'abandoned_ts': None, |
| 248 u'bot_dimensions': None, | 264 u'bot_dimensions': None, |
| 249 u'bot_id': unicode(socket.getfqdn().split('.', 1)[0]), | 265 u'bot_id': unicode(socket.getfqdn().split('.', 1)[0]), |
| 250 u'children_task_ids': [], | 266 u'children_task_ids': [], |
| 251 u'cost_saved_usd': None, | 267 u'cost_saved_usd': None, |
| 252 u'deduped_from': None, | 268 u'deduped_from': None, |
| 253 u'exit_codes': [0], | 269 u'exit_codes': [0], |
| (...skipping 375 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 629 self._run_isolated( | 645 self._run_isolated( |
| 630 hello_world, 'secret_bytes', | 646 hello_world, 'secret_bytes', |
| 631 ['--secret-bytes-path', tmp, '--', '${ISOLATED_OUTDIR}'], | 647 ['--secret-bytes-path', tmp, '--', '${ISOLATED_OUTDIR}'], |
| 632 expected_summary, {os.path.join('0', 'sekret'): 'foobar\n'}) | 648 expected_summary, {os.path.join('0', 'sekret'): 'foobar\n'}) |
| 633 finally: | 649 finally: |
| 634 os.remove(tmp) | 650 os.remove(tmp) |
| 635 | 651 |
| 636 def test_local_cache(self): | 652 def test_local_cache(self): |
| 637 # First task creates the cache, second copy the content to the output | 653 # First task creates the cache, second copy the content to the output |
| 638 # directory. Each time it's the exact same script. | 654 # directory. Each time it's the exact same script. |
| 655 dimensions = { |
| 656 i['key']: i['value'] for i in self.client.query_bot()['dimensions']} |
| 657 self.assertEqual(set(self.dimensions), set(dimensions)) |
| 658 self.assertNotIn(u'cache', set(dimensions)) |
| 639 script = '\n'.join(( | 659 script = '\n'.join(( |
| 640 'import os, shutil, sys', | 660 'import os, shutil, sys', |
| 641 'p = "p/b/a.txt"', | 661 'p = "p/b/a.txt"', |
| 642 'if not os.path.isfile(p):', | 662 'if not os.path.isfile(p):', |
| 643 ' with open(p, "wb") as f:', | 663 ' with open(p, "wb") as f:', |
| 644 ' f.write("Yo!")', | 664 ' f.write("Yo!")', |
| 645 'else:', | 665 'else:', |
| 646 ' shutil.copy(p, sys.argv[1])', | 666 ' shutil.copy(p, sys.argv[1])', |
| 647 'print "hi"')) | 667 'print "hi"')) |
| 648 sizes = sorted([len(script), 200]) | 668 sizes = sorted([len(script), 200]) |
| (...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 685 u'initial_size': unicode(sum(sizes)), | 705 u'initial_size': unicode(sum(sizes)), |
| 686 u'items_cold': [], | 706 u'items_cold': [], |
| 687 u'items_hot': sizes, | 707 u'items_hot': sizes, |
| 688 }, | 708 }, |
| 689 u'isolated_upload': { | 709 u'isolated_upload': { |
| 690 u'items_cold': [3, 110], | 710 u'items_cold': [3, 110], |
| 691 u'items_hot': [], | 711 u'items_hot': [], |
| 692 }, | 712 }, |
| 693 }, | 713 }, |
| 694 ) | 714 ) |
| 715 # The previous task caused the bot to have a named cache. |
| 716 expected_summary['bot_dimensions'][u'caches'] = [u'fuu'] |
| 695 self._run_isolated( | 717 self._run_isolated( |
| 696 script, 'cache_second', | 718 script, 'cache_second', |
| 697 ['--named-cache', 'fuu', 'p/b', '--', '${ISOLATED_OUTDIR}/yo'], | 719 ['--named-cache', 'fuu', 'p/b', '--', '${ISOLATED_OUTDIR}/yo'], |
| 698 expected_summary, | 720 expected_summary, |
| 699 {'0/yo': 'Yo!'}) | 721 {'0/yo': 'Yo!'}) |
| 700 | 722 |
| 723 # Check that the bot now has a cache dimension by independently querying. |
| 724 expected = set(self.dimensions) |
| 725 expected.add(u'caches') |
| 726 dimensions = { |
| 727 i['key']: i['value'] for i in self.client.query_bot()['dimensions']} |
| 728 self.assertEqual(expected, set(dimensions)) |
| 729 |
| 701 def _run_isolated(self, hello_world, name, args, expected_summary, | 730 def _run_isolated(self, hello_world, name, args, expected_summary, |
| 702 expected_files, deduped=False, isolated_content=None): | 731 expected_files, deduped=False, isolated_content=None): |
| 703 """Runs hello_world.py as an isolated file.""" | 732 """Runs hello_world.py as an isolated file.""" |
| 704 # Shared code for all test_isolated_* test cases. | 733 # Shared code for all test_isolated_* test cases. |
| 705 tmpdir = tempfile.mkdtemp(prefix='swarming_smoke') | 734 tmpdir = tempfile.mkdtemp(prefix='swarming_smoke') |
| 706 try: | 735 try: |
| 707 isolate_path = os.path.join(tmpdir, 'i.isolate') | 736 isolate_path = os.path.join(tmpdir, 'i.isolate') |
| 708 isolated_path = os.path.join(tmpdir, 'i.isolated') | 737 isolated_path = os.path.join(tmpdir, 'i.isolated') |
| 709 with open(isolate_path, 'wb') as f: | 738 with open(isolate_path, 'wb') as f: |
| 710 json.dump(isolated_content or ISOLATE_HELLO_WORLD, f) | 739 json.dump(isolated_content or ISOLATE_HELLO_WORLD, f) |
| (...skipping 132 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 843 servers.start() | 872 servers.start() |
| 844 bot = start_bot.LocalBot(servers.swarming_server.url) | 873 bot = start_bot.LocalBot(servers.swarming_server.url) |
| 845 Test.bot = bot | 874 Test.bot = bot |
| 846 bot.start() | 875 bot.start() |
| 847 client = SwarmingClient( | 876 client = SwarmingClient( |
| 848 servers.swarming_server.url, servers.isolate_server.url) | 877 servers.swarming_server.url, servers.isolate_server.url) |
| 849 # Test cases only interract with the client; except for test_update_continue | 878 # Test cases only interract with the client; except for test_update_continue |
| 850 # which mutates the bot. | 879 # which mutates the bot. |
| 851 Test.client = client | 880 Test.client = client |
| 852 Test.servers = servers | 881 Test.servers = servers |
| 882 while not client.query_bot(): |
| 883 # Wait for the bot to come online helps when the unit test cases query the |
| 884 # bot. It may takes a few loop. |
| 885 time.sleep(0.1) |
| 853 failed = not unittest.main(exit=False).result.wasSuccessful() | 886 failed = not unittest.main(exit=False).result.wasSuccessful() |
| 854 | 887 |
| 855 # Then try to terminate the bot sanely. After the terminate request | 888 # Then try to terminate the bot sanely. After the terminate request |
| 856 # completed, the bot process should have terminated. Give it a few | 889 # completed, the bot process should have terminated. Give it a few |
| 857 # seconds due to delay between sending the event that the process is | 890 # seconds due to delay between sending the event that the process is |
| 858 # shutting down vs the process is shut down. | 891 # shutting down vs the process is shut down. |
| 859 if client.terminate(bot.bot_id) is not 0: | 892 if client.terminate(bot.bot_id) is not 0: |
| 860 print >> sys.stderr, 'swarming.py terminate failed' | 893 print >> sys.stderr, 'swarming.py terminate failed' |
| 861 failed = True | 894 failed = True |
| 862 try: | 895 try: |
| 863 bot.wait(10) | 896 bot.wait(10) |
| 864 except subprocess42.TimeoutExpired: | 897 except subprocess42.TimeoutExpired: |
| 865 print >> sys.stderr, 'Bot is still alive after swarming.py terminate' | 898 print >> sys.stderr, 'Bot is still alive after swarming.py terminate' |
| 866 failed = True | 899 failed = True |
| 867 except KeyboardInterrupt: | 900 except KeyboardInterrupt: |
| 868 print >> sys.stderr, '<Ctrl-C>' | 901 print >> sys.stderr, '<Ctrl-C>' |
| 869 failed = True | 902 failed = True |
| 870 if bot is not None and bot.poll() is None: | 903 if bot is not None and bot.poll() is None: |
| 871 bot.kill() | 904 bot.kill() |
| 872 bot.wait() | 905 bot.wait() |
| 873 finally: | 906 finally: |
| 874 cleanup(bot, client, servers, failed or verbose, leak) | 907 cleanup(bot, client, servers, failed or verbose, leak) |
| 875 return int(failed) | 908 return int(failed) |
| 876 | 909 |
| 877 | 910 |
| 878 if __name__ == '__main__': | 911 if __name__ == '__main__': |
| 879 sys.exit(main()) | 912 sys.exit(main()) |
| OLD | NEW |