refactoring wsgi to separate controller/serialization/deserialization logic; creating osapi-specific module

This commit is contained in:
Brian Waldon
2011-05-18 19:13:22 -04:00
parent 0946bac5fa
commit 5e722ea7b9
19 changed files with 624 additions and 366 deletions

View File

@@ -26,7 +26,7 @@ import webob.exc
from nova import flags from nova import flags
from nova import log as logging 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 accounts
from nova.api.openstack import faults from nova.api.openstack import faults
from nova.api.openstack import backup_schedules 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 server_metadata
from nova.api.openstack import shared_ip_groups from nova.api.openstack import shared_ip_groups
from nova.api.openstack import users from nova.api.openstack import users
from nova.api.openstack import wsgi
from nova.api.openstack import zones 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.') '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.""" """Calls down the middleware stack, making exceptions into faults."""
@webob.dec.wsgify(RequestClass=wsgi.Request) @webob.dec.wsgify(RequestClass=wsgi.Request)
@@ -63,7 +64,7 @@ class FaultWrapper(wsgi.Middleware):
return faults.Fault(exc) return faults.Fault(exc)
class APIRouter(wsgi.Router): class APIRouter(base_wsgi.Router):
""" """
Routes requests on the OpenStack API to the appropriate controller Routes requests on the OpenStack API to the appropriate controller
and method. and method.
@@ -97,18 +98,20 @@ class APIRouter(wsgi.Router):
server_members['reset_network'] = 'POST' server_members['reset_network'] = 'POST'
server_members['inject_network_info'] = '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'}), collection={'detail': 'GET', 'info': 'GET'}),
mapper.resource("user", "users", controller=users.Controller(), mapper.resource("user", "users",
controller=users.resource_factory(),
collection={'detail': 'GET'}) collection={'detail': 'GET'})
mapper.resource("account", "accounts", mapper.resource("account", "accounts",
controller=accounts.Controller(), controller=accounts.resource_factory(),
collection={'detail': 'GET'}) collection={'detail': 'GET'})
mapper.resource("console", "consoles", mapper.resource("console", "consoles",
controller=consoles.Controller(), controller=consoles.resource_factory(),
parent_resource=dict(member_name='server', parent_resource=dict(member_name='server',
collection_name='servers')) collection_name='servers'))
@@ -121,31 +124,31 @@ class APIRouterV10(APIRouter):
def _setup_routes(self, mapper): def _setup_routes(self, mapper):
super(APIRouterV10, self)._setup_routes(mapper) super(APIRouterV10, self)._setup_routes(mapper)
mapper.resource("server", "servers", mapper.resource("server", "servers",
controller=servers.ControllerV10(), controller=servers.resource_factory('1.0'),
collection={'detail': 'GET'}, collection={'detail': 'GET'},
member=self.server_members) member=self.server_members)
mapper.resource("image", "images", mapper.resource("image", "images",
controller=images.ControllerV10(), controller=images.resource_factory('1.0'),
collection={'detail': 'GET'}) collection={'detail': 'GET'})
mapper.resource("flavor", "flavors", mapper.resource("flavor", "flavors",
controller=flavors.ControllerV10(), controller=flavors.resource_factory('1.0'),
collection={'detail': 'GET'}) collection={'detail': 'GET'})
mapper.resource("shared_ip_group", "shared_ip_groups", mapper.resource("shared_ip_group", "shared_ip_groups",
collection={'detail': 'GET'}, collection={'detail': 'GET'},
controller=shared_ip_groups.Controller()) controller=shared_ip_groups.resource_factory())
mapper.resource("backup_schedule", "backup_schedule", mapper.resource("backup_schedule", "backup_schedule",
controller=backup_schedules.Controller(), controller=backup_schedules.resource_factory(),
parent_resource=dict(member_name='server', parent_resource=dict(member_name='server',
collection_name='servers')) collection_name='servers'))
mapper.resource("limit", "limits", 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'), collection=dict(public='GET', private='GET'),
parent_resource=dict(member_name='server', parent_resource=dict(member_name='server',
collection_name='servers')) collection_name='servers'))
@@ -157,27 +160,27 @@ class APIRouterV11(APIRouter):
def _setup_routes(self, mapper): def _setup_routes(self, mapper):
super(APIRouterV11, self)._setup_routes(mapper) super(APIRouterV11, self)._setup_routes(mapper)
mapper.resource("server", "servers", mapper.resource("server", "servers",
controller=servers.ControllerV11(), controller=servers.resource_factory('1.1'),
collection={'detail': 'GET'}, collection={'detail': 'GET'},
member=self.server_members) member=self.server_members)
mapper.resource("image", "images", mapper.resource("image", "images",
controller=images.ControllerV11(), controller=images.resource_factory('1.1'),
collection={'detail': 'GET'}) collection={'detail': 'GET'})
mapper.resource("image_meta", "meta", mapper.resource("image_meta", "meta",
controller=image_metadata.Controller(), controller=image_metadata.resource_factory(),
parent_resource=dict(member_name='image', parent_resource=dict(member_name='image',
collection_name='images')) collection_name='images'))
mapper.resource("server_meta", "meta", mapper.resource("server_meta", "meta",
controller=server_metadata.Controller(), controller=server_metadata.resource_factory(),
parent_resource=dict(member_name='server', parent_resource=dict(member_name='server',
collection_name='servers')) collection_name='servers'))
mapper.resource("flavor", "flavors", mapper.resource("flavor", "flavors",
controller=flavors.ControllerV11(), controller=flavors.resource_factory('1.1'),
collection={'detail': 'GET'}) collection={'detail': 'GET'})
mapper.resource("limit", "limits", mapper.resource("limit", "limits",
controller=limits.LimitsControllerV11()) controller=limits.resource_factory('1.1'))

View File

@@ -20,8 +20,9 @@ from nova import flags
from nova import log as logging from nova import log as logging
from nova.auth import manager from nova.auth import manager
from nova.api.openstack import common
from nova.api.openstack import faults from nova.api.openstack import faults
from nova.api.openstack import wsgi
FLAGS = flags.FLAGS FLAGS = flags.FLAGS
LOG = logging.getLogger('nova.api.openstack') LOG = logging.getLogger('nova.api.openstack')
@@ -34,12 +35,7 @@ def _translate_keys(account):
manager=account.project_manager_id) manager=account.project_manager_id)
class Controller(common.OpenstackController): class Controller(object):
_serialization_metadata = {
'application/xml': {
"attributes": {
"account": ["id", "name", "description", "manager"]}}}
def __init__(self): def __init__(self):
self.manager = manager.AuthManager() self.manager = manager.AuthManager()
@@ -66,20 +62,33 @@ class Controller(common.OpenstackController):
self.manager.delete_project(id) self.manager.delete_project(id)
return {} return {}
def create(self, req): def create(self, req, body):
"""We use update with create-or-update semantics """We use update with create-or-update semantics
because the id comes from an external source""" because the id comes from an external source"""
raise faults.Fault(webob.exc.HTTPNotImplemented()) raise faults.Fault(webob.exc.HTTPNotImplemented())
def update(self, req, id): def update(self, req, id, body):
"""This is really create or update.""" """This is really create or update."""
self._check_admin(req.environ['nova.context']) self._check_admin(req.environ['nova.context'])
env = self._deserialize(req.body, req.get_content_type()) description = body['account'].get('description')
description = env['account'].get('description') manager = body['account'].get('manager')
manager = env['account'].get('manager')
try: try:
account = self.manager.get_project(id) account = self.manager.get_project(id)
self.manager.modify_project(id, manager, description) self.manager.modify_project(id, manager, description)
except exception.NotFound: except exception.NotFound:
account = self.manager.create_project(id, manager, description) account = self.manager.create_project(id, manager, description)
return dict(account=_translate_keys(account)) 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)

View File

@@ -19,9 +19,8 @@ import time
from webob import exc from webob import exc
from nova.api.openstack import common
from nova.api.openstack import faults from nova.api.openstack import faults
import nova.image.service from nova.api.openstack import wsgi
def _translate_keys(inst): def _translate_keys(inst):
@@ -29,14 +28,9 @@ def _translate_keys(inst):
return dict(backupSchedule=inst) return dict(backupSchedule=inst)
class Controller(common.OpenstackController): class Controller(object):
""" The backup schedule API controller for the Openstack API """ """ The backup schedule API controller for the Openstack API """
_serialization_metadata = {
'application/xml': {
'attributes': {
'backupSchedule': []}}}
def __init__(self): def __init__(self):
pass pass
@@ -48,7 +42,7 @@ class Controller(common.OpenstackController):
""" Returns a single backup schedule for a given instance """ """ Returns a single backup schedule for a given instance """
return faults.Fault(exc.HTTPNotImplemented()) 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 """ No actual update method required, since the existing API allows
both create and update through a POST """ both create and update through a POST """
return faults.Fault(exc.HTTPNotImplemented()) return faults.Fault(exc.HTTPNotImplemented())
@@ -56,3 +50,18 @@ class Controller(common.OpenstackController):
def delete(self, req, server_id, id): def delete(self, req, server_id, id):
""" Deletes an existing backup schedule """ """ Deletes an existing backup schedule """
return faults.Fault(exc.HTTPNotImplemented()) 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)

