Merge "Remove nova.wsgi module"
This commit is contained in:
commit
818f0cd4a3
|
@ -153,7 +153,7 @@ def get_sort_params(input_params, default_key='created_at',
|
|||
The input parameters are not modified.
|
||||
|
||||
:param input_params: webob.multidict of request parameters (from
|
||||
nova.wsgi.Request.params)
|
||||
nova.api.wsgi.Request.params)
|
||||
:param default_key: default sort key value, added to the list if no
|
||||
'sort_key' parameters are supplied
|
||||
:param default_dir: default sort dir value, added to the list if no
|
||||
|
|
|
@ -880,5 +880,8 @@ class APIRouterV21(base_wsgi.Router):
|
|||
|
||||
@classmethod
|
||||
def factory(cls, global_config, **local_config):
|
||||
"""Simple paste factory, :class:`nova.wsgi.Router` doesn't have one."""
|
||||
"""Simple paste factory.
|
||||
|
||||
:class:`nova.api.wsgi.Router` doesn't have one.
|
||||
"""
|
||||
return cls()
|
||||
|
|
205
nova/service.py
205
nova/service.py
|
@ -18,13 +18,20 @@
|
|||
"""Generic Node base class for all workers that run on hosts."""
|
||||
|
||||
import os
|
||||
import os.path
|
||||
import random
|
||||
import socket
|
||||
import ssl
|
||||
import sys
|
||||
|
||||
import eventlet
|
||||
import eventlet.wsgi
|
||||
import greenlet
|
||||
from oslo_concurrency import processutils
|
||||
from oslo_log import log as logging
|
||||
import oslo_messaging as messaging
|
||||
from oslo_service import service
|
||||
from oslo_utils import excutils
|
||||
from oslo_utils import importutils
|
||||
|
||||
from nova.api import wsgi as api_wsgi
|
||||
|
@ -42,15 +49,12 @@ from nova import rpc
|
|||
from nova import servicegroup
|
||||
from nova import utils
|
||||
from nova import version
|
||||
from nova import wsgi
|
||||
|
||||
osprofiler = importutils.try_import("osprofiler")
|
||||
osprofiler_initializer = importutils.try_import("osprofiler.initializer")
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
CONF = nova.conf.CONF
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
SERVICE_MANAGERS = {
|
||||
'nova-compute': 'nova.compute.manager.ComputeManager',
|
||||
|
@ -323,6 +327,193 @@ class Service(service.Service):
|
|||
context.CELL_CACHE = {}
|
||||
|
||||
|
||||
class WSGIServer(service.ServiceBase):
|
||||
"""Server class to manage a WSGI server, serving a WSGI application."""
|
||||
|
||||
default_pool_size = CONF.wsgi.default_pool_size
|
||||
|
||||
def __init__(self, name, app, host='0.0.0.0', port=0, pool_size=None,
|
||||
protocol=eventlet.wsgi.HttpProtocol, backlog=128,
|
||||
use_ssl=False, max_url_len=None):
|
||||
"""Initialize, but do not start, a WSGI server.
|
||||
|
||||
:param name: Pretty name for logging.
|
||||
:param app: The WSGI application to serve.
|
||||
:param host: IP address to serve the application.
|
||||
:param port: Port number to server the application.
|
||||
:param pool_size: Maximum number of eventlets to spawn concurrently.
|
||||
:param backlog: Maximum number of queued connections.
|
||||
:param max_url_len: Maximum length of permitted URLs.
|
||||
:returns: None
|
||||
:raises: nova.exception.InvalidInput
|
||||
"""
|
||||
# Allow operators to customize http requests max header line size.
|
||||
eventlet.wsgi.MAX_HEADER_LINE = CONF.wsgi.max_header_line
|
||||
self.name = name
|
||||
self.app = app
|
||||
self._server = None
|
||||
self._protocol = protocol
|
||||
self.pool_size = pool_size or self.default_pool_size
|
||||
self._pool = eventlet.GreenPool(self.pool_size)
|
||||
self._logger = logging.getLogger("nova.%s.wsgi.server" % self.name)
|
||||
self._use_ssl = use_ssl
|
||||
self._max_url_len = max_url_len
|
||||
self.client_socket_timeout = CONF.wsgi.client_socket_timeout or None
|
||||
|
||||
if backlog < 1:
|
||||
raise exception.InvalidInput(
|
||||
reason=_('The backlog must be more than 0'))
|
||||
|
||||
bind_addr = (host, port)
|
||||
# TODO(dims): eventlet's green dns/socket module does not actually
|
||||
# support IPv6 in getaddrinfo(). We need to get around this in the
|
||||
# future or monitor upstream for a fix
|
||||
try:
|
||||
info = socket.getaddrinfo(bind_addr[0],
|
||||
bind_addr[1],
|
||||
socket.AF_UNSPEC,
|
||||
socket.SOCK_STREAM)[0]
|
||||
family = info[0]
|
||||
bind_addr = info[-1]
|
||||
except Exception:
|
||||
family = socket.AF_INET
|
||||
|
||||
try:
|
||||
self._socket = eventlet.listen(bind_addr, family, backlog=backlog)
|
||||
except EnvironmentError:
|
||||
LOG.error("Could not bind to %(host)s:%(port)s",
|
||||
{'host': host, 'port': port})
|
||||
raise
|
||||
|
||||
(self.host, self.port) = self._socket.getsockname()[0:2]
|
||||
LOG.info("%(name)s listening on %(host)s:%(port)s",
|
||||
{'name': self.name, 'host': self.host, 'port': self.port})
|
||||
|
||||
def start(self):
|
||||
"""Start serving a WSGI application.
|
||||
|
||||
:returns: None
|
||||
"""
|
||||
# The server socket object will be closed after server exits,
|
||||
# but the underlying file descriptor will remain open, and will
|
||||
# give bad file descriptor error. So duplicating the socket object,
|
||||
# to keep file descriptor usable.
|
||||
|
||||
dup_socket = self._socket.dup()
|
||||
dup_socket.setsockopt(socket.SOL_SOCKET,
|
||||
socket.SO_REUSEADDR, 1)
|
||||
# sockets can hang around forever without keepalive
|
||||
dup_socket.setsockopt(socket.SOL_SOCKET,
|
||||
socket.SO_KEEPALIVE, 1)
|
||||
|
||||
# This option isn't available in the OS X version of eventlet
|
||||
if hasattr(socket, 'TCP_KEEPIDLE'):
|
||||
dup_socket.setsockopt(socket.IPPROTO_TCP,
|
||||
socket.TCP_KEEPIDLE,
|
||||
CONF.wsgi.tcp_keepidle)
|
||||
|
||||
if self._use_ssl:
|
||||
try:
|
||||
ca_file = CONF.wsgi.ssl_ca_file
|
||||
cert_file = CONF.wsgi.ssl_cert_file
|
||||
key_file = CONF.wsgi.ssl_key_file
|
||||
|
||||
if cert_file and not os.path.exists(cert_file):
|
||||
raise RuntimeError(
|
||||
_("Unable to find cert_file : %s") % cert_file)
|
||||
|
||||
if ca_file and not os.path.exists(ca_file):
|
||||
raise RuntimeError(
|
||||
_("Unable to find ca_file : %s") % ca_file)
|
||||
|
||||
if key_file and not os.path.exists(key_file):
|
||||
raise RuntimeError(
|
||||
_("Unable to find key_file : %s") % key_file)
|
||||
|
||||
if self._use_ssl and (not cert_file or not key_file):
|
||||
raise RuntimeError(
|
||||
_("When running server in SSL mode, you must "
|
||||
"specify both a cert_file and key_file "
|
||||
"option value in your configuration file"))
|
||||
ssl_kwargs = {
|
||||
'server_side': True,
|
||||
'certfile': cert_file,
|
||||
'keyfile': key_file,
|
||||
'cert_reqs': ssl.CERT_NONE,
|
||||
}
|
||||
|
||||
if CONF.wsgi.ssl_ca_file:
|
||||
ssl_kwargs['ca_certs'] = ca_file
|
||||
ssl_kwargs['cert_reqs'] = ssl.CERT_REQUIRED
|
||||
|
||||
dup_socket = eventlet.wrap_ssl(dup_socket,
|
||||
**ssl_kwargs)
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception():
|
||||
LOG.error(
|
||||
"Failed to start %(name)s on %(host)s:%(port)s with "
|
||||
"SSL support",
|
||||
{'name': self.name, 'host': self.host,
|
||||
'port': self.port})
|
||||
|
||||
wsgi_kwargs = {
|
||||
'func': eventlet.wsgi.server,
|
||||
'sock': dup_socket,
|
||||
'site': self.app,
|
||||
'protocol': self._protocol,
|
||||
'custom_pool': self._pool,
|
||||
'log': self._logger,
|
||||
'log_format': CONF.wsgi.wsgi_log_format,
|
||||
'debug': False,
|
||||
'keepalive': CONF.wsgi.keep_alive,
|
||||
'socket_timeout': self.client_socket_timeout
|
||||
}
|
||||
|
||||
if self._max_url_len:
|
||||
wsgi_kwargs['url_length_limit'] = self._max_url_len
|
||||
|
||||
self._server = utils.spawn(**wsgi_kwargs)
|
||||
|
||||
def reset(self):
|
||||
"""Reset server greenpool size to default.
|
||||
|
||||
:returns: None
|
||||
|
||||
"""
|
||||
self._pool.resize(self.pool_size)
|
||||
|
||||
def stop(self):
|
||||
"""Stop this server.
|
||||
|
||||
This is not a very nice action, as currently the method by which a
|
||||
server is stopped is by killing its eventlet.
|
||||
|
||||
:returns: None
|
||||
|
||||
"""
|
||||
LOG.info("Stopping WSGI server.")
|
||||
|
||||
if self._server is not None:
|
||||
# Resize pool to stop new requests from being processed
|
||||
self._pool.resize(0)
|
||||
self._server.kill()
|
||||
|
||||
def wait(self):
|
||||
"""Block, until the server has stopped.
|
||||
|
||||
Waits on the server's eventlet to finish, then returns.
|
||||
|
||||
:returns: None
|
||||
|
||||
"""
|
||||
try:
|
||||
if self._server is not None:
|
||||
self._pool.waitall()
|
||||
self._server.wait()
|
||||
except greenlet.GreenletExit:
|
||||
LOG.info("WSGI server has stopped.")
|
||||
|
||||
|
||||
class WSGIService(service.Service):
|
||||
"""Provides ability to launch API from a 'paste' configuration."""
|
||||
|
||||
|
@ -363,12 +554,14 @@ class WSGIService(service.Service):
|
|||
'workers': str(self.workers)})
|
||||
raise exception.InvalidInput(msg)
|
||||
self.use_ssl = use_ssl
|
||||
self.server = wsgi.Server(name,
|
||||
self.server = WSGIServer(
|
||||
name,
|
||||
self.app,
|
||||
host=self.host,
|
||||
port=self.port,
|
||||
use_ssl=self.use_ssl,
|
||||
max_url_len=max_url_len)
|
||||
max_url_len=max_url_len,
|
||||
)
|
||||
# Pull back actual port used
|
||||
self.port = self.server.port
|
||||
self.backdoor_port = None
|
||||
|
|
|
@ -19,19 +19,17 @@
|
|||
Test WSGI basics and provide some helper functions for other WSGI tests.
|
||||
"""
|
||||
|
||||
import sys
|
||||
from unittest import mock
|
||||
import tempfile
|
||||
|
||||
import routes
|
||||
import webob
|
||||
|
||||
from nova.api.openstack import wsgi_app
|
||||
from nova.api import wsgi
|
||||
import nova.exception
|
||||
from nova import test
|
||||
from nova import utils
|
||||
|
||||
|
||||
class Test(test.NoDBTestCase):
|
||||
class TestRouter(test.NoDBTestCase):
|
||||
|
||||
def test_router(self):
|
||||
|
||||
|
@ -55,14 +53,60 @@ class Test(test.NoDBTestCase):
|
|||
result = webob.Request.blank('/bad').get_response(Router())
|
||||
self.assertNotEqual(result.body, "Router result")
|
||||
|
||||
@mock.patch('nova.api.openstack.wsgi_app._setup_service', new=mock.Mock())
|
||||
@mock.patch('paste.deploy.loadapp', new=mock.Mock())
|
||||
def test_init_application_passes_sys_argv_to_config(self):
|
||||
|
||||
with utils.temporary_mutation(sys, argv=mock.sentinel.argv):
|
||||
with mock.patch('nova.config.parse_args') as mock_parse_args:
|
||||
wsgi_app.init_application('test-app')
|
||||
mock_parse_args.assert_called_once_with(
|
||||
mock.sentinel.argv,
|
||||
default_config_files=[
|
||||
'/etc/nova/api-paste.ini', '/etc/nova/nova.conf'])
|
||||
class TestLoaderNothingExists(test.NoDBTestCase):
|
||||
"""Loader tests where os.path.exists always returns False."""
|
||||
|
||||
def setUp(self):
|
||||
super(TestLoaderNothingExists, self).setUp()
|
||||
self.stub_out('os.path.exists', lambda _: False)
|
||||
|
||||
def test_relpath_config_not_found(self):
|
||||
self.flags(api_paste_config='api-paste.ini', group='wsgi')
|
||||
self.assertRaises(
|
||||
nova.exception.ConfigNotFound,
|
||||
wsgi.Loader,
|
||||
)
|
||||
|
||||
def test_asbpath_config_not_found(self):
|
||||
self.flags(api_paste_config='/etc/nova/api-paste.ini', group='wsgi')
|
||||
self.assertRaises(
|
||||
nova.exception.ConfigNotFound,
|
||||
wsgi.Loader,
|
||||
)
|
||||
|
||||
|
||||
class TestLoaderNormalFilesystem(test.NoDBTestCase):
|
||||
"""Loader tests with normal filesystem (unmodified os.path module)."""
|
||||
|
||||
_paste_config = """
|
||||
[app:test_app]
|
||||
use = egg:Paste#static
|
||||
document_root = /tmp
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super(TestLoaderNormalFilesystem, self).setUp()
|
||||
self.config = tempfile.NamedTemporaryFile(mode="w+t")
|
||||
self.config.write(self._paste_config.lstrip())
|
||||
self.config.seek(0)
|
||||
self.config.flush()
|
||||
self.loader = wsgi.Loader(self.config.name)
|
||||
|
||||
def test_config_found(self):
|
||||
self.assertEqual(self.config.name, self.loader.config_path)
|
||||
|
||||
def test_app_not_found(self):
|
||||
self.assertRaises(
|
||||
nova.exception.PasteAppNotFound,
|
||||
self.loader.load_app,
|
||||
"nonexistent app",
|
||||
)
|
||||
|
||||
def test_app_found(self):
|
||||
url_parser = self.loader.load_app("test_app")
|
||||
self.assertEqual("/tmp", url_parser.directory)
|
||||
|
||||
def tearDown(self):
|
||||
self.config.close()
|
||||
super(TestLoaderNormalFilesystem, self).tearDown()
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
# Copyright 2010 United States Government as represented by the
|
||||
# Administrator of the National Aeronautics and Space Administration.
|
||||
# Copyright 2010 OpenStack Foundation
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import sys
|
||||
from unittest import mock
|
||||
|
||||
from nova.api.openstack import wsgi_app
|
||||
from nova import test
|
||||
from nova import utils
|
||||
|
||||
|
||||
class TestInitApplication(test.NoDBTestCase):
|
||||
|
||||
@mock.patch('nova.api.openstack.wsgi_app._setup_service', new=mock.Mock())
|
||||
@mock.patch('paste.deploy.loadapp', new=mock.Mock())
|
||||
def test_init_application_passes_sys_argv_to_config(self):
|
||||
|
||||
with utils.temporary_mutation(sys, argv=mock.sentinel.argv):
|
||||
with mock.patch('nova.config.parse_args') as mock_parse_args:
|
||||
wsgi_app.init_application('test-app')
|
||||
mock_parse_args.assert_called_once_with(
|
||||
mock.sentinel.argv,
|
||||
default_config_files=[
|
||||
'/etc/nova/api-paste.ini', '/etc/nova/nova.conf'])
|
|
@ -18,12 +18,18 @@
|
|||
Unit Tests for remote procedure calls using queue
|
||||
"""
|
||||
|
||||
import os.path
|
||||
import socket
|
||||
from unittest import mock
|
||||
|
||||
import eventlet
|
||||
import eventlet.wsgi
|
||||
from oslo_concurrency import processutils
|
||||
from oslo_config import cfg
|
||||
from oslo_service import service as _service
|
||||
import requests
|
||||
import testtools
|
||||
import webob
|
||||
|
||||
from nova import exception
|
||||
from nova import manager
|
||||
|
@ -42,6 +48,9 @@ test_service_opts = [
|
|||
default=0,
|
||||
help="Port number to bind test service to"),
|
||||
]
|
||||
SSL_CERT_DIR = os.path.normpath(
|
||||
os.path.join(os.path.dirname(os.path.abspath(__file__)), 'ssl_cert')
|
||||
)
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.register_opts(test_service_opts)
|
||||
|
@ -314,6 +323,247 @@ class ServiceTestCase(test.NoDBTestCase):
|
|||
mock_check_old.assert_has_calls([mock.call(), mock.call()])
|
||||
|
||||
|
||||
class TestWSGIServer(test.NoDBTestCase):
|
||||
"""WSGI server tests."""
|
||||
|
||||
def test_no_app(self):
|
||||
server = service.WSGIServer("test_app", None)
|
||||
self.assertEqual("test_app", server.name)
|
||||
|
||||
def test_custom_max_header_line(self):
|
||||
self.flags(max_header_line=4096, group='wsgi') # Default is 16384
|
||||
service.WSGIServer("test_custom_max_header_line", None)
|
||||
self.assertEqual(CONF.wsgi.max_header_line,
|
||||
eventlet.wsgi.MAX_HEADER_LINE)
|
||||
|
||||
def test_start_random_port(self):
|
||||
server = service.WSGIServer(
|
||||
"test_random_port", None, host="127.0.0.1", port=0)
|
||||
server.start()
|
||||
self.assertNotEqual(0, server.port)
|
||||
server.stop()
|
||||
server.wait()
|
||||
|
||||
@testtools.skipIf(not utils.is_ipv6_supported(), "no ipv6 support")
|
||||
def test_start_random_port_with_ipv6(self):
|
||||
server = service.WSGIServer(
|
||||
"test_random_port", None,
|
||||
host="::1", port=0)
|
||||
server.start()
|
||||
self.assertEqual("::1", server.host)
|
||||
self.assertNotEqual(0, server.port)
|
||||
server.stop()
|
||||
server.wait()
|
||||
|
||||
@testtools.skipIf(not utils.is_linux(), 'SO_REUSEADDR behaves differently '
|
||||
'on OSX and BSD, see bugs '
|
||||
'1436895 and 1467145')
|
||||
def test_socket_options_for_simple_server(self):
|
||||
# test normal socket options has set properly
|
||||
self.flags(tcp_keepidle=500, group='wsgi')
|
||||
server = service.WSGIServer(
|
||||
"test_socket_options", None, host="127.0.0.1", port=0)
|
||||
server.start()
|
||||
sock = server._socket
|
||||
self.assertEqual(1, sock.getsockopt(socket.SOL_SOCKET,
|
||||
socket.SO_REUSEADDR))
|
||||
self.assertEqual(1, sock.getsockopt(socket.SOL_SOCKET,
|
||||
socket.SO_KEEPALIVE))
|
||||
if hasattr(socket, 'TCP_KEEPIDLE'):
|
||||
self.assertEqual(CONF.wsgi.tcp_keepidle,
|
||||
sock.getsockopt(socket.IPPROTO_TCP,
|
||||
socket.TCP_KEEPIDLE))
|
||||
server.stop()
|
||||
server.wait()
|
||||
|
||||
def test_server_pool_waitall(self):
|
||||
# test pools waitall method gets called while stopping server
|
||||
server = service.WSGIServer(
|
||||
"test_server", None, host="127.0.0.1")
|
||||
server.start()
|
||||
with mock.patch.object(server._pool,
|
||||
'waitall') as mock_waitall:
|
||||
server.stop()
|
||||
server.wait()
|
||||
mock_waitall.assert_called_once_with()
|
||||
|
||||
def test_uri_length_limit(self):
|
||||
server = service.WSGIServer(
|
||||
"test_uri_length_limit", None,
|
||||
host="127.0.0.1", max_url_len=16384)
|
||||
server.start()
|
||||
|
||||
uri = "http://127.0.0.1:%d/%s" % (server.port, 10000 * 'x')
|
||||
resp = requests.get(uri, proxies={"http": ""})
|
||||
eventlet.sleep(0)
|
||||
self.assertNotEqual(resp.status_code,
|
||||
requests.codes.REQUEST_URI_TOO_LARGE)
|
||||
|
||||
uri = "http://127.0.0.1:%d/%s" % (server.port, 20000 * 'x')
|
||||
resp = requests.get(uri, proxies={"http": ""})
|
||||
eventlet.sleep(0)
|
||||
self.assertEqual(resp.status_code,
|
||||
requests.codes.REQUEST_URI_TOO_LARGE)
|
||||
server.stop()
|
||||
server.wait()
|
||||
|
||||
def test_reset_pool_size_to_default(self):
|
||||
server = service.WSGIServer(
|
||||
"test_resize", None,
|
||||
host="127.0.0.1", max_url_len=16384)
|
||||
server.start()
|
||||
|
||||
# Stopping the server, which in turn sets pool size to 0
|
||||
server.stop()
|
||||
self.assertEqual(server._pool.size, 0)
|
||||
|
||||
# Resetting pool size to default
|
||||
server.reset()
|
||||
server.start()
|
||||
self.addCleanup(server.stop)
|
||||
self.assertEqual(server._pool.size, CONF.wsgi.default_pool_size)
|
||||
|
||||
def test_client_socket_timeout(self):
|
||||
self.flags(client_socket_timeout=5, group='wsgi')
|
||||
|
||||
# mocking eventlet spawn method to check it is called with
|
||||
# configured 'client_socket_timeout' value.
|
||||
with mock.patch('nova.utils.spawn') as mock_spawn:
|
||||
server = service.WSGIServer(
|
||||
"test_app", None,
|
||||
host="127.0.0.1", port=0)
|
||||
server.start()
|
||||
self.addCleanup(server.stop)
|
||||
_, kwargs = mock_spawn.call_args
|
||||
self.assertEqual(CONF.wsgi.client_socket_timeout,
|
||||
kwargs['socket_timeout'])
|
||||
|
||||
def test_keep_alive(self):
|
||||
self.flags(keep_alive=False, group='wsgi')
|
||||
|
||||
# mocking eventlet spawn method to check it is called with
|
||||
# configured 'keep_alive' value.
|
||||
with mock.patch('nova.utils.spawn') as mock_spawn:
|
||||
server = service.WSGIServer(
|
||||
"test_app", None,
|
||||
host="127.0.0.1", port=0)
|
||||
server.start()
|
||||
self.addCleanup(server.stop)
|
||||
_, kwargs = mock_spawn.call_args
|
||||
self.assertEqual(CONF.wsgi.keep_alive,
|
||||
kwargs['keepalive'])
|
||||
|
||||
|
||||
@testtools.skip("bug/1482633: test hangs on Python 3")
|
||||
class TestWSGIServerWithSSL(test.NoDBTestCase):
|
||||
"""WSGI server with SSL tests."""
|
||||
|
||||
def setUp(self):
|
||||
super(TestWSGIServerWithSSL, self).setUp()
|
||||
self.flags(enabled_ssl_apis=['fake_ssl'])
|
||||
self.flags(
|
||||
ssl_cert_file=os.path.join(SSL_CERT_DIR, 'certificate.crt'),
|
||||
ssl_key_file=os.path.join(SSL_CERT_DIR, 'privatekey.key'),
|
||||
group='wsgi')
|
||||
|
||||
def test_ssl_server(self):
|
||||
|
||||
def test_app(env, start_response):
|
||||
start_response('200 OK', {})
|
||||
return ['PONG']
|
||||
|
||||
fake_ssl_server = service.WSGIServer(
|
||||
"fake_ssl", test_app,
|
||||
host="127.0.0.1", port=0,
|
||||
use_ssl=True)
|
||||
fake_ssl_server.start()
|
||||
self.assertNotEqual(0, fake_ssl_server.port)
|
||||
|
||||
response = requests.post(
|
||||
'https://127.0.0.1:%s/' % fake_ssl_server.port,
|
||||
verify=os.path.join(SSL_CERT_DIR, 'ca.crt'), data='PING')
|
||||
self.assertEqual(response.text, 'PONG')
|
||||
|
||||
fake_ssl_server.stop()
|
||||
fake_ssl_server.wait()
|
||||
|
||||
def test_two_servers(self):
|
||||
|
||||
def test_app(env, start_response):
|
||||
start_response('200 OK', {})
|
||||
return ['PONG']
|
||||
|
||||
fake_ssl_server = service.WSGIServer(
|
||||
"fake_ssl", test_app,
|
||||
host="127.0.0.1", port=0, use_ssl=True)
|
||||
fake_ssl_server.start()
|
||||
self.assertNotEqual(0, fake_ssl_server.port)
|
||||
|
||||
fake_server = service.WSGIServer(
|
||||
"fake", test_app,
|
||||
host="127.0.0.1", port=0)
|
||||
fake_server.start()
|
||||
self.assertNotEqual(0, fake_server.port)
|
||||
|
||||
response = requests.post(
|
||||
'https://127.0.0.1:%s/' % fake_ssl_server.port,
|
||||
verify=os.path.join(SSL_CERT_DIR, 'ca.crt'), data='PING')
|
||||
self.assertEqual(response.text, 'PONG')
|
||||
|
||||
response = requests.post('http://127.0.0.1:%s/' % fake_server.port,
|
||||
data='PING')
|
||||
self.assertEqual(response.text, 'PONG')
|
||||
|
||||
fake_ssl_server.stop()
|
||||
fake_ssl_server.wait()
|
||||
fake_server.stop()
|
||||
fake_server.wait()
|
||||
|
||||
@testtools.skipIf(not utils.is_linux(), 'SO_REUSEADDR behaves differently '
|
||||
'on OSX and BSD, see bugs '
|
||||
'1436895 and 1467145')
|
||||
def test_socket_options_for_ssl_server(self):
|
||||
# test normal socket options has set properly
|
||||
self.flags(tcp_keepidle=500, group='wsgi')
|
||||
server = service.WSGIServer(
|
||||
"test_socket_options", None,
|
||||
host="127.0.0.1", port=0,
|
||||
use_ssl=True)
|
||||
server.start()
|
||||
sock = server._socket
|
||||
self.assertEqual(1, sock.getsockopt(socket.SOL_SOCKET,
|
||||
socket.SO_REUSEADDR))
|
||||
self.assertEqual(1, sock.getsockopt(socket.SOL_SOCKET,
|
||||
socket.SO_KEEPALIVE))
|
||||
if hasattr(socket, 'TCP_KEEPIDLE'):
|
||||
self.assertEqual(CONF.wsgi.tcp_keepidle,
|
||||
sock.getsockopt(socket.IPPROTO_TCP,
|
||||
socket.TCP_KEEPIDLE))
|
||||
server.stop()
|
||||
server.wait()
|
||||
|
||||
@testtools.skipIf(not utils.is_ipv6_supported(), "no ipv6 support")
|
||||
def test_app_using_ipv6_and_ssl(self):
|
||||
greetings = 'Hello, World!!!'
|
||||
|
||||
@webob.dec.wsgify
|
||||
def hello_world(req):
|
||||
return greetings
|
||||
|
||||
server = service.WSGIServer(
|
||||
"fake_ssl", hello_world,
|
||||
host="::1", port=0, use_ssl=True)
|
||||
|
||||
server.start()
|
||||
|
||||
response = requests.get('https://[::1]:%d/' % server.port,
|
||||
verify=os.path.join(SSL_CERT_DIR, 'ca.crt'))
|
||||
self.assertEqual(greetings, response.text)
|
||||
|
||||
server.stop()
|
||||
server.wait()
|
||||
|
||||
|
||||
class TestWSGIService(test.NoDBTestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
|
|
@ -1,332 +0,0 @@
|
|||
# Copyright 2011 United States Government as represented by the
|
||||
# Administrator of the National Aeronautics and Space Administration.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""Unit tests for `nova.wsgi`."""
|
||||
|
||||
import os.path
|
||||
import socket
|
||||
import tempfile
|
||||
from unittest import mock
|
||||
|
||||
import eventlet
|
||||
import eventlet.wsgi
|
||||
from oslo_config import cfg
|
||||
import requests
|
||||
import testtools
|
||||
import webob
|
||||
|
||||
import nova.api.wsgi
|
||||
import nova.exception
|
||||
from nova import test
|
||||
from nova.tests.unit import utils
|
||||
import nova.wsgi
|
||||
|
||||
SSL_CERT_DIR = os.path.normpath(os.path.join(
|
||||
os.path.dirname(os.path.abspath(__file__)),
|
||||
'ssl_cert'))
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
class TestLoaderNothingExists(test.NoDBTestCase):
|
||||
"""Loader tests where os.path.exists always returns False."""
|
||||
|
||||
def setUp(self):
|
||||
super(TestLoaderNothingExists, self).setUp()
|
||||
self.stub_out('os.path.exists', lambda _: False)
|
||||
|
||||
def test_relpath_config_not_found(self):
|
||||
self.flags(api_paste_config='api-paste.ini', group='wsgi')
|
||||
self.assertRaises(
|
||||
nova.exception.ConfigNotFound,
|
||||
nova.api.wsgi.Loader,
|
||||
)
|
||||
|
||||
def test_asbpath_config_not_found(self):
|
||||
self.flags(api_paste_config='/etc/nova/api-paste.ini', group='wsgi')
|
||||
self.assertRaises(
|
||||
nova.exception.ConfigNotFound,
|
||||
nova.api.wsgi.Loader,
|
||||
)
|
||||
|
||||
|
||||
class TestLoaderNormalFilesystem(test.NoDBTestCase):
|
||||
"""Loader tests with normal filesystem (unmodified os.path module)."""
|
||||
|
||||
_paste_config = """
|
||||
[app:test_app]
|
||||
use = egg:Paste#static
|
||||
document_root = /tmp
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super(TestLoaderNormalFilesystem, self).setUp()
|
||||
self.config = tempfile.NamedTemporaryFile(mode="w+t")
|
||||
self.config.write(self._paste_config.lstrip())
|
||||
self.config.seek(0)
|
||||
self.config.flush()
|
||||
self.loader = nova.api.wsgi.Loader(self.config.name)
|
||||
|
||||
def test_config_found(self):
|
||||
self.assertEqual(self.config.name, self.loader.config_path)
|
||||
|
||||
def test_app_not_found(self):
|
||||
self.assertRaises(
|
||||
nova.exception.PasteAppNotFound,
|
||||
self.loader.load_app,
|
||||
"nonexistent app",
|
||||
)
|
||||
|
||||
def test_app_found(self):
|
||||
url_parser = self.loader.load_app("test_app")
|
||||
self.assertEqual("/tmp", url_parser.directory)
|
||||
|
||||
def tearDown(self):
|
||||
self.config.close()
|
||||
super(TestLoaderNormalFilesystem, self).tearDown()
|
||||
|
||||
|
||||
class TestWSGIServer(test.NoDBTestCase):
|
||||
"""WSGI server tests."""
|
||||
|
||||
def test_no_app(self):
|
||||
server = nova.wsgi.Server("test_app", None)
|
||||
self.assertEqual("test_app", server.name)
|
||||
|
||||
def test_custom_max_header_line(self):
|
||||
self.flags(max_header_line=4096, group='wsgi') # Default is 16384
|
||||
nova.wsgi.Server("test_custom_max_header_line", None)
|
||||
self.assertEqual(CONF.wsgi.max_header_line,
|
||||
eventlet.wsgi.MAX_HEADER_LINE)
|
||||
|
||||
def test_start_random_port(self):
|
||||
server = nova.wsgi.Server("test_random_port", None,
|
||||
host="127.0.0.1", port=0)
|
||||
server.start()
|
||||
self.assertNotEqual(0, server.port)
|
||||
server.stop()
|
||||
server.wait()
|
||||
|
||||
@testtools.skipIf(not utils.is_ipv6_supported(), "no ipv6 support")
|
||||
def test_start_random_port_with_ipv6(self):
|
||||
server = nova.wsgi.Server("test_random_port", None,
|
||||
host="::1", port=0)
|
||||
server.start()
|
||||
self.assertEqual("::1", server.host)
|
||||
self.assertNotEqual(0, server.port)
|
||||
server.stop()
|
||||
server.wait()
|
||||
|
||||
@testtools.skipIf(not utils.is_linux(), 'SO_REUSEADDR behaves differently '
|
||||
'on OSX and BSD, see bugs '
|
||||
'1436895 and 1467145')
|
||||
def test_socket_options_for_simple_server(self):
|
||||
# test normal socket options has set properly
|
||||
self.flags(tcp_keepidle=500, group='wsgi')
|
||||
server = nova.wsgi.Server("test_socket_options", None,
|
||||
host="127.0.0.1", port=0)
|
||||
server.start()
|
||||
sock = server._socket
|
||||
self.assertEqual(1, sock.getsockopt(socket.SOL_SOCKET,
|
||||
socket.SO_REUSEADDR))
|
||||
self.assertEqual(1, sock.getsockopt(socket.SOL_SOCKET,
|
||||
socket.SO_KEEPALIVE))
|
||||
if hasattr(socket, 'TCP_KEEPIDLE'):
|
||||
self.assertEqual(CONF.wsgi.tcp_keepidle,
|
||||
sock.getsockopt(socket.IPPROTO_TCP,
|
||||
socket.TCP_KEEPIDLE))
|
||||
server.stop()
|
||||
server.wait()
|
||||
|
||||
def test_server_pool_waitall(self):
|
||||
# test pools waitall method gets called while stopping server
|
||||
server = nova.wsgi.Server("test_server", None,
|
||||
host="127.0.0.1")
|
||||
server.start()
|
||||
with mock.patch.object(server._pool,
|
||||
'waitall') as mock_waitall:
|
||||
server.stop()
|
||||
server.wait()
|
||||
mock_waitall.assert_called_once_with()
|
||||
|
||||
def test_uri_length_limit(self):
|
||||
server = nova.wsgi.Server("test_uri_length_limit", None,
|
||||
host="127.0.0.1", max_url_len=16384)
|
||||
server.start()
|
||||
|
||||
uri = "http://127.0.0.1:%d/%s" % (server.port, 10000 * 'x')
|
||||
resp = requests.get(uri, proxies={"http": ""})
|
||||
eventlet.sleep(0)
|
||||
self.assertNotEqual(resp.status_code,
|
||||
requests.codes.REQUEST_URI_TOO_LARGE)
|
||||
|
||||
uri = "http://127.0.0.1:%d/%s" % (server.port, 20000 * 'x')
|
||||
resp = requests.get(uri, proxies={"http": ""})
|
||||
eventlet.sleep(0)
|
||||
self.assertEqual(resp.status_code,
|
||||
requests.codes.REQUEST_URI_TOO_LARGE)
|
||||
server.stop()
|
||||
server.wait()
|
||||
|
||||
def test_reset_pool_size_to_default(self):
|
||||
server = nova.wsgi.Server("test_resize", None,
|
||||
host="127.0.0.1", max_url_len=16384)
|
||||
server.start()
|
||||
|
||||
# Stopping the server, which in turn sets pool size to 0
|
||||
server.stop()
|
||||
self.assertEqual(server._pool.size, 0)
|
||||
|
||||
# Resetting pool size to default
|
||||
server.reset()
|
||||
server.start()
|
||||
self.addCleanup(server.stop)
|
||||
self.assertEqual(server._pool.size, CONF.wsgi.default_pool_size)
|
||||
|
||||
def test_client_socket_timeout(self):
|
||||
self.flags(client_socket_timeout=5, group='wsgi')
|
||||
|
||||
# mocking eventlet spawn method to check it is called with
|
||||
# configured 'client_socket_timeout' value.
|
||||
with mock.patch('nova.utils.spawn') as mock_spawn:
|
||||
server = nova.wsgi.Server("test_app", None,
|
||||
host="127.0.0.1", port=0)
|
||||
server.start()
|
||||
self.addCleanup(server.stop)
|
||||
_, kwargs = mock_spawn.call_args
|
||||
self.assertEqual(CONF.wsgi.client_socket_timeout,
|
||||
kwargs['socket_timeout'])
|
||||
|
||||
def test_keep_alive(self):
|
||||
self.flags(keep_alive=False, group='wsgi')
|
||||
|
||||
# mocking eventlet spawn method to check it is called with
|
||||
# configured 'keep_alive' value.
|
||||
with mock.patch('nova.utils.spawn') as mock_spawn:
|
||||
server = nova.wsgi.Server("test_app", None,
|
||||
host="127.0.0.1", port=0)
|
||||
server.start()
|
||||
self.addCleanup(server.stop)
|
||||
_, kwargs = mock_spawn.call_args
|
||||
self.assertEqual(CONF.wsgi.keep_alive,
|
||||
kwargs['keepalive'])
|
||||
|
||||
|
||||
@testtools.skip("bug/1482633: test hangs on Python 3")
|
||||
class TestWSGIServerWithSSL(test.NoDBTestCase):
|
||||
"""WSGI server with SSL tests."""
|
||||
|
||||
def setUp(self):
|
||||
super(TestWSGIServerWithSSL, self).setUp()
|
||||
self.flags(enabled_ssl_apis=['fake_ssl'])
|
||||
self.flags(
|
||||
ssl_cert_file=os.path.join(SSL_CERT_DIR, 'certificate.crt'),
|
||||
ssl_key_file=os.path.join(SSL_CERT_DIR, 'privatekey.key'),
|
||||
group='wsgi')
|
||||
|
||||
def test_ssl_server(self):
|
||||
|
||||
def test_app(env, start_response):
|
||||
start_response('200 OK', {})
|
||||
return ['PONG']
|
||||
|
||||
fake_ssl_server = nova.wsgi.Server("fake_ssl", test_app,
|
||||
host="127.0.0.1", port=0,
|
||||
use_ssl=True)
|
||||
fake_ssl_server.start()
|
||||
self.assertNotEqual(0, fake_ssl_server.port)
|
||||
|
||||
response = requests.post(
|
||||
'https://127.0.0.1:%s/' % fake_ssl_server.port,
|
||||
verify=os.path.join(SSL_CERT_DIR, 'ca.crt'), data='PING')
|
||||
self.assertEqual(response.text, 'PONG')
|
||||
|
||||
fake_ssl_server.stop()
|
||||
fake_ssl_server.wait()
|
||||
|
||||
def test_two_servers(self):
|
||||
|
||||
def test_app(env, start_response):
|
||||
start_response('200 OK', {})
|
||||
return ['PONG']
|
||||
|
||||
fake_ssl_server = nova.wsgi.Server("fake_ssl", test_app,
|
||||
host="127.0.0.1", port=0, use_ssl=True)
|
||||
fake_ssl_server.start()
|
||||
self.assertNotEqual(0, fake_ssl_server.port)
|
||||
|
||||
fake_server = nova.wsgi.Server("fake", test_app,
|
||||
host="127.0.0.1", port=0)
|
||||
fake_server.start()
|
||||
self.assertNotEqual(0, fake_server.port)
|
||||
|
||||
response = requests.post(
|
||||
'https://127.0.0.1:%s/' % fake_ssl_server.port,
|
||||
verify=os.path.join(SSL_CERT_DIR, 'ca.crt'), data='PING')
|
||||
self.assertEqual(response.text, 'PONG')
|
||||
|
||||
response = requests.post('http://127.0.0.1:%s/' % fake_server.port,
|
||||
data='PING')
|
||||
self.assertEqual(response.text, 'PONG')
|
||||
|
||||
fake_ssl_server.stop()
|
||||
fake_ssl_server.wait()
|
||||
fake_server.stop()
|
||||
fake_server.wait()
|
||||
|
||||
@testtools.skipIf(not utils.is_linux(), 'SO_REUSEADDR behaves differently '
|
||||
'on OSX and BSD, see bugs '
|
||||
'1436895 and 1467145')
|
||||
def test_socket_options_for_ssl_server(self):
|
||||
# test normal socket options has set properly
|
||||
self.flags(tcp_keepidle=500, group='wsgi')
|
||||
server = nova.wsgi.Server("test_socket_options", None,
|
||||
host="127.0.0.1", port=0,
|
||||
use_ssl=True)
|
||||
server.start()
|
||||
sock = server._socket
|
||||
self.assertEqual(1, sock.getsockopt(socket.SOL_SOCKET,
|
||||
socket.SO_REUSEADDR))
|
||||
self.assertEqual(1, sock.getsockopt(socket.SOL_SOCKET,
|
||||
socket.SO_KEEPALIVE))
|
||||
if hasattr(socket, 'TCP_KEEPIDLE'):
|
||||
self.assertEqual(CONF.wsgi.tcp_keepidle,
|
||||
sock.getsockopt(socket.IPPROTO_TCP,
|
||||
socket.TCP_KEEPIDLE))
|
||||
server.stop()
|
||||
server.wait()
|
||||
|
||||
@testtools.skipIf(not utils.is_ipv6_supported(), "no ipv6 support")
|
||||
def test_app_using_ipv6_and_ssl(self):
|
||||
greetings = 'Hello, World!!!'
|
||||
|
||||
@webob.dec.wsgify
|
||||
def hello_world(req):
|
||||
return greetings
|
||||
|
||||
server = nova.wsgi.Server("fake_ssl",
|
||||
hello_world,
|
||||
host="::1",
|
||||
port=0,
|
||||
use_ssl=True)
|
||||
|
||||
server.start()
|
||||
|
||||
response = requests.get('https://[::1]:%d/' % server.port,
|
||||
verify=os.path.join(SSL_CERT_DIR, 'ca.crt'))
|
||||
self.assertEqual(greetings, response.text)
|
||||
|
||||
server.stop()
|
||||
server.wait()
|
225
nova/wsgi.py
225
nova/wsgi.py
|
@ -1,225 +0,0 @@
|
|||
# Copyright 2010 United States Government as represented by the
|
||||
# Administrator of the National Aeronautics and Space Administration.
|
||||
# Copyright 2010 OpenStack Foundation
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""Utility methods for working with WSGI servers."""
|
||||
|
||||
import os.path
|
||||
import socket
|
||||
import ssl
|
||||
|
||||
import eventlet
|
||||
import eventlet.wsgi
|
||||
import greenlet
|
||||
from oslo_log import log as logging
|
||||
from oslo_service import service
|
||||
from oslo_utils import excutils
|
||||
|
||||
import nova.conf
|
||||
from nova import exception
|
||||
from nova.i18n import _
|
||||
from nova import utils
|
||||
|
||||
CONF = nova.conf.CONF
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Server(service.ServiceBase):
|
||||
"""Server class to manage a WSGI server, serving a WSGI application."""
|
||||
|
||||
default_pool_size = CONF.wsgi.default_pool_size
|
||||
|
||||
def __init__(self, name, app, host='0.0.0.0', port=0, pool_size=None,
|
||||
protocol=eventlet.wsgi.HttpProtocol, backlog=128,
|
||||
use_ssl=False, max_url_len=None):
|
||||
"""Initialize, but do not start, a WSGI server.
|
||||
|
||||
:param name: Pretty name for logging.
|
||||
:param app: The WSGI application to serve.
|
||||
:param host: IP address to serve the application.
|
||||
:param port: Port number to server the application.
|
||||
:param pool_size: Maximum number of eventlets to spawn concurrently.
|
||||
:param backlog: Maximum number of queued connections.
|
||||
:param max_url_len: Maximum length of permitted URLs.
|
||||
:returns: None
|
||||
:raises: nova.exception.InvalidInput
|
||||
"""
|
||||
# Allow operators to customize http requests max header line size.
|
||||
eventlet.wsgi.MAX_HEADER_LINE = CONF.wsgi.max_header_line
|
||||
self.name = name
|
||||
self.app = app
|
||||
self._server = None
|
||||
self._protocol = protocol
|
||||
self.pool_size = pool_size or self.default_pool_size
|
||||
self._pool = eventlet.GreenPool(self.pool_size)
|
||||
self._logger = logging.getLogger("nova.%s.wsgi.server" % self.name)
|
||||
self._use_ssl = use_ssl
|
||||
self._max_url_len = max_url_len
|
||||
self.client_socket_timeout = CONF.wsgi.client_socket_timeout or None
|
||||
|
||||
if backlog < 1:
|
||||
raise exception.InvalidInput(
|
||||
reason=_('The backlog must be more than 0'))
|
||||
|
||||
bind_addr = (host, port)
|
||||
# TODO(dims): eventlet's green dns/socket module does not actually
|
||||
# support IPv6 in getaddrinfo(). We need to get around this in the
|
||||
# future or monitor upstream for a fix
|
||||
try:
|
||||
info = socket.getaddrinfo(bind_addr[0],
|
||||
bind_addr[1],
|
||||
socket.AF_UNSPEC,
|
||||
socket.SOCK_STREAM)[0]
|
||||
family = info[0]
|
||||
bind_addr = info[-1]
|
||||
except Exception:
|
||||
family = socket.AF_INET
|
||||
|
||||
try:
|
||||
self._socket = eventlet.listen(bind_addr, family, backlog=backlog)
|
||||
except EnvironmentError:
|
||||
LOG.error("Could not bind to %(host)s:%(port)s",
|
||||
{'host': host, 'port': port})
|
||||
raise
|
||||
|
||||
(self.host, self.port) = self._socket.getsockname()[0:2]
|
||||
LOG.info("%(name)s listening on %(host)s:%(port)s",
|
||||
{'name': self.name, 'host': self.host, 'port': self.port})
|
||||
|
||||
def start(self):
|
||||
"""Start serving a WSGI application.
|
||||
|
||||
:returns: None
|
||||
"""
|
||||
# The server socket object will be closed after server exits,
|
||||
# but the underlying file descriptor will remain open, and will
|
||||
# give bad file descriptor error. So duplicating the socket object,
|
||||
# to keep file descriptor usable.
|
||||
|
||||
dup_socket = self._socket.dup()
|
||||
dup_socket.setsockopt(socket.SOL_SOCKET,
|
||||
socket.SO_REUSEADDR, 1)
|
||||
# sockets can hang around forever without keepalive
|
||||
dup_socket.setsockopt(socket.SOL_SOCKET,
|
||||
socket.SO_KEEPALIVE, 1)
|
||||
|
||||
# This option isn't available in the OS X version of eventlet
|
||||
if hasattr(socket, 'TCP_KEEPIDLE'):
|
||||
dup_socket.setsockopt(socket.IPPROTO_TCP,
|
||||
socket.TCP_KEEPIDLE,
|
||||
CONF.wsgi.tcp_keepidle)
|
||||
|
||||
if self._use_ssl:
|
||||
try:
|
||||
ca_file = CONF.wsgi.ssl_ca_file
|
||||
cert_file = CONF.wsgi.ssl_cert_file
|
||||
key_file = CONF.wsgi.ssl_key_file
|
||||
|
||||
if cert_file and not os.path.exists(cert_file):
|
||||
raise RuntimeError(
|
||||
_("Unable to find cert_file : %s") % cert_file)
|
||||
|
||||
if ca_file and not os.path.exists(ca_file):
|
||||
raise RuntimeError(
|
||||
_("Unable to find ca_file : %s") % ca_file)
|
||||
|
||||
if key_file and not os.path.exists(key_file):
|
||||
raise RuntimeError(
|
||||
_("Unable to find key_file : %s") % key_file)
|
||||
|
||||
if self._use_ssl and (not cert_file or not key_file):
|
||||
raise RuntimeError(
|
||||
_("When running server in SSL mode, you must "
|
||||
"specify both a cert_file and key_file "
|
||||
"option value in your configuration file"))
|
||||
ssl_kwargs = {
|
||||
'server_side': True,
|
||||
'certfile': cert_file,
|
||||
'keyfile': key_file,
|
||||
'cert_reqs': ssl.CERT_NONE,
|
||||
}
|
||||
|
||||
if CONF.wsgi.ssl_ca_file:
|
||||
ssl_kwargs['ca_certs'] = ca_file
|
||||
ssl_kwargs['cert_reqs'] = ssl.CERT_REQUIRED
|
||||
|
||||
dup_socket = eventlet.wrap_ssl(dup_socket,
|
||||
**ssl_kwargs)
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception():
|
||||
LOG.error(
|
||||
"Failed to start %(name)s on %(host)s:%(port)s with "
|
||||
"SSL support",
|
||||
{'name': self.name, 'host': self.host,
|
||||
'port': self.port})
|
||||
|
||||
wsgi_kwargs = {
|
||||
'func': eventlet.wsgi.server,
|
||||
'sock': dup_socket,
|
||||
'site': self.app,
|
||||
'protocol': self._protocol,
|
||||
'custom_pool': self._pool,
|
||||
'log': self._logger,
|
||||
'log_format': CONF.wsgi.wsgi_log_format,
|
||||
'debug': False,
|
||||
'keepalive': CONF.wsgi.keep_alive,
|
||||
'socket_timeout': self.client_socket_timeout
|
||||
}
|
||||
|
||||
if self._max_url_len:
|
||||
wsgi_kwargs['url_length_limit'] = self._max_url_len
|
||||
|
||||
self._server = utils.spawn(**wsgi_kwargs)
|
||||
|
||||
def reset(self):
|
||||
"""Reset server greenpool size to default.
|
||||
|
||||
:returns: None
|
||||
|
||||
"""
|
||||
self._pool.resize(self.pool_size)
|
||||
|
||||
def stop(self):
|
||||
"""Stop this server.
|
||||
|
||||
This is not a very nice action, as currently the method by which a
|
||||
server is stopped is by killing its eventlet.
|
||||
|
||||
:returns: None
|
||||
|
||||
"""
|
||||
LOG.info("Stopping WSGI server.")
|
||||
|
||||
if self._server is not None:
|
||||
# Resize pool to stop new requests from being processed
|
||||
self._pool.resize(0)
|
||||
self._server.kill()
|
||||
|
||||
def wait(self):
|
||||
"""Block, until the server has stopped.
|
||||
|
||||
Waits on the server's eventlet to finish, then returns.
|
||||
|
||||
:returns: None
|
||||
|
||||
"""
|
||||
try:
|
||||
if self._server is not None:
|
||||
self._pool.waitall()
|
||||
self._server.wait()
|
||||
except greenlet.GreenletExit:
|
||||
LOG.info("WSGI server has stopped.")
|
Loading…
Reference in New Issue