mirror of
https://github.com/bigchaindb/bigchaindb.git
synced 2024-10-13 13:34:05 +00:00
Merge branch 'feat/116/more-solid-webserver' into feat/156/refactor-monitoring-code
This commit is contained in:
commit
203f1bf70a
@ -1,6 +1,9 @@
|
|||||||
|
|
||||||
import json
|
import json
|
||||||
import time
|
import time
|
||||||
|
import contextlib
|
||||||
|
import threading
|
||||||
|
import queue
|
||||||
import multiprocessing as mp
|
import multiprocessing as mp
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
@ -31,6 +34,53 @@ class ProcessGroup(object):
|
|||||||
self.processes.append(proc)
|
self.processes.append(proc)
|
||||||
|
|
||||||
|
|
||||||
|
# Inspired by:
|
||||||
|
# - http://stackoverflow.com/a/24741694/597097
|
||||||
|
def pool(builder, size, timeout=None):
|
||||||
|
"""Create a pool that imposes a limit on the number of stored
|
||||||
|
instances.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
builder: a function to build an instance.
|
||||||
|
size: the size of the pool.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A context manager that can be used with the ``with``
|
||||||
|
statement.
|
||||||
|
"""
|
||||||
|
|
||||||
|
lock = threading.Lock()
|
||||||
|
local_pool = queue.Queue()
|
||||||
|
current_size = 0
|
||||||
|
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def pooled():
|
||||||
|
nonlocal current_size
|
||||||
|
instance = None
|
||||||
|
|
||||||
|
# If we still have free slots, then we have room to create new
|
||||||
|
# instances.
|
||||||
|
if current_size < size:
|
||||||
|
with lock:
|
||||||
|
# We need to check again if we have slots available, since
|
||||||
|
# the situation might be different after acquiring the lock
|
||||||
|
if current_size < size:
|
||||||
|
current_size += 1
|
||||||
|
instance = builder()
|
||||||
|
|
||||||
|
# Watchout: current_size can be equal to size if the previous part of
|
||||||
|
# the function has been executed, that's why we need to check if the
|
||||||
|
# instance is None.
|
||||||
|
if instance is None and current_size == size:
|
||||||
|
instance = local_pool.get(timeout=timeout)
|
||||||
|
|
||||||
|
yield instance
|
||||||
|
|
||||||
|
local_pool.put(instance)
|
||||||
|
|
||||||
|
return pooled
|
||||||
|
|
||||||
|
|
||||||
def serialize(data):
|
def serialize(data):
|
||||||
"""Serialize a dict into a JSON formatted string.
|
"""Serialize a dict into a JSON formatted string.
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ import multiprocessing
|
|||||||
from flask import Flask
|
from flask import Flask
|
||||||
import gunicorn.app.base
|
import gunicorn.app.base
|
||||||
|
|
||||||
|
from bigchaindb import util
|
||||||
from bigchaindb import Bigchain
|
from bigchaindb import Bigchain
|
||||||
from bigchaindb.web import views
|
from bigchaindb.web import views
|
||||||
from bigchaindb.monitor import Monitor
|
from bigchaindb.monitor import Monitor
|
||||||
@ -46,7 +47,7 @@ class StandaloneApplication(gunicorn.app.base.BaseApplication):
|
|||||||
return self.application
|
return self.application
|
||||||
|
|
||||||
|
|
||||||
def create_app(debug=False):
|
def create_app(settings):
|
||||||
"""Return an instance of the Flask application.
|
"""Return an instance of the Flask application.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -55,9 +56,12 @@ def create_app(debug=False):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
app.debug = debug
|
|
||||||
app.config['bigchain'] = Bigchain()
|
app.debug = settings.get('debug', False)
|
||||||
|
|
||||||
|
app.config['bigchain_pool'] = util.pool(Bigchain, size=settings.get('threads', 4))
|
||||||
app.config['monitor'] = Monitor()
|
app.config['monitor'] = Monitor()
|
||||||
|
|
||||||
app.register_blueprint(views.basic_views, url_prefix='/api/v1')
|
app.register_blueprint(views.basic_views, url_prefix='/api/v1')
|
||||||
return app
|
return app
|
||||||
|
|
||||||
@ -81,8 +85,8 @@ def create_server(settings):
|
|||||||
if not settings.get('threads'):
|
if not settings.get('threads'):
|
||||||
settings['threads'] = (multiprocessing.cpu_count() * 2) + 1
|
settings['threads'] = (multiprocessing.cpu_count() * 2) + 1
|
||||||
|
|
||||||
debug = settings.pop('debug', False)
|
app = create_app(settings)
|
||||||
app = create_app(debug)
|
settings.pop('debug', False)
|
||||||
standalone = StandaloneApplication(app, settings)
|
standalone = StandaloneApplication(app, settings)
|
||||||
return standalone
|
return standalone
|
||||||
|
|
||||||
|
@ -22,12 +22,12 @@ def record(state):
|
|||||||
"""This function checks if the blueprint can be initialized
|
"""This function checks if the blueprint can be initialized
|
||||||
with the provided state."""
|
with the provided state."""
|
||||||
|
|
||||||
bigchain = state.app.config.get('bigchain')
|
bigchain_pool = state.app.config.get('bigchain_pool')
|
||||||
monitor = state.app.config.get('monitor')
|
monitor = state.app.config.get('monitor')
|
||||||
|
|
||||||
if bigchain is None:
|
if bigchain_pool is None:
|
||||||
raise ValueError('This blueprint expects you to provide '
|
raise Exception('This blueprint expects you to provide '
|
||||||
'database access through `bigchain`.')
|
'a pool of Bigchain instances called `bigchain_pool`')
|
||||||
|
|
||||||
if monitor is None:
|
if monitor is None:
|
||||||
raise ValueError('This blueprint expects you to provide '
|
raise ValueError('This blueprint expects you to provide '
|
||||||
@ -46,9 +46,11 @@ def get_transaction(tx_id):
|
|||||||
A JSON string containing the data about the transaction.
|
A JSON string containing the data about the transaction.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
bigchain = current_app.config['bigchain']
|
pool = current_app.config['bigchain_pool']
|
||||||
|
|
||||||
|
with pool() as bigchain:
|
||||||
|
tx = bigchain.get_transaction(tx_id)
|
||||||
|
|
||||||
tx = bigchain.get_transaction(tx_id)
|
|
||||||
return flask.jsonify(**tx)
|
return flask.jsonify(**tx)
|
||||||
|
|
||||||
|
|
||||||
@ -59,7 +61,7 @@ def create_transaction():
|
|||||||
Return:
|
Return:
|
||||||
A JSON string containing the data about the transaction.
|
A JSON string containing the data about the transaction.
|
||||||
"""
|
"""
|
||||||
bigchain = current_app.config['bigchain']
|
pool = current_app.config['bigchain_pool']
|
||||||
monitor = current_app.config['monitor']
|
monitor = current_app.config['monitor']
|
||||||
|
|
||||||
val = {}
|
val = {}
|
||||||
@ -68,17 +70,16 @@ def create_transaction():
|
|||||||
# set to `application/json`
|
# set to `application/json`
|
||||||
tx = request.get_json(force=True)
|
tx = request.get_json(force=True)
|
||||||
|
|
||||||
if tx['transaction']['operation'] == 'CREATE':
|
with pool() as bigchain:
|
||||||
tx = util.transform_create(tx)
|
if tx['transaction']['operation'] == 'CREATE':
|
||||||
tx = bigchain.consensus.sign_transaction(
|
tx = util.transform_create(tx)
|
||||||
tx, private_key=bigchain.me_private)
|
tx = bigchain.consensus.sign_transaction(tx, private_key=bigchain.me_private)
|
||||||
|
|
||||||
if not bigchain.consensus.verify_signature(tx):
|
if not bigchain.consensus.verify_signature(tx):
|
||||||
val['error'] = 'Invalid transaction signature'
|
val['error'] = 'Invalid transaction signature'
|
||||||
|
|
||||||
with monitor.timer('write_transaction',
|
with monitor.timer('write_transaction', rate=bigchaindb.config['statsd']['rate']):
|
||||||
rate=bigchaindb.config['statsd']['rate']):
|
val = bigchain.write_transaction(tx)
|
||||||
val = bigchain.write_transaction(tx)
|
|
||||||
|
|
||||||
return flask.jsonify(**tx)
|
return flask.jsonify(**tx)
|
||||||
|
|
||||||
|
@ -1,7 +1,33 @@
|
|||||||
from bigchaindb import util
|
import pytest
|
||||||
|
import queue
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_queue(monkeypatch):
|
||||||
|
|
||||||
|
class MockQueue:
|
||||||
|
items = []
|
||||||
|
|
||||||
|
def get(self, timeout=None):
|
||||||
|
try:
|
||||||
|
return self.items.pop()
|
||||||
|
except IndexError:
|
||||||
|
if timeout:
|
||||||
|
raise queue.Empty()
|
||||||
|
raise
|
||||||
|
|
||||||
|
def put(self, item):
|
||||||
|
self.items.append(item)
|
||||||
|
|
||||||
|
mockqueue = MockQueue()
|
||||||
|
|
||||||
|
monkeypatch.setattr('queue.Queue', lambda: mockqueue)
|
||||||
|
return mockqueue
|
||||||
|
|
||||||
|
|
||||||
def test_transform_create(b, user_private_key, user_public_key):
|
def test_transform_create(b, user_private_key, user_public_key):
|
||||||
|
from bigchaindb import util
|
||||||
|
|
||||||
tx = util.create_tx(user_public_key, user_public_key, None, 'CREATE')
|
tx = util.create_tx(user_public_key, user_public_key, None, 'CREATE')
|
||||||
tx = util.transform_create(tx)
|
tx = util.transform_create(tx)
|
||||||
tx = util.sign_tx(tx, b.me_private)
|
tx = util.sign_tx(tx, b.me_private)
|
||||||
@ -10,3 +36,88 @@ def test_transform_create(b, user_private_key, user_public_key):
|
|||||||
assert tx['transaction']['new_owner'] == user_public_key
|
assert tx['transaction']['new_owner'] == user_public_key
|
||||||
assert util.verify_signature(tx)
|
assert util.verify_signature(tx)
|
||||||
|
|
||||||
|
|
||||||
|
def test_empty_pool_is_populated_with_instances(mock_queue):
|
||||||
|
from bigchaindb import util
|
||||||
|
|
||||||
|
pool = util.pool(lambda: 'hello', 4)
|
||||||
|
|
||||||
|
assert len(mock_queue.items) == 0
|
||||||
|
|
||||||
|
with pool() as instance:
|
||||||
|
assert instance == 'hello'
|
||||||
|
assert len(mock_queue.items) == 1
|
||||||
|
|
||||||
|
with pool() as instance:
|
||||||
|
assert instance == 'hello'
|
||||||
|
assert len(mock_queue.items) == 2
|
||||||
|
|
||||||
|
with pool() as instance:
|
||||||
|
assert instance == 'hello'
|
||||||
|
assert len(mock_queue.items) == 3
|
||||||
|
|
||||||
|
with pool() as instance:
|
||||||
|
assert instance == 'hello'
|
||||||
|
assert len(mock_queue.items) == 4
|
||||||
|
|
||||||
|
with pool() as instance:
|
||||||
|
assert instance == 'hello'
|
||||||
|
assert len(mock_queue.items) == 4
|
||||||
|
|
||||||
|
|
||||||
|
def test_pool_blocks_if_no_instances_available(mock_queue):
|
||||||
|
from bigchaindb import util
|
||||||
|
|
||||||
|
pool = util.pool(lambda: 'hello', 4)
|
||||||
|
|
||||||
|
assert len(mock_queue.items) == 0
|
||||||
|
|
||||||
|
# We need to manually trigger the `__enter__` method so the context
|
||||||
|
# manager will "hang" and not return the resource to the pool
|
||||||
|
assert pool().__enter__() == 'hello'
|
||||||
|
assert len(mock_queue.items) == 0
|
||||||
|
|
||||||
|
assert pool().__enter__() == 'hello'
|
||||||
|
assert len(mock_queue.items) == 0
|
||||||
|
|
||||||
|
assert pool().__enter__() == 'hello'
|
||||||
|
assert len(mock_queue.items) == 0
|
||||||
|
|
||||||
|
# We need to keep a reference of the last context manager so we can
|
||||||
|
# manually release the resource
|
||||||
|
last = pool()
|
||||||
|
assert last.__enter__() == 'hello'
|
||||||
|
assert len(mock_queue.items) == 0
|
||||||
|
|
||||||
|
# This would block using `queue.Queue` but since we mocked it it will
|
||||||
|
# just raise a IndexError because it's trying to pop from an empty list.
|
||||||
|
with pytest.raises(IndexError):
|
||||||
|
assert pool().__enter__() == 'hello'
|
||||||
|
assert len(mock_queue.items) == 0
|
||||||
|
|
||||||
|
# Release the last resource
|
||||||
|
last.__exit__(None, None, None)
|
||||||
|
assert len(mock_queue.items) == 1
|
||||||
|
|
||||||
|
assert pool().__enter__() == 'hello'
|
||||||
|
assert len(mock_queue.items) == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_pool_raises_empty_exception_when_timeout(mock_queue):
|
||||||
|
from bigchaindb import util
|
||||||
|
|
||||||
|
pool = util.pool(lambda: 'hello', 1, timeout=1)
|
||||||
|
|
||||||
|
assert len(mock_queue.items) == 0
|
||||||
|
|
||||||
|
with pool() as instance:
|
||||||
|
assert instance == 'hello'
|
||||||
|
assert len(mock_queue.items) == 1
|
||||||
|
|
||||||
|
# take the only resource available
|
||||||
|
assert pool().__enter__() == 'hello'
|
||||||
|
|
||||||
|
with pytest.raises(queue.Empty):
|
||||||
|
with pool() as instance:
|
||||||
|
assert instance == 'hello'
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@ def app(request, node_config):
|
|||||||
restore_config(request, node_config)
|
restore_config(request, node_config)
|
||||||
|
|
||||||
from bigchaindb.web import server
|
from bigchaindb.web import server
|
||||||
app = server.create_app(debug=True)
|
app = server.create_app({'debug': True})
|
||||||
return app
|
return app
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user