View File

@@ -19,8 +19,8 @@ from webob import exc
from nova import console from nova import console
from nova import exception from nova import exception
from nova.api.openstack import common
from nova.api.openstack import faults from nova.api.openstack import faults
from nova.api.openstack import wsgi
def _translate_keys(cons): def _translate_keys(cons):
@@ -43,14 +43,9 @@ def _translate_detail_keys(cons):
return dict(console=info) return dict(console=info)
class Controller(common.OpenstackController): class Controller(object):
"""The Consoles Controller for the Openstack API""" """The Consoles Controller for the Openstack API"""
_serialization_metadata = {
'application/xml': {
'attributes': {
'console': []}}}
def __init__(self): def __init__(self):
self.console_api = console.API() self.console_api = console.API()
super(Controller, self).__init__() super(Controller, self).__init__()
@@ -63,9 +58,8 @@ class Controller(common.OpenstackController):
return dict(consoles=[_translate_keys(console) return dict(consoles=[_translate_keys(console)
for console in consoles]) for console in consoles])
def create(self, req, server_id): def create(self, req, server_id, body):
"""Creates a new console""" """Creates a new console"""
#info = self._deserialize(req.body, req.get_content_type())
self.console_api.create_console( self.console_api.create_console(
req.environ['nova.context'], req.environ['nova.context'],
int(server_id)) int(server_id))
@@ -94,3 +88,17 @@ class Controller(common.OpenstackController):
except exception.NotFound: except exception.NotFound:
return faults.Fault(exc.HTTPNotFound()) return faults.Fault(exc.HTTPNotFound())
return exc.HTTPAccepted() return exc.HTTPAccepted()
def resource_factory():
metadata = {
'attributes': {
'console': [],
},
}
serializers = {
'application/xml': wsgi.XMLSerializer(metadata=metadata),
}
return wsgi.Resource(Controller(), serializers=serializers)

