api.py 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. # coding: utf-8
  2. import json
  3. import uuid
  4. import asyncio
  5. import logging
  6. from aiohttp import web
  7. import aioredis
  8. from echo.utils import (encode_json, decode_json, generate_key, normalize_path,
  9. request_to_dict, hash_dict, compare_hash)
  10. log = logging.getLogger(__name__)
  11. app = None
  12. redis_pool = None
  13. @asyncio.coroutine
  14. def get_mock(request):
  15. data = json.dumps({'mocks': request.app['mock_db']})
  16. return web.Response(text=data, content_type='application/json')
  17. @asyncio.coroutine
  18. def put_mock(request):
  19. data = yield from request.json()
  20. uuid_hex = uuid.uuid4().hex
  21. path = yield from normalize_path('/mock/{}/{}/'.format(uuid_hex,
  22. data['path']))
  23. request.app.router.add_route(data['method'], path, mock)
  24. request.app['mock_db'][path] = data
  25. return web.Response(text=json.dumps({'path': path}),
  26. status=201,
  27. content_type='application/json')
  28. @asyncio.coroutine
  29. def get_proxy(request):
  30. data = json.dumps({'proxies': []})
  31. return web.Response(text=data, content_type='application/json')
  32. @asyncio.coroutine
  33. def put_proxy(request):
  34. return web.Response(text=json.dumps({'status': 'ok'}),
  35. content_type='application/json')
  36. @asyncio.coroutine
  37. def all_callback(request):
  38. app = request.match_info['app']
  39. queue = request.match_info['queue']
  40. key = yield from generate_key(app, queue)
  41. with (yield from request.app['redis_pool']) as redis:
  42. requests = yield from redis.lrange(key, 0, -1)
  43. requests = [decode_json(r) for r in requests]
  44. redis.delete(key)
  45. data = json.dumps({'requests': requests})
  46. return web.Response(text=data, content_type='application/json')
  47. @asyncio.coroutine
  48. def first_callback(request):
  49. app = request.match_info['app']
  50. queue = request.match_info['queue']
  51. key = yield from generate_key(app, queue)
  52. with (yield from request.app['redis_pool']) as redis:
  53. request_ = yield from redis.lpop(key)
  54. request_ = decode_json(request_)
  55. data = json.dumps({'request': request_})
  56. return web.Response(text=data, content_type='application/json')
  57. @asyncio.coroutine
  58. def last_callback(request):
  59. app = request.match_info['app']
  60. queue = request.match_info['queue']
  61. key = yield from generate_key(app, queue)
  62. with (yield from request.app['redis_pool']) as redis:
  63. request_ = yield from redis.rpop(key)
  64. request_ = decode_json(request_)
  65. data = json.dumps({'request': request_})
  66. return web.Response(text=data, content_type='application/json')
  67. @asyncio.coroutine
  68. def clean_callback(request):
  69. app = request.match_info['app']
  70. queue = request.match_info['queue']
  71. key = yield from generate_key(app, queue)
  72. with (yield from request.app['redis_pool']) as redis:
  73. redis.delete(key)
  74. return web.Response(text=json.dumps({}),
  75. content_type='application/json')
  76. @asyncio.coroutine
  77. def callback(request):
  78. # TODO: Add support for addtional_url
  79. app = request.match_info['app']
  80. queue = request.match_info['queue']
  81. key = yield from generate_key(app, queue)
  82. request_ = yield from request_to_dict(request)
  83. json_ = yield from encode_json(request_)
  84. with (yield from request.app['redis_pool']) as redis:
  85. redis.rpush(key, json_)
  86. return web.Response(text=json.dumps({'request': request_}),
  87. content_type='application/json')
  88. @asyncio.coroutine
  89. def mock(request):
  90. received = yield from request.json()
  91. # Check/Validate for KeyError
  92. config = request.app['mock_db'][request.path]
  93. response = config['response']['body']
  94. expected = config['request']['body']
  95. status = config['response']['status_code']
  96. content_type = config['response']['headers']['content_type']
  97. expected_hash = yield from hash_dict(expected)
  98. received_hash = yield from hash_dict(received)
  99. if not compare_hash(expected_hash, received_hash):
  100. status = 400
  101. response = {
  102. 'message': 'Received request data did not match with expected',
  103. 'received_data': received,
  104. 'expected_data': expected,
  105. }
  106. return web.Response(text=json.dumps(response),
  107. status=status,
  108. content_type=content_type)
  109. @asyncio.coroutine
  110. def health(request):
  111. return web.Response(text=json.dumps({'status': 'ok'}),
  112. content_type='application/json')
  113. @asyncio.coroutine
  114. def start(loop, tcp_port):
  115. app = web.Application(loop=loop)
  116. app['mock_db'] = {}
  117. # TODO: Use prettyconf here
  118. # TODO: Use utf-8 as enconding here
  119. redis_pool = yield from aioredis.create_pool(('localhost', 6379),
  120. minsize=5, maxsize=10,
  121. loop=loop)
  122. app['redis_pool'] = redis_pool
  123. # Mock
  124. app.router.add_route('GET', '/mocks/', get_mock)
  125. app.router.add_route('PUT', '/mocks/', put_mock)
  126. # Proxies
  127. app.router.add_route('GET', '/proxies/', get_proxy)
  128. app.router.add_route('PUT', '/proxies/', put_proxy)
  129. # Callbacks
  130. app.router.add_route('*', '/callbacks/{app}/{queue}/', callback)
  131. app.router.add_route('GET', '/callbacks/_all/{app}/{queue}/', all_callback)
  132. app.router.add_route('GET', '/callbacks/_first/{app}/{queue}/',
  133. first_callback)
  134. app.router.add_route('GET', '/callbacks/_last/{app}/{queue}/',
  135. last_callback)
  136. app.router.add_route('GET', '/callbacks/_clean/{app}/{queue}/',
  137. clean_callback)
  138. # Health
  139. app.router.add_route('GET', '/health/', health)
  140. # TODO: Use prettyconf here
  141. host = '127.0.0.1'
  142. port = tcp_port
  143. handler = app.make_handler()
  144. server = yield from loop.create_server(handler, host, port)
  145. name = server.sockets[0].getsockname()
  146. log.info('API started at http://{}:{}/'.format(*name))
  147. return server, handler, redis_pool
  148. def stop(loop):
  149. if app:
  150. loop.run_until_complete(app.finish())