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:
Mehdi Abaakouk 2015-02-10 09:41:05 +01:00 committed by Mehdi Abaakouk
parent fa27b13390
commit 09a2f0994f
6 changed files with 28 additions and 63 deletions

View File

@ -27,12 +27,6 @@ OPTS = [
default='0.0.0.0',
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

View File

@ -15,19 +15,20 @@
import logging
import os
import socket
from wsgiref import simple_server
import netaddr
from oslo_config import cfg
from paste import deploy
import pecan
from werkzeug import serving
from ceilometer.api import config as api_config
from ceilometer.api import hooks
from ceilometer.api import middleware
from ceilometer.i18n import _
from ceilometer.i18n import _LW
from ceilometer.openstack.common import log
from ceilometer import service
from ceilometer import storage
LOG = log.getLogger(__name__)
@ -39,6 +40,8 @@ OPTS = [
default="api_paste.ini",
help="Configuration file for WSGI definition of API."
),
cfg.IntOpt('api_workers', default=1,
help='Number of workers for Ceilometer API server.'),
]
API_OPTS = [
@ -77,9 +80,16 @@ def setup_app(pecan_config=None, extra_hooks=None):
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(
pecan_config.app.root,
debug=CONF.api.pecan_debug,
debug=pecan_debug,
force_canonical=getattr(pecan_config.app, 'force_canonical', True),
hooks=app_hooks,
wrap_app=middleware.ParsableErrorMiddleware,
@ -106,36 +116,6 @@ class VersionSelectorApplication(object):
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():
# Build the WSGI app
cfg_file = None
@ -155,10 +135,6 @@ def build_server():
app = load_app()
# Create the WSGI server and start it
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(_("Configuration:"))
@ -172,7 +148,9 @@ def build_server():
LOG.info(_("serving on http://%(host)s:%(port)s") % (
{'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):

View File

@ -20,5 +20,4 @@ from ceilometer import service
def main():
service.prepare_service()
srv = app.build_server()
srv.serve_forever()
app.build_server()

View File

@ -15,8 +15,6 @@
# License for the specific language governing permissions and limitations
# under the License.
import socket
import mock
from oslo_config import cfg
from oslo_config import fixture as fixture_config
@ -31,19 +29,6 @@ class TestApp(base.BaseTestCase):
super(TestApp, self).setUp()
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):
self.CONF.set_override('api_paste_config', 'non-existent-file')
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('pecan.make_app')
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)
if p_debug is not None:
self.CONF.set_override('pecan_debug', p_debug, group='api')
self.CONF.set_override('api_workers', workers)
app.setup_app()
args, kwargs = mocked.call_args
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=False, expected=False)
_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)

View File

@ -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
setting ``pecan_debug`` to ``False`` in the ``api`` section of
``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

View File

@ -44,5 +44,6 @@ SQLAlchemy>=0.9.7,<=0.9.99
sqlalchemy-migrate>=0.9.1,!=0.9.2
stevedore>=1.1.0 # Apache-2.0
tooz>=0.3 # Apache-2.0
werkzeug>=0.7 # BSD License
WebOb>=1.2.3
WSME>=0.6