View File

@@ -19,22 +19,13 @@ import webob
from nova import db from nova import db
from nova import exception from nova import exception
from nova.api.openstack import common
from nova.api.openstack import views 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.""" """Flavor controller for the OpenStack API."""
_serialization_metadata = {
'application/xml': {
"attributes": {
"flavor": ["id", "name", "ram", "disk"],
"link": ["rel", "type", "href"],
}
}
}
def index(self, req): def index(self, req):
"""Return all flavors in brief.""" """Return all flavors in brief."""
items = self._get_flavors(req, is_detail=False) items = self._get_flavors(req, is_detail=False)
@@ -71,14 +62,31 @@ class Controller(common.OpenstackController):
class ControllerV10(Controller): class ControllerV10(Controller):
def _get_view_builder(self, req): def _get_view_builder(self, req):
return views.flavors.ViewBuilder() return views.flavors.ViewBuilder()
class ControllerV11(Controller): class ControllerV11(Controller):
def _get_view_builder(self, req): def _get_view_builder(self, req):
base_url = req.application_url base_url = req.application_url
return views.flavors.ViewBuilderV11(base_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)

View File

@@ -21,19 +21,18 @@ from nova import flags
from nova import quota from nova import quota
from nova import utils from nova import utils
from nova import wsgi from nova import wsgi
from nova.api.openstack import common
from nova.api.openstack import faults from nova.api.openstack import faults
from nova.api.openstack import wsgi
FLAGS = flags.FLAGS FLAGS = flags.FLAGS
class Controller(common.OpenstackController): class Controller(object):
"""The image metadata API controller for the Openstack API""" """The image metadata API controller for the Openstack API"""
def __init__(self): def __init__(self):
self.image_service = utils.import_object(FLAGS.image_service) self.image_service = utils.import_object(FLAGS.image_service)
super(Controller, self).__init__()
def _get_metadata(self, context, image_id, image=None): def _get_metadata(self, context, image_id, image=None):
if not image: if not image:
@@ -64,9 +63,8 @@ class Controller(common.OpenstackController):
else: else:
return faults.Fault(exc.HTTPNotFound()) return faults.Fault(exc.HTTPNotFound())
def create(self, req, image_id): def create(self, req, image_id, body):
context = req.environ['nova.context'] context = req.environ['nova.context']
body = self._deserialize(req.body, req.get_content_type())
img = self.image_service.show(context, image_id) img = self.image_service.show(context, image_id)
metadata = self._get_metadata(context, image_id, img) metadata = self._get_metadata(context, image_id, img)
if 'metadata' in body: if 'metadata' in body:
@@ -77,9 +75,8 @@ class Controller(common.OpenstackController):
self.image_service.update(context, image_id, img, None) self.image_service.update(context, image_id, img, None)
return dict(metadata=metadata) return dict(metadata=metadata)
def update(self, req, image_id, id): def update(self, req, image_id, id, body):
context = req.environ['nova.context'] context = req.environ['nova.context']
body = self._deserialize(req.body, req.get_content_type())
if not id in body: if not id in body:
expl = _('Request body and URI mismatch') expl = _('Request body and URI mismatch')
raise exc.HTTPBadRequest(explanation=expl) raise exc.HTTPBadRequest(explanation=expl)
@@ -104,3 +101,11 @@ class Controller(common.OpenstackController):
metadata.pop(id) metadata.pop(id)
img['properties'] = metadata img['properties'] = metadata
self.image_service.update(context, image_id, img, None) 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)

View File

@@ -23,25 +23,16 @@ from nova import utils
from nova.api.openstack import common from nova.api.openstack import common
from nova.api.openstack import faults from nova.api.openstack import faults
from nova.api.openstack.views import images as images_view from nova.api.openstack.views import images as images_view
from nova.api.openstack import wsgi
LOG = log.getLogger('nova.api.openstack.images') LOG = log.getLogger('nova.api.openstack.images')
FLAGS = flags.FLAGS FLAGS = flags.FLAGS
class Controller(common.OpenstackController): class Controller(object):
"""Base `wsgi.Controller` for retrieving/displaying images.""" """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): def __init__(self, image_service=None, compute_service=None):
"""Initialize new `ImageController`. """Initialize new `ImageController`.
@@ -153,3 +144,30 @@ class ControllerV11(Controller):
def get_default_xmlns(self, req): def get_default_xmlns(self, req):
return common.XML_NS_V11 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)

