Add functionality for creating Unix domain WSGI servers
This change modifies WSGI Server to allow creating Unix sockets along with ip sockets. This would allow consuming projects to use wsgi.Server to start Unix domain WSGI servers too, and thus get rid of duplicating code. Change-Id: I3de23efcd661b95cce6bbd1aa2c94abf0c814dbe
This commit is contained in:
parent
52edcd4bfb
commit
9d8acfb38e
|
@ -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…
Reference in New Issue