Merge "Refactor WSGI apps and utils to limit imports"
This commit is contained in:
		@@ -22,10 +22,10 @@ from oslo_serialization import jsonutils
 | 
				
			|||||||
import webob.dec
 | 
					import webob.dec
 | 
				
			||||||
import webob.exc
 | 
					import webob.exc
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from nova.api import wsgi
 | 
				
			||||||
import nova.conf
 | 
					import nova.conf
 | 
				
			||||||
from nova import context
 | 
					from nova import context
 | 
				
			||||||
from nova.i18n import _
 | 
					from nova.i18n import _
 | 
				
			||||||
from nova import wsgi
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
CONF = nova.conf.CONF
 | 
					CONF = nova.conf.CONF
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -27,13 +27,13 @@ import webob.dec
 | 
				
			|||||||
import webob.exc
 | 
					import webob.exc
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from nova.api.metadata import base
 | 
					from nova.api.metadata import base
 | 
				
			||||||
 | 
					from nova.api import wsgi
 | 
				
			||||||
from nova import cache_utils
 | 
					from nova import cache_utils
 | 
				
			||||||
import nova.conf
 | 
					import nova.conf
 | 
				
			||||||
from nova import context as nova_context
 | 
					from nova import context as nova_context
 | 
				
			||||||
from nova import exception
 | 
					from nova import exception
 | 
				
			||||||
from nova.i18n import _
 | 
					from nova.i18n import _
 | 
				
			||||||
from nova.network.neutronv2 import api as neutronapi
 | 
					from nova.network.neutronv2 import api as neutronapi
 | 
				
			||||||
from nova import wsgi
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
CONF = nova.conf.CONF
 | 
					CONF = nova.conf.CONF
 | 
				
			||||||
LOG = logging.getLogger(__name__)
 | 
					LOG = logging.getLogger(__name__)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -24,16 +24,28 @@ import webob.dec
 | 
				
			|||||||
import webob.exc
 | 
					import webob.exc
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from nova.api.openstack import wsgi
 | 
					from nova.api.openstack import wsgi
 | 
				
			||||||
 | 
					from nova.api import wsgi as base_wsgi
 | 
				
			||||||
import nova.conf
 | 
					import nova.conf
 | 
				
			||||||
from nova.i18n import translate
 | 
					from nova.i18n import translate
 | 
				
			||||||
from nova import utils
 | 
					 | 
				
			||||||
from nova import wsgi as base_wsgi
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
LOG = logging.getLogger(__name__)
 | 
					LOG = logging.getLogger(__name__)
 | 
				
			||||||
