Refactor WSGI apps and utils to limit imports

The file nova/api/openstack/__init__.py had imported a lot of
modules, notably nova.utils. This means that any code which
runs within that package, notably the placement service, imports
all those modules, even if it is not going to use them. This
results in scripts/binaries that are heavier than they need
to be and in some cases including modules, like eventlet, that
it would feel safe to not have in the stack.

Unfortunately we cannot sinply rename nova/api/openstack/__init__.py
to another name because it contains FaultWrapper and FaultWrapper
is referred to, by package path, from the paste.ini file and that
file is out there in config land, and something we prefer not to
change. Therefore alternate methods of cleaning up were explored
and this has led to some useful changes:

Fault wrapper is the only consumer of walk_class_hierarchy so
there is no reason for it it to be in nova.utils.

nova.wsgi contains a mismash of WSGI middleware and applications,
which need only a small number of imports, and Server classes
which are more complex and not required by the WSGI wares.

Therefore nova.wsgi was split into nova.wsgi and nova.api.wsgi.
The name choices may not be ideal, but they were chosen to limit
the cascades of changes that are needed across code and tests.

Where utils.utf8 was used it has been replaced with the similar (but not
exactly equivalient) method from oslo_utils.encodeutils.

Change-Id: I297f30aa6eb01fe3b53fd8c9b7853949be31156d
Partial-Bug: #1743120
This commit is contained in:
Chris Dent 2018-01-15 20:58:48 +00:00
parent 5f881ff030
commit ef6f4e4c8e
17 changed files with 355 additions and 317 deletions

View File

@ -22,10 +22,10 @@ from oslo_serialization import jsonutils
import webob.dec
import webob.exc
from nova.api import wsgi
import nova.conf
from nova import context
from nova.i18n import _
from nova import wsgi
CONF = nova.conf.CONF

View File

@ -27,13 +27,13 @@ import webob.dec
import webob.exc
from nova.api.metadata import base
from nova.api import wsgi
from nova import cache_utils
import nova.conf
from nova import context as nova_context
from nova import exception
from nova.i18n import _
from nova.network.neutronv2 import api as neutronapi
from nova import wsgi
CONF = nova.conf.CONF
LOG = logging.getLogger(__name__)

View File

@ -24,16 +24,28 @@ import webob.dec
import webob.exc
from nova.api.openstack import wsgi
from nova.api import wsgi as base_wsgi
import nova.conf
from nova.i18n import translate
from nova import utils
from nova import wsgi as base_wsgi
LOG = logging.getLogger(__name__)
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):
"""Calls down the middleware stack, making exceptions into faults."""
@ -42,7 +54,7 @@ class FaultWrapper(base_wsgi.Middleware):
@staticmethod
def status_to_type(status):
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
return FaultWrapper._status_to_type.get(
status, webob.exc.HTTPInternalServerError)()

View File

@ -18,9 +18,9 @@ import webob.dec
import webob.exc
from nova.api.openstack import wsgi
from nova.api import wsgi as base_wsgi
import nova.conf
from nova import context
from nova import wsgi as base_wsgi
CONF = nova.conf.CONF

View File

@ -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 volumes
from nova.api.openstack import wsgi
from nova.api import wsgi as base_wsgi
import nova.conf
from nova import wsgi as base_wsgi
CONF = nova.conf.CONF

View File

@ -20,7 +20,7 @@ import webob.dec
import webob.exc
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
LOG = logging.getLogger(__name__)

View File

@ -26,11 +26,10 @@ import webob
from nova.api.openstack import api_version_request as api_version
from nova.api.openstack import versioned_method
from nova.api import wsgi
from nova import exception
from nova import i18n
from nova.i18n import _
from nova import utils
from nova import wsgi
LOG = logging.getLogger(__name__)
@ -334,16 +333,28 @@ class ResponseObject(object):
if self.obj is not None:
body = serializer.serialize(self.obj)
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
for hdr, value in self._headers.items():
response.headers[hdr] = utils.utf8(value)
response.headers['Content-Type'] = utils.utf8(content_type)
for hdr, val in self._headers.items():
if not isinstance(val, six.text_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
@property
@ -658,13 +669,15 @@ class Resource(wsgi.Application):
if hasattr(response, 'headers'):
for hdr, val in list(response.headers.items()):
if not isinstance(val, six.text_type):
val = six.text_type(val)
if six.PY2:
# In Py2.X Headers must be byte strings
response.headers[hdr] = utils.utf8(val)
response.headers[hdr] = encodeutils.safe_encode(val)
else:
# In Py3.X Headers must be utf-8 strings
response.headers[hdr] = encodeutils.safe_decode(
utils.utf8(val))
encodeutils.safe_encode(val))
if not request.api_version_request.is_null():
response.headers[API_VERSION_REQUEST_HEADER] = \

