]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
Add Cinder API wsgi application
authorAnton Arefiev <aarefiev@mirantis.com>
Fri, 10 Jul 2015 13:44:59 +0000 (16:44 +0300)
committerAnton Arefiev <aarefiev@mirantis.com>
Tue, 25 Aug 2015 10:48:03 +0000 (13:48 +0300)
This change adds cinder API application, so cinder API can be run
under wsgi server(Apache, Nginx, etc). Eventlet will still be used
by default.

Cinder API with eventlet as a webserver and wsgi application
managing has some cons:

* Cinder API is deployed in other way as a common web application.
Apache/Nginx is generally used web servers for REST API application.

* Cinder API is run as a separate service. It means that cloud
operators need to configure some software to monitor that the API
is running.

* Apache/Nginx works better under the real heavy load than eventlet.

To use c-api deployment under Apache in devstack assign
devstack var CINDER_USE_MOD_WSGI="True".
Related changes in devstack:
 https://review.openstack.org/#/c/204643/

Cinder documentation on how deploy cinder API under Apache:
 https://review.openstack.org/#/c/207020/

Patch for Infra to make it tested:
 https://review.openstack.org/#/c/208498/

DocImpact
Implements: blueprint: non-eventlet-wsgi-app

Change-Id: If877d700b0efaa5406efa8f8f17c5816928e83ce

17 files changed:
cinder/api/middleware/auth.py
cinder/api/middleware/fault.py
cinder/api/openstack/__init__.py
cinder/api/openstack/wsgi.py
cinder/api/v1/limits.py
cinder/api/v2/limits.py
cinder/service.py
cinder/tests/unit/api/fakes.py
cinder/tests/unit/test_service.py
cinder/tests/unit/wsgi/__init__.py [new file with mode: 0644]
cinder/tests/unit/wsgi/test_eventlet_server.py [moved from cinder/tests/unit/test_wsgi.py with 89% similarity]
cinder/tests/unit/wsgi/test_wsgi.py [moved from cinder/tests/unit/api/test_wsgi.py with 98% similarity]
cinder/wsgi/__init__.py [new file with mode: 0644]
cinder/wsgi/common.py [new file with mode: 0644]
cinder/wsgi/eventlet_server.py [moved from cinder/wsgi.py with 54% similarity]
cinder/wsgi/wsgi.py [new file with mode: 0644]
etc/cinder/api-httpd.conf [new file with mode: 0644]

index 9789e1b146adf9ef87a3425a687fcdf30b18f376..110e728a21c4050b6994b29ab42af3f3f6997b72 100644 (file)
@@ -30,7 +30,7 @@ import webob.exc
 from cinder.api.openstack import wsgi
 from cinder import context
 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(
index 0575b46f108f7bbf1727a70f7943260695f70e47..ce56d5f903ab1aee84f2e11ac65198a95e06cf00 100644 (file)
@@ -23,7 +23,7 @@ from cinder.api.openstack import wsgi
 from cinder import exception
 from cinder.i18n import _, _LE, _LI
 from cinder import utils
-from cinder import wsgi as base_wsgi
+from cinder.wsgi import common as base_wsgi
 
 
 LOG = logging.getLogger(__name__)
index 3b8dc07c2d08f8dd8ce4322f38b89d2394821731..966a8d0a83293c1b2345484c709b983c4ce0f4d2 100644 (file)
@@ -23,7 +23,7 @@ import routes
 
 from cinder.api.openstack import wsgi
 from cinder.i18n import _, _LW
-from cinder import wsgi as base_wsgi
+from cinder.wsgi import common as base_wsgi
 
 
 LOG = logging.getLogger(__name__)
index d2502d4eb85f3091b4de8a95c29dc02a0f6a27ef..78fd20d062f0c362d4b4f6d5d72b023bb50f3927 100644 (file)
@@ -31,7 +31,7 @@ from cinder import exception
 from cinder import i18n
 from cinder.i18n import _, _LE, _LI
 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'
index 16f09a37d8c8aa6d3e7fda9093b167b6e66edbfb..b0124deaebf85090b5585b95500889195723ce46 100644 (file)
@@ -34,7 +34,7 @@ from cinder.api.views import limits as limits_views
 from cinder.api import xmlutil
 from cinder.i18n import _
 from cinder import quota
-from cinder import wsgi as base_wsgi
+from cinder.wsgi import common as base_wsgi
 
 QUOTAS = quota.QUOTAS
 LIMITS_PREFIX = "limits."
index b6c21b4da45a9d6431dadf75faaed2336a81282b..0b6fb61397e7b83a1c6069ac259071fcbcbb167b 100644 (file)
@@ -34,7 +34,7 @@ from cinder.api.views import limits as limits_views
 from cinder.api import xmlutil
 from cinder.i18n import _
 from cinder import quota
-from cinder import wsgi as base_wsgi
+from cinder.wsgi import common as base_wsgi
 
 QUOTAS = quota.QUOTAS
 LIMITS_PREFIX = "limits."
index 01f929edee903460ef11b5be18666a84757a0a36..5be0fc1c3dc2c37fe945c161942e62672e72c703 100644 (file)
@@ -41,8 +41,8 @@ from cinder.i18n import _, _LE, _LI, _LW
 from cinder.objects import base as objects_base
 from cinder import rpc
 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__)
 
