Use werkzeug to run the developement API server
wsgi.simple_server in a mono threaded process that can handle only 5 requests at a time. Even the doc recommands to setup Ceilometer through an other WSGI services like Apache 'mod_wsgi', we can provide a better testing API server. So this patch changes the default HTTP server to the werkzeug one with a autodiscovery of number of workers that we can use. The client queue of werkzeug is 128, so on a 4 cpus machine, ceilometer-api can now handle 512 connections instead of 5. Also the change adds references of how to deploy pecan application in the documentation. The config option enable_reverse_dns_lookup can be safely removed, because werkzeug doesn't do any reverse dns lookup. DocImpact: configuration options changed: enable_reverse_dns_lookup removed, api_workers added Change-Id: If7450b393ea88bc185e5c82b706ace9c38ce350e
This commit is contained in:
parent
fa27b13390
commit
09a2f0994f
@ -27,12 +27,6 @@ OPTS = [
|
|||||||
default='0.0.0.0',
|
default='0.0.0.0',
|
||||||
help='The listen IP for the ceilometer API server.',
|
help='The listen IP for the ceilometer API server.',
|
||||||
),
|
),
|
||||||
cfg.BoolOpt('enable_reverse_dns_lookup',
|
|
||||||
default=False,
|
|
||||||
help=('Set it to False if your environment does not need '
|
|
||||||
'or have a DNS server, otherwise it will delay the '
|
|
||||||
'response from the API.')
|
|
||||||
),
|
|
||||||
]
|
]
|
||||||
|
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
|
@ -15,19 +15,20 @@
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import socket
|
|
||||||
from wsgiref import simple_server
|
|
||||||
|
|
||||||
import netaddr
|
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from paste import deploy
|
from paste import deploy
|
||||||
import pecan
|
import pecan
|
||||||
|
from werkzeug import serving
|
||||||
|
|
||||||
from ceilometer.api import config as api_config
|
from ceilometer.api import config as api_config
|
||||||
from ceilometer.api import hooks
|
from ceilometer.api import hooks
|
||||||
from ceilometer.api import middleware
|
from ceilometer.api import middleware
|
||||||
from ceilometer.i18n import _
|
from ceilometer.i18n import _
|
||||||
|
from ceilometer.i18n import _LW
|
||||||
from ceilometer.openstack.common import log
|
from ceilometer.openstack.common import log
|
||||||
|
from ceilometer import service
|
||||||
from ceilometer import storage
|
from ceilometer import storage
|
||||||
|
|
||||||
LOG = log.getLogger(__name__)
|
LOG = log.getLogger(__name__)
|
||||||
@ -39,6 +40,8 @@ OPTS = [
|
|||||||
default="api_paste.ini",
|
default="api_paste.ini",
|
||||||
help="Configuration file for WSGI definition of API."
|
help="Configuration file for WSGI definition of API."
|
||||||
),
|
),
|
||||||
|
cfg.IntOpt('api_workers', default=1,
|
||||||
|
help='Number of workers for Ceilometer API server.'),
|
||||||
]
|
]
|
||||||
|
|
||||||
API_OPTS = [
|
API_OPTS = [
|
||||||
@ -77,9 +80,16 @@ def setup_app(pecan_config=None, extra_hooks=None):
|
|||||||
|
|
||||||
cfg.set_defaults(API_OPTS, pecan_debug=CONF.debug)
|
cfg.set_defaults(API_OPTS, pecan_debug=CONF.debug)
|
||||||
|
|
||||||
|
# NOTE(sileht): pecan debug won't work in multi-process environment
|
||||||
|
pecan_debug = CONF.api.pecan_debug
|
||||||
|
if service.get_workers('api') != 1 and pecan_debug:
|
||||||
|
pecan_debug = False
|
||||||
|
LOG.warning(_LW('pecan_debug cannot be enabled, if workers is > 1, '
|
||||||
|
'the value is overrided with False'))
|
||||||
|
|
||||||
app = pecan.make_app(
|
app = pecan.make_app(
|
||||||
pecan_config.app.root,
|
pecan_config.app.root,
|
||||||
debug=CONF.api.pecan_debug,
|
debug=pecan_debug,
|
||||||
force_canonical=getattr(pecan_config.app, 'force_canonical', True),
|
force_canonical=getattr(pecan_config.app, 'force_canonical', True),
|
||||||
hooks=app_hooks,
|
hooks=app_hooks,
|
||||||
wrap_app=middleware.ParsableErrorMiddleware,
|
wrap_app=middleware.ParsableErrorMiddleware,
|
||||||
@ -106,36 +116,6 @@ class VersionSelectorApplication(object):
|
|||||||
return self.v2(environ, start_response)
|
return self.v2(environ, start_response)
|
||||||
|
|
||||||
|
|
||||||
def get_server_cls(host):
|
|
||||||
"""Return an appropriate WSGI server class base on provided host
|
|
||||||
|
|
||||||
:param host: The listen host for the ceilometer API server.
|
|
||||||
"""
|
|
||||||
server_cls = simple_server.WSGIServer
|
|
||||||
if netaddr.valid_ipv6(host):
|
|
||||||
# NOTE(dzyu) make sure use IPv6 sockets if host is in IPv6 pattern
|
|
||||||
if getattr(server_cls, 'address_family') == socket.AF_INET:
|
|
||||||
class ipv6_server_cls(server_cls):
|
|
||||||
address_family = socket.AF_INET6
|
|
||||||
return ipv6_server_cls
|
|
||||||
return server_cls
|
|
||||||
|
|
||||||
|
|
||||||
def get_handler_cls():
|
|
||||||
cls = simple_server.WSGIRequestHandler
|
|
||||||
|
|
||||||
# old-style class doesn't support super
|
|
||||||
class CeilometerHandler(cls, object):
|
|
||||||
def address_string(self):
|
|
||||||
if cfg.CONF.api.enable_reverse_dns_lookup:
|
|
||||||
return super(CeilometerHandler, self).address_string()
|
|
||||||
else:
|
|
||||||
# disable reverse dns lookup, directly return ip address
|
|
||||||
return self.client_address[0]
|
|
||||||
|
|
||||||
return CeilometerHandler
|
|
||||||
|
|
||||||
|
|
||||||
def load_app():
|
def load_app():
|
||||||
# Build the WSGI app
|
# Build the WSGI app
|
||||||
cfg_file = None
|
cfg_file = None
|
||||||
@ -155,10 +135,6 @@ def build_server():
|
|||||||
app = load_app()
|
app = load_app()
|
||||||
# Create the WSGI server and start it
|
# Create the WSGI server and start it
|
||||||
host, port = cfg.CONF.api.host, cfg.CONF.api.port
|
host, port = cfg.CONF.api.host, cfg.CONF.api.port
|
||||||
server_cls = get_server_cls(host)
|
|
||||||
|
|
||||||
srv = simple_server.make_server(host, port, app,
|
|
||||||
server_cls, get_handler_cls())
|
|
||||||
|
|
||||||
LOG.info(_('Starting server in PID %s') % os.getpid())
|
LOG.info(_('Starting server in PID %s') % os.getpid())
|
||||||
LOG.info(_("Configuration:"))
|
LOG.info(_("Configuration:"))
|
||||||
@ -172,7 +148,9 @@ def build_server():
|
|||||||
LOG.info(_("serving on http://%(host)s:%(port)s") % (
|
LOG.info(_("serving on http://%(host)s:%(port)s") % (
|
||||||
{'host': host, 'port': port}))
|
{'host': host, 'port': port}))
|
||||||
|
|
||||||
return srv
|
workers = service.get_workers('api')
|
||||||
|
serving.run_simple(cfg.CONF.api.host, cfg.CONF.api.port,
|
||||||
|
app, processes=workers)
|
||||||
|
|
||||||
|
|
||||||
def app_factory(global_config, **local_conf):
|
def app_factory(global_config, **local_conf):
|
||||||
|
@ -20,5 +20,4 @@ from ceilometer import service
|
|||||||
|
|
||||||
def main():
|
def main():
|
||||||
service.prepare_service()
|
service.prepare_service()
|
||||||
srv = app.build_server()
|
app.build_server()
|
||||||
srv.serve_forever()
|
|
||||||
|
@ -15,8 +15,6 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
import socket
|
|
||||||
|
|
||||||
import mock
|
import mock
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_config import fixture as fixture_config
|
from oslo_config import fixture as fixture_config
|
||||||
@ -31,19 +29,6 @@ class TestApp(base.BaseTestCase):
|
|||||||
super(TestApp, self).setUp()
|
super(TestApp, self).setUp()
|
||||||
self.CONF = self.useFixture(fixture_config.Config()).conf
|
self.CONF = self.useFixture(fixture_config.Config()).conf
|
||||||
|
|
||||||
def test_WSGI_address_family(self):
|
|
||||||
self.CONF.set_override('host', '::', group='api')
|
|
||||||
server_cls = app.get_server_cls(cfg.CONF.api.host)
|
|
||||||
self.assertEqual(server_cls.address_family, socket.AF_INET6)
|
|
||||||
|
|
||||||
self.CONF.set_override('host', '127.0.0.1', group='api')
|
|
||||||
server_cls = app.get_server_cls(cfg.CONF.api.host)
|
|
||||||
self.assertEqual(server_cls.address_family, socket.AF_INET)
|
|
||||||
|
|
||||||
self.CONF.set_override('host', 'ddddd', group='api')
|
|
||||||
server_cls = app.get_server_cls(cfg.CONF.api.host)
|
|
||||||
self.assertEqual(server_cls.address_family, socket.AF_INET)
|
|
||||||
|
|
||||||
def test_api_paste_file_not_exist(self):
|
def test_api_paste_file_not_exist(self):
|
||||||
self.CONF.set_override('api_paste_config', 'non-existent-file')
|
self.CONF.set_override('api_paste_config', 'non-existent-file')
|
||||||
with mock.patch.object(self.CONF, 'find_file') as ff:
|
with mock.patch.object(self.CONF, 'find_file') as ff:
|
||||||
@ -55,10 +40,11 @@ class TestApp(base.BaseTestCase):
|
|||||||
@mock.patch('ceilometer.api.hooks.PipelineHook', mock.MagicMock())
|
@mock.patch('ceilometer.api.hooks.PipelineHook', mock.MagicMock())
|
||||||
@mock.patch('pecan.make_app')
|
@mock.patch('pecan.make_app')
|
||||||
def test_pecan_debug(self, mocked):
|
def test_pecan_debug(self, mocked):
|
||||||
def _check_pecan_debug(g_debug, p_debug, expected):
|
def _check_pecan_debug(g_debug, p_debug, expected, workers=1):
|
||||||
self.CONF.set_override('debug', g_debug)
|
self.CONF.set_override('debug', g_debug)
|
||||||
if p_debug is not None:
|
if p_debug is not None:
|
||||||
self.CONF.set_override('pecan_debug', p_debug, group='api')
|
self.CONF.set_override('pecan_debug', p_debug, group='api')
|
||||||
|
self.CONF.set_override('api_workers', workers)
|
||||||
app.setup_app()
|
app.setup_app()
|
||||||
args, kwargs = mocked.call_args
|
args, kwargs = mocked.call_args
|
||||||
self.assertEqual(expected, kwargs.get('debug'))
|
self.assertEqual(expected, kwargs.get('debug'))
|
||||||
@ -67,3 +53,7 @@ class TestApp(base.BaseTestCase):
|
|||||||
_check_pecan_debug(g_debug=True, p_debug=None, expected=True)
|
_check_pecan_debug(g_debug=True, p_debug=None, expected=True)
|
||||||
_check_pecan_debug(g_debug=True, p_debug=False, expected=False)
|
_check_pecan_debug(g_debug=True, p_debug=False, expected=False)
|
||||||
_check_pecan_debug(g_debug=False, p_debug=True, expected=True)
|
_check_pecan_debug(g_debug=False, p_debug=True, expected=True)
|
||||||
|
_check_pecan_debug(g_debug=True, p_debug=None, expected=False,
|
||||||
|
workers=5)
|
||||||
|
_check_pecan_debug(g_debug=False, p_debug=True, expected=False,
|
||||||
|
workers=5)
|
||||||
|
@ -63,3 +63,6 @@ multiple processes, there is no way to set debug mode in the multiprocessing
|
|||||||
case. To allow multiple processes the DebugMiddleware may be turned off by
|
case. To allow multiple processes the DebugMiddleware may be turned off by
|
||||||
setting ``pecan_debug`` to ``False`` in the ``api`` section of
|
setting ``pecan_debug`` to ``False`` in the ``api`` section of
|
||||||
``ceilometer.conf``.
|
``ceilometer.conf``.
|
||||||
|
|
||||||
|
For other WSGI setup you can refer to the `pecan deployement`_ documentation.
|
||||||
|
.. _`pecan deployement`: http://pecan.readthedocs.org/en/latest/deployment.html#deployment
|
||||||
|
@ -44,5 +44,6 @@ SQLAlchemy>=0.9.7,<=0.9.99
|
|||||||
sqlalchemy-migrate>=0.9.1,!=0.9.2
|
sqlalchemy-migrate>=0.9.1,!=0.9.2
|
||||||
stevedore>=1.1.0 # Apache-2.0
|
stevedore>=1.1.0 # Apache-2.0
|
||||||
tooz>=0.3 # Apache-2.0
|
tooz>=0.3 # Apache-2.0
|
||||||
|
werkzeug>=0.7 # BSD License
|
||||||
WebOb>=1.2.3
|
WebOb>=1.2.3
|
||||||
WSME>=0.6
|
WSME>=0.6
|
||||||
|
Loading…
x
Reference in New Issue
Block a user