From dcda6be23c3797872c406f58578b05befd378c97 Mon Sep 17 00:00:00 2001 From: MORITA Kazutaka Date: Tue, 19 Apr 2011 20:48:26 +0900 Subject: Add support for creating a snapshot of a nova volume with euca-create-snapshot. --- nova/api/ec2/__init__.py | 6 ++++++ nova/api/ec2/cloud.py | 52 ++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 50 insertions(+), 8 deletions(-) (limited to 'nova/api') diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py index a3c3b25a1..a89d65a38 100644 --- a/nova/api/ec2/__init__.py +++ b/nova/api/ec2/__init__.py @@ -331,6 +331,12 @@ class Executor(wsgi.Application): ec2_id = ec2utils.id_to_ec2_id(ex.volume_id, 'vol-%08x') message = _('Volume %s not found') % ec2_id return self._error(req, context, type(ex).__name__, message) + except exception.SnapshotNotFound as ex: + LOG.info(_('SnapshotNotFound raised: %s'), unicode(ex), + context=context) + ec2_id = ec2utils.id_to_ec2_id(ex.snapshot_id, 'snap-%08x') + message = _('Snapshot %s not found') % ec2_id + return self._error(req, context, type(ex).__name__, message) except exception.NotFound as ex: LOG.info(_('NotFound raised: %s'), unicode(ex), context=context) return self._error(req, context, type(ex).__name__, unicode(ex)) diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index bd4c9dcd4..6daf299b9 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -283,14 +283,46 @@ class CloudController(object): owner=None, restorable_by=None, **kwargs): - return {'snapshotSet': [{'snapshotId': 'fixme', - 'volumeId': 'fixme', - 'status': 'fixme', - 'startTime': 'fixme', - 'progress': 'fixme', - 'ownerId': 'fixme', - 'volumeSize': 0, - 'description': 'fixme'}]} + if snapshot_id: + snapshots = [] + for ec2_id in snapshot_id: + internal_id = ec2utils.ec2_id_to_id(ec2_id) + snapshot = self.volume_api.get_snapshot(context, snapshot_id=internal_id) + snapshots.append(snapshot) + else: + snapshots = self.volume_api.get_all_snapshots(context) + snapshots = [self._format_snapshot(context, s) for s in snapshots] + return {'snapshotSet': snapshots} + + def _format_snapshot(self, context, snapshot): + s = {} + s['snapshotId'] = ec2utils.id_to_ec2_id(snapshot['id'], 'snap-%08x') + s['volumeId'] = ec2utils.id_to_ec2_id(snapshot['volume_id'], 'vol-%08x') + s['status'] = snapshot['status'] + s['startTime'] = snapshot['created_at'] + s['progress'] = snapshot['progress'] + s['ownerId'] = snapshot['project_id'] + s['volumeSize'] = snapshot['volume_size'] + s['description'] = snapshot['display_description'] + + s['display_name'] = snapshot['display_name'] + s['display_description'] = snapshot['display_description'] + return s + + def create_snapshot(self, context, volume_id, **kwargs): + LOG.audit(_("Create snapshot of volume %s"), volume_id, context=context) + volume_id = ec2utils.ec2_id_to_id(volume_id) + snapshot = self.volume_api.create_snapshot( + context, + volume_id=volume_id, + name=kwargs.get('display_name'), + description=kwargs.get('display_description')) + return {'snapshotSet': [self._format_snapshot(context, snapshot)]} + + def delete_snapshot(self, context, snapshot_id, **kwargs): + snapshot_id = ec2utils.ec2_id_to_id(snapshot_id) + self.volume_api.delete_snapshot(context, snapshot_id=snapshot_id) + return True def describe_key_pairs(self, context, key_name=None, **kwargs): key_pairs = db.key_pair_get_all_by_user(context, context.user_id) @@ -598,6 +630,10 @@ class CloudController(object): 'volumeId': v['volumeId']}] else: v['attachmentSet'] = [{}] + if volume.get('snapshot_id') != None: + v['snapshotId'] = ec2utils.id_to_ec2_id(volume['snapshot_id'], 'snap-%08x') + else: + v['snapshotId'] = None v['display_name'] = volume['display_name'] v['display_description'] = volume['display_description'] -- cgit From 1018a60e3194e7e283cd89af28efd689623058a8 Mon Sep 17 00:00:00 2001 From: MORITA Kazutaka Date: Tue, 19 Apr 2011 21:01:25 +0900 Subject: Add support for creating a new volume from a existing snapshot with EC2 API. --- nova/api/ec2/cloud.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) (limited to 'nova/api') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 6daf299b9..5d4d2ad27 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -639,11 +639,19 @@ class CloudController(object): v['display_description'] = volume['display_description'] return v - def create_volume(self, context, size, **kwargs): - LOG.audit(_("Create volume of %s GB"), size, context=context) + def create_volume(self, context, **kwargs): + size = kwargs.get('size'); + if kwargs.get('snapshot_id') != None: + snapshot_id = ec2utils.ec2_id_to_id(kwargs['snapshot_id']) + LOG.audit(_("Create volume from snapshot %s"), snapshot_id, context=context) + else: + snapshot_id = None + LOG.audit(_("Create volume of %s GB"), size, context=context) + volume = self.volume_api.create( context, size=size, + snapshot_id=snapshot_id, name=kwargs.get('display_name'), description=kwargs.get('display_description')) # TODO(vish): Instance should be None at db layer instead of -- cgit From 8cf2087747ab87fec0e1f7cc3d57ed1fa5065749 Mon Sep 17 00:00:00 2001 From: Mark Washenberger Date: Mon, 16 May 2011 14:50:07 -0400 Subject: add a todo --- nova/api/openstack/limits.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/limits.py b/nova/api/openstack/limits.py index 47bc238f1..f30c9ec59 100644 --- a/nova/api/openstack/limits.py +++ b/nova/api/openstack/limits.py @@ -64,7 +64,9 @@ class LimitsController(common.OpenstackController): """ Return all global and rate limit information. """ - abs_limits = {} + # TODO(alex.meade) make this work + project_quota = quota.get_project_quota(...) + abs_limits = project_quota.limits rate_limits = req.environ.get("nova.limits", []) builder = self._get_view_builder(req) -- cgit From b312ac2634f530273e599ee48ff2e3a238bbbf4f Mon Sep 17 00:00:00 2001 From: Johannes Erdfelt Date: Tue, 17 May 2011 16:46:13 +0000 Subject: Set password to one requested in API call --- nova/api/openstack/servers.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 8f2de2afe..fcb630fae 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -609,7 +609,8 @@ class ControllerV10(Controller): def _parse_update(self, context, server_id, inst_dict, update_dict): if 'adminPass' in inst_dict['server']: update_dict['admin_pass'] = inst_dict['server']['adminPass'] - self.compute_api.set_admin_password(context, server_id) + self.compute_api.set_admin_password(context, server_id, + inst_dict['server']['adminPass']) def _action_rebuild(self, info, request, instance_id): context = request.environ['nova.context'] -- cgit From 6c27e24a559722a5a82d8883f508a77d281956f5 Mon Sep 17 00:00:00 2001 From: Johannes Erdfelt Date: Tue, 17 May 2011 17:51:22 +0000 Subject: Avoid using spawn_n to fix LP784132 --- nova/api/openstack/servers.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index fcb630fae..bd9711555 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -609,7 +609,9 @@ class ControllerV10(Controller): def _parse_update(self, context, server_id, inst_dict, update_dict): if 'adminPass' in inst_dict['server']: update_dict['admin_pass'] = inst_dict['server']['adminPass'] - self.compute_api.set_admin_password(context, server_id, + # We call _set_admin_password() here to avoid the spawn_n in + # set_admin_password() + self.compute_api._set_admin_password(context, server_id, inst_dict['server']['adminPass']) def _action_rebuild(self, info, request, instance_id): -- cgit From 7ab16489276daa2ec6f51fea6ec24cc0c46a8e14 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Tue, 17 May 2011 15:14:52 -0400 Subject: Changed builder to match specs and added test --- nova/api/openstack/limits.py | 9 +++++++-- nova/api/openstack/views/limits.py | 24 +++++++++--------------- 2 files changed, 16 insertions(+), 17 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/limits.py b/nova/api/openstack/limits.py index cf96b1bce..e383b5efc 100644 --- a/nova/api/openstack/limits.py +++ b/nova/api/openstack/limits.py @@ -42,6 +42,9 @@ PER_MINUTE = 60 PER_HOUR = 60 * 60 PER_DAY = 60 * 60 * 24 +#TODO remove when mark catches up +TEST_ABSOLUTE_LIMITS = {} + class LimitsController(common.OpenstackController): """ @@ -53,7 +56,8 @@ class LimitsController(common.OpenstackController): "attributes": { "limit": ["verb", "URI", "uri", "regex", "value", "unit", "resetTime", "next-available", "remaining", "name"], - "absolute_limit": ["limit", "value"], + "absolute_limit": ["maxTotalRAMSize", "maxTotalInstances", + "maxTotalCores"], }, "plurals": { "rate": "limit", @@ -69,7 +73,8 @@ class LimitsController(common.OpenstackController): # TODO(alex.meade) make this work #project_quota = quota.get_project_quota(...) #abs_limits = project_quota.limits - abs_limits = {} + #TODO remove when mark catches up + abs_limits = TEST_ABSOLUTE_LIMITS rate_limits = req.environ.get("nova.limits", []) builder = self._get_view_builder(req) diff --git a/nova/api/openstack/views/limits.py b/nova/api/openstack/views/limits.py index 7fae2d166..ef1243f3d 100644 --- a/nova/api/openstack/views/limits.py +++ b/nova/api/openstack/views/limits.py @@ -112,25 +112,19 @@ class ViewBuilderV11(ViewBuilder): For example: {"ram": 512, "gigabytes": 1024}. """ - limits = [] + limits = {} #loops through absolute limits and their values for absolute_limit_key, absolute_limit_value \ in absolute_limits.items(): - _abs_limit = None - # check for existing key - for limit in limits: - if limit["limit"] == absolute_limit_key: - _abs_limit = limit - break - - # ensure we have a key if we didn't find one - if not _abs_limit: - _abs_limit = { - "limit": absolute_limit_key, - "value": absolute_limit_value, - } + _abs_limit_map = { + "ram": "maxTotalRAMSize", + "instances": "maxTotalInstances", + "cores": "maxTotalCores", + } - limits.append(_abs_limit) + if not absolute_limit_value is None: + limits[_abs_limit_map[absolute_limit_key]] \ + = absolute_limit_value return limits -- cgit From 0bc5511ccfb4ea97a0ba4c8533ce5d3cd3e6df19 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Tue, 17 May 2011 16:25:45 -0400 Subject: Removed extra serialization metadata --- nova/api/openstack/limits.py | 3 --- 1 file changed, 3 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/limits.py b/nova/api/openstack/limits.py index e383b5efc..5e3f93f79 100644 --- a/nova/api/openstack/limits.py +++ b/nova/api/openstack/limits.py @@ -56,12 +56,9 @@ class LimitsController(common.OpenstackController): "attributes": { "limit": ["verb", "URI", "uri", "regex", "value", "unit", "resetTime", "next-available", "remaining", "name"], - "absolute_limit": ["maxTotalRAMSize", "maxTotalInstances", - "maxTotalCores"], }, "plurals": { "rate": "limit", - "absolute": "absolute_limit", }, }, } -- cgit From bd0125647a04ab8da7eef934e4a97560c1553551 Mon Sep 17 00:00:00 2001 From: Johannes Erdfelt Date: Wed, 18 May 2011 15:31:41 +0000 Subject: Fix call to spawn_n() instead. It expects a callable --- nova/api/openstack/servers.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index bd9711555..fcb630fae 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -609,9 +609,7 @@ class ControllerV10(Controller): def _parse_update(self, context, server_id, inst_dict, update_dict): if 'adminPass' in inst_dict['server']: update_dict['admin_pass'] = inst_dict['server']['adminPass'] - # We call _set_admin_password() here to avoid the spawn_n in - # set_admin_password() - self.compute_api._set_admin_password(context, server_id, + self.compute_api.set_admin_password(context, server_id, inst_dict['server']['adminPass']) def _action_rebuild(self, info, request, instance_id): -- cgit From 156ebab6599f9500d8b98c7cc1271d2502fa0627 Mon Sep 17 00:00:00 2001 From: Mark Washenberger Date: Wed, 18 May 2011 13:54:51 -0400 Subject: get real absolute limits in openstack api and verify absolute limit responses --- nova/api/openstack/limits.py | 11 +++-------- nova/api/openstack/views/limits.py | 23 +++++++++-------------- 2 files changed, 12 insertions(+), 22 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/limits.py b/nova/api/openstack/limits.py index 5e3f93f79..1411cec01 100644 --- a/nova/api/openstack/limits.py +++ b/nova/api/openstack/limits.py @@ -30,6 +30,7 @@ from collections import defaultdict from webob.dec import wsgify +from nova import quota from nova import wsgi from nova.api.openstack import common from nova.api.openstack import faults @@ -42,9 +43,6 @@ PER_MINUTE = 60 PER_HOUR = 60 * 60 PER_DAY = 60 * 60 * 24 -#TODO remove when mark catches up -TEST_ABSOLUTE_LIMITS = {} - class LimitsController(common.OpenstackController): """ @@ -67,11 +65,8 @@ class LimitsController(common.OpenstackController): """ Return all global and rate limit information. """ - # TODO(alex.meade) make this work - #project_quota = quota.get_project_quota(...) - #abs_limits = project_quota.limits - #TODO remove when mark catches up - abs_limits = TEST_ABSOLUTE_LIMITS + context = req.environ['nova.context'] + abs_limits = quota.get_quota(context, context.project_id) rate_limits = req.environ.get("nova.limits", []) builder = self._get_view_builder(req) diff --git a/nova/api/openstack/views/limits.py b/nova/api/openstack/views/limits.py index ef1243f3d..464f91e33 100644 --- a/nova/api/openstack/views/limits.py +++ b/nova/api/openstack/views/limits.py @@ -112,19 +112,14 @@ class ViewBuilderV11(ViewBuilder): For example: {"ram": 512, "gigabytes": 1024}. """ + limit_names = { + "ram": "maxTotalRAMSize", + "instances": "maxTotalInstances", + "cores": "maxTotalCores", + "metadata_items": "maxServerMeta", + } limits = {} - #loops through absolute limits and their values - for absolute_limit_key, absolute_limit_value \ - in absolute_limits.items(): - - _abs_limit_map = { - "ram": "maxTotalRAMSize", - "instances": "maxTotalInstances", - "cores": "maxTotalCores", - } - - if not absolute_limit_value is None: - limits[_abs_limit_map[absolute_limit_key]] \ - = absolute_limit_value - + for name, value in absolute_limits.iteritems(): + if name in limit_names and value is not None: + limits[limit_names[name]] = value return limits -- cgit From ef42fa95197e7b0f73e04322456bbbdedaf3e2b3 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Wed, 18 May 2011 14:15:36 -0700 Subject: log any exceptions that get thrown trying to retrieve metadata --- nova/api/ec2/metadatarequesthandler.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/ec2/metadatarequesthandler.py b/nova/api/ec2/metadatarequesthandler.py index 28f99b0ef..481e34e12 100644 --- a/nova/api/ec2/metadatarequesthandler.py +++ b/nova/api/ec2/metadatarequesthandler.py @@ -71,7 +71,11 @@ class MetadataRequestHandler(wsgi.Application): remote_address = req.remote_addr if FLAGS.use_forwarded_for: remote_address = req.headers.get('X-Forwarded-For', remote_address) - meta_data = cc.get_metadata(remote_address) + try: + meta_data = cc.get_metadata(remote_address) + except Exception: + LOG.exception(_('Failed to get metadata for ip: %s'), remote_address) + raise if meta_data is None: LOG.error(_('Failed to get metadata for ip: %s'), remote_address) raise webob.exc.HTTPNotFound() -- cgit From 38ba122d9eb67c699ea0c10eab5961c3b4c25d81 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Wed, 18 May 2011 14:23:09 -0700 Subject: use a manual 500 with error text instead of traceback for failure --- nova/api/ec2/metadatarequesthandler.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/ec2/metadatarequesthandler.py b/nova/api/ec2/metadatarequesthandler.py index 481e34e12..720f264a4 100644 --- a/nova/api/ec2/metadatarequesthandler.py +++ b/nova/api/ec2/metadatarequesthandler.py @@ -23,6 +23,7 @@ import webob.exc from nova import log as logging from nova import flags +from nova import utils from nova import wsgi from nova.api.ec2 import cloud @@ -75,7 +76,12 @@ class MetadataRequestHandler(wsgi.Application): meta_data = cc.get_metadata(remote_address) except Exception: LOG.exception(_('Failed to get metadata for ip: %s'), remote_address) - raise + resp = webob.Response() + resp.status = 500 + message = _('An unknown error has occurred. ' + 'Please try your request again.') + resp.body = str(utils.utf8(message)) + return resp if meta_data is None: LOG.error(_('Failed to get metadata for ip: %s'), remote_address) raise webob.exc.HTTPNotFound() -- cgit From 5e722ea7b912f189c0a3b9434e9a38d08095ad00 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Wed, 18 May 2011 19:13:22 -0400 Subject: refactoring wsgi to separate controller/serialization/deserialization logic; creating osapi-specific module --- nova/api/openstack/__init__.py | 43 ++--- nova/api/openstack/accounts.py | 33 ++-- nova/api/openstack/backup_schedules.py | 27 ++- nova/api/openstack/consoles.py | 26 ++- nova/api/openstack/flavors.py | 34 ++-- nova/api/openstack/image_metadata.py | 19 ++- nova/api/openstack/images.py | 40 +++-- nova/api/openstack/ips.py | 33 ++-- nova/api/openstack/limits.py | 50 ++++-- nova/api/openstack/server_metadata.py | 21 ++- nova/api/openstack/servers.py | 124 +++++++------- nova/api/openstack/shared_ip_groups.py | 28 +--- nova/api/openstack/users.py | 43 +++-- nova/api/openstack/wsgi.py | 291 +++++++++++++++++++++++++++++++++ nova/api/openstack/zones.py | 33 ++-- 15 files changed, 620 insertions(+), 225 deletions(-) create mode 100644 nova/api/openstack/wsgi.py (limited to 'nova/api') diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index 348b70d5b..fbbd99cb9 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -26,7 +26,7 @@ import webob.exc from nova import flags from nova import log as logging -from nova import wsgi +from nova import wsgi as base_wsgi from nova.api.openstack import accounts from nova.api.openstack import faults from nova.api.openstack import backup_schedules @@ -40,6 +40,7 @@ from nova.api.openstack import servers from nova.api.openstack import server_metadata from nova.api.openstack import shared_ip_groups from nova.api.openstack import users +from nova.api.openstack import wsgi from nova.api.openstack import zones @@ -50,7 +51,7 @@ flags.DEFINE_bool('allow_admin_api', 'When True, this API service will accept admin operations.') -class FaultWrapper(wsgi.Middleware): +class FaultWrapper(base_wsgi.Middleware): """Calls down the middleware stack, making exceptions into faults.""" @webob.dec.wsgify(RequestClass=wsgi.Request) @@ -63,7 +64,7 @@ class FaultWrapper(wsgi.Middleware): return faults.Fault(exc) -class APIRouter(wsgi.Router): +class APIRouter(base_wsgi.Router): """ Routes requests on the OpenStack API to the appropriate controller and method. @@ -97,18 +98,20 @@ class APIRouter(wsgi.Router): server_members['reset_network'] = 'POST' server_members['inject_network_info'] = 'POST' - mapper.resource("zone", "zones", controller=zones.Controller(), + mapper.resource("zone", "zones", + controller=zones.resource_factory(), collection={'detail': 'GET', 'info': 'GET'}), - mapper.resource("user", "users", controller=users.Controller(), + mapper.resource("user", "users", + controller=users.resource_factory(), collection={'detail': 'GET'}) mapper.resource("account", "accounts", - controller=accounts.Controller(), + controller=accounts.resource_factory(), collection={'detail': 'GET'}) mapper.resource("console", "consoles", - controller=consoles.Controller(), + controller=consoles.resource_factory(), parent_resource=dict(member_name='server', collection_name='servers')) @@ -121,31 +124,31 @@ class APIRouterV10(APIRouter): def _setup_routes(self, mapper): super(APIRouterV10, self)._setup_routes(mapper) mapper.resource("server", "servers", - controller=servers.ControllerV10(), + controller=servers.resource_factory('1.0'), collection={'detail': 'GET'}, member=self.server_members) mapper.resource("image", "images", - controller=images.ControllerV10(), + controller=images.resource_factory('1.0'), collection={'detail': 'GET'}) mapper.resource("flavor", "flavors", - controller=flavors.ControllerV10(), + controller=flavors.resource_factory('1.0'), collection={'detail': 'GET'}) mapper.resource("shared_ip_group", "shared_ip_groups", collection={'detail': 'GET'}, - controller=shared_ip_groups.Controller()) + controller=shared_ip_groups.resource_factory()) mapper.resource("backup_schedule", "backup_schedule", - controller=backup_schedules.Controller(), + controller=backup_schedules.resource_factory(), parent_resource=dict(member_name='server', collection_name='servers')) mapper.resource("limit", "limits", - controller=limits.LimitsControllerV10()) + controller=limits.resource_factory('1.0')) - mapper.resource("ip", "ips", controller=ips.Controller(), + mapper.resource("ip", "ips", controller=ips.resource_factory(), collection=dict(public='GET', private='GET'), parent_resource=dict(member_name='server', collection_name='servers')) @@ -157,27 +160,27 @@ class APIRouterV11(APIRouter): def _setup_routes(self, mapper): super(APIRouterV11, self)._setup_routes(mapper) mapper.resource("server", "servers", - controller=servers.ControllerV11(), + controller=servers.resource_factory('1.1'), collection={'detail': 'GET'}, member=self.server_members) mapper.resource("image", "images", - controller=images.ControllerV11(), + controller=images.resource_factory('1.1'), collection={'detail': 'GET'}) mapper.resource("image_meta", "meta", - controller=image_metadata.Controller(), + controller=image_metadata.resource_factory(), parent_resource=dict(member_name='image', collection_name='images')) mapper.resource("server_meta", "meta", - controller=server_metadata.Controller(), + controller=server_metadata.resource_factory(), parent_resource=dict(member_name='server', collection_name='servers')) mapper.resource("flavor", "flavors", - controller=flavors.ControllerV11(), + controller=flavors.resource_factory('1.1'), collection={'detail': 'GET'}) mapper.resource("limit", "limits", - controller=limits.LimitsControllerV11()) + controller=limits.resource_factory('1.1')) diff --git a/nova/api/openstack/accounts.py b/nova/api/openstack/accounts.py index 00fdd4540..d8a9d1909 100644 --- a/nova/api/openstack/accounts.py +++ b/nova/api/openstack/accounts.py @@ -20,8 +20,9 @@ from nova import flags from nova import log as logging from nova.auth import manager -from nova.api.openstack import common from nova.api.openstack import faults +from nova.api.openstack import wsgi + FLAGS = flags.FLAGS LOG = logging.getLogger('nova.api.openstack') @@ -34,12 +35,7 @@ def _translate_keys(account): manager=account.project_manager_id) -class Controller(common.OpenstackController): - - _serialization_metadata = { - 'application/xml': { - "attributes": { - "account": ["id", "name", "description", "manager"]}}} +class Controller(object): def __init__(self): self.manager = manager.AuthManager() @@ -66,20 +62,33 @@ class Controller(common.OpenstackController): self.manager.delete_project(id) return {} - def create(self, req): + def create(self, req, body): """We use update with create-or-update semantics because the id comes from an external source""" raise faults.Fault(webob.exc.HTTPNotImplemented()) - def update(self, req, id): + def update(self, req, id, body): """This is really create or update.""" self._check_admin(req.environ['nova.context']) - env = self._deserialize(req.body, req.get_content_type()) - description = env['account'].get('description') - manager = env['account'].get('manager') + description = body['account'].get('description') + manager = body['account'].get('manager') try: account = self.manager.get_project(id) self.manager.modify_project(id, manager, description) except exception.NotFound: account = self.manager.create_project(id, manager, description) return dict(account=_translate_keys(account)) + + +def resource_factory(): + metadata = { + "attributes": { + "account": ["id", "name", "description", "manager"], + }, + } + + serializers = { + 'application/xml': wsgi.XMLSerializer(metadata=metadata), + } + + return wsgi.Resource(Controller(), serializers=serializers) diff --git a/nova/api/openstack/backup_schedules.py b/nova/api/openstack/backup_schedules.py index 4bf744046..4153c90c1 100644 --- a/nova/api/openstack/backup_schedules.py +++ b/nova/api/openstack/backup_schedules.py @@ -19,9 +19,8 @@ import time from webob import exc -from nova.api.openstack import common from nova.api.openstack import faults -import nova.image.service +from nova.api.openstack import wsgi def _translate_keys(inst): @@ -29,14 +28,9 @@ def _translate_keys(inst): return dict(backupSchedule=inst) -class Controller(common.OpenstackController): +class Controller(object): """ The backup schedule API controller for the Openstack API """ - _serialization_metadata = { - 'application/xml': { - 'attributes': { - 'backupSchedule': []}}} - def __init__(self): pass @@ -48,7 +42,7 @@ class Controller(common.OpenstackController): """ Returns a single backup schedule for a given instance """ return faults.Fault(exc.HTTPNotImplemented()) - def create(self, req, server_id): + def create(self, req, server_id, body): """ No actual update method required, since the existing API allows both create and update through a POST """ return faults.Fault(exc.HTTPNotImplemented()) @@ -56,3 +50,18 @@ class Controller(common.OpenstackController): def delete(self, req, server_id, id): """ Deletes an existing backup schedule """ return faults.Fault(exc.HTTPNotImplemented()) + + +def resource_factory(): + metadata = { + 'attributes': { + 'backupSchedule': [], + }, + } + + serializers = { + 'application/xml': wsgi.XMLSerializer(xmlns=wsgi.XMLNS_V10, + metadata=metadata), + } + + return wsgi.Resource(Controller(), serializers=serializers) diff --git a/nova/api/openstack/consoles.py b/nova/api/openstack/consoles.py index 1a77f25d7..36d570803 100644 --- a/nova/api/openstack/consoles.py +++ b/nova/api/openstack/consoles.py @@ -19,8 +19,8 @@ from webob import exc from nova import console from nova import exception -from nova.api.openstack import common from nova.api.openstack import faults +from nova.api.openstack import wsgi def _translate_keys(cons): @@ -43,14 +43,9 @@ def _translate_detail_keys(cons): return dict(console=info) -class Controller(common.OpenstackController): +class Controller(object): """The Consoles Controller for the Openstack API""" - _serialization_metadata = { - 'application/xml': { - 'attributes': { - 'console': []}}} - def __init__(self): self.console_api = console.API() super(Controller, self).__init__() @@ -63,9 +58,8 @@ class Controller(common.OpenstackController): return dict(consoles=[_translate_keys(console) for console in consoles]) - def create(self, req, server_id): + def create(self, req, server_id, body): """Creates a new console""" - #info = self._deserialize(req.body, req.get_content_type()) self.console_api.create_console( req.environ['nova.context'], int(server_id)) @@ -94,3 +88,17 @@ class Controller(common.OpenstackController): except exception.NotFound: return faults.Fault(exc.HTTPNotFound()) return exc.HTTPAccepted() + + +def resource_factory(): + metadata = { + 'attributes': { + 'console': [], + }, + } + + serializers = { + 'application/xml': wsgi.XMLSerializer(metadata=metadata), + } + + return wsgi.Resource(Controller(), serializers=serializers) diff --git a/nova/api/openstack/flavors.py b/nova/api/openstack/flavors.py index 4c5971cf6..46056a27a 100644 --- a/nova/api/openstack/flavors.py +++ b/nova/api/openstack/flavors.py @@ -19,22 +19,13 @@ import webob from nova import db from nova import exception -from nova.api.openstack import common from nova.api.openstack import views +from nova.api.openstack import wsgi -class Controller(common.OpenstackController): +class Controller(object): """Flavor controller for the OpenStack API.""" - _serialization_metadata = { - 'application/xml': { - "attributes": { - "flavor": ["id", "name", "ram", "disk"], - "link": ["rel", "type", "href"], - } - } - } - def index(self, req): """Return all flavors in brief.""" items = self._get_flavors(req, is_detail=False) @@ -71,14 +62,31 @@ class Controller(common.OpenstackController): class ControllerV10(Controller): + def _get_view_builder(self, req): return views.flavors.ViewBuilder() class ControllerV11(Controller): + def _get_view_builder(self, req): base_url = req.application_url return views.flavors.ViewBuilderV11(base_url) - def get_default_xmlns(self, req): - return common.XML_NS_V11 + +def resource_factory(version='1.0'): + controller = { + '1.0': ControllerV10, + '1.1': ControllerV11, + }[version]() + + xmlns = { + '1.0': wsgi.XMLNS_V10, + '1.1': wsgi.XMLNS_V11, + }[version] + + serializers = { + 'application/xml': wsgi.XMLSerializer(xmlns=xmlns), + } + + return wsgi.Resource(controller, serializers=serializers) diff --git a/nova/api/openstack/image_metadata.py b/nova/api/openstack/image_metadata.py index 1eccc0174..ce0140265 100644 --- a/nova/api/openstack/image_metadata.py +++ b/nova/api/openstack/image_metadata.py @@ -21,19 +21,18 @@ from nova import flags from nova import quota from nova import utils from nova import wsgi -from nova.api.openstack import common from nova.api.openstack import faults +from nova.api.openstack import wsgi FLAGS = flags.FLAGS -class Controller(common.OpenstackController): +class Controller(object): """The image metadata API controller for the Openstack API""" def __init__(self): self.image_service = utils.import_object(FLAGS.image_service) - super(Controller, self).__init__() def _get_metadata(self, context, image_id, image=None): if not image: @@ -64,9 +63,8 @@ class Controller(common.OpenstackController): else: return faults.Fault(exc.HTTPNotFound()) - def create(self, req, image_id): + def create(self, req, image_id, body): context = req.environ['nova.context'] - body = self._deserialize(req.body, req.get_content_type()) img = self.image_service.show(context, image_id) metadata = self._get_metadata(context, image_id, img) if 'metadata' in body: @@ -77,9 +75,8 @@ class Controller(common.OpenstackController): self.image_service.update(context, image_id, img, None) return dict(metadata=metadata) - def update(self, req, image_id, id): + def update(self, req, image_id, id, body): context = req.environ['nova.context'] - body = self._deserialize(req.body, req.get_content_type()) if not id in body: expl = _('Request body and URI mismatch') raise exc.HTTPBadRequest(explanation=expl) @@ -104,3 +101,11 @@ class Controller(common.OpenstackController): metadata.pop(id) img['properties'] = metadata self.image_service.update(context, image_id, img, None) + + +def resource_factory(): + serializers = { + 'application/xml': wsgi.XMLSerializer(xmlns=wsgi.XMLNS_V11), + } + + return wsgi.Resource(Controller(), serializers=serializers) diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index 34d4c27fc..e22854ebf 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -23,25 +23,16 @@ from nova import utils from nova.api.openstack import common from nova.api.openstack import faults from nova.api.openstack.views import images as images_view +from nova.api.openstack import wsgi LOG = log.getLogger('nova.api.openstack.images') FLAGS = flags.FLAGS -class Controller(common.OpenstackController): +class Controller(object): """Base `wsgi.Controller` for retrieving/displaying images.""" - _serialization_metadata = { - 'application/xml': { - "attributes": { - "image": ["id", "name", "updated", "created", "status", - "serverId", "progress"], - "link": ["rel", "type", "href"], - }, - }, - } - def __init__(self, image_service=None, compute_service=None): """Initialize new `ImageController`. @@ -153,3 +144,30 @@ class ControllerV11(Controller): def get_default_xmlns(self, req): return common.XML_NS_V11 + + +def resource_factory(version='1.0'): + controller = { + '1.0': ControllerV10, + '1.1': ControllerV11, + }[version]() + + xmlns = { + '1.0': wsgi.XMLNS_V10, + '1.1': wsgi.XMLNS_V11, + }[version] + + metadata = { + "attributes": { + "image": ["id", "name", "updated", "created", "status", + "serverId", "progress"], + "link": ["rel", "type", "href"], + }, + } + + serializers = { + 'application/xml': wsgi.XMLSerializer(xmlns=xmlns, + metadata=metadata), + } + + return wsgi.Resource(controller, serializers=serializers) diff --git a/nova/api/openstack/ips.py b/nova/api/openstack/ips.py index 778e9ba1a..24612eafb 100644 --- a/nova/api/openstack/ips.py +++ b/nova/api/openstack/ips.py @@ -20,23 +20,14 @@ import time from webob import exc import nova -import nova.api.openstack.views.addresses -from nova.api.openstack import common from nova.api.openstack import faults +import nova.api.openstack.views.addresses +from nova.api.openstack import wsgi -class Controller(common.OpenstackController): +class Controller(object): """The servers addresses API controller for the Openstack API.""" - _serialization_metadata = { - 'application/xml': { - 'list_collections': { - 'public': {'item_name': 'ip', 'item_key': 'addr'}, - 'private': {'item_name': 'ip', 'item_key': 'addr'}, - }, - }, - } - def __init__(self): self.compute_api = nova.compute.API() self.builder = nova.api.openstack.views.addresses.ViewBuilderV10() @@ -65,8 +56,24 @@ class Controller(common.OpenstackController): def show(self, req, server_id, id): return faults.Fault(exc.HTTPNotImplemented()) - def create(self, req, server_id): + def create(self, req, server_id, body): return faults.Fault(exc.HTTPNotImplemented()) def delete(self, req, server_id, id): return faults.Fault(exc.HTTPNotImplemented()) + + +def resource_factory(): + metadata = { + 'list_collections': { + 'public': {'item_name': 'ip', 'item_key': 'addr'}, + 'private': {'item_name': 'ip', 'item_key': 'addr'}, + }, + } + + serializers = { + 'application/xml': wsgi.XMLSerializer(metadata=metadata, + xmlns=wsgi.XMLNS_V10), + } + + return wsgi.Resource(Controller(), serializers=serializers) diff --git a/nova/api/openstack/limits.py b/nova/api/openstack/limits.py index 47bc238f1..306048d8f 100644 --- a/nova/api/openstack/limits.py +++ b/nova/api/openstack/limits.py @@ -30,10 +30,11 @@ from collections import defaultdict from webob.dec import wsgify -from nova import wsgi +from nova import wsgi as base_wsgi from nova.api.openstack import common from nova.api.openstack import faults from nova.api.openstack.views import limits as limits_views +from nova.api.openstack import wsgi # Convenience constants for the limits dictionary passed to Limiter(). @@ -43,23 +44,11 @@ PER_HOUR = 60 * 60 PER_DAY = 60 * 60 * 24 -class LimitsController(common.OpenstackController): +class LimitsController(object): """ Controller for accessing limits in the OpenStack API. """ - _serialization_metadata = { - "application/xml": { - "attributes": { - "limit": ["verb", "URI", "uri", "regex", "value", "unit", - "resetTime", "next-available", "remaining", "name"], - }, - "plurals": { - "rate": "limit", - }, - }, - } - def index(self, req): """ Return all global and rate limit information. @@ -84,6 +73,35 @@ class LimitsControllerV11(LimitsController): return limits_views.ViewBuilderV11() +def resource_factory(version='1.0'): + controller = { + '1.0': LimitsControllerV10, + '1.1': LimitsControllerV11, + }[version]() + + xmlns = { + '1.0': wsgi.XMLNS_V10, + '1.1': wsgi.XMLNS_V11, + }[version] + + metadata = { + "attributes": { + "limit": ["verb", "URI", "uri", "regex", "value", "unit", + "resetTime", "next-available", "remaining", "name"], + }, + "plurals": { + "rate": "limit", + }, + } + + serializers = { + 'application/xml': wsgi.XMLSerializer(xmlns=xmlns, + metadata=metadata) + } + + return wsgi.Resource(controller, serializers=serializers) + + class Limit(object): """ Stores information about a limit for HTTP requets. @@ -195,7 +213,7 @@ DEFAULT_LIMITS = [ ] -class RateLimitingMiddleware(wsgi.Middleware): +class RateLimitingMiddleware(base_wsgi.Middleware): """ Rate-limits requests passing through this middleware. All limit information is stored in memory for this implementation. @@ -209,7 +227,7 @@ class RateLimitingMiddleware(wsgi.Middleware): @param application: WSGI application to wrap @param limits: List of dictionaries describing limits """ - wsgi.Middleware.__init__(self, application) + base_wsgi.Middleware.__init__(self, application) self._limiter = Limiter(limits or DEFAULT_LIMITS) @wsgify(RequestClass=wsgi.Request) diff --git a/nova/api/openstack/server_metadata.py b/nova/api/openstack/server_metadata.py index fd64ee4fb..fb9449b4c 100644 --- a/nova/api/openstack/server_metadata.py +++ b/nova/api/openstack/server_metadata.py @@ -19,12 +19,11 @@ from webob import exc from nova import compute from nova import quota -from nova import wsgi -from nova.api.openstack import common from nova.api.openstack import faults +from nova.api.openstack import wsgi -class Controller(common.OpenstackController): +class Controller(object): """ The server metadata API controller for the Openstack API """ def __init__(self): @@ -43,10 +42,9 @@ class Controller(common.OpenstackController): context = req.environ['nova.context'] return self._get_metadata(context, server_id) - def create(self, req, server_id): + def create(self, req, server_id, body): context = req.environ['nova.context'] - data = self._deserialize(req.body, req.get_content_type()) - metadata = data.get('metadata') + metadata = body.get('metadata') try: self.compute_api.update_or_create_instance_metadata(context, server_id, @@ -55,9 +53,8 @@ class Controller(common.OpenstackController): self._handle_quota_error(error) return req.body - def update(self, req, server_id, id): + def update(self, req, server_id, id, body): context = req.environ['nova.context'] - body = self._deserialize(req.body, req.get_content_type()) if not id in body: expl = _('Request body and URI mismatch') raise exc.HTTPBadRequest(explanation=expl) @@ -92,3 +89,11 @@ class Controller(common.OpenstackController): if error.code == "MetadataLimitExceeded": raise exc.HTTPBadRequest(explanation=error.message) raise error + + +def resource_factory(): + serializers = { + 'application/xml': wsgi.XMLSerializer(xmlns=wsgi.XMLNS_V11), + } + + return wsgi.Resource(Controller(), serializers=serializers) diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 8f2de2afe..78f8bb1b7 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -31,6 +31,7 @@ import nova.api.openstack.views.addresses import nova.api.openstack.views.flavors import nova.api.openstack.views.images import nova.api.openstack.views.servers +from nova.api.openstack import wsgi from nova.auth import manager as auth_manager from nova.compute import instance_types import nova.api.openstack @@ -41,31 +42,12 @@ LOG = logging.getLogger('nova.api.openstack.servers') FLAGS = flags.FLAGS -class Controller(common.OpenstackController): +class Controller(object): """ The Server API controller for the OpenStack API """ - _serialization_metadata = { - "application/xml": { - "attributes": { - "server": ["id", "imageId", "name", "flavorId", "hostId", - "status", "progress", "adminPass", "flavorRef", - "imageRef"], - "link": ["rel", "type", "href"], - }, - "dict_collections": { - "metadata": {"item_name": "meta", "item_key": "key"}, - }, - "list_collections": { - "public": {"item_name": "ip", "item_key": "addr"}, - "private": {"item_name": "ip", "item_key": "addr"}, - }, - }, - } - def __init__(self): self.compute_api = compute.API() self._image_service = utils.import_object(FLAGS.image_service) - super(Controller, self).__init__() def index(self, req): """ Returns a list of server names and ids for a given user """ @@ -122,15 +104,14 @@ class Controller(common.OpenstackController): return faults.Fault(exc.HTTPNotFound()) return exc.HTTPAccepted() - def create(self, req): + def create(self, req, body): """ Creates a new server for a given user """ - env = self._deserialize_create(req) - if not env: + if not body: return faults.Fault(exc.HTTPUnprocessableEntity()) context = req.environ['nova.context'] - password = self._get_server_admin_password(env['server']) + password = self._get_server_admin_password(body['server']) key_name = None key_data = None @@ -140,7 +121,7 @@ class Controller(common.OpenstackController): key_name = key_pair['name'] key_data = key_pair['public_key'] - requested_image_id = self._image_id_from_req_data(env) + requested_image_id = self._image_id_from_req_data(body) try: image_id = common.get_image_id_from_image_hash(self._image_service, context, requested_image_id) @@ -151,18 +132,18 @@ class Controller(common.OpenstackController): kernel_id, ramdisk_id = self._get_kernel_ramdisk_from_image( req, image_id) - personality = env['server'].get('personality') + personality = body['server'].get('personality') injected_files = [] if personality: injected_files = self._get_injected_files(personality) - flavor_id = self._flavor_id_from_req_data(env) + flavor_id = self._flavor_id_from_req_data(body) - if not 'name' in env['server']: + if not 'name' in body['server']: msg = _("Server name is not defined") return exc.HTTPBadRequest(msg) - name = env['server']['name'] + name = body['server']['name'] self._validate_server_name(name) name = name.strip() @@ -179,7 +160,7 @@ class Controller(common.OpenstackController): display_description=name, key_name=key_name, key_data=key_data, - metadata=env['server'].get('metadata', {}), + metadata=body['server'].get('metadata', {}), injected_files=injected_files) except quota.QuotaError as error: self._handle_quota_error(error) @@ -194,18 +175,6 @@ class Controller(common.OpenstackController): password) return server - def _deserialize_create(self, request): - """ - Deserialize a create request - - Overrides normal behavior in the case of xml content - """ - if request.content_type == "application/xml": - deserializer = ServerCreateRequestXMLDeserializer() - return deserializer.deserialize(request.body) - else: - return self._deserialize(request.body, request.get_content_type()) - def _get_injected_files(self, personality): """ Create a list of injected files from the personality attribute @@ -255,24 +224,23 @@ class Controller(common.OpenstackController): return utils.generate_password(16) @scheduler_api.redirect_handler - def update(self, req, id): + def update(self, req, id, body): """ Updates the server name or password """ if len(req.body) == 0: raise exc.HTTPUnprocessableEntity() - inst_dict = self._deserialize(req.body, req.get_content_type()) - if not inst_dict: + if not body: return faults.Fault(exc.HTTPUnprocessableEntity()) ctxt = req.environ['nova.context'] update_dict = {} - if 'name' in inst_dict['server']: - name = inst_dict['server']['name'] + if 'name' in body['server']: + name = body['server']['name'] self._validate_server_name(name) update_dict['display_name'] = name.strip() - self._parse_update(ctxt, id, inst_dict, update_dict) + self._parse_update(ctxt, id, body, update_dict) try: self.compute_api.update(ctxt, id, **update_dict) @@ -294,7 +262,7 @@ class Controller(common.OpenstackController): pass @scheduler_api.redirect_handler - def action(self, req, id): + def action(self, req, id, body): """Multi-purpose method used to reboot, rebuild, or resize a server""" @@ -307,10 +275,9 @@ class Controller(common.OpenstackController): 'rebuild': self._action_rebuild, } - input_dict = self._deserialize(req.body, req.get_content_type()) for key in actions.keys(): - if key in input_dict: - return actions[key](input_dict, req, id) + if key in body: + return actions[key](body, req, id) return faults.Fault(exc.HTTPNotImplemented()) def _action_change_password(self, input_dict, req, id): @@ -410,7 +377,7 @@ class Controller(common.OpenstackController): return exc.HTTPAccepted() @scheduler_api.redirect_handler - def reset_network(self, req, id): + def reset_network(self, req, id, body): """ Reset networking on an instance (admin only). @@ -425,7 +392,7 @@ class Controller(common.OpenstackController): return exc.HTTPAccepted() @scheduler_api.redirect_handler - def inject_network_info(self, req, id): + def inject_network_info(self, req, id, body): """ Inject network info for an instance (admin only). @@ -440,7 +407,7 @@ class Controller(common.OpenstackController): return exc.HTTPAccepted() @scheduler_api.redirect_handler - def pause(self, req, id): + def pause(self, req, id, body): """ Permit Admins to Pause the server. """ ctxt = req.environ['nova.context'] try: @@ -452,7 +419,7 @@ class Controller(common.OpenstackController): return exc.HTTPAccepted() @scheduler_api.redirect_handler - def unpause(self, req, id): + def unpause(self, req, id, body): """ Permit Admins to Unpause the server. """ ctxt = req.environ['nova.context'] try: @@ -464,7 +431,7 @@ class Controller(common.OpenstackController): return exc.HTTPAccepted() @scheduler_api.redirect_handler - def suspend(self, req, id): + def suspend(self, req, id, body): """permit admins to suspend the server""" context = req.environ['nova.context'] try: @@ -476,7 +443,7 @@ class Controller(common.OpenstackController): return exc.HTTPAccepted() @scheduler_api.redirect_handler - def resume(self, req, id): + def resume(self, req, id, body): """permit admins to resume the server from suspend""" context = req.environ['nova.context'] try: @@ -815,3 +782,44 @@ class ServerCreateRequestXMLDeserializer(object): if child.nodeType == child.TEXT_NODE: return child.nodeValue return "" + + +def resource_factory(version='1.0'): + controller = { + '1.0': ControllerV10, + '1.1': ControllerV11, + }[version]() + + metadata = { + "attributes": { + "server": ["id", "imageId", "name", "flavorId", "hostId", + "status", "progress", "adminPass", "flavorRef", + "imageRef"], + "link": ["rel", "type", "href"], + }, + "dict_collections": { + "metadata": {"item_name": "meta", "item_key": "key"}, + }, + "list_collections": { + "public": {"item_name": "ip", "item_key": "addr"}, + "private": {"item_name": "ip", "item_key": "addr"}, + }, + } + + xmlns = { + '1.0': wsgi.XMLNS_V10, + '1.1': wsgi.XMLNS_V11, + }[version] + + serializers = { + 'application/xml': wsgi.XMLSerializer(metadata=metadata, + xmlns=xmlns), + } + + deserializers = { + 'application/xml': ServerCreateRequestXMLDeserializer(), + } + + return wsgi.Resource(controller, serializers=serializers, + deserializers=deserializers) + diff --git a/nova/api/openstack/shared_ip_groups.py b/nova/api/openstack/shared_ip_groups.py index 996db3648..db178f2a2 100644 --- a/nova/api/openstack/shared_ip_groups.py +++ b/nova/api/openstack/shared_ip_groups.py @@ -17,29 +17,13 @@ from webob import exc -from nova.api.openstack import common from nova.api.openstack import faults +from nova.api.openstack import wsgi -def _translate_keys(inst): - """ Coerces a shared IP group instance into proper dictionary format """ - return dict(sharedIpGroup=inst) - - -def _translate_detail_keys(inst): - """ Coerces a shared IP group instance into proper dictionary format with - correctly mapped attributes """ - return dict(sharedIpGroups=inst) - - -class Controller(common.OpenstackController): +class Controller(object): """ The Shared IP Groups Controller for the Openstack API """ - _serialization_metadata = { - 'application/xml': { - 'attributes': { - 'sharedIpGroup': []}}} - def index(self, req): """ Returns a list of Shared IP Groups for the user """ raise faults.Fault(exc.HTTPNotImplemented()) @@ -48,7 +32,7 @@ class Controller(common.OpenstackController): """ Shows in-depth information on a specific Shared IP Group """ raise faults.Fault(exc.HTTPNotImplemented()) - def update(self, req, id): + def update(self, req, id, body): """ You can't update a Shared IP Group """ raise faults.Fault(exc.HTTPNotImplemented()) @@ -60,6 +44,10 @@ class Controller(common.OpenstackController): """ Returns a complete list of Shared IP Groups """ raise faults.Fault(exc.HTTPNotImplemented()) - def create(self, req): + def create(self, req, body): """ Creates a new Shared IP group """ raise faults.Fault(exc.HTTPNotImplemented()) + + +def resource_factory(): + return wsgi.Resource(Controller()) diff --git a/nova/api/openstack/users.py b/nova/api/openstack/users.py index 7ae4c3232..35b6a502e 100644 --- a/nova/api/openstack/users.py +++ b/nova/api/openstack/users.py @@ -20,8 +20,10 @@ from nova import flags from nova import log as logging from nova.api.openstack import common from nova.api.openstack import faults +from nova.api.openstack import wsgi from nova.auth import manager + FLAGS = flags.FLAGS LOG = logging.getLogger('nova.api.openstack') @@ -34,12 +36,7 @@ def _translate_keys(user): admin=user.admin) -class Controller(common.OpenstackController): - - _serialization_metadata = { - 'application/xml': { - "attributes": { - "user": ["id", "name", "access", "secret", "admin"]}}} +class Controller(object): def __init__(self): self.manager = manager.AuthManager() @@ -81,23 +78,35 @@ class Controller(common.OpenstackController): self.manager.delete_user(id) return {} - def create(self, req): + def create(self, req, body): self._check_admin(req.environ['nova.context']) - env = self._deserialize(req.body, req.get_content_type()) - is_admin = env['user'].get('admin') in ('T', 'True', True) - name = env['user'].get('name') - access = env['user'].get('access') - secret = env['user'].get('secret') + is_admin = body['user'].get('admin') in ('T', 'True', True) + name = body['user'].get('name') + access = body['user'].get('access') + secret = body['user'].get('secret') user = self.manager.create_user(name, access, secret, is_admin) return dict(user=_translate_keys(user)) - def update(self, req, id): + def update(self, req, id, body): self._check_admin(req.environ['nova.context']) - env = self._deserialize(req.body, req.get_content_type()) - is_admin = env['user'].get('admin') + is_admin = body['user'].get('admin') if is_admin is not None: is_admin = is_admin in ('T', 'True', True) - access = env['user'].get('access') - secret = env['user'].get('secret') + access = body['user'].get('access') + secret = body['user'].get('secret') self.manager.modify_user(id, access, secret, is_admin) return dict(user=_translate_keys(self.manager.get_user(id))) + + +def resource_factory(): + metadata = { + "attributes": { + "user": ["id", "name", "access", "secret", "admin"], + }, + } + + serializers = { + 'application/xml': wsgi.XMLSerializer(metadata=metadata), + } + + return wsgi.Resource(Controller(), serializers=serializers) diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py new file mode 100644 index 000000000..9e0077932 --- /dev/null +++ b/nova/api/openstack/wsgi.py @@ -0,0 +1,291 @@ + +import json +import webob +from xml.dom import minidom + +from nova import exception +from nova import log as logging +from nova import utils + + +XMLNS_V10 = 'http://docs.rackspacecloud.com/servers/api/v1.0' +XMLNS_V11 = 'http://docs.openstack.org/compute/api/v1.1' + +LOG = logging.getLogger('nova.api.openstack.wsgi') + + +class Request(webob.Request): + def best_match_content_type(self, supported=None): + """Determine the requested content-type. + + Based on the query extension then the Accept header. + + :param supported: list of content-types to override defaults + + """ + supported = supported or ['application/json', 'application/xml'] + parts = self.path.rsplit('.', 1) + + if len(parts) > 1: + ctype = 'application/{0}'.format(parts[1]) + if ctype in supported: + return ctype + + bm = self.accept.best_match(supported) + + return bm or 'application/json' + + def get_content_type(self): + if not "Content-Type" in self.headers: + raise exception.InvalidContentType(content_type=None) + + allowed_types = ("application/xml", "application/json") + type = self.content_type + + if type not in allowed_types: + raise exception.InvalidContentType(content_type=type) + else: + return type + + +class JSONDeserializer(object): + def deserialize(self, datastring): + return utils.loads(datastring) + + +class JSONSerializer(object): + def serialize(self, data): + return utils.dumps(data) + + +class XMLDeserializer(object): + def __init__(self, metadata=None): + """ + :param metadata: information needed to deserialize xml into + a dictionary. + """ + super(XMLDeserializer, self).__init__() + self.metadata = metadata or {} + + def deserialize(self, datastring): + """XML deserialization entry point.""" + plurals = set(self.metadata.get('plurals', {})) + node = minidom.parseString(datastring).childNodes[0] + return {node.nodeName: self._from_xml_node(node, plurals)} + + def _from_xml_node(self, node, listnames): + """Convert a minidom node to a simple Python type. + + :param listnames: list of XML node names whose subnodes should + be considered list items. + + """ + if len(node.childNodes) == 1 and node.childNodes[0].nodeType == 3: + return node.childNodes[0].nodeValue + elif node.nodeName in listnames: + return [self._from_xml_node(n, listnames) for n in node.childNodes] + else: + result = dict() + for attr in node.attributes.keys(): + result[attr] = node.attributes[attr].nodeValue + for child in node.childNodes: + if child.nodeType != node.TEXT_NODE: + result[child.nodeName] = self._from_xml_node(child, + listnames) + return result + + +class XMLSerializer(object): + def __init__(self, metadata=None, xmlns=None): + """ + :param metadata: information needed to deserialize xml into + a dictionary. + :param xmlns: XML namespace to include with serialized xml + """ + super(XMLSerializer, self).__init__() + self.metadata = metadata or {} + self.xmlns = xmlns + + def serialize(self, data): + # We expect data to contain a single key which is the XML root. + root_key = data.keys()[0] + doc = minidom.Document() + node = self._to_xml_node(doc, self.metadata, root_key, data[root_key]) + + xmlns = node.getAttribute('xmlns') + if not xmlns and self.xmlns: + node.setAttribute('xmlns', self.xmlns) + + return node.toprettyxml(indent=' ') + + def _to_xml_node(self, doc, metadata, nodename, data): + """Recursive method to convert data members to XML nodes.""" + result = doc.createElement(nodename) + + # Set the xml namespace if one is specified + # TODO(justinsb): We could also use prefixes on the keys + xmlns = metadata.get('xmlns', None) + if xmlns: + result.setAttribute('xmlns', xmlns) + + if type(data) is list: + collections = metadata.get('list_collections', {}) + if nodename in collections: + metadata = collections[nodename] + for item in data: + node = doc.createElement(metadata['item_name']) + node.setAttribute(metadata['item_key'], str(item)) + result.appendChild(node) + return result + singular = metadata.get('plurals', {}).get(nodename, None) + if singular is None: + if nodename.endswith('s'): + singular = nodename[:-1] + else: + singular = 'item' + for item in data: + node = self._to_xml_node(doc, metadata, singular, item) + result.appendChild(node) + elif type(data) is dict: + collections = metadata.get('dict_collections', {}) + if nodename in collections: + metadata = collections[nodename] + for k, v in data.items(): + node = doc.createElement(metadata['item_name']) + node.setAttribute(metadata['item_key'], str(k)) + text = doc.createTextNode(str(v)) + node.appendChild(text) + result.appendChild(node) + return result + attrs = metadata.get('attributes', {}).get(nodename, {}) + for k, v in data.items(): + if k in attrs: + result.setAttribute(k, str(v)) + else: + node = self._to_xml_node(doc, metadata, k, v) + result.appendChild(node) + else: + # Type is atom + node = doc.createTextNode(str(data)) + result.appendChild(node) + return result + + +class Resource(object): + """WSGI app that dispatched to methods. + + WSGI app that reads routing information supplied by RoutesMiddleware + and calls the requested action method upon itself. All action methods + must, in addition to their normal parameters, accept a 'req' argument + which is the incoming wsgi.Request. They raise a webob.exc exception, + or return a dict which will be serialized by requested content type. + + """ + def __init__(self, controller, serializers=None, deserializers=None): + self.serializers = { + 'application/xml': XMLSerializer(), + 'application/json': JSONSerializer(), + } + self.serializers.update(serializers or {}) + + self.deserializers = { + 'application/xml': XMLDeserializer(), + 'application/json': JSONDeserializer(), + } + self.deserializers.update(deserializers or {}) + + self.controller = controller + + @webob.dec.wsgify(RequestClass=Request) + def __call__(self, request): + """Call the method specified in req.environ by RoutesMiddleware.""" + LOG.debug("%s %s" % (request.method, request.url)) + + try: + action, action_args, accept = self.deserialize_request(request) + except exception.InvalidContentType: + return webob.exc.HTTPBadRequest(_("Unsupported Content-Type")) + + controller_method = getattr(self.controller, action) + result = controller_method(req=request, **action_args) + + response = self.serialize_response(accept, result) + + try: + msg_dict = dict(url=request.url, status=response.status_int) + msg = _("%(url)s returned with HTTP %(status)d") % msg_dict + except AttributeError: + msg_dict = dict(url=request.url) + msg = _("%(url)s returned a fault") + + LOG.debug(msg) + + return response + + def serialize_response(self, content_type, response_body): + """Serialize a dict into a string and wrap in a wsgi.Request object. + + :param content_type: expected mimetype of serialized response body + :param response_body: dict produced by the Controller + + """ + if not type(response_body) is dict: + return response_body + + response = webob.Response() + response.headers['Content-Type'] = content_type + + serializer = self.get_serializer(content_type) + response.body = serializer.serialize(response_body) + + return response + + def get_serializer(self, content_type): + try: + return self.serializers[content_type] + except Exception: + raise exception.InvalidContentType(content_type=content_type) + + def deserialize_request(self, request): + """Parse a wsgi request into a set of params we care about. + + :param request: wsgi.Request object + + """ + action_args = self.get_action_args(request.environ) + action = action_args.pop('action') + + if request.method.lower() in ('post', 'put'): + if len(request.body) == 0: + action_args['body'] = None + else: + content_type = request.get_content_type() + deserializer = self.get_deserializer(content_type) + + try: + action_args['body'] = deserializer.deserialize(request.body) + except exception.InvalidContentType: + action_args['body'] = None + + accept = self.get_expected_content_type(request) + + return (action, action_args, accept) + + def get_expected_content_type(self, request): + return request.best_match_content_type() + + def get_action_args(self, request_environment): + args = request_environment['wsgiorg.routing_args'][1].copy() + + del args['controller'] + + if 'format' in args: + del args['format'] + + return args + + def get_deserializer(self, content_type): + try: + return self.deserializers[content_type] + except Exception: + raise exception.InvalidContentType(content_type=content_type) diff --git a/nova/api/openstack/zones.py b/nova/api/openstack/zones.py index 227ffecdc..d17ab7a9b 100644 --- a/nova/api/openstack/zones.py +++ b/nova/api/openstack/zones.py @@ -17,6 +17,7 @@ from nova import db from nova import flags from nova import log as logging from nova.api.openstack import common +from nova.api.openstack import wsgi from nova.scheduler import api @@ -41,12 +42,7 @@ def _scrub_zone(zone): 'deleted', 'deleted_at', 'updated_at')) -class Controller(common.OpenstackController): - - _serialization_metadata = { - 'application/xml': { - "attributes": { - "zone": ["id", "api_url", "name", "capabilities"]}}} +class Controller(object): def index(self, req): """Return all zones in brief""" @@ -85,15 +81,28 @@ class Controller(common.OpenstackController): api.zone_delete(req.environ['nova.context'], zone_id) return {} - def create(self, req): + def create(self, req, body): context = req.environ['nova.context'] - env = self._deserialize(req.body, req.get_content_type()) - zone = api.zone_create(context, env["zone"]) + zone = api.zone_create(context, body["zone"]) return dict(zone=_scrub_zone(zone)) - def update(self, req, id): + def update(self, req, id, body): context = req.environ['nova.context'] - env = self._deserialize(req.body, req.get_content_type()) zone_id = int(id) - zone = api.zone_update(context, zone_id, env["zone"]) + zone = api.zone_update(context, zone_id, body["zone"]) return dict(zone=_scrub_zone(zone)) + + +def resource_factory(): + metadata = { + "attributes": { + "zone": ["id", "api_url", "name", "capabilities"], + }, + } + + serializers = { + 'application/xml': wsgi.XMLSerializer(xmlns=wsgi.XMLNS_V10, + metadata=metadata), + } + + return wsgi.Resource(Controller(), serializers=serializers) -- cgit From 79d505c015bff1598e8e896f6198d65d90095ba6 Mon Sep 17 00:00:00 2001 From: Mark Washenberger Date: Wed, 18 May 2011 19:22:53 -0400 Subject: fixup absolute limits to latest 1.1 spec --- nova/api/openstack/limits.py | 2 +- nova/api/openstack/views/limits.py | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/limits.py b/nova/api/openstack/limits.py index 1411cec01..032a5ff2f 100644 --- a/nova/api/openstack/limits.py +++ b/nova/api/openstack/limits.py @@ -66,7 +66,7 @@ class LimitsController(common.OpenstackController): Return all global and rate limit information. """ context = req.environ['nova.context'] - abs_limits = quota.get_quota(context, context.project_id) + abs_limits = quota.get_quota(context, context.project_id) rate_limits = req.environ.get("nova.limits", []) builder = self._get_view_builder(req) diff --git a/nova/api/openstack/views/limits.py b/nova/api/openstack/views/limits.py index 464f91e33..5b34c8ad0 100644 --- a/nova/api/openstack/views/limits.py +++ b/nova/api/openstack/views/limits.py @@ -113,13 +113,14 @@ class ViewBuilderV11(ViewBuilder): """ limit_names = { - "ram": "maxTotalRAMSize", - "instances": "maxTotalInstances", - "cores": "maxTotalCores", - "metadata_items": "maxServerMeta", + "ram": ["maxTotalRAMSize"], + "instances": ["maxTotalInstances"], + "cores": ["maxTotalCores"], + "metadata_items": ["maxServerMeta", "maxImageMeta"], } limits = {} for name, value in absolute_limits.iteritems(): if name in limit_names and value is not None: - limits[limit_names[name]] = value + for name in limit_names[name]: + limits[name] = value return limits -- cgit From cfd58f5d58152e42ea9c131dc60427af5ef2118e Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Wed, 18 May 2011 20:33:25 -0400 Subject: removing controller/serializer code from wsgi.py; updating other code to use new modules --- nova/api/direct.py | 12 +++-- nova/api/openstack/common.py | 7 --- nova/api/openstack/consoles.py | 3 +- nova/api/openstack/contrib/volumes.py | 23 ++++----- nova/api/openstack/extensions.py | 97 +++++++++++++++++++++-------------- nova/api/openstack/faults.py | 39 ++++++++------ nova/api/openstack/image_metadata.py | 1 - nova/api/openstack/images.py | 11 ++-- nova/api/openstack/versions.py | 44 ++++++++-------- nova/api/openstack/wsgi.py | 23 ++++++--- 10 files changed, 141 insertions(+), 119 deletions(-) (limited to 'nova/api') diff --git a/nova/api/direct.py b/nova/api/direct.py index 8ceae299c..5e6c7c882 100644 --- a/nova/api/direct.py +++ b/nova/api/direct.py @@ -42,6 +42,7 @@ from nova import exception from nova import flags from nova import utils from nova import wsgi +import nova.api.openstack.wsgi # Global storage for registering modules. @@ -251,7 +252,7 @@ class Reflection(object): return self._methods[method] -class ServiceWrapper(wsgi.Controller): +class ServiceWrapper(object): """Wrapper to dynamically povide a WSGI controller for arbitrary objects. With lightweight introspection allows public methods on the object to @@ -265,7 +266,7 @@ class ServiceWrapper(wsgi.Controller): def __init__(self, service_handle): self.service_handle = service_handle - @webob.dec.wsgify(RequestClass=wsgi.Request) + @webob.dec.wsgify(RequestClass=nova.api.openstack.wsgi.Request) def __call__(self, req): arg_dict = req.environ['wsgiorg.routing_args'][1] action = arg_dict['action'] @@ -289,8 +290,11 @@ class ServiceWrapper(wsgi.Controller): try: content_type = req.best_match_content_type() - default_xmlns = self.get_default_xmlns(req) - return self._serialize(result, content_type, default_xmlns) + serializer = { + 'application/xml': nova.api.openstack.wsgi.XMLSerializer(), + 'application/json': nova.api.openstack.wsgi.JSONSerializer(), + }[content_type] + return serializer.serialize(result) except: raise exception.Error("returned non-serializable type: %s" % result) diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py index 32cd689ca..bb1a96812 100644 --- a/nova/api/openstack/common.py +++ b/nova/api/openstack/common.py @@ -23,7 +23,6 @@ import webob from nova import exception from nova import flags from nova import log as logging -from nova import wsgi LOG = logging.getLogger('nova.api.openstack.common') @@ -146,9 +145,3 @@ def get_id_from_href(href): except: LOG.debug(_("Error extracting id from href: %s") % href) raise webob.exc.HTTPBadRequest(_('could not parse id from href')) - - -class OpenstackController(wsgi.Controller): - def get_default_xmlns(self, req): - # Use V10 by default - return XML_NS_V10 diff --git a/nova/api/openstack/consoles.py b/nova/api/openstack/consoles.py index 36d570803..97304affe 100644 --- a/nova/api/openstack/consoles.py +++ b/nova/api/openstack/consoles.py @@ -44,11 +44,10 @@ def _translate_detail_keys(cons): class Controller(object): - """The Consoles Controller for the Openstack API""" + """The Consoles controller for the Openstack API""" def __init__(self): self.console_api = console.API() - super(Controller, self).__init__() def index(self, req, server_id): """Returns a list of consoles for this instance""" diff --git a/nova/api/openstack/contrib/volumes.py b/nova/api/openstack/contrib/volumes.py index 18de2ec71..b00790b7f 100644 --- a/nova/api/openstack/contrib/volumes.py +++ b/nova/api/openstack/contrib/volumes.py @@ -22,7 +22,6 @@ from nova import exception from nova import flags from nova import log as logging from nova import volume -from nova import wsgi from nova.api.openstack import common from nova.api.openstack import extensions from nova.api.openstack import faults @@ -64,7 +63,7 @@ def _translate_volume_summary_view(context, vol): return d -class VolumeController(wsgi.Controller): +class VolumeController(object): """The Volumes API controller for the OpenStack API.""" _serialization_metadata = { @@ -124,15 +123,14 @@ class VolumeController(wsgi.Controller): res = [entity_maker(context, vol) for vol in limited_list] return {'volumes': res} - def create(self, req): + def create(self, req, body): """Creates a new volume.""" context = req.environ['nova.context'] - env = self._deserialize(req.body, req.get_content_type()) - if not env: + if not body: return faults.Fault(exc.HTTPUnprocessableEntity()) - vol = env['volume'] + vol = body['volume'] size = vol['size'] LOG.audit(_("Create volume of %s GB"), size, context=context) new_volume = self.volume_api.create(context, size, @@ -175,7 +173,7 @@ def _translate_attachment_summary_view(_context, vol): return d -class VolumeAttachmentController(wsgi.Controller): +class VolumeAttachmentController(object): """The volume attachment API controller for the Openstack API. A child resource of the server. Note that we use the volume id @@ -219,17 +217,16 @@ class VolumeAttachmentController(wsgi.Controller): return {'volumeAttachment': _translate_attachment_detail_view(context, vol)} - def create(self, req, server_id): + def create(self, req, server_id, body): """Attach a volume to an instance.""" context = req.environ['nova.context'] - env = self._deserialize(req.body, req.get_content_type()) - if not env: + if not body: return faults.Fault(exc.HTTPUnprocessableEntity()) instance_id = server_id - volume_id = env['volumeAttachment']['volumeId'] - device = env['volumeAttachment']['device'] + volume_id = body['volumeAttachment']['volumeId'] + device = body['volumeAttachment']['device'] msg = _("Attach volume %(volume_id)s to instance %(server_id)s" " at %(device)s") % locals() @@ -259,7 +256,7 @@ class VolumeAttachmentController(wsgi.Controller): # TODO(justinsb): How do I return "accepted" here? return {'volumeAttachment': attachment} - def update(self, _req, _server_id, _id): + def update(self, req, server_id, id, body): """Update a volume attachment. We don't currently support this.""" return faults.Fault(exc.HTTPBadRequest()) diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py index 7ea7afef6..73f174e07 100644 --- a/nova/api/openstack/extensions.py +++ b/nova/api/openstack/extensions.py @@ -27,9 +27,10 @@ import webob.exc from nova import exception from nova import flags from nova import log as logging -from nova import wsgi +from nova import wsgi as base_wsgi from nova.api.openstack import common from nova.api.openstack import faults +from nova.api.openstack import wsgi LOG = logging.getLogger('extensions') @@ -116,28 +117,34 @@ class ExtensionDescriptor(object): return response_exts -class ActionExtensionController(common.OpenstackController): - +class ActionExtensionController(object): def __init__(self, application): - self.application = application self.action_handlers = {} def add_action(self, action_name, handler): self.action_handlers[action_name] = handler - def action(self, req, id): - - input_dict = self._deserialize(req.body, req.get_content_type()) + def action(self, req, id, body): for action_name, handler in self.action_handlers.iteritems(): - if action_name in input_dict: - return handler(input_dict, req, id) + if action_name in body: + return handler(body, req, id) # no action handler found (bump to downstream application) res = self.application return res -class ResponseExtensionController(common.OpenstackController): +class ActionExtensionResource(wsgi.Resource): + + def __init__(self, application): + controller = ActionExtensionController(application) + super(ActionExtensionResource, self).__init__(controller) + + def add_action(self, action_name, handler): + self.controller.add_action(action_name, handler) + + +class ResponseExtensionController(object): def __init__(self, application): self.application = application @@ -157,7 +164,11 @@ class ResponseExtensionController(common.OpenstackController): headers = res.headers except AttributeError: default_xmlns = None - body = self._serialize(res, content_type, default_xmlns) + serializer = { + 'application/xml': wsgi.XMLSerializer(), + 'application/json': wsgi.JSONSerializer(), + }[content_type] + body = serializer.serialize(res) headers = {"Content-Type": content_type} res = webob.Response() res.body = body @@ -165,7 +176,17 @@ class ResponseExtensionController(common.OpenstackController): return res -class ExtensionController(common.OpenstackController): +class ResponseExtensionResource(wsgi.Resource): + + def __init__(self, application): + controller = ResponseExtensionController(application) + super(ResponseExtensionResource, self).__init__(controller) + + def add_handler(self, handler): + self.controller.add_handler(handler) + + +class ExtensionController(object): def __init__(self, extension_manager): self.extension_manager = extension_manager @@ -198,7 +219,7 @@ class ExtensionController(common.OpenstackController): raise faults.Fault(webob.exc.HTTPNotFound()) -class ExtensionMiddleware(wsgi.Middleware): +class ExtensionMiddleware(base_wsgi.Middleware): """Extensions middleware for WSGI.""" @classmethod def factory(cls, global_config, **local_config): @@ -207,43 +228,43 @@ class ExtensionMiddleware(wsgi.Middleware): return cls(app, **local_config) return _factory - def _action_ext_controllers(self, application, ext_mgr, mapper): - """Return a dict of ActionExtensionController-s by collection.""" - action_controllers = {} + def _action_ext_resources(self, application, ext_mgr, mapper): + """Return a dict of ActionExtensionResource objects by collection.""" + action_resources = {} for action in ext_mgr.get_actions(): - if not action.collection in action_controllers.keys(): - controller = ActionExtensionController(application) + if not action.collection in action_resources.keys(): + resource = ActionExtensionResource(application) mapper.connect("/%s/:(id)/action.:(format)" % action.collection, action='action', - controller=controller, + controller=resource, conditions=dict(method=['POST'])) mapper.connect("/%s/:(id)/action" % action.collection, action='action', - controller=controller, + controller=resource, conditions=dict(method=['POST'])) - action_controllers[action.collection] = controller + action_resources[action.collection] = resource - return action_controllers + return action_resources - def _response_ext_controllers(self, application, ext_mgr, mapper): - """Returns a dict of ResponseExtensionController-s by collection.""" - response_ext_controllers = {} + def _response_ext_resources(self, application, ext_mgr, mapper): + """Returns a dict of ResponseExtensionResource objects by collection.""" + response_ext_resources = {} for resp_ext in ext_mgr.get_response_extensions(): - if not resp_ext.key in response_ext_controllers.keys(): - controller = ResponseExtensionController(application) + if not resp_ext.key in response_ext_resources.keys(): + resource = ResponseExtensionResource(application) mapper.connect(resp_ext.url_route + '.:(format)', action='process', - controller=controller, + controller=resource, conditions=resp_ext.conditions) mapper.connect(resp_ext.url_route, action='process', - controller=controller, + controller=resource, conditions=resp_ext.conditions) - response_ext_controllers[resp_ext.key] = controller + response_ext_resources[resp_ext.key] = resource - return response_ext_controllers + return response_ext_resources def __init__(self, application, ext_mgr=None): @@ -258,21 +279,21 @@ class ExtensionMiddleware(wsgi.Middleware): LOG.debug(_('Extended resource: %s'), resource.collection) mapper.resource(resource.collection, resource.collection, - controller=resource.controller, + controller=wsgi.Resource(resource.controller), collection=resource.collection_actions, member=resource.member_actions, parent_resource=resource.parent) # extended actions - action_controllers = self._action_ext_controllers(application, ext_mgr, + action_resources = self._action_ext_resources(application, ext_mgr, mapper) for action in ext_mgr.get_actions(): LOG.debug(_('Extended action: %s'), action.action_name) - controller = action_controllers[action.collection] - controller.add_action(action.action_name, action.handler) + resource = action_resources[action.collection] + resource.add_action(action.action_name, action.handler) # extended responses - resp_controllers = self._response_ext_controllers(application, ext_mgr, + resp_controllers = self._response_ext_resources(application, ext_mgr, mapper) for response_ext in ext_mgr.get_response_extensions(): LOG.debug(_('Extended response: %s'), response_ext.key) @@ -422,7 +443,7 @@ class ExtensionManager(object): class ResponseExtension(object): - """Add data to responses from core nova OpenStack API controllers.""" + """Add data to responses from core nova OpenStack API resources.""" def __init__(self, method, url_route, handler): self.url_route = url_route @@ -432,7 +453,7 @@ class ResponseExtension(object): class ActionExtension(object): - """Add custom actions to core nova OpenStack API controllers.""" + """Add custom actions to core nova OpenStack API resources.""" def __init__(self, collection, action_name, handler): self.collection = collection diff --git a/nova/api/openstack/faults.py b/nova/api/openstack/faults.py index 87118ce19..fd36f8f17 100644 --- a/nova/api/openstack/faults.py +++ b/nova/api/openstack/faults.py @@ -19,8 +19,7 @@ import webob.dec import webob.exc -from nova import wsgi -from nova.api.openstack import common +from nova.api.openstack import wsgi class Fault(webob.exc.HTTPException): @@ -55,13 +54,21 @@ class Fault(webob.exc.HTTPException): if code == 413: retry = self.wrapped_exc.headers['Retry-After'] fault_data[fault_name]['retryAfter'] = retry + # 'code' is an attribute on the fault tag itself - metadata = {'application/xml': {'attributes': {fault_name: 'code'}}} - default_xmlns = common.XML_NS_V10 - serializer = wsgi.Serializer(metadata, default_xmlns) + metadata = {'attributes': {fault_name: 'code'}} + content_type = req.best_match_content_type() - self.wrapped_exc.body = serializer.serialize(fault_data, content_type) + + serializer = { + 'application/xml': wsgi.XMLSerializer(metadata=metadata, + xmlns=wsgi.XMLNS_V10), + 'application/json': wsgi.JSONSerializer(), + }[content_type] + + self.wrapped_exc.body = serializer.serialize(fault_data) self.wrapped_exc.content_type = content_type + return self.wrapped_exc @@ -70,14 +77,6 @@ class OverLimitFault(webob.exc.HTTPException): Rate-limited request response. """ - _serialization_metadata = { - "application/xml": { - "attributes": { - "overLimitFault": "code", - }, - }, - } - def __init__(self, message, details, retry_time): """ Initialize new `OverLimitFault` with relevant information. @@ -97,8 +96,16 @@ class OverLimitFault(webob.exc.HTTPException): Return the wrapped exception with a serialized body conforming to our error format. """ - serializer = wsgi.Serializer(self._serialization_metadata) content_type = request.best_match_content_type() - content = serializer.serialize(self.content, content_type) + metadata = {"attributes": {"overLimitFault": "code"}} + + serializer = { + 'application/xml': wsgi.XMLSerializer(metadata=metadata, + xmlns=wsgi.XMLNS_V10), + 'application/json': wsgi.JSONSerializer(), + }[content_type] + + content = serializer.serialize(self.content) self.wrapped_exc.body = content + return self.wrapped_exc diff --git a/nova/api/openstack/image_metadata.py b/nova/api/openstack/image_metadata.py index ce0140265..506b63acf 100644 --- a/nova/api/openstack/image_metadata.py +++ b/nova/api/openstack/image_metadata.py @@ -20,7 +20,6 @@ from webob import exc from nova import flags from nova import quota from nova import utils -from nova import wsgi from nova.api.openstack import faults from nova.api.openstack import wsgi diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index e22854ebf..5a03573d8 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -31,7 +31,7 @@ FLAGS = flags.FLAGS class Controller(object): - """Base `wsgi.Controller` for retrieving/displaying images.""" + """Base controller for retrieving/displaying images.""" def __init__(self, image_service=None, compute_service=None): """Initialize new `ImageController`. @@ -99,21 +99,20 @@ class Controller(object): self._image_service.delete(context, image_id) return webob.exc.HTTPNoContent() - def create(self, req): + def create(self, req, body): """Snapshot a server instance and save the image. :param req: `wsgi.Request` object """ context = req.environ['nova.context'] content_type = req.get_content_type() - image = self._deserialize(req.body, content_type) - if not image: + if not body: raise webob.exc.HTTPBadRequest() try: - server_id = image["image"]["serverId"] - image_name = image["image"]["name"] + server_id = body["image"]["serverId"] + image_name = body["image"]["name"] except KeyError: raise webob.exc.HTTPBadRequest() diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py index 3f9d91934..a8d785b52 100644 --- a/nova/api/openstack/versions.py +++ b/nova/api/openstack/versions.py @@ -18,13 +18,27 @@ import webob import webob.dec -from nova import wsgi +from nova import wsgi as base_wsgi import nova.api.openstack.views.versions +from nova.api.openstack import wsgi -class Versions(wsgi.Application): - @webob.dec.wsgify(RequestClass=wsgi.Request) - def __call__(self, req): +class Versions(wsgi.Resource, base_wsgi.Application): + def __init__(self): + metadata = { + "attributes": { + "version": ["status", "id"], + "link": ["rel", "href"], + } + } + + serializers = { + 'application/xml': wsgi.XMLSerializer(metadata=metadata), + } + + super(Versions, self).__init__(None, serializers=serializers) + + def dispatch(self, request, *args): """Respond to a request for all OpenStack API versions.""" version_objs = [ { @@ -37,24 +51,6 @@ class Versions(wsgi.Application): }, ] - builder = nova.api.openstack.views.versions.get_view_builder(req) + builder = nova.api.openstack.views.versions.get_view_builder(request) versions = [builder.build(version) for version in version_objs] - response = dict(versions=versions) - - metadata = { - "application/xml": { - "attributes": { - "version": ["status", "id"], - "link": ["rel", "href"], - } - } - } - - content_type = req.best_match_content_type() - body = wsgi.Serializer(metadata).serialize(response, content_type) - - response = webob.Response() - response.content_type = content_type - response.body = body - - return response + return dict(versions=versions) diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index 9e0077932..97280c365 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -206,8 +206,7 @@ class Resource(object): except exception.InvalidContentType: return webob.exc.HTTPBadRequest(_("Unsupported Content-Type")) - controller_method = getattr(self.controller, action) - result = controller_method(req=request, **action_args) + result = self.dispatch(request, action, action_args) response = self.serialize_response(accept, result) @@ -222,6 +221,10 @@ class Resource(object): return response + def dispatch(self, request, action, action_args): + controller_method = getattr(self.controller, action) + return controller_method(req=request, **action_args) + def serialize_response(self, content_type, response_body): """Serialize a dict into a string and wrap in a wsgi.Request object. @@ -253,7 +256,7 @@ class Resource(object): """ action_args = self.get_action_args(request.environ) - action = action_args.pop('action') + action = action_args.pop('action', None) if request.method.lower() in ('post', 'put'): if len(request.body) == 0: @@ -275,14 +278,18 @@ class Resource(object): return request.best_match_content_type() def get_action_args(self, request_environment): - args = request_environment['wsgiorg.routing_args'][1].copy() + try: + args = request_environment['wsgiorg.routing_args'][1].copy() + + del args['controller'] - del args['controller'] + if 'format' in args: + del args['format'] - if 'format' in args: - del args['format'] + return args - return args + except KeyError: + return {} def get_deserializer(self, content_type): try: -- cgit From 10816023a71cca189fb77a1989e3dd542a0e9c25 Mon Sep 17 00:00:00 2001 From: Mark Washenberger Date: Thu, 19 May 2011 14:08:15 -0400 Subject: waldon's naming feedback --- nova/api/openstack/limits.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/limits.py b/nova/api/openstack/limits.py index 032a5ff2f..bd0250a7f 100644 --- a/nova/api/openstack/limits.py +++ b/nova/api/openstack/limits.py @@ -66,7 +66,7 @@ class LimitsController(common.OpenstackController): Return all global and rate limit information. """ context = req.environ['nova.context'] - abs_limits = quota.get_quota(context, context.project_id) + abs_limits = quota.get_project_quotas(context, context.project_id) rate_limits = req.environ.get("nova.limits", []) builder = self._get_view_builder(req) -- cgit From 68426df2287c24efc3d327d12371911ac29d117e Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Thu, 19 May 2011 16:16:06 -0400 Subject: further refactoring of wsgi module; adding documentation and tests --- nova/api/direct.py | 4 +- nova/api/openstack/accounts.py | 2 +- nova/api/openstack/backup_schedules.py | 4 +- nova/api/openstack/consoles.py | 2 +- nova/api/openstack/extensions.py | 8 +- nova/api/openstack/faults.py | 12 +- nova/api/openstack/flavors.py | 2 +- nova/api/openstack/image_metadata.py | 2 +- nova/api/openstack/images.py | 4 +- nova/api/openstack/ips.py | 4 +- nova/api/openstack/limits.py | 4 +- nova/api/openstack/server_metadata.py | 2 +- nova/api/openstack/servers.py | 11 +- nova/api/openstack/users.py | 2 +- nova/api/openstack/versions.py | 5 +- nova/api/openstack/wsgi.py | 301 +++++++++++++++++++++------------ nova/api/openstack/zones.py | 4 +- 17 files changed, 227 insertions(+), 146 deletions(-) (limited to 'nova/api') diff --git a/nova/api/direct.py b/nova/api/direct.py index 5e6c7c882..ea20042a7 100644 --- a/nova/api/direct.py +++ b/nova/api/direct.py @@ -291,8 +291,8 @@ class ServiceWrapper(object): try: content_type = req.best_match_content_type() serializer = { - 'application/xml': nova.api.openstack.wsgi.XMLSerializer(), - 'application/json': nova.api.openstack.wsgi.JSONSerializer(), + 'application/xml': nova.api.openstack.wsgi.XMLDictSerializer(), + 'application/json': nova.api.openstack.wsgi.JSONDictSerializer(), }[content_type] return serializer.serialize(result) except: diff --git a/nova/api/openstack/accounts.py b/nova/api/openstack/accounts.py index d8a9d1909..faff8bb2c 100644 --- a/nova/api/openstack/accounts.py +++ b/nova/api/openstack/accounts.py @@ -88,7 +88,7 @@ def resource_factory(): } serializers = { - 'application/xml': wsgi.XMLSerializer(metadata=metadata), + 'application/xml': wsgi.XMLDictSerializer(metadata=metadata), } return wsgi.Resource(Controller(), serializers=serializers) diff --git a/nova/api/openstack/backup_schedules.py b/nova/api/openstack/backup_schedules.py index 4153c90c1..d08a4799c 100644 --- a/nova/api/openstack/backup_schedules.py +++ b/nova/api/openstack/backup_schedules.py @@ -60,8 +60,8 @@ def resource_factory(): } serializers = { - 'application/xml': wsgi.XMLSerializer(xmlns=wsgi.XMLNS_V10, - metadata=metadata), + 'application/xml': wsgi.XMLDictSerializer(xmlns=wsgi.XMLNS_V10, + metadata=metadata), } return wsgi.Resource(Controller(), serializers=serializers) diff --git a/nova/api/openstack/consoles.py b/nova/api/openstack/consoles.py index 97304affe..56f79db60 100644 --- a/nova/api/openstack/consoles.py +++ b/nova/api/openstack/consoles.py @@ -97,7 +97,7 @@ def resource_factory(): } serializers = { - 'application/xml': wsgi.XMLSerializer(metadata=metadata), + 'application/xml': wsgi.XMLDictSerializer(metadata=metadata), } return wsgi.Resource(Controller(), serializers=serializers) diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py index 73f174e07..19147bbea 100644 --- a/nova/api/openstack/extensions.py +++ b/nova/api/openstack/extensions.py @@ -165,8 +165,8 @@ class ResponseExtensionController(object): except AttributeError: default_xmlns = None serializer = { - 'application/xml': wsgi.XMLSerializer(), - 'application/json': wsgi.JSONSerializer(), + 'application/xml': wsgi.XMLDictSerializer(), + 'application/json': wsgi.JSONDictSerializer(), }[content_type] body = serializer.serialize(res) headers = {"Content-Type": content_type} @@ -229,7 +229,7 @@ class ExtensionMiddleware(base_wsgi.Middleware): return _factory def _action_ext_resources(self, application, ext_mgr, mapper): - """Return a dict of ActionExtensionResource objects by collection.""" + """Return a dict of ActionExtensionResource-s by collection.""" action_resources = {} for action in ext_mgr.get_actions(): if not action.collection in action_resources.keys(): @@ -248,7 +248,7 @@ class ExtensionMiddleware(base_wsgi.Middleware): return action_resources def _response_ext_resources(self, application, ext_mgr, mapper): - """Returns a dict of ResponseExtensionResource objects by collection.""" + """Returns a dict of ResponseExtensionResource-s by collection.""" response_ext_resources = {} for resp_ext in ext_mgr.get_response_extensions(): if not resp_ext.key in response_ext_resources.keys(): diff --git a/nova/api/openstack/faults.py b/nova/api/openstack/faults.py index fd36f8f17..b9a23c126 100644 --- a/nova/api/openstack/faults.py +++ b/nova/api/openstack/faults.py @@ -61,9 +61,9 @@ class Fault(webob.exc.HTTPException): content_type = req.best_match_content_type() serializer = { - 'application/xml': wsgi.XMLSerializer(metadata=metadata, - xmlns=wsgi.XMLNS_V10), - 'application/json': wsgi.JSONSerializer(), + 'application/xml': wsgi.XMLDictSerializer(metadata=metadata, + xmlns=wsgi.XMLNS_V10), + 'application/json': wsgi.JSONDictSerializer(), }[content_type] self.wrapped_exc.body = serializer.serialize(fault_data) @@ -100,9 +100,9 @@ class OverLimitFault(webob.exc.HTTPException): metadata = {"attributes": {"overLimitFault": "code"}} serializer = { - 'application/xml': wsgi.XMLSerializer(metadata=metadata, - xmlns=wsgi.XMLNS_V10), - 'application/json': wsgi.JSONSerializer(), + 'application/xml': wsgi.XMLDictSerializer(metadata=metadata, + xmlns=wsgi.XMLNS_V10), + 'application/json': wsgi.JSONDictSerializer(), }[content_type] content = serializer.serialize(self.content) diff --git a/nova/api/openstack/flavors.py b/nova/api/openstack/flavors.py index 46056a27a..9e98e6c27 100644 --- a/nova/api/openstack/flavors.py +++ b/nova/api/openstack/flavors.py @@ -86,7 +86,7 @@ def resource_factory(version='1.0'): }[version] serializers = { - 'application/xml': wsgi.XMLSerializer(xmlns=xmlns), + 'application/xml': wsgi.XMLDictSerializer(xmlns=xmlns), } return wsgi.Resource(controller, serializers=serializers) diff --git a/nova/api/openstack/image_metadata.py b/nova/api/openstack/image_metadata.py index 506b63acf..8acde9fe8 100644 --- a/nova/api/openstack/image_metadata.py +++ b/nova/api/openstack/image_metadata.py @@ -104,7 +104,7 @@ class Controller(object): def resource_factory(): serializers = { - 'application/xml': wsgi.XMLSerializer(xmlns=wsgi.XMLNS_V11), + 'application/xml': wsgi.XMLDictSerializer(xmlns=wsgi.XMLNS_V11), } return wsgi.Resource(Controller(), serializers=serializers) diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index 5a03573d8..a9071ed8a 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -165,8 +165,8 @@ def resource_factory(version='1.0'): } serializers = { - 'application/xml': wsgi.XMLSerializer(xmlns=xmlns, - metadata=metadata), + 'application/xml': wsgi.XMLDictSerializer(xmlns=xmlns, + metadata=metadata), } return wsgi.Resource(controller, serializers=serializers) diff --git a/nova/api/openstack/ips.py b/nova/api/openstack/ips.py index 24612eafb..87c8c997a 100644 --- a/nova/api/openstack/ips.py +++ b/nova/api/openstack/ips.py @@ -72,8 +72,8 @@ def resource_factory(): } serializers = { - 'application/xml': wsgi.XMLSerializer(metadata=metadata, - xmlns=wsgi.XMLNS_V10), + 'application/xml': wsgi.XMLDictSerializer(metadata=metadata, + xmlns=wsgi.XMLNS_V10), } return wsgi.Resource(Controller(), serializers=serializers) diff --git a/nova/api/openstack/limits.py b/nova/api/openstack/limits.py index 306048d8f..b0e093702 100644 --- a/nova/api/openstack/limits.py +++ b/nova/api/openstack/limits.py @@ -95,8 +95,8 @@ def resource_factory(version='1.0'): } serializers = { - 'application/xml': wsgi.XMLSerializer(xmlns=xmlns, - metadata=metadata) + 'application/xml': wsgi.XMLDictSerializer(xmlns=xmlns, + metadata=metadata) } return wsgi.Resource(controller, serializers=serializers) diff --git a/nova/api/openstack/server_metadata.py b/nova/api/openstack/server_metadata.py index fb9449b4c..eff98c060 100644 --- a/nova/api/openstack/server_metadata.py +++ b/nova/api/openstack/server_metadata.py @@ -93,7 +93,7 @@ class Controller(object): def resource_factory(): serializers = { - 'application/xml': wsgi.XMLSerializer(xmlns=wsgi.XMLNS_V11), + 'application/xml': wsgi.XMLDictSerializer(xmlns=wsgi.XMLNS_V11), } return wsgi.Resource(Controller(), serializers=serializers) diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 78f8bb1b7..8f39bd256 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -707,7 +707,7 @@ class ControllerV11(Controller): return common.XML_NS_V11 -class ServerCreateRequestXMLDeserializer(object): +class ServerXMLDeserializer(wsgi.XMLDeserializer): """ Deserializer to handle xml-formatted server create requests. @@ -715,7 +715,7 @@ class ServerCreateRequestXMLDeserializer(object): and personality attributes """ - def deserialize(self, string): + def create(self, string): """Deserialize an xml-formatted server create request""" dom = minidom.parseString(string) server = self._extract_server(dom) @@ -812,14 +812,13 @@ def resource_factory(version='1.0'): }[version] serializers = { - 'application/xml': wsgi.XMLSerializer(metadata=metadata, - xmlns=xmlns), + 'application/xml': wsgi.XMLDictSerializer(metadata=metadata, + xmlns=xmlns), } deserializers = { - 'application/xml': ServerCreateRequestXMLDeserializer(), + 'application/xml': ServerXMLDeserializer(), } return wsgi.Resource(controller, serializers=serializers, deserializers=deserializers) - diff --git a/nova/api/openstack/users.py b/nova/api/openstack/users.py index 35b6a502e..e14616349 100644 --- a/nova/api/openstack/users.py +++ b/nova/api/openstack/users.py @@ -106,7 +106,7 @@ def resource_factory(): } serializers = { - 'application/xml': wsgi.XMLSerializer(metadata=metadata), + 'application/xml': wsgi.XMLDictSerializer(metadata=metadata), } return wsgi.Resource(Controller(), serializers=serializers) diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py index a8d785b52..9db160102 100644 --- a/nova/api/openstack/versions.py +++ b/nova/api/openstack/versions.py @@ -18,12 +18,11 @@ import webob import webob.dec -from nova import wsgi as base_wsgi import nova.api.openstack.views.versions from nova.api.openstack import wsgi -class Versions(wsgi.Resource, base_wsgi.Application): +class Versions(wsgi.Resource): def __init__(self): metadata = { "attributes": { @@ -33,7 +32,7 @@ class Versions(wsgi.Resource, base_wsgi.Application): } serializers = { - 'application/xml': wsgi.XMLSerializer(metadata=metadata), + 'application/xml': wsgi.XMLDictSerializer(metadata=metadata), } super(Versions, self).__init__(None, serializers=serializers) diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index 97280c365..bd840a6f7 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -6,6 +6,7 @@ from xml.dom import minidom from nova import exception from nova import log as logging from nova import utils +from nova import wsgi XMLNS_V10 = 'http://docs.rackspacecloud.com/servers/api/v1.0' @@ -15,17 +16,17 @@ LOG = logging.getLogger('nova.api.openstack.wsgi') class Request(webob.Request): - def best_match_content_type(self, supported=None): - """Determine the requested content-type. + """Add some Openstack API-specific logic to the base webob.Request.""" - Based on the query extension then the Accept header. + def best_match_content_type(self): + """Determine the requested response content-type. - :param supported: list of content-types to override defaults + Based on the query extension then the Accept header. """ - supported = supported or ['application/json', 'application/xml'] - parts = self.path.rsplit('.', 1) + supported = ('application/json', 'application/xml') + parts = self.path.rsplit('.', 1) if len(parts) > 1: ctype = 'application/{0}'.format(parts[1]) if ctype in supported: @@ -33,32 +34,52 @@ class Request(webob.Request): bm = self.accept.best_match(supported) + # default to application/json if we don't find a preference return bm or 'application/json' def get_content_type(self): + """Determine content type of the request body. + + Does not do any body introspection, only checks header + + """ if not "Content-Type" in self.headers: raise exception.InvalidContentType(content_type=None) allowed_types = ("application/xml", "application/json") - type = self.content_type + content_type = self.content_type - if type not in allowed_types: - raise exception.InvalidContentType(content_type=type) + if content_type not in allowed_types: + raise exception.InvalidContentType(content_type=content_type) else: - return type + return content_type -class JSONDeserializer(object): - def deserialize(self, datastring): - return utils.loads(datastring) +class TextDeserializer(object): + """Custom request body deserialization based on controller action name.""" + def deserialize(self, datastring, action=None): + """Find local deserialization method and parse request body.""" + try: + action_method = getattr(self, action) + except Exception: + action_method = self.default -class JSONSerializer(object): - def serialize(self, data): - return utils.dumps(data) + return action_method(datastring) + def default(self, datastring): + """Default deserialization code should live here""" + raise NotImplementedError() + + +class JSONDeserializer(TextDeserializer): + + def default(self, datastring): + return utils.loads(datastring) + + +class XMLDeserializer(TextDeserializer): -class XMLDeserializer(object): def __init__(self, metadata=None): """ :param metadata: information needed to deserialize xml into @@ -67,8 +88,7 @@ class XMLDeserializer(object): super(XMLDeserializer, self).__init__() self.metadata = metadata or {} - def deserialize(self, datastring): - """XML deserialization entry point.""" + def default(self, datastring): plurals = set(self.metadata.get('plurals', {})) node = minidom.parseString(datastring).childNodes[0] return {node.nodeName: self._from_xml_node(node, plurals)} @@ -95,18 +115,111 @@ class XMLDeserializer(object): return result -class XMLSerializer(object): +class RequestDeserializer(object): + """Break up a Request object into more useful pieces.""" + + def __init__(self, deserializers=None): + """ + :param deserializers: dictionary of content-type-specific deserializers + + """ + self.deserializers = { + 'application/xml': XMLDeserializer(), + 'application/json': JSONDeserializer(), + } + + self.deserializers.update(deserializers or {}) + + def deserialize(self, request): + """Extract necessary pieces of the request. + + :param request: Request object + :returns tuple of expected controller action name, dictionary of + keyword arguments to pass to the controller, the expected + content type of the response + + """ + action_args = self.get_action_args(request.environ) + action = action_args.pop('action', None) + + if request.method.lower() in ('post', 'put'): + if len(request.body) == 0: + action_args['body'] = None + else: + content_type = request.get_content_type() + deserializer = self.get_deserializer(content_type) + + try: + body = deserializer.deserialize(request.body, action) + action_args['body'] = body + except exception.InvalidContentType: + action_args['body'] = None + + accept = self.get_expected_content_type(request) + + return (action, action_args, accept) + + def get_deserializer(self, content_type): + try: + return self.deserializers[content_type] + except Exception: + raise exception.InvalidContentType(content_type=content_type) + + def get_expected_content_type(self, request): + return request.best_match_content_type() + + def get_action_args(self, request_environment): + """Parse dictionary created by routes library.""" + try: + args = request_environment['wsgiorg.routing_args'][1].copy() + + del args['controller'] + + if 'format' in args: + del args['format'] + + return args + + except KeyError: + return {} + + +class DictSerializer(object): + """Custom response body serialization based on controller action name.""" + + def serialize(self, data, action=None): + """Find local serialization method and encode response body.""" + try: + action_method = getattr(self, action) + except Exception: + action_method = self.default + + return action_method(data) + + def default(self, data): + """Default serialization code should live here""" + raise NotImplementedError() + + +class JSONDictSerializer(DictSerializer): + + def default(self, data): + return utils.dumps(data) + + +class XMLDictSerializer(DictSerializer): + def __init__(self, metadata=None, xmlns=None): """ :param metadata: information needed to deserialize xml into a dictionary. :param xmlns: XML namespace to include with serialized xml """ - super(XMLSerializer, self).__init__() + super(XMLDictSerializer, self).__init__() self.metadata = metadata or {} self.xmlns = xmlns - def serialize(self, data): + def default(self, data): # We expect data to contain a single key which is the XML root. root_key = data.keys()[0] doc = minidom.Document() @@ -171,75 +284,32 @@ class XMLSerializer(object): return result -class Resource(object): - """WSGI app that dispatched to methods. +class ResponseSerializer(object): + """Encode the necessary pieces into a response object""" - WSGI app that reads routing information supplied by RoutesMiddleware - and calls the requested action method upon itself. All action methods - must, in addition to their normal parameters, accept a 'req' argument - which is the incoming wsgi.Request. They raise a webob.exc exception, - or return a dict which will be serialized by requested content type. + def __init__(self, serializers=None): + """ + :param serializers: dictionary of content-type-specific serializers - """ - def __init__(self, controller, serializers=None, deserializers=None): + """ self.serializers = { - 'application/xml': XMLSerializer(), - 'application/json': JSONSerializer(), + 'application/xml': XMLDictSerializer(), + 'application/json': JSONDictSerializer(), } self.serializers.update(serializers or {}) - self.deserializers = { - 'application/xml': XMLDeserializer(), - 'application/json': JSONDeserializer(), - } - self.deserializers.update(deserializers or {}) - - self.controller = controller - - @webob.dec.wsgify(RequestClass=Request) - def __call__(self, request): - """Call the method specified in req.environ by RoutesMiddleware.""" - LOG.debug("%s %s" % (request.method, request.url)) - - try: - action, action_args, accept = self.deserialize_request(request) - except exception.InvalidContentType: - return webob.exc.HTTPBadRequest(_("Unsupported Content-Type")) - - result = self.dispatch(request, action, action_args) - - response = self.serialize_response(accept, result) - - try: - msg_dict = dict(url=request.url, status=response.status_int) - msg = _("%(url)s returned with HTTP %(status)d") % msg_dict - except AttributeError: - msg_dict = dict(url=request.url) - msg = _("%(url)s returned a fault") - - LOG.debug(msg) - - return response - - def dispatch(self, request, action, action_args): - controller_method = getattr(self.controller, action) - return controller_method(req=request, **action_args) - - def serialize_response(self, content_type, response_body): + def serialize(self, response_data, content_type): """Serialize a dict into a string and wrap in a wsgi.Request object. + :param response_data: dict produced by the Controller :param content_type: expected mimetype of serialized response body - :param response_body: dict produced by the Controller """ - if not type(response_body) is dict: - return response_body - response = webob.Response() response.headers['Content-Type'] = content_type serializer = self.get_serializer(content_type) - response.body = serializer.serialize(response_body) + response.body = serializer.serialize(response_data) return response @@ -249,50 +319,63 @@ class Resource(object): except Exception: raise exception.InvalidContentType(content_type=content_type) - def deserialize_request(self, request): - """Parse a wsgi request into a set of params we care about. - :param request: wsgi.Request object +class Resource(wsgi.Application): + """WSGI app that handles (de)serialization and controller dispatch. + + WSGI app that reads routing information supplied by RoutesMiddleware + and calls the requested action method upon its controller. All + controller action methods must accept a 'req' argument, which is the + incoming wsgi.Request. If the operation is a PUT or POST, the controller + method must also accept a 'body' argument (the deserialized request body). + They may raise a webob.exc exception or return a dict, which will be + serialized by requested content type. + """ + def __init__(self, controller, serializers=None, deserializers=None): """ - action_args = self.get_action_args(request.environ) - action = action_args.pop('action', None) + :param controller: object that implement methods created by routes lib + :param serializers: dict of content-type specific text serializers + :param deserializers: dict of content-type specific text deserializers - if request.method.lower() in ('post', 'put'): - if len(request.body) == 0: - action_args['body'] = None - else: - content_type = request.get_content_type() - deserializer = self.get_deserializer(content_type) + """ + self.controller = controller + self.serializer = ResponseSerializer(serializers) + self.deserializer = RequestDeserializer(deserializers) - try: - action_args['body'] = deserializer.deserialize(request.body) - except exception.InvalidContentType: - action_args['body'] = None + @webob.dec.wsgify(RequestClass=Request) + def __call__(self, request): + """WSGI method that controls (de)serialization and method dispatch.""" - accept = self.get_expected_content_type(request) + LOG.debug("%s %s" % (request.method, request.url)) - return (action, action_args, accept) + try: + action, action_args, accept = self.deserializer.deserialize( + request) + except exception.InvalidContentType: + return webob.exc.HTTPBadRequest(_("Unsupported Content-Type")) - def get_expected_content_type(self, request): - return request.best_match_content_type() + action_result = self.dispatch(request, action, action_args) - def get_action_args(self, request_environment): - try: - args = request_environment['wsgiorg.routing_args'][1].copy() + #TODO(bcwaldon): find a more elegant way to pass through non-dict types + if type(action_result) is dict: + response = self.serializer.serialize(action_result, accept) + else: + response = action_result - del args['controller'] + try: + msg_dict = dict(url=request.url, status=response.status_int) + msg = _("%(url)s returned with HTTP %(status)d") % msg_dict + except AttributeError: + msg_dict = dict(url=request.url) + msg = _("%(url)s returned a fault") - if 'format' in args: - del args['format'] + LOG.debug(msg) - return args + return response - except KeyError: - return {} + def dispatch(self, request, action, action_args): + """Find action-spefic method on controller and call it.""" - def get_deserializer(self, content_type): - try: - return self.deserializers[content_type] - except Exception: - raise exception.InvalidContentType(content_type=content_type) + controller_method = getattr(self.controller, action) + return controller_method(req=request, **action_args) diff --git a/nova/api/openstack/zones.py b/nova/api/openstack/zones.py index d17ab7a9b..e750fc230 100644 --- a/nova/api/openstack/zones.py +++ b/nova/api/openstack/zones.py @@ -101,8 +101,8 @@ def resource_factory(): } serializers = { - 'application/xml': wsgi.XMLSerializer(xmlns=wsgi.XMLNS_V10, - metadata=metadata), + 'application/xml': wsgi.XMLDictSerializer(xmlns=wsgi.XMLNS_V10, + metadata=metadata), } return wsgi.Resource(Controller(), serializers=serializers) -- cgit From 1c485a515b299551c44bd4411d82be1cccf5f4bd Mon Sep 17 00:00:00 2001 From: Mark Washenberger Date: Fri, 20 May 2011 00:24:35 -0400 Subject: add absolute limits support to 1.0 api as well --- nova/api/openstack/views/limits.py | 44 +++++++++++++++++--------------------- 1 file changed, 20 insertions(+), 24 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/views/limits.py b/nova/api/openstack/views/limits.py index dd7681db4..33be12c0c 100644 --- a/nova/api/openstack/views/limits.py +++ b/nova/api/openstack/views/limits.py @@ -46,7 +46,26 @@ class ViewBuilder(object): return output def _build_absolute_limits(self, absolute_limits): - raise NotImplementedError() + """Builder for absolute limits + + absolute_limits should be given as a dict of limits. + For example: {"ram": 512, "gigabytes": 1024}. + + """ + limit_names = { + "ram": ["maxTotalRAMSize"], + "instances": ["maxTotalInstances"], + "cores": ["maxTotalCores"], + "metadata_items": ["maxServerMeta", "maxImageMeta"], + "injected_files": ["maxPersonality"], + "injected_file_content_bytes": ["maxPersonalitySize"], + } + limits = {} + for name, value in absolute_limits.iteritems(): + if name in limit_names and value is not None: + for name in limit_names[name]: + limits[name] = value + return limits def _build_rate_limits(self, rate_limits): raise NotImplementedError() @@ -72,9 +91,6 @@ class ViewBuilderV10(ViewBuilder): "resetTime": rate_limit["resetTime"], } - def _build_absolute_limits(self, absolute_limit): - return {} - class ViewBuilderV11(ViewBuilder): """Openstack API v1.1 limits view builder.""" @@ -113,23 +129,3 @@ class ViewBuilderV11(ViewBuilder): "unit": rate_limit["unit"], "next-available": rate_limit["resetTime"], } - - def _build_absolute_limits(self, absolute_limits): - """Builder for absolute limits - - absolute_limits should be given as a dict of limits. - For example: {"ram": 512, "gigabytes": 1024}. - - """ - limit_names = { - "ram": ["maxTotalRAMSize"], - "instances": ["maxTotalInstances"], - "cores": ["maxTotalCores"], - "metadata_items": ["maxServerMeta", "maxImageMeta"], - } - limits = {} - for name, value in absolute_limits.iteritems(): - if name in limit_names and value is not None: - for name in limit_names[name]: - limits[name] = value - return limits -- cgit From 3fc3b7537cc1af2783829a2caaca272e83d6d3e8 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Fri, 20 May 2011 14:42:19 -0400 Subject: renaming resource_factory to create_resource --- nova/api/openstack/__init__.py | 34 +++++++++++++++++----------------- nova/api/openstack/accounts.py | 2 +- nova/api/openstack/backup_schedules.py | 2 +- nova/api/openstack/consoles.py | 2 +- nova/api/openstack/flavors.py | 2 +- nova/api/openstack/image_metadata.py | 2 +- nova/api/openstack/images.py | 2 +- nova/api/openstack/ips.py | 2 +- nova/api/openstack/limits.py | 2 +- nova/api/openstack/server_metadata.py | 2 +- nova/api/openstack/servers.py | 2 +- nova/api/openstack/shared_ip_groups.py | 2 +- nova/api/openstack/users.py | 2 +- nova/api/openstack/zones.py | 2 +- 14 files changed, 30 insertions(+), 30 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index fbbd99cb9..4419d0748 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -99,19 +99,19 @@ class APIRouter(base_wsgi.Router): server_members['inject_network_info'] = 'POST' mapper.resource("zone", "zones", - controller=zones.resource_factory(), + controller=zones.create_resource(), collection={'detail': 'GET', 'info': 'GET'}), mapper.resource("user", "users", - controller=users.resource_factory(), + controller=users.create_resource(), collection={'detail': 'GET'}) mapper.resource("account", "accounts", - controller=accounts.resource_factory(), + controller=accounts.create_resource(), collection={'detail': 'GET'}) mapper.resource("console", "consoles", - controller=consoles.resource_factory(), + controller=consoles.create_resource(), parent_resource=dict(member_name='server', collection_name='servers')) @@ -124,31 +124,31 @@ class APIRouterV10(APIRouter): def _setup_routes(self, mapper): super(APIRouterV10, self)._setup_routes(mapper) mapper.resource("server", "servers", - controller=servers.resource_factory('1.0'), + controller=servers.create_resource('1.0'), collection={'detail': 'GET'}, member=self.server_members) mapper.resource("image", "images", - controller=images.resource_factory('1.0'), + controller=images.create_resource('1.0'), collection={'detail': 'GET'}) mapper.resource("flavor", "flavors", - controller=flavors.resource_factory('1.0'), + controller=flavors.create_resource('1.0'), collection={'detail': 'GET'}) mapper.resource("shared_ip_group", "shared_ip_groups", collection={'detail': 'GET'}, - controller=shared_ip_groups.resource_factory()) + controller=shared_ip_groups.create_resource()) mapper.resource("backup_schedule", "backup_schedule", - controller=backup_schedules.resource_factory(), + controller=backup_schedules.create_resource(), parent_resource=dict(member_name='server', collection_name='servers')) mapper.resource("limit", "limits", - controller=limits.resource_factory('1.0')) + controller=limits.create_resource('1.0')) - mapper.resource("ip", "ips", controller=ips.resource_factory(), + mapper.resource("ip", "ips", controller=ips.create_resource(), collection=dict(public='GET', private='GET'), parent_resource=dict(member_name='server', collection_name='servers')) @@ -160,27 +160,27 @@ class APIRouterV11(APIRouter): def _setup_routes(self, mapper): super(APIRouterV11, self)._setup_routes(mapper) mapper.resource("server", "servers", - controller=servers.resource_factory('1.1'), + controller=servers.create_resource('1.1'), collection={'detail': 'GET'}, member=self.server_members) mapper.resource("image", "images", - controller=images.resource_factory('1.1'), + controller=images.create_resource('1.1'), collection={'detail': 'GET'}) mapper.resource("image_meta", "meta", - controller=image_metadata.resource_factory(), + controller=image_metadata.create_resource(), parent_resource=dict(member_name='image', collection_name='images')) mapper.resource("server_meta", "meta", - controller=server_metadata.resource_factory(), + controller=server_metadata.create_resource(), parent_resource=dict(member_name='server', collection_name='servers')) mapper.resource("flavor", "flavors", - controller=flavors.resource_factory('1.1'), + controller=flavors.create_resource('1.1'), collection={'detail': 'GET'}) mapper.resource("limit", "limits", - controller=limits.resource_factory('1.1')) + controller=limits.create_resource('1.1')) diff --git a/nova/api/openstack/accounts.py b/nova/api/openstack/accounts.py index faff8bb2c..0dcd37217 100644 --- a/nova/api/openstack/accounts.py +++ b/nova/api/openstack/accounts.py @@ -80,7 +80,7 @@ class Controller(object): return dict(account=_translate_keys(account)) -def resource_factory(): +def create_resource(): metadata = { "attributes": { "account": ["id", "name", "description", "manager"], diff --git a/nova/api/openstack/backup_schedules.py b/nova/api/openstack/backup_schedules.py index d08a4799c..71a14d4ce 100644 --- a/nova/api/openstack/backup_schedules.py +++ b/nova/api/openstack/backup_schedules.py @@ -52,7 +52,7 @@ class Controller(object): return faults.Fault(exc.HTTPNotImplemented()) -def resource_factory(): +def create_resource(): metadata = { 'attributes': { 'backupSchedule': [], diff --git a/nova/api/openstack/consoles.py b/nova/api/openstack/consoles.py index 56f79db60..bccf04d8f 100644 --- a/nova/api/openstack/consoles.py +++ b/nova/api/openstack/consoles.py @@ -89,7 +89,7 @@ class Controller(object): return exc.HTTPAccepted() -def resource_factory(): +def create_resource(): metadata = { 'attributes': { 'console': [], diff --git a/nova/api/openstack/flavors.py b/nova/api/openstack/flavors.py index 9e98e6c27..a21ff6cb2 100644 --- a/nova/api/openstack/flavors.py +++ b/nova/api/openstack/flavors.py @@ -74,7 +74,7 @@ class ControllerV11(Controller): return views.flavors.ViewBuilderV11(base_url) -def resource_factory(version='1.0'): +def create_resource(version='1.0'): controller = { '1.0': ControllerV10, '1.1': ControllerV11, diff --git a/nova/api/openstack/image_metadata.py b/nova/api/openstack/image_metadata.py index 8acde9fe8..88e10168d 100644 --- a/nova/api/openstack/image_metadata.py +++ b/nova/api/openstack/image_metadata.py @@ -102,7 +102,7 @@ class Controller(object): self.image_service.update(context, image_id, img, None) -def resource_factory(): +def create_resource(): serializers = { 'application/xml': wsgi.XMLDictSerializer(xmlns=wsgi.XMLNS_V11), } diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index a9071ed8a..3376f358a 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -145,7 +145,7 @@ class ControllerV11(Controller): return common.XML_NS_V11 -def resource_factory(version='1.0'): +def create_resource(version='1.0'): controller = { '1.0': ControllerV10, '1.1': ControllerV11, diff --git a/nova/api/openstack/ips.py b/nova/api/openstack/ips.py index 87c8c997a..abea71830 100644 --- a/nova/api/openstack/ips.py +++ b/nova/api/openstack/ips.py @@ -63,7 +63,7 @@ class Controller(object): return faults.Fault(exc.HTTPNotImplemented()) -def resource_factory(): +def create_resource(): metadata = { 'list_collections': { 'public': {'item_name': 'ip', 'item_key': 'addr'}, diff --git a/nova/api/openstack/limits.py b/nova/api/openstack/limits.py index b0e093702..2d9fe356f 100644 --- a/nova/api/openstack/limits.py +++ b/nova/api/openstack/limits.py @@ -73,7 +73,7 @@ class LimitsControllerV11(LimitsController): return limits_views.ViewBuilderV11() -def resource_factory(version='1.0'): +def create_resource(version='1.0'): controller = { '1.0': LimitsControllerV10, '1.1': LimitsControllerV11, diff --git a/nova/api/openstack/server_metadata.py b/nova/api/openstack/server_metadata.py index eff98c060..b38b84a2a 100644 --- a/nova/api/openstack/server_metadata.py +++ b/nova/api/openstack/server_metadata.py @@ -91,7 +91,7 @@ class Controller(object): raise error -def resource_factory(): +def create_resource(): serializers = { 'application/xml': wsgi.XMLDictSerializer(xmlns=wsgi.XMLNS_V11), } diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 8f39bd256..bdd2960d9 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -784,7 +784,7 @@ class ServerXMLDeserializer(wsgi.XMLDeserializer): return "" -def resource_factory(version='1.0'): +def create_resource(version='1.0'): controller = { '1.0': ControllerV10, '1.1': ControllerV11, diff --git a/nova/api/openstack/shared_ip_groups.py b/nova/api/openstack/shared_ip_groups.py index db178f2a2..4f11f8dfb 100644 --- a/nova/api/openstack/shared_ip_groups.py +++ b/nova/api/openstack/shared_ip_groups.py @@ -49,5 +49,5 @@ class Controller(object): raise faults.Fault(exc.HTTPNotImplemented()) -def resource_factory(): +def create_resource(): return wsgi.Resource(Controller()) diff --git a/nova/api/openstack/users.py b/nova/api/openstack/users.py index e14616349..50975fc1f 100644 --- a/nova/api/openstack/users.py +++ b/nova/api/openstack/users.py @@ -98,7 +98,7 @@ class Controller(object): return dict(user=_translate_keys(self.manager.get_user(id))) -def resource_factory(): +def create_resource(): metadata = { "attributes": { "user": ["id", "name", "access", "secret", "admin"], diff --git a/nova/api/openstack/zones.py b/nova/api/openstack/zones.py index e750fc230..0475deb52 100644 --- a/nova/api/openstack/zones.py +++ b/nova/api/openstack/zones.py @@ -93,7 +93,7 @@ class Controller(object): return dict(zone=_scrub_zone(zone)) -def resource_factory(): +def create_resource(): metadata = { "attributes": { "zone": ["id", "api_url", "name", "capabilities"], -- cgit From 2c16eb37822b3ebdb14ac36df26362636d0f5078 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Fri, 20 May 2011 16:36:10 -0400 Subject: minor cleanup --- nova/api/openstack/images.py | 3 --- nova/api/openstack/servers.py | 3 --- 2 files changed, 6 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index 3376f358a..7f5551664 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -141,9 +141,6 @@ class ControllerV11(Controller): base_url = request.application_url return images_view.ViewBuilderV11(base_url) - def get_default_xmlns(self, req): - return common.XML_NS_V11 - def create_resource(version='1.0'): controller = { diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index bdd2960d9..313321d7d 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -703,9 +703,6 @@ class ControllerV11(Controller): raise exc.HTTPBadRequest(msg) return password - def get_default_xmlns(self, req): - return common.XML_NS_V11 - class ServerXMLDeserializer(wsgi.XMLDeserializer): """ -- cgit From d72815193d64b9dcce974888bef05a18689c0504 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Mon, 23 May 2011 10:37:28 -0400 Subject: Fixed mistyped line --- nova/api/openstack/views/limits.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/views/limits.py b/nova/api/openstack/views/limits.py index 22d1c260d..02fa0fdd6 100644 --- a/nova/api/openstack/views/limits.py +++ b/nova/api/openstack/views/limits.py @@ -79,7 +79,7 @@ class ViewBuilderV11(ViewBuilder): # check for existing key for limit in limits: if limit["uri"] == rate_limit["URI"] and \ - limit["regex"] == limit["regex"]: + limit["regex"] == rate_limit["regex"]: _rate_limit_key = limit break -- cgit From f488576ae27f8eb96a04022d0ecd11a28bd15116 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Tue, 24 May 2011 16:44:28 -0400 Subject: Added filtering on image properties --- nova/api/openstack/images.py | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index 34d4c27fc..755ce8ead 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -28,6 +28,9 @@ from nova.api.openstack.views import images as images_view LOG = log.getLogger('nova.api.openstack.images') FLAGS = flags.FLAGS +SUPPORTED_FILTERS = ['name', 'status', 'container_format', 'disk_format', + 'size_min', 'size_max'] + class Controller(common.OpenstackController): """Base `wsgi.Controller` for retrieving/displaying images.""" @@ -59,7 +62,8 @@ class Controller(common.OpenstackController): :param req: `wsgi.Request` object """ context = req.environ['nova.context'] - images = self._image_service.index(context) + filters = self._get_filters(req) + images = self._image_service.index(context, filters) images = common.limited(images, req) builder = self.get_builder(req).build return dict(images=[builder(image, detail=False) for image in images]) @@ -70,11 +74,26 @@ class Controller(common.OpenstackController): :param req: `wsgi.Request` object. """ context = req.environ['nova.context'] - images = self._image_service.detail(context) + filters = self._get_filters(req) + images = self._image_service.detail(context, filters) images = common.limited(images, req) builder = self.get_builder(req).build return dict(images=[builder(image, detail=True) for image in images]) + def _get_filters(self, req): + """ + Return a dictionary of query param filters from the request + + :param req: the Request object coming from the wsgi layer + :retval a dict of key/value filters + """ + filters = {} + for param in req.str_params: + if param in SUPPORTED_FILTERS or param.startswith('property-'): + filters[param] = req.str_params.get(param) + + return filters + def show(self, req, id): """Return detailed information about a specific image. -- cgit From e0aa1369d8050f023fee1e60b276d44a6298feb9 Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Tue, 24 May 2011 21:09:43 -0700 Subject: instead of the API spawning a greenthread to wait for a host to be picked, the instance to boot, etc for setting the admin password... let's push the admin password down to the scheduler so that compute can just take care of setting the password as a part of the build process. --- nova/api/openstack/servers.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index fcb630fae..789c69977 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -180,7 +180,8 @@ class Controller(common.OpenstackController): key_name=key_name, key_data=key_data, metadata=env['server'].get('metadata', {}), - injected_files=injected_files) + injected_files=injected_files, + admin_password=password) except quota.QuotaError as error: self._handle_quota_error(error) @@ -190,8 +191,6 @@ class Controller(common.OpenstackController): builder = self._get_view_builder(req) server = builder.build(inst, is_detail=True) server['server']['adminPass'] = password - self.compute_api.set_admin_password(context, server['server']['id'], - password) return server def _deserialize_create(self, request): -- cgit From 17abaeafaf3fed2847e4377a16b47771eb663304 Mon Sep 17 00:00:00 2001 From: MORITA Kazutaka Date: Wed, 25 May 2011 16:27:28 +0900 Subject: Fix wrong call of the volume api create() --- nova/api/openstack/contrib/volumes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/contrib/volumes.py b/nova/api/openstack/contrib/volumes.py index 18de2ec71..b22bd2846 100644 --- a/nova/api/openstack/contrib/volumes.py +++ b/nova/api/openstack/contrib/volumes.py @@ -135,7 +135,7 @@ class VolumeController(wsgi.Controller): vol = env['volume'] size = vol['size'] LOG.audit(_("Create volume of %s GB"), size, context=context) - new_volume = self.volume_api.create(context, size, + new_volume = self.volume_api.create(context, size, None, vol.get('display_name'), vol.get('display_description')) -- cgit From e4bf97ba29e8e5858f37cedb34e20ccd8e210bae Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Wed, 25 May 2011 12:24:27 -0400 Subject: Updated tests to use mox pep8 --- nova/api/openstack/images.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index 755ce8ead..553566d58 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -93,7 +93,7 @@ class Controller(common.OpenstackController): filters[param] = req.str_params.get(param) return filters - + def show(self, req, id): """Return detailed information about a specific image. -- cgit From b933f90faecaddf7281455f4824577b586e07f0c Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Wed, 25 May 2011 17:55:51 +0000 Subject: updating admin_pass moved down to compute where the password is actually reset. only update if it succeeds. --- nova/api/openstack/servers.py | 1 - 1 file changed, 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 789c69977..5c10fc916 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -607,7 +607,6 @@ class ControllerV10(Controller): def _parse_update(self, context, server_id, inst_dict, update_dict): if 'adminPass' in inst_dict['server']: - update_dict['admin_pass'] = inst_dict['server']['adminPass'] self.compute_api.set_admin_password(context, server_id, inst_dict['server']['adminPass']) -- cgit From f37d94428dd0b56632958d5d3a6930531a51cd44 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Thu, 26 May 2011 10:54:46 -0400 Subject: Restricted image filtering by name and status only --- nova/api/openstack/images.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index 553566d58..2e779da79 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -28,8 +28,7 @@ from nova.api.openstack.views import images as images_view LOG = log.getLogger('nova.api.openstack.images') FLAGS = flags.FLAGS -SUPPORTED_FILTERS = ['name', 'status', 'container_format', 'disk_format', - 'size_min', 'size_max'] +SUPPORTED_FILTERS = ['name', 'status'] class Controller(common.OpenstackController): -- cgit From 2d834fa19078c645e3c36001b5dd34fb8e708f0a Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Thu, 26 May 2011 14:09:59 -0400 Subject: review fixups --- nova/api/openstack/wsgi.py | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index bd840a6f7..5577d326f 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -62,7 +62,7 @@ class TextDeserializer(object): """Find local deserialization method and parse request body.""" try: action_method = getattr(self, action) - except Exception: + except (AttributeError, TypeError): action_method = self.default return action_method(datastring) @@ -162,7 +162,7 @@ class RequestDeserializer(object): def get_deserializer(self, content_type): try: return self.deserializers[content_type] - except Exception: + except (KeyError, TypeError): raise exception.InvalidContentType(content_type=content_type) def get_expected_content_type(self, request): @@ -172,16 +172,20 @@ class RequestDeserializer(object): """Parse dictionary created by routes library.""" try: args = request_environment['wsgiorg.routing_args'][1].copy() + except Exception: + return {} + try: del args['controller'] + except KeyError: + pass - if 'format' in args: - del args['format'] - - return args - + try: + del args['format'] except KeyError: - return {} + pass + + return args class DictSerializer(object): @@ -191,7 +195,7 @@ class DictSerializer(object): """Find local serialization method and encode response body.""" try: action_method = getattr(self, action) - except Exception: + except (AttributeError, TypeError): action_method = self.default return action_method(data) @@ -316,7 +320,7 @@ class ResponseSerializer(object): def get_serializer(self, content_type): try: return self.serializers[content_type] - except Exception: + except (KeyError, TypeError): raise exception.InvalidContentType(content_type=content_type) @@ -347,7 +351,8 @@ class Resource(wsgi.Application): def __call__(self, request): """WSGI method that controls (de)serialization and method dispatch.""" - LOG.debug("%s %s" % (request.method, request.url)) + LOG.debug("%(method)s %(url)s" % {"method": request.method, + "url": request.url}) try: action, action_args, accept = self.deserializer.deserialize( -- cgit From 3264c18fffa26b1288fc253f2526d9a78fdc9dd4 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Thu, 26 May 2011 15:01:24 -0400 Subject: cleaning up getattr calls with default param --- nova/api/openstack/wsgi.py | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index 5577d326f..7a747842e 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -58,13 +58,9 @@ class Request(webob.Request): class TextDeserializer(object): """Custom request body deserialization based on controller action name.""" - def deserialize(self, datastring, action=None): + def deserialize(self, datastring, action='default'): """Find local deserialization method and parse request body.""" - try: - action_method = getattr(self, action) - except (AttributeError, TypeError): - action_method = self.default - + action_method = getattr(self, action, self.default) return action_method(datastring) def default(self, datastring): @@ -191,13 +187,9 @@ class RequestDeserializer(object): class DictSerializer(object): """Custom response body serialization based on controller action name.""" - def serialize(self, data, action=None): + def serialize(self, data, action='default'): """Find local serialization method and encode response body.""" - try: - action_method = getattr(self, action) - except (AttributeError, TypeError): - action_method = self.default - + action_method = getattr(self, action, self.default) return action_method(data) def default(self, data): -- cgit From a79f01fcea81bb6be233a65670c6a79af8534a10 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Thu, 26 May 2011 17:27:48 -0400 Subject: adding TODOs per dabo's review --- nova/api/openstack/wsgi.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'nova/api') diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index 7a747842e..ddf4e6fa9 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -237,6 +237,7 @@ class XMLDictSerializer(DictSerializer): if xmlns: result.setAttribute('xmlns', xmlns) + #TODO(bcwaldon): accomplish this without a type-check if type(data) is list: collections = metadata.get('list_collections', {}) if nodename in collections: @@ -255,6 +256,7 @@ class XMLDictSerializer(DictSerializer): for item in data: node = self._to_xml_node(doc, metadata, singular, item) result.appendChild(node) + #TODO(bcwaldon): accomplish this without a type-check elif type(data) is dict: collections = metadata.get('dict_collections', {}) if nodename in collections: -- cgit From 8b4c91b9f2c28e4809659f199affddbd66482dbb Mon Sep 17 00:00:00 2001 From: MORITA Kazutaka Date: Fri, 27 May 2011 13:36:59 +0900 Subject: Fix pep8 violations. --- nova/api/ec2/cloud.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) (limited to 'nova/api') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 6927d6774..403b7ab40 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -285,7 +285,9 @@ class CloudController(object): snapshots = [] for ec2_id in snapshot_id: internal_id = ec2utils.ec2_id_to_id(ec2_id) - snapshot = self.volume_api.get_snapshot(context, snapshot_id=internal_id) + snapshot = self.volume_api.get_snapshot( + context, + snapshot_id=internal_id) snapshots.append(snapshot) else: snapshots = self.volume_api.get_all_snapshots(context) @@ -295,7 +297,8 @@ class CloudController(object): def _format_snapshot(self, context, snapshot): s = {} s['snapshotId'] = ec2utils.id_to_ec2_id(snapshot['id'], 'snap-%08x') - s['volumeId'] = ec2utils.id_to_ec2_id(snapshot['volume_id'], 'vol-%08x') + s['volumeId'] = ec2utils.id_to_ec2_id(snapshot['volume_id'], + 'vol-%08x') s['status'] = snapshot['status'] s['startTime'] = snapshot['created_at'] s['progress'] = snapshot['progress'] @@ -308,7 +311,8 @@ class CloudController(object): return s def create_snapshot(self, context, volume_id, **kwargs): - LOG.audit(_("Create snapshot of volume %s"), volume_id, context=context) + LOG.audit(_("Create snapshot of volume %s"), volume_id, + context=context) volume_id = ec2utils.ec2_id_to_id(volume_id) snapshot = self.volume_api.create_snapshot( context, @@ -629,7 +633,8 @@ class CloudController(object): else: v['attachmentSet'] = [{}] if volume.get('snapshot_id') != None: - v['snapshotId'] = ec2utils.id_to_ec2_id(volume['snapshot_id'], 'snap-%08x') + v['snapshotId'] = ec2utils.id_to_ec2_id(volume['snapshot_id'], + 'snap-%08x') else: v['snapshotId'] = None -- cgit From c229d6e32f5275b2eb10e760f89a52dc31635c47 Mon Sep 17 00:00:00 2001 From: MORITA Kazutaka Date: Fri, 27 May 2011 14:13:17 +0900 Subject: Fix pep8 errors. --- nova/api/ec2/cloud.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'nova/api') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index b717a10c0..79cc3b3bf 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -666,14 +666,15 @@ class CloudController(object): return v def create_volume(self, context, **kwargs): - size = kwargs.get('size'); + size = kwargs.get('size') if kwargs.get('snapshot_id') != None: snapshot_id = ec2utils.ec2_id_to_id(kwargs['snapshot_id']) - LOG.audit(_("Create volume from snapshot %s"), snapshot_id, context=context) + LOG.audit(_("Create volume from snapshot %s"), snapshot_id, + context=context) else: snapshot_id = None LOG.audit(_("Create volume of %s GB"), size, context=context) - + volume = self.volume_api.create( context, size=size, -- cgit From 394b37f8c944fbd3ca683d7752cd751bc69cce51 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Sun, 29 May 2011 00:00:02 -0400 Subject: Implement the v1.1 style resize action with support for flavorRef. --- nova/api/openstack/servers.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 5c10fc916..a3066e578 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -332,6 +332,7 @@ class Controller(common.OpenstackController): return exc.HTTPAccepted() def _action_resize(self, input_dict, req, id): + return exc.HTTPNotImplemented() """ Resizes a given instance to the flavor size requested """ try: if 'resize' in input_dict and 'flavorId' in input_dict['resize']: @@ -610,6 +611,21 @@ class ControllerV10(Controller): self.compute_api.set_admin_password(context, server_id, inst_dict['server']['adminPass']) + def _action_resize(self, input_dict, req, id): + """ Resizes a given instance to the flavor size requested """ + try: + if 'resize' in input_dict and 'flavorId' in input_dict['resize']: + flavor_id = input_dict['resize']['flavorId'] + self.compute_api.resize(req.environ['nova.context'], id, + flavor_id) + else: + LOG.exception(_("Missing arguments for resize")) + return faults.Fault(exc.HTTPUnprocessableEntity()) + except Exception, e: + LOG.exception(_("Error in resize %s"), e) + return faults.Fault(exc.HTTPBadRequest()) + return exc.HTTPAccepted() + def _action_rebuild(self, info, request, instance_id): context = request.environ['nova.context'] instance_id = int(instance_id) @@ -695,6 +711,22 @@ class ControllerV11(Controller): LOG.info(msg) raise faults.Fault(exc.HTTPBadRequest(explanation=msg)) + def _action_resize(self, input_dict, req, id): + """ Resizes a given instance to the flavor size requested """ + try: + if 'resize' in input_dict and 'flavorRef' in input_dict['resize']: + flavor_ref = input_dict['resize']['flavorRef'] + flavor_id = common.get_id_from_href(flavor_ref) + self.compute_api.resize(req.environ['nova.context'], id, + flavor_id) + else: + LOG.exception(_("Missing arguments for resize")) + return faults.Fault(exc.HTTPUnprocessableEntity()) + except Exception, e: + LOG.exception(_("Error in resize %s"), e) + return faults.Fault(exc.HTTPBadRequest()) + return exc.HTTPAccepted() + def _action_rebuild(self, info, request, instance_id): context = request.environ['nova.context'] instance_id = int(instance_id) -- cgit From 2155f2b1ab22c6183ab5266e16a675f1469fca50 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Mon, 30 May 2011 11:29:55 -0400 Subject: Updates so that 'name' can be updated when doing a OS API v1.1 rebuild. Fixed issue where metadata wasn't getting deleted when an empty dict was POST'd on a rebuild. --- nova/api/openstack/servers.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 5c10fc916..8e191c232 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -708,14 +708,16 @@ class ControllerV11(Controller): image_id = common.get_id_from_href(image_ref) personalities = info["rebuild"].get("personality", []) - metadata = info["rebuild"].get("metadata", {}) + metadata = info["rebuild"].get("metadata") + name = info["rebuild"].get("name") - self._validate_metadata(metadata) + if metadata: + self._validate_metadata(metadata) self._decode_personalities(personalities) try: - self.compute_api.rebuild(context, instance_id, image_id, metadata, - personalities) + self.compute_api.rebuild(context, instance_id, image_id, name, + metadata, personalities) except exception.BuildInProgress: msg = _("Instance %d is currently being rebuilt.") % instance_id LOG.debug(msg) -- cgit From d6cd02a07ab3b66a53689fb8edbf55db03b4bff2 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Tue, 31 May 2011 08:20:40 -0400 Subject: Actually remove the _action_resize code from the base Servers controller. The V11 and V10 controllers implement these now. --- nova/api/openstack/servers.py | 13 ------------- 1 file changed, 13 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index a3066e578..4bd7ddb14 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -333,19 +333,6 @@ class Controller(common.OpenstackController): def _action_resize(self, input_dict, req, id): return exc.HTTPNotImplemented() - """ Resizes a given instance to the flavor size requested """ - try: - if 'resize' in input_dict and 'flavorId' in input_dict['resize']: - flavor_id = input_dict['resize']['flavorId'] - self.compute_api.resize(req.environ['nova.context'], id, - flavor_id) - else: - LOG.exception(_("Missing arguments for resize")) - return faults.Fault(exc.HTTPUnprocessableEntity()) - except Exception, e: - LOG.exception(_("Error in resize %s"), e) - return faults.Fault(exc.HTTPBadRequest()) - return exc.HTTPAccepted() def _action_reboot(self, input_dict, req, id): if 'reboot' in input_dict and 'type' in input_dict['reboot']: -- cgit From 95f103f276f6eb7decd6ebd17ff4ac106bc7222f Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Tue, 31 May 2011 11:17:35 -0400 Subject: More specific error messages for resize requests. --- nova/api/openstack/servers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 4bd7ddb14..1ec74bc2e 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -606,7 +606,7 @@ class ControllerV10(Controller): self.compute_api.resize(req.environ['nova.context'], id, flavor_id) else: - LOG.exception(_("Missing arguments for resize")) + LOG.exception(_("Missing 'flavorId' argument for resize")) return faults.Fault(exc.HTTPUnprocessableEntity()) except Exception, e: LOG.exception(_("Error in resize %s"), e) @@ -707,7 +707,7 @@ class ControllerV11(Controller): self.compute_api.resize(req.environ['nova.context'], id, flavor_id) else: - LOG.exception(_("Missing arguments for resize")) + LOG.exception(_("Missing 'flavorRef' argument for resize")) return faults.Fault(exc.HTTPUnprocessableEntity()) except Exception, e: LOG.exception(_("Error in resize %s"), e) -- cgit From b0c43e57ad6a7e5be8a749e70da39b7f7ba547bd Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 31 May 2011 14:49:47 -0700 Subject: switch to using webob exception --- nova/api/ec2/metadatarequesthandler.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) (limited to 'nova/api') diff --git a/nova/api/ec2/metadatarequesthandler.py b/nova/api/ec2/metadatarequesthandler.py index 720f264a4..9c8e52270 100644 --- a/nova/api/ec2/metadatarequesthandler.py +++ b/nova/api/ec2/metadatarequesthandler.py @@ -76,12 +76,10 @@ class MetadataRequestHandler(wsgi.Application): meta_data = cc.get_metadata(remote_address) except Exception: LOG.exception(_('Failed to get metadata for ip: %s'), remote_address) - resp = webob.Response() - resp.status = 500 - message = _('An unknown error has occurred. ' - 'Please try your request again.') - resp.body = str(utils.utf8(message)) - return resp + msg = _('An unknown error has occurred. ' + 'Please try your request again.') + exc = webob.exc.HTTPInternalServerError(explanation=unicode(msg)) + return exc if meta_data is None: LOG.error(_('Failed to get metadata for ip: %s'), remote_address) raise webob.exc.HTTPNotFound() -- cgit From 81f40ed1ca284bc9a8ee948ae23fdff93d632cb0 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 31 May 2011 15:50:33 -0700 Subject: pep8 --- nova/api/ec2/metadatarequesthandler.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/ec2/metadatarequesthandler.py b/nova/api/ec2/metadatarequesthandler.py index 9c8e52270..b70266a20 100644 --- a/nova/api/ec2/metadatarequesthandler.py +++ b/nova/api/ec2/metadatarequesthandler.py @@ -75,7 +75,8 @@ class MetadataRequestHandler(wsgi.Application): try: meta_data = cc.get_metadata(remote_address) except Exception: - LOG.exception(_('Failed to get metadata for ip: %s'), remote_address) + LOG.exception(_('Failed to get metadata for ip: %s'), + remote_address) msg = _('An unknown error has occurred. ' 'Please try your request again.') exc = webob.exc.HTTPInternalServerError(explanation=unicode(msg)) -- cgit