summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBrian Waldon <brian.waldon@rackspace.com>2011-05-19 16:16:06 -0400
committerBrian Waldon <brian.waldon@rackspace.com>2011-05-19 16:16:06 -0400
commit68426df2287c24efc3d327d12371911ac29d117e (patch)
tree5e3cdf55d825798a4f3f422cbaac4bd121b62f2b
parent0aefdc6da92b8db8b15a3e8a0bef8fc5c4b46450 (diff)
downloadnova-68426df2287c24efc3d327d12371911ac29d117e.tar.gz
nova-68426df2287c24efc3d327d12371911ac29d117e.tar.xz
nova-68426df2287c24efc3d327d12371911ac29d117e.zip
further refactoring of wsgi module; adding documentation and tests
-rw-r--r--nova/api/direct.py4
-rw-r--r--nova/api/openstack/accounts.py2
-rw-r--r--nova/api/openstack/backup_schedules.py4
-rw-r--r--nova/api/openstack/consoles.py2
-rw-r--r--nova/api/openstack/extensions.py8
-rw-r--r--nova/api/openstack/faults.py12
-rw-r--r--nova/api/openstack/flavors.py2
-rw-r--r--nova/api/openstack/image_metadata.py2
-rw-r--r--nova/api/openstack/images.py4
-rw-r--r--nova/api/openstack/ips.py4
-rw-r--r--nova/api/openstack/limits.py4
-rw-r--r--nova/api/openstack/server_metadata.py2
-rw-r--r--nova/api/openstack/servers.py11
-rw-r--r--nova/api/openstack/users.py2
-rw-r--r--nova/api/openstack/versions.py5
-rw-r--r--nova/api/openstack/wsgi.py301
-rw-r--r--nova/api/openstack/zones.py4
-rw-r--r--nova/tests/api/openstack/test_servers.py50
-rw-r--r--nova/tests/api/openstack/test_wsgi.py104
19 files changed, 331 insertions, 196 deletions
diff --git a/nova/api/direct.py b/nova/api/direct.py
index 5e6c7c882..ea20042a7 100644
--- a/nova/api/direct.py
+++ b/nova/api/direct.py
@@ -291,8 +291,8 @@ class ServiceWrapper(object):
try:
content_type = req.best_match_content_type()
serializer = {
- 'application/xml': nova.api.openstack.wsgi.XMLSerializer(),
- 'application/json': nova.api.openstack.wsgi.JSONSerializer(),
+ 'application/xml': nova.api.openstack.wsgi.XMLDictSerializer(),
+ 'application/json': nova.api.openstack.wsgi.JSONDictSerializer(),
}[content_type]
return serializer.serialize(result)
except:
diff --git a/nova/api/openstack/accounts.py b/nova/api/openstack/accounts.py
index d8a9d1909..faff8bb2c 100644
--- a/nova/api/openstack/accounts.py
+++ b/nova/api/openstack/accounts.py
@@ -88,7 +88,7 @@ def resource_factory():
}
serializers = {
- 'application/xml': wsgi.XMLSerializer(metadata=metadata),
+ 'application/xml': wsgi.XMLDictSerializer(metadata=metadata),
}
return wsgi.Resource(Controller(), serializers=serializers)
diff --git a/nova/api/openstack/backup_schedules.py b/nova/api/openstack/backup_schedules.py
index 4153c90c1..d08a4799c 100644
--- a/nova/api/openstack/backup_schedules.py
+++ b/nova/api/openstack/backup_schedules.py
@@ -60,8 +60,8 @@ def resource_factory():
}
serializers = {
- 'application/xml': wsgi.XMLSerializer(xmlns=wsgi.XMLNS_V10,
- metadata=metadata),
+ 'application/xml': wsgi.XMLDictSerializer(xmlns=wsgi.XMLNS_V10,
+ metadata=metadata),
}
return wsgi.Resource(Controller(), serializers=serializers)
diff --git a/nova/api/openstack/consoles.py b/nova/api/openstack/consoles.py
index 97304affe..56f79db60 100644
--- a/nova/api/openstack/consoles.py
+++ b/nova/api/openstack/consoles.py
@@ -97,7 +97,7 @@ def resource_factory():
}
serializers = {
- 'application/xml': wsgi.XMLSerializer(metadata=metadata),
+ 'application/xml': wsgi.XMLDictSerializer(metadata=metadata),
}
return wsgi.Resource(Controller(), serializers=serializers)
diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py
index 73f174e07..19147bbea 100644
--- a/nova/api/openstack/extensions.py
+++ b/nova/api/openstack/extensions.py
@@ -165,8 +165,8 @@ class ResponseExtensionController(object):
except AttributeError:
default_xmlns = None
serializer = {
- 'application/xml': wsgi.XMLSerializer(),
- 'application/json': wsgi.JSONSerializer(),
+ 'application/xml': wsgi.XMLDictSerializer(),
+ 'application/json': wsgi.JSONDictSerializer(),
}[content_type]
body = serializer.serialize(res)
headers = {"Content-Type": content_type}
@@ -229,7 +229,7 @@ class ExtensionMiddleware(base_wsgi.Middleware):
return _factory
def _action_ext_resources(self, application, ext_mgr, mapper):
- """Return a dict of ActionExtensionResource objects by collection."""
+ """Return a dict of ActionExtensionResource-s by collection."""
action_resources = {}
for action in ext_mgr.get_actions():
if not action.collection in action_resources.keys():
@@ -248,7 +248,7 @@ class ExtensionMiddleware(base_wsgi.Middleware):
return action_resources
def _response_ext_resources(self, application, ext_mgr, mapper):
- """Returns a dict of ResponseExtensionResource objects by collection."""
+ """Returns a dict of ResponseExtensionResource-s by collection."""
response_ext_resources = {}
for resp_ext in ext_mgr.get_response_extensions():
if not resp_ext.key in response_ext_resources.keys():
diff --git a/nova/api/openstack/faults.py b/nova/api/openstack/faults.py
index fd36f8f17..b9a23c126 100644
--- a/nova/api/openstack/faults.py
+++ b/nova/api/openstack/faults.py
@@ -61,9 +61,9 @@ class Fault(webob.exc.HTTPException):
content_type = req.best_match_content_type()
serializer = {
- 'application/xml': wsgi.XMLSerializer(metadata=metadata,
- xmlns=wsgi.XMLNS_V10),
- 'application/json': wsgi.JSONSerializer(),
+ 'application/xml': wsgi.XMLDictSerializer(metadata=metadata,
+ xmlns=wsgi.XMLNS_V10),
+ 'application/json': wsgi.JSONDictSerializer(),
}[content_type]
self.wrapped_exc.body = serializer.serialize(fault_data)
@@ -100,9 +100,9 @@ class OverLimitFault(webob.exc.HTTPException):
metadata = {"attributes": {"overLimitFault": "code"}}
serializer = {
- 'application/xml': wsgi.XMLSerializer(metadata=metadata,
- xmlns=wsgi.XMLNS_V10),
- 'application/json': wsgi.JSONSerializer(),
+ 'application/xml': wsgi.XMLDictSerializer(metadata=metadata,
+ xmlns=wsgi.XMLNS_V10),
+ 'application/json': wsgi.JSONDictSerializer(),
}[content_type]
content = serializer.serialize(self.content)
diff --git a/nova/api/openstack/flavors.py b/nova/api/openstack/flavors.py
index 46056a27a..9e98e6c27 100644
--- a/nova/api/openstack/flavors.py
+++ b/nova/api/openstack/flavors.py
@@ -86,7 +86,7 @@ def resource_factory(version='1.0'):
}[version]
serializers = {
- 'application/xml': wsgi.XMLSerializer(xmlns=xmlns),
+ 'application/xml': wsgi.XMLDictSerializer(xmlns=xmlns),
}
return wsgi.Resource(controller, serializers=serializers)
diff --git a/nova/api/openstack/image_metadata.py b/nova/api/openstack/image_metadata.py
index 506b63acf..8acde9fe8 100644
--- a/nova/api/openstack/image_metadata.py
+++ b/nova/api/openstack/image_metadata.py
@@ -104,7 +104,7 @@ class Controller(object):
def resource_factory():
serializers = {
- 'application/xml': wsgi.XMLSerializer(xmlns=wsgi.XMLNS_V11),
+ 'application/xml': wsgi.XMLDictSerializer(xmlns=wsgi.XMLNS_V11),
}
return wsgi.Resource(Controller(), serializers=serializers)
diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py
index 5a03573d8..a9071ed8a 100644
--- a/nova/api/openstack/images.py
+++ b/nova/api/openstack/images.py
@@ -165,8 +165,8 @@ def resource_factory(version='1.0'):
}
serializers = {
- 'application/xml': wsgi.XMLSerializer(xmlns=xmlns,
- metadata=metadata),
+ 'application/xml': wsgi.XMLDictSerializer(xmlns=xmlns,
+ metadata=metadata),
}
return wsgi.Resource(controller, serializers=serializers)
diff --git a/nova/api/openstack/ips.py b/nova/api/openstack/ips.py
index 24612eafb..87c8c997a 100644
--- a/nova/api/openstack/ips.py
+++ b/nova/api/openstack/ips.py
@@ -72,8 +72,8 @@ def resource_factory():
}
serializers = {
- 'application/xml': wsgi.XMLSerializer(metadata=metadata,
- xmlns=wsgi.XMLNS_V10),
+ 'application/xml': wsgi.XMLDictSerializer(metadata=metadata,
+ xmlns=wsgi.XMLNS_V10),
}
return wsgi.Resource(Controller(), serializers=serializers)
diff --git a/nova/api/openstack/limits.py b/nova/api/openstack/limits.py
index 306048d8f..b0e093702 100644
--- a/nova/api/openstack/limits.py
+++ b/nova/api/openstack/limits.py
@@ -95,8 +95,8 @@ def resource_factory(version='1.0'):
}
serializers = {
- 'application/xml': wsgi.XMLSerializer(xmlns=xmlns,
- metadata=metadata)
+ 'application/xml': wsgi.XMLDictSerializer(xmlns=xmlns,
+ metadata=metadata)
}
return wsgi.Resource(controller, serializers=serializers)
diff --git a/nova/api/openstack/server_metadata.py b/nova/api/openstack/server_metadata.py
index fb9449b4c..eff98c060 100644
--- a/nova/api/openstack/server_metadata.py
+++ b/nova/api/openstack/server_metadata.py
@@ -93,7 +93,7 @@ class Controller(object):
def resource_factory():
serializers = {
- 'application/xml': wsgi.XMLSerializer(xmlns=wsgi.XMLNS_V11),
+ 'application/xml': wsgi.XMLDictSerializer(xmlns=wsgi.XMLNS_V11),
}
return wsgi.Resource(Controller(), serializers=serializers)
diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py
index 78f8bb1b7..8f39bd256 100644
--- a/nova/api/openstack/servers.py
+++ b/nova/api/openstack/servers.py
@@ -707,7 +707,7 @@ class ControllerV11(Controller):
return common.XML_NS_V11
-class ServerCreateRequestXMLDeserializer(object):
+class ServerXMLDeserializer(wsgi.XMLDeserializer):
"""
Deserializer to handle xml-formatted server create requests.
@@ -715,7 +715,7 @@ class ServerCreateRequestXMLDeserializer(object):
and personality attributes
"""
- def deserialize(self, string):
+ def create(self, string):
"""Deserialize an xml-formatted server create request"""
dom = minidom.parseString(string)
server = self._extract_server(dom)
@@ -812,14 +812,13 @@ def resource_factory(version='1.0'):
}[version]
serializers = {
- 'application/xml': wsgi.XMLSerializer(metadata=metadata,
- xmlns=xmlns),
+ 'application/xml': wsgi.XMLDictSerializer(metadata=metadata,
+ xmlns=xmlns),
}
deserializers = {
- 'application/xml': ServerCreateRequestXMLDeserializer(),
+ 'application/xml': ServerXMLDeserializer(),
}
return wsgi.Resource(controller, serializers=serializers,
deserializers=deserializers)
-
diff --git a/nova/api/openstack/users.py b/nova/api/openstack/users.py
index 35b6a502e..e14616349 100644
--- a/nova/api/openstack/users.py
+++ b/nova/api/openstack/users.py
@@ -106,7 +106,7 @@ def resource_factory():
}
serializers = {
- 'application/xml': wsgi.XMLSerializer(metadata=metadata),
+ 'application/xml': wsgi.XMLDictSerializer(metadata=metadata),
}
return wsgi.Resource(Controller(), serializers=serializers)
diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py
index a8d785b52..9db160102 100644
--- a/nova/api/openstack/versions.py
+++ b/nova/api/openstack/versions.py
@@ -18,12 +18,11 @@
import webob
import webob.dec
-from nova import wsgi as base_wsgi
import nova.api.openstack.views.versions
from nova.api.openstack import wsgi
-class Versions(wsgi.Resource, base_wsgi.Application):
+class Versions(wsgi.Resource):
def __init__(self):
metadata = {
"attributes": {
@@ -33,7 +32,7 @@ class Versions(wsgi.Resource, base_wsgi.Application):
}
serializers = {
- 'application/xml': wsgi.XMLSerializer(metadata=metadata),
+ 'application/xml': wsgi.XMLDictSerializer(metadata=metadata),
}
super(Versions, self).__init__(None, serializers=serializers)
diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py
index 97280c365..bd840a6f7 100644
--- a/nova/api/openstack/wsgi.py
+++ b/nova/api/openstack/wsgi.py
@@ -6,6 +6,7 @@ from xml.dom import minidom
from nova import exception
from nova import log as logging
from nova import utils
+from nova import wsgi
XMLNS_V10 = 'http://docs.rackspacecloud.com/servers/api/v1.0'
@@ -15,17 +16,17 @@ LOG = logging.getLogger('nova.api.openstack.wsgi')
class Request(webob.Request):
- def best_match_content_type(self, supported=None):
- """Determine the requested content-type.
+ """Add some Openstack API-specific logic to the base webob.Request."""
- Based on the query extension then the Accept header.
+ def best_match_content_type(self):
+ """Determine the requested response content-type.
- :param supported: list of content-types to override defaults
+ Based on the query extension then the Accept header.
"""
- supported = supported or ['application/json', 'application/xml']
- parts = self.path.rsplit('.', 1)
+ supported = ('application/json', 'application/xml')
+ parts = self.path.rsplit('.', 1)
if len(parts) > 1:
ctype = 'application/{0}'.format(parts[1])
if ctype in supported:
@@ -33,32 +34,52 @@ class Request(webob.Request):
bm = self.accept.best_match(supported)
+ # default to application/json if we don't find a preference
return bm or 'application/json'
def get_content_type(self):
+ """Determine content type of the request body.
+
+ Does not do any body introspection, only checks header
+
+ """
if not "Content-Type" in self.headers:
raise exception.InvalidContentType(content_type=None)
allowed_types = ("application/xml", "application/json")
- type = self.content_type
+ content_type = self.content_type
- if type not in allowed_types:
- raise exception.InvalidContentType(content_type=type)
+ if content_type not in allowed_types:
+ raise exception.InvalidContentType(content_type=content_type)
else:
- return type
+ return content_type
-class JSONDeserializer(object):
- def deserialize(self, datastring):
- return utils.loads(datastring)
+class TextDeserializer(object):
+ """Custom request body deserialization based on controller action name."""
+ def deserialize(self, datastring, action=None):
+ """Find local deserialization method and parse request body."""
+ try:
+ action_method = getattr(self, action)
+ except Exception:
+ action_method = self.default
-class JSONSerializer(object):
- def serialize(self, data):
- return utils.dumps(data)
+ return action_method(datastring)
+ def default(self, datastring):
+ """Default deserialization code should live here"""
+ raise NotImplementedError()
+
+
+class JSONDeserializer(TextDeserializer):
+
+ def default(self, datastring):
+ return utils.loads(datastring)
+
+
+class XMLDeserializer(TextDeserializer):
-class XMLDeserializer(object):
def __init__(self, metadata=None):
"""
:param metadata: information needed to deserialize xml into
@@ -67,8 +88,7 @@ class XMLDeserializer(object):
super(XMLDeserializer, self).__init__()
self.metadata = metadata or {}
- def deserialize(self, datastring):
- """XML deserialization entry point."""
+ def default(self, datastring):
plurals = set(self.metadata.get('plurals', {}))
node = minidom.parseString(datastring).childNodes[0]
return {node.nodeName: self._from_xml_node(node, plurals)}
@@ -95,18 +115,111 @@ class XMLDeserializer(object):
return result
-class XMLSerializer(object):
+class RequestDeserializer(object):
+ """Break up a Request object into more useful pieces."""
+
+ def __init__(self, deserializers=None):
+ """
+ :param deserializers: dictionary of content-type-specific deserializers
+
+ """
+ self.deserializers = {
+ 'application/xml': XMLDeserializer(),
+ 'application/json': JSONDeserializer(),
+ }
+
+ self.deserializers.update(deserializers or {})
+
+ def deserialize(self, request):
+ """Extract necessary pieces of the request.
+
+ :param request: Request object
+ :returns tuple of expected controller action name, dictionary of
+ keyword arguments to pass to the controller, the expected
+ content type of the response
+
+ """
+ action_args = self.get_action_args(request.environ)
+ action = action_args.pop('action', None)
+
+ if request.method.lower() in ('post', 'put'):
+ if len(request.body) == 0:
+ action_args['body'] = None
+ else:
+ content_type = request.get_content_type()
+ deserializer = self.get_deserializer(content_type)
+
+ try:
+ body = deserializer.deserialize(request.body, action)
+ action_args['body'] = body
+ except exception.InvalidContentType:
+ action_args['body'] = None
+
+ accept = self.get_expected_content_type(request)
+
+ return (action, action_args, accept)
+
+ def get_deserializer(self, content_type):
+ try:
+ return self.deserializers[content_type]
+ except Exception:
+ raise exception.InvalidContentType(content_type=content_type)
+
+ def get_expected_content_type(self, request):
+ return request.best_match_content_type()
+
+ def get_action_args(self, request_environment):
+ """Parse dictionary created by routes library."""
+ try:
+ args = request_environment['wsgiorg.routing_args'][1].copy()
+
+ del args['controller']
+
+ if 'format' in args:
+ del args['format']
+
+ return args
+
+ except KeyError:
+ return {}
+
+
+class DictSerializer(object):
+ """Custom response body serialization based on controller action name."""
+
+ def serialize(self, data, action=None):
+ """Find local serialization method and encode response body."""
+ try:
+ action_method = getattr(self, action)
+ except Exception:
+ action_method = self.default
+
+ return action_method(data)
+
+ def default(self, data):
+ """Default serialization code should live here"""
+ raise NotImplementedError()
+
+
+class JSONDictSerializer(DictSerializer):
+
+ def default(self, data):
+ return utils.dumps(data)
+
+
+class XMLDictSerializer(DictSerializer):
+
def __init__(self, metadata=None, xmlns=None):
"""
:param metadata: information needed to deserialize xml into
a dictionary.
:param xmlns: XML namespace to include with serialized xml
"""
- super(XMLSerializer, self).__init__()
+ super(XMLDictSerializer, self).__init__()
self.metadata = metadata or {}
self.xmlns = xmlns
- def serialize(self, data):
+ def default(self, data):
# We expect data to contain a single key which is the XML root.
root_key = data.keys()[0]
doc = minidom.Document()
@@ -171,75 +284,32 @@ class XMLSerializer(object):
return result
-class Resource(object):
- """WSGI app that dispatched to methods.
+class ResponseSerializer(object):
+ """Encode the necessary pieces into a response object"""
- WSGI app that reads routing information supplied by RoutesMiddleware
- and calls the requested action method upon itself. All action methods
- must, in addition to their normal parameters, accept a 'req' argument
- which is the incoming wsgi.Request. They raise a webob.exc exception,
- or return a dict which will be serialized by requested content type.
+ def __init__(self, serializers=None):
+ """
+ :param serializers: dictionary of content-type-specific serializers
- """
- def __init__(self, controller, serializers=None, deserializers=None):
+ """
self.serializers = {
- 'application/xml': XMLSerializer(),
- 'application/json': JSONSerializer(),
+ 'application/xml': XMLDictSerializer(),
+ 'application/json': JSONDictSerializer(),
}
self.serializers.update(serializers or {})
- self.deserializers = {
- 'application/xml': XMLDeserializer(),
- 'application/json': JSONDeserializer(),
- }
- self.deserializers.update(deserializers or {})
-
- self.controller = controller
-
- @webob.dec.wsgify(RequestClass=Request)
- def __call__(self, request):
- """Call the method specified in req.environ by RoutesMiddleware."""
- LOG.debug("%s %s" % (request.method, request.url))
-
- try:
- action, action_args, accept = self.deserialize_request(request)
- except exception.InvalidContentType:
- return webob.exc.HTTPBadRequest(_("Unsupported Content-Type"))
-
- result = self.dispatch(request, action, action_args)
-
- response = self.serialize_response(accept, result)
-
- try:
- msg_dict = dict(url=request.url, status=response.status_int)
- msg = _("%(url)s returned with HTTP %(status)d") % msg_dict
- except AttributeError:
- msg_dict = dict(url=request.url)
- msg = _("%(url)s returned a fault")
-
- LOG.debug(msg)
-
- return response
-
- def dispatch(self, request, action, action_args):
- controller_method = getattr(self.controller, action)
- return controller_method(req=request, **action_args)
-
- def serialize_response(self, content_type, response_body):
+ def serialize(self, response_data, content_type):
"""Serialize a dict into a string and wrap in a wsgi.Request object.
+ :param response_data: dict produced by the Controller
:param content_type: expected mimetype of serialized response body
- :param response_body: dict produced by the Controller
"""
- if not type(response_body) is dict:
- return response_body
-
response = webob.Response()
response.headers['Content-Type'] = content_type
serializer = self.get_serializer(content_type)
- response.body = serializer.serialize(response_body)
+ response.body = serializer.serialize(response_data)
return response
@@ -249,50 +319,63 @@ class Resource(object):
except Exception:
raise exception.InvalidContentType(content_type=content_type)
- def deserialize_request(self, request):
- """Parse a wsgi request into a set of params we care about.
- :param request: wsgi.Request object
+class Resource(wsgi.Application):
+ """WSGI app that handles (de)serialization and controller dispatch.
+
+ WSGI app that reads routing information supplied by RoutesMiddleware
+ and calls the requested action method upon its controller. All
+ controller action methods must accept a 'req' argument, which is the
+ incoming wsgi.Request. If the operation is a PUT or POST, the controller
+ method must also accept a 'body' argument (the deserialized request body).
+ They may raise a webob.exc exception or return a dict, which will be
+ serialized by requested content type.
+ """
+ def __init__(self, controller, serializers=None, deserializers=None):
"""
- action_args = self.get_action_args(request.environ)
- action = action_args.pop('action', None)
+ :param controller: object that implement methods created by routes lib
+ :param serializers: dict of content-type specific text serializers
+ :param deserializers: dict of content-type specific text deserializers
- if request.method.lower() in ('post', 'put'):
- if len(request.body) == 0:
- action_args['body'] = None
- else:
- content_type = request.get_content_type()
- deserializer = self.get_deserializer(content_type)
+ """
+ self.controller = controller
+ self.serializer = ResponseSerializer(serializers)
+ self.deserializer = RequestDeserializer(deserializers)
- try:
- action_args['body'] = deserializer.deserialize(request.body)
- except exception.InvalidContentType:
- action_args['body'] = None
+ @webob.dec.wsgify(RequestClass=Request)
+ def __call__(self, request):
+ """WSGI method that controls (de)serialization and method dispatch."""
- accept = self.get_expected_content_type(request)
+ LOG.debug("%s %s" % (request.method, request.url))
- return (action, action_args, accept)
+ try:
+ action, action_args, accept = self.deserializer.deserialize(
+ request)
+ except exception.InvalidContentType:
+ return webob.exc.HTTPBadRequest(_("Unsupported Content-Type"))
- def get_expected_content_type(self, request):
- return request.best_match_content_type()
+ action_result = self.dispatch(request, action, action_args)
- def get_action_args(self, request_environment):
- try:
- args = request_environment['wsgiorg.routing_args'][1].copy()
+ #TODO(bcwaldon): find a more elegant way to pass through non-dict types
+ if type(action_result) is dict:
+ response = self.serializer.serialize(action_result, accept)
+ else:
+ response = action_result
- del args['controller']
+ try:
+ msg_dict = dict(url=request.url, status=response.status_int)
+ msg = _("%(url)s returned with HTTP %(status)d") % msg_dict
+ except AttributeError:
+ msg_dict = dict(url=request.url)
+ msg = _("%(url)s returned a fault")
- if 'format' in args:
- del args['format']
+ LOG.debug(msg)
- return args
+ return response
- except KeyError:
- return {}
+ def dispatch(self, request, action, action_args):
+ """Find action-spefic method on controller and call it."""
- def get_deserializer(self, content_type):
- try:
- return self.deserializers[content_type]
- except Exception:
- raise exception.InvalidContentType(content_type=content_type)
+ controller_method = getattr(self.controller, action)
+ return controller_method(req=request, **action_args)
diff --git a/nova/api/openstack/zones.py b/nova/api/openstack/zones.py
index d17ab7a9b..e750fc230 100644
--- a/nova/api/openstack/zones.py
+++ b/nova/api/openstack/zones.py
@@ -101,8 +101,8 @@ def resource_factory():
}
serializers = {
- 'application/xml': wsgi.XMLSerializer(xmlns=wsgi.XMLNS_V10,
- metadata=metadata),
+ 'application/xml': wsgi.XMLDictSerializer(xmlns=wsgi.XMLNS_V10,
+ metadata=metadata),
}
return wsgi.Resource(Controller(), serializers=serializers)
diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py
index 15f376f74..31571fc46 100644
--- a/nova/tests/api/openstack/test_servers.py
+++ b/nova/tests/api/openstack/test_servers.py
@@ -993,6 +993,14 @@ class ServersTest(test.TestCase):
res = req.get_response(fakes.wsgi_app())
self.assertEqual(res.status_int, 501)
+ def test_server_change_password_xml(self):
+ req = webob.Request.blank('/v1.0/servers/1/action')
+ req.method = 'POST'
+ req.content_type = 'application/xml'
+ req.body = '<changePassword adminPass="1234pass">'
+# res = req.get_response(fakes.wsgi_app())
+# self.assertEqual(res.status_int, 501)
+
def test_server_change_password_v1_1(self):
class MockSetAdminPassword(object):
@@ -1375,13 +1383,13 @@ class ServersTest(test.TestCase):
class TestServerCreateRequestXMLDeserializer(unittest.TestCase):
def setUp(self):
- self.deserializer = servers.ServerCreateRequestXMLDeserializer()
+ self.deserializer = servers.ServerXMLDeserializer()
def test_minimal_request(self):
serial_request = """
<server xmlns="http://docs.rackspacecloud.com/servers/api/v1.0"
name="new-server-test" imageId="1" flavorId="1"/>"""
- request = self.deserializer.deserialize(serial_request)
+ request = self.deserializer.deserialize(serial_request, 'create')
expected = {"server": {
"name": "new-server-test",
"imageId": "1",
@@ -1395,7 +1403,7 @@ class TestServerCreateRequestXMLDeserializer(unittest.TestCase):
name="new-server-test" imageId="1" flavorId="1">
<metadata/>
</server>"""
- request = self.deserializer.deserialize(serial_request)
+ request = self.deserializer.deserialize(serial_request, 'create')
expected = {"server": {
"name": "new-server-test",
"imageId": "1",
@@ -1410,7 +1418,7 @@ class TestServerCreateRequestXMLDeserializer(unittest.TestCase):
name="new-server-test" imageId="1" flavorId="1">
<personality/>
</server>"""
- request = self.deserializer.deserialize(serial_request)
+ request = self.deserializer.deserialize(serial_request, 'create')
expected = {"server": {
"name": "new-server-test",
"imageId": "1",
@@ -1426,7 +1434,7 @@ class TestServerCreateRequestXMLDeserializer(unittest.TestCase):
<metadata/>
<personality/>
</server>"""
- request = self.deserializer.deserialize(serial_request)
+ request = self.deserializer.deserialize(serial_request, 'create')
expected = {"server": {
"name": "new-server-test",
"imageId": "1",
@@ -1443,7 +1451,7 @@ class TestServerCreateRequestXMLDeserializer(unittest.TestCase):
<personality/>
<metadata/>
</server>"""
- request = self.deserializer.deserialize(serial_request)
+ request = self.deserializer.deserialize(serial_request, 'create')
expected = {"server": {
"name": "new-server-test",
"imageId": "1",
@@ -1461,7 +1469,7 @@ class TestServerCreateRequestXMLDeserializer(unittest.TestCase):
<file path="/etc/conf">aabbccdd</file>
</personality>
</server>"""
- request = self.deserializer.deserialize(serial_request)
+ request = self.deserializer.deserialize(serial_request, 'create')
expected = [{"path": "/etc/conf", "contents": "aabbccdd"}]
self.assertEquals(request["server"]["personality"], expected)
@@ -1471,7 +1479,7 @@ class TestServerCreateRequestXMLDeserializer(unittest.TestCase):
name="new-server-test" imageId="1" flavorId="1">
<personality><file path="/etc/conf">aabbccdd</file>
<file path="/etc/sudoers">abcd</file></personality></server>"""
- request = self.deserializer.deserialize(serial_request)
+ request = self.deserializer.deserialize(serial_request, 'create')
expected = [{"path": "/etc/conf", "contents": "aabbccdd"},
{"path": "/etc/sudoers", "contents": "abcd"}]
self.assertEquals(request["server"]["personality"], expected)
@@ -1487,7 +1495,7 @@ class TestServerCreateRequestXMLDeserializer(unittest.TestCase):
<file path="/etc/ignoreme">anything</file>
</personality>
</server>"""
- request = self.deserializer.deserialize(serial_request)
+ request = self.deserializer.deserialize(serial_request, 'create')
expected = [{"path": "/etc/conf", "contents": "aabbccdd"}]
self.assertEquals(request["server"]["personality"], expected)
@@ -1496,7 +1504,7 @@ class TestServerCreateRequestXMLDeserializer(unittest.TestCase):
<server xmlns="http://docs.rackspacecloud.com/servers/api/v1.0"
name="new-server-test" imageId="1" flavorId="1">
<personality><file>aabbccdd</file></personality></server>"""
- request = self.deserializer.deserialize(serial_request)
+ request = self.deserializer.deserialize(serial_request, 'create')
expected = [{"contents": "aabbccdd"}]
self.assertEquals(request["server"]["personality"], expected)
@@ -1505,7 +1513,7 @@ class TestServerCreateRequestXMLDeserializer(unittest.TestCase):
<server xmlns="http://docs.rackspacecloud.com/servers/api/v1.0"
name="new-server-test" imageId="1" flavorId="1">
<personality><file path="/etc/conf"></file></personality></server>"""
- request = self.deserializer.deserialize(serial_request)
+ request = self.deserializer.deserialize(serial_request, 'create')
expected = [{"path": "/etc/conf", "contents": ""}]
self.assertEquals(request["server"]["personality"], expected)
@@ -1514,7 +1522,7 @@ class TestServerCreateRequestXMLDeserializer(unittest.TestCase):
<server xmlns="http://docs.rackspacecloud.com/servers/api/v1.0"
name="new-server-test" imageId="1" flavorId="1">
<personality><file path="/etc/conf"/></personality></server>"""
- request = self.deserializer.deserialize(serial_request)
+ request = self.deserializer.deserialize(serial_request, 'create')
expected = [{"path": "/etc/conf", "contents": ""}]
self.assertEquals(request["server"]["personality"], expected)
@@ -1526,7 +1534,7 @@ class TestServerCreateRequestXMLDeserializer(unittest.TestCase):
<meta key="alpha">beta</meta>
</metadata>
</server>"""
- request = self.deserializer.deserialize(serial_request)
+ request = self.deserializer.deserialize(serial_request, 'create')
expected = {"alpha": "beta"}
self.assertEquals(request["server"]["metadata"], expected)
@@ -1539,7 +1547,7 @@ class TestServerCreateRequestXMLDeserializer(unittest.TestCase):
<meta key="foo">bar</meta>
</metadata>
</server>"""
- request = self.deserializer.deserialize(serial_request)
+ request = self.deserializer.deserialize(serial_request, 'create')
expected = {"alpha": "beta", "foo": "bar"}
self.assertEquals(request["server"]["metadata"], expected)
@@ -1551,7 +1559,7 @@ class TestServerCreateRequestXMLDeserializer(unittest.TestCase):
<meta key="alpha"></meta>
</metadata>
</server>"""
- request = self.deserializer.deserialize(serial_request)
+ request = self.deserializer.deserialize(serial_request, 'create')
expected = {"alpha": ""}
self.assertEquals(request["server"]["metadata"], expected)
@@ -1564,7 +1572,7 @@ class TestServerCreateRequestXMLDeserializer(unittest.TestCase):
<meta key="delta"/>
</metadata>
</server>"""
- request = self.deserializer.deserialize(serial_request)
+ request = self.deserializer.deserialize(serial_request, 'create')
expected = {"alpha": "", "delta": ""}
self.assertEquals(request["server"]["metadata"], expected)
@@ -1576,7 +1584,7 @@ class TestServerCreateRequestXMLDeserializer(unittest.TestCase):
<meta>beta</meta>
</metadata>
</server>"""
- request = self.deserializer.deserialize(serial_request)
+ request = self.deserializer.deserialize(serial_request, 'create')
expected = {"": "beta"}
self.assertEquals(request["server"]["metadata"], expected)
@@ -1589,7 +1597,7 @@ class TestServerCreateRequestXMLDeserializer(unittest.TestCase):
<meta>gamma</meta>
</metadata>
</server>"""
- request = self.deserializer.deserialize(serial_request)
+ request = self.deserializer.deserialize(serial_request, 'create')
expected = {"": "gamma"}
self.assertEquals(request["server"]["metadata"], expected)
@@ -1602,7 +1610,7 @@ class TestServerCreateRequestXMLDeserializer(unittest.TestCase):
<meta key="foo">baz</meta>
</metadata>
</server>"""
- request = self.deserializer.deserialize(serial_request)
+ request = self.deserializer.deserialize(serial_request, 'create')
expected = {"foo": "baz"}
self.assertEquals(request["server"]["metadata"], expected)
@@ -1649,7 +1657,7 @@ b25zLiINCg0KLVJpY2hhcmQgQmFjaA==""",
},
],
}}
- request = self.deserializer.deserialize(serial_request)
+ request = self.deserializer.deserialize(serial_request, 'create')
self.assertEqual(request, expected)
def test_request_xmlser_with_flavor_image_ref(self):
@@ -1659,7 +1667,7 @@ b25zLiINCg0KLVJpY2hhcmQgQmFjaA==""",
imageRef="http://localhost:8774/v1.1/images/1"
flavorRef="http://localhost:8774/v1.1/flavors/1">
</server>"""
- request = self.deserializer.deserialize(serial_request)
+ request = self.deserializer.deserialize(serial_request, 'create')
self.assertEquals(request["server"]["flavorRef"],
"http://localhost:8774/v1.1/flavors/1")
self.assertEquals(request["server"]["imageRef"],
diff --git a/nova/tests/api/openstack/test_wsgi.py b/nova/tests/api/openstack/test_wsgi.py
index 430dafe77..6c57d3e4f 100644
--- a/nova/tests/api/openstack/test_wsgi.py
+++ b/nova/tests/api/openstack/test_wsgi.py
@@ -76,26 +76,56 @@ class RequestTest(test.TestCase):
self.assertEqual(result, "application/json")
-class SerializationTest(test.TestCase):
+class DictSerializerTest(test.TestCase):
+ def test_dispatch(self):
+ serializer = wsgi.DictSerializer()
+ serializer.create = lambda x: 'pants'
+ serializer.default = lambda x: 'trousers'
+ self.assertEqual(serializer.serialize({}, 'create'), 'pants')
+
+ def test_dispatch_default(self):
+ serializer = wsgi.DictSerializer()
+ serializer.create = lambda x: 'pants'
+ serializer.default = lambda x: 'trousers'
+ self.assertEqual(serializer.serialize({}, 'update'), 'trousers')
+
+
+class XMLDictSerializerTest(test.TestCase):
def test_xml(self):
input_dict = dict(servers=dict(a=(2, 3)))
expected_xml = '<serversxmlns="asdf"><a>(2,3)</a></servers>'
xmlns = "testing xmlns"
- serializer = wsgi.XMLSerializer(xmlns="asdf")
+ serializer = wsgi.XMLDictSerializer(xmlns="asdf")
result = serializer.serialize(input_dict)
result = result.replace('\n', '').replace(' ', '')
self.assertEqual(result, expected_xml)
+
+class JSONDictSerializerTest(test.TestCase):
def test_json(self):
input_dict = dict(servers=dict(a=(2, 3)))
expected_json = '{"servers":{"a":[2,3]}}'
- serializer = wsgi.JSONSerializer()
+ serializer = wsgi.JSONDictSerializer()
result = serializer.serialize(input_dict)
result = result.replace('\n', '').replace(' ', '')
self.assertEqual(result, expected_json)
-class DeserializationTest(test.TestCase):
+class TextDeserializerTest(test.TestCase):
+ def test_dispatch(self):
+ deserializer = wsgi.TextDeserializer()
+ deserializer.create = lambda x: 'pants'
+ deserializer.default = lambda x: 'trousers'
+ self.assertEqual(deserializer.deserialize({}, 'create'), 'pants')
+
+ def test_dispatch_default(self):
+ deserializer = wsgi.TextDeserializer()
+ deserializer.create = lambda x: 'pants'
+ deserializer.default = lambda x: 'trousers'
+ self.assertEqual(deserializer.deserialize({}, 'update'), 'trousers')
+
+
+class JSONDeserializerTest(test.TestCase):
def test_json(self):
data = """{"a": {
"a1": "1",
@@ -112,6 +142,8 @@ class DeserializationTest(test.TestCase):
deserializer = wsgi.JSONDeserializer()
self.assertEqual(deserializer.deserialize(data), as_dict)
+
+class XMLDeserializerTest(test.TestCase):
def test_xml(self):
xml = """
<a a1="1" a2="2">
@@ -137,7 +169,7 @@ class DeserializationTest(test.TestCase):
self.assertEqual(deserializer.deserialize(xml), as_dict)
-class ResourceSerializerTest(test.TestCase):
+class ResponseSerializerTest(test.TestCase):
def setUp(self):
class JSONSerializer(object):
def serialize(self, data):
@@ -152,40 +184,32 @@ class ResourceSerializerTest(test.TestCase):
'application/XML': XMLSerializer(),
}
- self.resource = wsgi.Resource(None, serializers=self.serializers)
+ self.serializer = wsgi.ResponseSerializer(serializers=self.serializers)
def tearDown(self):
pass
def test_get_serializer(self):
- self.assertEqual(self.resource.get_serializer('application/json'),
+ self.assertEqual(self.serializer.get_serializer('application/json'),
self.serializers['application/json'])
def test_get_serializer_unknown_content_type(self):
self.assertRaises(exception.InvalidContentType,
- self.resource.get_serializer,
+ self.serializer.get_serializer,
'application/unknown')
- def test_serialize_response_dict(self):
- response = self.resource.serialize_response('application/json', {})
+ def test_serialize_response(self):
+ response = self.serializer.serialize({}, 'application/json')
self.assertEqual(response.headers['Content-Type'], 'application/json')
self.assertEqual(response.body, 'pew_json')
- def test_serialize_response_non_dict(self):
- response = self.resource.serialize_response('application/json', 'a')
- self.assertEqual(response, 'a')
-
def test_serialize_response_dict_to_unknown_content_type(self):
self.assertRaises(exception.InvalidContentType,
- self.resource.serialize_response,
+ self.serializer.serialize,
'application/unknown', {})
- def test_serialize_response_non_dict_to_unknown_content_type(self):
- response = self.resource.serialize_response('application/unknown', 'a')
- self.assertEqual(response, 'a')
-
-class ResourceDeserializerTest(test.TestCase):
+class RequestDeserializerTest(test.TestCase):
def setUp(self):
class JSONDeserializer(object):
def deserialize(self, data):
@@ -200,24 +224,25 @@ class ResourceDeserializerTest(test.TestCase):
'application/XML': XMLDeserializer(),
}
- self.resource = wsgi.Resource(None, deserializers=self.deserializers)
+ self.deserializer = wsgi.RequestDeserializer(
+ deserializers=self.deserializers)
def tearDown(self):
pass
def test_get_deserializer(self):
- self.assertEqual(self.resource.get_deserializer('application/json'),
- self.deserializers['application/json'])
+ expected = self.deserializer.get_deserializer('application/json')
+ self.assertEqual(expected, self.deserializers['application/json'])
def test_get_deserializer_unknown_content_type(self):
self.assertRaises(exception.InvalidContentType,
- self.resource.get_deserializer,
+ self.deserializer.get_deserializer,
'application/unknown')
def test_get_expected_content_type(self):
request = wsgi.Request.blank('/')
request.headers['Accept'] = 'application/json'
- self.assertEqual(self.resource.get_expected_content_type(request),
+ self.assertEqual(self.deserializer.get_expected_content_type(request),
'application/json')
def test_get_action_args(self):
@@ -232,17 +257,38 @@ class ResourceDeserializerTest(test.TestCase):
expected = {'action': 'update', 'id': 12}
- self.assertEqual(self.resource.get_action_args(env), expected)
+ self.assertEqual(self.deserializer.get_action_args(env), expected)
- def test_deserialize_request(self):
+ def test_deserialize(self):
def fake_get_routing_args(request):
return {'action': 'create'}
- self.resource.get_action_args = fake_get_routing_args
+ self.deserializer.get_action_args = fake_get_routing_args
request = wsgi.Request.blank('/')
request.headers['Accept'] = 'application/xml'
- deserialized = self.resource.deserialize_request(request)
+ deserialized = self.deserializer.deserialize(request)
expected = ('create', {}, 'application/xml')
self.assertEqual(expected, deserialized)
+
+
+class ResourceTest(test.TestCase):
+ def test_dispatch(self):
+ class Controller(object):
+ def index(self, req, pants=None):
+ return pants
+
+ resource = wsgi.Resource(Controller())
+ actual = resource.dispatch(None, 'index', {'pants': 'off'})
+ expected = 'off'
+ self.assertEqual(actual, expected)
+
+ def test_dispatch_unknown_controller_action(self):
+ class Controller(object):
+ def index(self, req, pants=None):
+ return pants
+
+ resource = wsgi.Resource(Controller())
+ self.assertRaises(AttributeError, resource.dispatch,
+ None, 'create', {})