View File

@@ -20,23 +20,14 @@ import time
from webob import exc from webob import exc
import nova import nova
import nova.api.openstack.views.addresses
from nova.api.openstack import common
from nova.api.openstack import faults 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.""" """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): def __init__(self):
self.compute_api = nova.compute.API() self.compute_api = nova.compute.API()
self.builder = nova.api.openstack.views.addresses.ViewBuilderV10() self.builder = nova.api.openstack.views.addresses.ViewBuilderV10()
@@ -65,8 +56,24 @@ class Controller(common.OpenstackController):
def show(self, req, server_id, id): def show(self, req, server_id, id):
return faults.Fault(exc.HTTPNotImplemented()) return faults.Fault(exc.HTTPNotImplemented())
def create(self, req, server_id): def create(self, req, server_id, body):
return faults.Fault(exc.HTTPNotImplemented()) return faults.Fault(exc.HTTPNotImplemented())
def delete(self, req, server_id, id): def delete(self, req, server_id, id):
return faults.Fault(exc.HTTPNotImplemented()) 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)

View File

@@ -30,10 +30,11 @@ from collections import defaultdict
from webob.dec import wsgify 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 common
from nova.api.openstack import faults from nova.api.openstack import faults
from nova.api.openstack.views import limits as limits_views 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(). # Convenience constants for the limits dictionary passed to Limiter().
@@ -43,23 +44,11 @@ PER_HOUR = 60 * 60
PER_DAY = 60 * 60 * 24 PER_DAY = 60 * 60 * 24
class LimitsController(common.OpenstackController): class LimitsController(object):
""" """
Controller for accessing limits in the OpenStack API. 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): def index(self, req):
""" """
Return all global and rate limit information. Return all global and rate limit information.
@@ -84,6 +73,35 @@ class LimitsControllerV11(LimitsController):
return limits_views.ViewBuilderV11() 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): class Limit(object):
""" """
Stores information about a limit for HTTP requets. 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 Rate-limits requests passing through this middleware. All limit information
is stored in memory for this implementation. is stored in memory for this implementation.
@@ -209,7 +227,7 @@ class RateLimitingMiddleware(wsgi.Middleware):
@param application: WSGI application to wrap @param application: WSGI application to wrap
@param limits: List of dictionaries describing limits @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) self._limiter = Limiter(limits or DEFAULT_LIMITS)
@wsgify(RequestClass=wsgi.Request) @wsgify(RequestClass=wsgi.Request)

View File

