
* Added some comments for the wsgi stuff. * Changed WSGI code to log in the event results can not be serialized, since the base class swallows the exceptions and hides nasty bugs. * No longer return wsgi.Resulti for instance get and delete, since it doesn't work with XML serialization. * Also fixed fake mode test conf. * Added ComputeInstanceNotFound to HTTPNotFound in exception map. * Fixed Fake Mode by adding another flavor type (this should probably be data driven...).
327 lines
11 KiB
Python
327 lines
11 KiB
Python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
|
|
# Copyright 2011 OpenStack LLC.
|
|
# All Rights Reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
"""I totally stole most of this from melange, thx guys!!!"""
|
|
|
|
import eventlet.wsgi
|
|
import logging
|
|
import paste.urlmap
|
|
import re
|
|
import traceback
|
|
import webob
|
|
import webob.dec
|
|
import webob.exc
|
|
|
|
from reddwarf.common import context as rd_context
|
|
from reddwarf.common import exception
|
|
from reddwarf.common import utils
|
|
from reddwarf.openstack.common import wsgi as openstack_wsgi
|
|
|
|
CONTEXT_KEY = 'reddwarf.context'
|
|
Router = openstack_wsgi.Router
|
|
Server = openstack_wsgi.Server
|
|
Debug = openstack_wsgi.Debug
|
|
Middleware = openstack_wsgi.Middleware
|
|
JSONDictSerializer = openstack_wsgi.JSONDictSerializer
|
|
XMLDictSerializer = openstack_wsgi.XMLDictSerializer
|
|
RequestDeserializer = openstack_wsgi.RequestDeserializer
|
|
|
|
eventlet.patcher.monkey_patch(all=False, socket=True)
|
|
|
|
LOG = logging.getLogger('reddwarf.common.wsgi')
|
|
|
|
|
|
def versioned_urlmap(*args, **kwargs):
|
|
urlmap = paste.urlmap.urlmap_factory(*args, **kwargs)
|
|
return VersionedURLMap(urlmap)
|
|
|
|
|
|
class VersionedURLMap(object):
|
|
|
|
def __init__(self, urlmap):
|
|
self.urlmap = urlmap
|
|
|
|
def __call__(self, environ, start_response):
|
|
req = Request(environ)
|
|
|
|
if req.url_version is None and req.accept_version is not None:
|
|
version = "/v" + req.accept_version
|
|
http_exc = webob.exc.HTTPNotAcceptable(_("version not supported"))
|
|
app = self.urlmap.get(version, Fault(http_exc))
|
|
else:
|
|
app = self.urlmap
|
|
return app(environ, start_response)
|
|
|
|
|
|
class Request(openstack_wsgi.Request):
|
|
|
|
@property
|
|
def params(self):
|
|
return utils.stringify_keys(super(Request, self).params)
|
|
|
|
def best_match_content_type(self, supported_content_types=None):
|
|
"""Determine the most acceptable content-type.
|
|
|
|
Based on the query extension then the Accept header.
|
|
|
|
"""
|
|
parts = self.path.rsplit('.', 1)
|
|
|
|
if len(parts) > 1:
|
|
format = parts[1]
|
|
if format in ['json', 'xml']:
|
|
return 'application/{0}'.format(parts[1])
|
|
|
|
ctypes = {'application/vnd.openstack.reddwarf+json':
|
|
"application/json",
|
|
'application/vnd.openstack.reddwarf+xml': "application/xml",
|
|
'application/json': "application/json",
|
|
'application/xml': "application/xml"}
|
|
bm = self.accept.best_match(ctypes.keys())
|
|
|
|
return ctypes.get(bm, 'application/json')
|
|
|
|
@utils.cached_property
|
|
def accept_version(self):
|
|
accept_header = self.headers.get('ACCEPT', "")
|
|
accept_version_re = re.compile(".*?application/vnd.openstack.reddwarf"
|
|
"(\+.+?)?;"
|
|
"version=(?P<version_no>\d+\.?\d*)")
|
|
|
|
match = accept_version_re.search(accept_header)
|
|
return match.group("version_no") if match else None
|
|
|
|
@utils.cached_property
|
|
def url_version(self):
|
|
versioned_url_re = re.compile("/v(?P<version_no>\d+\.?\d*)")
|
|
match = versioned_url_re.search(self.path)
|
|
return match.group("version_no") if match else None
|
|
|
|
|
|
class Result(object):
|
|
"""A result whose serialization is compatable with JSON and XML.
|
|
|
|
This class is used by ReddwarfResponseSerializer, which calls the
|
|
data method to grab a JSON or XML specific dictionary which it then
|
|
passes on to be serialized.
|
|
|
|
"""
|
|
|
|
def __init__(self, data, status=200):
|
|
self._data = data
|
|
self.status = status
|
|
|
|
def data(self, serialization_type):
|
|
"""Return an appropriate serialized type for the body.
|
|
|
|
In both cases a dictionary is returned. With JSON it maps directly,
|
|
while with XML the dictionary is expected to have a single key value
|
|
which becomes the root element.
|
|
|
|
"""
|
|
if (serialization_type == "application/xml" and
|
|
hasattr(self._data, "data_for_xml")):
|
|
return self._data.data_for_xml()
|
|
if hasattr(self._data, "data_for_json"):
|
|
return self._data.data_for_json()
|
|
return self._data
|
|
|
|
|
|
class Resource(openstack_wsgi.Resource):
|
|
|
|
def __init__(self, controller, deserializer, serializer,
|
|
exception_map=None):
|
|
exception_map = exception_map or {}
|
|
self.model_exception_map = self._invert_dict_list(exception_map)
|
|
super(Resource, self).__init__(controller, deserializer, serializer)
|
|
|
|
@webob.dec.wsgify(RequestClass=Request)
|
|
def __call__(self, request):
|
|
return super(Resource, self).__call__(request)
|
|
|
|
def execute_action(self, action, request, **action_args):
|
|
if getattr(self.controller, action, None) is None:
|
|
return Fault(webob.exc.HTTPNotFound())
|
|
try:
|
|
result = super(Resource, self).execute_action(action,
|
|
request,
|
|
**action_args)
|
|
if type(result) is dict:
|
|
result = Result(result)
|
|
return result
|
|
|
|
except exception.ReddwarfError as reddwarf_error:
|
|
LOG.debug(traceback.format_exc())
|
|
httpError = self._get_http_error(reddwarf_error)
|
|
return Fault(httpError(str(reddwarf_error), request=request))
|
|
except webob.exc.HTTPError as http_error:
|
|
LOG.debug(traceback.format_exc())
|
|
return Fault(http_error)
|
|
except Exception as error:
|
|
LOG.exception(error)
|
|
return Fault(webob.exc.HTTPInternalServerError(str(error),
|
|
request=request))
|
|
|
|
def _get_http_error(self, error):
|
|
return self.model_exception_map.get(type(error),
|
|
webob.exc.HTTPBadRequest)
|
|
|
|
def _invert_dict_list(self, exception_dict):
|
|
"""Flattens values of keys and inverts keys and values.
|
|
|
|
Example:
|
|
{'x':[1,2,3],'y':[4,5,6]} converted to
|
|
{1:'x',2:'x',3:'x',4:'y',5:'y',6:'y'}
|
|
|
|
"""
|
|
inverted_dict = {}
|
|
for key, value_list in exception_dict.items():
|
|
for value in value_list:
|
|
inverted_dict[value] = key
|
|
return inverted_dict
|
|
|
|
def serialize_response(self, action, action_result, accept):
|
|
# If an exception is raised here in the base class, it is swallowed,
|
|
# and the action_result is returned as-is. For us, that's bad news -
|
|
# we never want that to happen except in the case of webob types.
|
|
# So we override the behavior here so we can at least log it (raising
|
|
# an exception in the base class creates a circular reference issue).
|
|
try:
|
|
return super(Resource, self).serialize_response(
|
|
action, action_result, accept)
|
|
except Exception as ex:
|
|
# The super class code seems designed to either serialize things
|
|
# or pass them back if they're webobs.
|
|
if not isinstance(action_result, webob.Response):
|
|
LOG.error("unserializable result detected! "
|
|
"Exception type: %s Message:%s" % (type(ex), ex))
|
|
raise
|
|
|
|
|
|
class Controller(object):
|
|
"""Base controller that creates a Resource with default serializers."""
|
|
|
|
exception_map = {}
|
|
|
|
def create_resource(self):
|
|
serializer = ReddwarfResponseSerializer(
|
|
body_serializers={'application/xml': ReddwarfXMLDictSerializer()})
|
|
return Resource(self,
|
|
openstack_wsgi.RequestDeserializer(),
|
|
serializer,
|
|
self.exception_map)
|
|
|
|
|
|
class ReddwarfXMLDictSerializer(openstack_wsgi.XMLDictSerializer):
|
|
|
|
def _to_xml_node(self, doc, metadata, nodename, data):
|
|
if hasattr(data, "to_xml"):
|
|
return data.to_xml()
|
|
return super(ReddwarfXMLDictSerializer, self)._to_xml_node(doc,
|
|
metadata,
|
|
nodename,
|
|
data)
|
|
|
|
|
|
class ReddwarfResponseSerializer(openstack_wsgi.ResponseSerializer):
|
|
|
|
def serialize_body(self, response, data, content_type, action):
|
|
"""Overrides body serialization in openstack_wsgi.ResponseSerializer.
|
|
|
|
If the "data" argument is the Result class, its data
|
|
method is called and *that* is passed to the superclass implementation
|
|
instead of the actual data.
|
|
|
|
"""
|
|
if isinstance(data, Result):
|
|
data = data.data(content_type)
|
|
super(ReddwarfResponseSerializer, self).serialize_body(response,
|
|
data,
|
|
content_type,
|
|
action)
|
|
|
|
def serialize_headers(self, response, data, action):
|
|
super(ReddwarfResponseSerializer, self).serialize_headers(response,
|
|
data,
|
|
action)
|
|
if isinstance(data, Result):
|
|
response.status = data.status
|
|
|
|
|
|
class Fault(webob.exc.HTTPException):
|
|
"""Error codes for API faults."""
|
|
|
|
def __init__(self, exception):
|
|
"""Create a Fault for the given webob.exc.exception."""
|
|
|
|
self.wrapped_exc = exception
|
|
|
|
@webob.dec.wsgify(RequestClass=Request)
|
|
def __call__(self, req):
|
|
"""Generate a WSGI response based on the exception passed to ctor."""
|
|
|
|
# Replace the body with fault details.
|
|
fault_name = self.wrapped_exc.__class__.__name__
|
|
if fault_name.startswith("HTTP"):
|
|
fault_name = fault_name[4:]
|
|
fault_data = {
|
|
fault_name: {
|
|
'code': self.wrapped_exc.status_int,
|
|
'message': self.wrapped_exc.explanation,
|
|
'detail': self.wrapped_exc.detail,
|
|
}
|
|
}
|
|
|
|
# 'code' is an attribute on the fault tag itself
|
|
metadata = {'attributes': {fault_name: 'code'}}
|
|
content_type = req.best_match_content_type()
|
|
serializer = {
|
|
'application/xml': openstack_wsgi.XMLDictSerializer(metadata),
|
|
'application/json': openstack_wsgi.JSONDictSerializer(),
|
|
}[content_type]
|
|
|
|
self.wrapped_exc.body = serializer.serialize(fault_data, content_type)
|
|
self.wrapped_exc.content_type = content_type
|
|
return self.wrapped_exc
|
|
|
|
|
|
class ContextMiddleware(openstack_wsgi.Middleware):
|
|
|
|
def __init__(self, application):
|
|
super(ContextMiddleware, self).__init__(application)
|
|
|
|
def _extract_limits(self, params):
|
|
return dict([(key, params[key]) for key in params.keys()
|
|
if key in ["limit", "marker"]])
|
|
|
|
def process_request(self, request):
|
|
tenant_id = request.headers.get('X-Tenant-Id', None)
|
|
auth_tok = request.headers["X-Auth-Token"]
|
|
limits = self._extract_limits(request.params)
|
|
context = rd_context.ReddwarfContext(auth_tok=auth_tok,
|
|
tenant=tenant_id,
|
|
limit=limits.get('limit'),
|
|
marker=limits.get('marker'))
|
|
request.environ[CONTEXT_KEY] = context
|
|
|
|
@classmethod
|
|
def factory(cls, global_config, **local_config):
|
|
def _factory(app):
|
|
LOG.debug(_("Created context middleware with config: %s") %
|
|
local_config)
|
|
return cls(app)
|
|
return _factory
|