Merge "Prevent WSGI Server usage with threading backend"

This commit is contained in:
Zuul
2025-09-18 15:02:16 +00:00
committed by Gerrit Code Review
4 changed files with 198 additions and 0 deletions

View File

@@ -13,6 +13,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from oslo_service._i18n import _
class BackendAlreadySelected(Exception):
"""Raised when init_backend() is called more than once."""
@@ -22,3 +24,15 @@ class BackendAlreadySelected(Exception):
class BackendComponentNotAvailable(Exception):
"""Raised when a requested component is not available in the backend."""
pass
class UnsupportedBackendError(Exception):
"""Raised when a component incompatible with threading backend is used."""
def __init__(self, message=None):
if message is None:
message = _(
"This component is not compatible with the threading backend. "
"See the documentation for details about how to use the "
"threading backend."
)
super().__init__(message)

View File

@@ -29,6 +29,9 @@ import requests
import webob
from oslo_config import cfg
from oslo_service import backend
from oslo_service.backend.exceptions import BackendAlreadySelected
from oslo_service.backend.exceptions import UnsupportedBackendError
from oslo_service import sslutils
from oslo_service.tests import base
from oslo_service import wsgi
@@ -48,6 +51,10 @@ class WsgiTestCase(base.ServiceBaseTestCase):
super().setUp()
self.conf(args=[], default_config_files=[])
# Ensure eventlet backend is active for WSGI tests
backend._reset_backend()
backend.init_backend(backend.BackendType.EVENTLET)
class TestLoaderNothingExists(WsgiTestCase):
"""Loader tests where os.path.exists always returns False."""
@@ -401,3 +408,140 @@ class TestWSGIServerWithSSL(WsgiTestCase):
server.stop()
server.wait()
class TestWSGIServerBackendCompatibility(WsgiTestCase):
"""Tests for WSGI Server backend compatibility."""
def setUp(self):
super().setUp()
self.backend_module = backend
# Reset backend state before each test to allow re-initialization
self.backend_module._reset_backend()
def tearDown(self):
super().tearDown()
# Reset backend state after each test
self.backend_module._reset_backend()
def test_server_creation_with_eventlet_backend(self):
"""Test that Server can be created with eventlet backend."""
# Initialize eventlet backend explicitly
self.backend_module.init_backend(
self.backend_module.BackendType.EVENTLET)
# This should work without raising an exception
server = wsgi.Server(self.conf, "test_eventlet", None)
self.assertEqual("test_eventlet", server.name)
def test_server_creation_fails_with_threading_backend(self):
"""Test that Server creation fails with threading backend."""
# Initialize threading backend
self.backend_module.init_backend(
self.backend_module.BackendType.THREADING)
# This should raise UnsupportedBackendError
def create_server():
return wsgi.Server(self.conf, "test_threading", None)
exc = self.assertRaises(UnsupportedBackendError, create_server)
# Check that the error message contains specific information
error_message = str(exc)
self.assertIn("oslo.service.wsgi.Server", error_message)
self.assertIn("threading backend", error_message)
self.assertIn("standard WSGI servers", error_message)
def test_check_backend_compatibility_function_eventlet(self):
"""Test _check_backend_compatibility with eventlet backend."""
# Initialize eventlet backend
self.backend_module.init_backend(
self.backend_module.BackendType.EVENTLET)
# This should not raise an exception
try:
wsgi._check_backend_compatibility()
except Exception as e:
self.fail(f"_check_backend_compatibility raised {e} unexpectedly")
def test_check_backend_compatibility_function_threading(self):
"""Test _check_backend_compatibility with threading backend."""
# Initialize threading backend
self.backend_module.init_backend(
self.backend_module.BackendType.THREADING)
# This should raise UnsupportedBackendError
self.assertRaises(
UnsupportedBackendError,
wsgi._check_backend_compatibility)
def test_check_backend_compatibility_with_default_backend(self):
"""Test _check_backend_compatibility with defaut backend."""
# Don't explicitly initialize backend, use default (eventlet)
# This should not raise an exception since default is eventlet
try:
wsgi._check_backend_compatibility()
except Exception as e:
self.fail(f"_check_backend_compatibility raised {e} unexpectedly")
@mock.patch('oslo_service.wsgi.backend.get_backend_type')
def test_check_backend_compatibility_with_none_backend(
self, mock_get_backend_type):
"""Test _check_backend_compatibility when backend type is None."""
# Simulate the case where backend hasn't been initialized yet
mock_get_backend_type.return_value = None
# This should not raise an exception
try:
wsgi._check_backend_compatibility()
except Exception as e:
self.fail(f"_check_backend_compatibility raised {e} unexpectedly")
def test_server_initialization_sequence_with_threading_backend(self):
"""Test that the error occurs during Server.__init__, not after."""
# Initialize threading backend
self.backend_module.init_backend(
self.backend_module.BackendType.THREADING)
# The error should occur immediately during __init__
def create_server():
# Server.__init__ should fail before any actual server setup
return wsgi.Server(
self.conf, "test_early_fail", None, host="127.0.0.1", port=0)
self.assertRaises(UnsupportedBackendError, create_server)
def test_multiple_server_creation_attempts_with_threading_backend(self):
"""Test that all attempts creating Server fail consistently."""
# Initialize threading backend
self.backend_module.init_backend(
self.backend_module.BackendType.THREADING)
# Multiple attempts should all fail in the same way
for i in range(3):
def create_server():
return wsgi.Server(self.conf, f"test_multi_{i}", None)
self.assertRaises(UnsupportedBackendError, create_server)
def test_backend_switching_behavior(self):
"""Test behavior when trying to switch backends."""
# Start with eventlet backend
self.backend_module.init_backend(
self.backend_module.BackendType.EVENTLET)
# Server creation should work
server = wsgi.Server(self.conf, "test_eventlet_first", None)
self.assertEqual("test_eventlet_first", server.name)
# Attempting to switch to threading backend should fail
def switch_backend():
self.backend_module.init_backend(
self.backend_module.BackendType.THREADING)
self.assertRaises(BackendAlreadySelected, switch_backend)
# Server creation should still work since backend is still eventlet
server2 = wsgi.Server(self.conf, "test_eventlet_second", None)
self.assertEqual("test_eventlet_second", server2.name)

