Merge "Add functionality for creating Unix domain WSGI servers"
This commit is contained in:
commit
5c927520e0
@ -12,6 +12,7 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import fixtures
|
||||
from oslo_config import fixture as config
|
||||
from oslotest import base as test_base
|
||||
|
||||
@ -29,6 +30,44 @@ class ServiceBaseTestCase(test_base.BaseTestCase):
|
||||
self.conf_fixture.register_opts(_options.ssl_opts,
|
||||
sslutils.config_section)
|
||||
self.conf_fixture.register_opts(_options.periodic_opts)
|
||||
self.conf_fixture.register_opts(_options.wsgi_opts)
|
||||
|
||||
self.conf = self.conf_fixture.conf
|
||||
self.config = self.conf_fixture.config
|
||||
self.conf(args=[], default_config_files=[])
|
||||
|
||||
def get_new_temp_dir(self):
|
||||
"""Create a new temporary directory.
|
||||
|
||||
:returns fixtures.TempDir
|
||||
"""
|
||||
return self.useFixture(fixtures.TempDir())
|
||||
|
||||
def get_default_temp_dir(self):
|
||||
"""Create a default temporary directory.
|
||||
|
||||
Returns the same directory during the whole test case.
|
||||
|
||||
:returns fixtures.TempDir
|
||||
"""
|
||||
if not hasattr(self, '_temp_dir'):
|
||||
self._temp_dir = self.get_new_temp_dir()
|
||||
return self._temp_dir
|
||||
|
||||
def get_temp_file_path(self, filename, root=None):
|
||||
"""Returns an absolute path for a temporary file.
|
||||
|
||||
If root is None, the file is created in default temporary directory. It
|
||||
also creates the directory if it's not initialized yet.
|
||||
|
||||
If root is not None, the file is created inside the directory passed as
|
||||
root= argument.
|
||||
|
||||
:param filename: filename
|
||||
:type filename: string
|
||||
:param root: temporary directory to create a new file in
|
||||
:type root: fixtures.TempDir
|
||||
:returns absolute file path string
|
||||
"""
|
||||
root = root or self.get_default_temp_dir()
|
||||
return root.join(filename)
|
||||
|
@ -16,7 +16,7 @@
|
||||
|
||||
"""Unit tests for `wsgi`."""
|
||||
|
||||
import os.path
|
||||
import os
|
||||
import platform
|
||||
import socket
|
||||
import tempfile
|
||||
@ -29,12 +29,11 @@ import requests
|
||||
import webob
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_config import fixture as config
|
||||
from oslo_service import _options
|
||||
from oslo_service import sslutils
|
||||
from oslo_service.tests import base
|
||||
from oslo_service import wsgi
|
||||
from oslo_utils import netutils
|
||||
from oslotest import base as test_base
|
||||
from oslotest import moxstubout
|
||||
|
||||
|
||||
@ -44,15 +43,12 @@ SSL_CERT_DIR = os.path.normpath(os.path.join(
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
class WsgiTestCase(test_base.BaseTestCase):
|
||||
class WsgiTestCase(base.ServiceBaseTestCase):
|
||||
"""Base class for WSGI tests."""
|
||||
|
||||
def setUp(self):
|
||||
super(WsgiTestCase, self).setUp()
|
||||
self.conf_fixture = self.useFixture(config.Config())
|
||||
self.conf_fixture.register_opts(_options.wsgi_opts)
|
||||
self.conf = self.conf_fixture.conf
|
||||
self.config = self.conf_fixture.config
|
||||
self.conf(args=[], default_config_files=[])
|
||||
|
||||
|
||||
@ -163,7 +159,7 @@ class TestWSGIServer(WsgiTestCase):
|
||||
server = wsgi.Server(self.conf, "test_socket_options", None,
|
||||
host="127.0.0.1", port=0)
|
||||
server.start()
|
||||
sock = server._socket
|
||||
sock = server.socket
|
||||
self.assertEqual(1, sock.getsockopt(socket.SOL_SOCKET,
|
||||
socket.SO_REUSEADDR))
|
||||
self.assertEqual(1, sock.getsockopt(socket.SOL_SOCKET,
|
||||
@ -177,6 +173,24 @@ class TestWSGIServer(WsgiTestCase):
|
||||
server.wait()
|
||||
self.assertTrue(server._server.dead)
|
||||
|
||||
@testtools.skipIf(not hasattr(socket, "AF_UNIX"),
|
||||
'UNIX sockets not supported')
|
||||
def test_server_with_unix_socket(self):
|
||||
socket_file = self.get_temp_file_path('sock')
|
||||
socket_mode = 0o644
|
||||
server = wsgi.Server(self.conf, "test_socket_options", None,
|
||||
socket_family=socket.AF_UNIX,
|
||||
socket_mode=socket_mode,
|
||||
socket_file=socket_file)
|
||||
self.assertEqual(socket_file, server.socket.getsockname())
|
||||
self.assertEqual(socket_mode,
|
||||
os.stat(socket_file).st_mode & 0o777)
|
||||
server.start()
|
||||
self.assertFalse(server._server.dead)
|
||||
server.stop()
|
||||
server.wait()
|
||||
self.assertTrue(server._server.dead)
|
||||
|
||||
def test_server_pool_waitall(self):
|
||||
# test pools waitall method gets called while stopping server
|
||||
server = wsgi.Server(self.conf, "test_server", None, host="127.0.0.1")
|
||||
@ -333,7 +347,7 @@ class TestWSGIServerWithSSL(WsgiTestCase):
|
||||
server = wsgi.Server(self.conf, "test_socket_options", None,
|
||||
host="127.0.0.1", port=0, use_ssl=True)
|
||||
server.start()
|
||||
sock = server._socket
|
||||
sock = server.socket
|
||||
self.assertEqual(1, sock.getsockopt(socket.SOL_SOCKET,
|
||||
socket.SO_REUSEADDR))
|
||||
self.assertEqual(1, sock.getsockopt(socket.SOL_SOCKET,
|
||||
|
@ -59,10 +59,11 @@ class InvalidInput(Exception):
|
||||
class Server(service.ServiceBase):
|
||||
"""Server class to manage a WSGI server, serving a WSGI application."""
|
||||
|
||||
def __init__(self, conf, 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,
|
||||
logger_name='eventlet.wsgi.server'):
|
||||
def __init__(self, conf, 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,
|
||||
logger_name='eventlet.wsgi.server',
|
||||
socket_family=None, socket_file=None, socket_mode=None):
|
||||
"""Initialize, but do not start, a WSGI server.
|
||||
|
||||
:param conf: Instance of ConfigOpts.
|
||||
@ -76,6 +77,9 @@ class Server(service.ServiceBase):
|
||||
:param use_ssl: Wraps the socket in an SSL context if True.
|
||||
:param max_url_len: Maximum length of permitted URLs.
|
||||
:param logger_name: The name for the logger.
|
||||
:param socket_family: Socket family.
|
||||
:param socket_file: location of UNIX socket.
|
||||
:param socket_mode: UNIX socket mode.
|
||||
:returns: None
|
||||
:raises: InvalidInput
|
||||
:raises: EnvironmentError
|
||||
@ -102,6 +106,21 @@ class Server(service.ServiceBase):
|
||||
if backlog < 1:
|
||||
raise InvalidInput(reason=_('The backlog must be more than 0'))
|
||||
|
||||
if not socket_family or socket_family in [socket.AF_INET,
|
||||
socket.AF_INET6]:
|
||||
self.socket = self._get_socket(host, port, backlog)
|
||||
elif hasattr(socket, "AF_UNIX") and socket_family == socket.AF_UNIX:
|
||||
self.socket = self._get_unix_socket(socket_file, socket_mode,
|
||||
backlog)
|
||||
else:
|
||||
raise ValueError(_("Unsupported socket family: %s"), socket_family)
|
||||
|
||||
(self.host, self.port) = self.socket.getsockname()[0:2]
|
||||
|
||||
if self._use_ssl:
|
||||
sslutils.is_enabled(conf)
|
||||
|
||||
def _get_socket(self, host, port, backlog):
|
||||
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
|
||||
@ -116,19 +135,25 @@ class Server(service.ServiceBase):
|
||||
except Exception:
|
||||
family = socket.AF_INET
|
||||
|
||||
if self._use_ssl:
|
||||
sslutils.is_enabled(conf)
|
||||
|
||||
try:
|
||||
self._socket = eventlet.listen(bind_addr, family, backlog=backlog)
|
||||
sock = eventlet.listen(bind_addr, family, backlog=backlog)
|
||||
except EnvironmentError:
|
||||
LOG.error(_LE("Could not bind to %(host)s:%(port)s"),
|
||||
{'host': host, 'port': port})
|
||||
raise
|
||||
|
||||
(self.host, self.port) = self._socket.getsockname()[0:2]
|
||||
sock = self._set_socket_opts(sock)
|
||||
LOG.info(_LI("%(name)s listening on %(host)s:%(port)s"),
|
||||
{'name': self.name, 'host': self.host, 'port': self.port})
|
||||
{'name': self.name, 'host': host, 'port': port})
|
||||
return sock
|
||||
|
||||
def _get_unix_socket(self, socket_file, socket_mode, backlog):
|
||||
sock = eventlet.listen(socket_file, family=socket.AF_UNIX,
|
||||
backlog=backlog)
|
||||
if socket_mode is not None:
|
||||
os.chmod(socket_file, socket_mode)
|
||||
LOG.info(_LI("%(name)s listening on %(socket_file)s:"),
|
||||
{'name': self.name, 'socket_file': socket_file})
|
||||
return sock
|
||||
|
||||
def start(self):
|
||||
"""Start serving a WSGI application.
|
||||
@ -140,9 +165,7 @@ class Server(service.ServiceBase):
|
||||
# give bad file descriptor error. So duplicating the socket object,
|
||||
# to keep file descriptor usable.
|
||||
|
||||
self.dup_socket = self._socket.dup()
|
||||
|
||||
self.dup_socket = self._set_socket_opts(self.dup_socket)
|
||||
self.dup_socket = self.socket.dup()
|
||||
|
||||
if self._use_ssl:
|
||||
self.dup_socket = sslutils.wrap(self.conf, self.dup_socket)
|
||||
|
@ -2,6 +2,7 @@
|
||||
# of appearance. Changing the order has an impact on the overall integration
|
||||
# process, which may cause wedges in the gate later.
|
||||
|
||||
fixtures>=1.3.1
|
||||
hacking<0.11,>=0.10.0
|
||||
mock>=1.2
|
||||
oslotest>=1.10.0 # Apache-2.0
|
||||
|
Loading…
x
Reference in New Issue
Block a user