@@ -19,12 +19,11 @@ from webob import exc
from nova import compute from nova import compute
from nova import quota 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 faults
from nova.api.openstack import wsgi
class Controller(common.OpenstackController): class Controller(object):
""" The server metadata API controller for the Openstack API """ """ The server metadata API controller for the Openstack API """
def __init__(self): def __init__(self):
@@ -43,10 +42,9 @@ class Controller(common.OpenstackController):
context = req.environ['nova.context'] context = req.environ['nova.context']
return self._get_metadata(context, server_id) 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'] context = req.environ['nova.context']
data = self._deserialize(req.body, req.get_content_type()) metadata = body.get('metadata')
metadata = data.get('metadata')
try: try:
self.compute_api.update_or_create_instance_metadata(context, self.compute_api.update_or_create_instance_metadata(context,
server_id, server_id,
@@ -55,9 +53,8 @@ class Controller(common.OpenstackController):
self._handle_quota_error(error) self._handle_quota_error(error)
return req.body return req.body
def update(self, req, server_id, id): def update(self, req, server_id, id, body):
context = req.environ['nova.context'] context = req.environ['nova.context']
body = self._deserialize(req.body, req.get_content_type())
if not id in body: if not id in body:
expl = _('Request body and URI mismatch') expl = _('Request body and URI mismatch')
raise exc.HTTPBadRequest(explanation=expl) raise exc.HTTPBadRequest(explanation=expl)
@@ -92,3 +89,11 @@ class Controller(common.OpenstackController):
if error.code == "MetadataLimitExceeded": if error.code == "MetadataLimitExceeded":
raise exc.HTTPBadRequest(explanation=error.message) raise exc.HTTPBadRequest(explanation=error.message)
raise error raise error
def resource_factory():
serializers = {
'application/xml': wsgi.XMLSerializer(xmlns=wsgi.XMLNS_V11),
}
return wsgi.Resource(Controller(), serializers=serializers)

View File

@@ -31,6 +31,7 @@ import nova.api.openstack.views.addresses
import nova.api.openstack.views.flavors import nova.api.openstack.views.flavors
import nova.api.openstack.views.images import nova.api.openstack.views.images
import nova.api.openstack.views.servers import nova.api.openstack.views.servers
from nova.api.openstack import wsgi
from nova.auth import manager as auth_manager from nova.auth import manager as auth_manager
from nova.compute import instance_types from nova.compute import instance_types
import nova.api.openstack import nova.api.openstack
@@ -41,31 +42,12 @@ LOG = logging.getLogger('nova.api.openstack.servers')
FLAGS = flags.FLAGS FLAGS = flags.FLAGS
class Controller(common.OpenstackController): class Controller(object):
""" The Server API controller for the OpenStack API """ """ 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): def __init__(self):
self.compute_api = compute.API() self.compute_api = compute.API()
self._image_service = utils.import_object(FLAGS.image_service) self._image_service = utils.import_object(FLAGS.image_service)
super(Controller, self).__init__()
def index(self, req): def index(self, req):
""" Returns a list of server names and ids for a given user """ """ 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 faults.Fault(exc.HTTPNotFound())
return exc.HTTPAccepted() return exc.HTTPAccepted()
def create(self, req): def create(self, req, body):
""" Creates a new server for a given user """ """ Creates a new server for a given user """
env = self._deserialize_create(req) if not body:
if not env:
return faults.Fault(exc.HTTPUnprocessableEntity()) return faults.Fault(exc.HTTPUnprocessableEntity())
context = req.environ['nova.context'] 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_name = None
key_data = None key_data = None
@@ -140,7 +121,7 @@ class Controller(common.OpenstackController):
key_name = key_pair['name'] key_name = key_pair['name']
key_data = key_pair['public_key'] 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: try:
image_id = common.get_image_id_from_image_hash(self._image_service, image_id = common.get_image_id_from_image_hash(self._image_service,
context, requested_image_id) context, requested_image_id)
@@ -151,18 +132,18 @@ class Controller(common.OpenstackController):
kernel_id, ramdisk_id = self._get_kernel_ramdisk_from_image( kernel_id, ramdisk_id = self._get_kernel_ramdisk_from_image(
req, image_id) req, image_id)
personality = env['server'].get('personality') personality = body['server'].get('personality')
injected_files = [] injected_files = []
if personality: if personality:
injected_files = self._get_injected_files(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") msg = _("Server name is not defined")
return exc.HTTPBadRequest(msg) return exc.HTTPBadRequest(msg)
name = env['server']['name'] name = body['server']['name']
self._validate_server_name(name) self._validate_server_name(name)
name = name.strip() name = name.strip()
@@ -179,7 +160,7 @@ class Controller(common.OpenstackController):
display_description=name, display_description=name,
key_name=key_name, key_name=key_name,
key_data=key_data, key_data=key_data,
metadata=env['server'].get('metadata', {}), metadata=body['server'].get('metadata', {}),
injected_files=injected_files) injected_files=injected_files)
except quota.QuotaError as error: except quota.QuotaError as error:
self._handle_quota_error(error) self._handle_quota_error(error)
@@ -194,18 +175,6 @@ class Controller(common.OpenstackController):
password) password)
return server 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): def _get_injected_files(self, personality):
""" """
Create a list of injected files from the personality attribute Create a list of injected files from the personality attribute
@@ -255,24 +224,23 @@ class Controller(common.OpenstackController):
return utils.generate_password(16) return utils.generate_password(16)
@scheduler_api.redirect_handler @scheduler_api.redirect_handler
def update(self, req, id): def update(self, req, id, body):
""" Updates the server name or password """ """ Updates the server name or password """
if len(req.body) == 0: if len(req.body) == 0:
raise exc.HTTPUnprocessableEntity() raise exc.HTTPUnprocessableEntity()
inst_dict = self._deserialize(req.body, req.get_content_type()) if not body:
if not inst_dict:
return faults.Fault(exc.HTTPUnprocessableEntity()) return faults.Fault(exc.HTTPUnprocessableEntity())
ctxt = req.environ['nova.context'] ctxt = req.environ['nova.context']
update_dict = {} update_dict = {}
if 'name' in inst_dict['server']: if 'name' in body['server']:
name = inst_dict['server']['name'] name = body['server']['name']
self._validate_server_name(name) self._validate_server_name(name)
update_dict['display_name'] = name.strip() update_dict['display_name'] = name.strip()
self._parse_update(ctxt, id, inst_dict, update_dict) self._parse_update(ctxt, id, body, update_dict)
try: try:
self.compute_api.update(ctxt, id, **update_dict) self.compute_api.update(ctxt, id, **update_dict)
@@ -294,7 +262,7 @@ class Controller(common.OpenstackController):
pass pass
@scheduler_api.redirect_handler @scheduler_api.redirect_handler
def action(self, req, id): def action(self, req, id, body):
"""Multi-purpose method used to reboot, rebuild, or """Multi-purpose method used to reboot, rebuild, or
resize a server""" resize a server"""
@@ -307,10 +275,9 @@ class Controller(common.OpenstackController):
'rebuild': self._action_rebuild, 'rebuild': self._action_rebuild,
} }
input_dict = self._deserialize(req.body, req.get_content_type())
for key in actions.keys(): for key in actions.keys():
if key in input_dict: if key in body:
return actions[key](input_dict, req, id) return actions[key](body, req, id)
return faults.Fault(exc.HTTPNotImplemented()) return faults.Fault(exc.HTTPNotImplemented())
def _action_change_password(self, input_dict, req, id): def _action_change_password(self, input_dict, req, id):
@@ -410,7 +377,7 @@ class Controller(common.OpenstackController):
return exc.HTTPAccepted() return exc.HTTPAccepted()
@scheduler_api.redirect_handler @scheduler_api.redirect_handler
def reset_network(self, req, id): def reset_network(self, req, id, body):
""" """
Reset networking on an instance (admin only). Reset networking on an instance (admin only).
@@ -425,7 +392,7 @@ class Controller(common.OpenstackController):
return exc.HTTPAccepted() return exc.HTTPAccepted()
@scheduler_api.redirect_handler @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). Inject network info for an instance (admin only).
@@ -440,7 +407,7 @@ class Controller(common.OpenstackController):
return exc.HTTPAccepted() return exc.HTTPAccepted()
@scheduler_api.redirect_handler @scheduler_api.redirect_handler
def pause(self, req, id): def pause(self, req, id, body):
""" Permit Admins to Pause the server. """ """ Permit Admins to Pause the server. """
ctxt = req.environ['nova.context'] ctxt = req.environ['nova.context']
try: try:
@@ -452,7 +419,7 @@ class Controller(common.OpenstackController):
return exc.HTTPAccepted() return exc.HTTPAccepted()
@scheduler_api.redirect_handler @scheduler_api.redirect_handler
def unpause(self, req, id): def unpause(self, req, id, body):
""" Permit Admins to Unpause the server. """ """ Permit Admins to Unpause the server. """
ctxt = req.environ['nova.context'] ctxt = req.environ['nova.context']
try: try:
@@ -464,7 +431,7 @@ class Controller(common.OpenstackController):
return exc.HTTPAccepted() return exc.HTTPAccepted()
@scheduler_api.redirect_handler @scheduler_api.redirect_handler
def suspend(self, req, id): def suspend(self, req, id, body):
"""permit admins to suspend the server""" """permit admins to suspend the server"""
context = req.environ['nova.context'] context = req.environ['nova.context']
try: try:
@@ -476,7 +443,7 @@ class Controller(common.OpenstackController):
return exc.HTTPAccepted() return exc.HTTPAccepted()
@scheduler_api.redirect_handler @scheduler_api.redirect_handler
def resume(self, req, id): def resume(self, req, id, body):
"""permit admins to resume the server from suspend""" """permit admins to resume the server from suspend"""
context = req.environ['nova.context'] context = req.environ['nova.context']
try: try:
@@ -815,3 +782,44 @@ class ServerCreateRequestXMLDeserializer(object):
if child.nodeType == child.TEXT_NODE: if child.nodeType == child.TEXT_NODE:
return child.nodeValue return child.nodeValue
return "" 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)

