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,