From 5e722ea7b912f189c0a3b9434e9a38d08095ad00 Mon Sep 17 00:00:00 2001
From: Brian Waldon
Date: Wed, 18 May 2011 19:13:22 -0400
Subject: refactoring wsgi to separate controller/serialization/deserialization
logic; creating osapi-specific module
---
nova/api/openstack/__init__.py | 43 ++---
nova/api/openstack/accounts.py | 33 ++--
nova/api/openstack/backup_schedules.py | 27 ++-
nova/api/openstack/consoles.py | 26 ++-
nova/api/openstack/flavors.py | 34 ++--
nova/api/openstack/image_metadata.py | 19 +-
nova/api/openstack/images.py | 40 +++--
nova/api/openstack/ips.py | 33 ++--
nova/api/openstack/limits.py | 50 ++++--
nova/api/openstack/server_metadata.py | 21 ++-
nova/api/openstack/servers.py | 124 +++++++------
nova/api/openstack/shared_ip_groups.py | 28 +--
nova/api/openstack/users.py | 43 +++--
nova/api/openstack/wsgi.py | 291 +++++++++++++++++++++++++++++++
nova/api/openstack/zones.py | 33 ++--
nova/tests/api/openstack/test_limits.py | 4 +-
nova/tests/api/openstack/test_servers.py | 2 -
nova/tests/api/test_wsgi.py | 135 --------------
nova/tests/integrated/test_xml.py | 4 +-
19 files changed, 624 insertions(+), 366 deletions(-)
create mode 100644 nova/api/openstack/wsgi.py
diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py
index 348b70d5b..fbbd99cb9 100644
--- a/nova/api/openstack/__init__.py
+++ b/nova/api/openstack/__init__.py
@@ -26,7 +26,7 @@ import webob.exc
from nova import flags
from nova import log as logging
-from nova import wsgi
+from nova import wsgi as base_wsgi
from nova.api.openstack import accounts
from nova.api.openstack import faults
from nova.api.openstack import backup_schedules
@@ -40,6 +40,7 @@ from nova.api.openstack import servers
from nova.api.openstack import server_metadata
from nova.api.openstack import shared_ip_groups
from nova.api.openstack import users
+from nova.api.openstack import wsgi
from nova.api.openstack import zones
@@ -50,7 +51,7 @@ flags.DEFINE_bool('allow_admin_api',
'When True, this API service will accept admin operations.')
-class FaultWrapper(wsgi.Middleware):
+class FaultWrapper(base_wsgi.Middleware):
"""Calls down the middleware stack, making exceptions into faults."""
@webob.dec.wsgify(RequestClass=wsgi.Request)
@@ -63,7 +64,7 @@ class FaultWrapper(wsgi.Middleware):
return faults.Fault(exc)
-class APIRouter(wsgi.Router):
+class APIRouter(base_wsgi.Router):
"""
Routes requests on the OpenStack API to the appropriate controller
and method.
@@ -97,18 +98,20 @@ class APIRouter(wsgi.Router):
server_members['reset_network'] = 'POST'
server_members['inject_network_info'] = 'POST'
- mapper.resource("zone", "zones", controller=zones.Controller(),
+ mapper.resource("zone", "zones",
+ controller=zones.resource_factory(),
collection={'detail': 'GET', 'info': 'GET'}),
- mapper.resource("user", "users", controller=users.Controller(),
+ mapper.resource("user", "users",
+ controller=users.resource_factory(),
collection={'detail': 'GET'})
mapper.resource("account", "accounts",
- controller=accounts.Controller(),
+ controller=accounts.resource_factory(),
collection={'detail': 'GET'})
mapper.resource("console", "consoles",
- controller=consoles.Controller(),
+ controller=consoles.resource_factory(),
parent_resource=dict(member_name='server',
collection_name='servers'))
@@ -121,31 +124,31 @@ class APIRouterV10(APIRouter):
def _setup_routes(self, mapper):
super(APIRouterV10, self)._setup_routes(mapper)
mapper.resource("server", "servers",
- controller=servers.ControllerV10(),
+ controller=servers.resource_factory('1.0'),
collection={'detail': 'GET'},
member=self.server_members)
mapper.resource("image", "images",
- controller=images.ControllerV10(),
+ controller=images.resource_factory('1.0'),
collection={'detail': 'GET'})
mapper.resource("flavor", "flavors",
- controller=flavors.ControllerV10(),
+ controller=flavors.resource_factory('1.0'),
collection={'detail': 'GET'})
mapper.resource("shared_ip_group", "shared_ip_groups",
collection={'detail': 'GET'},
- controller=shared_ip_groups.Controller())
+ controller=shared_ip_groups.resource_factory())
mapper.resource("backup_schedule", "backup_schedule",
- controller=backup_schedules.Controller(),
+ controller=backup_schedules.resource_factory(),
parent_resource=dict(member_name='server',
collection_name='servers'))
mapper.resource("limit", "limits",
- controller=limits.LimitsControllerV10())
+ controller=limits.resource_factory('1.0'))
- mapper.resource("ip", "ips", controller=ips.Controller(),
+ mapper.resource("ip", "ips", controller=ips.resource_factory(),
collection=dict(public='GET', private='GET'),
parent_resource=dict(member_name='server',
collection_name='servers'))
@@ -157,27 +160,27 @@ class APIRouterV11(APIRouter):
def _setup_routes(self, mapper):
super(APIRouterV11, self)._setup_routes(mapper)
mapper.resource("server", "servers",
- controller=servers.ControllerV11(),
+ controller=servers.resource_factory('1.1'),
collection={'detail': 'GET'},
member=self.server_members)
mapper.resource("image", "images",
- controller=images.ControllerV11(),
+ controller=images.resource_factory('1.1'),
collection={'detail': 'GET'})
mapper.resource("image_meta", "meta",
- controller=image_metadata.Controller(),
+ controller=image_metadata.resource_factory(),
parent_resource=dict(member_name='image',
collection_name='images'))
mapper.resource("server_meta", "meta",
- controller=server_metadata.Controller(),
+ controller=server_metadata.resource_factory(),
parent_resource=dict(member_name='server',
collection_name='servers'))
mapper.resource("flavor", "flavors",
- controller=flavors.ControllerV11(),
+ controller=flavors.resource_factory('1.1'),
collection={'detail': 'GET'})
mapper.resource("limit", "limits",
- controller=limits.LimitsControllerV11())
+ controller=limits.resource_factory('1.1'))
diff --git a/nova/api/openstack/accounts.py b/nova/api/openstack/accounts.py
index 00fdd4540..d8a9d1909 100644
--- a/nova/api/openstack/accounts.py
+++ b/nova/api/openstack/accounts.py
@@ -20,8 +20,9 @@ from nova import flags
from nova import log as logging
from nova.auth import manager
-from nova.api.openstack import common
from nova.api.openstack import faults
+from nova.api.openstack import wsgi
+
FLAGS = flags.FLAGS
LOG = logging.getLogger('nova.api.openstack')
@@ -34,12 +35,7 @@ def _translate_keys(account):
manager=account.project_manager_id)
-class Controller(common.OpenstackController):
-
- _serialization_metadata = {
- 'application/xml': {
- "attributes": {
- "account": ["id", "name", "description", "manager"]}}}
+class Controller(object):
def __init__(self):
self.manager = manager.AuthManager()
@@ -66,20 +62,33 @@ class Controller(common.OpenstackController):
self.manager.delete_project(id)
return {}
- def create(self, req):
+ def create(self, req, body):
"""We use update with create-or-update semantics
because the id comes from an external source"""
raise faults.Fault(webob.exc.HTTPNotImplemented())
- def update(self, req, id):
+ def update(self, req, id, body):
"""This is really create or update."""
self._check_admin(req.environ['nova.context'])
- env = self._deserialize(req.body, req.get_content_type())
- description = env['account'].get('description')
- manager = env['account'].get('manager')
+ description = body['account'].get('description')
+ manager = body['account'].get('manager')
try:
account = self.manager.get_project(id)
self.manager.modify_project(id, manager, description)
except exception.NotFound:
account = self.manager.create_project(id, manager, description)
return dict(account=_translate_keys(account))
+
+
+def resource_factory():
+ metadata = {
+ "attributes": {
+ "account": ["id", "name", "description", "manager"],
+ },
+ }
+
+ serializers = {
+ 'application/xml': wsgi.XMLSerializer(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 4bf744046..4153c90c1 100644
--- a/nova/api/openstack/backup_schedules.py
+++ b/nova/api/openstack/backup_schedules.py
@@ -19,9 +19,8 @@ import time
from webob import exc
-from nova.api.openstack import common
from nova.api.openstack import faults
-import nova.image.service
+from nova.api.openstack import wsgi
def _translate_keys(inst):
@@ -29,14 +28,9 @@ def _translate_keys(inst):
return dict(backupSchedule=inst)
-class Controller(common.OpenstackController):
+class Controller(object):
""" The backup schedule API controller for the Openstack API """
- _serialization_metadata = {
- 'application/xml': {
- 'attributes': {
- 'backupSchedule': []}}}
-
def __init__(self):
pass
@@ -48,7 +42,7 @@ class Controller(common.OpenstackController):
""" Returns a single backup schedule for a given instance """
return faults.Fault(exc.HTTPNotImplemented())
- def create(self, req, server_id):
+ def create(self, req, server_id, body):
""" No actual update method required, since the existing API allows
both create and update through a POST """
return faults.Fault(exc.HTTPNotImplemented())
@@ -56,3 +50,18 @@ class Controller(common.OpenstackController):
def delete(self, req, server_id, id):
""" Deletes an existing backup schedule """
return faults.Fault(exc.HTTPNotImplemented())
+
+
+def resource_factory():
+ metadata = {
+ 'attributes': {
+ 'backupSchedule': [],
+ },
+ }
+
+ serializers = {
+ 'application/xml': wsgi.XMLSerializer(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 1a77f25d7..36d570803 100644
--- a/nova/api/openstack/consoles.py
+++ b/nova/api/openstack/consoles.py
@@ -19,8 +19,8 @@ from webob import exc
from nova import console
from nova import exception
-from nova.api.openstack import common
from nova.api.openstack import faults
+from nova.api.openstack import wsgi
def _translate_keys(cons):
@@ -43,14 +43,9 @@ def _translate_detail_keys(cons):
return dict(console=info)
-class Controller(common.OpenstackController):
+class Controller(object):
"""The Consoles Controller for the Openstack API"""
- _serialization_metadata = {
- 'application/xml': {
- 'attributes': {
- 'console': []}}}
-
def __init__(self):
self.console_api = console.API()
super(Controller, self).__init__()
@@ -63,9 +58,8 @@ class Controller(common.OpenstackController):
return dict(consoles=[_translate_keys(console)
for console in consoles])
- def create(self, req, server_id):
+ def create(self, req, server_id, body):
"""Creates a new console"""
- #info = self._deserialize(req.body, req.get_content_type())
self.console_api.create_console(
req.environ['nova.context'],
int(server_id))
@@ -94,3 +88,17 @@ class Controller(common.OpenstackController):
except exception.NotFound:
return faults.Fault(exc.HTTPNotFound())
return exc.HTTPAccepted()
+
+
+def resource_factory():
+ metadata = {
+ 'attributes': {
+ 'console': [],
+ },
+ }
+
+ serializers = {
+ 'application/xml': wsgi.XMLSerializer(metadata=metadata),
+ }
+
+ return wsgi.Resource(Controller(), serializers=serializers)
diff --git a/nova/api/openstack/flavors.py b/nova/api/openstack/flavors.py
index 4c5971cf6..46056a27a 100644
--- a/nova/api/openstack/flavors.py
+++ b/nova/api/openstack/flavors.py
@@ -19,22 +19,13 @@ import webob
from nova import db
from nova import exception
-from nova.api.openstack import common
from nova.api.openstack import views
+from nova.api.openstack import wsgi
-class Controller(common.OpenstackController):
+class Controller(object):
"""Flavor controller for the OpenStack API."""
- _serialization_metadata = {
- 'application/xml': {
- "attributes": {
- "flavor": ["id", "name", "ram", "disk"],
- "link": ["rel", "type", "href"],
- }
- }
- }
-
def index(self, req):
"""Return all flavors in brief."""
items = self._get_flavors(req, is_detail=False)
@@ -71,14 +62,31 @@ class Controller(common.OpenstackController):
class ControllerV10(Controller):
+
def _get_view_builder(self, req):
return views.flavors.ViewBuilder()
class ControllerV11(Controller):
+
def _get_view_builder(self, req):
base_url = req.application_url
return views.flavors.ViewBuilderV11(base_url)
- def get_default_xmlns(self, req):
- return common.XML_NS_V11
+
+def resource_factory(version='1.0'):
+ controller = {
+ '1.0': ControllerV10,
+ '1.1': ControllerV11,
+ }[version]()
+
+ xmlns = {
+ '1.0': wsgi.XMLNS_V10,
+ '1.1': wsgi.XMLNS_V11,
+ }[version]
+
+ serializers = {
+ 'application/xml': wsgi.XMLSerializer(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 1eccc0174..ce0140265 100644
--- a/nova/api/openstack/image_metadata.py
+++ b/nova/api/openstack/image_metadata.py
@@ -21,19 +21,18 @@ from nova import flags
from nova import quota
from nova import utils
from nova import wsgi
-from nova.api.openstack import common
from nova.api.openstack import faults
+from nova.api.openstack import wsgi
FLAGS = flags.FLAGS
-class Controller(common.OpenstackController):
+class Controller(object):
"""The image metadata API controller for the Openstack API"""
def __init__(self):
self.image_service = utils.import_object(FLAGS.image_service)
- super(Controller, self).__init__()
def _get_metadata(self, context, image_id, image=None):
if not image:
@@ -64,9 +63,8 @@ class Controller(common.OpenstackController):
else:
return faults.Fault(exc.HTTPNotFound())
- def create(self, req, image_id):
+ def create(self, req, image_id, body):
context = req.environ['nova.context']
- body = self._deserialize(req.body, req.get_content_type())
img = self.image_service.show(context, image_id)
metadata = self._get_metadata(context, image_id, img)
if 'metadata' in body:
@@ -77,9 +75,8 @@ class Controller(common.OpenstackController):
self.image_service.update(context, image_id, img, None)
return dict(metadata=metadata)
- def update(self, req, image_id, id):
+ def update(self, req, image_id, id, body):
context = req.environ['nova.context']
- body = self._deserialize(req.body, req.get_content_type())
if not id in body:
expl = _('Request body and URI mismatch')
raise exc.HTTPBadRequest(explanation=expl)
@@ -104,3 +101,11 @@ class Controller(common.OpenstackController):
metadata.pop(id)
img['properties'] = metadata
self.image_service.update(context, image_id, img, None)
+
+
+def resource_factory():
+ serializers = {
+ 'application/xml': wsgi.XMLSerializer(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 34d4c27fc..e22854ebf 100644
--- a/nova/api/openstack/images.py
+++ b/nova/api/openstack/images.py
@@ -23,25 +23,16 @@ from nova import utils
from nova.api.openstack import common
from nova.api.openstack import faults
from nova.api.openstack.views import images as images_view
+from nova.api.openstack import wsgi
LOG = log.getLogger('nova.api.openstack.images')
FLAGS = flags.FLAGS
-class Controller(common.OpenstackController):
+class Controller(object):
"""Base `wsgi.Controller` for retrieving/displaying images."""
- _serialization_metadata = {
- 'application/xml': {
- "attributes": {
- "image": ["id", "name", "updated", "created", "status",
- "serverId", "progress"],
- "link": ["rel", "type", "href"],
- },
- },
- }
-
def __init__(self, image_service=None, compute_service=None):
"""Initialize new `ImageController`.
@@ -153,3 +144,30 @@ class ControllerV11(Controller):
def get_default_xmlns(self, req):
return common.XML_NS_V11
+
+
+def resource_factory(version='1.0'):
+ controller = {
+ '1.0': ControllerV10,
+ '1.1': ControllerV11,
+ }[version]()
+
+ xmlns = {
+ '1.0': wsgi.XMLNS_V10,
+ '1.1': wsgi.XMLNS_V11,
+ }[version]
+
+ metadata = {
+ "attributes": {
+ "image": ["id", "name", "updated", "created", "status",
+ "serverId", "progress"],
+ "link": ["rel", "type", "href"],
+ },
+ }
+
+ serializers = {
+ 'application/xml': wsgi.XMLSerializer(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 778e9ba1a..24612eafb 100644
--- a/nova/api/openstack/ips.py
+++ b/nova/api/openstack/ips.py
@@ -20,23 +20,14 @@ import time
from webob import exc
import nova
-import nova.api.openstack.views.addresses
-from nova.api.openstack import common
from nova.api.openstack import faults
+import nova.api.openstack.views.addresses
+from nova.api.openstack import wsgi
-class Controller(common.OpenstackController):
+class Controller(object):
"""The servers addresses API controller for the Openstack API."""
- _serialization_metadata = {
- 'application/xml': {
- 'list_collections': {
- 'public': {'item_name': 'ip', 'item_key': 'addr'},
- 'private': {'item_name': 'ip', 'item_key': 'addr'},
- },
- },
- }
-
def __init__(self):
self.compute_api = nova.compute.API()
self.builder = nova.api.openstack.views.addresses.ViewBuilderV10()
@@ -65,8 +56,24 @@ class Controller(common.OpenstackController):
def show(self, req, server_id, id):
return faults.Fault(exc.HTTPNotImplemented())
- def create(self, req, server_id):
+ def create(self, req, server_id, body):
return faults.Fault(exc.HTTPNotImplemented())
def delete(self, req, server_id, id):
return faults.Fault(exc.HTTPNotImplemented())
+
+
+def resource_factory():
+ metadata = {
+ 'list_collections': {
+ 'public': {'item_name': 'ip', 'item_key': 'addr'},
+ 'private': {'item_name': 'ip', 'item_key': 'addr'},
+ },
+ }
+
+ serializers = {
+ 'application/xml': wsgi.XMLSerializer(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 47bc238f1..306048d8f 100644
--- a/nova/api/openstack/limits.py
+++ b/nova/api/openstack/limits.py
@@ -30,10 +30,11 @@ from collections import defaultdict
from webob.dec import wsgify
-from nova import wsgi
+from nova import wsgi as base_wsgi
from nova.api.openstack import common
from nova.api.openstack import faults
from nova.api.openstack.views import limits as limits_views
+from nova.api.openstack import wsgi
# Convenience constants for the limits dictionary passed to Limiter().
@@ -43,23 +44,11 @@ PER_HOUR = 60 * 60
PER_DAY = 60 * 60 * 24
-class LimitsController(common.OpenstackController):
+class LimitsController(object):
"""
Controller for accessing limits in the OpenStack API.
"""
- _serialization_metadata = {
- "application/xml": {
- "attributes": {
- "limit": ["verb", "URI", "uri", "regex", "value", "unit",
- "resetTime", "next-available", "remaining", "name"],
- },
- "plurals": {
- "rate": "limit",
- },
- },
- }
-
def index(self, req):
"""
Return all global and rate limit information.
@@ -84,6 +73,35 @@ class LimitsControllerV11(LimitsController):
return limits_views.ViewBuilderV11()
+def resource_factory(version='1.0'):
+ controller = {
+ '1.0': LimitsControllerV10,
+ '1.1': LimitsControllerV11,
+ }[version]()
+
+ xmlns = {
+ '1.0': wsgi.XMLNS_V10,
+ '1.1': wsgi.XMLNS_V11,
+ }[version]
+
+ metadata = {
+ "attributes": {
+ "limit": ["verb", "URI", "uri", "regex", "value", "unit",
+ "resetTime", "next-available", "remaining", "name"],
+ },
+ "plurals": {
+ "rate": "limit",
+ },
+ }
+
+ serializers = {
+ 'application/xml': wsgi.XMLSerializer(xmlns=xmlns,
+ metadata=metadata)
+ }
+
+ return wsgi.Resource(controller, serializers=serializers)
+
+
class Limit(object):
"""
Stores information about a limit for HTTP requets.
@@ -195,7 +213,7 @@ DEFAULT_LIMITS = [
]
-class RateLimitingMiddleware(wsgi.Middleware):
+class RateLimitingMiddleware(base_wsgi.Middleware):
"""
Rate-limits requests passing through this middleware. All limit information
is stored in memory for this implementation.
@@ -209,7 +227,7 @@ class RateLimitingMiddleware(wsgi.Middleware):
@param application: WSGI application to wrap
@param limits: List of dictionaries describing limits
"""
- wsgi.Middleware.__init__(self, application)
+ base_wsgi.Middleware.__init__(self, application)
self._limiter = Limiter(limits or DEFAULT_LIMITS)
@wsgify(RequestClass=wsgi.Request)
diff --git a/nova/api/openstack/server_metadata.py b/nova/api/openstack/server_metadata.py
index fd64ee4fb..fb9449b4c 100644
--- a/nova/api/openstack/server_metadata.py
+++ b/nova/api/openstack/server_metadata.py
@@ -19,12 +19,11 @@ from webob import exc
from nova import compute
from nova import quota
-from nova import wsgi
-from nova.api.openstack import common
from nova.api.openstack import faults
+from nova.api.openstack import wsgi
-class Controller(common.OpenstackController):
+class Controller(object):
""" The server metadata API controller for the Openstack API """
def __init__(self):
@@ -43,10 +42,9 @@ class Controller(common.OpenstackController):
context = req.environ['nova.context']
return self._get_metadata(context, server_id)
- def create(self, req, server_id):
+ def create(self, req, server_id, body):
context = req.environ['nova.context']
- data = self._deserialize(req.body, req.get_content_type())
- metadata = data.get('metadata')
+ metadata = body.get('metadata')
try:
self.compute_api.update_or_create_instance_metadata(context,
server_id,
@@ -55,9 +53,8 @@ class Controller(common.OpenstackController):
self._handle_quota_error(error)
return req.body
- def update(self, req, server_id, id):
+ def update(self, req, server_id, id, body):
context = req.environ['nova.context']
- body = self._deserialize(req.body, req.get_content_type())
if not id in body:
expl = _('Request body and URI mismatch')
raise exc.HTTPBadRequest(explanation=expl)
@@ -92,3 +89,11 @@ class Controller(common.OpenstackController):
if error.code == "MetadataLimitExceeded":
raise exc.HTTPBadRequest(explanation=error.message)
raise error
+
+
+def resource_factory():
+ serializers = {
+ 'application/xml': wsgi.XMLSerializer(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 8f2de2afe..78f8bb1b7 100644
--- a/nova/api/openstack/servers.py
+++ b/nova/api/openstack/servers.py
@@ -31,6 +31,7 @@ import nova.api.openstack.views.addresses
import nova.api.openstack.views.flavors
import nova.api.openstack.views.images
import nova.api.openstack.views.servers
+from nova.api.openstack import wsgi
from nova.auth import manager as auth_manager
from nova.compute import instance_types
import nova.api.openstack
@@ -41,31 +42,12 @@ LOG = logging.getLogger('nova.api.openstack.servers')
FLAGS = flags.FLAGS
-class Controller(common.OpenstackController):
+class Controller(object):
""" The Server API controller for the OpenStack API """
- _serialization_metadata = {
- "application/xml": {
- "attributes": {
- "server": ["id", "imageId", "name", "flavorId", "hostId",
- "status", "progress", "adminPass", "flavorRef",
- "imageRef"],
- "link": ["rel", "type", "href"],
- },
- "dict_collections": {
- "metadata": {"item_name": "meta", "item_key": "key"},
- },
- "list_collections": {
- "public": {"item_name": "ip", "item_key": "addr"},
- "private": {"item_name": "ip", "item_key": "addr"},
- },
- },
- }
-
def __init__(self):
self.compute_api = compute.API()
self._image_service = utils.import_object(FLAGS.image_service)
- super(Controller, self).__init__()
def index(self, req):
""" Returns a list of server names and ids for a given user """
@@ -122,15 +104,14 @@ class Controller(common.OpenstackController):
return faults.Fault(exc.HTTPNotFound())
return exc.HTTPAccepted()
- def create(self, req):
+ def create(self, req, body):
""" Creates a new server for a given user """
- env = self._deserialize_create(req)
- if not env:
+ if not body:
return faults.Fault(exc.HTTPUnprocessableEntity())
context = req.environ['nova.context']
- password = self._get_server_admin_password(env['server'])
+ password = self._get_server_admin_password(body['server'])
key_name = None
key_data = None
@@ -140,7 +121,7 @@ class Controller(common.OpenstackController):
key_name = key_pair['name']
key_data = key_pair['public_key']
- requested_image_id = self._image_id_from_req_data(env)
+ requested_image_id = self._image_id_from_req_data(body)
try:
image_id = common.get_image_id_from_image_hash(self._image_service,
context, requested_image_id)
@@ -151,18 +132,18 @@ class Controller(common.OpenstackController):
kernel_id, ramdisk_id = self._get_kernel_ramdisk_from_image(
req, image_id)
- personality = env['server'].get('personality')
+ personality = body['server'].get('personality')
injected_files = []
if personality:
injected_files = self._get_injected_files(personality)
- flavor_id = self._flavor_id_from_req_data(env)
+ flavor_id = self._flavor_id_from_req_data(body)
- if not 'name' in env['server']:
+ if not 'name' in body['server']:
msg = _("Server name is not defined")
return exc.HTTPBadRequest(msg)
- name = env['server']['name']
+ name = body['server']['name']
self._validate_server_name(name)
name = name.strip()
@@ -179,7 +160,7 @@ class Controller(common.OpenstackController):
display_description=name,
key_name=key_name,
key_data=key_data,
- metadata=env['server'].get('metadata', {}),
+ metadata=body['server'].get('metadata', {}),
injected_files=injected_files)
except quota.QuotaError as error:
self._handle_quota_error(error)
@@ -194,18 +175,6 @@ class Controller(common.OpenstackController):
password)
return server
- def _deserialize_create(self, request):
- """
- Deserialize a create request
-
- Overrides normal behavior in the case of xml content
- """
- if request.content_type == "application/xml":
- deserializer = ServerCreateRequestXMLDeserializer()
- return deserializer.deserialize(request.body)
- else:
- return self._deserialize(request.body, request.get_content_type())
-
def _get_injected_files(self, personality):
"""
Create a list of injected files from the personality attribute
@@ -255,24 +224,23 @@ class Controller(common.OpenstackController):
return utils.generate_password(16)
@scheduler_api.redirect_handler
- def update(self, req, id):
+ def update(self, req, id, body):
""" Updates the server name or password """
if len(req.body) == 0:
raise exc.HTTPUnprocessableEntity()
- inst_dict = self._deserialize(req.body, req.get_content_type())
- if not inst_dict:
+ if not body:
return faults.Fault(exc.HTTPUnprocessableEntity())
ctxt = req.environ['nova.context']
update_dict = {}
- if 'name' in inst_dict['server']:
- name = inst_dict['server']['name']
+ if 'name' in body['server']:
+ name = body['server']['name']
self._validate_server_name(name)
update_dict['display_name'] = name.strip()
- self._parse_update(ctxt, id, inst_dict, update_dict)
+ self._parse_update(ctxt, id, body, update_dict)
try:
self.compute_api.update(ctxt, id, **update_dict)
@@ -294,7 +262,7 @@ class Controller(common.OpenstackController):
pass
@scheduler_api.redirect_handler
- def action(self, req, id):
+ def action(self, req, id, body):
"""Multi-purpose method used to reboot, rebuild, or
resize a server"""
@@ -307,10 +275,9 @@ class Controller(common.OpenstackController):
'rebuild': self._action_rebuild,
}
- input_dict = self._deserialize(req.body, req.get_content_type())
for key in actions.keys():
- if key in input_dict:
- return actions[key](input_dict, req, id)
+ if key in body:
+ return actions[key](body, req, id)
return faults.Fault(exc.HTTPNotImplemented())
def _action_change_password(self, input_dict, req, id):
@@ -410,7 +377,7 @@ class Controller(common.OpenstackController):
return exc.HTTPAccepted()
@scheduler_api.redirect_handler
- def reset_network(self, req, id):
+ def reset_network(self, req, id, body):
"""
Reset networking on an instance (admin only).
@@ -425,7 +392,7 @@ class Controller(common.OpenstackController):
return exc.HTTPAccepted()
@scheduler_api.redirect_handler
- def inject_network_info(self, req, id):
+ def inject_network_info(self, req, id, body):
"""
Inject network info for an instance (admin only).
@@ -440,7 +407,7 @@ class Controller(common.OpenstackController):
return exc.HTTPAccepted()
@scheduler_api.redirect_handler
- def pause(self, req, id):
+ def pause(self, req, id, body):
""" Permit Admins to Pause the server. """
ctxt = req.environ['nova.context']
try:
@@ -452,7 +419,7 @@ class Controller(common.OpenstackController):
return exc.HTTPAccepted()
@scheduler_api.redirect_handler
- def unpause(self, req, id):
+ def unpause(self, req, id, body):
""" Permit Admins to Unpause the server. """
ctxt = req.environ['nova.context']
try:
@@ -464,7 +431,7 @@ class Controller(common.OpenstackController):
return exc.HTTPAccepted()
@scheduler_api.redirect_handler
- def suspend(self, req, id):
+ def suspend(self, req, id, body):
"""permit admins to suspend the server"""
context = req.environ['nova.context']
try:
@@ -476,7 +443,7 @@ class Controller(common.OpenstackController):
return exc.HTTPAccepted()
@scheduler_api.redirect_handler
- def resume(self, req, id):
+ def resume(self, req, id, body):
"""permit admins to resume the server from suspend"""
context = req.environ['nova.context']
try:
@@ -815,3 +782,44 @@ class ServerCreateRequestXMLDeserializer(object):
if child.nodeType == child.TEXT_NODE:
return child.nodeValue
return ""
+
+
+def resource_factory(version='1.0'):
+ controller = {
+ '1.0': ControllerV10,
+ '1.1': ControllerV11,
+ }[version]()
+
+ metadata = {
+ "attributes": {
+ "server": ["id", "imageId", "name", "flavorId", "hostId",
+ "status", "progress", "adminPass", "flavorRef",
+ "imageRef"],
+ "link": ["rel", "type", "href"],
+ },
+ "dict_collections": {
+ "metadata": {"item_name": "meta", "item_key": "key"},
+ },
+ "list_collections": {
+ "public": {"item_name": "ip", "item_key": "addr"},
+ "private": {"item_name": "ip", "item_key": "addr"},
+ },
+ }
+
+ xmlns = {
+ '1.0': wsgi.XMLNS_V10,
+ '1.1': wsgi.XMLNS_V11,
+ }[version]
+
+ serializers = {
+ 'application/xml': wsgi.XMLSerializer(metadata=metadata,
+ xmlns=xmlns),
+ }
+
+ deserializers = {
+ 'application/xml': ServerCreateRequestXMLDeserializer(),
+ }
+
+ return wsgi.Resource(controller, serializers=serializers,
+ deserializers=deserializers)
+
diff --git a/nova/api/openstack/shared_ip_groups.py b/nova/api/openstack/shared_ip_groups.py
index 996db3648..db178f2a2 100644
--- a/nova/api/openstack/shared_ip_groups.py
+++ b/nova/api/openstack/shared_ip_groups.py
@@ -17,29 +17,13 @@
from webob import exc
-from nova.api.openstack import common
from nova.api.openstack import faults
+from nova.api.openstack import wsgi
-def _translate_keys(inst):
- """ Coerces a shared IP group instance into proper dictionary format """
- return dict(sharedIpGroup=inst)
-
-
-def _translate_detail_keys(inst):
- """ Coerces a shared IP group instance into proper dictionary format with
- correctly mapped attributes """
- return dict(sharedIpGroups=inst)
-
-
-class Controller(common.OpenstackController):
+class Controller(object):
""" The Shared IP Groups Controller for the Openstack API """
- _serialization_metadata = {
- 'application/xml': {
- 'attributes': {
- 'sharedIpGroup': []}}}
-
def index(self, req):
""" Returns a list of Shared IP Groups for the user """
raise faults.Fault(exc.HTTPNotImplemented())
@@ -48,7 +32,7 @@ class Controller(common.OpenstackController):
""" Shows in-depth information on a specific Shared IP Group """
raise faults.Fault(exc.HTTPNotImplemented())
- def update(self, req, id):
+ def update(self, req, id, body):
""" You can't update a Shared IP Group """
raise faults.Fault(exc.HTTPNotImplemented())
@@ -60,6 +44,10 @@ class Controller(common.OpenstackController):
""" Returns a complete list of Shared IP Groups """
raise faults.Fault(exc.HTTPNotImplemented())
- def create(self, req):
+ def create(self, req, body):
""" Creates a new Shared IP group """
raise faults.Fault(exc.HTTPNotImplemented())
+
+
+def resource_factory():
+ return wsgi.Resource(Controller())
diff --git a/nova/api/openstack/users.py b/nova/api/openstack/users.py
index 7ae4c3232..35b6a502e 100644
--- a/nova/api/openstack/users.py
+++ b/nova/api/openstack/users.py
@@ -20,8 +20,10 @@ from nova import flags
from nova import log as logging
from nova.api.openstack import common
from nova.api.openstack import faults
+from nova.api.openstack import wsgi
from nova.auth import manager
+
FLAGS = flags.FLAGS
LOG = logging.getLogger('nova.api.openstack')
@@ -34,12 +36,7 @@ def _translate_keys(user):
admin=user.admin)
-class Controller(common.OpenstackController):
-
- _serialization_metadata = {
- 'application/xml': {
- "attributes": {
- "user": ["id", "name", "access", "secret", "admin"]}}}
+class Controller(object):
def __init__(self):
self.manager = manager.AuthManager()
@@ -81,23 +78,35 @@ class Controller(common.OpenstackController):
self.manager.delete_user(id)
return {}
- def create(self, req):
+ def create(self, req, body):
self._check_admin(req.environ['nova.context'])
- env = self._deserialize(req.body, req.get_content_type())
- is_admin = env['user'].get('admin') in ('T', 'True', True)
- name = env['user'].get('name')
- access = env['user'].get('access')
- secret = env['user'].get('secret')
+ is_admin = body['user'].get('admin') in ('T', 'True', True)
+ name = body['user'].get('name')
+ access = body['user'].get('access')
+ secret = body['user'].get('secret')
user = self.manager.create_user(name, access, secret, is_admin)
return dict(user=_translate_keys(user))
- def update(self, req, id):
+ def update(self, req, id, body):
self._check_admin(req.environ['nova.context'])
- env = self._deserialize(req.body, req.get_content_type())
- is_admin = env['user'].get('admin')
+ is_admin = body['user'].get('admin')
if is_admin is not None:
is_admin = is_admin in ('T', 'True', True)
- access = env['user'].get('access')
- secret = env['user'].get('secret')
+ access = body['user'].get('access')
+ secret = body['user'].get('secret')
self.manager.modify_user(id, access, secret, is_admin)
return dict(user=_translate_keys(self.manager.get_user(id)))
+
+
+def resource_factory():
+ metadata = {
+ "attributes": {
+ "user": ["id", "name", "access", "secret", "admin"],
+ },
+ }
+
+ serializers = {
+ 'application/xml': wsgi.XMLSerializer(metadata=metadata),
+ }
+
+ return wsgi.Resource(Controller(), serializers=serializers)
diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py
new file mode 100644
index 000000000..9e0077932
--- /dev/null
+++ b/nova/api/openstack/wsgi.py
@@ -0,0 +1,291 @@
+
+import json
+import webob
+from xml.dom import minidom
+
+from nova import exception
+from nova import log as logging
+from nova import utils
+
+
+XMLNS_V10 = 'http://docs.rackspacecloud.com/servers/api/v1.0'
+XMLNS_V11 = 'http://docs.openstack.org/compute/api/v1.1'
+
+LOG = logging.getLogger('nova.api.openstack.wsgi')
+
+
+class Request(webob.Request):
+ def best_match_content_type(self, supported=None):
+ """Determine the requested content-type.
+
+ Based on the query extension then the Accept header.
+
+ :param supported: list of content-types to override defaults
+
+ """
+ supported = supported or ['application/json', 'application/xml']
+ parts = self.path.rsplit('.', 1)
+
+ if len(parts) > 1:
+ ctype = 'application/{0}'.format(parts[1])
+ if ctype in supported:
+ return ctype
+
+ bm = self.accept.best_match(supported)
+
+ return bm or 'application/json'
+
+ def get_content_type(self):
+ if not "Content-Type" in self.headers:
+ raise exception.InvalidContentType(content_type=None)
+
+ allowed_types = ("application/xml", "application/json")
+ type = self.content_type
+
+ if type not in allowed_types:
+ raise exception.InvalidContentType(content_type=type)
+ else:
+ return type
+
+
+class JSONDeserializer(object):
+ def deserialize(self, datastring):
+ return utils.loads(datastring)
+
+
+class JSONSerializer(object):
+ def serialize(self, data):
+ return utils.dumps(data)
+
+
+class XMLDeserializer(object):
+ def __init__(self, metadata=None):
+ """
+ :param metadata: information needed to deserialize xml into
+ a dictionary.
+ """
+ super(XMLDeserializer, self).__init__()
+ self.metadata = metadata or {}
+
+ def deserialize(self, datastring):
+ """XML deserialization entry point."""
+ plurals = set(self.metadata.get('plurals', {}))
+ node = minidom.parseString(datastring).childNodes[0]
+ return {node.nodeName: self._from_xml_node(node, plurals)}
+
+ def _from_xml_node(self, node, listnames):
+ """Convert a minidom node to a simple Python type.
+
+ :param listnames: list of XML node names whose subnodes should
+ be considered list items.
+
+ """
+ if len(node.childNodes) == 1 and node.childNodes[0].nodeType == 3:
+ return node.childNodes[0].nodeValue
+ elif node.nodeName in listnames:
+ return [self._from_xml_node(n, listnames) for n in node.childNodes]
+ else:
+ result = dict()
+ for attr in node.attributes.keys():
+ result[attr] = node.attributes[attr].nodeValue
+ for child in node.childNodes:
+ if child.nodeType != node.TEXT_NODE:
+ result[child.nodeName] = self._from_xml_node(child,
+ listnames)
+ return result
+
+
+class XMLSerializer(object):
+ 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__()
+ self.metadata = metadata or {}
+ self.xmlns = xmlns
+
+ def serialize(self, data):
+ # We expect data to contain a single key which is the XML root.
+ root_key = data.keys()[0]
+ doc = minidom.Document()
+ node = self._to_xml_node(doc, self.metadata, root_key, data[root_key])
+
+ xmlns = node.getAttribute('xmlns')
+ if not xmlns and self.xmlns:
+ node.setAttribute('xmlns', self.xmlns)
+
+ return node.toprettyxml(indent=' ')
+
+ def _to_xml_node(self, doc, metadata, nodename, data):
+ """Recursive method to convert data members to XML nodes."""
+ result = doc.createElement(nodename)
+
+ # Set the xml namespace if one is specified
+ # TODO(justinsb): We could also use prefixes on the keys
+ xmlns = metadata.get('xmlns', None)
+ if xmlns:
+ result.setAttribute('xmlns', xmlns)
+
+ if type(data) is list:
+ collections = metadata.get('list_collections', {})
+ if nodename in collections:
+ metadata = collections[nodename]
+ for item in data:
+ node = doc.createElement(metadata['item_name'])
+ node.setAttribute(metadata['item_key'], str(item))
+ result.appendChild(node)
+ return result
+ singular = metadata.get('plurals', {}).get(nodename, None)
+ if singular is None:
+ if nodename.endswith('s'):
+ singular = nodename[:-1]
+ else:
+ singular = 'item'
+ for item in data:
+ node = self._to_xml_node(doc, metadata, singular, item)
+ result.appendChild(node)
+ elif type(data) is dict:
+ collections = metadata.get('dict_collections', {})
+ if nodename in collections:
+ metadata = collections[nodename]
+ for k, v in data.items():
+ node = doc.createElement(metadata['item_name'])
+ node.setAttribute(metadata['item_key'], str(k))
+ text = doc.createTextNode(str(v))
+ node.appendChild(text)
+ result.appendChild(node)
+ return result
+ attrs = metadata.get('attributes', {}).get(nodename, {})
+ for k, v in data.items():
+ if k in attrs:
+ result.setAttribute(k, str(v))
+ else:
+ node = self._to_xml_node(doc, metadata, k, v)
+ result.appendChild(node)
+ else:
+ # Type is atom
+ node = doc.createTextNode(str(data))
+ result.appendChild(node)
+ return result
+
+
+class Resource(object):
+ """WSGI app that dispatched to methods.
+
+ 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, controller, serializers=None, deserializers=None):
+ self.serializers = {
+ 'application/xml': XMLSerializer(),
+ 'application/json': JSONSerializer(),
+ }
+ 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"))
+
+ controller_method = getattr(self.controller, action)
+ result = controller_method(req=request, **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 serialize_response(self, content_type, response_body):
+ """Serialize a dict into a string and wrap in a wsgi.Request object.
+
+ :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)
+
+ return response
+
+ def get_serializer(self, content_type):
+ try:
+ return self.serializers[content_type]
+ 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
+
+ """
+ action_args = self.get_action_args(request.environ)
+ action = action_args.pop('action')
+
+ 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:
+ action_args['body'] = deserializer.deserialize(request.body)
+ except exception.InvalidContentType:
+ action_args['body'] = None
+
+ accept = self.get_expected_content_type(request)
+
+ return (action, action_args, accept)
+
+ def get_expected_content_type(self, request):
+ return request.best_match_content_type()
+
+ def get_action_args(self, request_environment):
+ args = request_environment['wsgiorg.routing_args'][1].copy()
+
+ del args['controller']
+
+ if 'format' in args:
+ del args['format']
+
+ return args
+
+ def get_deserializer(self, content_type):
+ try:
+ return self.deserializers[content_type]
+ except Exception:
+ raise exception.InvalidContentType(content_type=content_type)
diff --git a/nova/api/openstack/zones.py b/nova/api/openstack/zones.py
index 227ffecdc..d17ab7a9b 100644
--- a/nova/api/openstack/zones.py
+++ b/nova/api/openstack/zones.py
@@ -17,6 +17,7 @@ from nova import db
from nova import flags
from nova import log as logging
from nova.api.openstack import common
+from nova.api.openstack import wsgi
from nova.scheduler import api
@@ -41,12 +42,7 @@ def _scrub_zone(zone):
'deleted', 'deleted_at', 'updated_at'))
-class Controller(common.OpenstackController):
-
- _serialization_metadata = {
- 'application/xml': {
- "attributes": {
- "zone": ["id", "api_url", "name", "capabilities"]}}}
+class Controller(object):
def index(self, req):
"""Return all zones in brief"""
@@ -85,15 +81,28 @@ class Controller(common.OpenstackController):
api.zone_delete(req.environ['nova.context'], zone_id)
return {}
- def create(self, req):
+ def create(self, req, body):
context = req.environ['nova.context']
- env = self._deserialize(req.body, req.get_content_type())
- zone = api.zone_create(context, env["zone"])
+ zone = api.zone_create(context, body["zone"])
return dict(zone=_scrub_zone(zone))
- def update(self, req, id):
+ def update(self, req, id, body):
context = req.environ['nova.context']
- env = self._deserialize(req.body, req.get_content_type())
zone_id = int(id)
- zone = api.zone_update(context, zone_id, env["zone"])
+ zone = api.zone_update(context, zone_id, body["zone"])
return dict(zone=_scrub_zone(zone))
+
+
+def resource_factory():
+ metadata = {
+ "attributes": {
+ "zone": ["id", "api_url", "name", "capabilities"],
+ },
+ }
+
+ serializers = {
+ 'application/xml': wsgi.XMLSerializer(xmlns=wsgi.XMLNS_V10,
+ metadata=metadata),
+ }
+
+ return wsgi.Resource(Controller(), serializers=serializers)
diff --git a/nova/tests/api/openstack/test_limits.py b/nova/tests/api/openstack/test_limits.py
index 45bd4d501..db859c2f8 100644
--- a/nova/tests/api/openstack/test_limits.py
+++ b/nova/tests/api/openstack/test_limits.py
@@ -65,7 +65,7 @@ class LimitsControllerV10Test(BaseLimitTestSuite):
def setUp(self):
"""Run before each test."""
BaseLimitTestSuite.setUp(self)
- self.controller = limits.LimitsControllerV10()
+ self.controller = limits.resource_factory('1.0')
def _get_index_request(self, accept_header="application/json"):
"""Helper to set routing arguments."""
@@ -178,7 +178,7 @@ class LimitsControllerV11Test(BaseLimitTestSuite):
def setUp(self):
"""Run before each test."""
BaseLimitTestSuite.setUp(self)
- self.controller = limits.LimitsControllerV11()
+ self.controller = limits.resource_factory('1.1')
def _get_index_request(self, accept_header="application/json"):
"""Helper to set routing arguments."""
diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py
index e8182b6a9..15f376f74 100644
--- a/nova/tests/api/openstack/test_servers.py
+++ b/nova/tests/api/openstack/test_servers.py
@@ -207,7 +207,6 @@ class ServersTest(test.TestCase):
},
]
- print res_dict['server']
self.assertEqual(res_dict['server']['links'], expected_links)
def test_get_server_by_id_with_addresses_xml(self):
@@ -831,7 +830,6 @@ class ServersTest(test.TestCase):
req = webob.Request.blank('/v1.0/servers/detail')
req.headers['Accept'] = 'application/xml'
res = req.get_response(fakes.wsgi_app())
- print res.body
dom = minidom.parseString(res.body)
for i, server in enumerate(dom.getElementsByTagName('server')):
self.assertEqual(server.getAttribute('id'), str(i))
diff --git a/nova/tests/api/test_wsgi.py b/nova/tests/api/test_wsgi.py
index 5820ecdc2..0be3aecf1 100644
--- a/nova/tests/api/test_wsgi.py
+++ b/nova/tests/api/test_wsgi.py
@@ -121,138 +121,3 @@ class ControllerTest(test.TestCase):
result = request.get_response(self.TestRouter())
self.assertEqual(result.status_int, 200)
self.assertEqual(result.headers["Content-Type"], "application/json")
-
-
-class RequestTest(test.TestCase):
-
- def test_request_content_type_missing(self):
- request = wsgi.Request.blank('/tests/123')
- request.body = ""
- self.assertRaises(webob.exc.HTTPBadRequest, request.get_content_type)
-
- def test_request_content_type_unsupported(self):
- request = wsgi.Request.blank('/tests/123')
- request.headers["Content-Type"] = "text/html"
- request.body = "asdf
"
- self.assertRaises(webob.exc.HTTPBadRequest, request.get_content_type)
-
- def test_request_content_type_with_charset(self):
- request = wsgi.Request.blank('/tests/123')
- request.headers["Content-Type"] = "application/json; charset=UTF-8"
- result = request.get_content_type()
- self.assertEqual(result, "application/json")
-
- def test_content_type_from_accept_xml(self):
- request = wsgi.Request.blank('/tests/123')
- request.headers["Accept"] = "application/xml"
- result = request.best_match_content_type()
- self.assertEqual(result, "application/xml")
-
- request = wsgi.Request.blank('/tests/123')
- request.headers["Accept"] = "application/json"
- result = request.best_match_content_type()
- self.assertEqual(result, "application/json")
-
- request = wsgi.Request.blank('/tests/123')
- request.headers["Accept"] = "application/xml, application/json"
- result = request.best_match_content_type()
- self.assertEqual(result, "application/json")
-
- request = wsgi.Request.blank('/tests/123')
- request.headers["Accept"] = \
- "application/json; q=0.3, application/xml; q=0.9"
- result = request.best_match_content_type()
- self.assertEqual(result, "application/xml")
-
- def test_content_type_from_query_extension(self):
- request = wsgi.Request.blank('/tests/123.xml')
- result = request.best_match_content_type()
- self.assertEqual(result, "application/xml")
-
- request = wsgi.Request.blank('/tests/123.json')
- result = request.best_match_content_type()
- self.assertEqual(result, "application/json")
-
- request = wsgi.Request.blank('/tests/123.invalid')
- result = request.best_match_content_type()
- self.assertEqual(result, "application/json")
-
- def test_content_type_accept_and_query_extension(self):
- request = wsgi.Request.blank('/tests/123.xml')
- request.headers["Accept"] = "application/json"
- result = request.best_match_content_type()
- self.assertEqual(result, "application/xml")
-
- def test_content_type_accept_default(self):
- request = wsgi.Request.blank('/tests/123.unsupported')
- request.headers["Accept"] = "application/unsupported1"
- result = request.best_match_content_type()
- self.assertEqual(result, "application/json")
-
-
-class SerializerTest(test.TestCase):
-
- def test_xml(self):
- input_dict = dict(servers=dict(a=(2, 3)))
- expected_xml = '(2,3)'
- serializer = wsgi.Serializer()
- result = serializer.serialize(input_dict, "application/xml")
- result = result.replace('\n', '').replace(' ', '')
- self.assertEqual(result, expected_xml)
-
- def test_json(self):
- input_dict = dict(servers=dict(a=(2, 3)))
- expected_json = '{"servers":{"a":[2,3]}}'
- serializer = wsgi.Serializer()
- result = serializer.serialize(input_dict, "application/json")
- result = result.replace('\n', '').replace(' ', '')
- self.assertEqual(result, expected_json)
-
- def test_unsupported_content_type(self):
- serializer = wsgi.Serializer()
- self.assertRaises(exception.InvalidContentType, serializer.serialize,
- {}, "text/null")
-
- def test_deserialize_json(self):
- data = """{"a": {
- "a1": "1",
- "a2": "2",
- "bs": ["1", "2", "3", {"c": {"c1": "1"}}],
- "d": {"e": "1"},
- "f": "1"}}"""
- as_dict = dict(a={
- 'a1': '1',
- 'a2': '2',
- 'bs': ['1', '2', '3', {'c': dict(c1='1')}],
- 'd': {'e': '1'},
- 'f': '1'})
- metadata = {}
- serializer = wsgi.Serializer(metadata)
- self.assertEqual(serializer.deserialize(data, "application/json"),
- as_dict)
-
- def test_deserialize_xml(self):
- xml = """
-
- 123
- 1
- 1
-
- """.strip()
- as_dict = dict(a={
- 'a1': '1',
- 'a2': '2',
- 'bs': ['1', '2', '3', {'c': dict(c1='1')}],
- 'd': {'e': '1'},
- 'f': '1'})
- metadata = {'application/xml': dict(plurals={'bs': 'b', 'ts': 't'})}
- serializer = wsgi.Serializer(metadata)
- self.assertEqual(serializer.deserialize(xml, "application/xml"),
- as_dict)
-
- def test_deserialize_empty_xml(self):
- xml = """"""
- as_dict = {"a": {}}
- serializer = wsgi.Serializer()
- self.assertEqual(serializer.deserialize(xml, "application/xml"),
- as_dict)
diff --git a/nova/tests/integrated/test_xml.py b/nova/tests/integrated/test_xml.py
index 8a9754777..fde32f797 100644
--- a/nova/tests/integrated/test_xml.py
+++ b/nova/tests/integrated/test_xml.py
@@ -32,7 +32,7 @@ class XmlTests(integrated_helpers._IntegratedTestBase):
""""Some basic XML sanity checks."""
def test_namespace_limits(self):
- """/limits should have v1.0 namespace (hasn't changed in 1.1)."""
+ """/limits should have v1.1 namespace (has changed in 1.1)."""
headers = {}
headers['Accept'] = 'application/xml'
@@ -40,7 +40,7 @@ class XmlTests(integrated_helpers._IntegratedTestBase):
data = response.read()
LOG.debug("data: %s" % data)
- prefix = '
Date: Wed, 18 May 2011 20:33:25 -0400
Subject: removing controller/serializer code from wsgi.py; updating other code
to use new modules
---
nova/api/direct.py | 12 +-
nova/api/openstack/common.py | 7 -
nova/api/openstack/consoles.py | 3 +-
nova/api/openstack/contrib/volumes.py | 23 +-
nova/api/openstack/extensions.py | 97 +++++----
nova/api/openstack/faults.py | 39 ++--
nova/api/openstack/image_metadata.py | 1 -
nova/api/openstack/images.py | 11 +-
nova/api/openstack/versions.py | 44 ++--
nova/api/openstack/wsgi.py | 23 +-
nova/objectstore/s3server.py | 2 +-
nova/tests/api/openstack/extensions/foxinsocks.py | 4 +-
nova/tests/api/openstack/test_extensions.py | 4 +-
nova/tests/api/test_wsgi.py | 54 -----
nova/wsgi.py | 250 +---------------------
15 files changed, 147 insertions(+), 427 deletions(-)
diff --git a/nova/api/direct.py b/nova/api/direct.py
index 8ceae299c..5e6c7c882 100644
--- a/nova/api/direct.py
+++ b/nova/api/direct.py
@@ -42,6 +42,7 @@ from nova import exception
from nova import flags
from nova import utils
from nova import wsgi
+import nova.api.openstack.wsgi
# Global storage for registering modules.
@@ -251,7 +252,7 @@ class Reflection(object):
return self._methods[method]
-class ServiceWrapper(wsgi.Controller):
+class ServiceWrapper(object):
"""Wrapper to dynamically povide a WSGI controller for arbitrary objects.
With lightweight introspection allows public methods on the object to
@@ -265,7 +266,7 @@ class ServiceWrapper(wsgi.Controller):
def __init__(self, service_handle):
self.service_handle = service_handle
- @webob.dec.wsgify(RequestClass=wsgi.Request)
+ @webob.dec.wsgify(RequestClass=nova.api.openstack.wsgi.Request)
def __call__(self, req):
arg_dict = req.environ['wsgiorg.routing_args'][1]
action = arg_dict['action']
@@ -289,8 +290,11 @@ class ServiceWrapper(wsgi.Controller):
try:
content_type = req.best_match_content_type()
- default_xmlns = self.get_default_xmlns(req)
- return self._serialize(result, content_type, default_xmlns)
+ serializer = {
+ 'application/xml': nova.api.openstack.wsgi.XMLSerializer(),
+ 'application/json': nova.api.openstack.wsgi.JSONSerializer(),
+ }[content_type]
+ return serializer.serialize(result)
except:
raise exception.Error("returned non-serializable type: %s"
% result)
diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py
index 32cd689ca..bb1a96812 100644
--- a/nova/api/openstack/common.py
+++ b/nova/api/openstack/common.py
@@ -23,7 +23,6 @@ import webob
from nova import exception
from nova import flags
from nova import log as logging
-from nova import wsgi
LOG = logging.getLogger('nova.api.openstack.common')
@@ -146,9 +145,3 @@ def get_id_from_href(href):
except:
LOG.debug(_("Error extracting id from href: %s") % href)
raise webob.exc.HTTPBadRequest(_('could not parse id from href'))
-
-
-class OpenstackController(wsgi.Controller):
- def get_default_xmlns(self, req):
- # Use V10 by default
- return XML_NS_V10
diff --git a/nova/api/openstack/consoles.py b/nova/api/openstack/consoles.py
index 36d570803..97304affe 100644
--- a/nova/api/openstack/consoles.py
+++ b/nova/api/openstack/consoles.py
@@ -44,11 +44,10 @@ def _translate_detail_keys(cons):
class Controller(object):
- """The Consoles Controller for the Openstack API"""
+ """The Consoles controller for the Openstack API"""
def __init__(self):
self.console_api = console.API()
- super(Controller, self).__init__()
def index(self, req, server_id):
"""Returns a list of consoles for this instance"""
diff --git a/nova/api/openstack/contrib/volumes.py b/nova/api/openstack/contrib/volumes.py
index 18de2ec71..b00790b7f 100644
--- a/nova/api/openstack/contrib/volumes.py
+++ b/nova/api/openstack/contrib/volumes.py
@@ -22,7 +22,6 @@ from nova import exception
from nova import flags
from nova import log as logging
from nova import volume
-from nova import wsgi
from nova.api.openstack import common
from nova.api.openstack import extensions
from nova.api.openstack import faults
@@ -64,7 +63,7 @@ def _translate_volume_summary_view(context, vol):
return d
-class VolumeController(wsgi.Controller):
+class VolumeController(object):
"""The Volumes API controller for the OpenStack API."""
_serialization_metadata = {
@@ -124,15 +123,14 @@ class VolumeController(wsgi.Controller):
res = [entity_maker(context, vol) for vol in limited_list]
return {'volumes': res}
- def create(self, req):
+ def create(self, req, body):
"""Creates a new volume."""
context = req.environ['nova.context']
- env = self._deserialize(req.body, req.get_content_type())
- if not env:
+ if not body:
return faults.Fault(exc.HTTPUnprocessableEntity())
- vol = env['volume']
+ vol = body['volume']
size = vol['size']
LOG.audit(_("Create volume of %s GB"), size, context=context)
new_volume = self.volume_api.create(context, size,
@@ -175,7 +173,7 @@ def _translate_attachment_summary_view(_context, vol):
return d
-class VolumeAttachmentController(wsgi.Controller):
+class VolumeAttachmentController(object):
"""The volume attachment API controller for the Openstack API.
A child resource of the server. Note that we use the volume id
@@ -219,17 +217,16 @@ class VolumeAttachmentController(wsgi.Controller):
return {'volumeAttachment': _translate_attachment_detail_view(context,
vol)}
- def create(self, req, server_id):
+ def create(self, req, server_id, body):
"""Attach a volume to an instance."""
context = req.environ['nova.context']
- env = self._deserialize(req.body, req.get_content_type())
- if not env:
+ if not body:
return faults.Fault(exc.HTTPUnprocessableEntity())
instance_id = server_id
- volume_id = env['volumeAttachment']['volumeId']
- device = env['volumeAttachment']['device']
+ volume_id = body['volumeAttachment']['volumeId']
+ device = body['volumeAttachment']['device']
msg = _("Attach volume %(volume_id)s to instance %(server_id)s"
" at %(device)s") % locals()
@@ -259,7 +256,7 @@ class VolumeAttachmentController(wsgi.Controller):
# TODO(justinsb): How do I return "accepted" here?
return {'volumeAttachment': attachment}
- def update(self, _req, _server_id, _id):
+ def update(self, req, server_id, id, body):
"""Update a volume attachment. We don't currently support this."""
return faults.Fault(exc.HTTPBadRequest())
diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py
index 7ea7afef6..73f174e07 100644
--- a/nova/api/openstack/extensions.py
+++ b/nova/api/openstack/extensions.py
@@ -27,9 +27,10 @@ import webob.exc
from nova import exception
from nova import flags
from nova import log as logging
-from nova import wsgi
+from nova import wsgi as base_wsgi
from nova.api.openstack import common
from nova.api.openstack import faults
+from nova.api.openstack import wsgi
LOG = logging.getLogger('extensions')
@@ -116,28 +117,34 @@ class ExtensionDescriptor(object):
return response_exts
-class ActionExtensionController(common.OpenstackController):
-
+class ActionExtensionController(object):
def __init__(self, application):
-
self.application = application
self.action_handlers = {}
def add_action(self, action_name, handler):
self.action_handlers[action_name] = handler
- def action(self, req, id):
-
- input_dict = self._deserialize(req.body, req.get_content_type())
+ def action(self, req, id, body):
for action_name, handler in self.action_handlers.iteritems():
- if action_name in input_dict:
- return handler(input_dict, req, id)
+ if action_name in body:
+ return handler(body, req, id)
# no action handler found (bump to downstream application)
res = self.application
return res
-class ResponseExtensionController(common.OpenstackController):
+class ActionExtensionResource(wsgi.Resource):
+
+ def __init__(self, application):
+ controller = ActionExtensionController(application)
+ super(ActionExtensionResource, self).__init__(controller)
+
+ def add_action(self, action_name, handler):
+ self.controller.add_action(action_name, handler)
+
+
+class ResponseExtensionController(object):
def __init__(self, application):
self.application = application
@@ -157,7 +164,11 @@ class ResponseExtensionController(common.OpenstackController):
headers = res.headers
except AttributeError:
default_xmlns = None
- body = self._serialize(res, content_type, default_xmlns)
+ serializer = {
+ 'application/xml': wsgi.XMLSerializer(),
+ 'application/json': wsgi.JSONSerializer(),
+ }[content_type]
+ body = serializer.serialize(res)
headers = {"Content-Type": content_type}
res = webob.Response()
res.body = body
@@ -165,7 +176,17 @@ class ResponseExtensionController(common.OpenstackController):
return res
-class ExtensionController(common.OpenstackController):
+class ResponseExtensionResource(wsgi.Resource):
+
+ def __init__(self, application):
+ controller = ResponseExtensionController(application)
+ super(ResponseExtensionResource, self).__init__(controller)
+
+ def add_handler(self, handler):
+ self.controller.add_handler(handler)
+
+
+class ExtensionController(object):
def __init__(self, extension_manager):
self.extension_manager = extension_manager
@@ -198,7 +219,7 @@ class ExtensionController(common.OpenstackController):
raise faults.Fault(webob.exc.HTTPNotFound())
-class ExtensionMiddleware(wsgi.Middleware):
+class ExtensionMiddleware(base_wsgi.Middleware):
"""Extensions middleware for WSGI."""
@classmethod
def factory(cls, global_config, **local_config):
@@ -207,43 +228,43 @@ class ExtensionMiddleware(wsgi.Middleware):
return cls(app, **local_config)
return _factory
- def _action_ext_controllers(self, application, ext_mgr, mapper):
- """Return a dict of ActionExtensionController-s by collection."""
- action_controllers = {}
+ def _action_ext_resources(self, application, ext_mgr, mapper):
+ """Return a dict of ActionExtensionResource objects by collection."""
+ action_resources = {}
for action in ext_mgr.get_actions():
- if not action.collection in action_controllers.keys():
- controller = ActionExtensionController(application)
+ if not action.collection in action_resources.keys():
+ resource = ActionExtensionResource(application)
mapper.connect("/%s/:(id)/action.:(format)" %
action.collection,
action='action',
- controller=controller,
+ controller=resource,
conditions=dict(method=['POST']))
mapper.connect("/%s/:(id)/action" % action.collection,
action='action',
- controller=controller,
+ controller=resource,
conditions=dict(method=['POST']))
- action_controllers[action.collection] = controller
+ action_resources[action.collection] = resource
- return action_controllers
+ return action_resources
- def _response_ext_controllers(self, application, ext_mgr, mapper):
- """Returns a dict of ResponseExtensionController-s by collection."""
- response_ext_controllers = {}
+ def _response_ext_resources(self, application, ext_mgr, mapper):
+ """Returns a dict of ResponseExtensionResource objects by collection."""
+ response_ext_resources = {}
for resp_ext in ext_mgr.get_response_extensions():
- if not resp_ext.key in response_ext_controllers.keys():
- controller = ResponseExtensionController(application)
+ if not resp_ext.key in response_ext_resources.keys():
+ resource = ResponseExtensionResource(application)
mapper.connect(resp_ext.url_route + '.:(format)',
action='process',
- controller=controller,
+ controller=resource,
conditions=resp_ext.conditions)
mapper.connect(resp_ext.url_route,
action='process',
- controller=controller,
+ controller=resource,
conditions=resp_ext.conditions)
- response_ext_controllers[resp_ext.key] = controller
+ response_ext_resources[resp_ext.key] = resource
- return response_ext_controllers
+ return response_ext_resources
def __init__(self, application, ext_mgr=None):
@@ -258,21 +279,21 @@ class ExtensionMiddleware(wsgi.Middleware):
LOG.debug(_('Extended resource: %s'),
resource.collection)
mapper.resource(resource.collection, resource.collection,
- controller=resource.controller,
+ controller=wsgi.Resource(resource.controller),
collection=resource.collection_actions,
member=resource.member_actions,
parent_resource=resource.parent)
# extended actions
- action_controllers = self._action_ext_controllers(application, ext_mgr,
+ action_resources = self._action_ext_resources(application, ext_mgr,
mapper)
for action in ext_mgr.get_actions():
LOG.debug(_('Extended action: %s'), action.action_name)
- controller = action_controllers[action.collection]
- controller.add_action(action.action_name, action.handler)
+ resource = action_resources[action.collection]
+ resource.add_action(action.action_name, action.handler)
# extended responses
- resp_controllers = self._response_ext_controllers(application, ext_mgr,
+ resp_controllers = self._response_ext_resources(application, ext_mgr,
mapper)
for response_ext in ext_mgr.get_response_extensions():
LOG.debug(_('Extended response: %s'), response_ext.key)
@@ -422,7 +443,7 @@ class ExtensionManager(object):
class ResponseExtension(object):
- """Add data to responses from core nova OpenStack API controllers."""
+ """Add data to responses from core nova OpenStack API resources."""
def __init__(self, method, url_route, handler):
self.url_route = url_route
@@ -432,7 +453,7 @@ class ResponseExtension(object):
class ActionExtension(object):
- """Add custom actions to core nova OpenStack API controllers."""
+ """Add custom actions to core nova OpenStack API resources."""
def __init__(self, collection, action_name, handler):
self.collection = collection
diff --git a/nova/api/openstack/faults.py b/nova/api/openstack/faults.py
index 87118ce19..fd36f8f17 100644
--- a/nova/api/openstack/faults.py
+++ b/nova/api/openstack/faults.py
@@ -19,8 +19,7 @@
import webob.dec
import webob.exc
-from nova import wsgi
-from nova.api.openstack import common
+from nova.api.openstack import wsgi
class Fault(webob.exc.HTTPException):
@@ -55,13 +54,21 @@ class Fault(webob.exc.HTTPException):
if code == 413:
retry = self.wrapped_exc.headers['Retry-After']
fault_data[fault_name]['retryAfter'] = retry
+
# 'code' is an attribute on the fault tag itself
- metadata = {'application/xml': {'attributes': {fault_name: 'code'}}}
- default_xmlns = common.XML_NS_V10
- serializer = wsgi.Serializer(metadata, default_xmlns)
+ metadata = {'attributes': {fault_name: 'code'}}
+
content_type = req.best_match_content_type()
- self.wrapped_exc.body = serializer.serialize(fault_data, content_type)
+
+ serializer = {
+ 'application/xml': wsgi.XMLSerializer(metadata=metadata,
+ xmlns=wsgi.XMLNS_V10),
+ 'application/json': wsgi.JSONSerializer(),
+ }[content_type]
+
+ self.wrapped_exc.body = serializer.serialize(fault_data)
self.wrapped_exc.content_type = content_type
+
return self.wrapped_exc
@@ -70,14 +77,6 @@ class OverLimitFault(webob.exc.HTTPException):
Rate-limited request response.
"""
- _serialization_metadata = {
- "application/xml": {
- "attributes": {
- "overLimitFault": "code",
- },
- },
- }
-
def __init__(self, message, details, retry_time):
"""
Initialize new `OverLimitFault` with relevant information.
@@ -97,8 +96,16 @@ class OverLimitFault(webob.exc.HTTPException):
Return the wrapped exception with a serialized body conforming to our
error format.
"""
- serializer = wsgi.Serializer(self._serialization_metadata)
content_type = request.best_match_content_type()
- content = serializer.serialize(self.content, content_type)
+ metadata = {"attributes": {"overLimitFault": "code"}}
+
+ serializer = {
+ 'application/xml': wsgi.XMLSerializer(metadata=metadata,
+ xmlns=wsgi.XMLNS_V10),
+ 'application/json': wsgi.JSONSerializer(),
+ }[content_type]
+
+ content = serializer.serialize(self.content)
self.wrapped_exc.body = content
+
return self.wrapped_exc
diff --git a/nova/api/openstack/image_metadata.py b/nova/api/openstack/image_metadata.py
index ce0140265..506b63acf 100644
--- a/nova/api/openstack/image_metadata.py
+++ b/nova/api/openstack/image_metadata.py
@@ -20,7 +20,6 @@ from webob import exc
from nova import flags
from nova import quota
from nova import utils
-from nova import wsgi
from nova.api.openstack import faults
from nova.api.openstack import wsgi
diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py
index e22854ebf..5a03573d8 100644
--- a/nova/api/openstack/images.py
+++ b/nova/api/openstack/images.py
@@ -31,7 +31,7 @@ FLAGS = flags.FLAGS
class Controller(object):
- """Base `wsgi.Controller` for retrieving/displaying images."""
+ """Base controller for retrieving/displaying images."""
def __init__(self, image_service=None, compute_service=None):
"""Initialize new `ImageController`.
@@ -99,21 +99,20 @@ class Controller(object):
self._image_service.delete(context, image_id)
return webob.exc.HTTPNoContent()
- def create(self, req):
+ def create(self, req, body):
"""Snapshot a server instance and save the image.
:param req: `wsgi.Request` object
"""
context = req.environ['nova.context']
content_type = req.get_content_type()
- image = self._deserialize(req.body, content_type)
- if not image:
+ if not body:
raise webob.exc.HTTPBadRequest()
try:
- server_id = image["image"]["serverId"]
- image_name = image["image"]["name"]
+ server_id = body["image"]["serverId"]
+ image_name = body["image"]["name"]
except KeyError:
raise webob.exc.HTTPBadRequest()
diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py
index 3f9d91934..a8d785b52 100644
--- a/nova/api/openstack/versions.py
+++ b/nova/api/openstack/versions.py
@@ -18,13 +18,27 @@
import webob
import webob.dec
-from nova import wsgi
+from nova import wsgi as base_wsgi
import nova.api.openstack.views.versions
+from nova.api.openstack import wsgi
-class Versions(wsgi.Application):
- @webob.dec.wsgify(RequestClass=wsgi.Request)
- def __call__(self, req):
+class Versions(wsgi.Resource, base_wsgi.Application):
+ def __init__(self):
+ metadata = {
+ "attributes": {
+ "version": ["status", "id"],
+ "link": ["rel", "href"],
+ }
+ }
+
+ serializers = {
+ 'application/xml': wsgi.XMLSerializer(metadata=metadata),
+ }
+
+ super(Versions, self).__init__(None, serializers=serializers)
+
+ def dispatch(self, request, *args):
"""Respond to a request for all OpenStack API versions."""
version_objs = [
{
@@ -37,24 +51,6 @@ class Versions(wsgi.Application):
},
]
- builder = nova.api.openstack.views.versions.get_view_builder(req)
+ builder = nova.api.openstack.views.versions.get_view_builder(request)
versions = [builder.build(version) for version in version_objs]
- response = dict(versions=versions)
-
- metadata = {
- "application/xml": {
- "attributes": {
- "version": ["status", "id"],
- "link": ["rel", "href"],
- }
- }
- }
-
- content_type = req.best_match_content_type()
- body = wsgi.Serializer(metadata).serialize(response, content_type)
-
- response = webob.Response()
- response.content_type = content_type
- response.body = body
-
- return response
+ return dict(versions=versions)
diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py
index 9e0077932..97280c365 100644
--- a/nova/api/openstack/wsgi.py
+++ b/nova/api/openstack/wsgi.py
@@ -206,8 +206,7 @@ class Resource(object):
except exception.InvalidContentType:
return webob.exc.HTTPBadRequest(_("Unsupported Content-Type"))
- controller_method = getattr(self.controller, action)
- result = controller_method(req=request, **action_args)
+ result = self.dispatch(request, action, action_args)
response = self.serialize_response(accept, result)
@@ -222,6 +221,10 @@ class Resource(object):
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):
"""Serialize a dict into a string and wrap in a wsgi.Request object.
@@ -253,7 +256,7 @@ class Resource(object):
"""
action_args = self.get_action_args(request.environ)
- action = action_args.pop('action')
+ action = action_args.pop('action', None)
if request.method.lower() in ('post', 'put'):
if len(request.body) == 0:
@@ -275,14 +278,18 @@ class Resource(object):
return request.best_match_content_type()
def get_action_args(self, request_environment):
- args = request_environment['wsgiorg.routing_args'][1].copy()
+ try:
+ args = request_environment['wsgiorg.routing_args'][1].copy()
+
+ del args['controller']
- del args['controller']
+ if 'format' in args:
+ del args['format']
- if 'format' in args:
- del args['format']
+ return args
- return args
+ except KeyError:
+ return {}
def get_deserializer(self, content_type):
try:
diff --git a/nova/objectstore/s3server.py b/nova/objectstore/s3server.py
index dd6327c8f..76025a1e3 100644
--- a/nova/objectstore/s3server.py
+++ b/nova/objectstore/s3server.py
@@ -81,7 +81,7 @@ class S3Application(wsgi.Router):
super(S3Application, self).__init__(mapper)
-class BaseRequestHandler(wsgi.Controller):
+class BaseRequestHandler(object):
"""Base class emulating Tornado's web framework pattern in WSGI.
This is a direct port of Tornado's implementation, so some key decisions
diff --git a/nova/tests/api/openstack/extensions/foxinsocks.py b/nova/tests/api/openstack/extensions/foxinsocks.py
index 0860b51ac..a64552af1 100644
--- a/nova/tests/api/openstack/extensions/foxinsocks.py
+++ b/nova/tests/api/openstack/extensions/foxinsocks.py
@@ -17,12 +17,10 @@
import json
-from nova import wsgi
-
from nova.api.openstack import extensions
-class FoxInSocksController(wsgi.Controller):
+class FoxInSocksController(object):
def index(self, req):
return "Try to say this Mr. Knox, sir..."
diff --git a/nova/tests/api/openstack/test_extensions.py b/nova/tests/api/openstack/test_extensions.py
index 481d34ed1..a8168f88a 100644
--- a/nova/tests/api/openstack/test_extensions.py
+++ b/nova/tests/api/openstack/test_extensions.py
@@ -26,15 +26,15 @@ from nova import flags
from nova.api import openstack
from nova.api.openstack import extensions
from nova.api.openstack import flavors
+from nova.api.openstack import wsgi
from nova.tests.api.openstack import fakes
-import nova.wsgi
FLAGS = flags.FLAGS
response_body = "Try to say this Mr. Knox, sir..."
-class StubController(nova.wsgi.Controller):
+class StubController(object):
def __init__(self, body):
self.body = body
diff --git a/nova/tests/api/test_wsgi.py b/nova/tests/api/test_wsgi.py
index 0be3aecf1..d33268296 100644
--- a/nova/tests/api/test_wsgi.py
+++ b/nova/tests/api/test_wsgi.py
@@ -67,57 +67,3 @@ class Test(test.TestCase):
self.assertEqual(result.body, "Router result")
result = webob.Request.blank('/bad').get_response(Router())
self.assertNotEqual(result.body, "Router result")
-
-
-class ControllerTest(test.TestCase):
-
- class TestRouter(wsgi.Router):
-
- class TestController(wsgi.Controller):
-
- _serialization_metadata = {
- 'application/xml': {
- "attributes": {
- "test": ["id"]}}}
-
- def show(self, req, id): # pylint: disable=W0622,C0103
- return {"test": {"id": id}}
-
- def __init__(self):
- mapper = routes.Mapper()
- mapper.resource("test", "tests", controller=self.TestController())
- wsgi.Router.__init__(self, mapper)
-
- def test_show(self):
- request = wsgi.Request.blank('/tests/123')
- result = request.get_response(self.TestRouter())
- self.assertEqual(json.loads(result.body), {"test": {"id": "123"}})
-
- def test_response_content_type_from_accept_xml(self):
- request = webob.Request.blank('/tests/123')
- request.headers["Accept"] = "application/xml"
- result = request.get_response(self.TestRouter())
- self.assertEqual(result.headers["Content-Type"], "application/xml")
-
- def test_response_content_type_from_accept_json(self):
- request = wsgi.Request.blank('/tests/123')
- request.headers["Accept"] = "application/json"
- result = request.get_response(self.TestRouter())
- self.assertEqual(result.headers["Content-Type"], "application/json")
-
- def test_response_content_type_from_query_extension_xml(self):
- request = wsgi.Request.blank('/tests/123.xml')
- result = request.get_response(self.TestRouter())
- self.assertEqual(result.headers["Content-Type"], "application/xml")
-
- def test_response_content_type_from_query_extension_json(self):
- request = wsgi.Request.blank('/tests/123.json')
- result = request.get_response(self.TestRouter())
- self.assertEqual(result.headers["Content-Type"], "application/json")
-
- def test_response_content_type_default_when_unsupported(self):
- request = wsgi.Request.blank('/tests/123.unsupported')
- request.headers["Accept"] = "application/unsupported1"
- result = request.get_response(self.TestRouter())
- self.assertEqual(result.status_int, 200)
- self.assertEqual(result.headers["Content-Type"], "application/json")
diff --git a/nova/wsgi.py b/nova/wsgi.py
index e60a8820d..3a292073b 100644
--- a/nova/wsgi.py
+++ b/nova/wsgi.py
@@ -82,36 +82,7 @@ class Server(object):
class Request(webob.Request):
-
- def best_match_content_type(self):
- """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/json', 'application/xml']
- bm = self.accept.best_match(ctypes)
-
- return bm or 'application/json'
-
- def get_content_type(self):
- allowed_types = ("application/xml", "application/json")
- if not "Content-Type" in self.headers:
- msg = _("Missing Content-Type")
- LOG.debug(msg)
- raise webob.exc.HTTPBadRequest(msg)
- type = self.content_type
- if type in allowed_types:
- return type
- LOG.debug(_("Wrong Content-Type: %s") % type)
- raise webob.exc.HTTPBadRequest("Invalid content type")
+ pass
class Application(object):
@@ -286,7 +257,7 @@ class Router(object):
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 a wsgi.Controller, who will route
+ well and have your controller be a controller, who will route
the request to the action method.
Examples:
@@ -335,223 +306,6 @@ class Router(object):
return app
-class Controller(object):
- """WSGI app that dispatched to methods.
-
- 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.
-
- """
-
- @webob.dec.wsgify(RequestClass=Request)
- def __call__(self, req):
- """Call the method specified in req.environ by RoutesMiddleware."""
- arg_dict = req.environ['wsgiorg.routing_args'][1]
- action = arg_dict['action']
- method = getattr(self, action)
- LOG.debug("%s %s" % (req.method, req.url))
- del arg_dict['controller']
- del arg_dict['action']
- if 'format' in arg_dict:
- del arg_dict['format']
- arg_dict['req'] = req
- result = method(**arg_dict)
-
- if type(result) is dict:
- content_type = req.best_match_content_type()
- default_xmlns = self.get_default_xmlns(req)
- body = self._serialize(result, content_type, default_xmlns)
-
- response = webob.Response()
- response.headers['Content-Type'] = content_type
- response.body = body
- msg_dict = dict(url=req.url, status=response.status_int)
- msg = _("%(url)s returned with HTTP %(status)d") % msg_dict
- LOG.debug(msg)
- return response
- else:
- return result
-
- def _serialize(self, data, content_type, default_xmlns):
- """Serialize the given dict to the provided content_type.
-
- Uses self._serialization_metadata if it exists, which is a dict mapping
- MIME types to information needed to serialize to that type.
-
- """
- _metadata = getattr(type(self), '_serialization_metadata', {})
-
- serializer = Serializer(_metadata, default_xmlns)
- try:
- return serializer.serialize(data, content_type)
- except exception.InvalidContentType:
- raise webob.exc.HTTPNotAcceptable()
-
- def _deserialize(self, data, content_type):
- """Deserialize the request body to the specefied content type.
-
- Uses self._serialization_metadata if it exists, which is a dict mapping
- MIME types to information needed to serialize to that type.
-
- """
- _metadata = getattr(type(self), '_serialization_metadata', {})
- serializer = Serializer(_metadata)
- return serializer.deserialize(data, content_type)
-
- def get_default_xmlns(self, req):
- """Provide the XML namespace to use if none is otherwise specified."""
- return None
-
-
-class Serializer(object):
- """Serializes and deserializes dictionaries to certain MIME types."""
-
- def __init__(self, metadata=None, default_xmlns=None):
- """Create a serializer based on the given WSGI environment.
-
- 'metadata' is an optional dict mapping MIME types to information
- needed to serialize a dictionary to that type.
-
- """
- self.metadata = metadata or {}
- self.default_xmlns = default_xmlns
-
- def _get_serialize_handler(self, content_type):
- handlers = {
- 'application/json': self._to_json,
- 'application/xml': self._to_xml,
- }
-
- try:
- return handlers[content_type]
- except Exception:
- raise exception.InvalidContentType(content_type=content_type)
-
- def serialize(self, data, content_type):
- """Serialize a dictionary into the specified content type."""
- return self._get_serialize_handler(content_type)(data)
-
- def deserialize(self, datastring, content_type):
- """Deserialize a string to a dictionary.
-
- The string must be in the format of a supported MIME type.
-
- """
- return self.get_deserialize_handler(content_type)(datastring)
-
- def get_deserialize_handler(self, content_type):
- handlers = {
- 'application/json': self._from_json,
- 'application/xml': self._from_xml,
- }
-
- try:
- return handlers[content_type]
- except Exception:
- raise exception.InvalidContentType(content_type=content_type)
-
- def _from_json(self, datastring):
- return utils.loads(datastring)
-
- def _from_xml(self, datastring):
- xmldata = self.metadata.get('application/xml', {})
- plurals = set(xmldata.get('plurals', {}))
- node = minidom.parseString(datastring).childNodes[0]
- return {node.nodeName: self._from_xml_node(node, plurals)}
-
- def _from_xml_node(self, node, listnames):
- """Convert a minidom node to a simple Python type.
-
- listnames is a collection of names of XML nodes whose subnodes should
- be considered list items.
-
- """
- if len(node.childNodes) == 1 and node.childNodes[0].nodeType == 3:
- return node.childNodes[0].nodeValue
- elif node.nodeName in listnames:
- return [self._from_xml_node(n, listnames) for n in node.childNodes]
- else:
- result = dict()
- for attr in node.attributes.keys():
- result[attr] = node.attributes[attr].nodeValue
- for child in node.childNodes:
- if child.nodeType != node.TEXT_NODE:
- result[child.nodeName] = self._from_xml_node(child,
- listnames)
- return result
-
- def _to_json(self, data):
- return utils.dumps(data)
-
- def _to_xml(self, data):
- metadata = self.metadata.get('application/xml', {})
- # We expect data to contain a single key which is the XML root.
- root_key = data.keys()[0]
- doc = minidom.Document()
- node = self._to_xml_node(doc, metadata, root_key, data[root_key])
-
- xmlns = node.getAttribute('xmlns')
- if not xmlns and self.default_xmlns:
- node.setAttribute('xmlns', self.default_xmlns)
-
- return node.toprettyxml(indent=' ')
-
- def _to_xml_node(self, doc, metadata, nodename, data):
- """Recursive method to convert data members to XML nodes."""
- result = doc.createElement(nodename)
-
- # Set the xml namespace if one is specified
- # TODO(justinsb): We could also use prefixes on the keys
- xmlns = metadata.get('xmlns', None)
- if xmlns:
- result.setAttribute('xmlns', xmlns)
-
- if type(data) is list:
- collections = metadata.get('list_collections', {})
- if nodename in collections:
- metadata = collections[nodename]
- for item in data:
- node = doc.createElement(metadata['item_name'])
- node.setAttribute(metadata['item_key'], str(item))
- result.appendChild(node)
- return result
- singular = metadata.get('plurals', {}).get(nodename, None)
- if singular is None:
- if nodename.endswith('s'):
- singular = nodename[:-1]
- else:
- singular = 'item'
- for item in data:
- node = self._to_xml_node(doc, metadata, singular, item)
- result.appendChild(node)
- elif type(data) is dict:
- collections = metadata.get('dict_collections', {})
- if nodename in collections:
- metadata = collections[nodename]
- for k, v in data.items():
- node = doc.createElement(metadata['item_name'])
- node.setAttribute(metadata['item_key'], str(k))
- text = doc.createTextNode(str(v))
- node.appendChild(text)
- result.appendChild(node)
- return result
- attrs = metadata.get('attributes', {}).get(nodename, {})
- for k, v in data.items():
- if k in attrs:
- result.setAttribute(k, str(v))
- else:
- node = self._to_xml_node(doc, metadata, k, v)
- result.appendChild(node)
- else:
- # Type is atom
- node = doc.createTextNode(str(data))
- result.appendChild(node)
- return result
-
-
def paste_config_file(basename):
"""Find the best location in the system for a paste config file.
--
cgit
From 0aefdc6da92b8db8b15a3e8a0bef8fc5c4b46450 Mon Sep 17 00:00:00 2001
From: Brian Waldon
Date: Wed, 18 May 2011 20:33:52 -0400
Subject: missed the new wsgi test file
---
nova/tests/api/openstack/test_wsgi.py | 248 ++++++++++++++++++++++++++++++++++
1 file changed, 248 insertions(+)
create mode 100644 nova/tests/api/openstack/test_wsgi.py
diff --git a/nova/tests/api/openstack/test_wsgi.py b/nova/tests/api/openstack/test_wsgi.py
new file mode 100644
index 000000000..430dafe77
--- /dev/null
+++ b/nova/tests/api/openstack/test_wsgi.py
@@ -0,0 +1,248 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+import json
+import webob
+
+from nova import exception
+from nova import test
+from nova.api.openstack import wsgi
+
+
+class RequestTest(test.TestCase):
+ def test_content_type_missing(self):
+ request = wsgi.Request.blank('/tests/123')
+ request.body = ""
+ self.assertRaises(exception.InvalidContentType,
+ request.get_content_type)
+
+ def test_content_type_unsupported(self):
+ request = wsgi.Request.blank('/tests/123')
+ request.headers["Content-Type"] = "text/html"
+ request.body = "asdf
"
+ self.assertRaises(exception.InvalidContentType,
+ request.get_content_type)
+
+ def test_content_type_with_charset(self):
+ request = wsgi.Request.blank('/tests/123')
+ request.headers["Content-Type"] = "application/json; charset=UTF-8"
+ result = request.get_content_type()
+ self.assertEqual(result, "application/json")
+
+ def test_content_type_from_accept_xml(self):
+ request = wsgi.Request.blank('/tests/123')
+ request.headers["Accept"] = "application/xml"
+ result = request.best_match_content_type()
+ self.assertEqual(result, "application/xml")
+
+ request = wsgi.Request.blank('/tests/123')
+ request.headers["Accept"] = "application/json"
+ result = request.best_match_content_type()
+ self.assertEqual(result, "application/json")
+
+ request = wsgi.Request.blank('/tests/123')
+ request.headers["Accept"] = "application/xml, application/json"
+ result = request.best_match_content_type()
+ self.assertEqual(result, "application/json")
+
+ request = wsgi.Request.blank('/tests/123')
+ request.headers["Accept"] = \
+ "application/json; q=0.3, application/xml; q=0.9"
+ result = request.best_match_content_type()
+ self.assertEqual(result, "application/xml")
+
+ def test_content_type_from_query_extension(self):
+ request = wsgi.Request.blank('/tests/123.xml')
+ result = request.best_match_content_type()
+ self.assertEqual(result, "application/xml")
+
+ request = wsgi.Request.blank('/tests/123.json')
+ result = request.best_match_content_type()
+ self.assertEqual(result, "application/json")
+
+ request = wsgi.Request.blank('/tests/123.invalid')
+ result = request.best_match_content_type()
+ self.assertEqual(result, "application/json")
+
+ def test_content_type_accept_and_query_extension(self):
+ request = wsgi.Request.blank('/tests/123.xml')
+ request.headers["Accept"] = "application/json"
+ result = request.best_match_content_type()
+ self.assertEqual(result, "application/xml")
+
+ def test_content_type_accept_default(self):
+ request = wsgi.Request.blank('/tests/123.unsupported')
+ request.headers["Accept"] = "application/unsupported1"
+ result = request.best_match_content_type()
+ self.assertEqual(result, "application/json")
+
+
+class SerializationTest(test.TestCase):
+ def test_xml(self):
+ input_dict = dict(servers=dict(a=(2, 3)))
+ expected_xml = '(2,3)'
+ xmlns = "testing xmlns"
+ serializer = wsgi.XMLSerializer(xmlns="asdf")
+ result = serializer.serialize(input_dict)
+ result = result.replace('\n', '').replace(' ', '')
+ self.assertEqual(result, expected_xml)
+
+ def test_json(self):
+ input_dict = dict(servers=dict(a=(2, 3)))
+ expected_json = '{"servers":{"a":[2,3]}}'
+ serializer = wsgi.JSONSerializer()
+ result = serializer.serialize(input_dict)
+ result = result.replace('\n', '').replace(' ', '')
+ self.assertEqual(result, expected_json)
+
+
+class DeserializationTest(test.TestCase):
+ def test_json(self):
+ data = """{"a": {
+ "a1": "1",
+ "a2": "2",
+ "bs": ["1", "2", "3", {"c": {"c1": "1"}}],
+ "d": {"e": "1"},
+ "f": "1"}}"""
+ as_dict = dict(a={
+ 'a1': '1',
+ 'a2': '2',
+ 'bs': ['1', '2', '3', {'c': dict(c1='1')}],
+ 'd': {'e': '1'},
+ 'f': '1'})
+ deserializer = wsgi.JSONDeserializer()
+ self.assertEqual(deserializer.deserialize(data), as_dict)
+
+ def test_xml(self):
+ xml = """
+
+ 123
+ 1
+ 1
+
+ """.strip()
+ as_dict = dict(a={
+ 'a1': '1',
+ 'a2': '2',
+ 'bs': ['1', '2', '3', {'c': dict(c1='1')}],
+ 'd': {'e': '1'},
+ 'f': '1'})
+ metadata = {'plurals': {'bs': 'b', 'ts': 't'}}
+ deserializer = wsgi.XMLDeserializer(metadata=metadata)
+ self.assertEqual(deserializer.deserialize(xml), as_dict)
+
+ def test_xml_empty(self):
+ xml = """"""
+ as_dict = {"a": {}}
+ deserializer = wsgi.XMLDeserializer()
+ self.assertEqual(deserializer.deserialize(xml), as_dict)
+
+
+class ResourceSerializerTest(test.TestCase):
+ def setUp(self):
+ class JSONSerializer(object):
+ def serialize(self, data):
+ return 'pew_json'
+
+ class XMLSerializer(object):
+ def serialize(self, data):
+ return 'pew_xml'
+
+ self.serializers = {
+ 'application/json': JSONSerializer(),
+ 'application/XML': XMLSerializer(),
+ }
+
+ self.resource = wsgi.Resource(None, serializers=self.serializers)
+
+ def tearDown(self):
+ pass
+
+ def test_get_serializer(self):
+ self.assertEqual(self.resource.get_serializer('application/json'),
+ self.serializers['application/json'])
+
+ def test_get_serializer_unknown_content_type(self):
+ self.assertRaises(exception.InvalidContentType,
+ self.resource.get_serializer,
+ 'application/unknown')
+
+ def test_serialize_response_dict(self):
+ response = self.resource.serialize_response('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,
+ '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):
+ def setUp(self):
+ class JSONDeserializer(object):
+ def deserialize(self, data):
+ return 'pew_json'
+
+ class XMLDeserializer(object):
+ def deserialize(self, data):
+ return 'pew_xml'
+
+ self.deserializers = {
+ 'application/json': JSONDeserializer(),
+ 'application/XML': XMLDeserializer(),
+ }
+
+ self.resource = wsgi.Resource(None, deserializers=self.deserializers)
+
+ def tearDown(self):
+ pass
+
+ def test_get_deserializer(self):
+ self.assertEqual(self.resource.get_deserializer('application/json'),
+ self.deserializers['application/json'])
+
+ def test_get_deserializer_unknown_content_type(self):
+ self.assertRaises(exception.InvalidContentType,
+ self.resource.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),
+ 'application/json')
+
+ def test_get_action_args(self):
+ env = {
+ 'wsgiorg.routing_args': [None, {
+ 'controller': None,
+ 'format': None,
+ 'action': 'update',
+ 'id': 12,
+ }],
+ }
+
+ expected = {'action': 'update', 'id': 12}
+
+ self.assertEqual(self.resource.get_action_args(env), expected)
+
+ def test_deserialize_request(self):
+ def fake_get_routing_args(request):
+ return {'action': 'create'}
+ self.resource.get_action_args = fake_get_routing_args
+
+ request = wsgi.Request.blank('/')
+ request.headers['Accept'] = 'application/xml'
+
+ deserialized = self.resource.deserialize_request(request)
+ expected = ('create', {}, 'application/xml')
+
+ self.assertEqual(expected, deserialized)
--
cgit
From 68426df2287c24efc3d327d12371911ac29d117e Mon Sep 17 00:00:00 2001
From: Brian Waldon
Date: Thu, 19 May 2011 16:16:06 -0400
Subject: further refactoring of wsgi module; adding documentation and tests
---
nova/api/direct.py | 4 +-
nova/api/openstack/accounts.py | 2 +-
nova/api/openstack/backup_schedules.py | 4 +-
nova/api/openstack/consoles.py | 2 +-
nova/api/openstack/extensions.py | 8 +-
nova/api/openstack/faults.py | 12 +-
nova/api/openstack/flavors.py | 2 +-
nova/api/openstack/image_metadata.py | 2 +-
nova/api/openstack/images.py | 4 +-
nova/api/openstack/ips.py | 4 +-
nova/api/openstack/limits.py | 4 +-
nova/api/openstack/server_metadata.py | 2 +-
nova/api/openstack/servers.py | 11 +-
nova/api/openstack/users.py | 2 +-
nova/api/openstack/versions.py | 5 +-
nova/api/openstack/wsgi.py | 301 ++++++++++++++++++++-----------
nova/api/openstack/zones.py | 4 +-
nova/tests/api/openstack/test_servers.py | 50 ++---
nova/tests/api/openstack/test_wsgi.py | 104 ++++++++---
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 = ''
+# 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 = """
"""
- 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">
"""
- 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">
"""
- 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):
"""
- 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):
"""
- 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):
aabbccdd
"""
- 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">
aabbccdd
abcd"""
- 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):
anything
"""
- 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):
aabbccdd"""
- 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):
"""
- 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):
"""
- 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):
beta
"""
- 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):
bar
"""
- 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):
"""
- 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):
"""
- 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):
beta
"""
- 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):
gamma
"""
- 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):
baz
"""
- 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">
"""
- 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 = '(2,3)'
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 = """
@@ -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', {})
--
cgit
From 3fc3b7537cc1af2783829a2caaca272e83d6d3e8 Mon Sep 17 00:00:00 2001
From: Brian Waldon
Date: Fri, 20 May 2011 14:42:19 -0400
Subject: renaming resource_factory to create_resource
---
nova/api/openstack/__init__.py | 34 ++++++++++++++++-----------------
nova/api/openstack/accounts.py | 2 +-
nova/api/openstack/backup_schedules.py | 2 +-
nova/api/openstack/consoles.py | 2 +-
nova/api/openstack/flavors.py | 2 +-
nova/api/openstack/image_metadata.py | 2 +-
nova/api/openstack/images.py | 2 +-
nova/api/openstack/ips.py | 2 +-
nova/api/openstack/limits.py | 2 +-
nova/api/openstack/server_metadata.py | 2 +-
nova/api/openstack/servers.py | 2 +-
nova/api/openstack/shared_ip_groups.py | 2 +-
nova/api/openstack/users.py | 2 +-
nova/api/openstack/zones.py | 2 +-
nova/tests/api/openstack/test_limits.py | 4 ++--
15 files changed, 32 insertions(+), 32 deletions(-)
diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py
index fbbd99cb9..4419d0748 100644
--- a/nova/api/openstack/__init__.py
+++ b/nova/api/openstack/__init__.py
@@ -99,19 +99,19 @@ class APIRouter(base_wsgi.Router):
server_members['inject_network_info'] = 'POST'
mapper.resource("zone", "zones",
- controller=zones.resource_factory(),
+ controller=zones.create_resource(),
collection={'detail': 'GET', 'info': 'GET'}),
mapper.resource("user", "users",
- controller=users.resource_factory(),
+ controller=users.create_resource(),
collection={'detail': 'GET'})
mapper.resource("account", "accounts",
- controller=accounts.resource_factory(),
+ controller=accounts.create_resource(),
collection={'detail': 'GET'})
mapper.resource("console", "consoles",
- controller=consoles.resource_factory(),
+ controller=consoles.create_resource(),
parent_resource=dict(member_name='server',
collection_name='servers'))
@@ -124,31 +124,31 @@ class APIRouterV10(APIRouter):
def _setup_routes(self, mapper):
super(APIRouterV10, self)._setup_routes(mapper)
mapper.resource("server", "servers",
- controller=servers.resource_factory('1.0'),
+ controller=servers.create_resource('1.0'),
collection={'detail': 'GET'},
member=self.server_members)
mapper.resource("image", "images",
- controller=images.resource_factory('1.0'),
+ controller=images.create_resource('1.0'),
collection={'detail': 'GET'})
mapper.resource("flavor", "flavors",
- controller=flavors.resource_factory('1.0'),
+ controller=flavors.create_resource('1.0'),
collection={'detail': 'GET'})
mapper.resource("shared_ip_group", "shared_ip_groups",
collection={'detail': 'GET'},
- controller=shared_ip_groups.resource_factory())
+ controller=shared_ip_groups.create_resource())
mapper.resource("backup_schedule", "backup_schedule",
- controller=backup_schedules.resource_factory(),
+ controller=backup_schedules.create_resource(),
parent_resource=dict(member_name='server',
collection_name='servers'))
mapper.resource("limit", "limits",
- controller=limits.resource_factory('1.0'))
+ controller=limits.create_resource('1.0'))
- mapper.resource("ip", "ips", controller=ips.resource_factory(),
+ mapper.resource("ip", "ips", controller=ips.create_resource(),
collection=dict(public='GET', private='GET'),
parent_resource=dict(member_name='server',
collection_name='servers'))
@@ -160,27 +160,27 @@ class APIRouterV11(APIRouter):
def _setup_routes(self, mapper):
super(APIRouterV11, self)._setup_routes(mapper)
mapper.resource("server", "servers",
- controller=servers.resource_factory('1.1'),
+ controller=servers.create_resource('1.1'),
collection={'detail': 'GET'},
member=self.server_members)
mapper.resource("image", "images",
- controller=images.resource_factory('1.1'),
+ controller=images.create_resource('1.1'),
collection={'detail': 'GET'})
mapper.resource("image_meta", "meta",
- controller=image_metadata.resource_factory(),
+ controller=image_metadata.create_resource(),
parent_resource=dict(member_name='image',
collection_name='images'))
mapper.resource("server_meta", "meta",
- controller=server_metadata.resource_factory(),
+ controller=server_metadata.create_resource(),
parent_resource=dict(member_name='server',
collection_name='servers'))
mapper.resource("flavor", "flavors",
- controller=flavors.resource_factory('1.1'),
+ controller=flavors.create_resource('1.1'),
collection={'detail': 'GET'})
mapper.resource("limit", "limits",
- controller=limits.resource_factory('1.1'))
+ controller=limits.create_resource('1.1'))
diff --git a/nova/api/openstack/accounts.py b/nova/api/openstack/accounts.py
index faff8bb2c..0dcd37217 100644
--- a/nova/api/openstack/accounts.py
+++ b/nova/api/openstack/accounts.py
@@ -80,7 +80,7 @@ class Controller(object):
return dict(account=_translate_keys(account))
-def resource_factory():
+def create_resource():
metadata = {
"attributes": {
"account": ["id", "name", "description", "manager"],
diff --git a/nova/api/openstack/backup_schedules.py b/nova/api/openstack/backup_schedules.py
index d08a4799c..71a14d4ce 100644
--- a/nova/api/openstack/backup_schedules.py
+++ b/nova/api/openstack/backup_schedules.py
@@ -52,7 +52,7 @@ class Controller(object):
return faults.Fault(exc.HTTPNotImplemented())
-def resource_factory():
+def create_resource():
metadata = {
'attributes': {
'backupSchedule': [],
diff --git a/nova/api/openstack/consoles.py b/nova/api/openstack/consoles.py
index 56f79db60..bccf04d8f 100644
--- a/nova/api/openstack/consoles.py
+++ b/nova/api/openstack/consoles.py
@@ -89,7 +89,7 @@ class Controller(object):
return exc.HTTPAccepted()
-def resource_factory():
+def create_resource():
metadata = {
'attributes': {
'console': [],
diff --git a/nova/api/openstack/flavors.py b/nova/api/openstack/flavors.py
index 9e98e6c27..a21ff6cb2 100644
--- a/nova/api/openstack/flavors.py
+++ b/nova/api/openstack/flavors.py
@@ -74,7 +74,7 @@ class ControllerV11(Controller):
return views.flavors.ViewBuilderV11(base_url)
-def resource_factory(version='1.0'):
+def create_resource(version='1.0'):
controller = {
'1.0': ControllerV10,
'1.1': ControllerV11,
diff --git a/nova/api/openstack/image_metadata.py b/nova/api/openstack/image_metadata.py
index 8acde9fe8..88e10168d 100644
--- a/nova/api/openstack/image_metadata.py
+++ b/nova/api/openstack/image_metadata.py
@@ -102,7 +102,7 @@ class Controller(object):
self.image_service.update(context, image_id, img, None)
-def resource_factory():
+def create_resource():
serializers = {
'application/xml': wsgi.XMLDictSerializer(xmlns=wsgi.XMLNS_V11),
}
diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py
index a9071ed8a..3376f358a 100644
--- a/nova/api/openstack/images.py
+++ b/nova/api/openstack/images.py
@@ -145,7 +145,7 @@ class ControllerV11(Controller):
return common.XML_NS_V11
-def resource_factory(version='1.0'):
+def create_resource(version='1.0'):
controller = {
'1.0': ControllerV10,
'1.1': ControllerV11,
diff --git a/nova/api/openstack/ips.py b/nova/api/openstack/ips.py
index 87c8c997a..abea71830 100644
--- a/nova/api/openstack/ips.py
+++ b/nova/api/openstack/ips.py
@@ -63,7 +63,7 @@ class Controller(object):
return faults.Fault(exc.HTTPNotImplemented())
-def resource_factory():
+def create_resource():
metadata = {
'list_collections': {
'public': {'item_name': 'ip', 'item_key': 'addr'},
diff --git a/nova/api/openstack/limits.py b/nova/api/openstack/limits.py
index b0e093702..2d9fe356f 100644
--- a/nova/api/openstack/limits.py
+++ b/nova/api/openstack/limits.py
@@ -73,7 +73,7 @@ class LimitsControllerV11(LimitsController):
return limits_views.ViewBuilderV11()
-def resource_factory(version='1.0'):
+def create_resource(version='1.0'):
controller = {
'1.0': LimitsControllerV10,
'1.1': LimitsControllerV11,
diff --git a/nova/api/openstack/server_metadata.py b/nova/api/openstack/server_metadata.py
index eff98c060..b38b84a2a 100644
--- a/nova/api/openstack/server_metadata.py
+++ b/nova/api/openstack/server_metadata.py
@@ -91,7 +91,7 @@ class Controller(object):
raise error
-def resource_factory():
+def create_resource():
serializers = {
'application/xml': wsgi.XMLDictSerializer(xmlns=wsgi.XMLNS_V11),
}
diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py
index 8f39bd256..bdd2960d9 100644
--- a/nova/api/openstack/servers.py
+++ b/nova/api/openstack/servers.py
@@ -784,7 +784,7 @@ class ServerXMLDeserializer(wsgi.XMLDeserializer):
return ""
-def resource_factory(version='1.0'):
+def create_resource(version='1.0'):
controller = {
'1.0': ControllerV10,
'1.1': ControllerV11,
diff --git a/nova/api/openstack/shared_ip_groups.py b/nova/api/openstack/shared_ip_groups.py
index db178f2a2..4f11f8dfb 100644
--- a/nova/api/openstack/shared_ip_groups.py
+++ b/nova/api/openstack/shared_ip_groups.py
@@ -49,5 +49,5 @@ class Controller(object):
raise faults.Fault(exc.HTTPNotImplemented())
-def resource_factory():
+def create_resource():
return wsgi.Resource(Controller())
diff --git a/nova/api/openstack/users.py b/nova/api/openstack/users.py
index e14616349..50975fc1f 100644
--- a/nova/api/openstack/users.py
+++ b/nova/api/openstack/users.py
@@ -98,7 +98,7 @@ class Controller(object):
return dict(user=_translate_keys(self.manager.get_user(id)))
-def resource_factory():
+def create_resource():
metadata = {
"attributes": {
"user": ["id", "name", "access", "secret", "admin"],
diff --git a/nova/api/openstack/zones.py b/nova/api/openstack/zones.py
index e750fc230..0475deb52 100644
--- a/nova/api/openstack/zones.py
+++ b/nova/api/openstack/zones.py
@@ -93,7 +93,7 @@ class Controller(object):
return dict(zone=_scrub_zone(zone))
-def resource_factory():
+def create_resource():
metadata = {
"attributes": {
"zone": ["id", "api_url", "name", "capabilities"],
diff --git a/nova/tests/api/openstack/test_limits.py b/nova/tests/api/openstack/test_limits.py
index db859c2f8..4cf857507 100644
--- a/nova/tests/api/openstack/test_limits.py
+++ b/nova/tests/api/openstack/test_limits.py
@@ -65,7 +65,7 @@ class LimitsControllerV10Test(BaseLimitTestSuite):
def setUp(self):
"""Run before each test."""
BaseLimitTestSuite.setUp(self)
- self.controller = limits.resource_factory('1.0')
+ self.controller = limits.create_resource('1.0')
def _get_index_request(self, accept_header="application/json"):
"""Helper to set routing arguments."""
@@ -178,7 +178,7 @@ class LimitsControllerV11Test(BaseLimitTestSuite):
def setUp(self):
"""Run before each test."""
BaseLimitTestSuite.setUp(self)
- self.controller = limits.resource_factory('1.1')
+ self.controller = limits.create_resource('1.1')
def _get_index_request(self, accept_header="application/json"):
"""Helper to set routing arguments."""
--
cgit
From 2c16eb37822b3ebdb14ac36df26362636d0f5078 Mon Sep 17 00:00:00 2001
From: Brian Waldon
Date: Fri, 20 May 2011 16:36:10 -0400
Subject: minor cleanup
---
nova/api/openstack/images.py | 3 ---
nova/api/openstack/servers.py | 3 ---
nova/tests/api/openstack/test_wsgi.py | 1 -
3 files changed, 7 deletions(-)
diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py
index 3376f358a..7f5551664 100644
--- a/nova/api/openstack/images.py
+++ b/nova/api/openstack/images.py
@@ -141,9 +141,6 @@ class ControllerV11(Controller):
base_url = request.application_url
return images_view.ViewBuilderV11(base_url)
- def get_default_xmlns(self, req):
- return common.XML_NS_V11
-
def create_resource(version='1.0'):
controller = {
diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py
index bdd2960d9..313321d7d 100644
--- a/nova/api/openstack/servers.py
+++ b/nova/api/openstack/servers.py
@@ -703,9 +703,6 @@ class ControllerV11(Controller):
raise exc.HTTPBadRequest(msg)
return password
- def get_default_xmlns(self, req):
- return common.XML_NS_V11
-
class ServerXMLDeserializer(wsgi.XMLDeserializer):
"""
diff --git a/nova/tests/api/openstack/test_wsgi.py b/nova/tests/api/openstack/test_wsgi.py
index 6c57d3e4f..89603d82b 100644
--- a/nova/tests/api/openstack/test_wsgi.py
+++ b/nova/tests/api/openstack/test_wsgi.py
@@ -94,7 +94,6 @@ class XMLDictSerializerTest(test.TestCase):
def test_xml(self):
input_dict = dict(servers=dict(a=(2, 3)))
expected_xml = '(2,3)'
- xmlns = "testing xmlns"
serializer = wsgi.XMLDictSerializer(xmlns="asdf")
result = serializer.serialize(input_dict)
result = result.replace('\n', '').replace(' ', '')
--
cgit
From 2d834fa19078c645e3c36001b5dd34fb8e708f0a Mon Sep 17 00:00:00 2001
From: Brian Waldon
Date: Thu, 26 May 2011 14:09:59 -0400
Subject: review fixups
---
nova/api/openstack/wsgi.py | 27 ++++++++++++++++-----------
nova/tests/api/openstack/test_wsgi.py | 2 +-
nova/wsgi.py | 4 ++--
3 files changed, 19 insertions(+), 14 deletions(-)
diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py
index bd840a6f7..5577d326f 100644
--- a/nova/api/openstack/wsgi.py
+++ b/nova/api/openstack/wsgi.py
@@ -62,7 +62,7 @@ class TextDeserializer(object):
"""Find local deserialization method and parse request body."""
try:
action_method = getattr(self, action)
- except Exception:
+ except (AttributeError, TypeError):
action_method = self.default
return action_method(datastring)
@@ -162,7 +162,7 @@ class RequestDeserializer(object):
def get_deserializer(self, content_type):
try:
return self.deserializers[content_type]
- except Exception:
+ except (KeyError, TypeError):
raise exception.InvalidContentType(content_type=content_type)
def get_expected_content_type(self, request):
@@ -172,16 +172,20 @@ class RequestDeserializer(object):
"""Parse dictionary created by routes library."""
try:
args = request_environment['wsgiorg.routing_args'][1].copy()
+ except Exception:
+ return {}
+ try:
del args['controller']
+ except KeyError:
+ pass
- if 'format' in args:
- del args['format']
-
- return args
-
+ try:
+ del args['format']
except KeyError:
- return {}
+ pass
+
+ return args
class DictSerializer(object):
@@ -191,7 +195,7 @@ class DictSerializer(object):
"""Find local serialization method and encode response body."""
try:
action_method = getattr(self, action)
- except Exception:
+ except (AttributeError, TypeError):
action_method = self.default
return action_method(data)
@@ -316,7 +320,7 @@ class ResponseSerializer(object):
def get_serializer(self, content_type):
try:
return self.serializers[content_type]
- except Exception:
+ except (KeyError, TypeError):
raise exception.InvalidContentType(content_type=content_type)
@@ -347,7 +351,8 @@ class Resource(wsgi.Application):
def __call__(self, request):
"""WSGI method that controls (de)serialization and method dispatch."""
- LOG.debug("%s %s" % (request.method, request.url))
+ LOG.debug("%(method)s %(url)s" % {"method": request.method,
+ "url": request.url})
try:
action, action_args, accept = self.deserializer.deserialize(
diff --git a/nova/tests/api/openstack/test_wsgi.py b/nova/tests/api/openstack/test_wsgi.py
index 89603d82b..ebbdc9409 100644
--- a/nova/tests/api/openstack/test_wsgi.py
+++ b/nova/tests/api/openstack/test_wsgi.py
@@ -205,7 +205,7 @@ class ResponseSerializerTest(test.TestCase):
def test_serialize_response_dict_to_unknown_content_type(self):
self.assertRaises(exception.InvalidContentType,
self.serializer.serialize,
- 'application/unknown', {})
+ {}, 'application/unknown')
class RequestDeserializerTest(test.TestCase):
diff --git a/nova/wsgi.py b/nova/wsgi.py
index d59d2ee13..33ba852bc 100644
--- a/nova/wsgi.py
+++ b/nova/wsgi.py
@@ -260,8 +260,8 @@ class Router(object):
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 a controller, who will route
- the request to the action method.
+ well and have your controller be an object that can route
+ the request to the action-specific method.
Examples:
mapper = routes.Mapper()
--
cgit
From 3264c18fffa26b1288fc253f2526d9a78fdc9dd4 Mon Sep 17 00:00:00 2001
From: Brian Waldon
Date: Thu, 26 May 2011 15:01:24 -0400
Subject: cleaning up getattr calls with default param
---
nova/api/openstack/wsgi.py | 16 ++++------------
1 file changed, 4 insertions(+), 12 deletions(-)
diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py
index 5577d326f..7a747842e 100644
--- a/nova/api/openstack/wsgi.py
+++ b/nova/api/openstack/wsgi.py
@@ -58,13 +58,9 @@ class Request(webob.Request):
class TextDeserializer(object):
"""Custom request body deserialization based on controller action name."""
- def deserialize(self, datastring, action=None):
+ def deserialize(self, datastring, action='default'):
"""Find local deserialization method and parse request body."""
- try:
- action_method = getattr(self, action)
- except (AttributeError, TypeError):
- action_method = self.default
-
+ action_method = getattr(self, action, self.default)
return action_method(datastring)
def default(self, datastring):
@@ -191,13 +187,9 @@ class RequestDeserializer(object):
class DictSerializer(object):
"""Custom response body serialization based on controller action name."""
- def serialize(self, data, action=None):
+ def serialize(self, data, action='default'):
"""Find local serialization method and encode response body."""
- try:
- action_method = getattr(self, action)
- except (AttributeError, TypeError):
- action_method = self.default
-
+ action_method = getattr(self, action, self.default)
return action_method(data)
def default(self, data):
--
cgit
From a79f01fcea81bb6be233a65670c6a79af8534a10 Mon Sep 17 00:00:00 2001
From: Brian Waldon
Date: Thu, 26 May 2011 17:27:48 -0400
Subject: adding TODOs per dabo's review
---
nova/api/openstack/wsgi.py | 2 ++
1 file changed, 2 insertions(+)
diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py
index 7a747842e..ddf4e6fa9 100644
--- a/nova/api/openstack/wsgi.py
+++ b/nova/api/openstack/wsgi.py
@@ -237,6 +237,7 @@ class XMLDictSerializer(DictSerializer):
if xmlns:
result.setAttribute('xmlns', xmlns)
+ #TODO(bcwaldon): accomplish this without a type-check
if type(data) is list:
collections = metadata.get('list_collections', {})
if nodename in collections:
@@ -255,6 +256,7 @@ class XMLDictSerializer(DictSerializer):
for item in data:
node = self._to_xml_node(doc, metadata, singular, item)
result.appendChild(node)
+ #TODO(bcwaldon): accomplish this without a type-check
elif type(data) is dict:
collections = metadata.get('dict_collections', {})
if nodename in collections:
--
cgit