refactoring wsgi to separate controller/serialization/deserialization logic; creating osapi-specific module
This commit is contained in:
@@ -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'))
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -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())
|
||||||
|
|||||||
@@ -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
291
nova/api/openstack/wsgi.py
Normal 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)
|
||||||
@@ -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)
|
||||||
|
|||||||
@@ -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."""
|
||||||
|
|||||||
@@ -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))
|
||||||
|
|||||||
@@ -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)
|
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
Reference in New Issue
Block a user