diff options
| author | Chris Behrens <cbehrens@codestud.com> | 2011-07-12 16:13:01 -0700 |
|---|---|---|
| committer | Chris Behrens <cbehrens@codestud.com> | 2011-07-12 16:13:01 -0700 |
| commit | e547f4bde48a0142fbdb407a4c51f4b6f8fa56e2 (patch) | |
| tree | 36b98fac8f2890294d0a1fb42f72545652227fb7 /nova/api | |
| parent | bbd8f482b916168871d1d83192b354355858e77c (diff) | |
| parent | 11611716e30f368df77816b40c4c77de0e0e047f (diff) | |
| download | nova-e547f4bde48a0142fbdb407a4c51f4b6f8fa56e2.tar.gz nova-e547f4bde48a0142fbdb407a4c51f4b6f8fa56e2.tar.xz nova-e547f4bde48a0142fbdb407a4c51f4b6f8fa56e2.zip | |
merged trunk
Diffstat (limited to 'nova/api')
| -rw-r--r-- | nova/api/ec2/cloud.py | 11 | ||||
| -rw-r--r-- | nova/api/ec2/metadatarequesthandler.py | 6 | ||||
| -rw-r--r-- | nova/api/openstack/accounts.py | 6 | ||||
| -rw-r--r-- | nova/api/openstack/backup_schedules.py | 13 | ||||
| -rw-r--r-- | nova/api/openstack/consoles.py | 12 | ||||
| -rw-r--r-- | nova/api/openstack/contrib/floating_ips.py | 4 | ||||
| -rw-r--r-- | nova/api/openstack/contrib/multinic.py | 125 | ||||
| -rw-r--r-- | nova/api/openstack/create_instance_helper.py | 2 | ||||
| -rw-r--r-- | nova/api/openstack/flavors.py | 6 | ||||
| -rw-r--r-- | nova/api/openstack/image_metadata.py | 5 | ||||
| -rw-r--r-- | nova/api/openstack/images.py | 26 | ||||
| -rw-r--r-- | nova/api/openstack/ips.py | 5 | ||||
| -rw-r--r-- | nova/api/openstack/limits.py | 6 | ||||
| -rw-r--r-- | nova/api/openstack/server_metadata.py | 6 | ||||
| -rw-r--r-- | nova/api/openstack/servers.py | 55 | ||||
| -rw-r--r-- | nova/api/openstack/shared_ip_groups.py | 12 | ||||
| -rw-r--r-- | nova/api/openstack/users.py | 6 | ||||
| -rw-r--r-- | nova/api/openstack/versions.py | 5 | ||||
| -rw-r--r-- | nova/api/openstack/wsgi.py | 195 | ||||
| -rw-r--r-- | nova/api/openstack/zones.py | 9 |
20 files changed, 375 insertions, 140 deletions
diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 9efbb5985..0d24f0938 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -172,6 +172,9 @@ class CloudController(object): instance_ref['id']) ec2_id = ec2utils.id_to_ec2_id(instance_ref['id']) image_ec2_id = self.image_ec2_id(instance_ref['image_ref']) + security_groups = db.security_group_get_by_instance(ctxt, + instance_ref['id']) + security_groups = [x['name'] for x in security_groups] data = { 'user-data': base64.b64decode(instance_ref['user_data']), 'meta-data': { @@ -195,7 +198,7 @@ class CloudController(object): 'public-ipv4': floating_ip or '', 'public-keys': keys, 'reservation-id': instance_ref['reservation_id'], - 'security-groups': '', + 'security-groups': security_groups, 'mpi': mpi}} for image_type in ['kernel', 'ramdisk']: @@ -1101,12 +1104,16 @@ class CloudController(object): def _get_image(self, context, ec2_id): try: internal_id = ec2utils.ec2_id_to_id(ec2_id) - return self.image_service.show(context, internal_id) + image = self.image_service.show(context, internal_id) except (exception.InvalidEc2Id, exception.ImageNotFound): try: return self.image_service.show_by_name(context, ec2_id) except exception.NotFound: raise exception.ImageNotFound(image_id=ec2_id) + image_type = ec2_id.split('-')[0] + if self._image_type(image.get('container_format')) != image_type: + raise exception.ImageNotFound(image_id=ec2_id) + return image def _format_image(self, image): """Convert from format defined by BaseImageService to S3 format.""" diff --git a/nova/api/ec2/metadatarequesthandler.py b/nova/api/ec2/metadatarequesthandler.py index b70266a20..1dc275c90 100644 --- a/nova/api/ec2/metadatarequesthandler.py +++ b/nova/api/ec2/metadatarequesthandler.py @@ -35,6 +35,9 @@ FLAGS = flags.FLAGS class MetadataRequestHandler(wsgi.Application): """Serve metadata from the EC2 API.""" + def __init__(self): + self.cc = cloud.CloudController() + def print_data(self, data): if isinstance(data, dict): output = '' @@ -68,12 +71,11 @@ class MetadataRequestHandler(wsgi.Application): @webob.dec.wsgify(RequestClass=wsgi.Request) def __call__(self, req): - cc = cloud.CloudController() remote_address = req.remote_addr if FLAGS.use_forwarded_for: remote_address = req.headers.get('X-Forwarded-For', remote_address) try: - meta_data = cc.get_metadata(remote_address) + meta_data = self.cc.get_metadata(remote_address) except Exception: LOG.exception(_('Failed to get metadata for ip: %s'), remote_address) diff --git a/nova/api/openstack/accounts.py b/nova/api/openstack/accounts.py index 0dcd37217..e3201b14f 100644 --- a/nova/api/openstack/accounts.py +++ b/nova/api/openstack/accounts.py @@ -87,8 +87,8 @@ def create_resource(): }, } - serializers = { + body_serializers = { 'application/xml': wsgi.XMLDictSerializer(metadata=metadata), } - - return wsgi.Resource(Controller(), serializers=serializers) + serializer = wsgi.ResponseSerializer(body_serializers) + return wsgi.Resource(Controller(), serializer=serializer) diff --git a/nova/api/openstack/backup_schedules.py b/nova/api/openstack/backup_schedules.py index 71a14d4ce..3e95aedf3 100644 --- a/nova/api/openstack/backup_schedules.py +++ b/nova/api/openstack/backup_schedules.py @@ -34,20 +34,20 @@ class Controller(object): def __init__(self): pass - def index(self, req, server_id): + def index(self, req, server_id, **kwargs): """ Returns the list of backup schedules for a given instance """ return faults.Fault(exc.HTTPNotImplemented()) - def show(self, req, server_id, id): + def show(self, req, server_id, id, **kwargs): """ Returns a single backup schedule for a given instance """ return faults.Fault(exc.HTTPNotImplemented()) - def create(self, req, server_id, body): + def create(self, req, server_id, **kwargs): """ No actual update method required, since the existing API allows both create and update through a POST """ return faults.Fault(exc.HTTPNotImplemented()) - def delete(self, req, server_id, id): + def delete(self, req, server_id, id, **kwargs): """ Deletes an existing backup schedule """ return faults.Fault(exc.HTTPNotImplemented()) @@ -59,9 +59,10 @@ def create_resource(): }, } - serializers = { + body_serializers = { 'application/xml': wsgi.XMLDictSerializer(xmlns=wsgi.XMLNS_V10, metadata=metadata), } - return wsgi.Resource(Controller(), serializers=serializers) + serializer = wsgi.ResponseSerializer(body_serializers) + return wsgi.Resource(Controller(), serializer=serializer) diff --git a/nova/api/openstack/consoles.py b/nova/api/openstack/consoles.py index bccf04d8f..7a43fba96 100644 --- a/nova/api/openstack/consoles.py +++ b/nova/api/openstack/consoles.py @@ -90,14 +90,4 @@ class Controller(object): def create_resource(): - metadata = { - 'attributes': { - 'console': [], - }, - } - - serializers = { - 'application/xml': wsgi.XMLDictSerializer(metadata=metadata), - } - - return wsgi.Resource(Controller(), serializers=serializers) + return wsgi.Resource(Controller()) diff --git a/nova/api/openstack/contrib/floating_ips.py b/nova/api/openstack/contrib/floating_ips.py index b27336574..b4a211857 100644 --- a/nova/api/openstack/contrib/floating_ips.py +++ b/nova/api/openstack/contrib/floating_ips.py @@ -78,7 +78,7 @@ class FloatingIPController(object): return _translate_floating_ips_view(floating_ips) - def create(self, req, body): + def create(self, req): context = req.environ['nova.context'] try: @@ -124,7 +124,7 @@ class FloatingIPController(object): "floating_ip": floating_ip, "fixed_ip": fixed_ip}} - def disassociate(self, req, id, body): + def disassociate(self, req, id): """ POST /floating_ips/{id}/disassociate """ context = req.environ['nova.context'] floating_ip = self.network_api.get_floating_ip(context, id) diff --git a/nova/api/openstack/contrib/multinic.py b/nova/api/openstack/contrib/multinic.py new file mode 100644 index 000000000..841061721 --- /dev/null +++ b/nova/api/openstack/contrib/multinic.py @@ -0,0 +1,125 @@ +# Copyright 2011 OpenStack LLC. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +"""The multinic extension.""" + +from webob import exc + +from nova import compute +from nova import log as logging +from nova.api.openstack import extensions +from nova.api.openstack import faults + + +LOG = logging.getLogger("nova.api.multinic") + + +# Note: The class name is as it has to be for this to be loaded as an +# extension--only first character capitalized. +class Multinic(extensions.ExtensionDescriptor): + """The multinic extension. + + Exposes addFixedIp and removeFixedIp actions on servers. + + """ + + def __init__(self, *args, **kwargs): + """Initialize the extension. + + Gets a compute.API object so we can call the back-end + add_fixed_ip() and remove_fixed_ip() methods. + """ + + super(Multinic, self).__init__(*args, **kwargs) + self.compute_api = compute.API() + + def get_name(self): + """Return the extension name, as required by contract.""" + + return "Multinic" + + def get_alias(self): + """Return the extension alias, as required by contract.""" + + return "NMN" + + def get_description(self): + """Return the extension description, as required by contract.""" + + return "Multiple network support" + + def get_namespace(self): + """Return the namespace, as required by contract.""" + + return "http://docs.openstack.org/ext/multinic/api/v1.1" + + def get_updated(self): + """Return the last updated timestamp, as required by contract.""" + + return "2011-06-09T00:00:00+00:00" + + def get_actions(self): + """Return the actions the extension adds, as required by contract.""" + + actions = [] + + # Add the add_fixed_ip action + act = extensions.ActionExtension("servers", "addFixedIp", + self._add_fixed_ip) + actions.append(act) + + # Add the remove_fixed_ip action + act = extensions.ActionExtension("servers", "removeFixedIp", + self._remove_fixed_ip) + actions.append(act) + + return actions + + def _add_fixed_ip(self, input_dict, req, id): + """Adds an IP on a given network to an instance.""" + + try: + # Validate the input entity + if 'networkId' not in input_dict['addFixedIp']: + LOG.exception(_("Missing 'networkId' argument for addFixedIp")) + return faults.Fault(exc.HTTPUnprocessableEntity()) + + # Add the fixed IP + network_id = input_dict['addFixedIp']['networkId'] + self.compute_api.add_fixed_ip(req.environ['nova.context'], id, + network_id) + except Exception, e: + LOG.exception(_("Error in addFixedIp %s"), e) + return faults.Fault(exc.HTTPBadRequest()) + return exc.HTTPAccepted() + + def _remove_fixed_ip(self, input_dict, req, id): + """Removes an IP from an instance.""" + + try: + # Validate the input entity + if 'address' not in input_dict['removeFixedIp']: + LOG.exception(_("Missing 'address' argument for " + "removeFixedIp")) + return faults.Fault(exc.HTTPUnprocessableEntity()) + + # Remove the fixed IP + address = input_dict['removeFixedIp']['address'] + self.compute_api.remove_fixed_ip(req.environ['nova.context'], id, + address) + except Exception, e: + LOG.exception(_("Error in removeFixedIp %s"), e) + return faults.Fault(exc.HTTPBadRequest()) + return exc.HTTPAccepted() diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index 1066713a3..2654e3c40 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -289,7 +289,7 @@ class ServerXMLDeserializer(wsgi.XMLDeserializer): """Deserialize an xml-formatted server create request""" dom = minidom.parseString(string) server = self._extract_server(dom) - return {'server': server} + return {'body': {'server': server}} def _extract_server(self, node): """Marshal the server attribute of a parsed request""" diff --git a/nova/api/openstack/flavors.py b/nova/api/openstack/flavors.py index a21ff6cb2..6fab13147 100644 --- a/nova/api/openstack/flavors.py +++ b/nova/api/openstack/flavors.py @@ -85,8 +85,10 @@ def create_resource(version='1.0'): '1.1': wsgi.XMLNS_V11, }[version] - serializers = { + body_serializers = { 'application/xml': wsgi.XMLDictSerializer(xmlns=xmlns), } - return wsgi.Resource(controller, serializers=serializers) + serializer = wsgi.ResponseSerializer(body_serializers) + + return wsgi.Resource(controller, serializer=serializer) diff --git a/nova/api/openstack/image_metadata.py b/nova/api/openstack/image_metadata.py index 638b1ec15..4f33844fa 100644 --- a/nova/api/openstack/image_metadata.py +++ b/nova/api/openstack/image_metadata.py @@ -160,8 +160,9 @@ class ImageMetadataXMLSerializer(wsgi.XMLDictSerializer): def create_resource(): - serializers = { + body_serializers = { 'application/xml': ImageMetadataXMLSerializer(), } + serializer = wsgi.ResponseSerializer(body_serializers) - return wsgi.Resource(Controller(), serializers=serializers) + return wsgi.Resource(Controller(), serializer=serializer) diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index bde9507c8..8ff92b8fe 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -13,6 +13,7 @@ # License for the specific language governing permissions and limitations # under the License. +import urlparse import os.path import webob.exc @@ -23,7 +24,6 @@ from nova import exception from nova import flags import nova.image from nova import log -from nova import utils from nova.api.openstack import common from nova.api.openstack import faults from nova.api.openstack import image_metadata @@ -246,13 +246,23 @@ class ControllerV11(Controller): msg = _("Expected serverRef attribute on server entity.") raise webob.exc.HTTPBadRequest(explanation=msg) - head, tail = os.path.split(server_ref) - - if head and head != os.path.join(req.application_url, 'servers'): + if not server_ref.startswith('http'): + return server_ref + + passed = urlparse.urlparse(server_ref) + expected = urlparse.urlparse(req.application_url) + version = expected.path.split('/')[1] + expected_prefix = "/%s/servers/" % version + _empty, _sep, server_id = passed.path.partition(expected_prefix) + scheme_ok = passed.scheme == expected.scheme + host_ok = passed.hostname == expected.hostname + port_ok = (passed.port == expected.port or + passed.port == FLAGS.osapi_port) + if not (scheme_ok and port_ok and host_ok and server_id): msg = _("serverRef must match request url") raise webob.exc.HTTPBadRequest(explanation=msg) - return tail + return server_id def _get_extra_properties(self, req, data): server_ref = data['image']['serverRef'] @@ -338,8 +348,10 @@ def create_resource(version='1.0'): '1.1': ImageXMLSerializer(), }[version] - serializers = { + body_serializers = { 'application/xml': xml_serializer, } - return wsgi.Resource(controller, serializers=serializers) + serializer = wsgi.ResponseSerializer(body_serializers) + + return wsgi.Resource(controller, serializer=serializer) diff --git a/nova/api/openstack/ips.py b/nova/api/openstack/ips.py index 71646b6d3..23e5432d6 100644 --- a/nova/api/openstack/ips.py +++ b/nova/api/openstack/ips.py @@ -70,9 +70,10 @@ def create_resource(): }, } - serializers = { + body_serializers = { 'application/xml': wsgi.XMLDictSerializer(metadata=metadata, xmlns=wsgi.XMLNS_V10), } + serializer = wsgi.ResponseSerializer(body_serializers) - return wsgi.Resource(Controller(), serializers=serializers) + return wsgi.Resource(Controller(), serializer=serializer) diff --git a/nova/api/openstack/limits.py b/nova/api/openstack/limits.py index fede96e33..d08287f6b 100644 --- a/nova/api/openstack/limits.py +++ b/nova/api/openstack/limits.py @@ -97,12 +97,14 @@ def create_resource(version='1.0'): }, } - serializers = { + body_serializers = { 'application/xml': wsgi.XMLDictSerializer(xmlns=xmlns, metadata=metadata), } - return wsgi.Resource(controller, serializers=serializers) + serializer = wsgi.ResponseSerializer(body_serializers) + + return wsgi.Resource(controller, serializer=serializer) class Limit(object): diff --git a/nova/api/openstack/server_metadata.py b/nova/api/openstack/server_metadata.py index 8a314de22..3b9169f81 100644 --- a/nova/api/openstack/server_metadata.py +++ b/nova/api/openstack/server_metadata.py @@ -123,8 +123,10 @@ class Controller(object): def create_resource(): - serializers = { + body_serializers = { 'application/xml': wsgi.XMLDictSerializer(xmlns=wsgi.XMLNS_V11), } - return wsgi.Resource(Controller(), serializers=serializers) + serializer = wsgi.ResponseSerializer(body_serializers) + + return wsgi.Resource(Controller(), serializer=serializer) diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index d259590a5..8a947c0e0 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -106,15 +106,6 @@ class Controller(object): except exception.NotFound: return faults.Fault(exc.HTTPNotFound()) - @scheduler_api.redirect_handler - def delete(self, req, id): - """ Destroys a server """ - try: - self.compute_api.delete(req.environ['nova.context'], id) - except exception.NotFound: - return faults.Fault(exc.HTTPNotFound()) - return exc.HTTPAccepted() - def create(self, req, body): """ Creates a new server for a given user """ extra_values = None @@ -178,7 +169,7 @@ class Controller(object): 'confirmResize': self._action_confirm_resize, 'revertResize': self._action_revert_resize, 'rebuild': self._action_rebuild, - } + 'migrate': self._action_migrate} for key in actions.keys(): if key in body: @@ -222,6 +213,14 @@ class Controller(object): return faults.Fault(exc.HTTPUnprocessableEntity()) return exc.HTTPAccepted() + def _action_migrate(self, input_dict, req, id): + try: + self.compute_api.resize(req.environ['nova.context'], id) + except Exception, e: + LOG.exception(_("Error in migrate %s"), e) + return faults.Fault(exc.HTTPBadRequest()) + return exc.HTTPAccepted() + @scheduler_api.redirect_handler def lock(self, req, id): """ @@ -414,6 +413,15 @@ class Controller(object): class ControllerV10(Controller): + @scheduler_api.redirect_handler + def delete(self, req, id): + """ Destroys a server """ + try: + self.compute_api.delete(req.environ['nova.context'], id) + except exception.NotFound: + return faults.Fault(exc.HTTPNotFound()) + return exc.HTTPAccepted() + def _image_ref_from_req_data(self, data): return data['server']['imageId'] @@ -476,6 +484,15 @@ class ControllerV10(Controller): class ControllerV11(Controller): + + @scheduler_api.redirect_handler + def delete(self, req, id): + """ Destroys a server """ + try: + self.compute_api.delete(req.environ['nova.context'], id) + except exception.NotFound: + return faults.Fault(exc.HTTPNotFound()) + def _image_ref_from_req_data(self, data): return data['server']['imageRef'] @@ -591,6 +608,12 @@ class ControllerV11(Controller): return self.helper._get_server_admin_password_new_style(server) +class HeadersSerializer(wsgi.ResponseHeadersSerializer): + + def delete(self, response, data): + response.status_int = 204 + + def create_resource(version='1.0'): controller = { '1.0': ControllerV10, @@ -618,14 +641,18 @@ def create_resource(version='1.0'): '1.1': wsgi.XMLNS_V11, }[version] - serializers = { + headers_serializer = HeadersSerializer() + + body_serializers = { 'application/xml': wsgi.XMLDictSerializer(metadata=metadata, xmlns=xmlns), } - deserializers = { + body_deserializers = { 'application/xml': helper.ServerXMLDeserializer(), } - return wsgi.Resource(controller, serializers=serializers, - deserializers=deserializers) + serializer = wsgi.ResponseSerializer(body_serializers, headers_serializer) + deserializer = wsgi.RequestDeserializer(body_deserializers) + + return wsgi.Resource(controller, deserializer, serializer) diff --git a/nova/api/openstack/shared_ip_groups.py b/nova/api/openstack/shared_ip_groups.py index 4f11f8dfb..cf2ddbabb 100644 --- a/nova/api/openstack/shared_ip_groups.py +++ b/nova/api/openstack/shared_ip_groups.py @@ -24,27 +24,27 @@ from nova.api.openstack import wsgi class Controller(object): """ The Shared IP Groups Controller for the Openstack API """ - def index(self, req): + def index(self, req, **kwargs): """ Returns a list of Shared IP Groups for the user """ raise faults.Fault(exc.HTTPNotImplemented()) - def show(self, req, id): + def show(self, req, id, **kwargs): """ Shows in-depth information on a specific Shared IP Group """ raise faults.Fault(exc.HTTPNotImplemented()) - def update(self, req, id, body): + def update(self, req, id, **kwargs): """ You can't update a Shared IP Group """ raise faults.Fault(exc.HTTPNotImplemented()) - def delete(self, req, id): + def delete(self, req, id, **kwargs): """ Deletes a Shared IP Group """ raise faults.Fault(exc.HTTPNotImplemented()) - def detail(self, req): + def detail(self, req, **kwargs): """ Returns a complete list of Shared IP Groups """ raise faults.Fault(exc.HTTPNotImplemented()) - def create(self, req, body): + def create(self, req, **kwargs): """ Creates a new Shared IP group """ raise faults.Fault(exc.HTTPNotImplemented()) diff --git a/nova/api/openstack/users.py b/nova/api/openstack/users.py index 50975fc1f..6ae1eaf2a 100644 --- a/nova/api/openstack/users.py +++ b/nova/api/openstack/users.py @@ -105,8 +105,10 @@ def create_resource(): }, } - serializers = { + body_serializers = { 'application/xml': wsgi.XMLDictSerializer(metadata=metadata), } - return wsgi.Resource(Controller(), serializers=serializers) + serializer = wsgi.ResponseSerializer(body_serializers) + + return wsgi.Resource(Controller(), serializer=serializer) diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py index 4c682302f..a634c3267 100644 --- a/nova/api/openstack/versions.py +++ b/nova/api/openstack/versions.py @@ -31,11 +31,12 @@ class Versions(wsgi.Resource): } } - serializers = { + body_serializers = { 'application/xml': wsgi.XMLDictSerializer(metadata=metadata), } + serializer = wsgi.ResponseSerializer(body_serializers) - wsgi.Resource.__init__(self, None, serializers=serializers) + wsgi.Resource.__init__(self, None, serializer=serializer) def dispatch(self, request, *args): """Respond to a request for all OpenStack API versions.""" diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index 5b6e3cb1d..8eff9e441 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -46,38 +46,51 @@ class Request(webob.Request): """ if not "Content-Type" in self.headers: - raise exception.InvalidContentType(content_type=None) + return None allowed_types = ("application/xml", "application/json") content_type = self.content_type if content_type not in allowed_types: raise exception.InvalidContentType(content_type=content_type) - else: - return content_type + return content_type -class TextDeserializer(object): - """Custom request body deserialization based on controller action name.""" - def deserialize(self, datastring, action='default'): - """Find local deserialization method and parse request body.""" +class ActionDispatcher(object): + """Maps method name to local methods through action name.""" + + def dispatch(self, *args, **kwargs): + """Find and call local method.""" + action = kwargs.pop('action', 'default') action_method = getattr(self, str(action), self.default) - return action_method(datastring) + return action_method(*args, **kwargs) - def default(self, datastring): - """Default deserialization code should live here""" + def default(self, data): raise NotImplementedError() -class JSONDeserializer(TextDeserializer): +class TextDeserializer(ActionDispatcher): + """Default request body deserialization""" + + def deserialize(self, datastring, action='default'): + return self.dispatch(datastring, action=action) def default(self, datastring): + return {} + + +class JSONDeserializer(TextDeserializer): + + def _from_json(self, datastring): try: return utils.loads(datastring) except ValueError: - raise exception.MalformedRequestBody( - reason=_("malformed JSON in request body")) + msg = _("cannot understand JSON") + raise exception.MalformedRequestBody(reason=msg) + + def default(self, datastring): + return {'body': self._from_json(datastring)} class XMLDeserializer(TextDeserializer): @@ -90,15 +103,15 @@ class XMLDeserializer(TextDeserializer): super(XMLDeserializer, self).__init__() self.metadata = metadata or {} - def default(self, datastring): + def _from_xml(self, datastring): plurals = set(self.metadata.get('plurals', {})) try: node = minidom.parseString(datastring).childNodes[0] return {node.nodeName: self._from_xml_node(node, plurals)} except expat.ExpatError: - raise exception.MalformedRequestBody( - reason=_("malformed XML in request body")) + msg = _("cannot understand XML") + raise exception.MalformedRequestBody(reason=msg) def _from_xml_node(self, node, listnames): """Convert a minidom node to a simple Python type. @@ -121,21 +134,32 @@ class XMLDeserializer(TextDeserializer): listnames) return result + def default(self, datastring): + return {'body': self._from_xml(datastring)} + + +class RequestHeadersDeserializer(ActionDispatcher): + """Default request headers deserializer""" + + def deserialize(self, request, action): + return self.dispatch(request, action=action) + + def default(self, request): + return {} + class RequestDeserializer(object): """Break up a Request object into more useful pieces.""" - def __init__(self, deserializers=None): - """ - :param deserializers: dictionary of content-type-specific deserializers - - """ - self.deserializers = { + def __init__(self, body_deserializers=None, headers_deserializer=None): + self.body_deserializers = { 'application/xml': XMLDeserializer(), 'application/json': JSONDeserializer(), } + self.body_deserializers.update(body_deserializers or {}) - self.deserializers.update(deserializers or {}) + self.headers_deserializer = headers_deserializer or \ + RequestHeadersDeserializer() def deserialize(self, request): """Extract necessary pieces of the request. @@ -149,26 +173,42 @@ class RequestDeserializer(object): action_args = self.get_action_args(request.environ) action = action_args.pop('action', None) - if request.method.lower() in ('post', 'put'): - if len(request.body) == 0: - action_args['body'] = None - else: - content_type = request.get_content_type() - deserializer = self.get_deserializer(content_type) - - try: - body = deserializer.deserialize(request.body, action) - action_args['body'] = body - except exception.InvalidContentType: - action_args['body'] = None + action_args.update(self.deserialize_headers(request, action)) + action_args.update(self.deserialize_body(request, action)) accept = self.get_expected_content_type(request) return (action, action_args, accept) - def get_deserializer(self, content_type): + def deserialize_headers(self, request, action): + return self.headers_deserializer.deserialize(request, action) + + def deserialize_body(self, request, action): try: - return self.deserializers[content_type] + content_type = request.get_content_type() + except exception.InvalidContentType: + LOG.debug(_("Unrecognized Content-Type provided in request")) + return {} + + if content_type is None: + LOG.debug(_("No Content-Type provided in request")) + return {} + + if not len(request.body) > 0: + LOG.debug(_("Empty body provided in request")) + return {} + + try: + deserializer = self.get_body_deserializer(content_type) + except exception.InvalidContentType: + LOG.debug(_("Unable to deserialize body as provided Content-Type")) + raise + + return deserializer.deserialize(request.body, action) + + def get_body_deserializer(self, content_type): + try: + return self.body_deserializers[content_type] except (KeyError, TypeError): raise exception.InvalidContentType(content_type=content_type) @@ -195,20 +235,18 @@ class RequestDeserializer(object): return args -class DictSerializer(object): - """Custom response body serialization based on controller action name.""" +class DictSerializer(ActionDispatcher): + """Default request body serialization""" def serialize(self, data, action='default'): - """Find local serialization method and encode response body.""" - action_method = getattr(self, str(action), self.default) - return action_method(data) + return self.dispatch(data, action=action) def default(self, data): - """Default serialization code should live here""" - raise NotImplementedError() + return "" class JSONDictSerializer(DictSerializer): + """Default JSON request body serialization""" def default(self, data): return utils.dumps(data) @@ -295,19 +333,28 @@ class XMLDictSerializer(DictSerializer): return result +class ResponseHeadersSerializer(ActionDispatcher): + """Default response headers serialization""" + + def serialize(self, response, data, action): + self.dispatch(response, data, action=action) + + def default(self, response, data): + response.status_int = 200 + + class ResponseSerializer(object): """Encode the necessary pieces into a response object""" - def __init__(self, serializers=None): - """ - :param serializers: dictionary of content-type-specific serializers - - """ - self.serializers = { + def __init__(self, body_serializers=None, headers_serializer=None): + self.body_serializers = { 'application/xml': XMLDictSerializer(), 'application/json': JSONDictSerializer(), } - self.serializers.update(serializers or {}) + self.body_serializers.update(body_serializers or {}) + + self.headers_serializer = headers_serializer or \ + ResponseHeadersSerializer() def serialize(self, response_data, content_type, action='default'): """Serialize a dict into a string and wrap in a wsgi.Request object. @@ -317,16 +364,21 @@ class ResponseSerializer(object): """ response = webob.Response() - response.headers['Content-Type'] = content_type + self.serialize_headers(response, response_data, action) + self.serialize_body(response, response_data, content_type, action) + return response - serializer = self.get_serializer(content_type) - response.body = serializer.serialize(response_data, action) + def serialize_headers(self, response, data, action): + self.headers_serializer.serialize(response, data, action) - return response + def serialize_body(self, response, data, content_type, action): + response.headers['Content-Type'] = content_type + serializer = self.get_body_serializer(content_type) + response.body = serializer.serialize(data, action) - def get_serializer(self, content_type): + def get_body_serializer(self, content_type): try: - return self.serializers[content_type] + return self.body_serializers[content_type] except (KeyError, TypeError): raise exception.InvalidContentType(content_type=content_type) @@ -343,16 +395,18 @@ class Resource(wsgi.Application): serialized by requested content type. """ - def __init__(self, controller, serializers=None, deserializers=None): + def __init__(self, controller, deserializer=None, serializer=None): """ :param controller: object that implement methods created by routes lib - :param serializers: dict of content-type specific text serializers - :param deserializers: dict of content-type specific text deserializers + :param deserializer: object that can serialize the output of a + controller into a webob response + :param serializer: object that can deserialize a webob request + into necessary pieces """ self.controller = controller - self.serializer = ResponseSerializer(serializers) - self.deserializer = RequestDeserializer(deserializers) + self.deserializer = deserializer or RequestDeserializer() + self.serializer = serializer or ResponseSerializer() @webob.dec.wsgify(RequestClass=Request) def __call__(self, request): @@ -362,8 +416,7 @@ class Resource(wsgi.Application): "url": request.url}) try: - action, action_args, accept = self.deserializer.deserialize( - request) + action, args, accept = self.deserializer.deserialize(request) except exception.InvalidContentType: msg = _("Unsupported Content-Type") return webob.exc.HTTPBadRequest(explanation=msg) @@ -371,11 +424,13 @@ class Resource(wsgi.Application): msg = _("Malformed request body") return faults.Fault(webob.exc.HTTPBadRequest(explanation=msg)) - action_result = self.dispatch(request, action, action_args) + action_result = self.dispatch(request, action, args) #TODO(bcwaldon): find a more elegant way to pass through non-dict types - if type(action_result) is dict: - response = self.serializer.serialize(action_result, accept, action) + if type(action_result) is dict or action_result is None: + response = self.serializer.serialize(action_result, + accept, + action=action) else: response = action_result @@ -394,4 +449,8 @@ class Resource(wsgi.Application): """Find action-spefic method on controller and call it.""" controller_method = getattr(self.controller, action) - return controller_method(req=request, **action_args) + try: + return controller_method(req=request, **action_args) + except TypeError, exc: + LOG.debug(str(exc)) + return webob.exc.HTTPBadRequest() diff --git a/nova/api/openstack/zones.py b/nova/api/openstack/zones.py index 8864f825b..2e02ec380 100644 --- a/nova/api/openstack/zones.py +++ b/nova/api/openstack/zones.py @@ -196,14 +196,15 @@ def create_resource(version): }, } - serializers = { + body_serializers = { 'application/xml': wsgi.XMLDictSerializer(xmlns=wsgi.XMLNS_V10, metadata=metadata), } + serializer = wsgi.ResponseSerializer(body_serializers) - deserializers = { + body_deserializers = { 'application/xml': helper.ServerXMLDeserializer(), } + deserializer = wsgi.RequestDeserializer(body_deserializers) - return wsgi.Resource(controller, serializers=serializers, - deserializers=deserializers) + return wsgi.Resource(controller, deserializer, serializer) |