View File

@@ -32,6 +32,8 @@ import webob.exc
from oslo_log import log as logging
from oslo_service._i18n import _
from oslo_service import _options
from oslo_service import backend
from oslo_service.backend.exceptions import UnsupportedBackendError
from oslo_service import service
from oslo_service import sslutils
@@ -40,6 +42,21 @@ from debtcollector import removals
LOG = logging.getLogger(__name__)
def _check_backend_compatibility():
"""Check if the current backend is compatible with WSGI Server.
Raises UnsupportedBackendError if the threading backend is active.
"""
current_backend = backend.get_backend_type()
if current_backend == backend.BackendType.THREADING:
message = _(
"The oslo.service.wsgi.Server class is deprecated and cannot "
"be used with the threading backend. Please switch to "
"deploying your application via standard WSGI servers instead."
)
raise UnsupportedBackendError(message)
def list_opts():
"""Entry point for oslo-config-generator."""
return [(None, copy.deepcopy(_options.wsgi_opts))]
@@ -95,8 +112,12 @@ class Server(service.ServiceBase):
:returns: None
:raises: InvalidInput
:raises: EnvironmentError
:raises: UnsupportedBackendError
"""
# Check backend compatibility before initializing
_check_backend_compatibility()
self.conf = conf
self.conf.register_opts(_options.wsgi_opts)

View File

@@ -0,0 +1,19 @@
---
upgrade:
- |
The ``oslo.service.wsgi.Server`` class now includes
backend compatibility checks to prevent improper usage with the threading
backend. Applications using the threading backend that attempt to
instantiate the WSGI Server will now receive an immediate
``UnsupportedBackendError`` exception with a clear error message.
This is an intentional breaking change to prevent potential runtime issues
and security concerns that could arise from using a component designed for
eventlet in a threading environment. The fail-fast behavior ensures that
applications using the threading backend are guided towards the correct
deployment patterns.
**Migration Path**: Users should migrate to deploying their WSGI
applications using standard WSGI servers such as ``uwsgi``, ``gunicorn``,
or similar alternatives instead of relying on the deprecated oslo.service
WSGI server.