View File

@@ -17,29 +17,13 @@
from webob import exc from webob import exc
from nova.api.openstack import common
from nova.api.openstack import faults from nova.api.openstack import faults
from nova.api.openstack import wsgi
def _translate_keys(inst): class Controller(object):
""" 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):
""" The Shared IP Groups Controller for the Openstack API """ """ The Shared IP Groups Controller for the Openstack API """
_serialization_metadata = {
'application/xml': {
'attributes': {
'sharedIpGroup': []}}}
def index(self, req): def index(self, req):
""" Returns a list of Shared IP Groups for the user """ """ Returns a list of Shared IP Groups for the user """
raise faults.Fault(exc.HTTPNotImplemented()) raise faults.Fault(exc.HTTPNotImplemented())
@@ -48,7 +32,7 @@ class Controller(common.OpenstackController):
""" Shows in-depth information on a specific Shared IP Group """ """ Shows in-depth information on a specific Shared IP Group """
raise faults.Fault(exc.HTTPNotImplemented()) raise faults.Fault(exc.HTTPNotImplemented())
def update(self, req, id): def update(self, req, id, body):
""" You can't update a Shared IP Group """ """ You can't update a Shared IP Group """
raise faults.Fault(exc.HTTPNotImplemented()) raise faults.Fault(exc.HTTPNotImplemented())
@@ -60,6 +44,10 @@ class Controller(common.OpenstackController):
""" Returns a complete list of Shared IP Groups """ """ Returns a complete list of Shared IP Groups """
raise faults.Fault(exc.HTTPNotImplemented()) raise faults.Fault(exc.HTTPNotImplemented())
def create(self, req): def create(self, req, body):
""" Creates a new Shared IP group """ """ Creates a new Shared IP group """
raise faults.Fault(exc.HTTPNotImplemented()) raise faults.Fault(exc.HTTPNotImplemented())
def resource_factory():
return wsgi.Resource(Controller())

View File

@@ -20,8 +20,10 @@ from nova import flags
from nova import log as logging from nova import log as logging
from nova.api.openstack import common from nova.api.openstack import common
from nova.api.openstack import faults from nova.api.openstack import faults
from nova.api.openstack import wsgi
from nova.auth import manager from nova.auth import manager
FLAGS = flags.FLAGS FLAGS = flags.FLAGS
LOG = logging.getLogger('nova.api.openstack') LOG = logging.getLogger('nova.api.openstack')
@@ -34,12 +36,7 @@ def _translate_keys(user):
admin=user.admin) admin=user.admin)
class Controller(common.OpenstackController): class Controller(object):
_serialization_metadata = {
'application/xml': {
"attributes": {
"user": ["id", "name", "access", "secret", "admin"]}}}
def __init__(self): def __init__(self):
self.manager = manager.AuthManager() self.manager = manager.AuthManager()
@@ -81,23 +78,35 @@ class Controller(common.OpenstackController):
self.manager.delete_user(id) self.manager.delete_user(id)
return {} return {}
def create(self, req): def create(self, req, body):
self._check_admin(req.environ['nova.context']) self._check_admin(req.environ['nova.context'])
env = self._deserialize(req.body, req.get_content_type()) is_admin = body['user'].get('admin') in ('T', 'True', True)
is_admin = env['user'].get('admin') in ('T', 'True', True) name = body['user'].get('name')
name = env['user'].get('name') access = body['user'].get('access')
access = env['user'].get('access') secret = body['user'].get('secret')
secret = env['user'].get('secret')
user = self.manager.create_user(name, access, secret, is_admin) user = self.manager.create_user(name, access, secret, is_admin)
return dict(user=_translate_keys(user)) return dict(user=_translate_keys(user))
def update(self, req, id): def update(self, req, id, body):
self._check_admin(req.environ['nova.context']) self._check_admin(req.environ['nova.context'])
env = self._deserialize(req.body, req.get_content_type()) is_admin = body['user'].get('admin')
is_admin = env['user'].get('admin')
if is_admin is not None: if is_admin is not None:
is_admin = is_admin in ('T', 'True', True) is_admin = is_admin in ('T', 'True', True)
access = env['user'].get('access') access = body['user'].get('access')
secret = env['user'].get('secret') secret = body['user'].get('secret')
self.manager.modify_user(id, access, secret, is_admin) self.manager.modify_user(id, access, secret, is_admin)
return dict(user=_translate_keys(self.manager.get_user(id))) 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)

291
nova/api/openstack/wsgi.py Normal file
View File

@@ -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)

View File

@@ -17,6 +17,7 @@ from nova import db
from nova import flags from nova import flags
from nova import log as logging from nova import log as logging
from nova.api.openstack import common from nova.api.openstack import common
from nova.api.openstack import wsgi
from nova.scheduler import api from nova.scheduler import api
@@ -41,12 +42,7 @@ def _scrub_zone(zone):
'deleted', 'deleted_at', 'updated_at')) 'deleted', 'deleted_at', 'updated_at'))
class Controller(common.OpenstackController): class Controller(object):
_serialization_metadata = {
'application/xml': {
"attributes": {
"zone": ["id", "api_url", "name", "capabilities"]}}}
def index(self, req): def index(self, req):
"""Return all zones in brief""" """Return all zones in brief"""
@@ -85,15 +81,28 @@ class Controller(common.OpenstackController):
api.zone_delete(req.environ['nova.context'], zone_id) api.zone_delete(req.environ['nova.context'], zone_id)
return {} return {}
def create(self, req): def create(self, req, body):
context = req.environ['nova.context'] context = req.environ['nova.context']
env = self._deserialize(req.body, req.get_content_type()) zone = api.zone_create(context, body["zone"])
zone = api.zone_create(context, env["zone"])
return dict(zone=_scrub_zone(zone)) return dict(zone=_scrub_zone(zone))
def update(self, req, id): def update(self, req, id, body):
context = req.environ['nova.context'] context = req.environ['nova.context']
env = self._deserialize(req.body, req.get_content_type())
zone_id = int(id) 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)) 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)

View File

@@ -65,7 +65,7 @@ class LimitsControllerV10Test(BaseLimitTestSuite):
def setUp(self): def setUp(self):
"""Run before each test.""" """Run before each test."""
BaseLimitTestSuite.setUp(self) BaseLimitTestSuite.setUp(self)
self.controller = limits.LimitsControllerV10() self.controller = limits.resource_factory('1.0')
def _get_index_request(self, accept_header="application/json"): def _get_index_request(self, accept_header="application/json"):
"""Helper to set routing arguments.""" """Helper to set routing arguments."""
@@ -178,7 +178,7 @@ class LimitsControllerV11Test(BaseLimitTestSuite):
def setUp(self): def setUp(self):
"""Run before each test.""" """Run before each test."""
BaseLimitTestSuite.setUp(self) BaseLimitTestSuite.setUp(self)
self.controller = limits.LimitsControllerV11() self.controller = limits.resource_factory('1.1')
def _get_index_request(self, accept_header="application/json"): def _get_index_request(self, accept_header="application/json"):
"""Helper to set routing arguments.""" """Helper to set routing arguments."""

View File

@@ -207,7 +207,6 @@ class ServersTest(test.TestCase):
}, },
] ]
print res_dict['server']
self.assertEqual(res_dict['server']['links'], expected_links) self.assertEqual(res_dict['server']['links'], expected_links)
def test_get_server_by_id_with_addresses_xml(self): 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 = webob.Request.blank('/v1.0/servers/detail')
req.headers['Accept'] = 'application/xml' req.headers['Accept'] = 'application/xml'
res = req.get_response(fakes.wsgi_app()) res = req.get_response(fakes.wsgi_app())
print res.body
dom = minidom.parseString(res.body) dom = minidom.parseString(res.body)
for i, server in enumerate(dom.getElementsByTagName('server')): for i, server in enumerate(dom.getElementsByTagName('server')):
self.assertEqual(server.getAttribute('id'), str(i)) self.assertEqual(server.getAttribute('id'), str(i))

View File

@@ -121,138 +121,3 @@ class ControllerTest(test.TestCase):
result = request.get_response(self.TestRouter()) result = request.get_response(self.TestRouter())
self.assertEqual(result.status_int, 200) self.assertEqual(result.status_int, 200)
self.assertEqual(result.headers["Content-Type"], "application/json") 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 = "<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<br />"
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 = '<servers><a>(2,3)</a></servers>'
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 = """
<a a1="1" a2="2">
<bs><b>1</b><b>2</b><b>3</b><b><c c1="1"/></b></bs>
<d><e>1</e></d>
<f>1</f>
</a>
""".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 = """<a></a>"""
as_dict = {"a": {}}
serializer = wsgi.Serializer()
self.assertEqual(serializer.deserialize(xml, "application/xml"),
as_dict)

View File

@@ -32,7 +32,7 @@ class XmlTests(integrated_helpers._IntegratedTestBase):
""""Some basic XML sanity checks.""" """"Some basic XML sanity checks."""
def test_namespace_limits(self): 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 = {}
headers['Accept'] = 'application/xml' headers['Accept'] = 'application/xml'
@@ -40,7 +40,7 @@ class XmlTests(integrated_helpers._IntegratedTestBase):
data = response.read() data = response.read()
LOG.debug("data: %s" % data) LOG.debug("data: %s" % data)
prefix = '<limits xmlns="%s"' % common.XML_NS_V10 prefix = '<limits xmlns="%s"' % common.XML_NS_V11
self.assertTrue(data.startswith(prefix)) self.assertTrue(data.startswith(prefix))
def test_namespace_servers(self): def test_namespace_servers(self):