Merge "Apply fault middleware"
This commit is contained in:
commit
9e0d583b88
@ -1,5 +1,5 @@
|
|||||||
[pipeline:murano]
|
[pipeline:murano]
|
||||||
pipeline = versionnegotiation authtoken context rootapp
|
pipeline = versionnegotiation faultwrap authtoken context rootapp
|
||||||
|
|
||||||
[filter:context]
|
[filter:context]
|
||||||
paste.filter_factory = murano.api.middleware.context:ContextMiddleware.factory
|
paste.filter_factory = murano.api.middleware.context:ContextMiddleware.factory
|
||||||
@ -22,3 +22,6 @@ paste.app_factory = murano.api.v1.router:API.factory
|
|||||||
|
|
||||||
[filter:versionnegotiation]
|
[filter:versionnegotiation]
|
||||||
paste.filter_factory = murano.api.middleware.version_negotiation:VersionNegotiationFilter.factory
|
paste.filter_factory = murano.api.middleware.version_negotiation:VersionNegotiationFilter.factory
|
||||||
|
|
||||||
|
[filter:faultwrap]
|
||||||
|
paste.filter_factory = murano.api.middleware.fault:FaultWrapper.factory
|
||||||
|
131
murano/api/middleware/fault.py
Normal file
131
murano/api/middleware/fault.py
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
"""A middleware that turns exceptions into parsable string. Inspired by
|
||||||
|
Cinder's faultwrapper
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
from oslo.config import cfg
|
||||||
|
import webob
|
||||||
|
|
||||||
|
from murano.openstack.common import wsgi
|
||||||
|
from murano.packages import exceptions as pkg_exc
|
||||||
|
|
||||||
|
cfg.CONF.import_opt('debug', 'murano.openstack.common.log')
|
||||||
|
|
||||||
|
|
||||||
|
class HTTPExceptionDisguise(Exception):
|
||||||
|
"""Disguises HTTP exceptions so they can be handled by the webob fault
|
||||||
|
application in the wsgi pipeline.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, exception):
|
||||||
|
self.exc = exception
|
||||||
|
self.tb = sys.exc_info()[2]
|
||||||
|
|
||||||
|
|
||||||
|
class Fault(object):
|
||||||
|
|
||||||
|
def __init__(self, error):
|
||||||
|
self.error = error
|
||||||
|
|
||||||
|
@webob.dec.wsgify(RequestClass=wsgi.Request)
|
||||||
|
def __call__(self, req):
|
||||||
|
if req.content_type == 'application/xml':
|
||||||
|
serializer = wsgi.XMLDictSerializer()
|
||||||
|
else:
|
||||||
|
serializer = wsgi.JSONDictSerializer()
|
||||||
|
resp = webob.Response(request=req)
|
||||||
|
default_webob_exc = webob.exc.HTTPInternalServerError()
|
||||||
|
resp.status_code = self.error.get('code', default_webob_exc.code)
|
||||||
|
serializer.default(resp, self.error)
|
||||||
|
return resp
|
||||||
|
|
||||||
|
|
||||||
|
class FaultWrapper(wsgi.Middleware):
|
||||||
|
"""Replace error body with something the client can parse."""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def factory(cls, global_conf, **local_conf):
|
||||||
|
def filter(app):
|
||||||
|
return cls(app)
|
||||||
|
return filter
|
||||||
|
|
||||||
|
error_map = {
|
||||||
|
'ValueError': webob.exc.HTTPBadRequest,
|
||||||
|
'LookupError': webob.exc.HTTPNotFound,
|
||||||
|
'PackageClassLoadError': webob.exc.HTTPBadRequest,
|
||||||
|
'PackageUILoadError': webob.exc.HTTPBadRequest,
|
||||||
|
'PackageLoadError': webob.exc.HTTPBadRequest,
|
||||||
|
'PackageFormatError': webob.exc.HTTPBadRequest,
|
||||||
|
}
|
||||||
|
|
||||||
|
def _map_exception_to_error(self, class_exception):
|
||||||
|
if class_exception == Exception:
|
||||||
|
return webob.exc.HTTPInternalServerError
|
||||||
|
|
||||||
|
if class_exception.__name__ not in self.error_map:
|
||||||
|
return self._map_exception_to_error(class_exception.__base__)
|
||||||
|
|
||||||
|
return self.error_map[class_exception.__name__]
|
||||||
|
|
||||||
|
def _error(self, ex):
|
||||||
|
|
||||||
|
trace = None
|
||||||
|
webob_exc = None
|
||||||
|
if isinstance(ex, HTTPExceptionDisguise):
|
||||||
|
# An HTTP exception was disguised so it could make it here
|
||||||
|
# let's remove the disguise and set the original HTTP exception
|
||||||
|
if cfg.CONF.debug:
|
||||||
|
trace = ''.join(traceback.format_tb(ex.tb))
|
||||||
|
ex = ex.exc
|
||||||
|
webob_exc = ex
|
||||||
|
|
||||||
|
ex_type = ex.__class__.__name__
|
||||||
|
|
||||||
|
full_message = unicode(ex)
|
||||||
|
if full_message.find('\n') > -1:
|
||||||
|
message, msg_trace = full_message.split('\n', 1)
|
||||||
|
else:
|
||||||
|
msg_trace = traceback.format_exc()
|
||||||
|
message = full_message
|
||||||
|
|
||||||
|
if isinstance(ex, pkg_exc.PackageException):
|
||||||
|
message = ex.message
|
||||||
|
|
||||||
|
if cfg.CONF.debug and not trace:
|
||||||
|
trace = msg_trace
|
||||||
|
|
||||||
|
if not webob_exc:
|
||||||
|
webob_exc = self._map_exception_to_error(ex.__class__)
|
||||||
|
|
||||||
|
error = {
|
||||||
|
'code': webob_exc.code,
|
||||||
|
'title': webob_exc.title,
|
||||||
|
'explanation': webob_exc.explanation,
|
||||||
|
'error': {
|
||||||
|
'message': message,
|
||||||
|
'type': ex_type,
|
||||||
|
'traceback': trace,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return error
|
||||||
|
|
||||||
|
def process_request(self, req):
|
||||||
|
try:
|
||||||
|
return req.get_response(self.application)
|
||||||
|
except Exception as exc:
|
||||||
|
return req.get_response(Fault(self._error(exc)))
|
@ -452,13 +452,14 @@ class DictSerializer(ActionDispatcher):
|
|||||||
class JSONDictSerializer(DictSerializer):
|
class JSONDictSerializer(DictSerializer):
|
||||||
"""Default JSON request body serialization"""
|
"""Default JSON request body serialization"""
|
||||||
|
|
||||||
def default(self, data):
|
def default(self, data, result=None):
|
||||||
def sanitizer(obj):
|
def sanitizer(obj):
|
||||||
if isinstance(obj, datetime.datetime):
|
if isinstance(obj, datetime.datetime):
|
||||||
_dtime = obj - datetime.timedelta(microseconds=obj.microsecond)
|
_dtime = obj - datetime.timedelta(microseconds=obj.microsecond)
|
||||||
return _dtime.isoformat()
|
return _dtime.isoformat()
|
||||||
return unicode(obj)
|
return unicode(obj)
|
||||||
|
if result:
|
||||||
|
data.body = jsonutils.dumps(result)
|
||||||
return jsonutils.dumps(data, default=sanitizer)
|
return jsonutils.dumps(data, default=sanitizer)
|
||||||
|
|
||||||
|
|
||||||
@ -473,7 +474,7 @@ class XMLDictSerializer(DictSerializer):
|
|||||||
self.metadata = metadata or {}
|
self.metadata = metadata or {}
|
||||||
self.xmlns = xmlns
|
self.xmlns = xmlns
|
||||||
|
|
||||||
def default(self, data):
|
def default(self, data, result=None):
|
||||||
# We expect data to contain a single key which is the XML root.
|
# We expect data to contain a single key which is the XML root.
|
||||||
root_key = data.keys()[0]
|
root_key = data.keys()[0]
|
||||||
doc = minidom.Document()
|
doc = minidom.Document()
|
||||||
|
@ -16,7 +16,12 @@
|
|||||||
import murano.openstack.common.exception as e
|
import murano.openstack.common.exception as e
|
||||||
|
|
||||||
|
|
||||||
class PackageClassLoadError(e.Error):
|
class PackageException(e.Error):
|
||||||
|
def __str__(self):
|
||||||
|
return unicode(self.message).encode('UTF-8')
|
||||||
|
|
||||||
|
|
||||||
|
class PackageClassLoadError(PackageException):
|
||||||
def __init__(self, class_name, message=None):
|
def __init__(self, class_name, message=None):
|
||||||
msg = 'Unable to load class "{0}" from package'.format(class_name)
|
msg = 'Unable to load class "{0}" from package'.format(class_name)
|
||||||
if message:
|
if message:
|
||||||
@ -24,7 +29,7 @@ class PackageClassLoadError(e.Error):
|
|||||||
super(PackageClassLoadError, self).__init__(msg)
|
super(PackageClassLoadError, self).__init__(msg)
|
||||||
|
|
||||||
|
|
||||||
class PackageUILoadError(e.Error):
|
class PackageUILoadError(PackageException):
|
||||||
def __init__(self, message=None):
|
def __init__(self, message=None):
|
||||||
msg = 'Unable to load ui definition from package'
|
msg = 'Unable to load ui definition from package'
|
||||||
if message:
|
if message:
|
||||||
@ -32,7 +37,7 @@ class PackageUILoadError(e.Error):
|
|||||||
super(PackageUILoadError, self).__init__(msg)
|
super(PackageUILoadError, self).__init__(msg)
|
||||||
|
|
||||||
|
|
||||||
class PackageLoadError(e.Error):
|
class PackageLoadError(PackageException):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user