Remove WSGIService and WSGIServer classes
The previous patch[1] removed the Eventlet based WSGI entry points, and that code was the only real user of the in tree WSGIService and WSGIServer classes, we can remove those too. This removes a good chunk of eventlet dependency from our tree. There is a catch though. The functional test env used these to start the nova-metadata-api service. We re-implemented the fixture to use load the wsgi app and use the wsgi intercept instead. This also showed that while the Eventlet based API service could be reset via the oslo.service interface the wsgi APP based API service cannot. So the related cell caches reset testing is removed. [1] Ie758550c0b8fb02aeb398396961467d9f845fcc9 Change-Id: I79b725f3b3569e9c1460a93ac40ca92269e7d003
This commit is contained in:
344
nova/service.py
344
nova/service.py
@@ -20,21 +20,13 @@
|
||||
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
|
||||
from nova import baserpc
|
||||
from nova import conductor
|
||||
import nova.conf
|
||||
@@ -327,342 +319,6 @@ 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."""
|
||||
|
||||
def __init__(self, name, loader=None, use_ssl=False, max_url_len=None):
|
||||
"""Initialize, but do not start the WSGI server.
|
||||
|
||||
:param name: The name of the WSGI server given to the loader.
|
||||
:param loader: Loads the WSGI application using the given name.
|
||||
:returns: None
|
||||
|
||||
"""
|
||||
self.name = name
|
||||
# NOTE(danms): Name can be metadata, osapi_compute, per
|
||||
# nova.service's enabled_apis
|
||||
self.binary = 'nova-%s' % name
|
||||
|
||||
LOG.warning('Running %s using eventlet is deprecated. Deploy with '
|
||||
'a WSGI server such as uwsgi or mod_wsgi.', self.binary)
|
||||
|
||||
self.topic = None
|
||||
self.manager = self._get_manager()
|
||||
self.loader = loader or api_wsgi.Loader()
|
||||
self.app = self.loader.load_app(name)
|
||||
# inherit all compute_api worker counts from osapi_compute
|
||||
if name.startswith('openstack_compute_api'):
|
||||
wname = 'osapi_compute'
|
||||
else:
|
||||
wname = name
|
||||
self.host = getattr(CONF, '%s_listen' % name, "0.0.0.0")
|
||||
self.port = getattr(CONF, '%s_listen_port' % name, 0)
|
||||
self.workers = (getattr(CONF, '%s_workers' % wname, None) or
|
||||
processutils.get_worker_count())
|
||||
if self.workers and self.workers < 1:
|
||||
worker_name = '%s_workers' % name
|
||||
msg = (_("%(worker_name)s value of %(workers)s is invalid, "
|
||||
"must be greater than 0") %
|
||||
{'worker_name': worker_name,
|
||||
'workers': str(self.workers)})
|
||||
raise exception.InvalidInput(msg)
|
||||
self.use_ssl = use_ssl
|
||||
self.server = WSGIServer(
|
||||
name,
|
||||
self.app,
|
||||
host=self.host,
|
||||
port=self.port,
|
||||
use_ssl=self.use_ssl,
|
||||
max_url_len=max_url_len,
|
||||
)
|
||||
# Pull back actual port used
|
||||
self.port = self.server.port
|
||||
self.backdoor_port = None
|
||||
setup_profiler(name, self.host)
|
||||
|
||||
def reset(self):
|
||||
"""Reset the following:
|
||||
|
||||
* server greenpool size to default
|
||||
* service version cache
|
||||
* cell cache holding database transaction context managers
|
||||
|
||||
:returns: None
|
||||
|
||||
"""
|
||||
self.server.reset()
|
||||
service_obj.Service.clear_min_version_cache()
|
||||
context.CELL_CACHE = {}
|
||||
|
||||
def _get_manager(self):
|
||||
"""Initialize a Manager object appropriate for this service.
|
||||
|
||||
Use the service name to look up a Manager subclass from the
|
||||
configuration and initialize an instance. If no class name
|
||||
is configured, just return None.
|
||||
|
||||
:returns: a Manager instance, or None.
|
||||
|
||||
"""
|
||||
manager = SERVICE_MANAGERS.get(self.binary)
|
||||
if manager is None:
|
||||
return None
|
||||
|
||||
manager_class = importutils.import_class(manager)
|
||||
return manager_class()
|
||||
|
||||
def start(self):
|
||||
"""Start serving this service using loaded configuration.
|
||||
|
||||
Also, retrieve updated port number in case '0' was passed in, which
|
||||
indicates a random port should be used.
|
||||
|
||||
:returns: None
|
||||
|
||||
"""
|
||||
# NOTE(melwitt): Clear the cell cache holding database transaction
|
||||
# context manager objects. We do this to ensure we create new internal
|
||||
# oslo.db locks to avoid a situation where a child process receives an
|
||||
# already locked oslo.db lock when it is forked. When a child process
|
||||
# inherits a locked oslo.db lock, database accesses through that
|
||||
# transaction context manager will never be able to acquire the lock
|
||||
# and requests will fail with CellTimeout errors.
|
||||
# See https://bugs.python.org/issue6721 for more information.
|
||||
# With python 3.7, it would be possible for oslo.db to make use of the
|
||||
# os.register_at_fork() method to reinitialize its lock. Until we
|
||||
# require python 3.7 as a minimum version, we must handle the situation
|
||||
# outside of oslo.db.
|
||||
context.CELL_CACHE = {}
|
||||
|
||||
ctxt = context.get_admin_context()
|
||||
service_ref = objects.Service.get_by_host_and_binary(ctxt, self.host,
|
||||
self.binary)
|
||||
if service_ref:
|
||||
_update_service_ref(service_ref)
|
||||
else:
|
||||
try:
|
||||
service_ref = _create_service_ref(self, ctxt)
|
||||
except (exception.ServiceTopicExists,
|
||||
exception.ServiceBinaryExists):
|
||||
# NOTE(danms): If we race to create a record with a sibling,
|
||||
# don't fail here.
|
||||
service_ref = objects.Service.get_by_host_and_binary(
|
||||
ctxt, self.host, self.binary)
|
||||
|
||||
self.service_ref = service_ref
|
||||
if self.manager:
|
||||
self.manager.init_host()
|
||||
self.manager.pre_start_hook(self.service_ref)
|
||||
if self.backdoor_port is not None:
|
||||
self.manager.backdoor_port = self.backdoor_port
|
||||
self.server.start()
|
||||
if self.manager:
|
||||
self.manager.post_start_hook()
|
||||
|
||||
def stop(self):
|
||||
"""Stop serving this API.
|
||||
|
||||
:returns: None
|
||||
|
||||
"""
|
||||
self.server.stop()
|
||||
|
||||
def wait(self):
|
||||
"""Wait for the service to stop serving this API.
|
||||
|
||||
:returns: None
|
||||
|
||||
"""
|
||||
self.server.wait()
|
||||
|
||||
|
||||
def process_launcher():
|
||||
return service.ProcessLauncher(CONF, restart_method='mutate')
|
||||
|
||||
|
29
nova/tests/fixtures/nova.py
vendored
29
nova/tests/fixtures/nova.py
vendored
@@ -1106,23 +1106,30 @@ class OSMetadataServer(fixtures.Fixture):
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.md_url = None
|
||||
|
||||
def setUp(self):
|
||||
super(OSMetadataServer, self).setUp()
|
||||
# in order to run these in tests we need to bind only to local
|
||||
# host, and dynamically allocate ports
|
||||
|
||||
# A unique hostname for the wsgi-intercept.
|
||||
hostname = uuidsentinel.metadata_host
|
||||
service_name = 'metadata'
|
||||
endpoint = f'http://{hostname}/'
|
||||
conf_overrides = {
|
||||
'metadata_listen': '127.0.0.1',
|
||||
'metadata_listen_port': 0,
|
||||
'debug': True
|
||||
'metadata_listen': hostname,
|
||||
'debug': True,
|
||||
}
|
||||
self.useFixture(ConfPatcher(**conf_overrides))
|
||||
|
||||
self.metadata = service.WSGIService("metadata")
|
||||
self.metadata.start()
|
||||
self.addCleanup(self.metadata.stop)
|
||||
self.md_url = "http://%s:%s/" % (
|
||||
conf_overrides['metadata_listen'],
|
||||
self.metadata.port)
|
||||
loader = wsgi.Loader().load_app(service_name)
|
||||
app = lambda: loader
|
||||
|
||||
intercept = interceptor.RequestsInterceptor(app, url=endpoint)
|
||||
intercept.install_intercept()
|
||||
self.addCleanup(intercept.uninstall_intercept)
|
||||
|
||||
self.md_url = endpoint
|
||||
|
||||
|
||||
class PoisonFunctions(fixtures.Fixture):
|
||||
|
@@ -44,10 +44,6 @@ class ServiceTestCase(test.TestCase,
|
||||
api_version='v2.1')).api
|
||||
self.start_service('conductor')
|
||||
self.scheduler = self.start_service('scheduler')
|
||||
# Our OSAPIFixture does not use a WSGIService, so just use the metadata
|
||||
# server fixture (which uses WSGIService) for testing.
|
||||
self.metadata = self.useFixture(
|
||||
nova_fixtures.OSMetadataServer()).metadata
|
||||
# Start one compute service.
|
||||
self.start_service('compute')
|
||||
|
||||
@@ -55,6 +51,7 @@ class ServiceTestCase(test.TestCase,
|
||||
"""Tests that the cell cache for database transaction context managers
|
||||
is cleared after a service reset (example scenario: SIGHUP).
|
||||
"""
|
||||
self.assertFalse(nova_context.CELL_CACHE)
|
||||
server_req = self._build_server()
|
||||
server = self.api.post_server({'server': server_req})
|
||||
self._wait_for_state_change(server, 'ACTIVE')
|
||||
@@ -64,20 +61,12 @@ class ServiceTestCase(test.TestCase,
|
||||
# Cell cache should be empty after the service reset.
|
||||
self.assertEqual({}, nova_context.CELL_CACHE)
|
||||
|
||||
# Now test the WSGI service.
|
||||
server = self.api.post_server({'server': server_req})
|
||||
self._wait_for_state_change(server, 'ACTIVE')
|
||||
# Cell cache should be populated after creating a server.
|
||||
self.assertTrue(nova_context.CELL_CACHE)
|
||||
self.metadata.reset()
|
||||
# Cell cache should be empty after the service reset.
|
||||
self.assertEqual({}, nova_context.CELL_CACHE)
|
||||
|
||||
def test_service_start_resets_cell_cache(self):
|
||||
"""Tests that the cell cache for database transaction context managers
|
||||
is cleared upon a service start (example scenario: service start after
|
||||
a SIGTERM and the parent process forks child process workers).
|
||||
"""
|
||||
self.assertFalse(nova_context.CELL_CACHE)
|
||||
server_req = self._build_server()
|
||||
server = self.api.post_server({'server': server_req})
|
||||
self._wait_for_state_change(server, 'ACTIVE')
|
||||
@@ -96,20 +85,6 @@ class ServiceTestCase(test.TestCase,
|
||||
# Cell cache should be empty after the service start.
|
||||
self.assertEqual({}, nova_context.CELL_CACHE)
|
||||
|
||||
# Now test the WSGI service.
|
||||
server = self.api.post_server({'server': server_req})
|
||||
self._wait_for_state_change(server, 'ACTIVE')
|
||||
# Cell cache should be populated after creating a server.
|
||||
self.assertTrue(nova_context.CELL_CACHE)
|
||||
# we need to mock nova.utils.raise_if_old_compute() that is run at
|
||||
# service startup as that will check the global service level which
|
||||
# populates the cell cache
|
||||
with mock.patch("nova.utils.raise_if_old_compute"):
|
||||
self.metadata.stop()
|
||||
self.metadata.start()
|
||||
# Cell cache should be empty after the service reset.
|
||||
self.assertEqual({}, nova_context.CELL_CACHE)
|
||||
|
||||
|
||||
class TestOldComputeCheck(
|
||||
test.TestCase, integrated_helpers.InstanceHelperMixin):
|
||||
|
@@ -19,17 +19,10 @@ 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
|
||||
@@ -38,7 +31,6 @@ from nova.objects import base as obj_base
|
||||
from nova import rpc
|
||||
from nova import service
|
||||
from nova import test
|
||||
from nova.tests.unit import utils
|
||||
|
||||
test_service_opts = [
|
||||
cfg.HostAddressOpt("test_service_listen",
|
||||
@@ -323,323 +315,6 @@ 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):
|
||||
super(TestWSGIService, self).setUp()
|
||||
self.stub_out('nova.api.wsgi.Loader.load_app',
|
||||
lambda *a, **kw: mock.MagicMock())
|
||||
|
||||
@mock.patch('nova.objects.Service.get_by_host_and_binary')
|
||||
@mock.patch('nova.objects.Service.create')
|
||||
def test_service_start_creates_record(self, mock_create, mock_get):
|
||||
mock_get.return_value = None
|
||||
test_service = service.WSGIService("test_service")
|
||||
test_service.start()
|
||||
self.addCleanup(test_service.stop)
|
||||
self.assertTrue(mock_create.called)
|
||||
|
||||
@mock.patch('nova.objects.Service.get_by_host_and_binary')
|
||||
@mock.patch('nova.objects.Service.create')
|
||||
def test_service_start_does_not_create_record(self, mock_create, mock_get):
|
||||
test_service = service.WSGIService("test_service")
|
||||
test_service.start()
|
||||
self.addCleanup(test_service.stop)
|
||||
self.assertFalse(mock_create.called)
|
||||
|
||||
@mock.patch('nova.objects.Service.get_by_host_and_binary')
|
||||
def test_service_random_port(self, mock_get):
|
||||
test_service = service.WSGIService("test_service")
|
||||
test_service.start()
|
||||
self.addCleanup(test_service.stop)
|
||||
self.assertNotEqual(0, test_service.port)
|
||||
|
||||
def test_workers_set_default(self):
|
||||
test_service = service.WSGIService("osapi_compute")
|
||||
self.assertEqual(test_service.workers, processutils.get_worker_count())
|
||||
|
||||
def test_workers_set_good_user_setting(self):
|
||||
CONF.set_override('osapi_compute_workers', 8)
|
||||
test_service = service.WSGIService("osapi_compute")
|
||||
self.assertEqual(test_service.workers, 8)
|
||||
|
||||
def test_openstack_compute_api_workers_set_default(self):
|
||||
test_service = service.WSGIService("openstack_compute_api_v2")
|
||||
self.assertEqual(test_service.workers, processutils.get_worker_count())
|
||||
|
||||
def test_openstack_compute_api_workers_set_good_user_setting(self):
|
||||
CONF.set_override('osapi_compute_workers', 8)
|
||||
test_service = service.WSGIService("openstack_compute_api_v2")
|
||||
self.assertEqual(test_service.workers, 8)
|
||||
|
||||
@testtools.skipIf(not utils.is_ipv6_supported(), "no ipv6 support")
|
||||
@mock.patch('nova.objects.Service.get_by_host_and_binary')
|
||||
def test_service_random_port_with_ipv6(self, mock_get):
|
||||
CONF.set_default("test_service_listen", "::1")
|
||||
test_service = service.WSGIService("test_service")
|
||||
test_service.start()
|
||||
self.addCleanup(test_service.stop)
|
||||
self.assertEqual("::1", test_service.host)
|
||||
self.assertNotEqual(0, test_service.port)
|
||||
|
||||
@mock.patch('nova.objects.Service.get_by_host_and_binary')
|
||||
def test_reset_pool_size_to_default(self, mock_get):
|
||||
test_service = service.WSGIService("test_service")
|
||||
test_service.start()
|
||||
|
||||
# Stopping the service, which in turn sets pool size to 0
|
||||
test_service.stop()
|
||||
self.assertEqual(test_service.server._pool.size, 0)
|
||||
|
||||
# Resetting pool size to default
|
||||
test_service.reset()
|
||||
test_service.start()
|
||||
self.addCleanup(test_service.stop)
|
||||
self.assertEqual(test_service.server._pool.size,
|
||||
CONF.wsgi.default_pool_size)
|
||||
|
||||
|
||||
class TestLauncher(test.NoDBTestCase):
|
||||
|
||||
@mock.patch.object(_service, 'launch')
|
||||
|
Reference in New Issue
Block a user