diff --git a/glance/common/wsgi.py b/glance/common/wsgi.py index 058e9e19e2..6c8853da20 100644 --- a/glance/common/wsgi.py +++ b/glance/common/wsgi.py @@ -320,6 +320,14 @@ profiler_opts.set_defaults(CONF) ASYNC_EVENTLET_THREAD_POOL_LIST = [] +# Detect if we're running under the uwsgi server +try: + import uwsgi + LOG.debug('Detected running under uwsgi') +except ImportError: + LOG.debug('Detected not running under uwsgi') + uwsgi = None + def get_num_workers(): """Return the configured number of workers.""" @@ -925,6 +933,31 @@ class Router(object): return app +class _UWSGIChunkFile(object): + + def read(self, length=None): + position = 0 + if length == 0: + return b"" + + if length and length < 0: + length = None + + response = [] + while True: + data = uwsgi.chunked_read() + # Return everything if we reached the end of the file + if not data: + break + response.append(data) + # Return the data if we've reached the length + if length is not None: + position += len(data) + if position >= length: + break + return b''.join(response) + + class Request(webob.Request): """Add some OpenStack API-specific logic to the base webob.Request.""" @@ -935,6 +968,19 @@ class Request(webob.Request): environ['wsgi.url_scheme'] = scheme super(Request, self).__init__(environ, *args, **kwargs) + @property + def body_file(self): + if uwsgi: + if self.headers.get('transfer-encoding', '').lower() == 'chunked': + return _UWSGIChunkFile() + return super(Request, self).body_file + + @body_file.setter + def body_file(self, value): + # NOTE(cdent): If you have a property setter in a superclass, it will + # not be inherited. + webob.Request.body_file.fset(self, value) + @property def params(self): """Override params property of webob.request.BaseRequest. diff --git a/glance/tests/unit/common/test_wsgi.py b/glance/tests/unit/common/test_wsgi.py index f82418775a..0f4c5ad566 100644 --- a/glance/tests/unit/common/test_wsgi.py +++ b/glance/tests/unit/common/test_wsgi.py @@ -738,3 +738,68 @@ class GetSocketTestCase(test_utils.BaseTestCase): 'glance.common.wsgi.ssl.wrap_socket', lambda *x, **y: None)) self.assertRaises(wsgi.socket.error, wsgi.get_socket, 1234) + + +def _cleanup_uwsgi(): + wsgi.uwsgi = None + + +class Test_UwsgiChunkedFile(test_utils.BaseTestCase): + + def test_read_no_data(self): + reader = wsgi._UWSGIChunkFile() + wsgi.uwsgi = mock.MagicMock() + self.addCleanup(_cleanup_uwsgi) + + def fake_read(): + return None + + wsgi.uwsgi.chunked_read = fake_read + out = reader.read() + self.assertEqual(out, b'') + + def test_read_data_no_length(self): + reader = wsgi._UWSGIChunkFile() + wsgi.uwsgi = mock.MagicMock() + self.addCleanup(_cleanup_uwsgi) + + values = iter([b'a', b'b', b'c', None]) + + def fake_read(): + return next(values) + + wsgi.uwsgi.chunked_read = fake_read + out = reader.read() + self.assertEqual(out, b'abc') + + def test_read_zero_length(self): + reader = wsgi._UWSGIChunkFile() + self.assertEqual(b'', reader.read(length=0)) + + def test_read_data_length(self): + reader = wsgi._UWSGIChunkFile() + wsgi.uwsgi = mock.MagicMock() + self.addCleanup(_cleanup_uwsgi) + + values = iter([b'a', b'b', b'c', None]) + + def fake_read(): + return next(values) + + wsgi.uwsgi.chunked_read = fake_read + out = reader.read(length=2) + self.assertEqual(out, b'ab') + + def test_read_data_negative_length(self): + reader = wsgi._UWSGIChunkFile() + wsgi.uwsgi = mock.MagicMock() + self.addCleanup(_cleanup_uwsgi) + + values = iter([b'a', b'b', b'c', None]) + + def fake_read(): + return next(values) + + wsgi.uwsgi.chunked_read = fake_read + out = reader.read(length=-2) + self.assertEqual(out, b'abc')