From 20d3dd4ff3721e5b2d4a952d915542ca855c9636 Mon Sep 17 00:00:00 2001 From: vrde <agranzot@gmail.com> Date: Tue, 22 Mar 2016 18:42:50 +0100 Subject: [PATCH 1/5] Wrap the wsgi app in a standalone Gunicorn app --- bigchaindb/__init__.py | 5 +++ bigchaindb/commands/bigchain.py | 4 ++ bigchaindb/processes.py | 6 ++- bigchaindb/web/server.py | 67 ++++++++++++++++++++++++++++++++- tests/web/test_server.py | 13 +++++++ 5 files changed, 91 insertions(+), 4 deletions(-) create mode 100644 tests/web/test_server.py diff --git a/bigchaindb/__init__.py b/bigchaindb/__init__.py index 9283913f..28e2384e 100644 --- a/bigchaindb/__init__.py +++ b/bigchaindb/__init__.py @@ -24,6 +24,10 @@ def e(key, default=None, conv=None): config = { + 'server': { + 'bind': ':'.join([e('BIGCHAIN_SERVER_BINDHOST', default='0.0.0.0'), + e('BIGCHAIN_SERVER_BINDPORT', default='5000')]) + }, 'database': { 'host': e('BIGCHAIN_DATABASE_HOST', default='localhost'), 'port': e('BIGCHAIN_DATABASE_PORT', default=28015), @@ -49,3 +53,4 @@ config = { # for more info. _config = copy.deepcopy(config) from bigchaindb.core import Bigchain # noqa + diff --git a/bigchaindb/commands/bigchain.py b/bigchaindb/commands/bigchain.py index 35b2de2f..c1be6761 100644 --- a/bigchaindb/commands/bigchain.py +++ b/bigchaindb/commands/bigchain.py @@ -55,6 +55,10 @@ def run_configure(args, skip_if_exists=False): conf['keypair']['private'], conf['keypair']['public'] = crypto.generate_key_pair() if not args.yes: + for key in ('host', 'port'): + val = conf['server'][key] + conf['server'][key] = input('API Server {}? (default `{}`): '.format(key, val)) or val + for key in ('host', 'port', 'name'): val = conf['database'][key] conf['database'][key] = input('Database {}? (default `{}`): '.format(key, val)) or val diff --git a/bigchaindb/processes.py b/bigchaindb/processes.py index 661cd9db..51844d6c 100644 --- a/bigchaindb/processes.py +++ b/bigchaindb/processes.py @@ -1,8 +1,10 @@ +import copy import logging import multiprocessing as mp import rethinkdb as r +import bigchaindb from bigchaindb import Bigchain from bigchaindb.voter import Voter from bigchaindb.block import Block @@ -68,8 +70,8 @@ class Processes(object): block = Block(self.q_new_transaction) # start the web api - webapi = server.create_app() - p_webapi = mp.Process(name='webapi', target=webapi.run, kwargs={'host': 'localhost'}) + app_server = server.create_server(bigchaindb.config['server']) + p_webapi = mp.Process(name='webapi', target=app_server.run) p_webapi.start() # initialize the processes diff --git a/bigchaindb/web/server.py b/bigchaindb/web/server.py index 0872fae5..208f2458 100644 --- a/bigchaindb/web/server.py +++ b/bigchaindb/web/server.py @@ -1,16 +1,56 @@ -"""This module contains basic functions to instantiate the BigchainDB API. """ +"""This module contains basic functions to instantiate the BigchainDB API. + +The application is implemented in Flask and runs using Gunicorn. +""" + +import copy +import multiprocessing from flask import Flask from bigchaindb import Bigchain from bigchaindb.web import views +import gunicorn.app.base + + +class StandaloneApplication(gunicorn.app.base.BaseApplication): + """Run a **wsgi** app wrapping it in a Gunicorn Base Application. + + Adapted from: + - http://docs.gunicorn.org/en/latest/custom.html + """ + + def __init__(self, app, options=None): + '''Initialize a new standalone application. + + Args: + app: A wsgi Python application. + options (dict): the configuration. + + ''' + self.options = options or {} + self.application = app + super(StandaloneApplication, self).__init__() + + def load_config(self): + config = dict((key, value) for key, value in self.options.items() + if key in self.cfg.settings and value is not None) + + for key, value in config.items(): + # not sure if we need the `key.lower` here, will just keep + # keep it for now. + self.cfg.set(key.lower(), value) + + def load(self): + return self.application def create_app(debug=False): """Return an instance of the Flask application. Args: - debug (bool): a flag to activate the debug mode for the app (default: False). + debug (bool): a flag to activate the debug mode for the app + (default: False). """ app = Flask(__name__) @@ -18,3 +58,26 @@ def create_app(debug=False): app.config['bigchain'] = Bigchain() app.register_blueprint(views.basic_views, url_prefix='/api/v1') return app + + +def create_server(settings): + """Wrap and return an application ready to be run. + + Args: + settings (dict): a dictionary containing the settings, more info + here http://docs.gunicorn.org/en/latest/settings.html + + Return: + an initialized instance of the application. + """ + + settings = copy.deepcopy(settings) + + if not settings.get('workers'): + settings['workers'] = (multiprocessing.cpu_count() * 2) + 1 + + debug = settings.pop('debug', False) + app = create_app(debug) + standalone = StandaloneApplication(app, settings) + return standalone + diff --git a/tests/web/test_server.py b/tests/web/test_server.py new file mode 100644 index 00000000..874567d7 --- /dev/null +++ b/tests/web/test_server.py @@ -0,0 +1,13 @@ +import copy + + +def test_settings(monkeypatch): + import bigchaindb + from bigchaindb.web import server + + s = server.create_server(bigchaindb.config['server']) + + # for whatever reason the value is wrapped in a list + # needs further investigation + assert s.cfg.bind[0] == bigchaindb.config['server']['bind'] + From 0f212ea1137ef250440243e3e168ea08e5faf8aa Mon Sep 17 00:00:00 2001 From: vrde <agranzot@gmail.com> Date: Tue, 22 Mar 2016 21:49:19 +0100 Subject: [PATCH 2/5] Simplify env var name --- bigchaindb/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bigchaindb/__init__.py b/bigchaindb/__init__.py index 28e2384e..a5ebac7e 100644 --- a/bigchaindb/__init__.py +++ b/bigchaindb/__init__.py @@ -25,8 +25,7 @@ def e(key, default=None, conv=None): config = { 'server': { - 'bind': ':'.join([e('BIGCHAIN_SERVER_BINDHOST', default='0.0.0.0'), - e('BIGCHAIN_SERVER_BINDPORT', default='5000')]) + 'bind': e('BIGCHAIN_SERVER_BIND', default='0.0.0.0:5000'), }, 'database': { 'host': e('BIGCHAIN_DATABASE_HOST', default='localhost'), From fcb87b3c9b4e1a7cd76d8e9421cd99d0e7831bdc Mon Sep 17 00:00:00 2001 From: vrde <agranzot@gmail.com> Date: Wed, 23 Mar 2016 16:32:05 +0100 Subject: [PATCH 3/5] Fix banner --- bigchaindb/__init__.py | 1 - bigchaindb/processes.py | 16 +++++++++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/bigchaindb/__init__.py b/bigchaindb/__init__.py index a5ebac7e..414397f6 100644 --- a/bigchaindb/__init__.py +++ b/bigchaindb/__init__.py @@ -2,7 +2,6 @@ import os import copy - def e(key, default=None, conv=None): '''Get the environment variable `key`, fallback to `default` if nothing is found. diff --git a/bigchaindb/processes.py b/bigchaindb/processes.py index 51844d6c..20b8df8d 100644 --- a/bigchaindb/processes.py +++ b/bigchaindb/processes.py @@ -1,4 +1,3 @@ -import copy import logging import multiprocessing as mp @@ -13,6 +12,18 @@ from bigchaindb.web import server logger = logging.getLogger(__name__) +BANNER = """ +**************************************************************************** +* * +* Initialization complete. BigchainDB is ready and waiting for events. * +* You can send events through the API documented at: * +* - http://docs.bigchaindb.apiary.io/ * +* * +* Listening to client connections on: {:<15} * +* * +**************************************************************************** +""" + class Processes(object): @@ -94,5 +105,4 @@ class Processes(object): # start message block.initialized.wait() p_voter.initialized.wait() - logger.info('Initialization complete. BigchainDB ready and waiting for events.') - logger.info('You can send events through the API documented at http://docs.bigchaindb.apiary.io/') + logger.info(BANNER.format(bigchaindb.config['server']['bind'])) From e10a5aed9597b9fb560a7c7cfb164cfb7aace2a8 Mon Sep 17 00:00:00 2001 From: vrde <agranzot@gmail.com> Date: Wed, 30 Mar 2016 17:54:17 +0200 Subject: [PATCH 4/5] Add gunicorn dep --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 6156b417..1f1b0ccf 100644 --- a/setup.py +++ b/setup.py @@ -79,6 +79,7 @@ setup( 'bitcoin==1.1.42', 'flask==0.10.1', 'requests==2.9', + 'gunicorn', ], setup_requires=['pytest-runner'], tests_require=tests_require, From b988b3f6f7ed2993df285b3d3350747bc836a358 Mon Sep 17 00:00:00 2001 From: vrde <agranzot@gmail.com> Date: Thu, 7 Apr 2016 10:58:29 +0200 Subject: [PATCH 5/5] Pin major version for gunicorn --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 1f1b0ccf..9bf961ad 100644 --- a/setup.py +++ b/setup.py @@ -79,7 +79,7 @@ setup( 'bitcoin==1.1.42', 'flask==0.10.1', 'requests==2.9', - 'gunicorn', + 'gunicorn~=19.0', ], setup_requires=['pytest-runner'], tests_require=tests_require,