298
nova/api/wsgi.py Normal file
View 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)

View File

@ -27,6 +27,7 @@ import oslo_messaging as messaging
from oslo_service import service
from oslo_utils import importutils
from nova.api import wsgi as api_wsgi
from nova import baserpc
from nova import conductor
import nova.conf
@ -326,7 +327,7 @@ class WSGIService(service.Service):
self.binary = 'nova-%s' % name
self.topic = None
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)
# inherit all compute_api worker counts from osapi_compute
if name.startswith('openstack_compute_api'):

View File

@ -39,6 +39,7 @@ from wsgi_intercept import interceptor
from nova.api.openstack.compute import tenant_networks
from nova.api.openstack.placement import deploy as placement_deploy
from nova.api.openstack import wsgi_app
from nova.api import wsgi
from nova.compute import rpcapi as compute_rpcapi
from nova import context
from nova.db import migration
@ -53,7 +54,6 @@ from nova import rpc
from nova import service
from nova.tests.functional.api import client
from nova.tests import uuidsentinel
from nova import wsgi
_TRUE_VALUES = ('True', 'true', '1', 'yes')

View File

@ -19,11 +19,11 @@ from oslo_serialization import jsonutils
from nova.api.openstack import api_version_request as avr
from nova.api.openstack.compute import views
from nova.api import wsgi
from nova import test
from nova.tests.unit.api.openstack import fakes
from nova.tests.unit import matchers
from nova.tests import uuidsentinel as uuids
from nova import wsgi
NS = {

View File

@ -30,6 +30,7 @@ from nova.api.openstack import compute
from nova.api.openstack.compute import versions
from nova.api.openstack import urlmap
from nova.api.openstack import wsgi as os_wsgi
from nova.api import wsgi
from nova.compute import flavors
from nova.compute import vm_states
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.objects import test_keypair
from nova import utils
from nova import wsgi
CONF = nova.conf.CONF

View File

@ -24,8 +24,8 @@ import routes
from six.moves import StringIO
import webob
from nova.api import wsgi
from nova import test
from nova import wsgi
class Test(test.NoDBTestCase):

View File

@ -263,7 +263,7 @@ class TestWSGIService(test.NoDBTestCase):
def setUp(self):
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())
@mock.patch('nova.objects.Service.get_by_host_and_binary')

View File

@ -29,6 +29,7 @@ import six
import testtools
import webob
import nova.api.wsgi
import nova.exception
from nova import test
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.assertRaises(
nova.exception.ConfigNotFound,
nova.wsgi.Loader,
nova.api.wsgi.Loader,
)
def test_asbpath_config_not_found(self):
self.flags(api_paste_config='/etc/nova/api-paste.ini', group='wsgi')
self.assertRaises(
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.seek(0)
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):
self.assertEqual(self.config.name, self.loader.config_path)

View File

@ -683,19 +683,6 @@ def tempdir(**kwargs):
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):
"""Provides a mechanism to facilitate rolling back a series of actions
when an exception is raised.

View File

@ -22,7 +22,6 @@ from __future__ import print_function
import os.path
import socket
import ssl
import sys
import eventlet
import eventlet.wsgi
@ -30,11 +29,6 @@ import greenlet
from oslo_log import log as logging
from oslo_service import service
from oslo_utils import excutils
from paste import deploy
import routes.middleware
import six
import webob.dec
import webob.exc
import nova.conf
from nova import exception
@ -230,271 +224,3 @@ class Server(service.ServiceBase):
self._server.wait()
except greenlet.GreenletExit:
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)