CONF = nova.conf.CONF
 | 
					CONF = nova.conf.CONF
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def walk_class_hierarchy(clazz, encountered=None):
 | 
				
			||||||
 | 
					    """Walk class hierarchy, yielding most derived classes first."""
 | 
				
			||||||
 | 
					    if not encountered:
 | 
				
			||||||
 | 
					        encountered = []
 | 
				
			||||||
 | 
					    for subclass in clazz.__subclasses__():
 | 
				
			||||||
 | 
					        if subclass not in encountered:
 | 
				
			||||||
 | 
					            encountered.append(subclass)
 | 
				
			||||||
 | 
					            # drill down to leaves first
 | 
				
			||||||
 | 
					            for subsubclass in walk_class_hierarchy(subclass, encountered):
 | 
				
			||||||
 | 
					                yield subsubclass
 | 
				
			||||||
 | 
					            yield subclass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class FaultWrapper(base_wsgi.Middleware):
 | 
					class FaultWrapper(base_wsgi.Middleware):
 | 
				
			||||||
    """Calls down the middleware stack, making exceptions into faults."""
 | 
					    """Calls down the middleware stack, making exceptions into faults."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -42,7 +54,7 @@ class FaultWrapper(base_wsgi.Middleware):
 | 
				
			|||||||
    @staticmethod
 | 
					    @staticmethod
 | 
				
			||||||
    def status_to_type(status):
 | 
					    def status_to_type(status):
 | 
				
			||||||
        if not FaultWrapper._status_to_type:
 | 
					        if not FaultWrapper._status_to_type:
 | 
				
			||||||
            for clazz in utils.walk_class_hierarchy(webob.exc.HTTPError):
 | 
					            for clazz in walk_class_hierarchy(webob.exc.HTTPError):
 | 
				
			||||||
                FaultWrapper._status_to_type[clazz.code] = clazz
 | 
					                FaultWrapper._status_to_type[clazz.code] = clazz
 | 
				
			||||||
        return FaultWrapper._status_to_type.get(
 | 
					        return FaultWrapper._status_to_type.get(
 | 
				
			||||||
                                  status, webob.exc.HTTPInternalServerError)()
 | 
					                                  status, webob.exc.HTTPInternalServerError)()
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -18,9 +18,9 @@ import webob.dec
 | 
				
			|||||||
import webob.exc
 | 
					import webob.exc
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from nova.api.openstack import wsgi
 | 
					from nova.api.openstack import wsgi
 | 
				
			||||||
 | 
					from nova.api import wsgi as base_wsgi
 | 
				
			||||||
import nova.conf
 | 
					import nova.conf
 | 
				
			||||||
from nova import context
 | 
					from nova import context
 | 
				
			||||||
from nova import wsgi as base_wsgi
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
CONF = nova.conf.CONF
 | 
					CONF = nova.conf.CONF
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -93,8 +93,8 @@ from nova.api.openstack.compute import versionsV21
 | 
				
			|||||||
from nova.api.openstack.compute import virtual_interfaces
 | 
					from nova.api.openstack.compute import virtual_interfaces
 | 
				
			||||||
from nova.api.openstack.compute import volumes
 | 
					from nova.api.openstack.compute import volumes
 | 
				
			||||||
from nova.api.openstack import wsgi
 | 
					from nova.api.openstack import wsgi
 | 
				
			||||||
 | 
					from nova.api import wsgi as base_wsgi
 | 
				
			||||||
import nova.conf
 | 
					import nova.conf
 | 
				
			||||||
from nova import wsgi as base_wsgi
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
CONF = nova.conf.CONF
 | 
					CONF = nova.conf.CONF
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -20,7 +20,7 @@ import webob.dec
 | 
				
			|||||||
import webob.exc
 | 
					import webob.exc
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from nova.api.openstack import wsgi
 | 
					from nova.api.openstack import wsgi
 | 
				
			||||||
from nova import wsgi as base_wsgi
 | 
					from nova.api import wsgi as base_wsgi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# TODO(sdague) maybe we can use a better name here for the logger
 | 
					# TODO(sdague) maybe we can use a better name here for the logger
 | 
				
			||||||
LOG = logging.getLogger(__name__)
 | 
					LOG = logging.getLogger(__name__)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -26,11 +26,10 @@ import webob
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
from nova.api.openstack import api_version_request as api_version
 | 
					from nova.api.openstack import api_version_request as api_version
 | 
				
			||||||
from nova.api.openstack import versioned_method
 | 
					from nova.api.openstack import versioned_method
 | 
				
			||||||
 | 
					from nova.api import wsgi
 | 
				
			||||||
from nova import exception
 | 
					from nova import exception
 | 
				
			||||||
from nova import i18n
 | 
					from nova import i18n
 | 
				
			||||||
from nova.i18n import _
 | 
					from nova.i18n import _
 | 
				
			||||||
from nova import utils
 | 
					 | 
				
			||||||
from nova import wsgi
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
LOG = logging.getLogger(__name__)
 | 
					LOG = logging.getLogger(__name__)
 | 
				
			||||||
@@ -334,16 +333,28 @@ class ResponseObject(object):
 | 
				
			|||||||
        if self.obj is not None:
 | 
					        if self.obj is not None:
 | 
				
			||||||
            body = serializer.serialize(self.obj)
 | 
					            body = serializer.serialize(self.obj)
 | 
				
			||||||
        response = webob.Response(body=body)
 | 
					        response = webob.Response(body=body)
 | 
				
			||||||
        if response.headers.get('Content-Length'):
 | 
					 | 
				
			||||||
            # NOTE(andreykurilin): we need to encode 'Content-Length' header,
 | 
					 | 
				
			||||||
            # since webob.Response auto sets it if "body" attr is presented.
 | 
					 | 
				
			||||||
            # https://github.com/Pylons/webob/blob/1.5.0b0/webob/response.py#L147
 | 
					 | 
				
			||||||
            response.headers['Content-Length'] = utils.utf8(
 | 
					 | 
				
			||||||
                response.headers['Content-Length'])
 | 
					 | 
				
			||||||
        response.status_int = self.code
 | 
					        response.status_int = self.code
 | 
				
			||||||
        for hdr, value in self._headers.items():
 | 
					        for hdr, val in self._headers.items():
 | 
				
			||||||
            response.headers[hdr] = utils.utf8(value)
 | 
					            if not isinstance(val, six.text_type):
 | 
				
			||||||
        response.headers['Content-Type'] = utils.utf8(content_type)
 | 
					                val = six.text_type(val)
 | 
				
			||||||
 | 
					            if six.PY2:
 | 
				
			||||||
 | 
					                # In Py2.X Headers must be byte strings
 | 
				
			||||||
 | 
					                response.headers[hdr] = encodeutils.safe_encode(val)
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                # In Py3.X Headers must be utf-8 strings
 | 
				
			||||||
 | 
					                response.headers[hdr] = encodeutils.safe_decode(
 | 
				
			||||||
 | 
					                        encodeutils.safe_encode(val))
 | 
				
			||||||
 | 
					        # Deal with content_type
 | 
				
			||||||
 | 
					        if not isinstance(content_type, six.text_type):
 | 
				
			||||||
 | 
					            content_type = six.text_type(content_type)
 | 
				
			||||||
 | 
					        if six.PY2:
 | 
				
			||||||
 | 
					            # In Py2.X Headers must be byte strings
 | 
				
			||||||
 | 
					            response.headers['Content-Type'] = encodeutils.safe_encode(
 | 
				
			||||||
 | 
					                content_type)
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            # In Py3.X Headers must be utf-8 strings
 | 
				
			||||||
 | 
					            response.headers['Content-Type'] = encodeutils.safe_decode(
 | 
				
			||||||
 | 
					                    encodeutils.safe_encode(content_type))
 | 
				
			||||||
        return response
 | 
					        return response
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @property
 | 
					    @property
 | 
				
			||||||
@@ -658,13 +669,15 @@ class Resource(wsgi.Application):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        if hasattr(response, 'headers'):
 | 
					        if hasattr(response, 'headers'):
 | 
				
			||||||
            for hdr, val in list(response.headers.items()):
 | 
					            for hdr, val in list(response.headers.items()):
 | 
				
			||||||
 | 
					                if not isinstance(val, six.text_type):
 | 
				
			||||||
 | 
					                    val = six.text_type(val)
 | 
				
			||||||
                if six.PY2:
 | 
					                if six.PY2:
 | 
				
			||||||
                    # In Py2.X Headers must be byte strings
 | 
					                    # In Py2.X Headers must be byte strings
 | 
				
			||||||
                    response.headers[hdr] = utils.utf8(val)
 | 
					                    response.headers[hdr] = encodeutils.safe_encode(val)
 | 
				
			||||||
                else:
 | 
					                else:
 | 
				
			||||||
                    # In Py3.X Headers must be utf-8 strings
 | 
					                    # In Py3.X Headers must be utf-8 strings
 | 
				
			||||||
                    response.headers[hdr] = encodeutils.safe_decode(
 | 
					                    response.headers[hdr] = encodeutils.safe_decode(
 | 
				
			||||||
                            utils.utf8(val))
 | 
					                            encodeutils.safe_encode(val))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if not request.api_version_request.is_null():
 | 
					            if not request.api_version_request.is_null():
 | 
				
			||||||
                response.headers[API_VERSION_REQUEST_HEADER] = \
 | 
					                response.headers[API_VERSION_REQUEST_HEADER] = \
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										298
									
								
								nova/api/wsgi.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										298
									
								
								nova/api/wsgi.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,298 @@
 | 
				
			|||||||
 | 
					#    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.
 | 
				
			||||||
 | 
					"""WSGI primitives used throughout the nova WSGI apps."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import os
 | 
				
			||||||
 | 
					import sys
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from oslo_log import log as logging
 | 
				
			||||||
 | 
					from paste import deploy
 | 
				
			||||||
 | 
					import routes.middleware
 | 
				
			||||||
 | 
					import six
 | 
				
			||||||
 | 
					import webob
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import nova.conf
 | 
				
			||||||
 | 
					from nova import exception
 | 
				
			||||||
 | 
					from nova.i18n import _, _LE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CONF = nova.conf.CONF
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					LOG = logging.getLogger(__name__)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Request(webob.Request):
 | 
				
			||||||
 | 
					    def __init__(self, environ, *args, **kwargs):
 | 
				
			||||||
 | 
					        if CONF.wsgi.secure_proxy_ssl_header:
 | 
				
			||||||
 | 
					            scheme = environ.get(CONF.wsgi.secure_proxy_ssl_header)
 | 
				
			||||||
 | 
					            if scheme:
 | 
				
			||||||
 | 
					                environ['wsgi.url_scheme'] = scheme
 | 
				
			||||||
 | 
					        super(Request, self).__init__(environ, *args, **kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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 = nova.api.fancy_api:Wadl.factory
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        which would result in a call to the `Wadl` class as
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            import nova.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, or ...)
 | 
				
			||||||
 | 
					          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 = nova.api.analytics:Analytics.factory
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        which would result in a call to the `Analytics` class as
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            import nova.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')
 | 
				
			||||||
 | 
					        for key, value in req.environ.items():
 | 
				
			||||||
 | 
					            print(key, '=', value)
 | 
				
			||||||
 | 
					        print()
 | 
				
			||||||
 | 
					        resp = req.get_response(self.application)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        print(('*' * 40) + ' RESPONSE HEADERS')
 | 
				
			||||||
 | 
					        for (key, value) in resp.headers.items():
 | 
				
			||||||
 | 
					            print(key, '=', value)
 | 
				
			||||||
 | 
					        print()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        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')
 | 
				
			||||||
 | 
					        for part in app_iter:
 | 
				
			||||||
 | 
					            sys.stdout.write(six.text_type(part))
 | 
				
			||||||
 | 
					            sys.stdout.flush()
 | 
				
			||||||
 | 
					            yield part
 | 
				
			||||||
 | 
					        print()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        self.config_path = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        config_path = config_path or CONF.wsgi.api_paste_config
 | 
				
			||||||
 | 
					        if not os.path.isabs(config_path):
 | 
				
			||||||
 | 
					            self.config_path = CONF.find_file(config_path)
 | 
				
			||||||
 | 
					        elif os.path.exists(config_path):
 | 
				
			||||||
 | 
					            self.config_path = config_path
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if not self.config_path:
 | 
				
			||||||
 | 
					            raise exception.ConfigNotFound(path=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: `nova.exception.PasteAppNotFound`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            LOG.debug("Loading app %(name)s from %(path)s",
 | 
				
			||||||
 | 
					                      {'name': name, 'path': self.config_path})
 | 
				
			||||||
 | 
					            return deploy.loadapp("config:%s" % self.config_path, name=name)
 | 
				
			||||||
 | 
					        except LookupError:
 | 
				
			||||||
 | 
					            LOG.exception(_LE("Couldn't lookup app: %s"), name)
 | 
				
			||||||
 | 
					            raise exception.PasteAppNotFound(name=name, path=self.config_path)
 | 
				
			||||||
@@ -27,6 +27,7 @@ import oslo_messaging as messaging
 | 
				
			|||||||
from oslo_service import service
 | 
					from oslo_service import service
 | 
				
			||||||
from oslo_utils import importutils
 | 
					from oslo_utils import importutils
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from nova.api import wsgi as api_wsgi
 | 
				
			||||||
from nova import baserpc
 | 
					from nova import baserpc
 | 
				
			||||||
from nova import conductor
 | 
					from nova import conductor
 | 
				
			||||||
import nova.conf
 | 
					import nova.conf
 | 
				
			||||||
@@ -326,7 +327,7 @@ class WSGIService(service.Service):
 | 
				
			|||||||
        self.binary = 'nova-%s' % name
 | 
					        self.binary = 'nova-%s' % name
 | 
				
			||||||
        self.topic = None
 | 
					        self.topic = None
 | 
				
			||||||
        self.manager = self._get_manager()
 | 
					        self.manager = self._get_manager()
 | 
				
			||||||
        self.loader = loader or wsgi.Loader()
 | 
					        self.loader = loader or api_wsgi.Loader()
 | 
				
			||||||
        self.app = self.loader.load_app(name)
 | 
					        self.app = self.loader.load_app(name)
 | 
				
			||||||
        # inherit all compute_api worker counts from osapi_compute
 | 
					        # inherit all compute_api worker counts from osapi_compute
 | 
				
			||||||
        if name.startswith('openstack_compute_api'):
 | 
					        if name.startswith('openstack_compute_api'):
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -39,6 +39,7 @@ from wsgi_intercept import interceptor
 | 
				
			|||||||
from nova.api.openstack.compute import tenant_networks
 | 
					from nova.api.openstack.compute import tenant_networks
 | 
				
			||||||
from nova.api.openstack.placement import deploy as placement_deploy
 | 
					from nova.api.openstack.placement import deploy as placement_deploy
 | 
				
			||||||
from nova.api.openstack import wsgi_app
 | 
					from nova.api.openstack import wsgi_app
 | 
				
			||||||
 | 
					from nova.api import wsgi
 | 
				
			||||||
from nova.compute import rpcapi as compute_rpcapi
 | 
					from nova.compute import rpcapi as compute_rpcapi
 | 
				
			||||||
from nova import context
 | 
					from nova import context
 | 
				
			||||||
from nova.db import migration
 | 
					from nova.db import migration
 | 
				
			||||||
@@ -53,7 +54,6 @@ from nova import rpc
 | 
				
			|||||||
from nova import service
 | 
					from nova import service
 | 
				
			||||||
from nova.tests.functional.api import client
 | 
					from nova.tests.functional.api import client
 | 
				
			||||||
from nova.tests import uuidsentinel
 | 
					from nova.tests import uuidsentinel
 | 
				
			||||||
from nova import wsgi
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
_TRUE_VALUES = ('True', 'true', '1', 'yes')
 | 
					_TRUE_VALUES = ('True', 'true', '1', 'yes')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -19,11 +19,11 @@ from oslo_serialization import jsonutils
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
from nova.api.openstack import api_version_request as avr
 | 
					from nova.api.openstack import api_version_request as avr
 | 
				
			||||||
from nova.api.openstack.compute import views
 | 
					from nova.api.openstack.compute import views
 | 
				
			||||||
 | 
					from nova.api import wsgi
 | 
				
			||||||
from nova import test
 | 
					from nova import test
 | 
				
			||||||
from nova.tests.unit.api.openstack import fakes
 | 
					from nova.tests.unit.api.openstack import fakes
 | 
				
			||||||
from nova.tests.unit import matchers
 | 
					from nova.tests.unit import matchers
 | 
				
			||||||
from nova.tests import uuidsentinel as uuids
 | 
					from nova.tests import uuidsentinel as uuids
 | 
				
			||||||
from nova import wsgi
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
NS = {
 | 
					NS = {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -30,6 +30,7 @@ from nova.api.openstack import compute
 | 
				
			|||||||
from nova.api.openstack.compute import versions
 | 
					from nova.api.openstack.compute import versions
 | 
				
			||||||
from nova.api.openstack import urlmap
 | 
					from nova.api.openstack import urlmap
 | 
				
			||||||
from nova.api.openstack import wsgi as os_wsgi
 | 
					from nova.api.openstack import wsgi as os_wsgi
 | 
				
			||||||
 | 
					from nova.api import wsgi
 | 
				
			||||||
from nova.compute import flavors
 | 
					from nova.compute import flavors
 | 
				
			||||||
from nova.compute import vm_states
 | 
					from nova.compute import vm_states
 | 
				
			||||||
import nova.conf
 | 
					import nova.conf
 | 
				
			||||||
@@ -44,7 +45,6 @@ from nova.tests.unit import fake_block_device
 | 
				
			|||||||
from nova.tests.unit import fake_network
 | 
					from nova.tests.unit import fake_network
 | 
				
			||||||
from nova.tests.unit.objects import test_keypair
 | 
					from nova.tests.unit.objects import test_keypair
 | 
				
			||||||
from nova import utils
 | 
					from nova import utils
 | 
				
			||||||
from nova import wsgi
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
CONF = nova.conf.CONF
 | 
					CONF = nova.conf.CONF
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -24,8 +24,8 @@ import routes
 | 
				
			|||||||
from six.moves import StringIO
 | 
					from six.moves import StringIO
 | 
				
			||||||
import webob
 | 
					import webob
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from nova.api import wsgi
 | 
				
			||||||
from nova import test
 | 
					from nova import test
 | 
				
			||||||
from nova import wsgi
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Test(test.NoDBTestCase):
 | 
					class Test(test.NoDBTestCase):
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -263,7 +263,7 @@ class TestWSGIService(test.NoDBTestCase):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    def setUp(self):
 | 
					    def setUp(self):
 | 
				
			||||||
        super(TestWSGIService, self).setUp()
 | 
					        super(TestWSGIService, self).setUp()
 | 
				
			||||||
        self.stub_out('nova.wsgi.Loader.load_app',
 | 
					        self.stub_out('nova.api.wsgi.Loader.load_app',
 | 
				
			||||||
                      lambda *a, **kw: mock.MagicMock())
 | 
					                      lambda *a, **kw: mock.MagicMock())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @mock.patch('nova.objects.Service.get_by_host_and_binary')
 | 
					    @mock.patch('nova.objects.Service.get_by_host_and_binary')
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -29,6 +29,7 @@ import six
 | 
				
			|||||||
import testtools
 | 
					import testtools
 | 
				
			||||||
import webob
 | 
					import webob
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import nova.api.wsgi
 | 
				
			||||||
import nova.exception
 | 
					import nova.exception
 | 
				
			||||||
from nova import test
 | 
					from nova import test
 | 
				
			||||||
from nova.tests.unit import utils
 | 
					from nova.tests.unit import utils
 | 
				
			||||||
@@ -51,14 +52,14 @@ class TestLoaderNothingExists(test.NoDBTestCase):
 | 
				
			|||||||
        self.flags(api_paste_config='api-paste.ini', group='wsgi')
 | 
					        self.flags(api_paste_config='api-paste.ini', group='wsgi')
 | 
				
			||||||
        self.assertRaises(
 | 
					        self.assertRaises(
 | 
				
			||||||
            nova.exception.ConfigNotFound,
 | 
					            nova.exception.ConfigNotFound,
 | 
				
			||||||
            nova.wsgi.Loader,
 | 
					            nova.api.wsgi.Loader,
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_asbpath_config_not_found(self):
 | 
					    def test_asbpath_config_not_found(self):
 | 
				
			||||||
        self.flags(api_paste_config='/etc/nova/api-paste.ini', group='wsgi')
 | 
					        self.flags(api_paste_config='/etc/nova/api-paste.ini', group='wsgi')
 | 
				
			||||||
        self.assertRaises(
 | 
					        self.assertRaises(
 | 
				
			||||||
            nova.exception.ConfigNotFound,
 | 
					            nova.exception.ConfigNotFound,
 | 
				
			||||||
            nova.wsgi.Loader,
 | 
					            nova.api.wsgi.Loader,
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -77,7 +78,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 = nova.wsgi.Loader(self.config.name)
 | 
					        self.loader = nova.api.wsgi.Loader(self.config.name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_config_found(self):
 | 
					    def test_config_found(self):
 | 
				
			||||||
        self.assertEqual(self.config.name, self.loader.config_path)
 | 
					        self.assertEqual(self.config.name, self.loader.config_path)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -683,19 +683,6 @@ def tempdir(**kwargs):
 | 
				
			|||||||
            LOG.error(_LE('Could not remove tmpdir: %s'), e)
 | 
					            LOG.error(_LE('Could not remove tmpdir: %s'), e)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def walk_class_hierarchy(clazz, encountered=None):
 | 
					 | 
				
			||||||
    """Walk class hierarchy, yielding most derived classes first."""
 | 
					 | 
				
			||||||
    if not encountered:
 | 
					 | 
				
			||||||
        encountered = []
 | 
					 | 
				
			||||||
    for subclass in clazz.__subclasses__():
 | 
					 | 
				
			||||||
        if subclass not in encountered:
 | 
					 | 
				
			||||||
            encountered.append(subclass)
 | 
					 | 
				
			||||||
            # drill down to leaves first
 | 
					 | 
				
			||||||
            for subsubclass in walk_class_hierarchy(subclass, encountered):
 | 
					 | 
				
			||||||
                yield subsubclass
 | 
					 | 
				
			||||||
            yield subclass
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class UndoManager(object):
 | 
					class UndoManager(object):
 | 
				
			||||||
    """Provides a mechanism to facilitate rolling back a series of actions
 | 
					    """Provides a mechanism to facilitate rolling back a series of actions
 | 
				
			||||||
    when an exception is raised.
 | 
					    when an exception is raised.
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										274
									
								
								nova/wsgi.py
									
									
									
									
									
								
							
							
						
						
									
										274
									
								
								nova/wsgi.py
									
									
									
									
									
								
							@@ -22,7 +22,6 @@ from __future__ import print_function
 | 
				
			|||||||
import os.path
 | 
					import os.path
 | 
				
			||||||
import socket
 | 
					import socket
 | 
				
			||||||
import ssl
 | 
					import ssl
 | 
				
			||||||
import sys
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
import eventlet
 | 
					import eventlet
 | 
				
			||||||
import eventlet.wsgi
 | 
					import eventlet.wsgi
 | 
				
			||||||
@@ -30,11 +29,6 @@ import greenlet
 | 
				
			|||||||
from oslo_log import log as logging
 | 
					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 paste import deploy
 | 
					 | 
				
			||||||
import routes.middleware
 | 
					 | 
				
			||||||
import six
 | 
					 | 
				
			||||||
import webob.dec
 | 
					 | 
				
			||||||
import webob.exc
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
import nova.conf
 | 
					import nova.conf
 | 
				
			||||||
from nova import exception
 | 
					from nova import exception
 | 
				
			||||||
@@ -230,271 +224,3 @@ class Server(service.ServiceBase):
 | 
				
			|||||||
                self._server.wait()
 | 
					                self._server.wait()
 | 
				
			||||||
        except greenlet.GreenletExit:
 | 
					        except greenlet.GreenletExit:
 | 
				
			||||||
            LOG.info(_LI("WSGI server has stopped."))
 | 
					            LOG.info(_LI("WSGI server has stopped."))
 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Request(webob.Request):
 | 
					 | 
				
			||||||
    def __init__(self, environ, *args, **kwargs):
 | 
					 | 
				
			||||||
        if CONF.wsgi.secure_proxy_ssl_header:
 | 
					 | 
				
			||||||
            scheme = environ.get(CONF.wsgi.secure_proxy_ssl_header)
 | 
					 | 
				
			||||||
            if scheme:
 | 
					 | 
				
			||||||
                environ['wsgi.url_scheme'] = scheme
 | 
					 | 
				
			||||||
        super(Request, self).__init__(environ, *args, **kwargs)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
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 = nova.api.fancy_api:Wadl.factory
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        which would result in a call to the `Wadl` class as
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            import nova.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, or ...)
 | 
					 | 
				
			||||||
          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 = nova.api.analytics:Analytics.factory
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        which would result in a call to the `Analytics` class as
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            import nova.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')
 | 
					 | 
				
			||||||
        for key, value in req.environ.items():
 | 
					 | 
				
			||||||
            print(key, '=', value)
 | 
					 | 
				
			||||||
        print()
 | 
					 | 
				
			||||||
        resp = req.get_response(self.application)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        print(('*' * 40) + ' RESPONSE HEADERS')
 | 
					 | 
				
			||||||
        for (key, value) in resp.headers.items():
 | 
					 | 
				
			||||||
            print(key, '=', value)
 | 
					 | 
				
			||||||
        print()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        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')
 | 
					 | 
				
			||||||
        for part in app_iter:
 | 
					 | 
				
			||||||
            sys.stdout.write(six.text_type(part))
 | 
					 | 
				
			||||||
            sys.stdout.flush()
 | 
					 | 
				
			||||||
            yield part
 | 
					 | 
				
			||||||
        print()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
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
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        self.config_path = None
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        config_path = config_path or CONF.wsgi.api_paste_config
 | 
					 | 
				
			||||||
        if not os.path.isabs(config_path):
 | 
					 | 
				
			||||||
            self.config_path = CONF.find_file(config_path)
 | 
					 | 
				
			||||||
        elif os.path.exists(config_path):
 | 
					 | 
				
			||||||
            self.config_path = config_path
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if not self.config_path:
 | 
					 | 
				
			||||||
            raise exception.ConfigNotFound(path=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: `nova.exception.PasteAppNotFound`
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        try:
 | 
					 | 
				
			||||||
            LOG.debug("Loading app %(name)s from %(path)s",
 | 
					 | 
				
			||||||
                      {'name': name, 'path': self.config_path})
 | 
					 | 
				
			||||||
            return deploy.loadapp("config:%s" % self.config_path, name=name)
 | 
					 | 
				
			||||||
        except LookupError:
 | 
					 | 
				
			||||||
            LOG.exception(_LE("Couldn't lookup app: %s"), name)
 | 
					 | 
				
			||||||
            raise exception.PasteAppNotFound(name=name, path=self.config_path)
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user