@@ -355,7 +355,7 @@ class WSGIService(service.ServiceBase):
         """
         self.name = name
         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.host = getattr(CONF, '%s_listen' % name, "0.0.0.0")
         self.port = getattr(CONF, '%s_listen_port' % name, 0)
index a87a0ed827e0316e6392135d0c10ca9da9cbeea5..e567dbc3a963914173d70f2c301c1090b973078e 100644 (file)
@@ -29,7 +29,7 @@ from cinder.api.v2 import limits
 from cinder.api.v2 import router
 from cinder.api import versions
 from cinder import context
-from cinder import wsgi
+from cinder.wsgi import common as wsgi
 
 
 FAKE_UUID = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'
index 2d54e7245b0120c09e8979e66682eaf20d496ebc..2c95d65e558c87c6d0ddcdb4a2f5561e99f50a77 100644 (file)
@@ -31,7 +31,7 @@ from cinder import manager
 from cinder import rpc
 from cinder import service
 from cinder import test
-from cinder import wsgi
+from cinder.wsgi import common as wsgi
 
 
 test_service_opts = [
@@ -275,25 +275,25 @@ class TestWSGIService(test.TestCase):
             self.assertEqual(1000, test_service.server._pool.size)
             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):
         test_service = service.WSGIService("osapi_volume")
         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):
         self.override_config('osapi_volume_workers', 8)
         test_service = service.WSGIService("osapi_volume")
         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):
         self.override_config('osapi_volume_workers', 0)
         test_service = service.WSGIService("osapi_volume")
         # If a value less than 1 is used, defaults to number of procs available
         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):
         self.override_config('osapi_volume_workers', -1)
         self.assertRaises(exception.InvalidInput,
diff --git a/cinder/tests/unit/wsgi/__init__.py b/cinder/tests/unit/wsgi/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
similarity index 89%
rename from cinder/tests/unit/test_wsgi.py
rename to cinder/tests/unit/wsgi/test_eventlet_server.py
index e60964ab40cc91afc32bc43b4392a711e2ae3cb4..c014deb27375bf0e20ecfdcf07cdebd22ae92773 100644 (file)
@@ -34,12 +34,13 @@ import webob.dec
 from cinder import exception
 from cinder.i18n import _
 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
 
 TEST_VAR_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__),
-                               'var'))
+                               '../var'))
 
 
 def open_no_proxy(*args, **kwargs):
@@ -67,8 +68,8 @@ class TestLoaderNothingExists(test.TestCase):
 
     def test_config_not_found(self):
         self.assertRaises(
-            cinder.exception.ConfigNotFound,
-            cinder.wsgi.Loader,
+            exception.ConfigNotFound,
+            wsgi_common.Loader,
         )
 
 
@@ -87,7 +88,7 @@ document_root = /tmp
         self.config.write(self._paste_config.lstrip())
         self.config.seek(0)
         self.config.flush()
-        self.loader = cinder.wsgi.Loader(self.config.name)
+        self.loader = wsgi_common.Loader(self.config.name)
         self.addCleanup(self.config.close)
 
     def test_config_found(self):
@@ -95,7 +96,7 @@ document_root = /tmp
 
     def test_app_not_found(self):
         self.assertRaises(
-            cinder.exception.PasteAppNotFound,
+            exception.PasteAppNotFound,
             self.loader.load_app,
             "non-existent app",
         )
@@ -115,12 +116,12 @@ class TestWSGIServer(test.TestCase):
             return False
 
     def test_no_app(self):
-        server = cinder.wsgi.Server("test_app", None,
-                                    host="127.0.0.1", port=0)
+        server = wsgi.Server("test_app", None,
+                             host="127.0.0.1", port=0)
         self.assertEqual("test_app", server.name)
 
     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()
         self.assertNotEqual(0, server.port)
         server.stop()
@@ -129,9 +130,9 @@ class TestWSGIServer(test.TestCase):
     @testtools.skipIf(not _ipv6_configured(),
                       "Test requires an IPV6 configured interface")
     def test_start_random_port_with_ipv6(self):
-        server = cinder.wsgi.Server("test_random_port",
-                                    None,
-                                    host="::1")
+        server = wsgi.Server("test_random_port",
+                             None,
+                             host="::1")
         server.start()
         self.assertEqual("::1", server.host)
         self.assertNotEqual(0, server.port)
@@ -140,8 +141,8 @@ class TestWSGIServer(test.TestCase):
 
     def test_server_pool_waitall(self):
         # test pools waitall method gets called while stopping server
-        server = cinder.wsgi.Server("test_server", None,
-                                    host="127.0.0.1")
+        server = wsgi.Server("test_server", None,
+                             host="127.0.0.1")
         server.start()
         with mock.patch.object(server._pool,
                                'waitall') as mock_waitall:
@@ -160,8 +161,8 @@ class TestWSGIServer(test.TestCase):
             start_response('200 OK', [('Content-Type', 'text/plain')])
             return [greetings]
 
-        server = cinder.wsgi.Server("test_app", hello_world,
-                                    host="127.0.0.1", port=0)
+        server = wsgi.Server("test_app", hello_world,
+                             host="127.0.0.1", port=0)
         server.start()
 
         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')])
             return [greetings]
 
-        server = cinder.wsgi.Server("test_app", hello_world,
-                                    host="127.0.0.1", port=0)
+        server = wsgi.Server("test_app", hello_world,
+                             host="127.0.0.1", port=0)
         server.start()
 
         s = socket.socket()
@@ -215,8 +216,8 @@ class TestWSGIServer(test.TestCase):
         def hello_world(req):
             return greetings
 
-        server = cinder.wsgi.Server("test_app", hello_world,
-                                    host="127.0.0.1", port=0)
+        server = wsgi.Server("test_app", hello_world,
+                             host="127.0.0.1", port=0)
 
         server.start()
 
@@ -239,10 +240,10 @@ class TestWSGIServer(test.TestCase):
         def hello_world(req):
             return greetings
 
-        server = cinder.wsgi.Server("test_app",
-                                    hello_world,
-                                    host="::1",
-                                    port=0)
+        server = wsgi.Server("test_app",
+                             hello_world,
+                             host="::1",
+                             port=0)
         server.start()
 
         response = open_no_proxy('https://[::1]:%d/' % server.port)
@@ -251,7 +252,7 @@ class TestWSGIServer(test.TestCase):
         server.stop()
 
     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()
 
         # Stopping the server, which in turn sets pool size to 0
similarity index 98%
rename from cinder/tests/unit/api/test_wsgi.py
rename to cinder/tests/unit/wsgi/test_wsgi.py
index 2eafc544d434783b9dea9793ac849b2812fe0170..63f0550165a5a4fc86374aa71afefe91a84f85f6 100644 (file)
@@ -27,7 +27,7 @@ from cinder import test
 import routes
 import webob
 
-from cinder import wsgi
+from cinder.wsgi import common as wsgi
 
 
 class Test(test.TestCase):
diff --git a/cinder/wsgi/__init__.py b/cinder/wsgi/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/cinder/wsgi/common.py b/cinder/wsgi/common.py
new file mode 100644 (file)
index 0000000..2fd4e6a
--- /dev/null
@@ -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)
similarity index 54%
rename from cinder/wsgi.py
rename to cinder/wsgi/eventlet_server.py
index 6b85122771443a6639cf0806cf3f09ef06308620..f40595680781cf60ac7a864578460657ff07b2e7 100644 (file)
@@ -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
 #    not use this file except in compliance with the License. You may obtain
 #    a copy of the License at
@@ -15,7 +10,7 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-"""Utility methods for working with WSGI servers."""
+"""Methods for working with eventlet WSGI servers."""
 
 from __future__ import print_function
 
@@ -23,7 +18,6 @@ import errno
 import os
 import socket
 import ssl
-import sys
 import time
 
 import eventlet
@@ -34,14 +28,10 @@ from oslo_log import log as logging
 from oslo_service import service
 from oslo_utils import excutils
 from oslo_utils import netutils
-from paste import deploy
-import routes.middleware
-import webob.dec
-import webob.exc
+
 
 from cinder import exception
 from cinder.i18n import _, _LE, _LI
-from cinder import utils
 
 
 socket_opts = [
@@ -231,7 +221,7 @@ class Server(service.ServiceBase):
                                              **ssl_kwargs)
             except 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 "
                                   "support."), self.__dict__)
 
@@ -293,256 +283,3 @@ class Server(service.ServiceBase):
 
         """
         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)
diff --git a/cinder/wsgi/wsgi.py b/cinder/wsgi/wsgi.py
new file mode 100644 (file)
index 0000000..d51bec4
--- /dev/null
@@ -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()
diff --git a/etc/cinder/api-httpd.conf b/etc/cinder/api-httpd.conf
new file mode 100644 (file)
index 0000000..f355547
--- /dev/null
@@ -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>