Просмотр исходного кода

api: Do not wrap Fault wrap HTTPNoContent exceptions

According to RFC 7231, HTTP 204 (No Content) should not have any
additional content in the payload body. However, since we Fault wrap
these exceptions, a body is added.

This becomes a problem when urllib3 reuses the connection, resulting in
this type of errors:

```
  File "/usr/lib/python3.12/http/client.py", line 1448, in getresponse
response.begin()
  File "/usr/lib/python3.12/http/client.py", line 336, in begin
version, status, reason = self._read_status()
                          ^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.12/http/client.py", line 318, in _read_status
raise BadStatusLine(line)
http.client.BadStatusLine: {"error": {"fault": "computeFault", "code": 204, "message": ""}}HTTP/1.1 204 No Content
```
Claudiu Belu 2 недель назад
Родитель
Сommit
46b9fdc217
2 измененных файлов с 69 добавлено и 0 удалено
  1. 3 0
      coriolis/api/wsgi.py
  2. 66 0
      coriolis/tests/api/test_wsgi.py

+ 3 - 0
coriolis/api/wsgi.py

@@ -656,6 +656,9 @@ class ResourceExceptionHandler(object):
         elif isinstance(ex_value, Fault):
             LOG.info(_LI("Fault thrown: %s"), ex_value)
             raise ex_value
+        elif isinstance(ex_value, webob.exc.HTTPNoContent):
+            LOG.info(_LI("HTTPNoContent: %s"), ex_value)
+            raise ex_value
         elif isinstance(ex_value, webob.exc.HTTPException):
             LOG.info(_LI("HTTP exception thrown: %s"), ex_value)
             raise Fault(ex_value)

+ 66 - 0
coriolis/tests/api/test_wsgi.py

@@ -0,0 +1,66 @@
+# Copyright 2026 Cloudbase Solutions Srl
+# All Rights Reserved.
+
+import webob.exc
+
+from coriolis.api import wsgi
+from coriolis import exception
+from coriolis.tests import test_base
+
+
+class ResourceExceptionHandlerTestCase(test_base.CoriolisBaseTestCase):
+    """Tests for ResourceExceptionHandler context manager."""
+
+    def _run(self, exc_to_raise):
+        """Enter the context manager and raise the given exception"""
+        with wsgi.ResourceExceptionHandler():
+            raise exc_to_raise
+
+    def test_no_exception(self):
+        with wsgi.ResourceExceptionHandler():
+            pass  # no exception raised
+
+    def test_not_authorized(self):
+        exc = exception.NotAuthorized()
+        raised = self.assertRaises(wsgi.Fault, self._run, exc)
+        self.assertEqual(403, raised.status_int)
+
+    def test_invalid(self):
+        exc = exception.Invalid("bad value")
+        raised = self.assertRaises(wsgi.Fault, self._run, exc)
+        self.assertEqual(exc.code, raised.status_int)
+
+    def test_type_error(self):
+        exc = TypeError("wrong type")
+        raised = self.assertRaises(wsgi.Fault, self._run, exc)
+        self.assertEqual(400, raised.status_int)
+
+    def test_fault(self):
+        original = wsgi.Fault(webob.exc.HTTPBadRequest())
+        raised = self.assertRaises(wsgi.Fault, self._run, original)
+        self.assertIs(original, raised)
+
+    def test_http_no_content(self):
+        exc = webob.exc.HTTPNoContent()
+
+        # HTTPNoContent (204) must propagate without Fault wrapping, so that
+        # the HTTP client sees an empty body.
+        # Fixes BadStatusLine when urllib3 reuses the connection after a
+        # 204+body response.
+        raised = self.assertRaises(webob.exc.HTTPNoContent, self._run, exc)
+        self.assertIs(exc, raised)
+
+    def test_http_not_found(self):
+        exc = webob.exc.HTTPNotFound()
+        raised = self.assertRaises(wsgi.Fault, self._run, exc)
+        self.assertEqual(404, raised.status_int)
+
+    def test_internal_server_error(self):
+        exc = webob.exc.HTTPInternalServerError()
+        raised = self.assertRaises(wsgi.Fault, self._run, exc)
+        self.assertEqual(500, raised.status_int)
+
+    def test_unknown_exception(self):
+        exc = RuntimeError("unexpected")
+        raised = self.assertRaises(RuntimeError, self._run, exc)
+        self.assertIs(exc, raised)