Merge "Add Cinder API wsgi application"
This commit is contained in:
commit
9326a630d8
@ -30,7 +30,7 @@ import webob.exc
|
|||||||
from cinder.api.openstack import wsgi
|
from cinder.api.openstack import wsgi
|
||||||
from cinder import context
|
from cinder import context
|
||||||
from cinder.i18n import _
|
from cinder.i18n import _
|
||||||
from cinder import wsgi as base_wsgi
|
from cinder.wsgi import common as base_wsgi
|
||||||
|
|
||||||
|
|
||||||
use_forwarded_for_opt = cfg.BoolOpt(
|
use_forwarded_for_opt = cfg.BoolOpt(
|
||||||
|
@ -23,7 +23,7 @@ from cinder.api.openstack import wsgi
|
|||||||
from cinder import exception
|
from cinder import exception
|
||||||
from cinder.i18n import _, _LE, _LI
|
from cinder.i18n import _, _LE, _LI
|
||||||
from cinder import utils
|
from cinder import utils
|
||||||
from cinder import wsgi as base_wsgi
|
from cinder.wsgi import common as base_wsgi
|
||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
@ -23,7 +23,7 @@ import routes
|
|||||||
|
|
||||||
from cinder.api.openstack import wsgi
|
from cinder.api.openstack import wsgi
|
||||||
from cinder.i18n import _, _LW
|
from cinder.i18n import _, _LW
|
||||||
from cinder import wsgi as base_wsgi
|
from cinder.wsgi import common as base_wsgi
|
||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
@ -31,7 +31,7 @@ from cinder import exception
|
|||||||
from cinder import i18n
|
from cinder import i18n
|
||||||
from cinder.i18n import _, _LE, _LI
|
from cinder.i18n import _, _LE, _LI
|
||||||
from cinder import utils
|
from cinder import utils
|
||||||
from cinder import wsgi
|
from cinder.wsgi import common as wsgi
|
||||||
|
|
||||||
|
|
||||||
XML_NS_V1 = 'http://docs.openstack.org/api/openstack-block-storage/1.0/content'
|
XML_NS_V1 = 'http://docs.openstack.org/api/openstack-block-storage/1.0/content'
|
||||||
|
@ -34,7 +34,7 @@ from cinder.api.views import limits as limits_views
|
|||||||
from cinder.api import xmlutil
|
from cinder.api import xmlutil
|
||||||
from cinder.i18n import _
|
from cinder.i18n import _
|
||||||
from cinder import quota
|
from cinder import quota
|
||||||
from cinder import wsgi as base_wsgi
|
from cinder.wsgi import common as base_wsgi
|
||||||
|
|
||||||
QUOTAS = quota.QUOTAS
|
QUOTAS = quota.QUOTAS
|
||||||
LIMITS_PREFIX = "limits."
|
LIMITS_PREFIX = "limits."
|
||||||
|
@ -34,7 +34,7 @@ from cinder.api.views import limits as limits_views
|
|||||||
from cinder.api import xmlutil
|
from cinder.api import xmlutil
|
||||||
from cinder.i18n import _
|
from cinder.i18n import _
|
||||||
from cinder import quota
|
from cinder import quota
|
||||||
from cinder import wsgi as base_wsgi
|
from cinder.wsgi import common as base_wsgi
|
||||||
|
|
||||||
QUOTAS = quota.QUOTAS
|
QUOTAS = quota.QUOTAS
|
||||||
LIMITS_PREFIX = "limits."
|
LIMITS_PREFIX = "limits."
|
||||||
|
@ -41,8 +41,8 @@ from cinder.i18n import _, _LE, _LI, _LW
|
|||||||
from cinder.objects import base as objects_base
|
from cinder.objects import base as objects_base
|
||||||
from cinder import rpc
|
from cinder import rpc
|
||||||
from cinder import version
|
from cinder import version
|
||||||
from cinder import wsgi
|
from cinder.wsgi import common as wsgi_common
|
||||||
|
from cinder.wsgi import eventlet_server as wsgi
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -356,7 +356,7 @@ class WSGIService(service.ServiceBase):
|
|||||||
"""
|
"""
|
||||||
self.name = name
|
self.name = name
|
||||||
self.manager = self._get_manager()
|
self.manager = self._get_manager()
|
||||||
self.loader = loader or wsgi.Loader()
|
self.loader = loader or wsgi_common.Loader()
|
||||||
self.app = self.loader.load_app(name)
|
self.app = self.loader.load_app(name)
|
||||||
self.host = getattr(CONF, '%s_listen' % name, "0.0.0.0")
|
self.host = getattr(CONF, '%s_listen' % name, "0.0.0.0")
|
||||||
self.port = getattr(CONF, '%s_listen_port' % name, 0)
|
self.port = getattr(CONF, '%s_listen_port' % name, 0)
|
||||||
|
@ -29,7 +29,7 @@ from cinder.api.v2 import limits
|
|||||||
from cinder.api.v2 import router
|
from cinder.api.v2 import router
|
||||||
from cinder.api import versions
|
from cinder.api import versions
|
||||||
from cinder import context
|
from cinder import context
|
||||||
from cinder import wsgi
|
from cinder.wsgi import common as wsgi
|
||||||
|
|
||||||
|
|
||||||
FAKE_UUID = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'
|
FAKE_UUID = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'
|
||||||
|
@ -31,7 +31,7 @@ from cinder import manager
|
|||||||
from cinder import rpc
|
from cinder import rpc
|
||||||
from cinder import service
|
from cinder import service
|
||||||
from cinder import test
|
from cinder import test
|
||||||
from cinder import wsgi
|
from cinder.wsgi import common as wsgi
|
||||||
|
|
||||||
|
|
||||||
test_service_opts = [
|
test_service_opts = [
|
||||||
@ -275,25 +275,25 @@ class TestWSGIService(test.TestCase):
|
|||||||
self.assertEqual(1000, test_service.server._pool.size)
|
self.assertEqual(1000, test_service.server._pool.size)
|
||||||
self.assertTrue(mock_load_app.called)
|
self.assertTrue(mock_load_app.called)
|
||||||
|
|
||||||
@mock.patch('cinder.wsgi.Server')
|
@mock.patch('cinder.wsgi.eventlet_server.Server')
|
||||||
def test_workers_set_default(self, wsgi_server):
|
def test_workers_set_default(self, wsgi_server):
|
||||||
test_service = service.WSGIService("osapi_volume")
|
test_service = service.WSGIService("osapi_volume")
|
||||||
self.assertEqual(processutils.get_worker_count(), test_service.workers)
|
self.assertEqual(processutils.get_worker_count(), test_service.workers)
|
||||||
|
|
||||||
@mock.patch('cinder.wsgi.Server')
|
@mock.patch('cinder.wsgi.eventlet_server.Server')
|
||||||
def test_workers_set_good_user_setting(self, wsgi_server):
|
def test_workers_set_good_user_setting(self, wsgi_server):
|
||||||
self.override_config('osapi_volume_workers', 8)
|
self.override_config('osapi_volume_workers', 8)
|
||||||
test_service = service.WSGIService("osapi_volume")
|
test_service = service.WSGIService("osapi_volume")
|
||||||
self.assertEqual(8, test_service.workers)
|
self.assertEqual(8, test_service.workers)
|
||||||
|
|
||||||
@mock.patch('cinder.wsgi.Server')
|
@mock.patch('cinder.wsgi.eventlet_server.Server')
|
||||||
def test_workers_set_zero_user_setting(self, wsgi_server):
|
def test_workers_set_zero_user_setting(self, wsgi_server):
|
||||||
self.override_config('osapi_volume_workers', 0)
|
self.override_config('osapi_volume_workers', 0)
|
||||||
test_service = service.WSGIService("osapi_volume")
|
test_service = service.WSGIService("osapi_volume")
|
||||||
# If a value less than 1 is used, defaults to number of procs available
|
# If a value less than 1 is used, defaults to number of procs available
|
||||||
self.assertEqual(processutils.get_worker_count(), test_service.workers)
|
self.assertEqual(processutils.get_worker_count(), test_service.workers)
|
||||||
|
|
||||||
@mock.patch('cinder.wsgi.Server')
|
@mock.patch('cinder.wsgi.eventlet_server.Server')
|
||||||
def test_workers_set_negative_user_setting(self, wsgi_server):
|
def test_workers_set_negative_user_setting(self, wsgi_server):
|
||||||
self.override_config('osapi_volume_workers', -1)
|
self.override_config('osapi_volume_workers', -1)
|
||||||
self.assertRaises(exception.InvalidInput,
|
self.assertRaises(exception.InvalidInput,
|
||||||
|
0
cinder/tests/unit/wsgi/__init__.py
Normal file
0
cinder/tests/unit/wsgi/__init__.py
Normal file
@ -34,12 +34,13 @@ import webob.dec
|
|||||||
from cinder import exception
|
from cinder import exception
|
||||||
from cinder.i18n import _
|
from cinder.i18n import _
|
||||||
from cinder import test
|
from cinder import test
|
||||||
import cinder.wsgi
|
from cinder.wsgi import common as wsgi_common
|
||||||
|
from cinder.wsgi import eventlet_server as wsgi
|
||||||
|
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
|
|
||||||
TEST_VAR_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__),
|
TEST_VAR_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__),
|
||||||
'var'))
|
'../var'))
|
||||||
|
|
||||||
|
|
||||||
def open_no_proxy(*args, **kwargs):
|
def open_no_proxy(*args, **kwargs):
|
||||||
@ -67,8 +68,8 @@ class TestLoaderNothingExists(test.TestCase):
|
|||||||
|
|
||||||
def test_config_not_found(self):
|
def test_config_not_found(self):
|
||||||
self.assertRaises(
|
self.assertRaises(
|
||||||
cinder.exception.ConfigNotFound,
|
exception.ConfigNotFound,
|
||||||
cinder.wsgi.Loader,
|
wsgi_common.Loader,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -87,7 +88,7 @@ document_root = /tmp
|
|||||||
self.config.write(self._paste_config.lstrip())
|
self.config.write(self._paste_config.lstrip())
|
||||||
self.config.seek(0)
|
self.config.seek(0)
|
||||||
self.config.flush()
|
self.config.flush()
|
||||||
self.loader = cinder.wsgi.Loader(self.config.name)
|
self.loader = wsgi_common.Loader(self.config.name)
|
||||||
self.addCleanup(self.config.close)
|
self.addCleanup(self.config.close)
|
||||||
|
|
||||||
def test_config_found(self):
|
def test_config_found(self):
|
||||||
@ -95,7 +96,7 @@ document_root = /tmp
|
|||||||
|
|
||||||
def test_app_not_found(self):
|
def test_app_not_found(self):
|
||||||
self.assertRaises(
|
self.assertRaises(
|
||||||
cinder.exception.PasteAppNotFound,
|
exception.PasteAppNotFound,
|
||||||
self.loader.load_app,
|
self.loader.load_app,
|
||||||
"non-existent app",
|
"non-existent app",
|
||||||
)
|
)
|
||||||
@ -115,12 +116,12 @@ class TestWSGIServer(test.TestCase):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
def test_no_app(self):
|
def test_no_app(self):
|
||||||
server = cinder.wsgi.Server("test_app", None,
|
server = wsgi.Server("test_app", None,
|
||||||
host="127.0.0.1", port=0)
|
host="127.0.0.1", port=0)
|
||||||
self.assertEqual("test_app", server.name)
|
self.assertEqual("test_app", server.name)
|
||||||
|
|
||||||
def test_start_random_port(self):
|
def test_start_random_port(self):
|
||||||
server = cinder.wsgi.Server("test_random_port", None, host="127.0.0.1")
|
server = wsgi.Server("test_random_port", None, host="127.0.0.1")
|
||||||
server.start()
|
server.start()
|
||||||
self.assertNotEqual(0, server.port)
|
self.assertNotEqual(0, server.port)
|
||||||
server.stop()
|
server.stop()
|
||||||
@ -129,9 +130,9 @@ class TestWSGIServer(test.TestCase):
|
|||||||
@testtools.skipIf(not _ipv6_configured(),
|
@testtools.skipIf(not _ipv6_configured(),
|
||||||
"Test requires an IPV6 configured interface")
|
"Test requires an IPV6 configured interface")
|
||||||
def test_start_random_port_with_ipv6(self):
|
def test_start_random_port_with_ipv6(self):
|
||||||
server = cinder.wsgi.Server("test_random_port",
|
server = wsgi.Server("test_random_port",
|
||||||
None,
|
None,
|
||||||
host="::1")
|
host="::1")
|
||||||
server.start()
|
server.start()
|
||||||
self.assertEqual("::1", server.host)
|
self.assertEqual("::1", server.host)
|
||||||
self.assertNotEqual(0, server.port)
|
self.assertNotEqual(0, server.port)
|
||||||
@ -140,8 +141,8 @@ class TestWSGIServer(test.TestCase):
|
|||||||
|
|
||||||
def test_server_pool_waitall(self):
|
def test_server_pool_waitall(self):
|
||||||
# test pools waitall method gets called while stopping server
|
# test pools waitall method gets called while stopping server
|
||||||
server = cinder.wsgi.Server("test_server", None,
|
server = wsgi.Server("test_server", None,
|
||||||
host="127.0.0.1")
|
host="127.0.0.1")
|
||||||
server.start()
|
server.start()
|
||||||
with mock.patch.object(server._pool,
|
with mock.patch.object(server._pool,
|
||||||
'waitall') as mock_waitall:
|
'waitall') as mock_waitall:
|
||||||
@ -160,8 +161,8 @@ class TestWSGIServer(test.TestCase):
|
|||||||
start_response('200 OK', [('Content-Type', 'text/plain')])
|
start_response('200 OK', [('Content-Type', 'text/plain')])
|
||||||
return [greetings]
|
return [greetings]
|
||||||
|
|
||||||
server = cinder.wsgi.Server("test_app", hello_world,
|
server = wsgi.Server("test_app", hello_world,
|
||||||
host="127.0.0.1", port=0)
|
host="127.0.0.1", port=0)
|
||||||
server.start()
|
server.start()
|
||||||
|
|
||||||
response = open_no_proxy('http://127.0.0.1:%d/' % server.port)
|
response = open_no_proxy('http://127.0.0.1:%d/' % server.port)
|
||||||
@ -176,8 +177,8 @@ class TestWSGIServer(test.TestCase):
|
|||||||
start_response('200 OK', [('Content-Type', 'text/plain')])
|
start_response('200 OK', [('Content-Type', 'text/plain')])
|
||||||
return [greetings]
|
return [greetings]
|
||||||
|
|
||||||
server = cinder.wsgi.Server("test_app", hello_world,
|
server = wsgi.Server("test_app", hello_world,
|
||||||
host="127.0.0.1", port=0)
|
host="127.0.0.1", port=0)
|
||||||
server.start()
|
server.start()
|
||||||
|
|
||||||
s = socket.socket()
|
s = socket.socket()
|
||||||
@ -215,8 +216,8 @@ class TestWSGIServer(test.TestCase):
|
|||||||
def hello_world(req):
|
def hello_world(req):
|
||||||
return greetings
|
return greetings
|
||||||
|
|
||||||
server = cinder.wsgi.Server("test_app", hello_world,
|
server = wsgi.Server("test_app", hello_world,
|
||||||
host="127.0.0.1", port=0)
|
host="127.0.0.1", port=0)
|
||||||
|
|
||||||
server.start()
|
server.start()
|
||||||
|
|
||||||
@ -239,10 +240,10 @@ class TestWSGIServer(test.TestCase):
|
|||||||
def hello_world(req):
|
def hello_world(req):
|
||||||
return greetings
|
return greetings
|
||||||
|
|
||||||
server = cinder.wsgi.Server("test_app",
|
server = wsgi.Server("test_app",
|
||||||
hello_world,
|
hello_world,
|
||||||
host="::1",
|
host="::1",
|
||||||
port=0)
|
port=0)
|
||||||
server.start()
|
server.start()
|
||||||
|
|
||||||
response = open_no_proxy('https://[::1]:%d/' % server.port)
|
response = open_no_proxy('https://[::1]:%d/' % server.port)
|
||||||
@ -251,7 +252,7 @@ class TestWSGIServer(test.TestCase):
|
|||||||
server.stop()
|
server.stop()
|
||||||
|
|
||||||
def test_reset_pool_size_to_default(self):
|
def test_reset_pool_size_to_default(self):
|
||||||
server = cinder.wsgi.Server("test_resize", None, host="127.0.0.1")
|
server = wsgi.Server("test_resize", None, host="127.0.0.1")
|
||||||
server.start()
|
server.start()
|
||||||
|
|
||||||
# Stopping the server, which in turn sets pool size to 0
|
# Stopping the server, which in turn sets pool size to 0
|
@ -27,7 +27,7 @@ from cinder import test
|
|||||||
import routes
|
import routes
|
||||||
import webob
|
import webob
|
||||||
|
|
||||||
from cinder import wsgi
|
from cinder.wsgi import common as wsgi
|
||||||
|
|
||||||
|
|
||||||
class Test(test.TestCase):
|
class Test(test.TestCase):
|
0
cinder/wsgi/__init__.py
Normal file
0
cinder/wsgi/__init__.py
Normal file
284
cinder/wsgi/common.py
Normal file
284
cinder/wsgi/common.py
Normal file
@ -0,0 +1,284 @@
|
|||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
"""Utility methods for working with WSGI servers."""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from oslo_config import cfg
|
||||||
|
from oslo_log import log as logging
|
||||||
|
from paste import deploy
|
||||||
|
import routes.middleware
|
||||||
|
import webob.dec
|
||||||
|
import webob.exc
|
||||||
|
|
||||||
|
from cinder import exception
|
||||||
|
from cinder.i18n import _, _LE
|
||||||
|
from cinder import utils
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class Request(webob.Request):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Application(object):
|
||||||
|
"""Base WSGI application wrapper. Subclasses need to implement __call__."""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def factory(cls, global_config, **local_config):
|
||||||
|
"""Used for paste app factories in paste.deploy config files.
|
||||||
|
|
||||||
|
Any local configuration (that is, values under the [app:APPNAME]
|
||||||
|
section of the paste config) will be passed into the `__init__` method
|
||||||
|
as kwargs.
|
||||||
|
|
||||||
|
A hypothetical configuration would look like:
|
||||||
|
|
||||||
|
[app:wadl]
|
||||||
|
latest_version = 1.3
|
||||||
|
paste.app_factory = cinder.api.fancy_api:Wadl.factory
|
||||||
|
|
||||||
|
which would result in a call to the `Wadl` class as
|
||||||
|
|
||||||
|
import cinder.api.fancy_api
|
||||||
|
fancy_api.Wadl(latest_version='1.3')
|
||||||
|
|
||||||
|
You could of course re-implement the `factory` method in subclasses,
|
||||||
|
but using the kwarg passing it shouldn't be necessary.
|
||||||
|
|
||||||
|
"""
|
||||||
|
return cls(**local_config)
|
||||||
|
|
||||||
|
def __call__(self, environ, start_response):
|
||||||
|
r"""Subclasses will probably want to implement __call__ like this:
|
||||||
|
|
||||||
|
@webob.dec.wsgify(RequestClass=Request)
|
||||||
|
def __call__(self, req):
|
||||||
|
# Any of the following objects work as responses:
|
||||||
|
|
||||||
|
# Option 1: simple string
|
||||||
|
res = 'message\n'
|
||||||
|
|
||||||
|
# Option 2: a nicely formatted HTTP exception page
|
||||||
|
res = exc.HTTPForbidden(explanation='Nice try')
|
||||||
|
|
||||||
|
# Option 3: a webob Response object (in case you need to play with
|
||||||
|
# headers, or you want to be treated like an iterable)
|
||||||
|
res = Response();
|
||||||
|
res.app_iter = open('somefile')
|
||||||
|
|
||||||
|
# Option 4: any wsgi app to be run next
|
||||||
|
res = self.application
|
||||||
|
|
||||||
|
# Option 5: you can get a Response object for a wsgi app, too, to
|
||||||
|
# play with headers etc
|
||||||
|
res = req.get_response(self.application)
|
||||||
|
|
||||||
|
# You can then just return your response...
|
||||||
|
return res
|
||||||
|
# ... or set req.response and return None.
|
||||||
|
req.response = res
|
||||||
|
|
||||||
|
See the end of http://pythonpaste.org/webob/modules/dec.html
|
||||||
|
for more info.
|
||||||
|
|
||||||
|
"""
|
||||||
|
raise NotImplementedError(_('You must implement __call__'))
|
||||||
|
|
||||||
|
|
||||||
|
class Middleware(Application):
|
||||||
|
"""Base WSGI middleware.
|
||||||
|
|
||||||
|
These classes require an application to be
|
||||||
|
initialized that will be called next. By default the middleware will
|
||||||
|
simply call its wrapped app, or you can override __call__ to customize its
|
||||||
|
behavior.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def factory(cls, global_config, **local_config):
|
||||||
|
"""Used for paste app factories in paste.deploy config files.
|
||||||
|
|
||||||
|
Any local configuration (that is, values under the [filter:APPNAME]
|
||||||
|
section of the paste config) will be passed into the `__init__` method
|
||||||
|
as kwargs.
|
||||||
|
|
||||||
|
A hypothetical configuration would look like:
|
||||||
|
|
||||||
|
[filter:analytics]
|
||||||
|
redis_host = 127.0.0.1
|
||||||
|
paste.filter_factory = cinder.api.analytics:Analytics.factory
|
||||||
|
|
||||||
|
which would result in a call to the `Analytics` class as
|
||||||
|
|
||||||
|
import cinder.api.analytics
|
||||||
|
analytics.Analytics(app_from_paste, redis_host='127.0.0.1')
|
||||||
|
|
||||||
|
You could of course re-implement the `factory` method in subclasses,
|
||||||
|
but using the kwarg passing it shouldn't be necessary.
|
||||||
|
|
||||||
|
"""
|
||||||
|
def _factory(app):
|
||||||
|
return cls(app, **local_config)
|
||||||
|
return _factory
|
||||||
|
|
||||||
|
def __init__(self, application):
|
||||||
|
self.application = application
|
||||||
|
|
||||||
|
def process_request(self, req):
|
||||||
|
"""Called on each request.
|
||||||
|
|
||||||
|
If this returns None, the next application down the stack will be
|
||||||
|
executed. If it returns a response then that response will be returned
|
||||||
|
and execution will stop here.
|
||||||
|
|
||||||
|
"""
|
||||||
|
return None
|
||||||
|
|
||||||
|
def process_response(self, response):
|
||||||
|
"""Do whatever you'd like to the response."""
|
||||||
|
return response
|
||||||
|
|
||||||
|
@webob.dec.wsgify(RequestClass=Request)
|
||||||
|
def __call__(self, req):
|
||||||
|
response = self.process_request(req)
|
||||||
|
if response:
|
||||||
|
return response
|
||||||
|
response = req.get_response(self.application)
|
||||||
|
return self.process_response(response)
|
||||||
|
|
||||||
|
|
||||||
|
class Debug(Middleware):
|
||||||
|
"""Helper class for debugging a WSGI application.
|
||||||
|
|
||||||
|
Can be inserted into any WSGI application chain to get information
|
||||||
|
about the request and response.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
@webob.dec.wsgify(RequestClass=Request)
|
||||||
|
def __call__(self, req):
|
||||||
|
print(('*' * 40) + ' REQUEST ENVIRON') # noqa
|
||||||
|
for key, value in req.environ.items():
|
||||||
|
print(key, '=', value) # noqa
|
||||||
|
print() # noqa
|
||||||
|
resp = req.get_response(self.application)
|
||||||
|
|
||||||
|
print(('*' * 40) + ' RESPONSE HEADERS') # noqa
|
||||||
|
for (key, value) in resp.headers.items():
|
||||||
|
print(key, '=', value) # noqa
|
||||||
|
print() # noqa
|
||||||
|
|
||||||
|
resp.app_iter = self.print_generator(resp.app_iter)
|
||||||
|
|
||||||
|
return resp
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def print_generator(app_iter):
|
||||||
|
"""Iterator that prints the contents of a wrapper string."""
|
||||||
|
print(('*' * 40) + ' BODY') # noqa
|
||||||
|
for part in app_iter:
|
||||||
|
sys.stdout.write(part)
|
||||||
|
sys.stdout.flush()
|
||||||
|
yield part
|
||||||
|
print() # noqa
|
||||||
|
|
||||||
|
|
||||||
|
class Router(object):
|
||||||
|
"""WSGI middleware that maps incoming requests to WSGI apps."""
|
||||||
|
|
||||||
|
def __init__(self, mapper):
|
||||||
|
"""Create a router for the given routes.Mapper.
|
||||||
|
|
||||||
|
Each route in `mapper` must specify a 'controller', which is a
|
||||||
|
WSGI app to call. You'll probably want to specify an 'action' as
|
||||||
|
well and have your controller be an object that can route
|
||||||
|
the request to the action-specific method.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
mapper = routes.Mapper()
|
||||||
|
sc = ServerController()
|
||||||
|
|
||||||
|
# Explicit mapping of one route to a controller+action
|
||||||
|
mapper.connect(None, '/svrlist', controller=sc, action='list')
|
||||||
|
|
||||||
|
# Actions are all implicitly defined
|
||||||
|
mapper.resource('server', 'servers', controller=sc)
|
||||||
|
|
||||||
|
# Pointing to an arbitrary WSGI app. You can specify the
|
||||||
|
# {path_info:.*} parameter so the target app can be handed just that
|
||||||
|
# section of the URL.
|
||||||
|
mapper.connect(None, '/v1.0/{path_info:.*}', controller=BlogApp())
|
||||||
|
|
||||||
|
"""
|
||||||
|
self.map = mapper
|
||||||
|
self._router = routes.middleware.RoutesMiddleware(self._dispatch,
|
||||||
|
self.map)
|
||||||
|
|
||||||
|
@webob.dec.wsgify(RequestClass=Request)
|
||||||
|
def __call__(self, req):
|
||||||
|
"""Route the incoming request to a controller based on self.map.
|
||||||
|
|
||||||
|
If no match, return a 404.
|
||||||
|
|
||||||
|
"""
|
||||||
|
return self._router
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
@webob.dec.wsgify(RequestClass=Request)
|
||||||
|
def _dispatch(req):
|
||||||
|
"""Dispatch the request to the appropriate controller.
|
||||||
|
|
||||||
|
Called by self._router after matching the incoming request to a route
|
||||||
|
and putting the information into req.environ. Either returns 404
|
||||||
|
or the routed WSGI app's response.
|
||||||
|
|
||||||
|
"""
|
||||||
|
match = req.environ['wsgiorg.routing_args'][1]
|
||||||
|
if not match:
|
||||||
|
return webob.exc.HTTPNotFound()
|
||||||
|
app = match['controller']
|
||||||
|
return app
|
||||||
|
|
||||||
|
|
||||||
|
class Loader(object):
|
||||||
|
"""Used to load WSGI applications from paste configurations."""
|
||||||
|
|
||||||
|
def __init__(self, config_path=None):
|
||||||
|
"""Initialize the loader, and attempt to find the config.
|
||||||
|
|
||||||
|
:param config_path: Full or relative path to the paste config.
|
||||||
|
:returns: None
|
||||||
|
|
||||||
|
"""
|
||||||
|
config_path = config_path or CONF.api_paste_config
|
||||||
|
self.config_path = utils.find_config(config_path)
|
||||||
|
|
||||||
|
def load_app(self, name):
|
||||||
|
"""Return the paste URLMap wrapped WSGI application.
|
||||||
|
|
||||||
|
:param name: Name of the application to load.
|
||||||
|
:returns: Paste URLMap object wrapping the requested application.
|
||||||
|
:raises: `cinder.exception.PasteAppNotFound`
|
||||||
|
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return deploy.loadapp("config:%s" % self.config_path, name=name)
|
||||||
|
except LookupError:
|
||||||
|
LOG.exception(_LE("Error loading app %s"), name)
|
||||||
|
raise exception.PasteAppNotFound(name=name, path=self.config_path)
|
@ -1,8 +1,3 @@
|
|||||||
# Copyright 2010 United States Government as represented by the
|
|
||||||
# Administrator of the National Aeronautics and Space Administration.
|
|
||||||
# Copyright 2010 OpenStack Foundation
|
|
||||||
# All Rights Reserved.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
# not use this file except in compliance with the License. You may obtain
|
# not use this file except in compliance with the License. You may obtain
|
||||||
# a copy of the License at
|
# a copy of the License at
|
||||||
@ -15,7 +10,7 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
"""Utility methods for working with WSGI servers."""
|
"""Methods for working with eventlet WSGI servers."""
|
||||||
|
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
|
||||||
@ -23,7 +18,6 @@ import errno
|
|||||||
import os
|
import os
|
||||||
import socket
|
import socket
|
||||||
import ssl
|
import ssl
|
||||||
import sys
|
|
||||||
import time
|
import time
|
||||||
|
|
||||||
import eventlet
|
import eventlet
|
||||||
@ -34,14 +28,10 @@ from oslo_log import log as logging
|
|||||||
from oslo_service import service
|
from oslo_service import service
|
||||||
from oslo_utils import excutils
|
from oslo_utils import excutils
|
||||||
from oslo_utils import netutils
|
from oslo_utils import netutils
|
||||||
from paste import deploy
|
|
||||||
import routes.middleware
|
|
||||||
import webob.dec
|
|
||||||
import webob.exc
|
|
||||||
|
|
||||||
from cinder import exception
|
from cinder import exception
|
||||||
from cinder.i18n import _, _LE, _LI
|
from cinder.i18n import _, _LE, _LI
|
||||||
from cinder import utils
|
|
||||||
|
|
||||||
|
|
||||||
socket_opts = [
|
socket_opts = [
|
||||||
@ -231,7 +221,7 @@ class Server(service.ServiceBase):
|
|||||||
**ssl_kwargs)
|
**ssl_kwargs)
|
||||||
except Exception:
|
except Exception:
|
||||||
with excutils.save_and_reraise_exception():
|
with excutils.save_and_reraise_exception():
|
||||||
LOG.error(_LE("Failed to start %(name)s on %(_host)s:"
|
LOG.error(_LE("Failed to start %(name)s on %(_host)s: "
|
||||||
"%(_port)s with SSL "
|
"%(_port)s with SSL "
|
||||||
"support."), self.__dict__)
|
"support."), self.__dict__)
|
||||||
|
|
||||||
@ -293,256 +283,3 @@ class Server(service.ServiceBase):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
self._pool.resize(self.pool_size)
|
self._pool.resize(self.pool_size)
|
||||||
|
|
||||||
|
|
||||||
class Request(webob.Request):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class Application(object):
|
|
||||||
"""Base WSGI application wrapper. Subclasses need to implement __call__."""
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def factory(cls, global_config, **local_config):
|
|
||||||
"""Used for paste app factories in paste.deploy config files.
|
|
||||||
|
|
||||||
Any local configuration (that is, values under the [app:APPNAME]
|
|
||||||
section of the paste config) will be passed into the `__init__` method
|
|
||||||
as kwargs.
|
|
||||||
|
|
||||||
A hypothetical configuration would look like:
|
|
||||||
|
|
||||||
[app:wadl]
|
|
||||||
latest_version = 1.3
|
|
||||||
paste.app_factory = cinder.api.fancy_api:Wadl.factory
|
|
||||||
|
|
||||||
which would result in a call to the `Wadl` class as
|
|
||||||
|
|
||||||
import cinder.api.fancy_api
|
|
||||||
fancy_api.Wadl(latest_version='1.3')
|
|
||||||
|
|
||||||
You could of course re-implement the `factory` method in subclasses,
|
|
||||||
but using the kwarg passing it shouldn't be necessary.
|
|
||||||
|
|
||||||
"""
|
|
||||||
return cls(**local_config)
|
|
||||||
|
|
||||||
def __call__(self, environ, start_response):
|
|
||||||
r"""Subclasses will probably want to implement __call__ like this:
|
|
||||||
|
|
||||||
@webob.dec.wsgify(RequestClass=Request)
|
|
||||||
def __call__(self, req):
|
|
||||||
# Any of the following objects work as responses:
|
|
||||||
|
|
||||||
# Option 1: simple string
|
|
||||||
res = 'message\n'
|
|
||||||
|
|
||||||
# Option 2: a nicely formatted HTTP exception page
|
|
||||||
res = exc.HTTPForbidden(explanation='Nice try')
|
|
||||||
|
|
||||||
# Option 3: a webob Response object (in case you need to play with
|
|
||||||
# headers, or you want to be treated like an iterable)
|
|
||||||
res = Response();
|
|
||||||
res.app_iter = open('somefile')
|
|
||||||
|
|
||||||
# Option 4: any wsgi app to be run next
|
|
||||||
res = self.application
|
|
||||||
|
|
||||||
# Option 5: you can get a Response object for a wsgi app, too, to
|
|
||||||
# play with headers etc
|
|
||||||
res = req.get_response(self.application)
|
|
||||||
|
|
||||||
# You can then just return your response...
|
|
||||||
return res
|
|
||||||
# ... or set req.response and return None.
|
|
||||||
req.response = res
|
|
||||||
|
|
||||||
See the end of http://pythonpaste.org/webob/modules/dec.html
|
|
||||||
for more info.
|
|
||||||
|
|
||||||
"""
|
|
||||||
raise NotImplementedError(_('You must implement __call__'))
|
|
||||||
|
|
||||||
|
|
||||||
class Middleware(Application):
|
|
||||||
"""Base WSGI middleware.
|
|
||||||
|
|
||||||
These classes require an application to be
|
|
||||||
initialized that will be called next. By default the middleware will
|
|
||||||
simply call its wrapped app, or you can override __call__ to customize its
|
|
||||||
behavior.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def factory(cls, global_config, **local_config):
|
|
||||||
"""Used for paste app factories in paste.deploy config files.
|
|
||||||
|
|
||||||
Any local configuration (that is, values under the [filter:APPNAME]
|
|
||||||
section of the paste config) will be passed into the `__init__` method
|
|
||||||
as kwargs.
|
|
||||||
|
|
||||||
A hypothetical configuration would look like:
|
|
||||||
|
|
||||||
[filter:analytics]
|
|
||||||
redis_host = 127.0.0.1
|
|
||||||
paste.filter_factory = cinder.api.analytics:Analytics.factory
|
|
||||||
|
|
||||||
which would result in a call to the `Analytics` class as
|
|
||||||
|
|
||||||
import cinder.api.analytics
|
|
||||||
analytics.Analytics(app_from_paste, redis_host='127.0.0.1')
|
|
||||||
|
|
||||||
You could of course re-implement the `factory` method in subclasses,
|
|
||||||
but using the kwarg passing it shouldn't be necessary.
|
|
||||||
|
|
||||||
"""
|
|
||||||
def _factory(app):
|
|
||||||
return cls(app, **local_config)
|
|
||||||
return _factory
|
|
||||||
|
|
||||||
def __init__(self, application):
|
|
||||||
self.application = application
|
|
||||||
|
|
||||||
def process_request(self, req):
|
|
||||||
"""Called on each request.
|
|
||||||
|
|
||||||
If this returns None, the next application down the stack will be
|
|
||||||
executed. If it returns a response then that response will be returned
|
|
||||||
and execution will stop here.
|
|
||||||
|
|
||||||
"""
|
|
||||||
return None
|
|
||||||
|
|
||||||
def process_response(self, response):
|
|
||||||
"""Do whatever you'd like to the response."""
|
|
||||||
return response
|
|
||||||
|
|
||||||
@webob.dec.wsgify(RequestClass=Request)
|
|
||||||
def __call__(self, req):
|
|
||||||
response = self.process_request(req)
|
|
||||||
if response:
|
|
||||||
return response
|
|
||||||
response = req.get_response(self.application)
|
|
||||||
return self.process_response(response)
|
|
||||||
|
|
||||||
|
|
||||||
class Debug(Middleware):
|
|
||||||
"""Helper class for debugging a WSGI application.
|
|
||||||
|
|
||||||
Can be inserted into any WSGI application chain to get information
|
|
||||||
about the request and response.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
@webob.dec.wsgify(RequestClass=Request)
|
|
||||||
def __call__(self, req):
|
|
||||||
print(('*' * 40) + ' REQUEST ENVIRON') # noqa
|
|
||||||
for key, value in req.environ.items():
|
|
||||||
print(key, '=', value) # noqa
|
|
||||||
print() # noqa
|
|
||||||
resp = req.get_response(self.application)
|
|
||||||
|
|
||||||
print(('*' * 40) + ' RESPONSE HEADERS') # noqa
|
|
||||||
for (key, value) in resp.headers.items():
|
|
||||||
print(key, '=', value) # noqa
|
|
||||||
print() # noqa
|
|
||||||
|
|
||||||
resp.app_iter = self.print_generator(resp.app_iter)
|
|
||||||
|
|
||||||
return resp
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def print_generator(app_iter):
|
|
||||||
"""Iterator that prints the contents of a wrapper string."""
|
|
||||||
print(('*' * 40) + ' BODY') # noqa
|
|
||||||
for part in app_iter:
|
|
||||||
sys.stdout.write(part)
|
|
||||||
sys.stdout.flush()
|
|
||||||
yield part
|
|
||||||
print() # noqa
|
|
||||||
|
|
||||||
|
|
||||||
class Router(object):
|
|
||||||
"""WSGI middleware that maps incoming requests to WSGI apps."""
|
|
||||||
|
|
||||||
def __init__(self, mapper):
|
|
||||||
"""Create a router for the given routes.Mapper.
|
|
||||||
|
|
||||||
Each route in `mapper` must specify a 'controller', which is a
|
|
||||||
WSGI app to call. You'll probably want to specify an 'action' as
|
|
||||||
well and have your controller be an object that can route
|
|
||||||
the request to the action-specific method.
|
|
||||||
|
|
||||||
Examples:
|
|
||||||
mapper = routes.Mapper()
|
|
||||||
sc = ServerController()
|
|
||||||
|
|
||||||
# Explicit mapping of one route to a controller+action
|
|
||||||
mapper.connect(None, '/svrlist', controller=sc, action='list')
|
|
||||||
|
|
||||||
# Actions are all implicitly defined
|
|
||||||
mapper.resource('server', 'servers', controller=sc)
|
|
||||||
|
|
||||||
# Pointing to an arbitrary WSGI app. You can specify the
|
|
||||||
# {path_info:.*} parameter so the target app can be handed just that
|
|
||||||
# section of the URL.
|
|
||||||
mapper.connect(None, '/v1.0/{path_info:.*}', controller=BlogApp())
|
|
||||||
|
|
||||||
"""
|
|
||||||
self.map = mapper
|
|
||||||
self._router = routes.middleware.RoutesMiddleware(self._dispatch,
|
|
||||||
self.map)
|
|
||||||
|
|
||||||
@webob.dec.wsgify(RequestClass=Request)
|
|
||||||
def __call__(self, req):
|
|
||||||
"""Route the incoming request to a controller based on self.map.
|
|
||||||
|
|
||||||
If no match, return a 404.
|
|
||||||
|
|
||||||
"""
|
|
||||||
return self._router
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
@webob.dec.wsgify(RequestClass=Request)
|
|
||||||
def _dispatch(req):
|
|
||||||
"""Dispatch the request to the appropriate controller.
|
|
||||||
|
|
||||||
Called by self._router after matching the incoming request to a route
|
|
||||||
and putting the information into req.environ. Either returns 404
|
|
||||||
or the routed WSGI app's response.
|
|
||||||
|
|
||||||
"""
|
|
||||||
match = req.environ['wsgiorg.routing_args'][1]
|
|
||||||
if not match:
|
|
||||||
return webob.exc.HTTPNotFound()
|
|
||||||
app = match['controller']
|
|
||||||
return app
|
|
||||||
|
|
||||||
|
|
||||||
class Loader(object):
|
|
||||||
"""Used to load WSGI applications from paste configurations."""
|
|
||||||
|
|
||||||
def __init__(self, config_path=None):
|
|
||||||
"""Initialize the loader, and attempt to find the config.
|
|
||||||
|
|
||||||
:param config_path: Full or relative path to the paste config.
|
|
||||||
:returns: None
|
|
||||||
|
|
||||||
"""
|
|
||||||
config_path = config_path or CONF.api_paste_config
|
|
||||||
self.config_path = utils.find_config(config_path)
|
|
||||||
|
|
||||||
def load_app(self, name):
|
|
||||||
"""Return the paste URLMap wrapped WSGI application.
|
|
||||||
|
|
||||||
:param name: Name of the application to load.
|
|
||||||
:returns: Paste URLMap object wrapping the requested application.
|
|
||||||
:raises: `cinder.exception.PasteAppNotFound`
|
|
||||||
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
return deploy.loadapp("config:%s" % self.config_path, name=name)
|
|
||||||
except LookupError:
|
|
||||||
LOG.exception(_LE("Error loading app %s"), name)
|
|
||||||
raise exception.PasteAppNotFound(name=name, path=self.config_path)
|
|
48
cinder/wsgi/wsgi.py
Normal file
48
cinder/wsgi/wsgi.py
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
"""Cinder OS API WSGI application."""
|
||||||
|
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
from cinder import objects
|
||||||
|
|
||||||
|
warnings.simplefilter('once', DeprecationWarning)
|
||||||
|
|
||||||
|
from oslo_config import cfg
|
||||||
|
from oslo_log import log as logging
|
||||||
|
|
||||||
|
from cinder import i18n
|
||||||
|
i18n.enable_lazy()
|
||||||
|
|
||||||
|
# Need to register global_opts
|
||||||
|
from cinder.common import config # noqa
|
||||||
|
from cinder import rpc
|
||||||
|
from cinder import version
|
||||||
|
from cinder.wsgi import common as wsgi_common
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
|
|
||||||
|
|
||||||
|
def _application():
|
||||||
|
objects.register_all()
|
||||||
|
CONF(sys.argv[1:], project='cinder',
|
||||||
|
version=version.version_string())
|
||||||
|
logging.setup(CONF, "cinder")
|
||||||
|
|
||||||
|
rpc.init(CONF)
|
||||||
|
return wsgi_common.Loader().load_app(name='osapi_volume')
|
||||||
|
|
||||||
|
|
||||||
|
application = _application()
|
16
etc/cinder/api-httpd.conf
Normal file
16
etc/cinder/api-httpd.conf
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
Listen 8776
|
||||||
|
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\" %D(us)" cinder_combined
|
||||||
|
|
||||||
|
<VirtualHost *:8776>
|
||||||
|
WSGIDaemonProcess osapi_volume processes=2 threads=1 user=cinder display-name=%{GROUP}
|
||||||
|
WSGIProcessGroup osapi_volume
|
||||||
|
WSGIScriptAlias / /var/www/cgi-bin/cinder/osapi_volume
|
||||||
|
WSGIApplicationGroup %{GLOBAL}
|
||||||
|
WSGIPassAuthorization On
|
||||||
|
<IfVersion >= 2.4>
|
||||||
|
ErrorLogFormat "%{cu}t %M"
|
||||||
|
</IfVersion>
|
||||||
|
ErrorLog /var/log/apache2/cinder_error.log
|
||||||
|
CustomLog /var/log/apache2/cinder.log cinder_combined
|
||||||
|
|
||||||
|
</VirtualHost>
|
Loading…
x
Reference in New Issue
Block a user