From d764a483497afc5d029a82db14cc5cc88f45f4c0 Mon Sep 17 00:00:00 2001 From: "Kevin L. Mitchell" Date: Thu, 9 Jun 2011 19:43:48 +0000 Subject: Add an extension to allow for an addFixedIp action on instances --- nova/api/openstack/contrib/multinic.py | 83 ++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 nova/api/openstack/contrib/multinic.py (limited to 'nova/api') diff --git a/nova/api/openstack/contrib/multinic.py b/nova/api/openstack/contrib/multinic.py new file mode 100644 index 000000000..164af79b0 --- /dev/null +++ b/nova/api/openstack/contrib/multinic.py @@ -0,0 +1,83 @@ +# Copyright 2011 OpenStack LLC. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +"""The multinic extension.""" + +from webob import exc + +from nova import compute +from nova import log as logging +from nova.api.openstack import extensions +from nova.api.openstack import faults + + +LOG = logging.getLogger("nova.api.multinic") + + +class Multinic(extensions.ExtensionDescriptor): + def __init__(self, *args, **kwargs): + super(Multinic, self).__init__(*args, **kwargs) + self.compute_api = compute.API() + + def get_name(self): + return "Multinic" + + def get_alias(self): + return "NMN" + + def get_description(self): + return "Multiple network support" + + def get_namespace(self): + return "http://docs.openstack.org/ext/multinic/api/v1.1" + + def get_updated(self): + return "2011-06-09T00:00:00+00:00" + + def get_actions(self): + actions = [] + + # Add the add_fixed_ip action + act = extensions.ActionExtension("servers", "addFixedIp", + self._add_fixed_ip) + actions.append(act) + + # Add the remove_fixed_ip action + act = extensions.ActionExtension("servers", "removeFixedIp", + self._remove_fixed_ip) + actions.append(act) + + return actions + + def _add_fixed_ip(self, input_dict, req, id): + """Adds an IP on a given network to an instance.""" + try: + # Validate the input entity + if 'networkId' not in input_dict['addFixedIp']: + LOG.exception(_("Missing 'networkId' argument for addFixedIp")) + return faults.Fault(exc.HTTPUnprocessableEntity()) + + # Add the fixed IP + network_id = input_dict['addFixedIp']['networkId'] + self.compute_api.add_fixed_ip(req.environ['nova.context'], id, + network_id) + except Exception, e: + LOG.exception(_("Error in addFixedIp %s"), e) + return faults.Fault(exc.HTTPBadRequest()) + return exc.HTTPAccepted() + + def _remove_fixed_ip(self, input_dict, req, id): + # Not yet implemented + raise faults.Fault(exc.HTTPNotImplemented()) -- cgit From 220ddb2bd5413ea5fa2bff450f4fb3aba136e909 Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Thu, 16 Jun 2011 20:29:10 +0900 Subject: api/ec2: check user permission for start/stop instances This patch adds precise permission check for start/stop instances. --- nova/api/ec2/__init__.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'nova/api') diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py index 890d57fe7..857fa96c3 100644 --- a/nova/api/ec2/__init__.py +++ b/nova/api/ec2/__init__.py @@ -262,6 +262,8 @@ class Authorizer(wsgi.Middleware): 'TerminateInstances': ['projectmanager', 'sysadmin'], 'RebootInstances': ['projectmanager', 'sysadmin'], 'UpdateInstance': ['projectmanager', 'sysadmin'], + 'StartInstances': ['projectmanager', 'sysadmin'], + 'StopInstances': ['projectmanager', 'sysadmin'], 'DeleteVolume': ['projectmanager', 'sysadmin'], 'DescribeImages': ['all'], 'DeregisterImage': ['projectmanager', 'sysadmin'], -- cgit From df63c8e14da8c93453d4d7485829e38e0db30711 Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Thu, 16 Jun 2011 20:35:49 +0900 Subject: ec2utils: consolidate 'vol-%08x' and 'snap-%08x' By introducing helper functions, consolidate scattered '{vol, snap}-%08x' --- nova/api/ec2/__init__.py | 4 ++-- nova/api/ec2/cloud.py | 14 ++++++-------- nova/api/ec2/ec2utils.py | 11 +++++++++++ 3 files changed, 19 insertions(+), 10 deletions(-) (limited to 'nova/api') diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py index 857fa96c3..35f67d39b 100644 --- a/nova/api/ec2/__init__.py +++ b/nova/api/ec2/__init__.py @@ -327,13 +327,13 @@ class Executor(wsgi.Application): except exception.VolumeNotFound as ex: LOG.info(_('VolumeNotFound raised: %s'), unicode(ex), context=context) - ec2_id = ec2utils.id_to_ec2_id(ex.volume_id, 'vol-%08x') + ec2_id = ec2utils.id_to_ec2_vol_id(ex.volume_id) 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') + ec2_id = ec2utils.id_to_ec2_snap_id(ex.snapshot_id) message = _('Snapshot %s not found') % ec2_id return self._error(req, context, type(ex).__name__, message) except exception.NotFound as ex: diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index a59173103..d36eb7a04 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -305,9 +305,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['snapshotId'] = ec2utils.id_to_ec2_snap_id(snapshot['id']) + s['volumeId'] = ec2utils.id_to_ec2_vol_id(snapshot['volume_id']) s['status'] = snapshot['status'] s['startTime'] = snapshot['created_at'] s['progress'] = snapshot['progress'] @@ -641,7 +640,7 @@ class CloudController(object): instance_data = '%s[%s]' % (instance_ec2_id, volume['instance']['host']) v = {} - v['volumeId'] = ec2utils.id_to_ec2_id(volume['id'], 'vol-%08x') + v['volumeId'] = ec2utils.id_to_ec2_vol_id(volume['id']) v['status'] = volume['status'] v['size'] = volume['size'] v['availabilityZone'] = volume['availability_zone'] @@ -663,8 +662,7 @@ 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_snap_id(volume['snapshot_id']) else: v['snapshotId'] = None @@ -727,7 +725,7 @@ class CloudController(object): 'instanceId': ec2utils.id_to_ec2_id(instance_id), 'requestId': context.request_id, 'status': volume['attach_status'], - 'volumeId': ec2utils.id_to_ec2_id(volume_id, 'vol-%08x')} + 'volumeId': ec2utils.id_to_ec2_vol_id(volume_id)} def detach_volume(self, context, volume_id, **kwargs): volume_id = ec2utils.ec2_id_to_id(volume_id) @@ -739,7 +737,7 @@ class CloudController(object): 'instanceId': ec2utils.id_to_ec2_id(instance['id']), 'requestId': context.request_id, 'status': volume['attach_status'], - 'volumeId': ec2utils.id_to_ec2_id(volume_id, 'vol-%08x')} + 'volumeId': ec2utils.id_to_ec2_vol_id(volume_id)} def _convert_to_set(self, lst, label): if lst is None or lst == []: diff --git a/nova/api/ec2/ec2utils.py b/nova/api/ec2/ec2utils.py index 222e1de1e..bcdf2ba78 100644 --- a/nova/api/ec2/ec2utils.py +++ b/nova/api/ec2/ec2utils.py @@ -34,6 +34,17 @@ def id_to_ec2_id(instance_id, template='i-%08x'): return template % instance_id +def id_to_ec2_snap_id(instance_id): + """Convert an snapshot ID (int) to an ec2 snapshot ID + (snap-[base 16 number])""" + return id_to_ec2_id(instance_id, 'snap-%08x') + + +def id_to_ec2_vol_id(instance_id): + """Convert an volume ID (int) to an ec2 volume ID (vol-[base 16 number])""" + return id_to_ec2_id(instance_id, 'vol-%08x') + + _c2u = re.compile('(((?<=[a-z])[A-Z])|([A-Z](?![A-Z]|$)))') -- cgit From 1a54498af5bc2b81bf8bf6e3b9a4ad4cc2db79e2 Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Thu, 16 Jun 2011 21:25:35 +0900 Subject: api/ec2/image: support block device mapping This patch adds --block-device-mapping support for register image and instance creation. --- nova/api/ec2/cloud.py | 189 ++++++++++++++++++++++++++++++++++++++++------- nova/api/ec2/ec2utils.py | 19 +++++ 2 files changed, 181 insertions(+), 27 deletions(-) (limited to 'nova/api') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index d36eb7a04..1e594cd73 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -27,6 +27,7 @@ import IPy import os import urllib import tempfile +import time import shutil from nova import compute @@ -75,6 +76,73 @@ def _gen_key(context, user_id, key_name): return {'private_key': private_key, 'fingerprint': fingerprint} +# TODO(yamahata): hypervisor dependent default device name +_DEFAULT_ROOT_DEVICE_NAME = '/dev/sda1' + + +def _parse_block_device_mapping(bdm): + """Parse BlockDeviceMappingItemType into flat hash + BlockDevicedMapping..DeviceName + BlockDevicedMapping..Ebs.SnapshotId + BlockDevicedMapping..Ebs.VolumeSize + BlockDevicedMapping..Ebs.DeleteOnTermination + BlockDevicedMapping..Ebs.NoDevice + BlockDevicedMapping..VirtualName + => remove .Ebs and allow volume id in SnapshotId + """ + ebs = bdm.pop('ebs', None) + if ebs: + ec2_id = ebs.pop('snapshot_id') + id = ec2utils.ec2_id_to_id(ec2_id) + if ec2_id.startswith('snap-'): + bdm['snapshot_id'] = id + elif ec2_id.startswith('vol-'): + bdm['volume_id'] = id + ebs.setdefault('delete_on_termination', True) + bdm.update(ebs) + return bdm + + +def _format_block_device_mapping(bdm): + """Contruct BlockDeviceMappingItemType + {'device_name': '...', 'Snapshot_Id': , ...} + => BlockDeviceMappingItemType + """ + keys = (('deviceName', 'device_name'), + ('virtualName', 'virtual_name')) + item = {} + for name, k in keys: + if k in bdm: + item[name] = bdm[k] + if bdm.get('no_device'): + item['noDevice'] = True + if ('snapshot_id' in bdm) or ('volume_id' in bdm): + ebs_keys = (('snapshotId', 'snapshot_id'), + ('snapshotId', 'volume_id'), # snapshotId is abused + ('volumeSize', 'volume_size'), + ('deleteOnTermination', 'delete_on_termination')) + ebs = {} + for name, k in ebs_keys: + if k in bdm: + if k == 'snapshot_id': + ebs[name] = ec2utils.id_to_ec2_snap_id(bdm[k]) + elif k == 'volume_id': + ebs[name] = ec2utils.id_to_ec2_vol_id(bdm[k]) + else: + ebs[name] = bdm[k] + assert 'snapshotId' in ebs + item['ebs'] = ebs + return item + + +def _format_mappings(mappings, result): + """Format multiple BlockDeviceMappingItemType""" + block_device_mapping = [_format_block_device_mapping(bdm) for bdm in + mappings] + if block_device_mapping: + result['blockDeviceMapping'] = block_device_mapping + + class CloudController(object): """ CloudController provides the critical dispatch between inbound API calls through the endpoint and messages @@ -177,7 +245,7 @@ class CloudController(object): # TODO(vish): replace with real data 'ami': 'sda1', 'ephemeral0': 'sda2', - 'root': '/dev/sda1', + 'root': _DEFAULT_ROOT_DEVICE_NAME, 'swap': 'sda3'}, 'hostname': hostname, 'instance-action': 'none', @@ -761,6 +829,37 @@ class CloudController(object): assert len(i) == 1 return i[0] + def _format_instance_bdm(self, context, instance_id, root_device_name, + result): + """Format InstanceBlockDeviceMappingResponseItemType""" + root_device_type = 'instance-store' + mapping = [] + for bdm in db.block_device_mapping_get_all_by_instance(context, + instance_id): + volume_id = bdm['volume_id'] + if (volume_id is None or bdm['no_device']): + continue + + if (bdm['device_name'] == root_device_name and + (bdm['snapshot_id'] or bdm['volume_id'])): + assert not bdm['virtual_name'] + root_device_type = 'ebs' + + vol = self.volume_api.get(context, volume_id=volume_id) + LOG.debug(_("vol = %s\n"), vol) + # TODO(yamahata): volume attach time + ebs = {'volumeId': volume_id, + 'deleteOnTermination': bdm['delete_on_termination'], + 'attachTime': vol['attach_time'] or '-', + 'status': vol['status'], } + res = {'deviceName': bdm['device_name'], + 'ebs': ebs, } + mapping.append(res) + + if mapping: + result['blockDeviceMapping'] = mapping + result['rootDeviceType'] = root_device_type + def _format_instances(self, context, instance_id=None, **kwargs): # TODO(termie): this method is poorly named as its name does not imply # that it will be making a variety of database calls @@ -822,6 +921,10 @@ class CloudController(object): i['amiLaunchIndex'] = instance['launch_index'] i['displayName'] = instance['display_name'] i['displayDescription'] = instance['display_description'] + i['rootDeviceName'] = (instance['root_device_name'] or + _DEFAULT_ROOT_DEVICE_NAME) + self._format_instance_bdm(context, instance_id, + i['rootDeviceName'], i) host = instance['host'] zone = self._get_availability_zone_by_host(context, host) i['placement'] = {'availabilityZone': zone} @@ -907,24 +1010,7 @@ class CloudController(object): if kwargs.get('ramdisk_id'): ramdisk = self._get_image(context, kwargs['ramdisk_id']) kwargs['ramdisk_id'] = ramdisk['id'] - for bdm in kwargs.get('block_device_mapping', []): - # NOTE(yamahata) - # BlockDevicedMapping..DeviceName - # BlockDevicedMapping..Ebs.SnapshotId - # BlockDevicedMapping..Ebs.VolumeSize - # BlockDevicedMapping..Ebs.DeleteOnTermination - # BlockDevicedMapping..VirtualName - # => remove .Ebs and allow volume id in SnapshotId - ebs = bdm.pop('ebs', None) - if ebs: - ec2_id = ebs.pop('snapshot_id') - id = ec2utils.ec2_id_to_id(ec2_id) - if ec2_id.startswith('snap-'): - bdm['snapshot_id'] = id - elif ec2_id.startswith('vol-'): - bdm['volume_id'] = id - ebs.setdefault('delete_on_termination', True) - bdm.update(ebs) + _parse_block_device_mapping(bdm) image = self._get_image(context, kwargs['image_id']) @@ -1078,6 +1164,20 @@ class CloudController(object): i['imageType'] = display_mapping.get(image_type) i['isPublic'] = image.get('is_public') == True i['architecture'] = image['properties'].get('architecture') + + properties = image['properties'] + root_device_name = ec2utils.properties_root_device_name(properties) + root_device_type = 'instance-store' + for bdm in properties.get('block_device_mapping', []): + if (bdm.get('device_name') == root_device_name and + ('snapshot_id' in bdm or 'volume_id' in bdm) and + not bdm.get('no_device')): + root_device_type = 'ebs' + i['rootDeviceName'] = (root_device_name or _DEFAULT_ROOT_DEVICE_NAME) + i['rootDeviceType'] = root_device_type + + _format_mappings(properties.get('block_device_mapping', []), i) + return i def describe_images(self, context, image_id=None, **kwargs): @@ -1102,30 +1202,65 @@ class CloudController(object): self.image_service.delete(context, internal_id) return {'imageId': image_id} + def _register_image(self, context, metadata): + image = self.image_service.create(context, metadata) + image_type = self._image_type(image.get('container_format')) + image_id = self.image_ec2_id(image['id'], image_type) + return image_id + def register_image(self, context, image_location=None, **kwargs): if image_location is None and 'name' in kwargs: image_location = kwargs['name'] metadata = {'properties': {'image_location': image_location}} - image = self.image_service.create(context, metadata) - image_type = self._image_type(image.get('container_format')) - image_id = self.image_ec2_id(image['id'], - image_type) + + if 'root_device_name' in kwargs: + metadata['properties']['root_device_name'] = \ + kwargs.get('root_device_name') + + mappings = [_parse_block_device_mapping(bdm) for bdm in + kwargs.get('block_device_mapping', [])] + if mappings: + metadata['properties']['block_device_mapping'] = mappings + + image_id = self._register_image(context, metadata) msg = _("Registered image %(image_location)s with" " id %(image_id)s") % locals() LOG.audit(msg, context=context) return {'imageId': image_id} def describe_image_attribute(self, context, image_id, attribute, **kwargs): - if attribute != 'launchPermission': + def _block_device_mapping_attribute(image, result): + _format_mappings( + image['properties'].get('block_device_mapping', []), result) + + def _launch_permission_attribute(image, result): + result['launchPermission'] = [] + if image['is_public']: + result['launchPermission'].append({'group': 'all'}) + + def _root_device_name_attribute(image, result): + result['rootDeviceName'] = \ + ec2utils.properties_root_device_name(image['properties']) + if result['rootDeviceName'] is None: + result['rootDeviceName'] = _DEFAULT_ROOT_DEVICE_NAME + + supported_attributes = { + 'blockDeviceMapping': _block_device_mapping_attribute, + 'launchPermission': _launch_permission_attribute, + 'rootDeviceName': _root_device_name_attribute, + } + + fn = supported_attributes.get(attribute) + if fn is None: raise exception.ApiError(_('attribute not supported: %s') % attribute) try: image = self._get_image(context, image_id) except exception.NotFound: raise exception.ImageNotFound(image_id=image_id) - result = {'imageId': image_id, 'launchPermission': []} - if image['is_public']: - result['launchPermission'].append({'group': 'all'}) + + result = {'imageId': image_id} + fn(image, result) return result def modify_image_attribute(self, context, image_id, attribute, diff --git a/nova/api/ec2/ec2utils.py b/nova/api/ec2/ec2utils.py index bcdf2ba78..9839f8604 100644 --- a/nova/api/ec2/ec2utils.py +++ b/nova/api/ec2/ec2utils.py @@ -135,3 +135,22 @@ def dict_from_dotted_str(items): args[key] = value return args + + +def properties_root_device_name(properties): + """get root device name from image meta data. + If it isn't specified, return None. + """ + root_device_name = None + + # NOTE(yamahata): see image_service.s3.s3create() + for bdm in properties.get('mappings', []): + if bdm['virtual'] == 'root': + root_device_name = bdm['device'] + + # NOTE(yamahata): register_image's command line can override + # .manifest.xml + if 'root_device_name' in properties: + root_device_name = properties['root_device_name'] + + return root_device_name -- cgit From 46c7e018ee3aed0f0e27ae3544193adb5fa62958 Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Thu, 16 Jun 2011 21:25:57 +0900 Subject: api/ec2: support CreateImage --- nova/api/ec2/__init__.py | 1 + nova/api/ec2/cloud.py | 101 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+) (limited to 'nova/api') diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py index 35f67d39b..cf1734281 100644 --- a/nova/api/ec2/__init__.py +++ b/nova/api/ec2/__init__.py @@ -271,6 +271,7 @@ class Authorizer(wsgi.Middleware): 'DescribeImageAttribute': ['all'], 'ModifyImageAttribute': ['projectmanager', 'sysadmin'], 'UpdateImage': ['projectmanager', 'sysadmin'], + 'CreateImage': ['projectmanager', 'sysadmin'], }, 'AdminController': { # All actions have the same permission: ['none'] (the default) diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 1e594cd73..b38061eaf 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -1291,3 +1291,104 @@ class CloudController(object): internal_id = ec2utils.ec2_id_to_id(image_id) result = self.image_service.update(context, internal_id, dict(kwargs)) return result + + # TODO(yamahata): race condition + # At the moment there is no way to prevent others from + # manipulating instances/volumes/snapshots. + # As other code doesn't take it into consideration, here we don't + # care of it for now. Ostrich algorithm + def create_image(self, context, instance_id, **kwargs): + # NOTE(yamahata): name/description are ignored by register_image(), + # do so here + #description = kwargs.get('name') + #description = kwargs.get('description') + no_reboot = kwargs.get('no_reboot', False) + + ec2_instance_id = instance_id + instance_id = ec2utils.ec2_id_to_id(ec2_instance_id) + instance = self.compute_api.get(context, instance_id) + + # stop the instance if necessary + restart_instance = False + if not no_reboot: + state_description = instance['state_description'] + + # if the instance is in subtle state, refuse to proceed. + if state_description not in ('running', 'stopping', 'stopped'): + raise exception.InstanceNotRunning(instance_id=ec2_instance_id) + + if state_description == 'running': + restart_instance = True + self.compute_api.stop(context, instance_id=instance_id) + + # wait instance for really stopped + while state_description != 'stopped': + time.sleep(1) + instance = self.compute_api.get(context, instance_id) + state_description = instance['state_description'] + # NOTE(yamahata): timeout and error? + + src_image = self.image_service.show(context, instance['image_id']) + properties = src_image['properties'] + if instance['root_device_name']: + properties['root_device_name'] = instance['root_device_name'] + + mapping = [] + bdms = db.block_device_mapping_get_all_by_instance(context, + instance_id) + for bdm in bdms: + if bdm.no_device: + continue + m = {} + for attr in ('device_name', 'snapshot_id', 'volume_id', + 'volume_size', 'delete_on_termination', 'no_device', + 'virtual_name'): + val = getattr(bdm, attr) + if val is not None: + m[attr] = val + + volume_id = m.get('volume_id') + if m.get('snapshot_id') and volume_id: + # create snapshot based on volume_id + vol = self.volume_api.get(context, volume_id=volume_id) + # NOTE(yamahata): Should we wait for snapshot creation? + # Linux LVM snapshot creation completes in + # short time, it doesn't matter for now. + snapshot = self.volume_api.create_snapshot( + context, volume_id=volume_id, name=vol['display_name'], + description=vol['display_description']) + m['snapshot_id'] = snapshot['id'] + del m['volume_id'] + + if m: + mapping.append(m) + + for m in properties.get('mappings', []): + virtual_name = m['virtual'] + if virtual_name in ('ami', 'root'): + continue + + assert (virtual_name == 'swap' or + virtual_name.startswith('ephemeral')) + device_name = m['device_name'] + if device_name in [b.device_name for b in mapping + if not b.no_device]: + continue + + # NOTE(yamahata): swap and ephemeral devices are specified in + # AMI, but disabled for this instance by user. + # So disable those device by no_device. + mapping.append({'device_name': device_name, 'no_device': True}) + + if mapping: + properties['block_device_mapping'] = mapping + + for attr in ('status', 'location', 'id'): + del src_image[attr] + + image_id = self._register_image(context, src_image) + + if restart_instance: + self.compute_api.start(context, instance_id=instance_id) + + return {'imageId': image_id} -- cgit From 7bc76ef403507f6762f782ff4d305cf2718346d5 Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Thu, 16 Jun 2011 22:17:54 +0900 Subject: ec2/cloud.py: fix mismerge --- nova/api/ec2/cloud.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index b38061eaf..1bf8bbd35 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -1010,7 +1010,8 @@ class CloudController(object): if kwargs.get('ramdisk_id'): ramdisk = self._get_image(context, kwargs['ramdisk_id']) kwargs['ramdisk_id'] = ramdisk['id'] - _parse_block_device_mapping(bdm) + for bdm in kwargs.get('block_device_mapping', []): + _parse_block_device_mapping(bdm) image = self._get_image(context, kwargs['image_id']) -- cgit From 3a83471ec002127a84d319e397ce54e49bd696a1 Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Wed, 22 Jun 2011 12:53:47 +0900 Subject: api/ec2/image: make block device mapping pass unit tests. This patch makes pass unit tests which will follow later. --- nova/api/ec2/cloud.py | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) (limited to 'nova/api') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 8138567d0..703dd82b4 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -135,12 +135,29 @@ def _format_block_device_mapping(bdm): return item -def _format_mappings(mappings, result): +def _format_mappings(properties, result): """Format multiple BlockDeviceMappingItemType""" + mappings = [{'virtualName': m['virtual'], 'deviceName': m['device']} + for m in properties.get('mappings', []) + if (m['virtual'] == 'swap' or + m['virtual'].startswith('ephemeral'))] + block_device_mapping = [_format_block_device_mapping(bdm) for bdm in - mappings] - if block_device_mapping: - result['blockDeviceMapping'] = block_device_mapping + properties.get('block_device_mapping', [])] + + # NOTE(yamahata): overwrite mappings with block_device_mapping + for bdm in block_device_mapping: + for i in range(len(mappings)): + if bdm['deviceName'] == mappings[i]['deviceName']: + del mappings[i] + break + mappings.append(bdm) + + # NOTE(yamahata): trim ebs.no_device == true. Is this necessary? + mappings = [bdm for bdm in mappings if not (bdm.get('noDevice', False))] + + if mappings: + result['blockDeviceMapping'] = mappings class CloudController(object): @@ -1178,7 +1195,7 @@ class CloudController(object): i['rootDeviceName'] = (root_device_name or _DEFAULT_ROOT_DEVICE_NAME) i['rootDeviceType'] = root_device_type - _format_mappings(properties.get('block_device_mapping', []), i) + _format_mappings(properties, i) return i @@ -1232,8 +1249,7 @@ class CloudController(object): def describe_image_attribute(self, context, image_id, attribute, **kwargs): def _block_device_mapping_attribute(image, result): - _format_mappings( - image['properties'].get('block_device_mapping', []), result) + _format_mappings(image['properties'], result) def _launch_permission_attribute(image, result): result['launchPermission'] = [] -- cgit From d81d75bec04fe19492544e5bf7548dce5a2366ad Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Wed, 22 Jun 2011 12:54:22 +0900 Subject: api/ec2: make CreateImage pass unit tests --- nova/api/ec2/cloud.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'nova/api') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 703dd82b4..c25db9014 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -1346,7 +1346,7 @@ class CloudController(object): state_description = instance['state_description'] # NOTE(yamahata): timeout and error? - src_image = self.image_service.show(context, instance['image_id']) + src_image = self._get_image(context, instance['image_ref']) properties = src_image['properties'] if instance['root_device_name']: properties['root_device_name'] = instance['root_device_name'] @@ -1372,7 +1372,7 @@ class CloudController(object): # NOTE(yamahata): Should we wait for snapshot creation? # Linux LVM snapshot creation completes in # short time, it doesn't matter for now. - snapshot = self.volume_api.create_snapshot( + snapshot = self.volume_api.create_snapshot_force( context, volume_id=volume_id, name=vol['display_name'], description=vol['display_description']) m['snapshot_id'] = snapshot['id'] @@ -1388,9 +1388,9 @@ class CloudController(object): assert (virtual_name == 'swap' or virtual_name.startswith('ephemeral')) - device_name = m['device_name'] - if device_name in [b.device_name for b in mapping - if not b.no_device]: + device_name = m['device'] + if device_name in [b['device_name'] for b in mapping + if not b.get('no_device', False)]: continue # NOTE(yamahata): swap and ephemeral devices are specified in @@ -1402,7 +1402,7 @@ class CloudController(object): properties['block_device_mapping'] = mapping for attr in ('status', 'location', 'id'): - del src_image[attr] + src_image.pop(attr, None) image_id = self._register_image(context, src_image) -- cgit From 1c4a3e14a0cef6938c477908d5c3bfe5ddf0e07b Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Thu, 23 Jun 2011 19:51:00 +0900 Subject: ec2utils: introduce helper function to prepend '/dev/' in mappings Introduce a helper function to prepend /dev/ to device name in block device mapping of bundle --- nova/api/ec2/ec2utils.py | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'nova/api') diff --git a/nova/api/ec2/ec2utils.py b/nova/api/ec2/ec2utils.py index 9839f8604..bae1e0ee5 100644 --- a/nova/api/ec2/ec2utils.py +++ b/nova/api/ec2/ec2utils.py @@ -154,3 +154,13 @@ def properties_root_device_name(properties): root_device_name = properties['root_device_name'] return root_device_name + + +def mappings_prepend_dev(mappings): + """Prepend '/dev/' to 'device' entry of swap/ephemeral virtual type""" + for m in mappings: + virtual = m['virtual'] + if ((virtual == 'swap' or virtual.startswith('ephemeral')) and + (not m['device'].startswith('/'))): + m['device'] = '/dev/' + m['device'] + return mappings -- cgit From 4b5fdb2ee109960be6b3ff1fa8068ab3ec428283 Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Thu, 23 Jun 2011 19:51:00 +0900 Subject: ec2: bundle block device mapping device name in block device mapping of bundle doesn't necessary carry "/dev/". So prepend it before processing. --- nova/api/ec2/cloud.py | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) (limited to 'nova/api') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index c25db9014..8bf0950b2 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -92,20 +92,25 @@ def _parse_block_device_mapping(bdm): """ ebs = bdm.pop('ebs', None) if ebs: - ec2_id = ebs.pop('snapshot_id') - id = ec2utils.ec2_id_to_id(ec2_id) - if ec2_id.startswith('snap-'): - bdm['snapshot_id'] = id - elif ec2_id.startswith('vol-'): - bdm['volume_id'] = id - ebs.setdefault('delete_on_termination', True) + ec2_id = ebs.pop('snapshot_id', None) + if ec2_id: + id = ec2utils.ec2_id_to_id(ec2_id) + if ec2_id.startswith('snap-'): + bdm['snapshot_id'] = id + elif ec2_id.startswith('vol-'): + bdm['volume_id'] = id + ebs.setdefault('delete_on_termination', True) bdm.update(ebs) return bdm +def _properties_get_mappings(properties): + return ec2utils.mappings_prepend_dev(properties.get('mappings', [])) + + def _format_block_device_mapping(bdm): """Contruct BlockDeviceMappingItemType - {'device_name': '...', 'Snapshot_Id': , ...} + {'device_name': '...', 'snapshot_id': , ...} => BlockDeviceMappingItemType """ keys = (('deviceName', 'device_name'), @@ -138,7 +143,7 @@ def _format_block_device_mapping(bdm): def _format_mappings(properties, result): """Format multiple BlockDeviceMappingItemType""" mappings = [{'virtualName': m['virtual'], 'deviceName': m['device']} - for m in properties.get('mappings', []) + for m in _properties_get_mappings(properties) if (m['virtual'] == 'swap' or m['virtual'].startswith('ephemeral'))] @@ -1381,7 +1386,7 @@ class CloudController(object): if m: mapping.append(m) - for m in properties.get('mappings', []): + for m in _properties_get_mappings(properties): virtual_name = m['virtual'] if virtual_name in ('ami', 'root'): continue -- cgit From 8a884121e6a7c5f03f51266632bb671603c9c9a0 Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Thu, 23 Jun 2011 19:51:01 +0900 Subject: ec2/cloud: address review. - eliminated commented out lines in create_image() - added time out to create_image() , --- nova/api/ec2/cloud.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) (limited to 'nova/api') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 8bf0950b2..b6274df0d 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -1323,8 +1323,6 @@ class CloudController(object): def create_image(self, context, instance_id, **kwargs): # NOTE(yamahata): name/description are ignored by register_image(), # do so here - #description = kwargs.get('name') - #description = kwargs.get('description') no_reboot = kwargs.get('no_reboot', False) ec2_instance_id = instance_id @@ -1345,11 +1343,18 @@ class CloudController(object): self.compute_api.stop(context, instance_id=instance_id) # wait instance for really stopped + start_time = time.time() while state_description != 'stopped': time.sleep(1) instance = self.compute_api.get(context, instance_id) state_description = instance['state_description'] - # NOTE(yamahata): timeout and error? + # NOTE(yamahata): timeout and error. 1 hour for now for safety. + # Is it too short/long? + # Or is there any better way? + timeout = 1 * 60 * 60 * 60 + if time.time() > start_time + timeout: + raise exception.ApiError( + _('Couldn\'t stop instance with in %d sec') % timeout) src_image = self._get_image(context, instance['image_ref']) properties = src_image['properties'] -- cgit From ce3b1ffec9bab02e2988b69e7e361d76e56ec002 Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Fri, 24 Jun 2011 19:08:26 +0900 Subject: ec2/cloud: typo --- nova/api/ec2/cloud.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index b6274df0d..a0a7db1f2 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -943,7 +943,7 @@ class CloudController(object): i['amiLaunchIndex'] = instance['launch_index'] i['displayName'] = instance['display_name'] i['displayDescription'] = instance['display_description'] - i['rootDeviceName'] = (instance['root_device_name'] or + i['rootDeviceName'] = (instance.get('root_device_name') or _DEFAULT_ROOT_DEVICE_NAME) self._format_instance_bdm(context, instance_id, i['rootDeviceName'], i) -- cgit From 14a63fa2c7de79fe173771fd98e448650387e924 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Sat, 25 Jun 2011 17:29:14 -0700 Subject: add support to list security groups --- nova/api/ec2/cloud.py | 5 ++++- nova/api/ec2/metadatarequesthandler.py | 6 ++++-- 2 files changed, 8 insertions(+), 3 deletions(-) (limited to 'nova/api') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 9aaf37a2d..194ddee97 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -167,6 +167,9 @@ class CloudController(object): instance_ref['id']) ec2_id = ec2utils.id_to_ec2_id(instance_ref['id']) image_ec2_id = self.image_ec2_id(instance_ref['image_ref']) + security_groups = db.security_group_get_by_instance(ctxt, + instance_ref['id']) + security_groups = [x['name'] for x in security_groups] data = { 'user-data': base64.b64decode(instance_ref['user_data']), 'meta-data': { @@ -190,7 +193,7 @@ class CloudController(object): 'public-ipv4': floating_ip or '', 'public-keys': keys, 'reservation-id': instance_ref['reservation_id'], - 'security-groups': '', + 'security-groups': security_groups, 'mpi': mpi}} for image_type in ['kernel', 'ramdisk']: diff --git a/nova/api/ec2/metadatarequesthandler.py b/nova/api/ec2/metadatarequesthandler.py index b70266a20..1dc275c90 100644 --- a/nova/api/ec2/metadatarequesthandler.py +++ b/nova/api/ec2/metadatarequesthandler.py @@ -35,6 +35,9 @@ FLAGS = flags.FLAGS class MetadataRequestHandler(wsgi.Application): """Serve metadata from the EC2 API.""" + def __init__(self): + self.cc = cloud.CloudController() + def print_data(self, data): if isinstance(data, dict): output = '' @@ -68,12 +71,11 @@ class MetadataRequestHandler(wsgi.Application): @webob.dec.wsgify(RequestClass=wsgi.Request) def __call__(self, req): - cc = cloud.CloudController() remote_address = req.remote_addr if FLAGS.use_forwarded_for: remote_address = req.headers.get('X-Forwarded-For', remote_address) try: - meta_data = cc.get_metadata(remote_address) + meta_data = self.cc.get_metadata(remote_address) except Exception: LOG.exception(_('Failed to get metadata for ip: %s'), remote_address) -- cgit From 5686488517f702bd4ba714edeea89ea1993ac220 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Wed, 29 Jun 2011 09:49:49 -0700 Subject: Allow a port name in the server ref for image create --- nova/api/openstack/images.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index d43340e10..22c79e2e9 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -208,9 +208,14 @@ class ControllerV11(Controller): msg = _("Expected serverRef attribute on server entity.") raise webob.exc.HTTPBadRequest(explanation=msg) - head, tail = os.path.split(server_ref) - - if head and head != os.path.join(req.application_url, 'servers'): + head, _sep, tail = server_ref.rpartition('/') + + url, _sep, version = req.application_url.rpartition('/') + long_url = '%s:%s/%s' % (url, FLAGS.osapi_port, version) + valid_urls = ['%s/servers' % req.application_url, + '%s/servers' % long_url] + if head and head not in valid_urls: + LOG.warn(head) msg = _("serverRef must match request url") raise webob.exc.HTTPBadRequest(explanation=msg) -- cgit From 81ea3d5fc47bed84c5f4bf722b02dfa58792e19e Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Wed, 29 Jun 2011 18:26:51 -0400 Subject: Updated v1.1 links in flavors to represent the curret spec --- nova/api/openstack/views/flavors.py | 2 -- 1 file changed, 2 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/views/flavors.py b/nova/api/openstack/views/flavors.py index 462890ab2..beef67a88 100644 --- a/nova/api/openstack/views/flavors.py +++ b/nova/api/openstack/views/flavors.py @@ -79,12 +79,10 @@ class ViewBuilderV11(ViewBuilder): }, { "rel": "bookmark", - "type": "application/json", "href": href, }, { "rel": "bookmark", - "type": "application/xml", "href": href, }, ] -- cgit From 639717b1eadb769f1d77a4ddcdb6618da4defbea Mon Sep 17 00:00:00 2001 From: William Wolf Date: Thu, 30 Jun 2011 10:06:06 -0400 Subject: added FlavorRef exception handling on create instance --- nova/api/openstack/create_instance_helper.py | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'nova/api') diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index 436e524c1..94ae3bb85 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -143,6 +143,10 @@ class CreateInstanceHelper(object): except exception.ImageNotFound as error: msg = _("Can not find requested image") raise faults.Fault(exc.HTTPBadRequest(explanation=msg)) + except exception.FlavorNotFound as error: + msg = _("Invalid flavorRef provided.") + raise faults.Fault(exc.HTTPBadRequest(explanation=msg)) + # Let the caller deal with unhandled exceptions. -- cgit From 8133b9af105f7924f03b710b30cf4f0acb52f143 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Thu, 30 Jun 2011 10:29:31 -0400 Subject: refactored flavors viewbuilder --- nova/api/openstack/views/flavors.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/views/flavors.py b/nova/api/openstack/views/flavors.py index beef67a88..4e609930c 100644 --- a/nova/api/openstack/views/flavors.py +++ b/nova/api/openstack/views/flavors.py @@ -71,6 +71,7 @@ class ViewBuilderV11(ViewBuilder): def _build_links(self, flavor_obj): """Generate a container of links that refer to the provided flavor.""" href = self.generate_href(flavor_obj["id"]) + bookmark = self.generate_bookmark(flavor_obj["id"]) links = [ { @@ -79,11 +80,7 @@ class ViewBuilderV11(ViewBuilder): }, { "rel": "bookmark", - "href": href, - }, - { - "rel": "bookmark", - "href": href, + "href": bookmark, }, ] @@ -92,3 +89,11 @@ class ViewBuilderV11(ViewBuilder): def generate_href(self, flavor_id): """Create an url that refers to a specific flavor id.""" return "%s/flavors/%s" % (self.base_url, flavor_id) + + def generate_bookmark(self, flavor_id): + """Create an url that refers to a specific flavor id.""" + return "%s/flavors/%s" % (self._remove_version(self.base_url), + flavor_id) + + def _remove_version(self, base_url): + return base_url.rsplit('/', 1).pop(0) -- cgit From 5f772ea10c22549a7149f608cfc2ff932878d6fe Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Thu, 30 Jun 2011 11:18:19 -0400 Subject: updated servers --- nova/api/openstack/views/servers.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index cbfa5aae7..1c6dbf87d 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -156,6 +156,7 @@ class ViewBuilderV11(ViewBuilder): def _build_links(self, response, inst): href = self.generate_href(inst["id"]) + bookmark = self.generate_bookmark(inst["id"]) links = [ { @@ -164,13 +165,7 @@ class ViewBuilderV11(ViewBuilder): }, { "rel": "bookmark", - "type": "application/json", - "href": href, - }, - { - "rel": "bookmark", - "type": "application/xml", - "href": href, + "href": bookmark, }, ] @@ -179,3 +174,11 @@ class ViewBuilderV11(ViewBuilder): def generate_href(self, server_id): """Create an url that refers to a specific server id.""" return os.path.join(self.base_url, "servers", str(server_id)) + + def generate_bookmark(self, server_id): + """Create an url that refers to a specific flavor id.""" + return os.path.join(self._remove_version(self.base_url), + "servers", str(server_id)) + + def _remove_version(self, base_url): + return base_url.rsplit('/', 1).pop(0) -- cgit From 386e2a28f2d92dea30a726722b49e97e1c7ebba7 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Thu, 30 Jun 2011 11:29:45 -0400 Subject: updated images --- nova/api/openstack/views/images.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/views/images.py b/nova/api/openstack/views/images.py index d6a054102..175bcb109 100644 --- a/nova/api/openstack/views/images.py +++ b/nova/api/openstack/views/images.py @@ -104,6 +104,7 @@ class ViewBuilderV11(ViewBuilder): """Return a standardized image structure for display by the API.""" image = ViewBuilder.build(self, image_obj, detail) href = self.generate_href(image_obj["id"]) + bookmark = self.generate_bookmark(image_obj["id"]) image["links"] = [{ "rel": "self", @@ -111,13 +112,15 @@ class ViewBuilderV11(ViewBuilder): }, { "rel": "bookmark", - "type": "application/json", - "href": href, - }, - { - "rel": "bookmark", - "type": "application/xml", - "href": href, + "href": bookmark, }] return image + + def generate_bookmark(self, image_id): + """Create an url that refers to a specific flavor id.""" + return os.path.join(self._remove_version(self._url), + "images", str(image_id)) + + def _remove_version(self, base_url): + return base_url.rsplit('/', 1).pop(0) -- cgit From 401bbecb4fbb819d2b1daa3ee1bebcd6460742a1 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Fri, 1 Jul 2011 11:12:13 -0700 Subject: use url parse instead of manually splitting --- nova/api/openstack/images.py | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index 22c79e2e9..9717f3175 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -13,6 +13,7 @@ # License for the specific language governing permissions and limitations # under the License. +import urlparse import os.path import webob.exc @@ -22,7 +23,6 @@ from nova import exception from nova import flags import nova.image from nova import log -from nova import utils from nova.api.openstack import common from nova.api.openstack import faults from nova.api.openstack.views import images as images_view @@ -208,18 +208,24 @@ class ControllerV11(Controller): msg = _("Expected serverRef attribute on server entity.") raise webob.exc.HTTPBadRequest(explanation=msg) - head, _sep, tail = server_ref.rpartition('/') - - url, _sep, version = req.application_url.rpartition('/') - long_url = '%s:%s/%s' % (url, FLAGS.osapi_port, version) - valid_urls = ['%s/servers' % req.application_url, - '%s/servers' % long_url] - if head and head not in valid_urls: - LOG.warn(head) + if not server_ref.startswith('http'): + return server_ref + + passed = urlparse.urlparse(server_ref) + expected = urlparse.urlparse(req.application_url) + version = expected.path.split('/')[1] + expected_prefix = "/%s/servers/" % version + _empty, _sep, server_id = passed.path.partition(expected_prefix) + scheme_ok = passed.scheme == expected.scheme + host_ok = passed.hostname == expected.hostname + port_ok = (passed.port == expected.port or + passed.port == FLAGS.osapi_port) + LOG.warn(locals()) + if not (scheme_ok and port_ok and host_ok and server_id): msg = _("serverRef must match request url") raise webob.exc.HTTPBadRequest(explanation=msg) - return tail + return server_id def _get_extra_properties(self, req, data): server_ref = data['image']['serverRef'] -- cgit From a9b0dbb8dcd708a46af58f61bab39b0bc9e8a6e8 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Fri, 1 Jul 2011 12:03:27 -0700 Subject: remove logging statement --- nova/api/openstack/images.py | 1 - 1 file changed, 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index 9717f3175..b4ab0b3af 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -220,7 +220,6 @@ class ControllerV11(Controller): host_ok = passed.hostname == expected.hostname port_ok = (passed.port == expected.port or passed.port == FLAGS.osapi_port) - LOG.warn(locals()) if not (scheme_ok and port_ok and host_ok and server_id): msg = _("serverRef must match request url") raise webob.exc.HTTPBadRequest(explanation=msg) -- cgit From 0834f3d64b2cc37407c24a9b717e218d758adf79 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Mon, 4 Jul 2011 15:14:36 -0400 Subject: properly displays addresses in each network, not just public/private; adding addresses attribute to server entities --- nova/api/openstack/__init__.py | 11 ++--- nova/api/openstack/ips.py | 79 ++++++++++++++++++++++++++++------- nova/api/openstack/servers.py | 31 ++++++++------ nova/api/openstack/views/addresses.py | 38 +++++++++++++---- nova/api/openstack/views/servers.py | 13 +++++- 5 files changed, 129 insertions(+), 43 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index f24017df0..3e4d87ee0 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -144,9 +144,6 @@ class APIRouterV10(APIRouter): def _setup_routes(self, mapper): super(APIRouterV10, self)._setup_routes(mapper, '1.0') - mapper.resource("image", "images", - controller=images.create_resource('1.0'), - collection={'detail': 'GET'}) mapper.resource("shared_ip_group", "shared_ip_groups", collection={'detail': 'GET'}, @@ -157,8 +154,8 @@ class APIRouterV10(APIRouter): parent_resource=dict(member_name='server', collection_name='servers')) - mapper.resource("ip", "ips", controller=ips.create_resource(), - collection=dict(public='GET', private='GET'), + mapper.resource("ip", "ips", controller=ips.create_resource('1.0'), + member=dict(public='GET', private='GET'), parent_resource=dict(member_name='server', collection_name='servers')) @@ -177,3 +174,7 @@ class APIRouterV11(APIRouter): controller=server_metadata.create_resource(), parent_resource=dict(member_name='server', collection_name='servers')) + + mapper.resource("ip", "ips", controller=ips.create_resource('1.1'), + parent_resource=dict(member_name='server', + collection_name='servers')) diff --git a/nova/api/openstack/ips.py b/nova/api/openstack/ips.py index 71646b6d3..b83e2f9b3 100644 --- a/nova/api/openstack/ips.py +++ b/nova/api/openstack/ips.py @@ -23,6 +23,7 @@ import nova from nova.api.openstack import faults import nova.api.openstack.views.addresses from nova.api.openstack import wsgi +from nova import db class Controller(object): @@ -30,7 +31,6 @@ class Controller(object): def __init__(self): self.compute_api = nova.compute.API() - self.builder = nova.api.openstack.views.addresses.ViewBuilderV10() def _get_instance(self, req, server_id): try: @@ -40,29 +40,78 @@ class Controller(object): return faults.Fault(exc.HTTPNotFound()) return instance + def create(self, req, server_id, body): + return faults.Fault(exc.HTTPNotImplemented()) + + def delete(self, req, server_id, id): + return faults.Fault(exc.HTTPNotImplemented()) + + +class ControllerV10(Controller): + def index(self, req, server_id): instance = self._get_instance(req, server_id) - return {'addresses': self.builder.build(instance)} + builder = nova.api.openstack.views.addresses.ViewBuilderV10() + return {'addresses': builder.build(instance)} - def public(self, req, server_id): + def show(self, req, server_id, id): instance = self._get_instance(req, server_id) - return {'public': self.builder.build_public_parts(instance)} + builder = self._get_view_builder(req) + if id == 'private': + view = builder.build_private_parts(instance) + elif id == 'public': + view = builder.build_public_parts(instance) + else: + msg = _("Only private and public networks available") + return faults.Fault(exc.HTTPNotFound(explanation=msg)) - def private(self, req, server_id): - instance = self._get_instance(req, server_id) - return {'private': self.builder.build_private_parts(instance)} + return {id: view} + + def _get_view_builder(self, req): + return nova.api.openstack.views.addresses.ViewBuilderV10() + + +class ControllerV11(Controller): + + def index(self, req, server_id): + context = req.environ['nova.context'] + interfaces = self._get_virtual_interfaces(context, server_id) + networks = self._get_view_builder(req).build(interfaces) + return {'addresses': networks} def show(self, req, server_id, id): - return faults.Fault(exc.HTTPNotImplemented()) + context = req.environ['nova.context'] + interfaces = self._get_virtual_interfaces(context, server_id) + network = self._get_view_builder(req).build_network(interfaces, id) - def create(self, req, server_id, body): - return faults.Fault(exc.HTTPNotImplemented()) + if network is None: + msg = _("Instance is not a member of specified network") + return faults.Fault(exc.HTTPNotFound(explanation=msg)) - def delete(self, req, server_id, id): - return faults.Fault(exc.HTTPNotImplemented()) + return network + + def _get_virtual_interfaces(self, context, server_id): + try: + return db.api.virtual_interface_get_by_instance(context, server_id) + except exception.InstanceNotFound: + msg = _("Instance does not exist") + raise exc.HTTPNotFound(explanation=msg) + + def _get_view_builder(self, req): + return nova.api.openstack.views.addresses.ViewBuilderV11() + + +def create_resource(version): + controller = { + '1.0': ControllerV10, + '1.1': ControllerV11, + }[version]() + xmlns = { + '1.0': wsgi.XMLNS_V10, + '1.1': wsgi.XMLNS_V11, + }[version] -def create_resource(): metadata = { 'list_collections': { 'public': {'item_name': 'ip', 'item_key': 'addr'}, @@ -72,7 +121,7 @@ def create_resource(): serializers = { 'application/xml': wsgi.XMLDictSerializer(metadata=metadata, - xmlns=wsgi.XMLNS_V10), + xmlns=xmlns), } - return wsgi.Resource(Controller(), serializers=serializers) + return wsgi.Resource(controller, serializers=serializers) diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index fc1ab8d46..3c29c2606 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -19,6 +19,7 @@ import traceback from webob import exc from nova import compute +from nova import db from nova import exception from nova import flags from nova import log as logging @@ -62,7 +63,7 @@ class Controller(object): return exc.HTTPBadRequest(explanation=str(err)) return servers - def _get_view_builder(self, req): + def _build_view(self, req, instance, is_detail=False): raise NotImplementedError() def _limit_items(self, items, req): @@ -88,8 +89,7 @@ class Controller(object): fixed_ip=fixed_ip, recurse_zones=recurse_zones) limited_list = self._limit_items(instance_list, req) - builder = self._get_view_builder(req) - servers = [builder.build(inst, is_detail)['server'] + servers = [self._build_view(req, inst, is_detail)['server'] for inst in limited_list] return dict(servers=servers) @@ -99,8 +99,7 @@ class Controller(object): try: instance = self.compute_api.routing_get( req.environ['nova.context'], id) - builder = self._get_view_builder(req) - return builder.build(instance, is_detail=True) + return self._build_view(req, instance, is_detail=True) except exception.NotFound: return faults.Fault(exc.HTTPNotFound()) @@ -130,8 +129,7 @@ class Controller(object): for key in ['instance_type', 'image_ref']: inst[key] = extra_values[key] - builder = self._get_view_builder(req) - server = builder.build(inst, is_detail=True) + server = self._build_view(req, inst, is_detail=True) server['server']['adminPass'] = extra_values['password'] return server @@ -418,10 +416,10 @@ class ControllerV10(Controller): def _flavor_id_from_req_data(self, data): return data['server']['flavorId'] - def _get_view_builder(self, req): - addresses_builder = nova.api.openstack.views.addresses.ViewBuilderV10() - return nova.api.openstack.views.servers.ViewBuilderV10( - addresses_builder) + def _build_view(self, req, instance, is_detail=False): + addresses = nova.api.openstack.views.addresses.ViewBuilderV10() + builder = nova.api.openstack.views.servers.ViewBuilderV10(addresses) + return builder.build(instance, is_detail=is_detail) def _limit_items(self, items, req): return common.limited(items, req) @@ -481,16 +479,23 @@ class ControllerV11(Controller): href = data['server']['flavorRef'] return common.get_id_from_href(href) - def _get_view_builder(self, req): + def _build_view(self, req, instance, is_detail=False): base_url = req.application_url flavor_builder = nova.api.openstack.views.flavors.ViewBuilderV11( base_url) image_builder = nova.api.openstack.views.images.ViewBuilderV11( base_url) addresses_builder = nova.api.openstack.views.addresses.ViewBuilderV11() - return nova.api.openstack.views.servers.ViewBuilderV11( + builder = nova.api.openstack.views.servers.ViewBuilderV11( addresses_builder, flavor_builder, image_builder, base_url) + context = req.environ['nova.context'] + interfaces = db.api.virtual_interface_get_by_instance(context, + instance['id']) + instance['virtual_interfaces'] = interfaces + + return builder.build(instance, is_detail=is_detail) + def _action_change_password(self, input_dict, req, id): context = req.environ['nova.context'] if (not 'changePassword' in input_dict diff --git a/nova/api/openstack/views/addresses.py b/nova/api/openstack/views/addresses.py index b59eb4751..c7464a7c5 100644 --- a/nova/api/openstack/views/addresses.py +++ b/nova/api/openstack/views/addresses.py @@ -20,13 +20,14 @@ from nova.api.openstack import common class ViewBuilder(object): - ''' Models a server addresses response as a python dictionary.''' + """Models a server addresses response as a python dictionary.""" def build(self, inst): raise NotImplementedError() class ViewBuilderV10(ViewBuilder): + def build(self, inst): private_ips = self.build_private_parts(inst) public_ips = self.build_public_parts(inst) @@ -40,11 +41,30 @@ class ViewBuilderV10(ViewBuilder): class ViewBuilderV11(ViewBuilder): - def build(self, inst): - # TODO(tr3buchet) - this shouldn't be hard coded to 4... - private_ips = utils.get_from_path(inst, 'fixed_ips/address') - private_ips = [dict(version=4, addr=a) for a in private_ips] - public_ips = utils.get_from_path(inst, - 'fixed_ips/floating_ips/address') - public_ips = [dict(version=4, addr=a) for a in public_ips] - return dict(public=public_ips, private=private_ips) + + def build(self, interfaces): + networks = {} + for interface in interfaces: + network_label = interface['network']['label'] + if network_label not in networks: + networks[network_label] = [] + + for fixed_ip in interface['fixed_ips']: + ip = {'addr': fixed_ip['address'], 'version': 4} + networks[network_label].append(ip) + return networks + + def build_network(self, interfaces, network_label): + for interface in interfaces: + if interface['network']['label'] == network_label: + ips = self._extract_fixed_ips(interface) + return {network_label: ips} + return None + + def _extract_fixed_ips(self, interface): + fixed_ips = [] + for fixed_ip in interface['fixed_ips']: + ip = {'addr': fixed_ip['address'], 'version': 4} + fixed_ips.append(ip) + return fixed_ips + diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index cbfa5aae7..691cc48ca 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -77,7 +77,6 @@ class ViewBuilder(object): inst_dict = { 'id': inst['id'], 'name': inst['display_name'], - 'addresses': self.addresses_builder.build(inst), 'status': power_mapping[inst.get('state')]} ctxt = nova.context.get_admin_context() @@ -98,10 +97,15 @@ class ViewBuilder(object): self._build_image(inst_dict, inst) self._build_flavor(inst_dict, inst) + self._build_addresses(inst_dict, inst) inst_dict['uuid'] = inst['uuid'] return dict(server=inst_dict) + def _build_addresses(self, response, inst): + """Return the addresses sub-resource of a server.""" + raise NotImplementedError() + def _build_image(self, response, inst): """Return the image sub-resource of a server.""" raise NotImplementedError() @@ -128,6 +132,9 @@ class ViewBuilderV10(ViewBuilder): if 'instance_type' in dict(inst): response['flavorId'] = inst['instance_type']['flavorid'] + def _build_addresses(self, response, inst): + response['addresses'] = self.addresses_builder.build(inst) + class ViewBuilderV11(ViewBuilder): """Model an Openstack API V1.0 server response.""" @@ -151,6 +158,10 @@ class ViewBuilderV11(ViewBuilder): flavor_ref = self.flavor_builder.generate_href(flavor_id) response["flavorRef"] = flavor_ref + def _build_addresses(self, response, inst): + interfaces = inst['virtual_interfaces'] + response['addresses'] = self.addresses_builder.build(interfaces) + def _build_extra(self, response, inst): self._build_links(response, inst) -- cgit From 94a6af26e46d4df35294ad0bf4dc4883b7bf052e Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Wed, 6 Jul 2011 14:47:41 -0400 Subject: Further test update and begin correcting serialization --- nova/api/openstack/images.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index bde9507c8..2e3d4f157 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -267,8 +267,9 @@ class ImageXMLSerializer(wsgi.XMLDictSerializer): metadata = { "attributes": { "image": ["id", "name", "updated", "created", "status", - "serverId", "progress", "serverRef"], - "link": ["rel", "type", "href"], + "serverId", "progress"], + "link": ["rel", "href"], + "server": ["name", "id"], }, } -- cgit From bb09e0e1bc1c587e7677eb5db68a8fbd293ecd5b Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Wed, 6 Jul 2011 16:28:10 -0400 Subject: first round --- nova/api/openstack/accounts.py | 6 +- nova/api/openstack/backup_schedules.py | 13 +-- nova/api/openstack/consoles.py | 12 +-- nova/api/openstack/contrib/floating_ips.py | 4 +- nova/api/openstack/create_instance_helper.py | 2 +- nova/api/openstack/flavors.py | 6 +- nova/api/openstack/image_metadata.py | 5 +- nova/api/openstack/images.py | 6 +- nova/api/openstack/ips.py | 5 +- nova/api/openstack/limits.py | 6 +- nova/api/openstack/server_metadata.py | 6 +- nova/api/openstack/servers.py | 10 +- nova/api/openstack/shared_ip_groups.py | 12 +-- nova/api/openstack/users.py | 6 +- nova/api/openstack/versions.py | 5 +- nova/api/openstack/wsgi.py | 144 +++++++++++++++++---------- nova/api/openstack/zones.py | 9 +- 17 files changed, 153 insertions(+), 104 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/accounts.py b/nova/api/openstack/accounts.py index 0dcd37217..e3201b14f 100644 --- a/nova/api/openstack/accounts.py +++ b/nova/api/openstack/accounts.py @@ -87,8 +87,8 @@ def create_resource(): }, } - serializers = { + body_serializers = { 'application/xml': wsgi.XMLDictSerializer(metadata=metadata), } - - return wsgi.Resource(Controller(), serializers=serializers) + serializer = wsgi.ResponseSerializer(body_serializers) + return wsgi.Resource(Controller(), serializer=serializer) diff --git a/nova/api/openstack/backup_schedules.py b/nova/api/openstack/backup_schedules.py index 71a14d4ce..3e95aedf3 100644 --- a/nova/api/openstack/backup_schedules.py +++ b/nova/api/openstack/backup_schedules.py @@ -34,20 +34,20 @@ class Controller(object): def __init__(self): pass - def index(self, req, server_id): + def index(self, req, server_id, **kwargs): """ Returns the list of backup schedules for a given instance """ return faults.Fault(exc.HTTPNotImplemented()) - def show(self, req, server_id, id): + def show(self, req, server_id, id, **kwargs): """ Returns a single backup schedule for a given instance """ return faults.Fault(exc.HTTPNotImplemented()) - def create(self, req, server_id, body): + def create(self, req, server_id, **kwargs): """ No actual update method required, since the existing API allows both create and update through a POST """ return faults.Fault(exc.HTTPNotImplemented()) - def delete(self, req, server_id, id): + def delete(self, req, server_id, id, **kwargs): """ Deletes an existing backup schedule """ return faults.Fault(exc.HTTPNotImplemented()) @@ -59,9 +59,10 @@ def create_resource(): }, } - serializers = { + body_serializers = { 'application/xml': wsgi.XMLDictSerializer(xmlns=wsgi.XMLNS_V10, metadata=metadata), } - return wsgi.Resource(Controller(), serializers=serializers) + serializer = wsgi.ResponseSerializer(body_serializers) + return wsgi.Resource(Controller(), serializer=serializer) diff --git a/nova/api/openstack/consoles.py b/nova/api/openstack/consoles.py index bccf04d8f..7a43fba96 100644 --- a/nova/api/openstack/consoles.py +++ b/nova/api/openstack/consoles.py @@ -90,14 +90,4 @@ class Controller(object): def create_resource(): - metadata = { - 'attributes': { - 'console': [], - }, - } - - serializers = { - 'application/xml': wsgi.XMLDictSerializer(metadata=metadata), - } - - return wsgi.Resource(Controller(), serializers=serializers) + return wsgi.Resource(Controller()) diff --git a/nova/api/openstack/contrib/floating_ips.py b/nova/api/openstack/contrib/floating_ips.py index b27336574..b4a211857 100644 --- a/nova/api/openstack/contrib/floating_ips.py +++ b/nova/api/openstack/contrib/floating_ips.py @@ -78,7 +78,7 @@ class FloatingIPController(object): return _translate_floating_ips_view(floating_ips) - def create(self, req, body): + def create(self, req): context = req.environ['nova.context'] try: @@ -124,7 +124,7 @@ class FloatingIPController(object): "floating_ip": floating_ip, "fixed_ip": fixed_ip}} - def disassociate(self, req, id, body): + def disassociate(self, req, id): """ POST /floating_ips/{id}/disassociate """ context = req.environ['nova.context'] floating_ip = self.network_api.get_floating_ip(context, id) diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index 1066713a3..2654e3c40 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -289,7 +289,7 @@ class ServerXMLDeserializer(wsgi.XMLDeserializer): """Deserialize an xml-formatted server create request""" dom = minidom.parseString(string) server = self._extract_server(dom) - return {'server': server} + return {'body': {'server': server}} def _extract_server(self, node): """Marshal the server attribute of a parsed request""" diff --git a/nova/api/openstack/flavors.py b/nova/api/openstack/flavors.py index a21ff6cb2..6fab13147 100644 --- a/nova/api/openstack/flavors.py +++ b/nova/api/openstack/flavors.py @@ -85,8 +85,10 @@ def create_resource(version='1.0'): '1.1': wsgi.XMLNS_V11, }[version] - serializers = { + body_serializers = { 'application/xml': wsgi.XMLDictSerializer(xmlns=xmlns), } - return wsgi.Resource(controller, serializers=serializers) + serializer = wsgi.ResponseSerializer(body_serializers) + + return wsgi.Resource(controller, serializer=serializer) diff --git a/nova/api/openstack/image_metadata.py b/nova/api/openstack/image_metadata.py index 638b1ec15..4f33844fa 100644 --- a/nova/api/openstack/image_metadata.py +++ b/nova/api/openstack/image_metadata.py @@ -160,8 +160,9 @@ class ImageMetadataXMLSerializer(wsgi.XMLDictSerializer): def create_resource(): - serializers = { + body_serializers = { 'application/xml': ImageMetadataXMLSerializer(), } + serializer = wsgi.ResponseSerializer(body_serializers) - return wsgi.Resource(Controller(), serializers=serializers) + return wsgi.Resource(Controller(), serializer=serializer) diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index bde9507c8..178374721 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -338,8 +338,10 @@ def create_resource(version='1.0'): '1.1': ImageXMLSerializer(), }[version] - serializers = { + body_serializers = { 'application/xml': xml_serializer, } - return wsgi.Resource(controller, serializers=serializers) + serializer = wsgi.ResponseSerializer(body_serializers) + + return wsgi.Resource(controller, serializer=serializer) diff --git a/nova/api/openstack/ips.py b/nova/api/openstack/ips.py index 71646b6d3..23e5432d6 100644 --- a/nova/api/openstack/ips.py +++ b/nova/api/openstack/ips.py @@ -70,9 +70,10 @@ def create_resource(): }, } - serializers = { + body_serializers = { 'application/xml': wsgi.XMLDictSerializer(metadata=metadata, xmlns=wsgi.XMLNS_V10), } + serializer = wsgi.ResponseSerializer(body_serializers) - return wsgi.Resource(Controller(), serializers=serializers) + return wsgi.Resource(Controller(), serializer=serializer) diff --git a/nova/api/openstack/limits.py b/nova/api/openstack/limits.py index fede96e33..d08287f6b 100644 --- a/nova/api/openstack/limits.py +++ b/nova/api/openstack/limits.py @@ -97,12 +97,14 @@ def create_resource(version='1.0'): }, } - serializers = { + body_serializers = { 'application/xml': wsgi.XMLDictSerializer(xmlns=xmlns, metadata=metadata), } - return wsgi.Resource(controller, serializers=serializers) + serializer = wsgi.ResponseSerializer(body_serializers) + + return wsgi.Resource(controller, serializer=serializer) class Limit(object): diff --git a/nova/api/openstack/server_metadata.py b/nova/api/openstack/server_metadata.py index 8a314de22..3b9169f81 100644 --- a/nova/api/openstack/server_metadata.py +++ b/nova/api/openstack/server_metadata.py @@ -123,8 +123,10 @@ class Controller(object): def create_resource(): - serializers = { + body_serializers = { 'application/xml': wsgi.XMLDictSerializer(xmlns=wsgi.XMLNS_V11), } - return wsgi.Resource(Controller(), serializers=serializers) + serializer = wsgi.ResponseSerializer(body_serializers) + + return wsgi.Resource(Controller(), serializer=serializer) diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index fc1ab8d46..9953a8fb7 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -616,14 +616,16 @@ def create_resource(version='1.0'): '1.1': wsgi.XMLNS_V11, }[version] - serializers = { + body_serializers = { 'application/xml': wsgi.XMLDictSerializer(metadata=metadata, xmlns=xmlns), } - deserializers = { + body_deserializers = { 'application/xml': helper.ServerXMLDeserializer(), } - return wsgi.Resource(controller, serializers=serializers, - deserializers=deserializers) + serializer = wsgi.ResponseSerializer(body_serializers) + deserializer = wsgi.RequestDeserializer(body_deserializers) + + return wsgi.Resource(controller, deserializer, serializer) diff --git a/nova/api/openstack/shared_ip_groups.py b/nova/api/openstack/shared_ip_groups.py index 4f11f8dfb..cf2ddbabb 100644 --- a/nova/api/openstack/shared_ip_groups.py +++ b/nova/api/openstack/shared_ip_groups.py @@ -24,27 +24,27 @@ from nova.api.openstack import wsgi class Controller(object): """ The Shared IP Groups Controller for the Openstack API """ - def index(self, req): + def index(self, req, **kwargs): """ Returns a list of Shared IP Groups for the user """ raise faults.Fault(exc.HTTPNotImplemented()) - def show(self, req, id): + def show(self, req, id, **kwargs): """ Shows in-depth information on a specific Shared IP Group """ raise faults.Fault(exc.HTTPNotImplemented()) - def update(self, req, id, body): + def update(self, req, id, **kwargs): """ You can't update a Shared IP Group """ raise faults.Fault(exc.HTTPNotImplemented()) - def delete(self, req, id): + def delete(self, req, id, **kwargs): """ Deletes a Shared IP Group """ raise faults.Fault(exc.HTTPNotImplemented()) - def detail(self, req): + def detail(self, req, **kwargs): """ Returns a complete list of Shared IP Groups """ raise faults.Fault(exc.HTTPNotImplemented()) - def create(self, req, body): + def create(self, req, **kwargs): """ Creates a new Shared IP group """ raise faults.Fault(exc.HTTPNotImplemented()) diff --git a/nova/api/openstack/users.py b/nova/api/openstack/users.py index 50975fc1f..6ae1eaf2a 100644 --- a/nova/api/openstack/users.py +++ b/nova/api/openstack/users.py @@ -105,8 +105,10 @@ def create_resource(): }, } - serializers = { + body_serializers = { 'application/xml': wsgi.XMLDictSerializer(metadata=metadata), } - return wsgi.Resource(Controller(), serializers=serializers) + serializer = wsgi.ResponseSerializer(body_serializers) + + return wsgi.Resource(Controller(), serializer=serializer) diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py index 4c682302f..a634c3267 100644 --- a/nova/api/openstack/versions.py +++ b/nova/api/openstack/versions.py @@ -31,11 +31,12 @@ class Versions(wsgi.Resource): } } - serializers = { + body_serializers = { 'application/xml': wsgi.XMLDictSerializer(metadata=metadata), } + serializer = wsgi.ResponseSerializer(body_serializers) - wsgi.Resource.__init__(self, None, serializers=serializers) + wsgi.Resource.__init__(self, None, serializer=serializer) def dispatch(self, request, *args): """Respond to a request for all OpenStack API versions.""" diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index 5b6e3cb1d..6726531be 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -46,7 +46,7 @@ class Request(webob.Request): """ if not "Content-Type" in self.headers: - raise exception.InvalidContentType(content_type=None) + return None allowed_types = ("application/xml", "application/json") content_type = self.content_type @@ -67,17 +67,20 @@ class TextDeserializer(object): def default(self, datastring): """Default deserialization code should live here""" - raise NotImplementedError() + return {} class JSONDeserializer(TextDeserializer): - def default(self, datastring): + def _from_json(self, datastring): try: return utils.loads(datastring) except ValueError: - raise exception.MalformedRequestBody( - reason=_("malformed JSON in request body")) + msg = _("cannot understand JSON") + raise exception.MalformedRequestBody(reason=msg) + + def default(self, datastring): + return {'body': self._from_json(datastring)} class XMLDeserializer(TextDeserializer): @@ -90,15 +93,15 @@ class XMLDeserializer(TextDeserializer): super(XMLDeserializer, self).__init__() self.metadata = metadata or {} - def default(self, datastring): + def _from_xml(self, datastring): plurals = set(self.metadata.get('plurals', {})) try: node = minidom.parseString(datastring).childNodes[0] return {node.nodeName: self._from_xml_node(node, plurals)} except expat.ExpatError: - raise exception.MalformedRequestBody( - reason=_("malformed XML in request body")) + msg = _("cannot understand XML") + raise exception.MalformedRequestBody(reason=msg) def _from_xml_node(self, node, listnames): """Convert a minidom node to a simple Python type. @@ -121,21 +124,27 @@ class XMLDeserializer(TextDeserializer): listnames) return result + def default(self, datastring): + return {'body': self._from_xml(datastring)} + + +class RequestHeadersDeserializer(object): + def deserialize(self, request, action): + return {} + class RequestDeserializer(object): """Break up a Request object into more useful pieces.""" - def __init__(self, deserializers=None): - """ - :param deserializers: dictionary of content-type-specific deserializers - - """ - self.deserializers = { + def __init__(self, body_deserializers=None, headers_deserializer=None): + self.body_deserializers = { 'application/xml': XMLDeserializer(), 'application/json': JSONDeserializer(), } + self.body_deserializers.update(body_deserializers or {}) - self.deserializers.update(deserializers or {}) + self.headers_deserializer = headers_deserializer or \ + RequestHeadersDeserializer() def deserialize(self, request): """Extract necessary pieces of the request. @@ -149,26 +158,45 @@ class RequestDeserializer(object): action_args = self.get_action_args(request.environ) action = action_args.pop('action', None) - if request.method.lower() in ('post', 'put'): - if len(request.body) == 0: - action_args['body'] = None - else: - content_type = request.get_content_type() - deserializer = self.get_deserializer(content_type) - - try: - body = deserializer.deserialize(request.body, action) - action_args['body'] = body - except exception.InvalidContentType: - action_args['body'] = None + action_args.update(self.deserialize_headers(request, action)) + action_args.update(self.deserialize_body(request, action)) accept = self.get_expected_content_type(request) return (action, action_args, accept) - def get_deserializer(self, content_type): + def deserialize_headers(self, request, action): + return self.headers_deserializer.deserialize(request, action) + + def deserialize_body(self, request, action): + try: + content_type = request.get_content_type() + + if content_type is None: + LOG.debug(_("No Content-Type provided in request")) + return {} + + if 'Content-Length' not in request.headers: + msg = _("No Content-Length provided in request") + LOG.debug(msg) + return {} + + if not request.content_length > 0: + msg = _("Request has Content-Length of zero") + LOG.debug(msg) + return {} + + deserializer = self.get_body_deserializer(content_type) + + except exception.InvalidContentType: + LOG.debug(_("Unable to read body as provided Content-Type")) + raise + + return deserializer.deserialize(request.body, action) + + def get_body_deserializer(self, content_type): try: - return self.deserializers[content_type] + return self.body_deserializers[content_type] except (KeyError, TypeError): raise exception.InvalidContentType(content_type=content_type) @@ -295,19 +323,23 @@ class XMLDictSerializer(DictSerializer): return result +class ResponseHeadersSerializer(object): + def serialize(self, response, data, action): + response.status_int = 200 + + class ResponseSerializer(object): """Encode the necessary pieces into a response object""" - def __init__(self, serializers=None): - """ - :param serializers: dictionary of content-type-specific serializers - - """ - self.serializers = { + def __init__(self, body_serializers=None, headers_serializer=None): + self.body_serializers = { 'application/xml': XMLDictSerializer(), 'application/json': JSONDictSerializer(), } - self.serializers.update(serializers or {}) + self.body_serializers.update(body_serializers or {}) + + self.headers_serializer = headers_serializer or \ + ResponseHeadersSerializer() def serialize(self, response_data, content_type, action='default'): """Serialize a dict into a string and wrap in a wsgi.Request object. @@ -317,16 +349,21 @@ class ResponseSerializer(object): """ response = webob.Response() - response.headers['Content-Type'] = content_type + self.serialize_headers(response, response_data, action) + self.serialize_body(response, response_data, content_type, action) + return response - serializer = self.get_serializer(content_type) - response.body = serializer.serialize(response_data, action) + def serialize_headers(self, response, data, action): + self.headers_serializer.serialize(response, data, action) - return response + def serialize_body(self, response, data, content_type, action): + response.headers['Content-Type'] = content_type + serializer = self.get_body_serializer(content_type) + response.body = serializer.serialize(data, action) - def get_serializer(self, content_type): + def get_body_serializer(self, content_type): try: - return self.serializers[content_type] + return self.body_serializers[content_type] except (KeyError, TypeError): raise exception.InvalidContentType(content_type=content_type) @@ -343,16 +380,18 @@ class Resource(wsgi.Application): serialized by requested content type. """ - def __init__(self, controller, serializers=None, deserializers=None): + def __init__(self, controller, deserializer=None, serializer=None): """ :param controller: object that implement methods created by routes lib - :param serializers: dict of content-type specific text serializers - :param deserializers: dict of content-type specific text deserializers + :param deserializer: object that can serialize the output of a + controller into a webob response + :param serializer: object that can deserialize a webob request + into necessary pieces """ self.controller = controller - self.serializer = ResponseSerializer(serializers) - self.deserializer = RequestDeserializer(deserializers) + self.deserializer = deserializer or RequestDeserializer() + self.serializer = serializer or ResponseSerializer() @webob.dec.wsgify(RequestClass=Request) def __call__(self, request): @@ -362,8 +401,7 @@ class Resource(wsgi.Application): "url": request.url}) try: - action, action_args, accept = self.deserializer.deserialize( - request) + action, args, accept = self.deserializer.deserialize(request) except exception.InvalidContentType: msg = _("Unsupported Content-Type") return webob.exc.HTTPBadRequest(explanation=msg) @@ -371,7 +409,7 @@ class Resource(wsgi.Application): msg = _("Malformed request body") return faults.Fault(webob.exc.HTTPBadRequest(explanation=msg)) - action_result = self.dispatch(request, action, action_args) + action_result = self.dispatch(request, action, args) #TODO(bcwaldon): find a more elegant way to pass through non-dict types if type(action_result) is dict: @@ -394,4 +432,8 @@ class Resource(wsgi.Application): """Find action-spefic method on controller and call it.""" controller_method = getattr(self.controller, action) - return controller_method(req=request, **action_args) + try: + return controller_method(req=request, **action_args) + except TypeError, exc: + LOG.debug(str(exc)) + return webob.exc.HTTPBadRequest() diff --git a/nova/api/openstack/zones.py b/nova/api/openstack/zones.py index 8864f825b..2e02ec380 100644 --- a/nova/api/openstack/zones.py +++ b/nova/api/openstack/zones.py @@ -196,14 +196,15 @@ def create_resource(version): }, } - serializers = { + body_serializers = { 'application/xml': wsgi.XMLDictSerializer(xmlns=wsgi.XMLNS_V10, metadata=metadata), } + serializer = wsgi.ResponseSerializer(body_serializers) - deserializers = { + body_deserializers = { 'application/xml': helper.ServerXMLDeserializer(), } + deserializer = wsgi.RequestDeserializer(body_deserializers) - return wsgi.Resource(controller, serializers=serializers, - deserializers=deserializers) + return wsgi.Resource(controller, deserializer, serializer) -- cgit From 0415c413872697c6f9fecc28928af0525780f868 Mon Sep 17 00:00:00 2001 From: Mark Washenberger Date: Wed, 6 Jul 2011 16:40:00 -0400 Subject: correct test_show --- nova/api/openstack/images.py | 70 ++++++++++++++++++++++++++++++-------------- 1 file changed, 48 insertions(+), 22 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index 2e3d4f157..750b034b9 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -262,34 +262,60 @@ class ControllerV11(Controller): return {'instance_ref': server_ref} -class ImageXMLSerializer(wsgi.XMLDictSerializer): +class ImageXMLSerializer(wsgi.DictSerializer): - metadata = { - "attributes": { - "image": ["id", "name", "updated", "created", "status", - "serverId", "progress"], - "link": ["rel", "href"], - "server": ["name", "id"], - }, - } - - xmlns = wsgi.XMLNS_V11 + xmlns = {'': wsgi.XMLNS_V11, 'atom': "http://www.w3.org/2005/Atom"} def __init__(self): self.metadata_serializer = image_metadata.ImageMetadataXMLSerializer() + def _add_xmlns(self, node): + for key, ns in self.xmlns.iteritems(): + if key is not '': + name = 'xmlns:%s' % key + else: + name = 'xmlns' + node.setAttribute(name, ns) + def _image_to_xml(self, xml_doc, image): - try: - metadata = image.pop('metadata').items() - except Exception: - LOG.debug(_("Image object missing metadata attribute")) - metadata = {} + image_node = xml_doc.createElement('image') + self._add_image_attributes(image_node, image) - node = self._to_xml_node(xml_doc, self.metadata, 'image', image) - metadata_node = self.metadata_serializer.meta_list_to_xml(xml_doc, - metadata) - node.appendChild(metadata_node) - return node + server_node = self._create_server_node(xml_doc, image['server']) + image_node.appendChild(server_node) + + metadata = image.get('metadata', {}) + metadata_node = self._create_metadata_node(xml_doc, metadata.items()) + image_node.appendChild(metadata_node) + + self._add_atom_links(xml_doc, image_node, image['links']) + + return image_node + + def _add_image_attributes(self, node, image): + node.setAttribute('id', str(image['id'])) + node.setAttribute('name', image['name']) + node.setAttribute('created', image['created']) + node.setAttribute('updated', image['updated']) + node.setAttribute('status', image['status']) + node.setAttribute('progress', str(image['progress'])) + + def _create_server_node(self, xml_doc, server): + server_node = xml_doc.createElement('server') + server_node.setAttribute('id', str(server['id'])) + server_node.setAttribute('name', server['name']) + self._add_atom_links(xml_doc, server_node, server['links']) + return server_node + + def _create_metadata_node(self, xml_doc, metadata): + return self.metadata_serializer.meta_list_to_xml(xml_doc, metadata) + + def _add_atom_links(self, xml_doc, node, links): + for link in links: + link_node = xml_doc.createElement('atom:link') + link_node.setAttribute('rel', link['rel']) + link_node.setAttribute('href', link['href']) + node.appendChild(link_node) def _image_list_to_xml(self, xml_doc, images): container_node = xml_doc.createElement('images') @@ -307,7 +333,7 @@ class ImageXMLSerializer(wsgi.XMLDictSerializer): def _image_list_to_xml_string(self, images): xml_doc = minidom.Document() container_node = self._image_list_to_xml(xml_doc, images) - self._add_xmlns(container_node) + self._add_xmlns(item_node) return container_node.toprettyxml(indent=' ') def detail(self, images_dict): -- cgit From 8caf69dc93d9112e9be8989cd2136a407e09df44 Mon Sep 17 00:00:00 2001 From: Mark Washenberger Date: Thu, 7 Jul 2011 09:24:40 -0400 Subject: progress and server are optional --- nova/api/openstack/images.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index 750b034b9..eaa7aef5a 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -281,8 +281,9 @@ class ImageXMLSerializer(wsgi.DictSerializer): image_node = xml_doc.createElement('image') self._add_image_attributes(image_node, image) - server_node = self._create_server_node(xml_doc, image['server']) - image_node.appendChild(server_node) + if 'server' in image: + server_node = self._create_server_node(xml_doc, image['server']) + image_node.appendChild(server_node) metadata = image.get('metadata', {}) metadata_node = self._create_metadata_node(xml_doc, metadata.items()) @@ -298,7 +299,8 @@ class ImageXMLSerializer(wsgi.DictSerializer): node.setAttribute('created', image['created']) node.setAttribute('updated', image['updated']) node.setAttribute('status', image['status']) - node.setAttribute('progress', str(image['progress'])) + if 'progress' in image: + node.setAttribute('progress', str(image['progress'])) def _create_server_node(self, xml_doc, server): server_node = xml_doc.createElement('server') @@ -333,7 +335,7 @@ class ImageXMLSerializer(wsgi.DictSerializer): def _image_list_to_xml_string(self, images): xml_doc = minidom.Document() container_node = self._image_list_to_xml(xml_doc, images) - self._add_xmlns(item_node) + self._add_xmlns(container_node) return container_node.toprettyxml(indent=' ') def detail(self, images_dict): -- cgit From a92158cee2a57316252ec6fd0d6c0c4f1e7a1fcf Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Thu, 7 Jul 2011 09:26:25 -0400 Subject: moved remove_version to common.py --- nova/api/openstack/common.py | 3 +++ nova/api/openstack/views/flavors.py | 5 +---- nova/api/openstack/views/images.py | 7 +++---- nova/api/openstack/views/servers.py | 5 +---- 4 files changed, 8 insertions(+), 12 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py index aa8911b62..8794bca6d 100644 --- a/nova/api/openstack/common.py +++ b/nova/api/openstack/common.py @@ -134,3 +134,6 @@ 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')) + +def remove_version(base_url): + return base_url.rsplit('/', 1).pop(0) diff --git a/nova/api/openstack/views/flavors.py b/nova/api/openstack/views/flavors.py index 4e609930c..d967c2af0 100644 --- a/nova/api/openstack/views/flavors.py +++ b/nova/api/openstack/views/flavors.py @@ -92,8 +92,5 @@ class ViewBuilderV11(ViewBuilder): def generate_bookmark(self, flavor_id): """Create an url that refers to a specific flavor id.""" - return "%s/flavors/%s" % (self._remove_version(self.base_url), + return "%s/flavors/%s" % (common.remove_version(self.base_url), flavor_id) - - def _remove_version(self, base_url): - return base_url.rsplit('/', 1).pop(0) diff --git a/nova/api/openstack/views/images.py b/nova/api/openstack/views/images.py index 5ab02671c..9d6722326 100644 --- a/nova/api/openstack/views/images.py +++ b/nova/api/openstack/views/images.py @@ -17,6 +17,8 @@ import os.path +from nova.api.openstack import common + class ViewBuilder(object): """Base class for generating responses to OpenStack API image requests.""" @@ -122,8 +124,5 @@ class ViewBuilderV11(ViewBuilder): def generate_bookmark(self, image_id): """Create an url that refers to a specific flavor id.""" - return os.path.join(self._remove_version(self._url), + return os.path.join(common.remove_version(self._url), "images", str(image_id)) - - def _remove_version(self, base_url): - return base_url.rsplit('/', 1).pop(0) diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index 1c6dbf87d..b85fceb19 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -177,8 +177,5 @@ class ViewBuilderV11(ViewBuilder): def generate_bookmark(self, server_id): """Create an url that refers to a specific flavor id.""" - return os.path.join(self._remove_version(self.base_url), + return os.path.join(common.remove_version(self.base_url), "servers", str(server_id)) - - def _remove_version(self, base_url): - return base_url.rsplit('/', 1).pop(0) -- cgit From bf851c7f403c7be8d8f27274fa5216cfa6eaf4f4 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Thu, 7 Jul 2011 09:42:13 -0400 Subject: Renamed function --- nova/api/openstack/common.py | 9 ++++++++- nova/api/openstack/views/flavors.py | 2 +- nova/api/openstack/views/images.py | 2 +- nova/api/openstack/views/servers.py | 2 +- 4 files changed, 11 insertions(+), 4 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py index 8794bca6d..48773291c 100644 --- a/nova/api/openstack/common.py +++ b/nova/api/openstack/common.py @@ -135,5 +135,12 @@ def get_id_from_href(href): LOG.debug(_("Error extracting id from href: %s") % href) raise webob.exc.HTTPBadRequest(_('could not parse id from href')) -def remove_version(base_url): + +def remove_version_from_href(base_url): + """Removes the api version from the href. + + Given: 'http://www.nova.com/v1.1/123' + Returns: 'http://www.nova.com/123' + + """ return base_url.rsplit('/', 1).pop(0) diff --git a/nova/api/openstack/views/flavors.py b/nova/api/openstack/views/flavors.py index d967c2af0..d2f7e3e56 100644 --- a/nova/api/openstack/views/flavors.py +++ b/nova/api/openstack/views/flavors.py @@ -92,5 +92,5 @@ class ViewBuilderV11(ViewBuilder): def generate_bookmark(self, flavor_id): """Create an url that refers to a specific flavor id.""" - return "%s/flavors/%s" % (common.remove_version(self.base_url), + return "%s/flavors/%s" % (common.remove_version_from_href(self.base_url), flavor_id) diff --git a/nova/api/openstack/views/images.py b/nova/api/openstack/views/images.py index 9d6722326..005341c62 100644 --- a/nova/api/openstack/views/images.py +++ b/nova/api/openstack/views/images.py @@ -124,5 +124,5 @@ class ViewBuilderV11(ViewBuilder): def generate_bookmark(self, image_id): """Create an url that refers to a specific flavor id.""" - return os.path.join(common.remove_version(self._url), + return os.path.join(common.remove_version_from_href(self._url), "images", str(image_id)) diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index b85fceb19..67fb6a84e 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -177,5 +177,5 @@ class ViewBuilderV11(ViewBuilder): def generate_bookmark(self, server_id): """Create an url that refers to a specific flavor id.""" - return os.path.join(common.remove_version(self.base_url), + return os.path.join(common.remove_version_from_href(self.base_url), "servers", str(server_id)) -- cgit From 19e4cef2518e2c1e02e27137cadea55861d092c4 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Thu, 7 Jul 2011 10:02:06 -0400 Subject: pep8 --- nova/api/openstack/common.py | 2 +- nova/api/openstack/views/flavors.py | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py index 48773291c..9aa384f33 100644 --- a/nova/api/openstack/common.py +++ b/nova/api/openstack/common.py @@ -138,7 +138,7 @@ def get_id_from_href(href): def remove_version_from_href(base_url): """Removes the api version from the href. - + Given: 'http://www.nova.com/v1.1/123' Returns: 'http://www.nova.com/123' diff --git a/nova/api/openstack/views/flavors.py b/nova/api/openstack/views/flavors.py index d2f7e3e56..0403ece1b 100644 --- a/nova/api/openstack/views/flavors.py +++ b/nova/api/openstack/views/flavors.py @@ -92,5 +92,7 @@ class ViewBuilderV11(ViewBuilder): def generate_bookmark(self, flavor_id): """Create an url that refers to a specific flavor id.""" - return "%s/flavors/%s" % (common.remove_version_from_href(self.base_url), - flavor_id) + return "%s/flavors/%s" % ( + common.remove_version_from_href(self.base_url), + flavor_id, + ) -- cgit From b50e92d43f958bf966fce4f608daa467b40453c1 Mon Sep 17 00:00:00 2001 From: Mark Washenberger Date: Thu, 7 Jul 2011 10:48:17 -0400 Subject: make server and image metadata optional --- nova/api/openstack/images.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index eaa7aef5a..8746db4e0 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -285,9 +285,10 @@ class ImageXMLSerializer(wsgi.DictSerializer): server_node = self._create_server_node(xml_doc, image['server']) image_node.appendChild(server_node) - metadata = image.get('metadata', {}) - metadata_node = self._create_metadata_node(xml_doc, metadata.items()) - image_node.appendChild(metadata_node) + metadata = image.get('metadata', {}).items() + if len(metadata) > 0: + metadata_node = self._create_metadata_node(xml_doc, metadata) + image_node.appendChild(metadata_node) self._add_atom_links(xml_doc, image_node, image['links']) -- cgit From ddc33b8163423c5138b40885cb3430104896c676 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Thu, 7 Jul 2011 11:36:45 -0400 Subject: Added image index --- nova/api/openstack/images.py | 41 ++++++++++++++++++++++++++++++++--------- 1 file changed, 32 insertions(+), 9 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index 8746db4e0..a81dd4299 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -278,6 +278,13 @@ class ImageXMLSerializer(wsgi.DictSerializer): node.setAttribute(name, ns) def _image_to_xml(self, xml_doc, image): + image_node = xml_doc.createElement('image') + image_node.setAttribute('id', str(image['id'])) + image_node.setAttribute('name', image['name']) + self._add_atom_links(xml_doc, image_node, image['links']) + return image_node + + def _image_to_xml_detailed(self, xml_doc, image): image_node = xml_doc.createElement('image') self._add_image_attributes(image_node, image) @@ -320,33 +327,49 @@ class ImageXMLSerializer(wsgi.DictSerializer): link_node.setAttribute('href', link['href']) node.appendChild(link_node) - def _image_list_to_xml(self, xml_doc, images): + def _image_list_to_xml(self, xml_doc, images, image_to_xml): container_node = xml_doc.createElement('images') for image in images: - item_node = self._image_to_xml(xml_doc, image) + item_node = image_to_xml(xml_doc, image) container_node.appendChild(item_node) return container_node - def _image_to_xml_string(self, image): + def _image_to_xml_string(self, image, detailed): xml_doc = minidom.Document() - item_node = self._image_to_xml(xml_doc, image) + if detailed: + image_to_xml = self._image_to_xml_detailed + else: + image_to_xml = self._image_to_xml + item_node = image_to_xml(xml_doc, image) self._add_xmlns(item_node) return item_node.toprettyxml(indent=' ') - def _image_list_to_xml_string(self, images): + def _image_list_to_xml_string(self, images, detailed): xml_doc = minidom.Document() - container_node = self._image_list_to_xml(xml_doc, images) + if detailed: + image_to_xml = self._image_to_xml_detailed + else: + image_to_xml = self._image_to_xml + container_node = self._image_list_to_xml(xml_doc, images, image_to_xml) + self._add_xmlns(container_node) return container_node.toprettyxml(indent=' ') + def index(self, images_dict): + return self._image_list_to_xml_string(images_dict['images'], + detailed=False) + def detail(self, images_dict): - return self._image_list_to_xml_string(images_dict['images']) + return self._image_list_to_xml_string(images_dict['images'], + detailed=True) def show(self, image_dict): - return self._image_to_xml_string(image_dict['image']) + return self._image_to_xml_string(image_dict['image'], + detailed=True) def create(self, image_dict): - return self._image_to_xml_string(image_dict['image']) + return self._image_to_xml_string(image_dict['image'], + detailed=True) def create_resource(version='1.0'): -- cgit From 49683f2f84a9eb4436c63465d11ae8f451265eae Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Thu, 7 Jul 2011 14:08:57 -0400 Subject: Dried up images XML serialization --- nova/api/openstack/images.py | 80 ++++++++++++++++++------------------------- nova/api/openstack/servers.py | 12 +++++++ nova/api/openstack/wsgi.py | 12 +++++++ 3 files changed, 58 insertions(+), 46 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index a81dd4299..bc7517c40 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -27,6 +27,7 @@ from nova import utils from nova.api.openstack import common from nova.api.openstack import faults from nova.api.openstack import image_metadata +from nova.api.openstack import servers from nova.api.openstack.views import images as images_view from nova.api.openstack import wsgi @@ -262,26 +263,19 @@ class ControllerV11(Controller): return {'instance_ref': server_ref} -class ImageXMLSerializer(wsgi.DictSerializer): +class ImageXMLSerializer(wsgi.XMLDictSerializer): - xmlns = {'': wsgi.XMLNS_V11, 'atom': "http://www.w3.org/2005/Atom"} + xmlns = wsgi.XMLNS_V11 def __init__(self): self.metadata_serializer = image_metadata.ImageMetadataXMLSerializer() - - def _add_xmlns(self, node): - for key, ns in self.xmlns.iteritems(): - if key is not '': - name = 'xmlns:%s' % key - else: - name = 'xmlns' - node.setAttribute(name, ns) + self.server_serializer = servers.ServerXMLSerializer() def _image_to_xml(self, xml_doc, image): image_node = xml_doc.createElement('image') image_node.setAttribute('id', str(image['id'])) image_node.setAttribute('name', image['name']) - self._add_atom_links(xml_doc, image_node, image['links']) + self._create_link_nodes(xml_doc, image_node, image['links']) return image_node def _image_to_xml_detailed(self, xml_doc, image): @@ -297,7 +291,7 @@ class ImageXMLSerializer(wsgi.DictSerializer): metadata_node = self._create_metadata_node(xml_doc, metadata) image_node.appendChild(metadata_node) - self._add_atom_links(xml_doc, image_node, image['links']) + self._create_link_nodes(xml_doc, image_node, image['links']) return image_node @@ -310,25 +304,19 @@ class ImageXMLSerializer(wsgi.DictSerializer): if 'progress' in image: node.setAttribute('progress', str(image['progress'])) - def _create_server_node(self, xml_doc, server): - server_node = xml_doc.createElement('server') - server_node.setAttribute('id', str(server['id'])) - server_node.setAttribute('name', server['name']) - self._add_atom_links(xml_doc, server_node, server['links']) - return server_node - def _create_metadata_node(self, xml_doc, metadata): return self.metadata_serializer.meta_list_to_xml(xml_doc, metadata) - def _add_atom_links(self, xml_doc, node, links): - for link in links: - link_node = xml_doc.createElement('atom:link') - link_node.setAttribute('rel', link['rel']) - link_node.setAttribute('href', link['href']) - node.appendChild(link_node) + def _create_server_node(self, xml_doc, server): + return self.server_serializer.server_to_xml(xml_doc, server) - def _image_list_to_xml(self, xml_doc, images, image_to_xml): + def _image_list_to_xml(self, xml_doc, images, detailed): container_node = xml_doc.createElement('images') + if detailed: + image_to_xml = self._image_to_xml_detailed + else: + image_to_xml = self._image_to_xml + for image in images: item_node = image_to_xml(xml_doc, image) container_node.appendChild(item_node) @@ -342,34 +330,34 @@ class ImageXMLSerializer(wsgi.DictSerializer): image_to_xml = self._image_to_xml item_node = image_to_xml(xml_doc, image) self._add_xmlns(item_node) - return item_node.toprettyxml(indent=' ') - - def _image_list_to_xml_string(self, images, detailed): - xml_doc = minidom.Document() - if detailed: - image_to_xml = self._image_to_xml_detailed - else: - image_to_xml = self._image_to_xml - container_node = self._image_list_to_xml(xml_doc, images, image_to_xml) - - self._add_xmlns(container_node) - return container_node.toprettyxml(indent=' ') + self._add_atom_xmlns(item_node) + return item_node.toprettyxml(indent=' ', encoding='UTF-8') def index(self, images_dict): - return self._image_list_to_xml_string(images_dict['images'], - detailed=False) + xml_doc = minidom.Document() + node = self._image_list_to_xml(xml_doc, + images_dict['images'], + detailed=False) + return self.xml_string(node) def detail(self, images_dict): - return self._image_list_to_xml_string(images_dict['images'], - detailed=True) + xml_doc = minidom.Document() + node = self._image_list_to_xml(xml_doc, + images_dict['images'], + detailed=True) + return self.xml_string(node) def show(self, image_dict): - return self._image_to_xml_string(image_dict['image'], - detailed=True) + xml_doc = minidom.Document() + node = self._image_to_xml_detailed(xml_doc, + image_dict['image']) + return self.xml_string(node) def create(self, image_dict): - return self._image_to_xml_string(image_dict['image'], - detailed=True) + xml_doc = minidom.Document() + node = self._image_to_xml_detailed(xml_doc, + image_dict['image']) + return self.xml_string(node) def create_resource(version='1.0'): diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index fc1ab8d46..cff4e9b82 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -627,3 +627,15 @@ def create_resource(version='1.0'): return wsgi.Resource(controller, serializers=serializers, deserializers=deserializers) + + +class ServerXMLSerializer(wsgi.XMLDictSerializer): + def __init__(self, xmlns=wsgi.XMLNS_V11): + super(ServerXMLSerializer, self).__init__(xmlns=xmlns) + + def server_to_xml(self, xml_doc, server): + server_node = xml_doc.createElement('server') + server_node.setAttribute('id', str(server['id'])) + server_node.setAttribute('name', server['name']) + self._create_link_nodes(xml_doc, server_node, server['links']) + return server_node diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index 5b6e3cb1d..693c85b97 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -236,9 +236,14 @@ class XMLDictSerializer(DictSerializer): return node.toprettyxml(indent=' ', encoding='utf-8') + def xml_string(self, node): + self._add_xmlns(node) + return node.toprettyxml(indent=' ', encoding='UTF-8') + def _add_xmlns(self, node): if self.xmlns is not None: node.setAttribute('xmlns', self.xmlns) + node.setAttribute('xmlns:atom', "http://www.w3.org/2005/Atom") def _to_xml_node(self, doc, metadata, nodename, data): """Recursive method to convert data members to XML nodes.""" @@ -294,6 +299,13 @@ class XMLDictSerializer(DictSerializer): result.appendChild(node) return result + def _create_link_nodes(self, xml_doc, node, links): + for link in links: + link_node = xml_doc.createElement('atom:link') + link_node.setAttribute('rel', link['rel']) + link_node.setAttribute('href', link['href']) + node.appendChild(link_node) + class ResponseSerializer(object): """Encode the necessary pieces into a response object""" -- cgit From 14ee32f07536f5686794d2dbe8f1fad159af4dfe Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Thu, 7 Jul 2011 14:09:23 -0400 Subject: Dried up images XML serialization --- nova/api/openstack/images.py | 11 ----------- 1 file changed, 11 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index bc7517c40..c540037fa 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -322,17 +322,6 @@ class ImageXMLSerializer(wsgi.XMLDictSerializer): container_node.appendChild(item_node) return container_node - def _image_to_xml_string(self, image, detailed): - xml_doc = minidom.Document() - if detailed: - image_to_xml = self._image_to_xml_detailed - else: - image_to_xml = self._image_to_xml - item_node = image_to_xml(xml_doc, image) - self._add_xmlns(item_node) - self._add_atom_xmlns(item_node) - return item_node.toprettyxml(indent=' ', encoding='UTF-8') - def index(self, images_dict): xml_doc = minidom.Document() node = self._image_list_to_xml(xml_doc, -- cgit From 162b9651c3e251d8acae764f08372f764597f8ca Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Thu, 7 Jul 2011 14:27:27 -0400 Subject: Added param to keep current things from breaking until we update all of the xml serializers and view builders to reflect the current spec --- nova/api/openstack/images.py | 8 ++++---- nova/api/openstack/wsgi.py | 15 ++++++++------- 2 files changed, 12 insertions(+), 11 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index c540037fa..8f75fae4c 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -327,26 +327,26 @@ class ImageXMLSerializer(wsgi.XMLDictSerializer): node = self._image_list_to_xml(xml_doc, images_dict['images'], detailed=False) - return self.xml_string(node) + return self.xml_string(node, True) def detail(self, images_dict): xml_doc = minidom.Document() node = self._image_list_to_xml(xml_doc, images_dict['images'], detailed=True) - return self.xml_string(node) + return self.xml_string(node, True) def show(self, image_dict): xml_doc = minidom.Document() node = self._image_to_xml_detailed(xml_doc, image_dict['image']) - return self.xml_string(node) + return self.xml_string(node, True) def create(self, image_dict): xml_doc = minidom.Document() node = self._image_to_xml_detailed(xml_doc, image_dict['image']) - return self.xml_string(node) + return self.xml_string(node, True) def create_resource(version='1.0'): diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index 693c85b97..4ac7b56c9 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -232,18 +232,19 @@ class XMLDictSerializer(DictSerializer): doc = minidom.Document() node = self._to_xml_node(doc, self.metadata, root_key, data[root_key]) - self._add_xmlns(node) + return self.xml_string(node) - return node.toprettyxml(indent=' ', encoding='utf-8') - - def xml_string(self, node): - self._add_xmlns(node) + def xml_string(self, node, has_atom=False): + self._add_xmlns(node, has_atom) return node.toprettyxml(indent=' ', encoding='UTF-8') - def _add_xmlns(self, node): + #NOTE (ameade): the has_atom should be removed after all of the + # xml serializers and view builders have been updated + def _add_xmlns(self, node, has_atom=False): if self.xmlns is not None: node.setAttribute('xmlns', self.xmlns) - node.setAttribute('xmlns:atom', "http://www.w3.org/2005/Atom") + if has_atom: + node.setAttribute('xmlns:atom', "http://www.w3.org/2005/Atom") def _to_xml_node(self, doc, metadata, nodename, data): """Recursive method to convert data members to XML nodes.""" -- cgit From e9d712c52830725b24f20da7ff8662e97d22014d Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Thu, 7 Jul 2011 14:51:12 -0400 Subject: Removed bookmark link from non detailed image viewbuilder --- nova/api/openstack/views/images.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/views/images.py b/nova/api/openstack/views/images.py index 005341c62..fc4c1b55e 100644 --- a/nova/api/openstack/views/images.py +++ b/nova/api/openstack/views/images.py @@ -108,18 +108,17 @@ class ViewBuilderV11(ViewBuilder): href = self.generate_href(image_obj["id"]) bookmark = self.generate_bookmark(image_obj["id"]) - if detail: - image["metadata"] = image_obj.get("properties", {}) - image["links"] = [{ "rel": "self", "href": href, - }, - { - "rel": "bookmark", - "href": bookmark, }] + if detail: + image["metadata"] = image_obj.get("properties", {}) + image["links"].append({"rel": "bookmark", + "href": bookmark, + }) + return image def generate_bookmark(self, image_id): -- cgit From 1940ccffb6c22c39d6c21be4e84f20e350599f71 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Thu, 7 Jul 2011 16:19:21 -0400 Subject: Removed serverRef from some tests and viewbuilder --- nova/api/openstack/views/images.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/views/images.py b/nova/api/openstack/views/images.py index fc4c1b55e..ae8e73678 100644 --- a/nova/api/openstack/views/images.py +++ b/nova/api/openstack/views/images.py @@ -98,7 +98,9 @@ class ViewBuilderV11(ViewBuilder): def _build_server(self, image, image_obj): try: - image['serverRef'] = image_obj['properties']['instance_ref'] + serverRef = image_obj['properties']['instance_ref'] + #TODO (ameade) get the server information + #image['server'] = ? except KeyError: return -- cgit From 1ce2780b7fe6b10f7f0831b4d6bd91f694831f9e Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Thu, 7 Jul 2011 16:29:17 -0400 Subject: Temporarily moved create server node functionality into images.py Temporarily changed image XML tests to expect server entities with only ids --- nova/api/openstack/images.py | 7 +++++-- nova/api/openstack/servers.py | 12 ------------ 2 files changed, 5 insertions(+), 14 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index 8f75fae4c..a6af6da6f 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -269,7 +269,6 @@ class ImageXMLSerializer(wsgi.XMLDictSerializer): def __init__(self): self.metadata_serializer = image_metadata.ImageMetadataXMLSerializer() - self.server_serializer = servers.ServerXMLSerializer() def _image_to_xml(self, xml_doc, image): image_node = xml_doc.createElement('image') @@ -308,7 +307,11 @@ class ImageXMLSerializer(wsgi.XMLDictSerializer): return self.metadata_serializer.meta_list_to_xml(xml_doc, metadata) def _create_server_node(self, xml_doc, server): - return self.server_serializer.server_to_xml(xml_doc, server) + server_node = xml_doc.createElement('server') + server_node.setAttribute('id', str(server['id'])) + #server_node.setAttribute('name', server['name']) + #self._create_link_nodes(xml_doc, server_node, server['links']) + return server_node def _image_list_to_xml(self, xml_doc, images, detailed): container_node = xml_doc.createElement('images') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index cff4e9b82..fc1ab8d46 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -627,15 +627,3 @@ def create_resource(version='1.0'): return wsgi.Resource(controller, serializers=serializers, deserializers=deserializers) - - -class ServerXMLSerializer(wsgi.XMLDictSerializer): - def __init__(self, xmlns=wsgi.XMLNS_V11): - super(ServerXMLSerializer, self).__init__(xmlns=xmlns) - - def server_to_xml(self, xml_doc, server): - server_node = xml_doc.createElement('server') - server_node.setAttribute('id', str(server['id'])) - server_node.setAttribute('name', server['name']) - self._create_link_nodes(xml_doc, server_node, server['links']) - return server_node -- cgit From 7c90561e7288967c364a874cde13d88a8cb8fab4 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Thu, 7 Jul 2011 16:32:17 -0400 Subject: Changed name of xml_string to to_xml_string --- nova/api/openstack/images.py | 8 ++++---- nova/api/openstack/wsgi.py | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index a6af6da6f..e5496ed94 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -330,26 +330,26 @@ class ImageXMLSerializer(wsgi.XMLDictSerializer): node = self._image_list_to_xml(xml_doc, images_dict['images'], detailed=False) - return self.xml_string(node, True) + return self.to_xml_string(node, True) def detail(self, images_dict): xml_doc = minidom.Document() node = self._image_list_to_xml(xml_doc, images_dict['images'], detailed=True) - return self.xml_string(node, True) + return self.to_xml_string(node, True) def show(self, image_dict): xml_doc = minidom.Document() node = self._image_to_xml_detailed(xml_doc, image_dict['image']) - return self.xml_string(node, True) + return self.to_xml_string(node, True) def create(self, image_dict): xml_doc = minidom.Document() node = self._image_to_xml_detailed(xml_doc, image_dict['image']) - return self.xml_string(node, True) + return self.to_xml_string(node, True) def create_resource(version='1.0'): diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index 4ac7b56c9..f13bd6cc1 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -232,9 +232,9 @@ class XMLDictSerializer(DictSerializer): doc = minidom.Document() node = self._to_xml_node(doc, self.metadata, root_key, data[root_key]) - return self.xml_string(node) + return self.to_xml_string(node) - def xml_string(self, node, has_atom=False): + def to_xml_string(self, node, has_atom=False): self._add_xmlns(node, has_atom) return node.toprettyxml(indent=' ', encoding='UTF-8') -- cgit From d4540d2abde8db9519d8e4dad9b57a116a2c8b9e Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Thu, 7 Jul 2011 16:41:22 -0400 Subject: Updated _create_link_nodes to be consistent with other create_*_nodes --- nova/api/openstack/images.py | 15 ++++++++++++--- nova/api/openstack/wsgi.py | 6 ++++-- 2 files changed, 16 insertions(+), 5 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index e5496ed94..bf98a5cdd 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -274,7 +274,10 @@ class ImageXMLSerializer(wsgi.XMLDictSerializer): image_node = xml_doc.createElement('image') image_node.setAttribute('id', str(image['id'])) image_node.setAttribute('name', image['name']) - self._create_link_nodes(xml_doc, image_node, image['links']) + link_nodes = self._create_link_nodes(xml_doc, + image['links']) + for link_node in link_nodes: + image_node.appendChild(link_node) return image_node def _image_to_xml_detailed(self, xml_doc, image): @@ -290,7 +293,10 @@ class ImageXMLSerializer(wsgi.XMLDictSerializer): metadata_node = self._create_metadata_node(xml_doc, metadata) image_node.appendChild(metadata_node) - self._create_link_nodes(xml_doc, image_node, image['links']) + link_nodes = self._create_link_nodes(xml_doc, + image['links']) + for link_node in link_nodes: + image_node.appendChild(link_node) return image_node @@ -310,7 +316,10 @@ class ImageXMLSerializer(wsgi.XMLDictSerializer): server_node = xml_doc.createElement('server') server_node.setAttribute('id', str(server['id'])) #server_node.setAttribute('name', server['name']) - #self._create_link_nodes(xml_doc, server_node, server['links']) + #link_nodes = self._create_link_nodes(xml_doc, + # image['links']) + #for link_node in link_nodes: + # server_node.appendChild(link_node) return server_node def _image_list_to_xml(self, xml_doc, images, detailed): diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index f13bd6cc1..03f83001a 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -300,12 +300,14 @@ class XMLDictSerializer(DictSerializer): result.appendChild(node) return result - def _create_link_nodes(self, xml_doc, node, links): + def _create_link_nodes(self, xml_doc, links): + link_nodes = [] for link in links: link_node = xml_doc.createElement('atom:link') link_node.setAttribute('rel', link['rel']) link_node.setAttribute('href', link['href']) - node.appendChild(link_node) + link_nodes.append(link_node) + return link_nodes class ResponseSerializer(object): -- cgit From a8c9082c701a65f221f218cd8baa92b3859fc0ab Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Thu, 7 Jul 2011 16:52:32 -0400 Subject: Added server entity to images that only has id --- nova/api/openstack/views/images.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/views/images.py b/nova/api/openstack/views/images.py index ae8e73678..d1f89a785 100644 --- a/nova/api/openstack/views/images.py +++ b/nova/api/openstack/views/images.py @@ -99,8 +99,9 @@ class ViewBuilderV11(ViewBuilder): def _build_server(self, image, image_obj): try: serverRef = image_obj['properties']['instance_ref'] - #TODO (ameade) get the server information - #image['server'] = ? + image['server'] = { + "id": common.get_id_from_href(serverRef), + } except KeyError: return -- cgit From 2c3eeb5f9f5f78d9cb8fb3e37d5b5e1610d32499 Mon Sep 17 00:00:00 2001 From: John Tran Date: Thu, 7 Jul 2011 20:43:50 -0700 Subject: ec2 api _get_image method logic flaw that strips the hex16 digit off of the image name, and does a search against the db for it and ignores that it may not be the correct image, such as if doing a search for aki-0000009, yet that image name doesn't exist, it strips off aki- and looks for any image_id 0000009 and if there was an image match that happens to be an ami instead of aki, it will go ahead and deregister that. That behavior is unintended, so added logic to ensure that the original request image_id matches the type of image being returned from database by matching against container_format attr --- nova/api/ec2/cloud.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 9be30cf75..a4f4dae4f 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -1088,12 +1088,16 @@ class CloudController(object): def _get_image(self, context, ec2_id): try: internal_id = ec2utils.ec2_id_to_id(ec2_id) - return self.image_service.show(context, internal_id) + image = self.image_service.show(context, internal_id) except (exception.InvalidEc2Id, exception.ImageNotFound): try: return self.image_service.show_by_name(context, ec2_id) except exception.NotFound: raise exception.ImageNotFound(image_id=ec2_id) + image_type = ec2_id.split('-')[0] + if image.get('container_format') != image_type: + raise exception.ImageNotFound(image_id=ec2_id) + return image def _format_image(self, image): """Convert from format defined by BaseImageService to S3 format.""" -- cgit From 0130bb3d14e9e2db800ea0b15a48570085989521 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Fri, 8 Jul 2011 10:59:17 -0500 Subject: First take at migrations. --- nova/api/openstack/servers.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index fc1ab8d46..eacc2109f 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -176,7 +176,7 @@ class Controller(object): 'confirmResize': self._action_confirm_resize, 'revertResize': self._action_revert_resize, 'rebuild': self._action_rebuild, - } + 'migrate': self._action_migrate} for key in actions.keys(): if key in body: @@ -220,6 +220,14 @@ class Controller(object): return faults.Fault(exc.HTTPUnprocessableEntity()) return exc.HTTPAccepted() + def _action_migrate(self, input_dict, req, id): + try: + self.compute_api.resize(req.environ['nova.context'], id) + except Exception, e: + LOG.exception(_("Error in migrate %s"), e) + return faults.Fault(exc.HTTPBadRequest()) + return exc.HTTPAccepted() + @scheduler_api.redirect_handler def lock(self, req, id): """ -- cgit From 921fee22ff42852b1ee0d7f3d051b44d60afd975 Mon Sep 17 00:00:00 2001 From: "Kevin L. Mitchell" Date: Fri, 8 Jul 2011 13:06:37 -0500 Subject: Add support for remove_fixed_ip() --- nova/api/openstack/contrib/multinic.py | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/contrib/multinic.py b/nova/api/openstack/contrib/multinic.py index 164af79b0..e202bd51c 100644 --- a/nova/api/openstack/contrib/multinic.py +++ b/nova/api/openstack/contrib/multinic.py @@ -26,6 +26,8 @@ from nova.api.openstack import faults LOG = logging.getLogger("nova.api.multinic") +# Note: The class name is as it has to be for this to be loaded as an +# extension--only first character capitalized. class Multinic(extensions.ExtensionDescriptor): def __init__(self, *args, **kwargs): super(Multinic, self).__init__(*args, **kwargs) @@ -79,5 +81,19 @@ class Multinic(extensions.ExtensionDescriptor): return exc.HTTPAccepted() def _remove_fixed_ip(self, input_dict, req, id): - # Not yet implemented - raise faults.Fault(exc.HTTPNotImplemented()) + """Removes an IP from an instance.""" + try: + # Validate the input entity + if 'address' not in input_dict['removeFixedIp']: + LOG.exception(_("Missing 'address' argument for " + "removeFixedIp")) + return faults.Fault(exc.HTTPUnprocessableEntity()) + + # Remove the fixed IP + address = input_dict['removeFixedIp']['address'] + self.compute_api.remove_fixed_ip(req.environ['nova.context'], id, + address) + except Exception, e: + LOG.exception(_("Error in removeFixedIp %s"), e) + return faults.Fault(exc.HTTPBadRequest()) + return exc.HTTPAccepted() -- cgit From c98f37c00d802abf2ac85cb6c800f39e1b067d72 Mon Sep 17 00:00:00 2001 From: "Kevin L. Mitchell" Date: Fri, 8 Jul 2011 14:10:32 -0500 Subject: Add docstrings for multinic extension --- nova/api/openstack/contrib/multinic.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) (limited to 'nova/api') diff --git a/nova/api/openstack/contrib/multinic.py b/nova/api/openstack/contrib/multinic.py index e202bd51c..841061721 100644 --- a/nova/api/openstack/contrib/multinic.py +++ b/nova/api/openstack/contrib/multinic.py @@ -29,26 +29,50 @@ LOG = logging.getLogger("nova.api.multinic") # Note: The class name is as it has to be for this to be loaded as an # extension--only first character capitalized. class Multinic(extensions.ExtensionDescriptor): + """The multinic extension. + + Exposes addFixedIp and removeFixedIp actions on servers. + + """ + def __init__(self, *args, **kwargs): + """Initialize the extension. + + Gets a compute.API object so we can call the back-end + add_fixed_ip() and remove_fixed_ip() methods. + """ + super(Multinic, self).__init__(*args, **kwargs) self.compute_api = compute.API() def get_name(self): + """Return the extension name, as required by contract.""" + return "Multinic" def get_alias(self): + """Return the extension alias, as required by contract.""" + return "NMN" def get_description(self): + """Return the extension description, as required by contract.""" + return "Multiple network support" def get_namespace(self): + """Return the namespace, as required by contract.""" + return "http://docs.openstack.org/ext/multinic/api/v1.1" def get_updated(self): + """Return the last updated timestamp, as required by contract.""" + return "2011-06-09T00:00:00+00:00" def get_actions(self): + """Return the actions the extension adds, as required by contract.""" + actions = [] # Add the add_fixed_ip action @@ -65,6 +89,7 @@ class Multinic(extensions.ExtensionDescriptor): def _add_fixed_ip(self, input_dict, req, id): """Adds an IP on a given network to an instance.""" + try: # Validate the input entity if 'networkId' not in input_dict['addFixedIp']: @@ -82,6 +107,7 @@ class Multinic(extensions.ExtensionDescriptor): def _remove_fixed_ip(self, input_dict, req, id): """Removes an IP from an instance.""" + try: # Validate the input entity if 'address' not in input_dict['removeFixedIp']: -- cgit From 32a3cf25721173014fbd20c8f2954f015316f439 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Fri, 8 Jul 2011 15:19:19 -0400 Subject: removing Content-Length requirement --- nova/api/openstack/wsgi.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index 6726531be..1a33b7744 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -176,14 +176,8 @@ class RequestDeserializer(object): LOG.debug(_("No Content-Type provided in request")) return {} - if 'Content-Length' not in request.headers: - msg = _("No Content-Length provided in request") - LOG.debug(msg) - return {} - - if not request.content_length > 0: - msg = _("Request has Content-Length of zero") - LOG.debug(msg) + if not len(request.body) > 0: + LOG.debug(_("Empty body provided in request")) return {} deserializer = self.get_body_deserializer(content_type) -- cgit From 17fceb2368d63dd937dc5e9385158c01130557a7 Mon Sep 17 00:00:00 2001 From: John Tran Date: Fri, 8 Jul 2011 14:19:35 -0700 Subject: peer review fix - per vish: 'This method automatically converts unknown formats to ami, which is the same logic used to display unknown images in the ec2 api. This will allow you to properly deregister raw images, etc.' --- nova/api/ec2/cloud.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index a4f4dae4f..ee263f614 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -1095,7 +1095,7 @@ class CloudController(object): except exception.NotFound: raise exception.ImageNotFound(image_id=ec2_id) image_type = ec2_id.split('-')[0] - if image.get('container_format') != image_type: + if self._image_type(image.get('container_format')) != image_type: raise exception.ImageNotFound(image_id=ec2_id) return image -- cgit From fe8da67779dbb03654b1cce90eeafdb323507673 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Fri, 8 Jul 2011 17:21:58 -0400 Subject: easing up content-type restrictions --- nova/api/openstack/wsgi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index 1a33b7744..bd91aed60 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -52,7 +52,7 @@ class Request(webob.Request): content_type = self.content_type if content_type not in allowed_types: - raise exception.InvalidContentType(content_type=content_type) + return None else: return content_type -- cgit From b5ca0d793826ac10ee41be84f18d64b09113aa80 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Fri, 8 Jul 2011 17:40:56 -0400 Subject: refactor --- nova/api/openstack/wsgi.py | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index bd91aed60..7a8376722 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -52,9 +52,9 @@ class Request(webob.Request): content_type = self.content_type if content_type not in allowed_types: - return None - else: - return content_type + raise exception.InvalidContentType(content_type=content_type) + + return content_type class TextDeserializer(object): @@ -171,19 +171,22 @@ class RequestDeserializer(object): def deserialize_body(self, request, action): try: content_type = request.get_content_type() + except exception.InvalidContentType: + LOG.debug(_("Unrecognized Content-Type provided in request")) + return {} - if content_type is None: - LOG.debug(_("No Content-Type provided in request")) - return {} + if content_type is None: + LOG.debug(_("No Content-Type provided in request")) + return {} - if not len(request.body) > 0: - LOG.debug(_("Empty body provided in request")) - return {} + if not len(request.body) > 0: + LOG.debug(_("Empty body provided in request")) + return {} + try: deserializer = self.get_body_deserializer(content_type) - except exception.InvalidContentType: - LOG.debug(_("Unable to read body as provided Content-Type")) + LOG.debug(_("Unable to deserialize body as provided Content-Type")) raise return deserializer.deserialize(request.body, action) -- cgit From 9e7c7706a76ad76612ba75314d436a8ba419a3eb Mon Sep 17 00:00:00 2001 From: Mohammed Naser Date: Sat, 9 Jul 2011 21:45:54 -0400 Subject: Fixed two typos in rescue API command --- nova/api/ec2/cloud.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index ee263f614..0e2fe128d 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -1037,7 +1037,7 @@ class CloudController(object): def rescue_instance(self, context, instance_id, **kwargs): """This is an extension to the normal ec2_api""" - self._do_instance(self.compute_api.rescue, contect, instnace_id) + self._do_instance(self.compute_api.rescue, context, instance_id) return True def unrescue_instance(self, context, instance_id, **kwargs): -- cgit From 63ac91a2f62c2f07c7458e4f55a8e10e182b9fdf Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Mon, 11 Jul 2011 10:50:49 -0400 Subject: adding 204 response code --- nova/api/openstack/servers.py | 33 ++++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index eacc2109f..241ba970c 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -104,15 +104,6 @@ class Controller(object): except exception.NotFound: return faults.Fault(exc.HTTPNotFound()) - @scheduler_api.redirect_handler - def delete(self, req, id): - """ Destroys a server """ - try: - self.compute_api.delete(req.environ['nova.context'], id) - except exception.NotFound: - return faults.Fault(exc.HTTPNotFound()) - return exc.HTTPAccepted() - def create(self, req, body): """ Creates a new server for a given user """ extra_values = None @@ -420,6 +411,15 @@ class Controller(object): class ControllerV10(Controller): + @scheduler_api.redirect_handler + def delete(self, req, id): + """ Destroys a server """ + try: + self.compute_api.delete(req.environ['nova.context'], id) + except exception.NotFound: + return faults.Fault(exc.HTTPNotFound()) + return exc.HTTPAccepted() + def _image_ref_from_req_data(self, data): return data['server']['imageId'] @@ -482,6 +482,15 @@ class ControllerV10(Controller): class ControllerV11(Controller): + + @scheduler_api.redirect_handler + def delete(self, req, id): + """ Destroys a server """ + try: + self.compute_api.delete(req.environ['nova.context'], id) + except exception.NotFound: + return faults.Fault(exc.HTTPNotFound()) + def _image_ref_from_req_data(self, data): return data['server']['imageRef'] @@ -597,6 +606,12 @@ class ControllerV11(Controller): return self.helper._get_server_admin_password_new_style(server) +class ServersHTTPSerializer(wsgi.DictSerializer): + + def delete(self, response): + response.status_int = 204 + + def create_resource(version='1.0'): controller = { '1.0': ControllerV10, -- cgit From 334f2215f0533e8181d40cd086e927e7913739f2 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Mon, 11 Jul 2011 12:31:38 -0400 Subject: minor refactoring --- nova/api/openstack/wsgi.py | 52 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 36 insertions(+), 16 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index 7a8376722..9a273bf6e 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -57,16 +57,26 @@ class Request(webob.Request): return content_type -class TextDeserializer(object): - """Custom request body deserialization based on controller action name.""" +class ActionDispatcher(object): + """Maps method name to local methods through action name.""" - def deserialize(self, datastring, action='default'): - """Find local deserialization method and parse request body.""" + def dispatch(self, *args, **kwargs): + """Find and call local method.""" + action = kwargs.pop('action', 'default') action_method = getattr(self, str(action), self.default) - return action_method(datastring) + return action_method(*args, **kwargs) + + def default(self, data): + raise NotImplementedError() + + +class TextDeserializer(ActionDispatcher): + """Default request body deserialization""" + + def deserialize(self, datastring, action='default'): + return self.dispatch(datastring, action=action) def default(self, datastring): - """Default deserialization code should live here""" return {} @@ -128,8 +138,13 @@ class XMLDeserializer(TextDeserializer): return {'body': self._from_xml(datastring)} -class RequestHeadersDeserializer(object): +class RequestHeadersDeserializer(ActionDispatcher): + """Default request headers deserializer""" + def deserialize(self, request, action): + return self.dispatch(request, action=action) + + def default(self, request): return {} @@ -220,20 +235,18 @@ class RequestDeserializer(object): return args -class DictSerializer(object): - """Custom response body serialization based on controller action name.""" +class DictSerializer(ActionDispatcher): + """Default request body serialization""" def serialize(self, data, action='default'): - """Find local serialization method and encode response body.""" - action_method = getattr(self, str(action), self.default) - return action_method(data) + return self.dispatch(data, action=action) def default(self, data): - """Default serialization code should live here""" - raise NotImplementedError() + return "" class JSONDictSerializer(DictSerializer): + """Default JSON request body serialization""" def default(self, data): return utils.dumps(data) @@ -320,8 +333,13 @@ class XMLDictSerializer(DictSerializer): return result -class ResponseHeadersSerializer(object): +class ResponseHeadersSerializer(ActionDispatcher): + """Default response headers serialization""" + def serialize(self, response, data, action): + self.dispatch(response, data, action=action) + + def default(self, response, data): response.status_int = 200 @@ -410,7 +428,9 @@ class Resource(wsgi.Application): #TODO(bcwaldon): find a more elegant way to pass through non-dict types if type(action_result) is dict: - response = self.serializer.serialize(action_result, accept, action) + response = self.serializer.serialize(action_result, + accept, + action=action) else: response = action_result -- cgit From 6a0b3b8a143e60334dab7ed541caca1eba27c88b Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Mon, 11 Jul 2011 12:34:07 -0400 Subject: adding headers serializer --- nova/api/openstack/servers.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 4cda8981c..15d23dc09 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -606,7 +606,7 @@ class ControllerV11(Controller): return self.helper._get_server_admin_password_new_style(server) -class ServersHTTPSerializer(wsgi.DictSerializer): +class HeadersSerializer(wsgi.DictSerializer): def delete(self, response): response.status_int = 204 @@ -639,6 +639,8 @@ def create_resource(version='1.0'): '1.1': wsgi.XMLNS_V11, }[version] + headers_serializer = HeadersSerializer() + body_serializers = { 'application/xml': wsgi.XMLDictSerializer(metadata=metadata, xmlns=xmlns), @@ -648,7 +650,7 @@ def create_resource(version='1.0'): 'application/xml': helper.ServerXMLDeserializer(), } - serializer = wsgi.ResponseSerializer(body_serializers) + serializer = wsgi.ResponseSerializer(body_serializers, headers_serializer) deserializer = wsgi.RequestDeserializer(body_deserializers) return wsgi.Resource(controller, deserializer, serializer) -- cgit From ad23d0f354b8698b5314ed2a55e5a4d17abffba0 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Mon, 11 Jul 2011 13:16:22 -0400 Subject: allowing controllers to return Nonew --- nova/api/openstack/wsgi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index 9a273bf6e..8eff9e441 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -427,7 +427,7 @@ class Resource(wsgi.Application): action_result = self.dispatch(request, action, args) #TODO(bcwaldon): find a more elegant way to pass through non-dict types - if type(action_result) is dict: + if type(action_result) is dict or action_result is None: response = self.serializer.serialize(action_result, accept, action=action) -- cgit From 02b0ca3e44626623c70d04ccaa50af8c75d640af Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Mon, 11 Jul 2011 13:29:56 -0400 Subject: updating code to implement tests --- 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 15d23dc09..12af44a8d 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -606,9 +606,9 @@ class ControllerV11(Controller): return self.helper._get_server_admin_password_new_style(server) -class HeadersSerializer(wsgi.DictSerializer): +class HeadersSerializer(wsgi.ResponseHeadersSerializer): - def delete(self, response): + def delete(self, response, data): response.status_int = 204 -- cgit From 495137fb383766ae5345fd8b30610a93483c0eaf Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Tue, 12 Jul 2011 12:01:13 -0400 Subject: Updated remove_version_from_href to be more intelligent Added tests --- nova/api/openstack/common.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py index 9aa384f33..83854b13f 100644 --- a/nova/api/openstack/common.py +++ b/nova/api/openstack/common.py @@ -136,11 +136,21 @@ def get_id_from_href(href): raise webob.exc.HTTPBadRequest(_('could not parse id from href')) -def remove_version_from_href(base_url): +def remove_version_from_href(href): """Removes the api version from the href. Given: 'http://www.nova.com/v1.1/123' Returns: 'http://www.nova.com/123' """ - return base_url.rsplit('/', 1).pop(0) + try: + #matches /v#.# + new_href = re.sub(r'[/][v][0-9]*.[0-9]*', '', href) + if new_href == href: + msg = _('href does not contain version') + raise webob.exc.HTTPBadRequest(explanation=msg) + return new_href + except: + LOG.debug(_("Error removing version from href: %s") % href) + msg = _('could not parse version from href') + raise webob.exc.HTTPBadRequest(explanation=msg) -- cgit From 3c8de7e99e7cd8868f63fb0d15845b2462b77b3e Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Tue, 12 Jul 2011 12:46:15 -0400 Subject: Updated images viewbuilder to return links in server entity --- nova/api/openstack/views/images.py | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'nova/api') diff --git a/nova/api/openstack/views/images.py b/nova/api/openstack/views/images.py index d1f89a785..5c0510377 100644 --- a/nova/api/openstack/views/images.py +++ b/nova/api/openstack/views/images.py @@ -101,6 +101,16 @@ class ViewBuilderV11(ViewBuilder): serverRef = image_obj['properties']['instance_ref'] image['server'] = { "id": common.get_id_from_href(serverRef), + "links": [ + { + "rel": "self", + "href": serverRef, + }, + { + "rel": "bookmark", + "href": common.remove_version_from_href(serverRef), + }, + ] } except KeyError: return -- cgit From f4dc32ad0729b40ebe5765a57edff9535b992953 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Tue, 12 Jul 2011 12:55:37 -0400 Subject: Updated ImageXMLSerializer to serialize links in the server entity --- nova/api/openstack/images.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index 873e025c7..d0317583e 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -325,11 +325,10 @@ class ImageXMLSerializer(wsgi.XMLDictSerializer): def _create_server_node(self, xml_doc, server): server_node = xml_doc.createElement('server') server_node.setAttribute('id', str(server['id'])) - #server_node.setAttribute('name', server['name']) - #link_nodes = self._create_link_nodes(xml_doc, - # image['links']) - #for link_node in link_nodes: - # server_node.appendChild(link_node) + link_nodes = self._create_link_nodes(xml_doc, + server['links']) + for link_node in link_nodes: + server_node.appendChild(link_node) return server_node def _image_list_to_xml(self, xml_doc, images, detailed): -- cgit From 7a700362d63de1da51e9a890d854c3b0eeb97aae Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Tue, 12 Jul 2011 13:29:26 -0400 Subject: minor cleanup --- nova/api/openstack/wsgi.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index 4e4ce4121..c3f841aa5 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -277,7 +277,9 @@ class XMLDictSerializer(DictSerializer): return node.toprettyxml(indent=' ', encoding='UTF-8') #NOTE (ameade): the has_atom should be removed after all of the - # xml serializers and view builders have been updated + # xml serializers and view builders have been updated to the current + # spec that required all responses include the xmlns:atom, the has_atom + # flag is to prevent current tests from breaking def _add_xmlns(self, node, has_atom=False): if self.xmlns is not None: node.setAttribute('xmlns', self.xmlns) -- cgit From c085294ccb6a8d449ccfd5739be67ee12538f48f Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Tue, 12 Jul 2011 14:19:30 -0400 Subject: Updated some common.py functions to raise ValueErrors instead of HTTPBadRequests --- nova/api/openstack/common.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py index 83854b13f..79969d393 100644 --- a/nova/api/openstack/common.py +++ b/nova/api/openstack/common.py @@ -133,7 +133,7 @@ def get_id_from_href(href): return int(urlparse(href).path.split('/')[-1]) except: LOG.debug(_("Error extracting id from href: %s") % href) - raise webob.exc.HTTPBadRequest(_('could not parse id from href')) + raise ValueError(_('could not parse id from href')) def remove_version_from_href(href): @@ -146,11 +146,12 @@ def remove_version_from_href(href): try: #matches /v#.# new_href = re.sub(r'[/][v][0-9]*.[0-9]*', '', href) - if new_href == href: - msg = _('href does not contain version') - raise webob.exc.HTTPBadRequest(explanation=msg) - return new_href except: LOG.debug(_("Error removing version from href: %s") % href) msg = _('could not parse version from href') - raise webob.exc.HTTPBadRequest(explanation=msg) + raise ValueError(msg) + + if new_href == href: + msg = _('href does not contain version') + raise ValueError(msg) + return new_href -- cgit From 54be28647ac3ad401006bca3069b1dfc1a65d093 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Tue, 12 Jul 2011 14:35:09 -0400 Subject: server create deserialization functional and tested --- nova/api/openstack/create_instance_helper.py | 81 ++++++++++++++++++++-------- nova/api/openstack/servers.py | 63 ++++++++++++++++++++-- nova/api/openstack/wsgi.py | 21 ++++++++ 3 files changed, 139 insertions(+), 26 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index 2654e3c40..eea973a56 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -180,7 +180,7 @@ class CreateInstanceHelper(object): Overrides normal behavior in the case of xml content """ if request.content_type == "application/xml": - deserializer = ServerCreateRequestXMLDeserializer() + deserializer = ServerXMLDeserializer() return deserializer.deserialize(request.body) else: return self._deserialize(request.body, request.get_content_type()) @@ -295,9 +295,15 @@ class ServerXMLDeserializer(wsgi.XMLDeserializer): """Marshal the server attribute of a parsed request""" server = {} server_node = self._find_first_child_named(node, 'server') - for attr in ["name", "imageId", "flavorId", "imageRef", "flavorRef"]: + for attr in ["name", "imageId", "flavorId"]: if server_node.getAttribute(attr): server[attr] = server_node.getAttribute(attr) + image = self._extract_image(server_node) + if image is not None: + server["image"] = image + flavor = self._extract_flavor(server_node) + if flavor is not None: + server["flavor"] = flavor metadata = self._extract_metadata(server_node) if metadata is not None: server["metadata"] = metadata @@ -306,6 +312,56 @@ class ServerXMLDeserializer(wsgi.XMLDeserializer): server["personality"] = personality return server + def _extract_image(self, server_node): + """Retrieve an image entity from the server node""" + image_node = self._find_first_child_named(server_node, "image") + if image_node is None: + return None + + image = {} + image_id = image_node.getAttribute('id') + if image_id: + image['id'] = image_id + + image_links = self._extract_links_from_node(image_node) + if len(image_links) > 0: + image['links'] = image_links + + return image + + def _extract_flavor(self, server_node): + """Retrieve a flavor entity from the server node""" + flavor_node = self._find_first_child_named(server_node, "flavor") + if flavor_node is None: + return None + + flavor = {} + flavor_id = flavor_node.getAttribute('id') + if flavor_id: + flavor['id'] = flavor_id + + flavor_links = self._extract_links_from_node(flavor_node) + if len(flavor_links) > 0: + flavor['links'] = flavor_links + + return flavor + + def _extract_links_from_node(self, parent_node): + """Retrieve link entities from a links container provided node""" + links = [] + + for link_node in self._find_children_named(parent_node, 'atom:link'): + link = {} + link_rel = link_node.getAttribute('rel') + if link_rel is not None: + link['rel'] = link_rel + link_href = link_node.getAttribute('href') + if link_href is not None: + link['href'] = link_href + links.append(link) + + return links + def _extract_metadata(self, server_node): """Marshal the metadata attribute of a parsed request""" metadata_node = self._find_first_child_named(server_node, "metadata") @@ -331,24 +387,3 @@ class ServerXMLDeserializer(wsgi.XMLDeserializer): item["contents"] = self._extract_text(file_node) personality.append(item) return personality - - def _find_first_child_named(self, parent, name): - """Search a nodes children for the first child with a given name""" - for node in parent.childNodes: - if node.nodeName == name: - return node - return None - - def _find_children_named(self, parent, name): - """Return all of a nodes children who have the given name""" - for node in parent.childNodes: - if node.nodeName == name: - yield node - - def _extract_text(self, node): - """Get the text field contained by the given node""" - if len(node.childNodes) == 1: - child = node.childNodes[0] - if child.nodeType == child.TEXT_NODE: - return child.nodeValue - return "" diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 12af44a8d..f239044ff 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -492,11 +492,68 @@ class ControllerV11(Controller): return faults.Fault(exc.HTTPNotFound()) def _image_ref_from_req_data(self, data): - return data['server']['imageRef'] + try: + image = data['server']['image'] + except (AttributeError, KeyError): + msg = _("Missing image entity") + raise exc.HTTPBadRequest(explanation=msg) + + try: + links = image.get('links', []) + except AttributeError: + msg = _("Malformed image entity") + raise exc.HTTPBadRequest(explanation=msg) + + image_ref = None + for link in links: + try: + if link.get('rel') == 'bookmark': + image_ref = link.get('href') + break + except AttributeError: + msg = _("Malformed image link") + raise exc.HTTPBadRequest(explanation=msg) + + if image_ref is None: + try: + image_ref = image['id'] + except KeyError: + msg = _("Missing id attribute on image entity") + raise exc.HTTPBadRequest(explanation=msg) + + return image_ref def _flavor_id_from_req_data(self, data): - href = data['server']['flavorRef'] - return common.get_id_from_href(href) + try: + flavor = data['server']['flavor'] + except (AttributeError, KeyError): + msg = _("Missing flavor entity") + raise exc.HTTPBadRequest(explanation=msg) + + try: + links = flavor.get('links', []) + except AttributeError: + msg = _("Malformed flavor entity") + raise exc.HTTPBadRequest(explanation=msg) + + flavor_ref = None + for link in links: + try: + if link.get('rel') == 'bookmark': + flavor_ref = link.get('href') + break + except AttributeError: + msg = _("Malformed flavor link") + raise exc.HTTPBadRequest(explanation=msg) + + if flavor_ref is None: + try: + return flavor['id'] + except (KeyError, AttributeError): + msg = _("Missing id attribute in flavor entity") + raise exc.HTTPBadRequest(explanation=msg) + else: + return common.get_id_from_href(flavor_ref) def _get_view_builder(self, req): base_url = req.application_url diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index 8eff9e441..0ece2cff0 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -134,6 +134,27 @@ class XMLDeserializer(TextDeserializer): listnames) return result + def _find_first_child_named(self, parent, name): + """Search a nodes children for the first child with a given name""" + for node in parent.childNodes: + if node.nodeName == name: + return node + return None + + def _find_children_named(self, parent, name): + """Return all of a nodes children who have the given name""" + for node in parent.childNodes: + if node.nodeName == name: + yield node + + def _extract_text(self, node): + """Get the text field contained by the given node""" + if len(node.childNodes) == 1: + child = node.childNodes[0] + if child.nodeType == child.TEXT_NODE: + return child.nodeValue + return "" + def default(self, datastring): return {'body': self._from_xml(datastring)} -- cgit From 07baabb67d9491da61fa5bfe9adc52f7ff744e22 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Tue, 12 Jul 2011 16:02:39 -0400 Subject: cleanup --- nova/api/openstack/create_instance_helper.py | 24 ++++++---------- nova/api/openstack/servers.py | 41 ++++++++++++---------------- 2 files changed, 27 insertions(+), 38 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index eea973a56..e46bc9d98 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -320,12 +320,10 @@ class ServerXMLDeserializer(wsgi.XMLDeserializer): image = {} image_id = image_node.getAttribute('id') - if image_id: + if image_id is not None: image['id'] = image_id - image_links = self._extract_links_from_node(image_node) - if len(image_links) > 0: - image['links'] = image_links + image['links'] = self._extract_links_from_node(image_node) return image @@ -340,9 +338,7 @@ class ServerXMLDeserializer(wsgi.XMLDeserializer): if flavor_id: flavor['id'] = flavor_id - flavor_links = self._extract_links_from_node(flavor_node) - if len(flavor_links) > 0: - flavor['links'] = flavor_links + flavor['links'] = self._extract_links_from_node(flavor_node) return flavor @@ -351,14 +347,12 @@ class ServerXMLDeserializer(wsgi.XMLDeserializer): links = [] for link_node in self._find_children_named(parent_node, 'atom:link'): - link = {} - link_rel = link_node.getAttribute('rel') - if link_rel is not None: - link['rel'] = link_rel - link_href = link_node.getAttribute('href') - if link_href is not None: - link['href'] = link_href - links.append(link) + link = { + 'rel': link_node.getAttribute('rel'), + 'href': link_node.getAttribute('href'), + } + if link['rel'] is not None and link['href'] is not None: + links.append(link) return links diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index f239044ff..1e8749f56 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -491,10 +491,21 @@ class ControllerV11(Controller): except exception.NotFound: return faults.Fault(exc.HTTPNotFound()) + def _href_from_bookmark_links(self, links) + for link in links: + try: + if link.get('rel') == 'bookmark': + href = link.get('href') + if href is not None: + return href + except AttributeError: + msg = _("Malformed link entity") + raise exc.HTTPBadRequest(explanation=msg) + def _image_ref_from_req_data(self, data): try: image = data['server']['image'] - except (AttributeError, KeyError): + except (TypeError, KeyError): msg = _("Missing image entity") raise exc.HTTPBadRequest(explanation=msg) @@ -504,29 +515,21 @@ class ControllerV11(Controller): msg = _("Malformed image entity") raise exc.HTTPBadRequest(explanation=msg) - image_ref = None - for link in links: - try: - if link.get('rel') == 'bookmark': - image_ref = link.get('href') - break - except AttributeError: - msg = _("Malformed image link") - raise exc.HTTPBadRequest(explanation=msg) + image_ref = self._href_from_bookmark_links(links) if image_ref is None: try: - image_ref = image['id'] + return image['id'] except KeyError: msg = _("Missing id attribute on image entity") raise exc.HTTPBadRequest(explanation=msg) - - return image_ref + else: + return image_ref def _flavor_id_from_req_data(self, data): try: flavor = data['server']['flavor'] - except (AttributeError, KeyError): + except (TypeError, KeyError): msg = _("Missing flavor entity") raise exc.HTTPBadRequest(explanation=msg) @@ -536,15 +539,7 @@ class ControllerV11(Controller): msg = _("Malformed flavor entity") raise exc.HTTPBadRequest(explanation=msg) - flavor_ref = None - for link in links: - try: - if link.get('rel') == 'bookmark': - flavor_ref = link.get('href') - break - except AttributeError: - msg = _("Malformed flavor link") - raise exc.HTTPBadRequest(explanation=msg) + flavor_ref = self._href_from_bookmark_links(links) if flavor_ref is None: try: -- cgit From 880121c8498530d9c0e9a38e983c4d4518c1189e Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Wed, 13 Jul 2011 11:10:40 -0400 Subject: adding test; casting instance to dict to prevent sqlalchemy errors --- nova/api/openstack/__init__.py | 13 ++++--------- nova/api/openstack/ips.py | 2 +- nova/api/openstack/servers.py | 5 +++-- 3 files changed, 8 insertions(+), 12 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index 3e4d87ee0..e87d7c754 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -125,6 +125,10 @@ class APIRouter(base_wsgi.Router): collection={'detail': 'GET'}, member=self.server_members) + mapper.resource("ip", "ips", controller=ips.create_resource(version), + parent_resource=dict(member_name='server', + collection_name='servers')) + mapper.resource("image", "images", controller=images.create_resource(version), collection={'detail': 'GET'}) @@ -154,11 +158,6 @@ class APIRouterV10(APIRouter): parent_resource=dict(member_name='server', collection_name='servers')) - mapper.resource("ip", "ips", controller=ips.create_resource('1.0'), - member=dict(public='GET', private='GET'), - parent_resource=dict(member_name='server', - collection_name='servers')) - class APIRouterV11(APIRouter): """Define routes specific to OpenStack API V1.1.""" @@ -174,7 +173,3 @@ class APIRouterV11(APIRouter): controller=server_metadata.create_resource(), parent_resource=dict(member_name='server', collection_name='servers')) - - mapper.resource("ip", "ips", controller=ips.create_resource('1.1'), - parent_resource=dict(member_name='server', - collection_name='servers')) diff --git a/nova/api/openstack/ips.py b/nova/api/openstack/ips.py index b83e2f9b3..59e3c084c 100644 --- a/nova/api/openstack/ips.py +++ b/nova/api/openstack/ips.py @@ -93,7 +93,7 @@ class ControllerV11(Controller): def _get_virtual_interfaces(self, context, server_id): try: return db.api.virtual_interface_get_by_instance(context, server_id) - except exception.InstanceNotFound: + except nova.exception.InstanceNotFound: msg = _("Instance does not exist") raise exc.HTTPNotFound(explanation=msg) diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 3c29c2606..2cb27472a 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -492,9 +492,10 @@ class ControllerV11(Controller): context = req.environ['nova.context'] interfaces = db.api.virtual_interface_get_by_instance(context, instance['id']) - instance['virtual_interfaces'] = interfaces + _instance = dict(instance) + _instance['virtual_interfaces'] = interfaces - return builder.build(instance, is_detail=is_detail) + return builder.build(_instance, is_detail=is_detail) def _action_change_password(self, input_dict, req, id): context = req.environ['nova.context'] -- cgit From 486afc9b9e38a68c18b80daab4f23c5b936ee185 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Wed, 13 Jul 2011 11:17:05 -0400 Subject: pep8 --- nova/api/openstack/servers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 1e8749f56..50d1442d4 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -491,7 +491,7 @@ class ControllerV11(Controller): except exception.NotFound: return faults.Fault(exc.HTTPNotFound()) - def _href_from_bookmark_links(self, links) + def _href_from_bookmark_links(self, links): for link in links: try: if link.get('rel') == 'bookmark': -- cgit From 132a47611b2fdbbb1e6c70a33bfd092854ea6e98 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Wed, 13 Jul 2011 14:02:44 -0400 Subject: updating testing; simplifying instance-level code --- nova/api/openstack/servers.py | 8 +------- nova/api/openstack/views/addresses.py | 1 - nova/api/openstack/views/servers.py | 2 +- 3 files changed, 2 insertions(+), 9 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 2cb27472a..5230f4d5c 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -489,13 +489,7 @@ class ControllerV11(Controller): builder = nova.api.openstack.views.servers.ViewBuilderV11( addresses_builder, flavor_builder, image_builder, base_url) - context = req.environ['nova.context'] - interfaces = db.api.virtual_interface_get_by_instance(context, - instance['id']) - _instance = dict(instance) - _instance['virtual_interfaces'] = interfaces - - return builder.build(_instance, is_detail=is_detail) + return builder.build(instance, is_detail=is_detail) def _action_change_password(self, input_dict, req, id): context = req.environ['nova.context'] diff --git a/nova/api/openstack/views/addresses.py b/nova/api/openstack/views/addresses.py index c7464a7c5..b127ac38c 100644 --- a/nova/api/openstack/views/addresses.py +++ b/nova/api/openstack/views/addresses.py @@ -67,4 +67,3 @@ class ViewBuilderV11(ViewBuilder): ip = {'addr': fixed_ip['address'], 'version': 4} fixed_ips.append(ip) return fixed_ips - diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index 691cc48ca..a957aa58e 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -159,7 +159,7 @@ class ViewBuilderV11(ViewBuilder): response["flavorRef"] = flavor_ref def _build_addresses(self, response, inst): - interfaces = inst['virtual_interfaces'] + interfaces = inst.get('virtual_interfaces', []) response['addresses'] = self.addresses_builder.build(interfaces) def _build_extra(self, response, inst): -- cgit From 6daf6d30dfeab459a0b672d909b115b1a5ce86c3 Mon Sep 17 00:00:00 2001 From: "Kevin L. Mitchell" Date: Wed, 13 Jul 2011 14:41:13 -0500 Subject: Augment rate limiting to allow greater flexibility through the api-paste.ini configuration --- nova/api/openstack/limits.py | 96 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 91 insertions(+), 5 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/limits.py b/nova/api/openstack/limits.py index d08287f6b..8642a28f9 100644 --- a/nova/api/openstack/limits.py +++ b/nova/api/openstack/limits.py @@ -31,8 +31,8 @@ from collections import defaultdict from webob.dec import wsgify from nova import quota +from nova import utils from nova import wsgi as base_wsgi -from nova import wsgi from nova.api.openstack import common from nova.api.openstack import faults from nova.api.openstack.views import limits as limits_views @@ -119,6 +119,8 @@ class Limit(object): 60 * 60 * 24: "DAY", } + UNIT_MAP = dict([(v, k) for k, v in UNITS.items()]) + def __init__(self, verb, uri, regex, value, unit): """ Initialize a new `Limit`. @@ -224,16 +226,30 @@ class RateLimitingMiddleware(base_wsgi.Middleware): is stored in memory for this implementation. """ - def __init__(self, application, limits=None): + def __init__(self, application, limits=None, limiter=None, **kwargs): """ Initialize new `RateLimitingMiddleware`, which wraps the given WSGI application and sets up the given limits. @param application: WSGI application to wrap - @param limits: List of dictionaries describing limits + @param limits: String describing limits + @param limiter: String identifying class for representing limits + + Other parameters are passed to the constructor for the limiter. """ base_wsgi.Middleware.__init__(self, application) - self._limiter = Limiter(limits or DEFAULT_LIMITS) + + # Select the limiter class + if limiter is None: + limiter = Limiter + else: + limiter = utils.import_class(limiter) + + # Parse the limits, if any are provided + if limits is not None: + limits = limiter.parse_limits(limits) + + self._limiter = limiter(limits or DEFAULT_LIMITS, **kwargs) @wsgify(RequestClass=wsgi.Request) def __call__(self, req): @@ -271,7 +287,7 @@ class Limiter(object): Rate-limit checking class which handles limits in memory. """ - def __init__(self, limits): + def __init__(self, limits, **kwargs): """ Initialize the new `Limiter`. @@ -280,6 +296,12 @@ class Limiter(object): self.limits = copy.deepcopy(limits) self.levels = defaultdict(lambda: copy.deepcopy(limits)) + # Pick up any per-user limit information + for key, value in kwargs.items(): + if key.startswith('user:'): + username = key[5:] + self.levels[username] = self.parse_limits(value) + def get_limits(self, username=None): """ Return the limits for a given user. @@ -305,6 +327,59 @@ class Limiter(object): return None, None + @staticmethod + def parse_limits(limits): + """ + Convert a string into a list of Limit instances. This + implementation expects a semicolon-separated sequence of + parenthesized groups, where each group contains a + comma-separated sequence consisting of HTTP method, + user-readable URI, a URI reg-exp, an integer number of + requests which can be made, and a unit of measure. Valid + values for the latter are "SECOND", "MINUTE", "HOUR", and + "DAY". + + @return: List of Limit instances. + """ + + # Handle empty limit strings + limits = limits.strip() + if not limits: + return [] + + # Split up the limits by semicolon + result = [] + for group in limits.split(';'): + group = group.strip() + if group[0] != '(' or group[-1] != ')': + raise ValueError("Groups must be surrounded by parentheses") + group = group[1:-1] + + # Extract the Limit arguments + args = [a.strip() for a in group.split(',')] + if len(args) != 5: + raise ValueError("Groups must contain exactly 5 elements") + + # Pull out the arguments + verb, uri, regex, value, unit = args + + # Upper-case the verb + verb = verb.upper() + + # Convert value--raises ValueError if it's not integer + value = int(value) + + # Convert unit + unit = unit.upper() + if unit not in Limit.UNIT_MAP: + raise ValueError("Invalid units specified") + unit = Limit.UNIT_MAP[unit] + + # Build a limit + result.append(Limit(verb, uri, regex, value, unit)) + + return result + class WsgiLimiter(object): """ @@ -388,3 +463,14 @@ class WsgiLimiterProxy(object): return None, None return resp.getheader("X-Wait-Seconds"), resp.read() or None + + @staticmethod + def parse_limits(limits): + """ + Ignore a limits string--simply doesn't apply for the limit + proxy. + + @return: Empty list. + """ + + return [] -- cgit From e1eca2190b66c745b425579affd2407e0ffd11c6 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Wed, 13 Jul 2011 15:59:05 -0400 Subject: beginning server detail spec 1.1 fixup --- nova/api/openstack/common.py | 23 +++++++++++++++++++++++ nova/api/openstack/views/servers.py | 27 ++++++++++++++++++++++----- 2 files changed, 45 insertions(+), 5 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py index 79969d393..26b8c1946 100644 --- a/nova/api/openstack/common.py +++ b/nova/api/openstack/common.py @@ -136,6 +136,29 @@ def get_id_from_href(href): raise ValueError(_('could not parse id from href')) +def get_uuid_from_href(href): + """Return the uuid portion of a url. + + Given: 'http://www.foo.com/bar/123?q=4' + Returns: '123' + + In order to support local hrefs, the href argument can be just an id: + Given: '123' + Returns: '123' + + """ + if not '/' in href: + return href + try: + href = urlparse(href).path.split('/')[-1] + if href == '': + raise Exception + return href + except: + LOG.debug(_("Error extracting uuid from href: %s") % href) + raise ValueError(_('could not parse uuid from href')) + + def remove_version_from_href(href): """Removes the api version from the href. diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index 67fb6a84e..7fb180246 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -50,7 +50,7 @@ class ViewBuilder(object): else: server = self._build_simple(inst) - self._build_extra(server, inst) + self._build_extra(server['server'], inst) return server @@ -99,7 +99,6 @@ class ViewBuilder(object): self._build_image(inst_dict, inst) self._build_flavor(inst_dict, inst) - inst_dict['uuid'] = inst['uuid'] return dict(server=inst_dict) def _build_image(self, response, inst): @@ -117,6 +116,9 @@ class ViewBuilder(object): class ViewBuilderV10(ViewBuilder): """Model an Openstack API V1.0 server response.""" + def _build_extra(self, response, inst): + response['uuid'] = inst['uuid'] + def _build_image(self, response, inst): if 'image_ref' in dict(inst): image_ref = inst['image_ref'] @@ -143,16 +145,31 @@ class ViewBuilderV11(ViewBuilder): image_href = inst['image_ref'] if str(image_href).isdigit(): image_href = int(image_href) - response['imageRef'] = image_href + response['image'] = { + "id": common.get_uuid_from_href(image_href), + "links": [ + { + "rel": "self", + "href": image_href, + }, + { + "rel": "bookmark", + "href": common.remove_version_from_href(image_href), + }, + ] + } def _build_flavor(self, response, inst): if "instance_type" in dict(inst): flavor_id = inst["instance_type"]['flavorid'] flavor_ref = self.flavor_builder.generate_href(flavor_id) - response["flavorRef"] = flavor_ref + response["flavor"] = { + "id": common.get_uuid_from_href(flavor_ref), + } def _build_extra(self, response, inst): self._build_links(response, inst) + response['id'] = inst['uuid'] def _build_links(self, response, inst): href = self.generate_href(inst["id"]) @@ -169,7 +186,7 @@ class ViewBuilderV11(ViewBuilder): }, ] - response["server"]["links"] = links + response["links"] = links def generate_href(self, server_id): """Create an url that refers to a specific server id.""" -- cgit From 38f949608fc3faaae855bf91719a074c1c545a1d Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Wed, 13 Jul 2011 16:22:55 -0400 Subject: updated to support and check for flavor links in server detail response --- nova/api/openstack/views/servers.py | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'nova/api') diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index 7fb180246..e17edd6b6 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -163,8 +163,19 @@ class ViewBuilderV11(ViewBuilder): if "instance_type" in dict(inst): flavor_id = inst["instance_type"]['flavorid'] flavor_ref = self.flavor_builder.generate_href(flavor_id) + flavor_bookmark = self.flavor_builder.generate_bookmark(flavor_id) response["flavor"] = { "id": common.get_uuid_from_href(flavor_ref), + "links": [ + { + "rel": "self", + "href": flavor_ref, + }, + { + "rel": "bookmark", + "href": flavor_bookmark, + }, + ] } def _build_extra(self, response, inst): -- cgit From 934e8ab289cd906a94170f0d53cc49edf2db0d6f Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Wed, 13 Jul 2011 19:32:17 -0400 Subject: adding new query parameters --- nova/api/openstack/images.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index d0317583e..b0364b403 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -35,7 +35,13 @@ from nova.api.openstack import wsgi LOG = log.getLogger('nova.api.openstack.images') FLAGS = flags.FLAGS -SUPPORTED_FILTERS = ['name', 'status'] +SUPPORTED_FILTERS = { + 'name': 'name', + 'status': 'status', + 'changes-since': 'changes-since', + 'server': 'property-instance_ref', + 'type': 'property-image_type', +} class Controller(object): @@ -62,8 +68,9 @@ class Controller(object): filters = {} for param in req.str_params: if param in SUPPORTED_FILTERS or param.startswith('property-'): - filters[param] = req.str_params.get(param) - + # map filter name or carry through if property-* + _param = SUPPORTED_FILTERS.get(param, param) + filters[_param] = req.str_params.get(param) return filters def show(self, req, id): -- cgit From 10ef948271130db0dcc53b0ea94c3a47313244f4 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Thu, 14 Jul 2011 09:29:27 -0400 Subject: removing mox object instantiation from each test; renaming _param to filter_name --- nova/api/openstack/images.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index b0364b403..f82b98a38 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -69,8 +69,8 @@ class Controller(object): for param in req.str_params: if param in SUPPORTED_FILTERS or param.startswith('property-'): # map filter name or carry through if property-* - _param = SUPPORTED_FILTERS.get(param, param) - filters[_param] = req.str_params.get(param) + filter_name = SUPPORTED_FILTERS.get(param, param) + filters[filter_name] = req.str_params.get(param) return filters def show(self, req, id): -- cgit From 11f05e040ad50c64e4de46dc8f8e6246956f774b Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Thu, 14 Jul 2011 10:31:42 -0400 Subject: add updated and created to servers detail test, and make it work --- nova/api/openstack/views/servers.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'nova/api') diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index e17edd6b6..5d906f3df 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -181,6 +181,8 @@ class ViewBuilderV11(ViewBuilder): def _build_extra(self, response, inst): self._build_links(response, inst) response['id'] = inst['uuid'] + response['created'] = inst['created_at'] + response['updated'] = inst['updated_at'] def _build_links(self, response, inst): href = self.generate_href(inst["id"]) -- cgit From 1539866314393e8565eef05f1f63dba9ffa69de3 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Thu, 14 Jul 2011 12:03:06 -0400 Subject: adding bookmark to images index --- nova/api/openstack/views/images.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/views/images.py b/nova/api/openstack/views/images.py index 5c0510377..873ce212a 100644 --- a/nova/api/openstack/views/images.py +++ b/nova/api/openstack/views/images.py @@ -121,16 +121,20 @@ class ViewBuilderV11(ViewBuilder): href = self.generate_href(image_obj["id"]) bookmark = self.generate_bookmark(image_obj["id"]) - image["links"] = [{ - "rel": "self", - "href": href, - }] + image["links"] = [ + { + "rel": "self", + "href": href, + }, + { + "rel": "bookmark", + "href": bookmark, + }, + + ] if detail: image["metadata"] = image_obj.get("properties", {}) - image["links"].append({"rel": "bookmark", - "href": bookmark, - }) return image -- cgit From cbf05e0b6351c9577e7e992da072d190c8c9a592 Mon Sep 17 00:00:00 2001 From: "Kevin L. Mitchell" Date: Thu, 14 Jul 2011 11:37:32 -0500 Subject: Comment on parse_limits(); expand an exception message; add unit tests; fix a minor discovered bug --- nova/api/openstack/limits.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/limits.py b/nova/api/openstack/limits.py index 8642a28f9..bc76547d8 100644 --- a/nova/api/openstack/limits.py +++ b/nova/api/openstack/limits.py @@ -327,6 +327,11 @@ class Limiter(object): return None, None + # Note: This method gets called before the class is instantiated, + # so this must be either a static method or a class method. It is + # used to develop a list of limits to feed to the constructor. We + # put this in the class so that subclasses can override the + # default limit parsing. @staticmethod def parse_limits(limits): """ @@ -351,14 +356,16 @@ class Limiter(object): result = [] for group in limits.split(';'): group = group.strip() - if group[0] != '(' or group[-1] != ')': - raise ValueError("Groups must be surrounded by parentheses") + if group[:1] != '(' or group[-1:] != ')': + raise ValueError("Limit rules must be surrounded by " + "parentheses") group = group[1:-1] # Extract the Limit arguments args = [a.strip() for a in group.split(',')] if len(args) != 5: - raise ValueError("Groups must contain exactly 5 elements") + raise ValueError("Limit rules must contain the following " + "arguments: verb, uri, regex, value, unit") # Pull out the arguments verb, uri, regex, value, unit = args @@ -464,6 +471,11 @@ class WsgiLimiterProxy(object): return resp.getheader("X-Wait-Seconds"), resp.read() or None + # Note: This method gets called before the class is instantiated, + # so this must be either a static method or a class method. It is + # used to develop a list of limits to feed to the constructor. + # This implementation returns an empty list, since all limit + # decisions are made by a remote server. @staticmethod def parse_limits(limits): """ -- cgit From 27b8d75f9b666ce08472270b38685d8e36a612d8 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Thu, 14 Jul 2011 13:25:05 -0400 Subject: fixing bad merge --- nova/api/openstack/ips.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/ips.py b/nova/api/openstack/ips.py index f08d8023e..1ebfdb831 100644 --- a/nova/api/openstack/ips.py +++ b/nova/api/openstack/ips.py @@ -125,4 +125,4 @@ def create_resource(version): } serializer = wsgi.ResponseSerializer(body_serializers) - return wsgi.Resource(Controller(), serializer=serializer) + return wsgi.Resource(controller, serializer=serializer) -- cgit From 3041f2e9eb90e447adbb48827c2c85ca27d436e6 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Thu, 14 Jul 2011 13:25:40 -0400 Subject: Added progress attribute to servers responses --- nova/api/openstack/views/servers.py | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'nova/api') diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index 5d906f3df..3f13e469a 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -183,6 +183,10 @@ class ViewBuilderV11(ViewBuilder): response['id'] = inst['uuid'] response['created'] = inst['created_at'] response['updated'] = inst['updated_at'] + if response['status'] == "ACTIVE": + response['progress'] = 100 + elif response['status'] == "BUILD": + response['progress'] = 0 def _build_links(self, response, inst): href = self.generate_href(inst["id"]) -- cgit From 7044080daf2e487a92bba2a9f9b99e3cdb874e88 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Thu, 14 Jul 2011 14:01:04 -0400 Subject: updated image entity for servers requests --- nova/api/openstack/views/servers.py | 37 ++++++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 15 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index 3f13e469a..968a904d9 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -143,21 +143,28 @@ class ViewBuilderV11(ViewBuilder): def _build_image(self, response, inst): if 'image_ref' in dict(inst): image_href = inst['image_ref'] - if str(image_href).isdigit(): - image_href = int(image_href) - response['image'] = { - "id": common.get_uuid_from_href(image_href), - "links": [ - { - "rel": "self", - "href": image_href, - }, - { - "rel": "bookmark", - "href": common.remove_version_from_href(image_href), - }, - ] - } + #if id is a uuid do: + if image_href == common.get_uuid_from_href(image_href): + image_id = image_href + _bookmark = self.image_builder.generate_bookmark(image_href) + response['image'] = { + "id": image_id, + "links": [ + { + "rel": "bookmark", + "href": _bookmark, + }, + ] + } + else: + response['image'] = { + "links": [ + { + "rel": "bookmark", + "href": image_href, + }, + ] + } def _build_flavor(self, response, inst): if "instance_type" in dict(inst): -- cgit From 38233d72aff36fbdb0fd49755458b7b5100366e1 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Thu, 14 Jul 2011 14:23:23 -0400 Subject: exposing floating ips --- nova/api/openstack/views/addresses.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/views/addresses.py b/nova/api/openstack/views/addresses.py index b127ac38c..a242efa45 100644 --- a/nova/api/openstack/views/addresses.py +++ b/nova/api/openstack/views/addresses.py @@ -46,24 +46,26 @@ class ViewBuilderV11(ViewBuilder): networks = {} for interface in interfaces: network_label = interface['network']['label'] + if network_label not in networks: networks[network_label] = [] - for fixed_ip in interface['fixed_ips']: - ip = {'addr': fixed_ip['address'], 'version': 4} - networks[network_label].append(ip) + networks[network_label].extend(self._extract_ipv4(interface)) + return networks def build_network(self, interfaces, network_label): for interface in interfaces: if interface['network']['label'] == network_label: - ips = self._extract_fixed_ips(interface) - return {network_label: ips} + ips = self._extract_ipv4(interface) + return {network_label: list(ips)} return None - def _extract_fixed_ips(self, interface): - fixed_ips = [] + def _extract_ipv4(self, interface): for fixed_ip in interface['fixed_ips']: - ip = {'addr': fixed_ip['address'], 'version': 4} - fixed_ips.append(ip) - return fixed_ips + yield self._build_ip_entity(fixed_ip['address'], 4) + for floating_ip in fixed_ip.get('floating_ips', []): + yield self._build_ip_entity(floating_ip['address'], 4) + + def _build_ip_entity(self, address, version): + return {'addr': address, 'version': version} -- cgit From 11e76f0e36f9da1840a9356b2e0a0dba87df3040 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Thu, 14 Jul 2011 14:35:11 -0400 Subject: removed self links from flavors --- nova/api/openstack/views/servers.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index 968a904d9..9d6699ed0 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -174,10 +174,6 @@ class ViewBuilderV11(ViewBuilder): response["flavor"] = { "id": common.get_uuid_from_href(flavor_ref), "links": [ - { - "rel": "self", - "href": flavor_ref, - }, { "rel": "bookmark", "href": flavor_bookmark, @@ -190,10 +186,11 @@ class ViewBuilderV11(ViewBuilder): response['id'] = inst['uuid'] response['created'] = inst['created_at'] response['updated'] = inst['updated_at'] - if response['status'] == "ACTIVE": - response['progress'] = 100 - elif response['status'] == "BUILD": - response['progress'] = 0 + if 'status' in response: + if response['status'] == "ACTIVE": + response['progress'] = 100 + elif response['status'] == "BUILD": + response['progress'] = 0 def _build_links(self, response, inst): href = self.generate_href(inst["id"]) -- cgit From 6cbce0442cf15d31e7ab05b808939bdc3a244f1b Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Thu, 14 Jul 2011 17:35:10 -0400 Subject: Added ServersTestv1_1 test case Changed servers links to use uuid instead of id --- nova/api/openstack/views/servers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index 9d6699ed0..45209d9e2 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -193,8 +193,8 @@ class ViewBuilderV11(ViewBuilder): response['progress'] = 0 def _build_links(self, response, inst): - href = self.generate_href(inst["id"]) - bookmark = self.generate_bookmark(inst["id"]) + href = self.generate_href(inst["uuid"]) + bookmark = self.generate_bookmark(inst["uuid"]) links = [ { -- cgit From dd78adbcf0b85f9473b5240af3366fb1dc2d4133 Mon Sep 17 00:00:00 2001 From: Stephanie Reese Date: Thu, 14 Jul 2011 23:09:28 -0400 Subject: Fixed remove_version_from_href Added tests --- nova/api/openstack/common.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py index 79969d393..8e12ce0c0 100644 --- a/nova/api/openstack/common.py +++ b/nova/api/openstack/common.py @@ -137,15 +137,22 @@ def get_id_from_href(href): def remove_version_from_href(href): - """Removes the api version from the href. + """Removes the first api version from the href. Given: 'http://www.nova.com/v1.1/123' Returns: 'http://www.nova.com/123' + Given: 'http://www.nova.com/v1.1' + Returns: 'http://www.nova.com' + """ try: - #matches /v#.# - new_href = re.sub(r'[/][v][0-9]*.[0-9]*', '', href) + #removes the first instance that matches /v#.#/ + new_href = re.sub(r'[/][v][0-9]+\.[0-9]+[/]', '/', href, count=1) + + #if no version was found, try finding /v#.# at the end of the string + if new_href == href: + new_href = re.sub(r'[/][v][0-9]+\.[0-9]+$', '', href, count=1) except: LOG.debug(_("Error removing version from href: %s") % href) msg = _('could not parse version from href') -- cgit From a36d93372db8556b70d28cf52644da38c7f9a02f Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Fri, 15 Jul 2011 10:29:06 -0400 Subject: Added ViewBuilderV11 tests Fixed bug with build detail --- nova/api/openstack/views/servers.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index 45209d9e2..570488cfa 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -140,6 +140,18 @@ class ViewBuilderV11(ViewBuilder): self.image_builder = image_builder self.base_url = base_url + def _build_detail(self, inst): + response = super(ViewBuilderV11, self)._build_detail(inst) + response['server']['created'] = inst['created_at'] + response['server']['updated'] = inst['updated_at'] + if 'status' in response['server']: + if response['server']['status'] == "ACTIVE": + response['server']['progress'] = 100 + elif response['server']['status'] == "BUILD": + response['server']['progress'] = 0 + return response + + def _build_image(self, response, inst): if 'image_ref' in dict(inst): image_href = inst['image_ref'] @@ -184,13 +196,6 @@ class ViewBuilderV11(ViewBuilder): def _build_extra(self, response, inst): self._build_links(response, inst) response['id'] = inst['uuid'] - response['created'] = inst['created_at'] - response['updated'] = inst['updated_at'] - if 'status' in response: - if response['status'] == "ACTIVE": - response['progress'] = 100 - elif response['status'] == "BUILD": - response['progress'] = 0 def _build_links(self, response, inst): href = self.generate_href(inst["uuid"]) -- cgit From 1e7b21452a2e622b94531bea59c68e6abcded40a Mon Sep 17 00:00:00 2001 From: William Wolf Date: Fri, 15 Jul 2011 12:44:40 -0400 Subject: return id and uuid for now --- nova/api/openstack/views/servers.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index a4c9dd95a..53ded4312 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -206,7 +206,8 @@ class ViewBuilderV11(ViewBuilder): def _build_extra(self, response, inst): self._build_links(response, inst) - response['id'] = inst['uuid'] + response['id'] = inst['id'] + response['uuid'] = inst['uuid'] def _build_links(self, response, inst): href = self.generate_href(inst["uuid"]) -- cgit From 054f828d341b6cc576e30744a39d443af7784fd9 Mon Sep 17 00:00:00 2001 From: William Wolf Date: Fri, 15 Jul 2011 14:05:26 -0400 Subject: pep8 fixes --- nova/api/openstack/views/servers.py | 1 - 1 file changed, 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index 53ded4312..a1aba20fc 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -158,7 +158,6 @@ class ViewBuilderV11(ViewBuilder): response['server']['progress'] = 0 return response - def _build_image(self, response, inst): if 'image_ref' in dict(inst): image_href = inst['image_ref'] -- cgit From 1c6837c7940ed979a05a063595f4d7e7a2154ee9 Mon Sep 17 00:00:00 2001 From: William Wolf Date: Fri, 15 Jul 2011 14:43:33 -0400 Subject: use id in links instead of uuid --- nova/api/openstack/views/servers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index a1aba20fc..71d685171 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -209,8 +209,8 @@ class ViewBuilderV11(ViewBuilder): response['uuid'] = inst['uuid'] def _build_links(self, response, inst): - href = self.generate_href(inst["uuid"]) - bookmark = self.generate_bookmark(inst["uuid"]) + href = self.generate_href(inst["id"]) + bookmark = self.generate_bookmark(inst["id"]) links = [ { -- cgit From 63d9a592823cdb2e120514137a1d71d838b336d7 Mon Sep 17 00:00:00 2001 From: William Wolf Date: Fri, 15 Jul 2011 15:08:31 -0400 Subject: stop using get_uuid_from_href for now --- nova/api/openstack/views/servers.py | 36 +++++++++++++----------------------- 1 file changed, 13 insertions(+), 23 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index 71d685171..a9db8357e 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -161,28 +161,18 @@ class ViewBuilderV11(ViewBuilder): def _build_image(self, response, inst): if 'image_ref' in dict(inst): image_href = inst['image_ref'] - #if id is a uuid do: - if image_href == common.get_uuid_from_href(image_href): - image_id = image_href - _bookmark = self.image_builder.generate_bookmark(image_href) - response['image'] = { - "id": image_id, - "links": [ - { - "rel": "bookmark", - "href": _bookmark, - }, - ] - } - else: - response['image'] = { - "links": [ - { - "rel": "bookmark", - "href": image_href, - }, - ] - } + image_id = str(common.get_id_from_href(image_href)) + print "IMAGE ID:", image_id + _bookmark = self.image_builder.generate_bookmark(image_id) + response['image'] = { + "id": image_id, + "links": [ + { + "rel": "bookmark", + "href": _bookmark, + }, + ] + } def _build_flavor(self, response, inst): if "instance_type" in dict(inst): @@ -190,7 +180,7 @@ class ViewBuilderV11(ViewBuilder): flavor_ref = self.flavor_builder.generate_href(flavor_id) flavor_bookmark = self.flavor_builder.generate_bookmark(flavor_id) response["flavor"] = { - "id": common.get_uuid_from_href(flavor_ref), + "id": str(common.get_id_from_href(flavor_ref)), "links": [ { "rel": "bookmark", -- cgit From 1e16da70134537ae53d0c79214e70f0f06b14dd2 Mon Sep 17 00:00:00 2001 From: William Wolf Date: Fri, 15 Jul 2011 15:10:15 -0400 Subject: remove get_uuid_from_href and tests --- nova/api/openstack/common.py | 23 ----------------------- nova/api/openstack/views/servers.py | 1 - 2 files changed, 24 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py index e7aa7d693..8e12ce0c0 100644 --- a/nova/api/openstack/common.py +++ b/nova/api/openstack/common.py @@ -136,29 +136,6 @@ def get_id_from_href(href): raise ValueError(_('could not parse id from href')) -def get_uuid_from_href(href): - """Return the uuid portion of a url. - - Given: 'http://www.foo.com/bar/123?q=4' - Returns: '123' - - In order to support local hrefs, the href argument can be just an id: - Given: '123' - Returns: '123' - - """ - if not '/' in href: - return href - try: - href = urlparse(href).path.split('/')[-1] - if href == '': - raise Exception - return href - except: - LOG.debug(_("Error extracting uuid from href: %s") % href) - raise ValueError(_('could not parse uuid from href')) - - def remove_version_from_href(href): """Removes the first api version from the href. diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index a9db8357e..6d6ebbafd 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -195,7 +195,6 @@ class ViewBuilderV11(ViewBuilder): def _build_extra(self, response, inst): self._build_links(response, inst) - response['id'] = inst['id'] response['uuid'] = inst['uuid'] def _build_links(self, response, inst): -- cgit From 910dd2124123d8e1989ce85d57bcc2a245e39683 Mon Sep 17 00:00:00 2001 From: William Wolf Date: Fri, 15 Jul 2011 16:04:41 -0400 Subject: take out print statements --- nova/api/openstack/views/servers.py | 1 - 1 file changed, 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index 6d6ebbafd..817e6ddfc 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -162,7 +162,6 @@ class ViewBuilderV11(ViewBuilder): if 'image_ref' in dict(inst): image_href = inst['image_ref'] image_id = str(common.get_id_from_href(image_href)) - print "IMAGE ID:", image_id _bookmark = self.image_builder.generate_bookmark(image_id) response['image'] = { "id": image_id, -- cgit From 7498fa608def9613552cf0e26dcb03fddf7b298d Mon Sep 17 00:00:00 2001 From: John Tran Date: Fri, 15 Jul 2011 22:56:16 +0000 Subject: renamed priv method arg_to_dict since it's not just used for revoke. modified to conform to latest AWS EC2 API spec for authorize & revoke ingress params using the IpPermissions data structure, which nests lists of CIDR blocks (IpRanges) as well as lists of Group data --- nova/api/ec2/cloud.py | 83 ++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 69 insertions(+), 14 deletions(-) (limited to 'nova/api') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index acfd1361c..5c0c79429 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -441,7 +441,52 @@ class CloudController(object): g['ipPermissions'] += [r] return g - def _revoke_rule_args_to_dict(self, context, to_port=None, from_port=None, + def _rule_args_to_dict(self, context, kwargs): + rules = [] + if not kwargs.has_key('groups') and not kwargs.has_key('ip_ranges'): + rule = self._rule_dict_last_step(context, **kwargs) + if rule: + rules.append(rule) + return rules + if kwargs.has_key('ip_ranges'): + rules = self._cidr_args_split(kwargs) + finalset = [] + for rule in rules: + if rule.has_key('groups'): + groups_values = self._groups_args_split(rule) + for groups_value in groups_values: + finalset.append(groups_value) + else: + if rule: + finalset.append(rule) + return finalset + + def _cidr_args_split(self, kwargs): + cidr_args_split= [] + cidrs = kwargs['ip_ranges'] + for key, cidr in cidrs.iteritems(): + mykwargs = kwargs.copy() + del mykwargs['ip_ranges'] + mykwargs['cidr_ip'] = cidr['cidr_ip'] + cidr_args_split.append(mykwargs) + return cidr_args_split + + def _groups_args_split(self, kwargs): + groups_args_split= [] + groups = kwargs['groups'] + for key, group in groups.iteritems(): + mykwargs = kwargs.copy() + del mykwargs['groups'] + if group.has_key('group_name'): + mykwargs['source_security_group_name'] = group['group_name'] + if group.has_key('user_id'): + mykwargs['source_security_group_owner_id'] = group['user_id'] + if group.has_key('group_id'): + mykwargs['source_security_group_id'] = group['group_id'] + groups_args_split.append(mykwargs) + return groups_args_split + + def _rule_dict_last_step(self, context, to_port=None, from_port=None, ip_protocol=None, cidr_ip=None, user_id=None, source_security_group_name=None, source_security_group_owner_id=None): @@ -526,7 +571,7 @@ class CloudController(object): msg = "Revoke security group ingress %s" LOG.audit(_(msg), security_group['name'], context=context) - criteria = self._revoke_rule_args_to_dict(context, **kwargs) + criteria = self._rule_args_to_dict(context, kwargs)[0] if criteria is None: raise exception.ApiError(_("Not enough parameters to build a " "valid rule.")) @@ -567,21 +612,31 @@ class CloudController(object): msg = "Authorize security group ingress %s" LOG.audit(_(msg), security_group['name'], context=context) - values = self._revoke_rule_args_to_dict(context, **kwargs) - if values is None: - raise exception.ApiError(_("Not enough parameters to build a " - "valid rule.")) - values['parent_group_id'] = security_group.id - - if self._security_group_rule_exists(security_group, values): - raise exception.ApiError(_('This rule already exists in group %s') - % group_name) - - security_group_rule = db.security_group_rule_create(context, values) + prevalues = [] + try: + prevalues = kwargs['ip_permissions'] + except KeyError: + prevalues.append(kwargs) + postvalues = [] + for values in prevalues: + rulesvalues = self._rule_args_to_dict(context, values) + if not rulesvalues: + raise exception.ApiError(_("%s Not enough parameters to build a " + "valid rule." % rulesvalues)) + for values_for_rule in rulesvalues: + values_for_rule['parent_group_id'] = security_group.id + if self._security_group_rule_exists(security_group, values_for_rule): + raise exception.ApiError(_('%s - This rule already exists in group %s') + % (values_for_rule, group_name)) + postvalues.append(values_for_rule) + + for values_for_rule in postvalues: + security_group_rule = db.security_group_rule_create(context, values_for_rule) self.compute_api.trigger_security_group_rules_refresh(context, - security_group_id=security_group['id']) + security_group_id=security_group['id']) + group = db.security_group_get_by_name(context, context.project_id, security_group['name']) return True def _get_source_project_id(self, context, source_security_group_owner_id): -- cgit From 64a9c37cbf070345831ba6e4db646c5d972e179b Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Sat, 16 Jul 2011 19:17:08 -0400 Subject: Added ServerXMLSerializer with working 'show' method Factored out MetadataXMLSerializer from images and servers into common --- nova/api/openstack/common.py | 50 ++++++++++++++++++++ nova/api/openstack/image_metadata.py | 49 -------------------- nova/api/openstack/images.py | 4 +- nova/api/openstack/servers.py | 89 ++++++++++++++++++++++++++++++++++++ 4 files changed, 141 insertions(+), 51 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py index 8e12ce0c0..8a78292aa 100644 --- a/nova/api/openstack/common.py +++ b/nova/api/openstack/common.py @@ -17,12 +17,14 @@ import re from urlparse import urlparse +from xml.dom import minidom import webob from nova import exception from nova import flags from nova import log as logging +from nova.api.openstack import wsgi LOG = logging.getLogger('nova.api.openstack.common') @@ -162,3 +164,51 @@ def remove_version_from_href(href): msg = _('href does not contain version') raise ValueError(msg) return new_href + + +class MetadataXMLSerializer(wsgi.XMLDictSerializer): + def __init__(self, xmlns=wsgi.XMLNS_V11): + super(MetadataXMLSerializer, self).__init__(xmlns=xmlns) + + def _meta_item_to_xml(self, doc, key, value): + node = doc.createElement('meta') + doc.appendChild(node) + node.setAttribute('key', '%s' % key) + text = doc.createTextNode('%s' % value) + node.appendChild(text) + return node + + def meta_list_to_xml(self, xml_doc, meta_items): + container_node = xml_doc.createElement('metadata') + for (key, value) in meta_items: + item_node = self._meta_item_to_xml(xml_doc, key, value) + container_node.appendChild(item_node) + return container_node + + def _meta_list_to_xml_string(self, metadata_dict): + xml_doc = minidom.Document() + items = metadata_dict['metadata'].items() + container_node = self.meta_list_to_xml(xml_doc, items) + xml_doc.appendChild(container_node) + self._add_xmlns(container_node) + return xml_doc.toprettyxml(indent=' ', encoding='UTF-8') + + def index(self, metadata_dict): + return self._meta_list_to_xml_string(metadata_dict) + + def create(self, metadata_dict): + return self._meta_list_to_xml_string(metadata_dict) + + def _meta_item_to_xml_string(self, meta_item_dict): + xml_doc = minidom.Document() + item_key, item_value = meta_item_dict.items()[0] + item_node = self._meta_item_to_xml(xml_doc, item_key, item_value) + xml_doc.appendChild(item_node) + self._add_xmlns(item_node) + return xml_doc.toprettyxml(indent=' ', encoding='UTF-8') + + def show(self, meta_item_dict): + return self._meta_item_to_xml_string(meta_item_dict['meta']) + + def update(self, meta_item_dict): + return self._meta_item_to_xml_string(meta_item_dict['meta']) diff --git a/nova/api/openstack/image_metadata.py b/nova/api/openstack/image_metadata.py index 4f33844fa..dd1a3f130 100644 --- a/nova/api/openstack/image_metadata.py +++ b/nova/api/openstack/image_metadata.py @@ -16,7 +16,6 @@ # under the License. from webob import exc -from xml.dom import minidom from nova import flags from nova import image @@ -111,54 +110,6 @@ class Controller(object): self.image_service.update(context, image_id, img, None) -class ImageMetadataXMLSerializer(wsgi.XMLDictSerializer): - def __init__(self, xmlns=wsgi.XMLNS_V11): - super(ImageMetadataXMLSerializer, self).__init__(xmlns=xmlns) - - def _meta_item_to_xml(self, doc, key, value): - node = doc.createElement('meta') - doc.appendChild(node) - node.setAttribute('key', '%s' % key) - text = doc.createTextNode('%s' % value) - node.appendChild(text) - return node - - def meta_list_to_xml(self, xml_doc, meta_items): - container_node = xml_doc.createElement('metadata') - for (key, value) in meta_items: - item_node = self._meta_item_to_xml(xml_doc, key, value) - container_node.appendChild(item_node) - return container_node - - def _meta_list_to_xml_string(self, metadata_dict): - xml_doc = minidom.Document() - items = metadata_dict['metadata'].items() - container_node = self.meta_list_to_xml(xml_doc, items) - xml_doc.appendChild(container_node) - self._add_xmlns(container_node) - return xml_doc.toprettyxml(indent=' ', encoding='UTF-8') - - def index(self, metadata_dict): - return self._meta_list_to_xml_string(metadata_dict) - - def create(self, metadata_dict): - return self._meta_list_to_xml_string(metadata_dict) - - def _meta_item_to_xml_string(self, meta_item_dict): - xml_doc = minidom.Document() - item_key, item_value = meta_item_dict.items()[0] - item_node = self._meta_item_to_xml(xml_doc, item_key, item_value) - xml_doc.appendChild(item_node) - self._add_xmlns(item_node) - return xml_doc.toprettyxml(indent=' ', encoding='UTF-8') - - def show(self, meta_item_dict): - return self._meta_item_to_xml_string(meta_item_dict['meta']) - - def update(self, meta_item_dict): - return self._meta_item_to_xml_string(meta_item_dict['meta']) - - def create_resource(): body_serializers = { 'application/xml': ImageMetadataXMLSerializer(), diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index d0317583e..6c7c0feb9 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -278,9 +278,9 @@ class ImageXMLSerializer(wsgi.XMLDictSerializer): xmlns = wsgi.XMLNS_V11 def __init__(self): - self.metadata_serializer = image_metadata.ImageMetadataXMLSerializer() + self.metadata_serializer = common.MetadataXMLSerializer() - def _image_to_xml(self, xml_doc, image): + def image_to_xml(self, xml_doc, image): image_node = xml_doc.createElement('image') image_node.setAttribute('id', str(image['id'])) image_node.setAttribute('name', image['name']) diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 93f8e832c..b07b903f8 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -17,6 +17,7 @@ import base64 import traceback from webob import exc +from xml.dom import minidom from nova import compute from nova import db @@ -612,6 +613,94 @@ class HeadersSerializer(wsgi.ResponseHeadersSerializer): response.status_int = 204 +class ServerXMLSerializer(wsgi.XMLDictSerializer): + + xmlns = wsgi.XMLNS_V11 + + def __init__(self): + self.metadata_serializer = common.MetadataXMLSerializer() + + def _create_basic_entity_node(self, xml_doc, id, links, name): + basic_node = xml_doc.createElement(name) + basic_node.setAttribute('id', str(id)) + link_nodes = self._create_link_nodes(xml_doc, links) + for link_node in link_nodes: + basic_node.appendChild(link_node) + return basic_node + + def _create_metadata_node(self, xml_doc, metadata): + return self.metadata_serializer.meta_list_to_xml(xml_doc, metadata) + + def _create_addresses_node(self, xml_doc, addresses): + addresses_node = xml_doc.createElement('addresses') + for name, network_dict in addresses.items(): + network_node = self._create_network_node(xml_doc, + name, + network_dict) + addresses_node.appendChild(network_node) + return addresses_node + + def _create_network_node(self, xml_doc, network_name, network_dict): + network_node = xml_doc.createElement('network') + network_node.setAttribute('id', network_name) + for ip in network_dict: + ip_node = xml_doc.createElement('ip') + ip_node.setAttribute('version', str(ip['version'])) + ip_node.setAttribute('addr', ip['addr']) + network_node.appendChild(ip_node) + return network_node + + + def _add_server_attributes(self, node, server): + node.setAttribute('id', str(server['id'])) + node.setAttribute('uuid', str(server['uuid'])) + node.setAttribute('hostId', str(server['hostId'])) + node.setAttribute('name', server['name']) + node.setAttribute('created', server['created']) + node.setAttribute('updated', server['updated']) + node.setAttribute('status', server['status']) + if 'progress' in server: + node.setAttribute('progress', str(server['progress'])) + + def _server_to_xml_detailed(self, xml_doc, server): + server_node = xml_doc.createElement('server') + self._add_server_attributes(server_node, server) + + link_nodes = self._create_link_nodes(xml_doc, + server['links']) + for link_node in link_nodes: + server_node.appendChild(link_node) + + image_node = self._create_basic_entity_node(xml_doc, + server['image']['id'], + server['image']['links'], + 'image') + server_node.appendChild(image_node) + + flavor_node = self._create_basic_entity_node(xml_doc, + server['flavor']['id'], + server['flavor']['links'], + 'flavor') + server_node.appendChild(flavor_node) + + metadata = server.get('metadata', {}).items() + if len(metadata) > 0: + metadata_node = self._create_metadata_node(xml_doc, metadata) + server_node.appendChild(metadata_node) + + addresses_node = self._create_addresses_node(xml_doc, + server['addresses']) + server_node.appendChild(addresses_node) + + return server_node + + def show(self, server_dict): + xml_doc = minidom.Document() + node = self._server_to_xml_detailed(xml_doc, + server_dict['server']) + return self.to_xml_string(node, True) + + def create_resource(version='1.0'): controller = { '1.0': ControllerV10, -- cgit From 7af043463a350cfc71c45ff719354511173b5c39 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Sat, 16 Jul 2011 19:39:27 -0400 Subject: Moved Metadata Serialization Test --- nova/api/openstack/image_metadata.py | 3 ++- nova/api/openstack/images.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/image_metadata.py b/nova/api/openstack/image_metadata.py index dd1a3f130..d1065a4e3 100644 --- a/nova/api/openstack/image_metadata.py +++ b/nova/api/openstack/image_metadata.py @@ -21,6 +21,7 @@ from nova import flags from nova import image from nova import quota from nova import utils +from nova.api.openstack import common from nova.api.openstack import faults from nova.api.openstack import wsgi @@ -112,7 +113,7 @@ class Controller(object): def create_resource(): body_serializers = { - 'application/xml': ImageMetadataXMLSerializer(), + 'application/xml': common.MetadataXMLSerializer(), } serializer = wsgi.ResponseSerializer(body_serializers) diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index 6c7c0feb9..df7bf0cf9 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -280,7 +280,7 @@ class ImageXMLSerializer(wsgi.XMLDictSerializer): def __init__(self): self.metadata_serializer = common.MetadataXMLSerializer() - def image_to_xml(self, xml_doc, image): + def _image_to_xml(self, xml_doc, image): image_node = xml_doc.createElement('image') image_node.setAttribute('id', str(image['id'])) image_node.setAttribute('name', image['name']) -- cgit From c538d38d890e74382e928d225e8abdc57da9760e Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Sat, 16 Jul 2011 19:45:28 -0400 Subject: pep8 --- nova/api/openstack/image_metadata.py | 2 +- nova/api/openstack/servers.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/image_metadata.py b/nova/api/openstack/image_metadata.py index d1065a4e3..b9b782f92 100644 --- a/nova/api/openstack/image_metadata.py +++ b/nova/api/openstack/image_metadata.py @@ -21,7 +21,7 @@ from nova import flags from nova import image from nova import quota from nova import utils -from nova.api.openstack import common +from nova.api.openstack import common from nova.api.openstack import faults from nova.api.openstack import wsgi diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index b07b903f8..1a85fda58 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -650,7 +650,6 @@ class ServerXMLSerializer(wsgi.XMLDictSerializer): network_node.appendChild(ip_node) return network_node - def _add_server_attributes(self, node, server): node.setAttribute('id', str(server['id'])) node.setAttribute('uuid', str(server['uuid'])) -- cgit From 712493f65415a7a5fc727f6b316c66ef90f1cad5 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Sun, 17 Jul 2011 14:50:44 -0400 Subject: added index to servers xml serializer --- nova/api/openstack/servers.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 1a85fda58..8d59c8626 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -661,6 +661,16 @@ class ServerXMLSerializer(wsgi.XMLDictSerializer): if 'progress' in server: node.setAttribute('progress', str(server['progress'])) + def _server_to_xml(self, xml_doc, server): + server_node = xml_doc.createElement('server') + server_node.setAttribute('id', str(server['id'])) + server_node.setAttribute('name', server['name']) + link_nodes = self._create_link_nodes(xml_doc, + server['links']) + for link_node in link_nodes: + server_node.appendChild(link_node) + return server_node + def _server_to_xml_detailed(self, xml_doc, server): server_node = xml_doc.createElement('server') self._add_server_attributes(server_node, server) @@ -693,6 +703,25 @@ class ServerXMLSerializer(wsgi.XMLDictSerializer): return server_node + def _server_list_to_xml(self, xml_doc, servers, detailed): + container_node = xml_doc.createElement('servers') + if detailed: + server_to_xml = self._server_to_xml_detailed + else: + server_to_xml = self._server_to_xml + + for server in servers: + item_node = server_to_xml(xml_doc, server) + container_node.appendChild(item_node) + return container_node + + def index(self, servers_dict): + xml_doc = minidom.Document() + node = self._server_list_to_xml(xml_doc, + servers_dict['servers'], + detailed=False) + return self.to_xml_string(node, True) + def show(self, server_dict): xml_doc = minidom.Document() node = self._server_to_xml_detailed(xml_doc, -- cgit From b0a02feb7bd25380a75b83344e0ac3210a168387 Mon Sep 17 00:00:00 2001 From: John Tran Date: Sun, 17 Jul 2011 22:07:07 +0000 Subject: changed to avoid localization test failure --- nova/api/ec2/cloud.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'nova/api') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 5c0c79429..4923a7666 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -626,8 +626,8 @@ class CloudController(object): for values_for_rule in rulesvalues: values_for_rule['parent_group_id'] = security_group.id if self._security_group_rule_exists(security_group, values_for_rule): - raise exception.ApiError(_('%s - This rule already exists in group %s') - % (values_for_rule, group_name)) + raise exception.ApiError(_('%s - This rule already exists in group') + % values_for_rule) postvalues.append(values_for_rule) for values_for_rule in postvalues: -- cgit From baaaa80d36570d5734ac823bc49be8ff2477e5c2 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Sun, 17 Jul 2011 21:24:02 -0400 Subject: added 'detail' to server XML serializer --- nova/api/openstack/servers.py | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 8d59c8626..82630397f 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -722,6 +722,13 @@ class ServerXMLSerializer(wsgi.XMLDictSerializer): detailed=False) return self.to_xml_string(node, True) + def detail(self, servers_dict): + xml_doc = minidom.Document() + node = self._server_list_to_xml(xml_doc, + servers_dict['servers'], + detailed=True) + return self.to_xml_string(node, True) + def show(self, server_dict): xml_doc = minidom.Document() node = self._server_to_xml_detailed(xml_doc, -- cgit From 8ab775585fee4af7b30a28a5bffae46c23ec76d1 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Sun, 17 Jul 2011 21:36:57 -0400 Subject: added 'create' to server XML serializer --- nova/api/openstack/servers.py | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 82630397f..4a29e1454 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -735,6 +735,13 @@ class ServerXMLSerializer(wsgi.XMLDictSerializer): server_dict['server']) return self.to_xml_string(node, True) + def create(self, server_dict): + xml_doc = minidom.Document() + node = self._server_to_xml_detailed(xml_doc, + server_dict['server']) + node.setAttribute('adminPass', server_dict['server']['adminPass']) + return self.to_xml_string(node, True) + def create_resource(version='1.0'): controller = { -- cgit From e23e70afd096ca1d7ad22c776f6f439986bbc8b5 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Sun, 17 Jul 2011 22:28:16 -0400 Subject: updated servers to use ServerXMLSerializer --- nova/api/openstack/servers.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 4a29e1454..a6229125d 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -773,8 +773,7 @@ def create_resource(version='1.0'): headers_serializer = HeadersSerializer() body_serializers = { - 'application/xml': wsgi.XMLDictSerializer(metadata=metadata, - xmlns=xmlns), + 'application/xml': ServerXMLSerializer(), } body_deserializers = { -- cgit From b9d316452a1e2a204e56d1434feade1ab0bd281c Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Sun, 17 Jul 2011 22:50:10 -0400 Subject: Updated servers to choose XML serializer based on api version --- nova/api/openstack/servers.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index a6229125d..6d55b856d 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -772,8 +772,13 @@ def create_resource(version='1.0'): headers_serializer = HeadersSerializer() + xml_serializer = { + '1.0': wsgi.XMLDictSerializer(metadata, wsgi.XMLNS_V10), + '1.1': ServerXMLSerializer(), + }[version] + body_serializers = { - 'application/xml': ServerXMLSerializer(), + 'application/xml': xml_serializer, } body_deserializers = { -- cgit From ab42384131077bae3986141279b605d8f994143c Mon Sep 17 00:00:00 2001 From: John Tran Date: Mon, 18 Jul 2011 03:45:16 +0000 Subject: resolved pep8 issues --- nova/api/ec2/cloud.py | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) (limited to 'nova/api') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 4923a7666..113faf13b 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -443,16 +443,16 @@ class CloudController(object): def _rule_args_to_dict(self, context, kwargs): rules = [] - if not kwargs.has_key('groups') and not kwargs.has_key('ip_ranges'): + if not 'groups' in kwargs and not 'ip_ranges' in kwargs: rule = self._rule_dict_last_step(context, **kwargs) if rule: rules.append(rule) return rules - if kwargs.has_key('ip_ranges'): + if 'ip_ranges' in kwargs: rules = self._cidr_args_split(kwargs) finalset = [] for rule in rules: - if rule.has_key('groups'): + if 'groups' in rule: groups_values = self._groups_args_split(rule) for groups_value in groups_values: finalset.append(groups_value) @@ -462,7 +462,7 @@ class CloudController(object): return finalset def _cidr_args_split(self, kwargs): - cidr_args_split= [] + cidr_args_split = [] cidrs = kwargs['ip_ranges'] for key, cidr in cidrs.iteritems(): mykwargs = kwargs.copy() @@ -472,16 +472,16 @@ class CloudController(object): return cidr_args_split def _groups_args_split(self, kwargs): - groups_args_split= [] + groups_args_split = [] groups = kwargs['groups'] for key, group in groups.iteritems(): mykwargs = kwargs.copy() del mykwargs['groups'] - if group.has_key('group_name'): + if 'group_name' in group: mykwargs['source_security_group_name'] = group['group_name'] - if group.has_key('user_id'): + if 'user_id' in group: mykwargs['source_security_group_owner_id'] = group['user_id'] - if group.has_key('group_id'): + if 'group_id' in group: mykwargs['source_security_group_id'] = group['group_id'] groups_args_split.append(mykwargs) return groups_args_split @@ -621,22 +621,25 @@ class CloudController(object): for values in prevalues: rulesvalues = self._rule_args_to_dict(context, values) if not rulesvalues: - raise exception.ApiError(_("%s Not enough parameters to build a " - "valid rule." % rulesvalues)) + err = "%s Not enough parameters to build a valid rule" + raise exception.ApiError(_(err % rulesvalues)) for values_for_rule in rulesvalues: values_for_rule['parent_group_id'] = security_group.id - if self._security_group_rule_exists(security_group, values_for_rule): - raise exception.ApiError(_('%s - This rule already exists in group') - % values_for_rule) + if self._security_group_rule_exists(security_group, + values_for_rule): + err = '%s - This rule already exists in group' + raise exception.ApiError(_(err) % values_for_rule) postvalues.append(values_for_rule) for values_for_rule in postvalues: - security_group_rule = db.security_group_rule_create(context, values_for_rule) + security_group_rule = db.security_group_rule_create(context, + values_for_rule) self.compute_api.trigger_security_group_rules_refresh(context, security_group_id=security_group['id']) - group = db.security_group_get_by_name(context, context.project_id, security_group['name']) + group = db.security_group_get_by_name(context, context.project_id, + security_group['name']) return True def _get_source_project_id(self, context, source_security_group_owner_id): -- cgit From 55de6a262f40024c2c9c8f7c6e84c56eaa14d206 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Mon, 18 Jul 2011 17:34:36 -0400 Subject: adding fixed_ipv6 property to VirtualInterface model; exposing ipv6 in api --- nova/api/openstack/views/addresses.py | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/views/addresses.py b/nova/api/openstack/views/addresses.py index a242efa45..77310e4b1 100644 --- a/nova/api/openstack/views/addresses.py +++ b/nova/api/openstack/views/addresses.py @@ -50,22 +50,36 @@ class ViewBuilderV11(ViewBuilder): if network_label not in networks: networks[network_label] = [] - networks[network_label].extend(self._extract_ipv4(interface)) + ip_addresses = list(self._extract_ipv4_addresses(interface)) + + ipv6_address = self._extract_ipv6_address(interface) + if ipv6_address is not None: + ip_addresses.append(ipv6_address) + + networks[network_label].extend(ip_addresses) return networks def build_network(self, interfaces, network_label): for interface in interfaces: if interface['network']['label'] == network_label: - ips = self._extract_ipv4(interface) - return {network_label: list(ips)} + ips = list(self._extract_ipv4_addresses(interface)) + ipv6 = self._extract_ipv6_address(interface) + if ipv6 is not None: + ips.append(ipv6) + return {network_label: ips} return None - def _extract_ipv4(self, interface): + def _extract_ipv4_addresses(self, interface): for fixed_ip in interface['fixed_ips']: yield self._build_ip_entity(fixed_ip['address'], 4) for floating_ip in fixed_ip.get('floating_ips', []): yield self._build_ip_entity(floating_ip['address'], 4) + def _extract_ipv6_address(self, interface): + fixed_ipv6 = interface.get('fixed_ipv6') + if fixed_ipv6 is not None: + return self._build_ip_entity(fixed_ipv6, 6) + def _build_ip_entity(self, address, version): return {'addr': address, 'version': version} -- cgit From 82e2eeb5a097f1c3c6cb56fc3dfa862575f5da9a Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Tue, 19 Jul 2011 09:36:37 -0400 Subject: respecting use_ipv6 flag if set to False --- nova/api/openstack/views/addresses.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/views/addresses.py b/nova/api/openstack/views/addresses.py index 77310e4b1..ddbf7a144 100644 --- a/nova/api/openstack/views/addresses.py +++ b/nova/api/openstack/views/addresses.py @@ -15,9 +15,12 @@ # License for the specific language governing permissions and limitations # under the License. +from nova import flags from nova import utils from nova.api.openstack import common +FLAGS = flags.FLAGS + class ViewBuilder(object): """Models a server addresses response as a python dictionary.""" @@ -52,9 +55,10 @@ class ViewBuilderV11(ViewBuilder): ip_addresses = list(self._extract_ipv4_addresses(interface)) - ipv6_address = self._extract_ipv6_address(interface) - if ipv6_address is not None: - ip_addresses.append(ipv6_address) + if FLAGS.use_ipv6: + ipv6_address = self._extract_ipv6_address(interface) + if ipv6_address is not None: + ip_addresses.append(ipv6_address) networks[network_label].extend(ip_addresses) -- cgit From 4b4bebad3b44e7b55e55a005a3629aebf50ecfa2 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Tue, 19 Jul 2011 15:51:45 -0400 Subject: Updates to the compute API and manager so that rebuild, reboot, snapshots, and password resets work with the most recent versions of novaclient. --- 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 93f8e832c..e16221fd8 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -454,7 +454,7 @@ class ControllerV10(Controller): def _action_rebuild(self, info, request, instance_id): context = request.environ['nova.context'] - instance_id = int(instance_id) + instance_id = instance_id try: image_id = info["rebuild"]["imageId"] @@ -569,7 +569,7 @@ class ControllerV11(Controller): def _action_rebuild(self, info, request, instance_id): context = request.environ['nova.context'] - instance_id = int(instance_id) + instance_id = instance_id try: image_href = info["rebuild"]["imageRef"] -- cgit From bc2747faf2164833b3e5412dd226f9ee431b1bbf Mon Sep 17 00:00:00 2001 From: William Wolf Date: Tue, 19 Jul 2011 17:51:43 -0400 Subject: updated links to use proper atom:link per spec --- nova/api/openstack/versions.py | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py index a634c3267..450b42765 100644 --- a/nova/api/openstack/versions.py +++ b/nova/api/openstack/versions.py @@ -17,6 +17,7 @@ import webob import webob.dec +from xml.dom import minidom import nova.api.openstack.views.versions from nova.api.openstack import wsgi @@ -32,7 +33,7 @@ class Versions(wsgi.Resource): } body_serializers = { - 'application/xml': wsgi.XMLDictSerializer(metadata=metadata), + 'application/xml': VersionsXMLSerializer(metadata=metadata), } serializer = wsgi.ResponseSerializer(body_serializers) @@ -54,3 +55,36 @@ class Versions(wsgi.Resource): builder = nova.api.openstack.views.versions.get_view_builder(request) versions = [builder.build(version) for version in version_objs] return dict(versions=versions) + +class VersionsXMLSerializer(wsgi.XMLDictSerializer): + def __init__(self, metadata=None, xmlns=None): + super(VersionsXMLSerializer, self).__init__(metadata, xmlns) + + def _versions_to_xml(self, versions): + root = self.xml_doc.createElement('versions') + + for version in versions: + root.appendChild(self._create_version_node(version)) + + return root + + def _create_version_node(self, version): + version_node = self.xml_doc.createElement('version') + version_node.setAttribute('id', version['id']) + version_node.setAttribute('status', version['status']) + #TODO(wwolf) need 'updated' attribute too + + for link in version['links']: + link_node = self.xml_doc.createElement('atom:link') + link_node.setAttribute('rel', link['rel']) + link_node.setAttribute('href', link['href']) + version_node.appendChild(link_node) + + return version_node + + + def default(self, data): + self.xml_doc = minidom.Document() + node = self._versions_to_xml(data['versions']) + + return self.to_xml_string(node) -- cgit From 27f326c712e485322003ccdc13acfd04a6fdb119 Mon Sep 17 00:00:00 2001 From: William Wolf Date: Wed, 20 Jul 2011 09:59:56 -0400 Subject: added 'update' field to versions --- nova/api/openstack/versions.py | 6 +++++- nova/api/openstack/views/versions.py | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py index 450b42765..1b1fc41ab 100644 --- a/nova/api/openstack/versions.py +++ b/nova/api/openstack/versions.py @@ -45,10 +45,14 @@ class Versions(wsgi.Resource): { "id": "v1.1", "status": "CURRENT", + #TODO(wwolf) get correct value for these + "updated": "2011-7-18T11:30:00Z", }, { "id": "v1.0", "status": "DEPRECATED", + #TODO(wwolf) get correct value for these + "updated": "2010-10-09T11:30:00Z", }, ] @@ -72,7 +76,7 @@ class VersionsXMLSerializer(wsgi.XMLDictSerializer): version_node = self.xml_doc.createElement('version') version_node.setAttribute('id', version['id']) version_node.setAttribute('status', version['status']) - #TODO(wwolf) need 'updated' attribute too + version_node.setAttribute('updated', version['updated']) for link in version['links']: link_node = self.xml_doc.createElement('atom:link') diff --git a/nova/api/openstack/views/versions.py b/nova/api/openstack/views/versions.py index d0145c94a..e815974f1 100644 --- a/nova/api/openstack/views/versions.py +++ b/nova/api/openstack/views/versions.py @@ -36,6 +36,7 @@ class ViewBuilder(object): version = { "id": version_data["id"], "status": version_data["status"], + "updated": version_data["updated"], "links": self._build_links(version_data), } -- cgit From 1ba04869623a0152a487a50e25bfce0ee6a65f53 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Wed, 20 Jul 2011 10:30:03 -0400 Subject: Ya! Apparently sleep helps me fix failing tests. --- 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 e16221fd8..1e09b6ea0 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -466,7 +466,7 @@ class ControllerV10(Controller): try: self.compute_api.rebuild(context, instance_id, image_id) except exception.BuildInProgress: - msg = _("Instance %d is currently being rebuilt.") % instance_id + msg = _("Instance %s is currently being rebuilt.") % instance_id LOG.debug(msg) return faults.Fault(exc.HTTPConflict(explanation=msg)) @@ -590,7 +590,7 @@ class ControllerV11(Controller): self.compute_api.rebuild(context, instance_id, image_href, name, metadata, personalities) except exception.BuildInProgress: - msg = _("Instance %d is currently being rebuilt.") % instance_id + msg = _("Instance %s is currently being rebuilt.") % instance_id LOG.debug(msg) return faults.Fault(exc.HTTPConflict(explanation=msg)) -- cgit From a35a70fbaef0ef6634213308d5a68ee60bd714f2 Mon Sep 17 00:00:00 2001 From: William Wolf Date: Wed, 20 Jul 2011 13:12:18 -0400 Subject: initial changes for application/atom+xml for versions --- nova/api/openstack/versions.py | 98 ++++++++++++++++++++++++++++++++++++++---- nova/api/openstack/wsgi.py | 19 +++++--- 2 files changed, 103 insertions(+), 14 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py index 1b1fc41ab..a83472e15 100644 --- a/nova/api/openstack/versions.py +++ b/nova/api/openstack/versions.py @@ -23,7 +23,11 @@ import nova.api.openstack.views.versions from nova.api.openstack import wsgi +ATOM_XMLNS = "http://www.w3.org/2005/Atom" + + class Versions(wsgi.Resource): + def __init__(self): metadata = { "attributes": { @@ -33,11 +37,19 @@ class Versions(wsgi.Resource): } body_serializers = { + 'application/atom+xml': VersionsAtomSerializer(metadata=metadata), 'application/xml': VersionsXMLSerializer(metadata=metadata), } serializer = wsgi.ResponseSerializer(body_serializers) - wsgi.Resource.__init__(self, None, serializer=serializer) + supported_content_types = ('application/json', + 'application/xml', + 'application/atom+xml') + deserializer = wsgi.RequestDeserializer( + supported_content_types=supported_content_types) + + wsgi.Resource.__init__(self, None, serializer=serializer, + deserializer=deserializer) def dispatch(self, request, *args): """Respond to a request for all OpenStack API versions.""" @@ -61,11 +73,8 @@ class Versions(wsgi.Resource): return dict(versions=versions) class VersionsXMLSerializer(wsgi.XMLDictSerializer): - def __init__(self, metadata=None, xmlns=None): - super(VersionsXMLSerializer, self).__init__(metadata, xmlns) - def _versions_to_xml(self, versions): - root = self.xml_doc.createElement('versions') + root = self._xml_doc.createElement('versions') for version in versions: root.appendChild(self._create_version_node(version)) @@ -73,13 +82,13 @@ class VersionsXMLSerializer(wsgi.XMLDictSerializer): return root def _create_version_node(self, version): - version_node = self.xml_doc.createElement('version') + version_node = self._xml_doc.createElement('version') version_node.setAttribute('id', version['id']) version_node.setAttribute('status', version['status']) version_node.setAttribute('updated', version['updated']) for link in version['links']: - link_node = self.xml_doc.createElement('atom:link') + link_node = self._xml_doc.createElement('atom:link') link_node.setAttribute('rel', link['rel']) link_node.setAttribute('href', link['href']) version_node.appendChild(link_node) @@ -88,7 +97,80 @@ class VersionsXMLSerializer(wsgi.XMLDictSerializer): def default(self, data): - self.xml_doc = minidom.Document() + self._xml_doc = minidom.Document() node = self._versions_to_xml(data['versions']) return self.to_xml_string(node) + +class VersionsAtomSerializer(wsgi.XMLDictSerializer): + def __init__(self, metadata=None, xmlns=None): + if not xmlns: + self.xmlns = ATOM_XMLNS + else: + self.xmlns = xmlns + + def _create_text_elem(self, name, text, type=None): + elem = self._xml_doc.createElement(name) + if type: + elem.setAttribute('type', type) + elem_text = self._xml_doc.createTextNode(text) + elem.appendChild(elem_text) + return elem + + def _create_meta(self, root): + title = self._create_text_elem('title', 'Available API Versions', + type='text') + #TODO(wwolf): what should updated be? + updated = self._create_text_elem('updated', '2010-12-12T18:30:02.25Z') + #TODO(wwolf): get URI + id = self._create_text_elem('id', '') + #TODO(wwolf): get link info + link = self._xml_doc.createElement('link') + link.setAttribute('rel', 'rel') + link.setAttribute('href', 'href') + + author = self._xml_doc.createElement('author') + author_name = self._create_text_elem('name', 'Rackspace') + author_uri = self._create_text_elem('uri', 'http://www.rackspace.com') + author.appendChild(author_name) + author.appendChild(author_uri) + + root.appendChild(title) + root.appendChild(updated) + root.appendChild(id) + root.appendChild(link) + root.appendChild(author) + + def _create_version_entries(self, root, versions): + for version in versions: + entry = self._xml_doc.createElement('entry') + #TODO(wwolf) GET URI + id = self._create_text_elem('id', 'URI') + title = self._create_text_elem('title', + 'Version %s' % version['id'], + type='text') + updated = self._create_text_elem('updated', version['updated']) + #TODO(wwolf): get link info + link = self._xml_doc.createElement('link') + link.setAttribute('rel', 'rel') + link.setAttribute('href', 'href') + content = self._create_text_elem('content', + 'Version %s %s (%s)' % + (version['id'], + version['status'], + version['updated'])) + + entry.appendChild(id) + entry.appendChild(title) + entry.appendChild(updated) + entry.appendChild(link) + entry.appendChild(content) + root.appendChild(entry) + + def default(self, data): + self._xml_doc = minidom.Document() + node = self._xml_doc.createElementNS(self.xmlns, 'feed') + self._create_meta(node) + self._create_version_entries(node, data['versions']) + + return self.to_xml_string(node) diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index c3f841aa5..9df6fd058 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -20,21 +20,22 @@ LOG = logging.getLogger('nova.api.openstack.wsgi') class Request(webob.Request): """Add some Openstack API-specific logic to the base webob.Request.""" - def best_match_content_type(self): + def best_match_content_type(self, supported_content_types=None): """Determine the requested response content-type. Based on the query extension then the Accept header. """ - supported = ('application/json', 'application/xml') + supported_content_types = supported_content_types or \ + ('application/json', 'application/xml') parts = self.path.rsplit('.', 1) if len(parts) > 1: ctype = 'application/{0}'.format(parts[1]) - if ctype in supported: + if ctype in supported_content_types: return ctype - bm = self.accept.best_match(supported) + bm = self.accept.best_match(supported_content_types) # default to application/json if we don't find a preference return bm or 'application/json' @@ -151,7 +152,12 @@ class RequestHeadersDeserializer(ActionDispatcher): class RequestDeserializer(object): """Break up a Request object into more useful pieces.""" - def __init__(self, body_deserializers=None, headers_deserializer=None): + def __init__(self, body_deserializers=None, headers_deserializer=None, + supported_content_types=None): + + self.supported_content_types = supported_content_types or \ + ('application/json', 'application/xml') + self.body_deserializers = { 'application/xml': XMLDeserializer(), 'application/json': JSONDeserializer(), @@ -213,7 +219,7 @@ class RequestDeserializer(object): raise exception.InvalidContentType(content_type=content_type) def get_expected_content_type(self, request): - return request.best_match_content_type() + return request.best_match_content_type(self.supported_content_types) def get_action_args(self, request_environment): """Parse dictionary created by routes library.""" @@ -412,6 +418,7 @@ class Resource(wsgi.Application): serialized by requested content type. """ + def __init__(self, controller, deserializer=None, serializer=None): """ :param controller: object that implement methods created by routes lib -- cgit From 1b423b464e0dd849d1a6dafde08b3c4420217151 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Wed, 20 Jul 2011 13:52:22 -0400 Subject: Set the status_int on fault wrapped exceptions. Fixes WSGI logging issues when faults are returned. Updated so that webob exceptions aren't used for the happy path (HTTP 200 responses). We now return a proper webob object response in these cases. This fixes issues where HTML/XML would get returned with the old style happy path exceptions. --- nova/api/openstack/consoles.py | 3 ++- nova/api/openstack/contrib/multinic.py | 5 ++-- nova/api/openstack/contrib/volumes.py | 5 ++-- nova/api/openstack/faults.py | 1 + nova/api/openstack/servers.py | 49 ++++++++++++++++------------------ 5 files changed, 32 insertions(+), 31 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/consoles.py b/nova/api/openstack/consoles.py index 7a43fba96..9c7b37f0d 100644 --- a/nova/api/openstack/consoles.py +++ b/nova/api/openstack/consoles.py @@ -16,6 +16,7 @@ # under the License. from webob import exc +import webob from nova import console from nova import exception @@ -86,7 +87,7 @@ class Controller(object): int(id)) except exception.NotFound: return faults.Fault(exc.HTTPNotFound()) - return exc.HTTPAccepted() + return webob.Response(status_int=202) def create_resource(): diff --git a/nova/api/openstack/contrib/multinic.py b/nova/api/openstack/contrib/multinic.py index 841061721..da8dcee5d 100644 --- a/nova/api/openstack/contrib/multinic.py +++ b/nova/api/openstack/contrib/multinic.py @@ -16,6 +16,7 @@ """The multinic extension.""" from webob import exc +import webob from nova import compute from nova import log as logging @@ -103,7 +104,7 @@ class Multinic(extensions.ExtensionDescriptor): except Exception, e: LOG.exception(_("Error in addFixedIp %s"), e) return faults.Fault(exc.HTTPBadRequest()) - return exc.HTTPAccepted() + return webob.Response(status_int=202) def _remove_fixed_ip(self, input_dict, req, id): """Removes an IP from an instance.""" @@ -122,4 +123,4 @@ class Multinic(extensions.ExtensionDescriptor): except Exception, e: LOG.exception(_("Error in removeFixedIp %s"), e) return faults.Fault(exc.HTTPBadRequest()) - return exc.HTTPAccepted() + return webob.Response(status_int=202) diff --git a/nova/api/openstack/contrib/volumes.py b/nova/api/openstack/contrib/volumes.py index e5e2c5b50..827e36097 100644 --- a/nova/api/openstack/contrib/volumes.py +++ b/nova/api/openstack/contrib/volumes.py @@ -16,6 +16,7 @@ """The volumes extension.""" from webob import exc +import webob from nova import compute from nova import exception @@ -104,7 +105,7 @@ class VolumeController(object): self.volume_api.delete(context, volume_id=id) except exception.NotFound: return faults.Fault(exc.HTTPNotFound()) - return exc.HTTPAccepted() + return webob.Response(status_int=202) def index(self, req): """Returns a summary list of volumes.""" @@ -279,7 +280,7 @@ class VolumeAttachmentController(object): self.compute_api.detach_volume(context, volume_id=volume_id) - return exc.HTTPAccepted() + return webob.Response(status_int=202) def _items(self, req, server_id, entity_maker): """Returns a list of attachments, transformed through entity_maker.""" diff --git a/nova/api/openstack/faults.py b/nova/api/openstack/faults.py index b9a23c126..24cde69e4 100644 --- a/nova/api/openstack/faults.py +++ b/nova/api/openstack/faults.py @@ -40,6 +40,7 @@ class Fault(webob.exc.HTTPException): def __init__(self, exception): """Create a Fault for the given webob.exc.exception.""" self.wrapped_exc = exception + self.status_int = exception.status_int @webob.dec.wsgify(RequestClass=wsgi.Request) def __call__(self, req): diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 93f8e832c..3b3d0685d 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -17,6 +17,7 @@ import base64 import traceback from webob import exc +import webob from nova import compute from nova import db @@ -189,7 +190,7 @@ class Controller(object): except Exception, e: LOG.exception(_("Error in revert-resize %s"), e) return faults.Fault(exc.HTTPBadRequest()) - return exc.HTTPAccepted() + return webob.Response(status_int=202) def _action_resize(self, input_dict, req, id): return exc.HTTPNotImplemented() @@ -207,7 +208,7 @@ class Controller(object): except Exception, e: LOG.exception(_("Error in reboot %s"), e) return faults.Fault(exc.HTTPUnprocessableEntity()) - return exc.HTTPAccepted() + return webob.Response(status_int=202) def _action_migrate(self, input_dict, req, id): try: @@ -215,7 +216,7 @@ class Controller(object): except Exception, e: LOG.exception(_("Error in migrate %s"), e) return faults.Fault(exc.HTTPBadRequest()) - return exc.HTTPAccepted() + return webob.Response(status_int=202) @scheduler_api.redirect_handler def lock(self, req, id): @@ -231,7 +232,7 @@ class Controller(object): readable = traceback.format_exc() LOG.exception(_("Compute.api::lock %s"), readable) return faults.Fault(exc.HTTPUnprocessableEntity()) - return exc.HTTPAccepted() + return webob.Response(status_int=202) @scheduler_api.redirect_handler def unlock(self, req, id): @@ -247,7 +248,7 @@ class Controller(object): readable = traceback.format_exc() LOG.exception(_("Compute.api::unlock %s"), readable) return faults.Fault(exc.HTTPUnprocessableEntity()) - return exc.HTTPAccepted() + return webob.Response(status_int=202) @scheduler_api.redirect_handler def get_lock(self, req, id): @@ -262,7 +263,7 @@ class Controller(object): readable = traceback.format_exc() LOG.exception(_("Compute.api::get_lock %s"), readable) return faults.Fault(exc.HTTPUnprocessableEntity()) - return exc.HTTPAccepted() + return webob.Response(status_int=202) @scheduler_api.redirect_handler def reset_network(self, req, id, body): @@ -277,7 +278,7 @@ class Controller(object): readable = traceback.format_exc() LOG.exception(_("Compute.api::reset_network %s"), readable) return faults.Fault(exc.HTTPUnprocessableEntity()) - return exc.HTTPAccepted() + return webob.Response(status_int=202) @scheduler_api.redirect_handler def inject_network_info(self, req, id, body): @@ -292,7 +293,7 @@ class Controller(object): readable = traceback.format_exc() LOG.exception(_("Compute.api::inject_network_info %s"), readable) return faults.Fault(exc.HTTPUnprocessableEntity()) - return exc.HTTPAccepted() + return webob.Response(status_int=202) @scheduler_api.redirect_handler def pause(self, req, id, body): @@ -304,7 +305,7 @@ class Controller(object): readable = traceback.format_exc() LOG.exception(_("Compute.api::pause %s"), readable) return faults.Fault(exc.HTTPUnprocessableEntity()) - return exc.HTTPAccepted() + return webob.Response(status_int=202) @scheduler_api.redirect_handler def unpause(self, req, id, body): @@ -316,7 +317,7 @@ class Controller(object): readable = traceback.format_exc() LOG.exception(_("Compute.api::unpause %s"), readable) return faults.Fault(exc.HTTPUnprocessableEntity()) - return exc.HTTPAccepted() + return webob.Response(status_int=202) @scheduler_api.redirect_handler def suspend(self, req, id, body): @@ -328,7 +329,7 @@ class Controller(object): readable = traceback.format_exc() LOG.exception(_("compute.api::suspend %s"), readable) return faults.Fault(exc.HTTPUnprocessableEntity()) - return exc.HTTPAccepted() + return webob.Response(status_int=202) @scheduler_api.redirect_handler def resume(self, req, id, body): @@ -340,7 +341,7 @@ class Controller(object): readable = traceback.format_exc() LOG.exception(_("compute.api::resume %s"), readable) return faults.Fault(exc.HTTPUnprocessableEntity()) - return exc.HTTPAccepted() + return webob.Response(status_int=202) @scheduler_api.redirect_handler def rescue(self, req, id): @@ -352,7 +353,7 @@ class Controller(object): readable = traceback.format_exc() LOG.exception(_("compute.api::rescue %s"), readable) return faults.Fault(exc.HTTPUnprocessableEntity()) - return exc.HTTPAccepted() + return webob.Response(status_int=202) @scheduler_api.redirect_handler def unrescue(self, req, id): @@ -364,7 +365,7 @@ class Controller(object): readable = traceback.format_exc() LOG.exception(_("compute.api::unrescue %s"), readable) return faults.Fault(exc.HTTPUnprocessableEntity()) - return exc.HTTPAccepted() + return webob.Response(status_int=202) @scheduler_api.redirect_handler def get_ajax_console(self, req, id): @@ -374,7 +375,7 @@ class Controller(object): int(id)) except exception.NotFound: return faults.Fault(exc.HTTPNotFound()) - return exc.HTTPAccepted() + return webob.Response(status_int=202) @scheduler_api.redirect_handler def get_vnc_console(self, req, id): @@ -384,7 +385,7 @@ class Controller(object): int(id)) except exception.NotFound: return faults.Fault(exc.HTTPNotFound()) - return exc.HTTPAccepted() + return webob.Response(status_int=202) @scheduler_api.redirect_handler def diagnostics(self, req, id): @@ -416,7 +417,7 @@ class ControllerV10(Controller): self.compute_api.delete(req.environ['nova.context'], id) except exception.NotFound: return faults.Fault(exc.HTTPNotFound()) - return exc.HTTPAccepted() + return webob.Response(status_int=202) def _image_ref_from_req_data(self, data): return data['server']['imageId'] @@ -450,7 +451,7 @@ class ControllerV10(Controller): except Exception, e: LOG.exception(_("Error in resize %s"), e) return faults.Fault(exc.HTTPBadRequest()) - return exc.HTTPAccepted() + return webob.Response(status_int=202) def _action_rebuild(self, info, request, instance_id): context = request.environ['nova.context'] @@ -470,9 +471,7 @@ class ControllerV10(Controller): LOG.debug(msg) return faults.Fault(exc.HTTPConflict(explanation=msg)) - response = exc.HTTPAccepted() - response.empty_body = True - return response + return webob.Response(status_int=202) def _get_server_admin_password(self, server): """ Determine the admin password for a server on creation """ @@ -519,7 +518,7 @@ class ControllerV11(Controller): msg = _("Invalid adminPass") return exc.HTTPBadRequest(explanation=msg) self.compute_api.set_admin_password(context, id, password) - return exc.HTTPAccepted() + return webob.Response(status_int=202) def _limit_items(self, items, req): return common.limited_by_marker(items, req) @@ -565,7 +564,7 @@ class ControllerV11(Controller): except Exception, e: LOG.exception(_("Error in resize %s"), e) return faults.Fault(exc.HTTPBadRequest()) - return exc.HTTPAccepted() + return webob.Response(status_int=202) def _action_rebuild(self, info, request, instance_id): context = request.environ['nova.context'] @@ -594,9 +593,7 @@ class ControllerV11(Controller): LOG.debug(msg) return faults.Fault(exc.HTTPConflict(explanation=msg)) - response = exc.HTTPAccepted() - response.empty_body = True - return response + return webob.Response(status_int=202) def get_default_xmlns(self, req): return common.XML_NS_V11 -- cgit From 148b4095a5f18f192ae243b02b4070af384c1152 Mon Sep 17 00:00:00 2001 From: William Wolf Date: Wed, 20 Jul 2011 16:47:17 -0400 Subject: added versions list atom test and it passes --- nova/api/openstack/versions.py | 68 ++++++++++++++++++++++++------------ nova/api/openstack/views/versions.py | 2 +- 2 files changed, 46 insertions(+), 24 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py index a83472e15..49633df88 100644 --- a/nova/api/openstack/versions.py +++ b/nova/api/openstack/versions.py @@ -15,6 +15,7 @@ # License for the specific language governing permissions and limitations # under the License. +from datetime import datetime import webob import webob.dec from xml.dom import minidom @@ -58,7 +59,7 @@ class Versions(wsgi.Resource): "id": "v1.1", "status": "CURRENT", #TODO(wwolf) get correct value for these - "updated": "2011-7-18T11:30:00Z", + "updated": "2011-07-18T11:30:00Z", }, { "id": "v1.0", @@ -117,60 +118,81 @@ class VersionsAtomSerializer(wsgi.XMLDictSerializer): elem.appendChild(elem_text) return elem - def _create_meta(self, root): + def _get_most_recent_update(self, versions): + recent = None + for version in versions: + updated = datetime.strptime(version['updated'],'%Y-%m-%dT%H:%M:%SZ') + if not recent: + recent = updated + elif updated > recent: + recent = updated + + return recent.strftime('%Y-%m-%dT%H:%M:%SZ') + + def _get_base_url(self, link_href): + # Make sure no trailing / + link_href = link_href.rstrip('/') + return link_href.rsplit('/',1)[0] + '/' + + def _create_meta(self, root, versions): title = self._create_text_elem('title', 'Available API Versions', type='text') - #TODO(wwolf): what should updated be? - updated = self._create_text_elem('updated', '2010-12-12T18:30:02.25Z') - #TODO(wwolf): get URI - id = self._create_text_elem('id', '') - #TODO(wwolf): get link info + # Set this updated to the most recently updated version + recent = self._get_most_recent_update(versions) + updated = self._create_text_elem('updated', recent) + + base_url = self._get_base_url(versions[0]['links'][0]['href']) + id = self._create_text_elem('id', base_url) link = self._xml_doc.createElement('link') - link.setAttribute('rel', 'rel') - link.setAttribute('href', 'href') + link.setAttribute('rel', 'self') + link.setAttribute('href', base_url) author = self._xml_doc.createElement('author') author_name = self._create_text_elem('name', 'Rackspace') - author_uri = self._create_text_elem('uri', 'http://www.rackspace.com') + author_uri = self._create_text_elem('uri', 'http://www.rackspace.com/') author.appendChild(author_name) author.appendChild(author_uri) root.appendChild(title) root.appendChild(updated) root.appendChild(id) - root.appendChild(link) root.appendChild(author) + root.appendChild(link) def _create_version_entries(self, root, versions): for version in versions: entry = self._xml_doc.createElement('entry') - #TODO(wwolf) GET URI - id = self._create_text_elem('id', 'URI') + + id = self._create_text_elem('id', version['links'][0]['href']) title = self._create_text_elem('title', 'Version %s' % version['id'], type='text') updated = self._create_text_elem('updated', version['updated']) - #TODO(wwolf): get link info - link = self._xml_doc.createElement('link') - link.setAttribute('rel', 'rel') - link.setAttribute('href', 'href') + + entry.appendChild(id) + entry.appendChild(title) + entry.appendChild(updated) + + for link in version['links']: + link_node = self._xml_doc.createElement('link') + link_node.setAttribute('rel', link['rel']) + link_node.setAttribute('href', link['href']) + entry.appendChild(link_node) + content = self._create_text_elem('content', 'Version %s %s (%s)' % (version['id'], version['status'], - version['updated'])) + version['updated']), + type='text') - entry.appendChild(id) - entry.appendChild(title) - entry.appendChild(updated) - entry.appendChild(link) entry.appendChild(content) root.appendChild(entry) def default(self, data): self._xml_doc = minidom.Document() node = self._xml_doc.createElementNS(self.xmlns, 'feed') - self._create_meta(node) + self._create_meta(node, data['versions']) self._create_version_entries(node, data['versions']) return self.to_xml_string(node) diff --git a/nova/api/openstack/views/versions.py b/nova/api/openstack/views/versions.py index e815974f1..9fa8f49dc 100644 --- a/nova/api/openstack/views/versions.py +++ b/nova/api/openstack/views/versions.py @@ -57,4 +57,4 @@ class ViewBuilder(object): def generate_href(self, version_number): """Create an url that refers to a specific version_number.""" - return os.path.join(self.base_url, version_number) + return os.path.join(self.base_url, version_number) + '/' -- cgit From 38fb47ab2a12be0b169e3f51f4da413efc041f9c Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Wed, 20 Jul 2011 17:06:36 -0400 Subject: adding flavors xml serialization --- nova/api/openstack/flavors.py | 55 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 51 insertions(+), 4 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/flavors.py b/nova/api/openstack/flavors.py index 6fab13147..b4bda68d4 100644 --- a/nova/api/openstack/flavors.py +++ b/nova/api/openstack/flavors.py @@ -16,6 +16,7 @@ # under the License. import webob +import xml.dom.minidom as minidom from nova import db from nova import exception @@ -74,19 +75,65 @@ class ControllerV11(Controller): return views.flavors.ViewBuilderV11(base_url) +class FlavorXMLSerializer(wsgi.XMLDictSerializer): + + def __init__(self): + super(FlavorXMLSerializer, self).__init__(xmlns=wsgi.XMLNS_V11) + + def _flavor_to_xml(self, xml_doc, flavor, detailed): + flavor_node = xml_doc.createElement('flavor') + flavor_node.setAttribute('id', str(flavor['id'])) + flavor_node.setAttribute('name', flavor['name']) + + if detailed: + flavor_node.setAttribute('ram', str(flavor['ram'])) + flavor_node.setAttribute('disk', str(flavor['disk'])) + + link_nodes = self._create_link_nodes(xml_doc, flavor['links']) + for link_node in link_nodes: + flavor_node.appendChild(link_node) + return flavor_node + + def _flavors_list_to_xml(self, xml_doc, flavors, detailed): + container_node = xml_doc.createElement('flavors') + + for flavor in flavors: + item_node = self._flavor_to_xml(xml_doc, flavor, detailed) + container_node.appendChild(item_node) + return container_node + + def show(self, flavor_container): + xml_doc = minidom.Document() + flavor = flavor_container['flavor'] + node = self._flavor_to_xml(xml_doc, flavor, True) + return self.to_xml_string(node, True) + + def detail(self, flavors_container): + xml_doc = minidom.Document() + flavors = flavors_container['flavors'] + node = self._flavors_list_to_xml(xml_doc, flavors, True) + return self.to_xml_string(node, True) + + def index(self, flavors_container): + xml_doc = minidom.Document() + flavors = flavors_container['flavors'] + node = self._flavors_list_to_xml(xml_doc, flavors, False) + return self.to_xml_string(node, True) + + def create_resource(version='1.0'): controller = { '1.0': ControllerV10, '1.1': ControllerV11, }[version]() - xmlns = { - '1.0': wsgi.XMLNS_V10, - '1.1': wsgi.XMLNS_V11, + xml_serializer = { + '1.0': wsgi.XMLDictSerializer(xmlns=wsgi.XMLNS_V10), + '1.1': FlavorXMLSerializer(), }[version] body_serializers = { - 'application/xml': wsgi.XMLDictSerializer(xmlns=xmlns), + 'application/xml': xml_serializer, } serializer = wsgi.ResponseSerializer(body_serializers) -- cgit From 97503418bea47ca38d2181e5dc5fa710d6e15df3 Mon Sep 17 00:00:00 2001 From: Yoshiaki Tamura Date: Thu, 21 Jul 2011 21:44:12 +0900 Subject: Add OpenStack API support for block_device_mapping. This patch enables boot from volume feature already implemented in EC2 API, as an OpenStack API extension. --- nova/api/openstack/contrib/volumes.py | 53 +++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) (limited to 'nova/api') diff --git a/nova/api/openstack/contrib/volumes.py b/nova/api/openstack/contrib/volumes.py index e5e2c5b50..43e6a8cd9 100644 --- a/nova/api/openstack/contrib/volumes.py +++ b/nova/api/openstack/contrib/volumes.py @@ -21,10 +21,12 @@ from nova import compute from nova import exception from nova import flags from nova import log as logging +from nova import quota from nova import volume from nova.api.openstack import common from nova.api.openstack import extensions from nova.api.openstack import faults +from nova.api.openstack import servers LOG = logging.getLogger("nova.api.volumes") @@ -296,6 +298,53 @@ class VolumeAttachmentController(object): return {'volumeAttachments': res} +class BootFromVolumeController(servers.ControllerV11): + """The boot from volume API controller for the Openstack API.""" + + def _create_instance(self, context, instance_type, image_href, **kwargs): + try: + return self.compute_api.create(context, instance_type, + image_href, **kwargs) + except quota.QuotaError as error: + self.helper._handle_quota_error(error) + except exception.ImageNotFound as error: + msg = _("Can not find requested image") + raise faults.Fault(exc.HTTPBadRequest(explanation=msg)) + + def create(self, req, body): + """ Creates a new server for a given user """ + extra_values = None + try: + + def get_kwargs(context, instance_type, image_href, **kwargs): + kwargs['context'] = context + kwargs['instance_type'] = instance_type + kwargs['image_href'] = image_href + return kwargs + + extra_values, kwargs = self.helper.create_instance(req, body, + get_kwargs) + + block_device_mapping = body['server'].get('block_device_mapping') + kwargs['block_device_mapping'] = block_device_mapping + + instances = self._create_instance(**kwargs) + except faults.Fault, f: + return f + + # We can only return 1 instance via the API, if we happen to + # build more than one... instances is a list, so we'll just + # use the first one.. + inst = instances[0] + for key in ['instance_type', 'image_ref']: + inst[key] = extra_values[key] + + builder = self._get_view_builder(req) + server = builder.build(inst, is_detail=True) + server['server']['adminPass'] = extra_values['password'] + return server + + class Volumes(extensions.ExtensionDescriptor): def get_name(self): return "Volumes" @@ -329,4 +378,8 @@ class Volumes(extensions.ExtensionDescriptor): collection_name='servers')) resources.append(res) + res = extensions.ResourceExtension('os-volumes_boot', + BootFromVolumeController()) + resources.append(res) + return resources -- cgit From b2f7ae2ad7762531a9200799de4be9849cd3b42c Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Thu, 21 Jul 2011 09:43:25 -0400 Subject: Added LimitsXMLSerializer Added LimitsViewBuidlerV11Test test case --- nova/api/openstack/limits.py | 61 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 59 insertions(+), 2 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/limits.py b/nova/api/openstack/limits.py index bc76547d8..86afa3b62 100644 --- a/nova/api/openstack/limits.py +++ b/nova/api/openstack/limits.py @@ -25,6 +25,7 @@ import re import time import urllib import webob.exc +from xml.dom import minidom from collections import defaultdict @@ -76,6 +77,58 @@ class LimitsControllerV11(LimitsController): return limits_views.ViewBuilderV11() +class LimitsXMLSerializer(wsgi.XMLDictSerializer): + + xmlns = wsgi.XMLNS_V11 + + def __init__(self): + pass + + def _create_rates_node(self, xml_doc, rates): + rates_node = xml_doc.createElement('rates') + for rate in rates: + rate_node = xml_doc.createElement('rate') + rate_node.setAttribute('uri', rate['uri']) + rate_node.setAttribute('regex', rate['regex']) + + for limit in rate['limit']: + limit_node = xml_doc.createElement('limit') + limit_node.setAttribute('value', str(limit['value'])) + limit_node.setAttribute('verb', limit['verb']) + limit_node.setAttribute('remaining', str(limit['remaining'])) + limit_node.setAttribute('unit', limit['unit']) + limit_node.setAttribute('next-available', + str(limit['next-available'])) + rate_node.appendChild(limit_node) + + rates_node.appendChild(rate_node) + return rates_node + + def _create_absolute_node(self, xml_doc, absolutes): + absolute_node = xml_doc.createElement('absolute') + for key, value in absolutes.iteritems(): + limit_node = xml_doc.createElement('limit') + limit_node.setAttribute('name', key) + limit_node.setAttribute('value', str(value)) + absolute_node.appendChild(limit_node) + return absolute_node + + def _limits_to_xml(self, xml_doc, limits): + limits_node = xml_doc.createElement('limits') + rates_node = self._create_rates_node(xml_doc, limits['rate']) + limits_node.appendChild(rates_node) + + absolute_node = self._create_absolute_node(xml_doc, limits['absolute']) + limits_node.appendChild(absolute_node) + + return limits_node + + def index(self, limits_dict): + xml_doc = minidom.Document() + node = self._limits_to_xml(xml_doc, limits_dict['limits']) + return self.to_xml_string(node, False) + + def create_resource(version='1.0'): controller = { '1.0': LimitsControllerV10, @@ -97,9 +150,13 @@ def create_resource(version='1.0'): }, } + xml_serializer = { + '1.0': wsgi.XMLDictSerializer(xmlns=xmlns, metadata=metadata), + '1.1': LimitsXMLSerializer(), + }[version] + body_serializers = { - 'application/xml': wsgi.XMLDictSerializer(xmlns=xmlns, - metadata=metadata), + 'application/xml': xml_serializer, } serializer = wsgi.ResponseSerializer(body_serializers) -- cgit From 126714f297fc29c0a0c930e7e1f351bf114b3795 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Thu, 21 Jul 2011 10:02:16 -0400 Subject: Perform fault wrapping in the openstack WSGI controller. This allows us to just raise webob Exceptions in OS API controllers with the appropriate explanations set. This resolves some inconsistencies with exception raising and returning that could cause HTML output to occur when an exception was raised. --- nova/api/openstack/accounts.py | 6 +- nova/api/openstack/backup_schedules.py | 9 ++- nova/api/openstack/common.py | 19 +++--- nova/api/openstack/consoles.py | 7 +-- nova/api/openstack/create_instance_helper.py | 7 +-- nova/api/openstack/image_metadata.py | 5 +- nova/api/openstack/images.py | 3 +- nova/api/openstack/ips.py | 11 ++-- nova/api/openstack/server_metadata.py | 1 - nova/api/openstack/servers.py | 92 +++++++++++++--------------- nova/api/openstack/shared_ip_groups.py | 13 ++-- nova/api/openstack/users.py | 3 +- nova/api/openstack/wsgi.py | 6 +- nova/api/openstack/zones.py | 8 +-- 14 files changed, 89 insertions(+), 101 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/accounts.py b/nova/api/openstack/accounts.py index e3201b14f..a13a758ab 100644 --- a/nova/api/openstack/accounts.py +++ b/nova/api/openstack/accounts.py @@ -47,10 +47,10 @@ class Controller(object): raise exception.AdminRequired() def index(self, req): - raise faults.Fault(webob.exc.HTTPNotImplemented()) + raise webob.exc.HTTPNotImplemented() def detail(self, req): - raise faults.Fault(webob.exc.HTTPNotImplemented()) + raise webob.exc.HTTPNotImplemented() def show(self, req, id): """Return data about the given account id""" @@ -65,7 +65,7 @@ class Controller(object): 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()) + raise webob.exc.HTTPNotImplemented() def update(self, req, id, body): """This is really create or update.""" diff --git a/nova/api/openstack/backup_schedules.py b/nova/api/openstack/backup_schedules.py index 3e95aedf3..7ff0d999e 100644 --- a/nova/api/openstack/backup_schedules.py +++ b/nova/api/openstack/backup_schedules.py @@ -19,7 +19,6 @@ import time from webob import exc -from nova.api.openstack import faults from nova.api.openstack import wsgi @@ -36,20 +35,20 @@ class Controller(object): def index(self, req, server_id, **kwargs): """ Returns the list of backup schedules for a given instance """ - return faults.Fault(exc.HTTPNotImplemented()) + raise exc.HTTPNotImplemented() def show(self, req, server_id, id, **kwargs): """ Returns a single backup schedule for a given instance """ - return faults.Fault(exc.HTTPNotImplemented()) + raise exc.HTTPNotImplemented() def create(self, req, server_id, **kwargs): """ No actual update method required, since the existing API allows both create and update through a POST """ - return faults.Fault(exc.HTTPNotImplemented()) + raise exc.HTTPNotImplemented() def delete(self, req, server_id, id, **kwargs): """ Deletes an existing backup schedule """ - return faults.Fault(exc.HTTPNotImplemented()) + raise exc.HTTPNotImplemented() def create_resource(): diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py index 8e12ce0c0..f41d25537 100644 --- a/nova/api/openstack/common.py +++ b/nova/api/openstack/common.py @@ -53,10 +53,10 @@ def get_pagination_params(request): params[param] = int(request.GET[param]) except ValueError: msg = _('%s param must be an integer') % param - raise webob.exc.HTTPBadRequest(msg) + raise webob.exc.HTTPBadRequest(explanation=msg) if params[param] < 0: msg = _('%s param must be positive') % param - raise webob.exc.HTTPBadRequest(msg) + raise webob.exc.HTTPBadRequest(explanation=msg) return params @@ -77,18 +77,22 @@ def limited(items, request, max_limit=FLAGS.osapi_max_limit): try: offset = int(request.GET.get('offset', 0)) except ValueError: - raise webob.exc.HTTPBadRequest(_('offset param must be an integer')) + msg = _('offset param must be an integer') + raise webob.exc.HTTPBadRequest(explanation=msg) try: limit = int(request.GET.get('limit', max_limit)) except ValueError: - raise webob.exc.HTTPBadRequest(_('limit param must be an integer')) + msg = _('limit param must be an integer') + raise webob.exc.HTTPBadRequest(explanation=msg) if limit < 0: - raise webob.exc.HTTPBadRequest(_('limit param must be positive')) + msg = _('limit param must be positive') + raise webob.exc.HTTPBadRequest(explanation=msg) if offset < 0: - raise webob.exc.HTTPBadRequest(_('offset param must be positive')) + msg = _('offset param must be positive') + raise webob.exc.HTTPBadRequest(explanation=msg) limit = min(max_limit, limit or max_limit) range_end = offset + limit @@ -111,7 +115,8 @@ def limited_by_marker(items, request, max_limit=FLAGS.osapi_max_limit): start_index = i + 1 break if start_index < 0: - raise webob.exc.HTTPBadRequest(_('marker [%s] not found' % marker)) + msg = _('marker [%s] not found' % marker) + raise webob.exc.HTTPBadRequest(explanation=msg) range_end = start_index + limit return items[start_index:range_end] diff --git a/nova/api/openstack/consoles.py b/nova/api/openstack/consoles.py index 9c7b37f0d..d2655acfa 100644 --- a/nova/api/openstack/consoles.py +++ b/nova/api/openstack/consoles.py @@ -20,7 +20,6 @@ import webob from nova import console from nova import exception -from nova.api.openstack import faults from nova.api.openstack import wsgi @@ -72,12 +71,12 @@ class Controller(object): int(server_id), int(id)) except exception.NotFound: - return faults.Fault(exc.HTTPNotFound()) + raise exc.HTTPNotFound() return _translate_detail_keys(console) def update(self, req, server_id, id): """You can't update a console""" - raise faults.Fault(exc.HTTPNotImplemented()) + raise exc.HTTPNotImplemented() def delete(self, req, server_id, id): """Deletes a console""" @@ -86,7 +85,7 @@ class Controller(object): int(server_id), int(id)) except exception.NotFound: - return faults.Fault(exc.HTTPNotFound()) + raise exc.HTTPNotFound() return webob.Response(status_int=202) diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index 2654e3c40..7784a3e81 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -28,7 +28,6 @@ from nova import quota from nova import utils from nova.compute import instance_types -from nova.api.openstack import faults from nova.api.openstack import wsgi from nova.auth import manager as auth_manager @@ -70,7 +69,7 @@ class CreateInstanceHelper(object): return type from this method is left to the caller. """ if not body: - raise faults.Fault(exc.HTTPUnprocessableEntity()) + raise exc.HTTPUnprocessableEntity() context = req.environ['nova.context'] @@ -94,7 +93,7 @@ class CreateInstanceHelper(object): except Exception, e: msg = _("Cannot find requested image %(image_href)s: %(e)s" % locals()) - raise faults.Fault(exc.HTTPBadRequest(explanation=msg)) + raise exc.HTTPBadRequest(explanation=msg) personality = body['server'].get('personality') @@ -153,7 +152,7 @@ class CreateInstanceHelper(object): self._handle_quota_error(error) except exception.ImageNotFound as error: msg = _("Can not find requested image") - raise faults.Fault(exc.HTTPBadRequest(explanation=msg)) + raise exc.HTTPBadRequest(explanation=msg) # Let the caller deal with unhandled exceptions. diff --git a/nova/api/openstack/image_metadata.py b/nova/api/openstack/image_metadata.py index 4f33844fa..c0fc8c09b 100644 --- a/nova/api/openstack/image_metadata.py +++ b/nova/api/openstack/image_metadata.py @@ -22,7 +22,6 @@ from nova import flags from nova import image from nova import quota from nova import utils -from nova.api.openstack import faults from nova.api.openstack import wsgi @@ -62,7 +61,7 @@ class Controller(object): if id in metadata: return {'meta': {id: metadata[id]}} else: - return faults.Fault(exc.HTTPNotFound()) + raise exc.HTTPNotFound() def create(self, req, image_id, body): context = req.environ['nova.context'] @@ -105,7 +104,7 @@ class Controller(object): img = self.image_service.show(context, image_id) metadata = self._get_metadata(context, image_id) if not id in metadata: - return faults.Fault(exc.HTTPNotFound()) + raise exc.HTTPNotFound() metadata.pop(id) img['properties'] = metadata self.image_service.update(context, image_id, img, None) diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index d0317583e..96c5cd3fa 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -25,7 +25,6 @@ from nova import flags import nova.image from nova import log from nova.api.openstack import common -from nova.api.openstack import faults from nova.api.openstack import image_metadata from nova.api.openstack import servers from nova.api.openstack.views import images as images_view @@ -78,7 +77,7 @@ class Controller(object): image = self._image_service.show(context, id) except (exception.NotFound, exception.InvalidImageRef): explanation = _("Image not found.") - raise faults.Fault(webob.exc.HTTPNotFound(explanation=explanation)) + raise webob.exc.HTTPNotFound(explanation=explanation) return dict(image=self.get_builder(req).build(image, detail=True)) diff --git a/nova/api/openstack/ips.py b/nova/api/openstack/ips.py index 1ebfdb831..2996b032d 100644 --- a/nova/api/openstack/ips.py +++ b/nova/api/openstack/ips.py @@ -20,7 +20,6 @@ import time from webob import exc import nova -from nova.api.openstack import faults import nova.api.openstack.views.addresses from nova.api.openstack import wsgi from nova import db @@ -37,14 +36,14 @@ class Controller(object): instance = self.compute_api.get( req.environ['nova.context'], server_id) except nova.exception.NotFound: - return faults.Fault(exc.HTTPNotFound()) + raise exc.HTTPNotFound() return instance def create(self, req, server_id, body): - return faults.Fault(exc.HTTPNotImplemented()) + raise exc.HTTPNotImplemented() def delete(self, req, server_id, id): - return faults.Fault(exc.HTTPNotImplemented()) + raise exc.HTTPNotImplemented() class ControllerV10(Controller): @@ -63,7 +62,7 @@ class ControllerV10(Controller): view = builder.build_public_parts(instance) else: msg = _("Only private and public networks available") - return faults.Fault(exc.HTTPNotFound(explanation=msg)) + raise exc.HTTPNotFound(explanation=msg) return {id: view} @@ -86,7 +85,7 @@ class ControllerV11(Controller): if network is None: msg = _("Instance is not a member of specified network") - return faults.Fault(exc.HTTPNotFound(explanation=msg)) + raise exc.HTTPNotFound(explanation=msg) return network diff --git a/nova/api/openstack/server_metadata.py b/nova/api/openstack/server_metadata.py index 3b9169f81..d4f42bbf5 100644 --- a/nova/api/openstack/server_metadata.py +++ b/nova/api/openstack/server_metadata.py @@ -18,7 +18,6 @@ from webob import exc from nova import compute -from nova.api.openstack import faults from nova.api.openstack import wsgi from nova import exception from nova import quota diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 3b3d0685d..7bef1d9b2 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -27,7 +27,6 @@ from nova import log as logging from nova import utils from nova.api.openstack import common from nova.api.openstack import create_instance_helper as helper -from nova.api.openstack import faults import nova.api.openstack.views.addresses import nova.api.openstack.views.flavors import nova.api.openstack.views.images @@ -102,17 +101,14 @@ class Controller(object): req.environ['nova.context'], id) return self._build_view(req, instance, is_detail=True) except exception.NotFound: - return faults.Fault(exc.HTTPNotFound()) + raise exc.HTTPNotFound() def create(self, req, body): """ Creates a new server for a given user """ extra_values = None result = None - try: - extra_values, instances = self.helper.create_instance( - req, body, self.compute_api.create) - except faults.Fault, f: - return f + extra_values, instances = self.helper.create_instance( + req, body, self.compute_api.create) # We can only return 1 instance via the API, if we happen to # build more than one... instances is a list, so we'll just @@ -132,7 +128,7 @@ class Controller(object): raise exc.HTTPUnprocessableEntity() if not body: - return faults.Fault(exc.HTTPUnprocessableEntity()) + raise exc.HTTPUnprocessableEntity() ctxt = req.environ['nova.context'] update_dict = {} @@ -147,7 +143,7 @@ class Controller(object): try: self.compute_api.update(ctxt, id, **update_dict) except exception.NotFound: - return faults.Fault(exc.HTTPNotFound()) + raise exc.HTTPNotFound() return exc.HTTPNoContent() @@ -171,7 +167,7 @@ class Controller(object): for key in actions.keys(): if key in body: return actions[key](body, req, id) - return faults.Fault(exc.HTTPNotImplemented()) + raise exc.HTTPNotImplemented() def _action_change_password(self, input_dict, req, id): return exc.HTTPNotImplemented() @@ -181,7 +177,7 @@ class Controller(object): self.compute_api.confirm_resize(req.environ['nova.context'], id) except Exception, e: LOG.exception(_("Error in confirm-resize %s"), e) - return faults.Fault(exc.HTTPBadRequest()) + raise exc.HTTPBadRequest() return exc.HTTPNoContent() def _action_revert_resize(self, input_dict, req, id): @@ -189,7 +185,7 @@ class Controller(object): self.compute_api.revert_resize(req.environ['nova.context'], id) except Exception, e: LOG.exception(_("Error in revert-resize %s"), e) - return faults.Fault(exc.HTTPBadRequest()) + raise exc.HTTPBadRequest() return webob.Response(status_int=202) def _action_resize(self, input_dict, req, id): @@ -200,14 +196,14 @@ class Controller(object): reboot_type = input_dict['reboot']['type'] else: LOG.exception(_("Missing argument 'type' for reboot")) - return faults.Fault(exc.HTTPUnprocessableEntity()) + raise exc.HTTPUnprocessableEntity() try: # TODO(gundlach): pass reboot_type, support soft reboot in # virt driver self.compute_api.reboot(req.environ['nova.context'], id) except Exception, e: LOG.exception(_("Error in reboot %s"), e) - return faults.Fault(exc.HTTPUnprocessableEntity()) + raise exc.HTTPUnprocessableEntity() return webob.Response(status_int=202) def _action_migrate(self, input_dict, req, id): @@ -215,7 +211,7 @@ class Controller(object): self.compute_api.resize(req.environ['nova.context'], id) except Exception, e: LOG.exception(_("Error in migrate %s"), e) - return faults.Fault(exc.HTTPBadRequest()) + raise exc.HTTPBadRequest() return webob.Response(status_int=202) @scheduler_api.redirect_handler @@ -231,7 +227,7 @@ class Controller(object): except: readable = traceback.format_exc() LOG.exception(_("Compute.api::lock %s"), readable) - return faults.Fault(exc.HTTPUnprocessableEntity()) + raise exc.HTTPUnprocessableEntity() return webob.Response(status_int=202) @scheduler_api.redirect_handler @@ -247,7 +243,7 @@ class Controller(object): except: readable = traceback.format_exc() LOG.exception(_("Compute.api::unlock %s"), readable) - return faults.Fault(exc.HTTPUnprocessableEntity()) + raise exc.HTTPUnprocessableEntity() return webob.Response(status_int=202) @scheduler_api.redirect_handler @@ -262,7 +258,7 @@ class Controller(object): except: readable = traceback.format_exc() LOG.exception(_("Compute.api::get_lock %s"), readable) - return faults.Fault(exc.HTTPUnprocessableEntity()) + raise exc.HTTPUnprocessableEntity() return webob.Response(status_int=202) @scheduler_api.redirect_handler @@ -277,7 +273,7 @@ class Controller(object): except: readable = traceback.format_exc() LOG.exception(_("Compute.api::reset_network %s"), readable) - return faults.Fault(exc.HTTPUnprocessableEntity()) + raise exc.HTTPUnprocessableEntity() return webob.Response(status_int=202) @scheduler_api.redirect_handler @@ -292,7 +288,7 @@ class Controller(object): except: readable = traceback.format_exc() LOG.exception(_("Compute.api::inject_network_info %s"), readable) - return faults.Fault(exc.HTTPUnprocessableEntity()) + raise exc.HTTPUnprocessableEntity() return webob.Response(status_int=202) @scheduler_api.redirect_handler @@ -304,7 +300,7 @@ class Controller(object): except: readable = traceback.format_exc() LOG.exception(_("Compute.api::pause %s"), readable) - return faults.Fault(exc.HTTPUnprocessableEntity()) + raise exc.HTTPUnprocessableEntity() return webob.Response(status_int=202) @scheduler_api.redirect_handler @@ -316,7 +312,7 @@ class Controller(object): except: readable = traceback.format_exc() LOG.exception(_("Compute.api::unpause %s"), readable) - return faults.Fault(exc.HTTPUnprocessableEntity()) + raise exc.HTTPUnprocessableEntity() return webob.Response(status_int=202) @scheduler_api.redirect_handler @@ -328,7 +324,7 @@ class Controller(object): except: readable = traceback.format_exc() LOG.exception(_("compute.api::suspend %s"), readable) - return faults.Fault(exc.HTTPUnprocessableEntity()) + raise exc.HTTPUnprocessableEntity() return webob.Response(status_int=202) @scheduler_api.redirect_handler @@ -340,7 +336,7 @@ class Controller(object): except: readable = traceback.format_exc() LOG.exception(_("compute.api::resume %s"), readable) - return faults.Fault(exc.HTTPUnprocessableEntity()) + raise exc.HTTPUnprocessableEntity() return webob.Response(status_int=202) @scheduler_api.redirect_handler @@ -352,7 +348,7 @@ class Controller(object): except: readable = traceback.format_exc() LOG.exception(_("compute.api::rescue %s"), readable) - return faults.Fault(exc.HTTPUnprocessableEntity()) + raise exc.HTTPUnprocessableEntity() return webob.Response(status_int=202) @scheduler_api.redirect_handler @@ -364,7 +360,7 @@ class Controller(object): except: readable = traceback.format_exc() LOG.exception(_("compute.api::unrescue %s"), readable) - return faults.Fault(exc.HTTPUnprocessableEntity()) + raise exc.HTTPUnprocessableEntity() return webob.Response(status_int=202) @scheduler_api.redirect_handler @@ -374,7 +370,7 @@ class Controller(object): self.compute_api.get_ajax_console(req.environ['nova.context'], int(id)) except exception.NotFound: - return faults.Fault(exc.HTTPNotFound()) + raise exc.HTTPNotFound() return webob.Response(status_int=202) @scheduler_api.redirect_handler @@ -384,7 +380,7 @@ class Controller(object): self.compute_api.get_vnc_console(req.environ['nova.context'], int(id)) except exception.NotFound: - return faults.Fault(exc.HTTPNotFound()) + raise exc.HTTPNotFound() return webob.Response(status_int=202) @scheduler_api.redirect_handler @@ -416,7 +412,7 @@ class ControllerV10(Controller): try: self.compute_api.delete(req.environ['nova.context'], id) except exception.NotFound: - return faults.Fault(exc.HTTPNotFound()) + raise exc.HTTPNotFound() return webob.Response(status_int=202) def _image_ref_from_req_data(self, data): @@ -440,17 +436,13 @@ class ControllerV10(Controller): 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 'flavorId' argument for resize")) - return faults.Fault(exc.HTTPUnprocessableEntity()) - except Exception, e: - LOG.exception(_("Error in resize %s"), e) - return faults.Fault(exc.HTTPBadRequest()) + 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 'flavorId' argument for resize")) + raise exc.HTTPUnprocessableEntity() return webob.Response(status_int=202) def _action_rebuild(self, info, request, instance_id): @@ -462,14 +454,14 @@ class ControllerV10(Controller): except (KeyError, TypeError): msg = _("Could not parse imageId from request.") LOG.debug(msg) - return faults.Fault(exc.HTTPBadRequest(explanation=msg)) + raise exc.HTTPBadRequest(explanation=msg) try: self.compute_api.rebuild(context, instance_id, image_id) except exception.BuildInProgress: msg = _("Instance %d is currently being rebuilt.") % instance_id LOG.debug(msg) - return faults.Fault(exc.HTTPConflict(explanation=msg)) + raise exc.HTTPConflict(explanation=msg) return webob.Response(status_int=202) @@ -486,7 +478,7 @@ class ControllerV11(Controller): try: self.compute_api.delete(req.environ['nova.context'], id) except exception.NotFound: - return faults.Fault(exc.HTTPNotFound()) + raise exc.HTTPNotFound() def _image_ref_from_req_data(self, data): return data['server']['imageRef'] @@ -530,7 +522,7 @@ class ControllerV11(Controller): except AttributeError as ex: msg = _("Unable to parse metadata key/value pairs.") LOG.debug(msg) - raise faults.Fault(exc.HTTPBadRequest(explanation=msg)) + raise exc.HTTPBadRequest(explanation=msg) def _decode_personalities(self, personalities): """Decode the Base64-encoded personalities.""" @@ -541,14 +533,14 @@ class ControllerV11(Controller): except (KeyError, TypeError): msg = _("Unable to parse personality path/contents.") LOG.info(msg) - raise faults.Fault(exc.HTTPBadRequest(explanation=msg)) + raise exc.HTTPBadRequest(explanation=msg) try: personality["contents"] = base64.b64decode(contents) except TypeError: msg = _("Personality content could not be Base64 decoded.") LOG.info(msg) - raise faults.Fault(exc.HTTPBadRequest(explanation=msg)) + raise exc.HTTPBadRequest(explanation=msg) def _action_resize(self, input_dict, req, id): """ Resizes a given instance to the flavor size requested """ @@ -560,10 +552,10 @@ class ControllerV11(Controller): flavor_id) else: LOG.exception(_("Missing 'flavorRef' argument for resize")) - return faults.Fault(exc.HTTPUnprocessableEntity()) + raise exc.HTTPUnprocessableEntity() except Exception, e: LOG.exception(_("Error in resize %s"), e) - return faults.Fault(exc.HTTPBadRequest()) + raise exc.HTTPBadRequest() return webob.Response(status_int=202) def _action_rebuild(self, info, request, instance_id): @@ -575,7 +567,7 @@ class ControllerV11(Controller): except (KeyError, TypeError): msg = _("Could not parse imageRef from request.") LOG.debug(msg) - return faults.Fault(exc.HTTPBadRequest(explanation=msg)) + raise exc.HTTPBadRequest(explanation=msg) personalities = info["rebuild"].get("personality", []) metadata = info["rebuild"].get("metadata") @@ -591,7 +583,7 @@ class ControllerV11(Controller): except exception.BuildInProgress: msg = _("Instance %d is currently being rebuilt.") % instance_id LOG.debug(msg) - return faults.Fault(exc.HTTPConflict(explanation=msg)) + raise exc.HTTPConflict(explanation=msg) return webob.Response(status_int=202) diff --git a/nova/api/openstack/shared_ip_groups.py b/nova/api/openstack/shared_ip_groups.py index cf2ddbabb..54d0a8334 100644 --- a/nova/api/openstack/shared_ip_groups.py +++ b/nova/api/openstack/shared_ip_groups.py @@ -17,7 +17,6 @@ from webob import exc -from nova.api.openstack import faults from nova.api.openstack import wsgi @@ -26,27 +25,27 @@ class Controller(object): def index(self, req, **kwargs): """ Returns a list of Shared IP Groups for the user """ - raise faults.Fault(exc.HTTPNotImplemented()) + raise exc.HTTPNotImplemented() def show(self, req, id, **kwargs): """ Shows in-depth information on a specific Shared IP Group """ - raise faults.Fault(exc.HTTPNotImplemented()) + raise exc.HTTPNotImplemented() def update(self, req, id, **kwargs): """ You can't update a Shared IP Group """ - raise faults.Fault(exc.HTTPNotImplemented()) + raise exc.HTTPNotImplemented() def delete(self, req, id, **kwargs): """ Deletes a Shared IP Group """ - raise faults.Fault(exc.HTTPNotImplemented()) + raise exc.HTTPNotImplemented() def detail(self, req, **kwargs): """ Returns a complete list of Shared IP Groups """ - raise faults.Fault(exc.HTTPNotImplemented()) + raise exc.HTTPNotImplemented() def create(self, req, **kwargs): """ Creates a new Shared IP group """ - raise faults.Fault(exc.HTTPNotImplemented()) + raise exc.HTTPNotImplemented() def create_resource(): diff --git a/nova/api/openstack/users.py b/nova/api/openstack/users.py index 6ae1eaf2a..8dd72d559 100644 --- a/nova/api/openstack/users.py +++ b/nova/api/openstack/users.py @@ -19,7 +19,6 @@ from nova import exception 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 @@ -69,7 +68,7 @@ class Controller(object): user = None if user is None: - raise faults.Fault(exc.HTTPNotFound()) + raise exc.HTTPNotFound() return dict(user=_translate_keys(user)) diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index c3f841aa5..05767da19 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -441,7 +441,11 @@ class Resource(wsgi.Application): msg = _("Malformed request body") return faults.Fault(webob.exc.HTTPBadRequest(explanation=msg)) - action_result = self.dispatch(request, action, args) + try: + action_result = self.dispatch(request, action, args) + except webob.exc.HTTPException as ex: + LOG.info(_("HTTP exception thrown: %s"), unicode(ex)) + action_result = faults.Fault(ex) #TODO(bcwaldon): find a more elegant way to pass through non-dict types if type(action_result) is dict or action_result is None: diff --git a/nova/api/openstack/zones.py b/nova/api/openstack/zones.py index 2e02ec380..f7fd87bcd 100644 --- a/nova/api/openstack/zones.py +++ b/nova/api/openstack/zones.py @@ -27,7 +27,6 @@ from nova.scheduler import api from nova.api.openstack import create_instance_helper as helper from nova.api.openstack import common -from nova.api.openstack import faults from nova.api.openstack import wsgi @@ -127,11 +126,8 @@ class Controller(object): Returns a reservation ID (a UUID). """ result = None - try: - extra_values, result = self.helper.create_instance(req, body, - self.compute_api.create_all_at_once) - except faults.Fault, f: - return f + extra_values, result = self.helper.create_instance(req, body, + self.compute_api.create_all_at_once) reservation_id = result return {'reservation_id': reservation_id} -- cgit From 822b5ecede8f18120ae37eb07ff046101f50d3aa Mon Sep 17 00:00:00 2001 From: William Wolf Date: Thu, 21 Jul 2011 11:10:47 -0400 Subject: cleanup tests and fix pep8 issues --- nova/api/openstack/versions.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py index 49633df88..8e172ae02 100644 --- a/nova/api/openstack/versions.py +++ b/nova/api/openstack/versions.py @@ -43,8 +43,8 @@ class Versions(wsgi.Resource): } serializer = wsgi.ResponseSerializer(body_serializers) - supported_content_types = ('application/json', - 'application/xml', + supported_content_types = ('application/json', + 'application/xml', 'application/atom+xml') deserializer = wsgi.RequestDeserializer( supported_content_types=supported_content_types) @@ -73,6 +73,7 @@ class Versions(wsgi.Resource): versions = [builder.build(version) for version in version_objs] return dict(versions=versions) + class VersionsXMLSerializer(wsgi.XMLDictSerializer): def _versions_to_xml(self, versions): root = self._xml_doc.createElement('versions') @@ -96,13 +97,13 @@ class VersionsXMLSerializer(wsgi.XMLDictSerializer): return version_node - def default(self, data): self._xml_doc = minidom.Document() node = self._versions_to_xml(data['versions']) return self.to_xml_string(node) + class VersionsAtomSerializer(wsgi.XMLDictSerializer): def __init__(self, metadata=None, xmlns=None): if not xmlns: @@ -121,7 +122,8 @@ class VersionsAtomSerializer(wsgi.XMLDictSerializer): def _get_most_recent_update(self, versions): recent = None for version in versions: - updated = datetime.strptime(version['updated'],'%Y-%m-%dT%H:%M:%SZ') + updated = datetime.strptime(version['updated'], + '%Y-%m-%dT%H:%M:%SZ') if not recent: recent = updated elif updated > recent: @@ -132,7 +134,7 @@ class VersionsAtomSerializer(wsgi.XMLDictSerializer): def _get_base_url(self, link_href): # Make sure no trailing / link_href = link_href.rstrip('/') - return link_href.rsplit('/',1)[0] + '/' + return link_href.rsplit('/', 1)[0] + '/' def _create_meta(self, root, versions): title = self._create_text_elem('title', 'Available API Versions', @@ -164,7 +166,7 @@ class VersionsAtomSerializer(wsgi.XMLDictSerializer): entry = self._xml_doc.createElement('entry') id = self._create_text_elem('id', version['links'][0]['href']) - title = self._create_text_elem('title', + title = self._create_text_elem('title', 'Version %s' % version['id'], type='text') updated = self._create_text_elem('updated', version['updated']) @@ -179,9 +181,9 @@ class VersionsAtomSerializer(wsgi.XMLDictSerializer): link_node.setAttribute('href', link['href']) entry.appendChild(link_node) - content = self._create_text_elem('content', - 'Version %s %s (%s)' % - (version['id'], + content = self._create_text_elem('content', + 'Version %s %s (%s)' % + (version['id'], version['status'], version['updated']), type='text') -- cgit From 9a843b10f3145405ebc01ded5d32ce68d02fdd8d Mon Sep 17 00:00:00 2001 From: William Wolf Date: Thu, 21 Jul 2011 11:56:15 -0400 Subject: pep8 issue --- nova/api/openstack/create_instance_helper.py | 2 -- 1 file changed, 2 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index 2ef83332b..10aaa23b1 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -157,8 +157,6 @@ class CreateInstanceHelper(object): except exception.FlavorNotFound as error: msg = _("Invalid flavorRef provided.") raise faults.Fault(exc.HTTPBadRequest(explanation=msg)) - - # Let the caller deal with unhandled exceptions. def _handle_quota_error(self, error): -- cgit From dc616cd633007aa83d7576ae74cf807aa0df6776 Mon Sep 17 00:00:00 2001 From: William Wolf Date: Thu, 21 Jul 2011 12:02:01 -0400 Subject: fixed another issue with invalid flavor_id parsing, and added tests --- nova/api/openstack/create_instance_helper.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index 10aaa23b1..2b9256896 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -102,7 +102,11 @@ class CreateInstanceHelper(object): if personality: injected_files = self._get_injected_files(personality) - flavor_id = self.controller._flavor_id_from_req_data(body) + try: + flavor_id = self.controller._flavor_id_from_req_data(body) + except ValueError as error: + msg = _("Invalid flavorRef provided.") + raise faults.Fault(exc.HTTPBadRequest(explanation=msg)) if not 'name' in body['server']: msg = _("Server name is not defined") -- cgit From a57504ef9a8b3e2c5d78cfe4ecf2f21c03653556 Mon Sep 17 00:00:00 2001 From: William Wolf Date: Thu, 21 Jul 2011 12:19:09 -0400 Subject: added tests, updated pep8 fixes --- nova/api/openstack/versions.py | 1 - 1 file changed, 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py index 8e172ae02..df7a94b7e 100644 --- a/nova/api/openstack/versions.py +++ b/nova/api/openstack/versions.py @@ -28,7 +28,6 @@ ATOM_XMLNS = "http://www.w3.org/2005/Atom" class Versions(wsgi.Resource): - def __init__(self): metadata = { "attributes": { -- cgit From 5913e537ceb352ec4e5999cdadb1d826771d5d72 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Thu, 21 Jul 2011 14:21:27 -0400 Subject: Updated the compute API so that has_finished_migration uses instance_uuid. Fixes some regressions with 1295-1296. --- nova/api/openstack/views/servers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index ab7e8da61..7131db088 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -82,7 +82,7 @@ class ViewBuilder(object): ctxt = nova.context.get_admin_context() compute_api = nova.compute.API() - if compute_api.has_finished_migration(ctxt, inst['id']): + if compute_api.has_finished_migration(ctxt, inst['uuid']): inst_dict['status'] = 'RESIZE-CONFIRM' # Return the metadata as a dictionary -- cgit From 5133f92e5c00395b9a9d690e86815aa9c97cda9e Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Thu, 21 Jul 2011 14:51:55 -0400 Subject: pep8 and stuff. --- nova/api/openstack/common.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py index f41d25537..57031ebf1 100644 --- a/nova/api/openstack/common.py +++ b/nova/api/openstack/common.py @@ -77,7 +77,7 @@ def limited(items, request, max_limit=FLAGS.osapi_max_limit): try: offset = int(request.GET.get('offset', 0)) except ValueError: - msg = _('offset param must be an integer') + msg = _('offset param must be an integer') raise webob.exc.HTTPBadRequest(explanation=msg) try: @@ -115,7 +115,7 @@ def limited_by_marker(items, request, max_limit=FLAGS.osapi_max_limit): start_index = i + 1 break if start_index < 0: - msg = _('marker [%s] not found' % marker) + msg = _('marker [%s] not found') % marker raise webob.exc.HTTPBadRequest(explanation=msg) range_end = start_index + limit return items[start_index:range_end] -- cgit From af5c549d7795fcf9fdcb7d8e9193aa985f121fc6 Mon Sep 17 00:00:00 2001 From: Naveed Massjouni Date: Thu, 21 Jul 2011 15:08:46 -0400 Subject: Moving lp:~rackspace-titan/nova/extensions-xml-serialization to new branch based off of trunk. To remove dep on another branch. --- nova/api/openstack/extensions.py | 43 +++++++++++++++++++++++++++++++++++----- nova/api/openstack/wsgi.py | 4 ++++ 2 files changed, 42 insertions(+), 5 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py index da06ecd15..e500e51fa 100644 --- a/nova/api/openstack/extensions.py +++ b/nova/api/openstack/extensions.py @@ -23,6 +23,7 @@ import sys import routes import webob.dec import webob.exc +from xml.etree import ElementTree from nova import exception from nova import flags @@ -194,7 +195,7 @@ class ExtensionsResource(wsgi.Resource): def show(self, req, id): # NOTE(dprince): the extensions alias is used as the 'id' for show ext = self.extension_manager.extensions[id] - return self._translate(ext) + return dict(extension=self._translate(ext)) def delete(self, req, id): raise faults.Fault(webob.exc.HTTPNotFound()) @@ -258,15 +259,18 @@ class ExtensionMiddleware(base_wsgi.Middleware): mapper = routes.Mapper() + serializer = wsgi.ResponseSerializer( + {'application/xml': ExtensionsXMLSerializer()}) # extended resources for resource in ext_mgr.get_resources(): LOG.debug(_('Extended resource: %s'), resource.collection) mapper.resource(resource.collection, resource.collection, - controller=wsgi.Resource(resource.controller), - collection=resource.collection_actions, - member=resource.member_actions, - parent_resource=resource.parent) + controller=wsgi.Resource( + resource.controller, serializer=serializer), + collection=resource.collection_actions, + member=resource.member_actions, + parent_resource=resource.parent) # extended actions action_resources = self._action_ext_resources(application, ext_mgr, @@ -462,3 +466,32 @@ class ResourceExtension(object): self.parent = parent self.collection_actions = collection_actions self.member_actions = member_actions + + +class ExtensionsXMLSerializer(wsgi.XMLDictSerializer): + + def _add_extension_attributes(self, node, extension): + node.setAttribute('name', extension['name']) + node.setAttribute('namespace', extension['namespace']) + node.setAttribute('alias', extension['alias']) + node.setAttribute('updated', extension['updated']) + + def show(self, ext_dict): + root = ElementTree.Element('extension'); + extension = ext_dict['extension'] + root.set('xmlns', wsgi.XMLNS_V11) + root.set('xmlns:atom', wsgi.XMLNS_ATOM) + root.set('name', extension['name']) + root.set('namespace', extension['namespace']) + root.set('alias', extension['alias']) + root.set('updated', extension['updated']) + desc = ElementTree.Element('description'); + desc.text = extension['description'] + root.append(desc) + for link in extension.get('links', []): + elem = ElementTree.Element('atom:link'); + elem.set('rel', link['rel']) + elem.set('href', link['href']) + elem.set('type', link['type']) + root.append(elem) + return ElementTree.tostring(root) diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index 9df6fd058..d9993282f 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -13,6 +13,7 @@ from nova import wsgi XMLNS_V10 = 'http://docs.rackspacecloud.com/servers/api/v1.0' XMLNS_V11 = 'http://docs.openstack.org/compute/api/v1.1' +XMLNS_ATOM = 'http://www.w3.org/2005/Atom' LOG = logging.getLogger('nova.api.openstack.wsgi') @@ -352,6 +353,9 @@ class XMLDictSerializer(DictSerializer): link_node = xml_doc.createElement('atom:link') link_node.setAttribute('rel', link['rel']) link_node.setAttribute('href', link['href']) + if link.get('type'): + link_node.setAttribute('type', link['type']) + link_nodes.append(link_node) return link_nodes -- cgit From c1b4dd1b8e3a8043b494854bc9ddd5e6cd335ef0 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Thu, 21 Jul 2011 16:16:34 -0400 Subject: Updated time-available to be correct format Fixed old tests to respect this --- nova/api/openstack/views/limits.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/views/limits.py b/nova/api/openstack/views/limits.py index 934b4921a..e34d47e63 100644 --- a/nova/api/openstack/views/limits.py +++ b/nova/api/openstack/views/limits.py @@ -15,9 +15,11 @@ # License for the specific language governing permissions and limitations # under the License. +import datetime import time from nova.api.openstack import common +from nova import utils class ViewBuilder(object): @@ -113,10 +115,11 @@ class ViewBuilderV11(ViewBuilder): return limits def _build_rate_limit(self, rate_limit): + next_avail = datetime.datetime.fromtimestamp(rate_limit["resetTime"]) return { "verb": rate_limit["verb"], "value": rate_limit["value"], "remaining": int(rate_limit["remaining"]), "unit": rate_limit["unit"], - "next-available": rate_limit["resetTime"], + "next-available": utils.isotime(at=next_avail), } -- cgit From f6514ba9fbb6b3c1939bde6ba081f632b3d0a24f Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Thu, 21 Jul 2011 17:11:57 -0400 Subject: updated next-available to use utc time --- 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 e34d47e63..c7163e33c 100644 --- a/nova/api/openstack/views/limits.py +++ b/nova/api/openstack/views/limits.py @@ -115,7 +115,7 @@ class ViewBuilderV11(ViewBuilder): return limits def _build_rate_limit(self, rate_limit): - next_avail = datetime.datetime.fromtimestamp(rate_limit["resetTime"]) + next_avail = datetime.datetime.utcfromtimestamp(rate_limit["resetTime"]) return { "verb": rate_limit["verb"], "value": rate_limit["value"], -- cgit From fe708ae810efd17da70659076faa9c2f3b544d3b Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Thu, 21 Jul 2011 17:13:12 -0400 Subject: pep8 --- nova/api/openstack/views/limits.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/views/limits.py b/nova/api/openstack/views/limits.py index c7163e33c..f603d7cb4 100644 --- a/nova/api/openstack/views/limits.py +++ b/nova/api/openstack/views/limits.py @@ -115,7 +115,8 @@ class ViewBuilderV11(ViewBuilder): return limits def _build_rate_limit(self, rate_limit): - next_avail = datetime.datetime.utcfromtimestamp(rate_limit["resetTime"]) + next_avail = \ + datetime.datetime.utcfromtimestamp(rate_limit["resetTime"]) return { "verb": rate_limit["verb"], "value": rate_limit["value"], -- cgit From 5f75097eb46fa03814fe53c5d9fda84f0000fdd4 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Thu, 21 Jul 2011 22:46:57 +0000 Subject: start removing references to AuthManager --- nova/api/direct.py | 3 ++- nova/api/ec2/__init__.py | 10 ++++++---- nova/api/openstack/auth.py | 26 +++++++------------------- 3 files changed, 15 insertions(+), 24 deletions(-) (limited to 'nova/api') diff --git a/nova/api/direct.py b/nova/api/direct.py index ec79151b1..993815fc7 100644 --- a/nova/api/direct.py +++ b/nova/api/direct.py @@ -107,7 +107,8 @@ class DelegatedAuthMiddleware(wsgi.Middleware): def process_request(self, request): os_user = request.headers['X-OpenStack-User'] os_project = request.headers['X-OpenStack-Project'] - context_ref = context.RequestContext(user=os_user, project=os_project) + context_ref = context.RequestContext(user_id=os_user, + project_id=os_project) request.environ['openstack.context'] = context_ref diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py index cf1734281..8bb2ea944 100644 --- a/nova/api/ec2/__init__.py +++ b/nova/api/ec2/__init__.py @@ -174,8 +174,8 @@ class Authenticate(wsgi.Middleware): remote_address = req.remote_addr if FLAGS.use_forwarded_for: remote_address = req.headers.get('X-Forwarded-For', remote_address) - ctxt = context.RequestContext(user=user, - project=project, + ctxt = context.RequestContext(user_id=user.id, + project_id=project.id, remote_address=remote_address) req.environ['ec2.context'] = ctxt uname = user.name @@ -295,13 +295,15 @@ class Authorizer(wsgi.Middleware): def _matches_any_role(self, context, roles): """Return True if any role in roles is allowed in context.""" - if context.user.is_superuser(): + authman = manager.AuthManager() + user = authman.get_user(context.user_id) + if user.is_superuser(): return True if 'all' in roles: return True if 'none' in roles: return False - return any(context.project.has_role(context.user_id, role) + return any(authman.has_role(context.user_id, role, context.project_id) for role in roles) diff --git a/nova/api/openstack/auth.py b/nova/api/openstack/auth.py index 7c3e683d6..5b387c081 100644 --- a/nova/api/openstack/auth.py +++ b/nova/api/openstack/auth.py @@ -48,31 +48,19 @@ class AuthMiddleware(wsgi.Middleware): def __call__(self, req): if not self.has_authentication(req): return self.authenticate(req) - user = self.get_user_by_authentication(req) - if not user: + user_id = self.get_user_by_authentication(req) + if not user_id: token = req.headers["X-Auth-Token"] - msg = _("%(user)s could not be found with token '%(token)s'") + msg = _("%(user_id)s could not be found with token '%(token)s'") LOG.warn(msg % locals()) return faults.Fault(webob.exc.HTTPUnauthorized()) try: - account = req.headers["X-Auth-Project-Id"] + project_id = req.headers["X-Auth-Project-Id"] except KeyError: - # FIXME(usrleon): It needed only for compatibility - # while osapi clients don't use this header - accounts = self.auth.get_projects(user=user) - if accounts: - account = accounts[0] - else: - return faults.Fault(webob.exc.HTTPUnauthorized()) - - if not self.auth.is_admin(user) and \ - not self.auth.is_project_member(user, account): - msg = _("%(user)s must be an admin or a member of %(account)s") - LOG.warn(msg % locals()) - return faults.Fault(webob.exc.HTTPUnauthorized()) + project_id = user_id - req.environ['nova.context'] = context.RequestContext(user, account) + req.environ['nova.context'] = context.RequestContext(user_id, project_id) return self.application def has_authentication(self, req): @@ -133,7 +121,7 @@ class AuthMiddleware(wsgi.Middleware): if delta.days >= 2: self.db.auth_token_destroy(ctxt, token['token_hash']) else: - return self.auth.get_user(token['user_id']) + return token['user_id'] return None def _authorize_user(self, username, key, req): -- cgit From e1cf345fa82c3a9b8088237f1025c41db0f4e829 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Fri, 22 Jul 2011 00:39:53 +0000 Subject: fix a whole bunch of tests --- nova/api/ec2/__init__.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) (limited to 'nova/api') diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py index 8bb2ea944..edae94331 100644 --- a/nova/api/ec2/__init__.py +++ b/nova/api/ec2/__init__.py @@ -138,8 +138,19 @@ class Lockout(wsgi.Middleware): return res -class Authenticate(wsgi.Middleware): +class InjectContext(wsgi.Middleware): + """Always add a fake 'ec2.context' to WSGI environ.""" + def __init__(self, context, *args, **kwargs): + self.context = context + super(InjectContext, self).__init__(*args, **kwargs) + @webob.dec.wsgify(RequestClass=wsgi.Request) + def __call__(self, req): + req.environ['ec2.context'] = self.context + return self.application + + +class Authenticate(wsgi.Middleware): """Authenticate an EC2 request and add 'ec2.context' to WSGI environ.""" @webob.dec.wsgify(RequestClass=wsgi.Request) @@ -295,16 +306,13 @@ class Authorizer(wsgi.Middleware): def _matches_any_role(self, context, roles): """Return True if any role in roles is allowed in context.""" - authman = manager.AuthManager() - user = authman.get_user(context.user_id) - if user.is_superuser(): + if context.is_admin: return True if 'all' in roles: return True if 'none' in roles: return False - return any(authman.has_role(context.user_id, role, context.project_id) - for role in roles) + return any(role in context.roles for role in roles) class Executor(wsgi.Application): -- cgit From 16aa079b933a8788c926745e0794d85558d442a8 Mon Sep 17 00:00:00 2001 From: Naveed Massjouni Date: Thu, 21 Jul 2011 22:35:26 -0400 Subject: Added xml serialization for GET => /extensions. Added corresponding tests. --- nova/api/openstack/extensions.py | 43 +++++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 20 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py index e500e51fa..ad03a6ac9 100644 --- a/nova/api/openstack/extensions.py +++ b/nova/api/openstack/extensions.py @@ -470,28 +470,31 @@ class ResourceExtension(object): class ExtensionsXMLSerializer(wsgi.XMLDictSerializer): - def _add_extension_attributes(self, node, extension): - node.setAttribute('name', extension['name']) - node.setAttribute('namespace', extension['namespace']) - node.setAttribute('alias', extension['alias']) - node.setAttribute('updated', extension['updated']) - - def show(self, ext_dict): - root = ElementTree.Element('extension'); - extension = ext_dict['extension'] - root.set('xmlns', wsgi.XMLNS_V11) - root.set('xmlns:atom', wsgi.XMLNS_ATOM) - root.set('name', extension['name']) - root.set('namespace', extension['namespace']) - root.set('alias', extension['alias']) - root.set('updated', extension['updated']) + def _create_ext_elem(self, ext_dict): + ext_elem = ElementTree.Element('extension'); + ext_elem.set('xmlns', wsgi.XMLNS_V11) + ext_elem.set('xmlns:atom', wsgi.XMLNS_ATOM) + ext_elem.set('name', ext_dict['name']) + ext_elem.set('namespace', ext_dict['namespace']) + ext_elem.set('alias', ext_dict['alias']) + ext_elem.set('updated', ext_dict['updated']) desc = ElementTree.Element('description'); - desc.text = extension['description'] - root.append(desc) - for link in extension.get('links', []): + desc.text = ext_dict['description'] + ext_elem.append(desc) + for link in ext_dict.get('links', []): elem = ElementTree.Element('atom:link'); elem.set('rel', link['rel']) elem.set('href', link['href']) elem.set('type', link['type']) - root.append(elem) - return ElementTree.tostring(root) + ext_elem.append(elem) + return ext_elem + + def show(self, ext_dict): + ext = self._create_ext_elem(ext_dict['extension']) + return ElementTree.tostring(ext, encoding='UTF-8') + + def index(self, exts_dict): + exts = ElementTree.Element('extensions'); + for ext_dict in exts_dict['extensions']: + exts.append(self._create_ext_elem(ext_dict)) + return ElementTree.tostring(exts, encoding='UTF-8') -- cgit From c83546c0c9d077fb69caf1d782e8cb420e399a20 Mon Sep 17 00:00:00 2001 From: Naveed Massjouni Date: Thu, 21 Jul 2011 23:36:40 -0400 Subject: Correctly add xml namespaces to extensions xml. --- nova/api/openstack/extensions.py | 33 +++++++++++++++++++-------------- nova/api/openstack/wsgi.py | 1 - 2 files changed, 19 insertions(+), 15 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py index ad03a6ac9..cc889703e 100644 --- a/nova/api/openstack/extensions.py +++ b/nova/api/openstack/extensions.py @@ -470,31 +470,36 @@ class ResourceExtension(object): class ExtensionsXMLSerializer(wsgi.XMLDictSerializer): + def show(self, ext_dict): + ext = self._create_ext_elem(ext_dict['extension']) + return self._to_xml(ext) + + def index(self, exts_dict): + exts = ElementTree.Element('extensions') + for ext_dict in exts_dict['extensions']: + exts.append(self._create_ext_elem(ext_dict)) + return self._to_xml(exts) + def _create_ext_elem(self, ext_dict): - ext_elem = ElementTree.Element('extension'); - ext_elem.set('xmlns', wsgi.XMLNS_V11) - ext_elem.set('xmlns:atom', wsgi.XMLNS_ATOM) + """Create an extension xml element from a dict.""" + ext_elem = ElementTree.Element('extension') ext_elem.set('name', ext_dict['name']) ext_elem.set('namespace', ext_dict['namespace']) ext_elem.set('alias', ext_dict['alias']) ext_elem.set('updated', ext_dict['updated']) - desc = ElementTree.Element('description'); + desc = ElementTree.Element('description') desc.text = ext_dict['description'] ext_elem.append(desc) for link in ext_dict.get('links', []): - elem = ElementTree.Element('atom:link'); + elem = ElementTree.Element('atom:link') elem.set('rel', link['rel']) elem.set('href', link['href']) elem.set('type', link['type']) ext_elem.append(elem) return ext_elem - def show(self, ext_dict): - ext = self._create_ext_elem(ext_dict['extension']) - return ElementTree.tostring(ext, encoding='UTF-8') - - def index(self, exts_dict): - exts = ElementTree.Element('extensions'); - for ext_dict in exts_dict['extensions']: - exts.append(self._create_ext_elem(ext_dict)) - return ElementTree.tostring(exts, encoding='UTF-8') + def _to_xml(self, root): + """Convert the xml tree object to an xml string.""" + root.set('xmlns', wsgi.XMLNS_V11) + root.set('xmlns:atom', wsgi.XMLNS_ATOM) + return ElementTree.tostring(root, encoding='UTF-8') diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index d9993282f..ee1572c4f 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -355,7 +355,6 @@ class XMLDictSerializer(DictSerializer): link_node.setAttribute('href', link['href']) if link.get('type'): link_node.setAttribute('type', link['type']) - link_nodes.append(link_node) return link_nodes -- cgit From 44d1024a53b8150cf9542d08d5886f430365f161 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Fri, 22 Jul 2011 19:47:41 +0000 Subject: fix all tests --- nova/api/ec2/__init__.py | 29 +++++++++-------------------- nova/api/openstack/auth.py | 20 ++++++++++++++++++-- 2 files changed, 27 insertions(+), 22 deletions(-) (limited to 'nova/api') diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py index edae94331..0a743075c 100644 --- a/nova/api/ec2/__init__.py +++ b/nova/api/ec2/__init__.py @@ -66,7 +66,7 @@ class RequestLogging(wsgi.Middleware): else: controller = None action = None - ctxt = request.environ.get('ec2.context', None) + ctxt = request.environ.get('nova.context', None) delta = utils.utcnow() - start seconds = delta.seconds microseconds = delta.microseconds @@ -138,20 +138,8 @@ class Lockout(wsgi.Middleware): return res -class InjectContext(wsgi.Middleware): - """Always add a fake 'ec2.context' to WSGI environ.""" - def __init__(self, context, *args, **kwargs): - self.context = context - super(InjectContext, self).__init__(*args, **kwargs) - - @webob.dec.wsgify(RequestClass=wsgi.Request) - def __call__(self, req): - req.environ['ec2.context'] = self.context - return self.application - - class Authenticate(wsgi.Middleware): - """Authenticate an EC2 request and add 'ec2.context' to WSGI environ.""" + """Authenticate an EC2 request and add 'nova.context' to WSGI environ.""" @webob.dec.wsgify(RequestClass=wsgi.Request) def __call__(self, req): @@ -187,12 +175,13 @@ class Authenticate(wsgi.Middleware): remote_address = req.headers.get('X-Forwarded-For', remote_address) ctxt = context.RequestContext(user_id=user.id, project_id=project.id, + is_admin=user.is_admin(), remote_address=remote_address) - req.environ['ec2.context'] = ctxt + req.environ['nova.context'] = ctxt uname = user.name pname = project.name msg = _('Authenticated Request For %(uname)s:%(pname)s)') % locals() - LOG.audit(msg, context=req.environ['ec2.context']) + LOG.audit(msg, context=req.environ['nova.context']) return self.application @@ -239,7 +228,7 @@ class Authorizer(wsgi.Middleware): """Authorize an EC2 API request. Return a 401 if ec2.controller and ec2.action in WSGI environ may not be - executed in ec2.context. + executed in nova.context. """ def __init__(self, application): @@ -293,7 +282,7 @@ class Authorizer(wsgi.Middleware): @webob.dec.wsgify(RequestClass=wsgi.Request) def __call__(self, req): - context = req.environ['ec2.context'] + context = req.environ['nova.context'] controller = req.environ['ec2.request'].controller.__class__.__name__ action = req.environ['ec2.request'].action allowed_roles = self.action_roles[controller].get(action, ['none']) @@ -319,14 +308,14 @@ class Executor(wsgi.Application): """Execute an EC2 API request. - Executes 'ec2.action' upon 'ec2.controller', passing 'ec2.context' and + Executes 'ec2.action' upon 'ec2.controller', passing 'nova.context' and 'ec2.action_args' (all variables in WSGI environ.) Returns an XML response, or a 400 upon failure. """ @webob.dec.wsgify(RequestClass=wsgi.Request) def __call__(self, req): - context = req.environ['ec2.context'] + context = req.environ['nova.context'] api_request = req.environ['ec2.request'] result = None try: diff --git a/nova/api/openstack/auth.py b/nova/api/openstack/auth.py index 5b387c081..9caa14a4e 100644 --- a/nova/api/openstack/auth.py +++ b/nova/api/openstack/auth.py @@ -58,9 +58,25 @@ class AuthMiddleware(wsgi.Middleware): try: project_id = req.headers["X-Auth-Project-Id"] except KeyError: - project_id = user_id + # FIXME(usrleon): It needed only for compatibility + # while osapi clients don't use this header + projects = self.auth.get_projects(user_id) + if projects: + project_id = projects[0] + else: + return faults.Fault(webob.exc.HTTPUnauthorized()) + + is_admin = self.auth.is_admin(user_id) + req.environ['nova.context'] = context.RequestContext(user_id, + project_id, + is_admin) + if not is_admin and not self.auth.is_project_member(user_id, + project_id): + msg = _("%(user_id)s must be an admin or a " + "member of %(project_id)s") + LOG.warn(msg % locals()) + return faults.Fault(webob.exc.HTTPUnauthorized()) - req.environ['nova.context'] = context.RequestContext(user_id, project_id) return self.application def has_authentication(self, req): -- cgit From 0f8eee7ff32a91c866742939b1f551f3610f1276 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Fri, 22 Jul 2011 20:20:31 +0000 Subject: fix auth tests --- nova/api/openstack/auth.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/auth.py b/nova/api/openstack/auth.py index 9caa14a4e..d42abe1f8 100644 --- a/nova/api/openstack/auth.py +++ b/nova/api/openstack/auth.py @@ -62,7 +62,7 @@ class AuthMiddleware(wsgi.Middleware): # while osapi clients don't use this header projects = self.auth.get_projects(user_id) if projects: - project_id = projects[0] + project_id = projects[0].id else: return faults.Fault(webob.exc.HTTPUnauthorized()) -- cgit From e8defa6bdd5af85486d0d3acce8956670ca16882 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Fri, 22 Jul 2011 20:41:46 +0000 Subject: fix test_access --- nova/api/ec2/__init__.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py index 0a743075c..1ea26fdeb 100644 --- a/nova/api/ec2/__init__.py +++ b/nova/api/ec2/__init__.py @@ -156,8 +156,9 @@ class Authenticate(wsgi.Middleware): auth_params.pop('Signature') # Authenticate the request. + authman = manager.AuthManager() try: - (user, project) = manager.AuthManager().authenticate( + (user, project) = authman.authenticate( access, signature, auth_params, @@ -173,9 +174,12 @@ class Authenticate(wsgi.Middleware): remote_address = req.remote_addr if FLAGS.use_forwarded_for: remote_address = req.headers.get('X-Forwarded-For', remote_address) + roles = authman.get_active_roles(user, project) + LOG.warn(roles) ctxt = context.RequestContext(user_id=user.id, project_id=project.id, is_admin=user.is_admin(), + roles=roles, remote_address=remote_address) req.environ['nova.context'] = ctxt uname = user.name @@ -295,6 +299,7 @@ class Authorizer(wsgi.Middleware): def _matches_any_role(self, context, roles): """Return True if any role in roles is allowed in context.""" + LOG.info(context.roles) if context.is_admin: return True if 'all' in roles: -- cgit From faf71d498e9e98e60e65be94c7e306fc7b4e4f98 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Fri, 22 Jul 2011 16:54:25 -0400 Subject: updating images metadata resource --- nova/api/openstack/__init__.py | 11 ++++-- nova/api/openstack/create_instance_helper.py | 45 ++++------------------- nova/api/openstack/image_metadata.py | 53 ++++++++++++++++++++++++++-- nova/api/openstack/wsgi.py | 52 +++++++++++++++++++++------ 4 files changed, 108 insertions(+), 53 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index e87d7c754..9242c2c29 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -164,10 +164,17 @@ class APIRouterV11(APIRouter): def _setup_routes(self, mapper): super(APIRouterV11, self)._setup_routes(mapper, '1.1') - mapper.resource("image_meta", "meta", - controller=image_metadata.create_resource(), + image_metadata_controller = image_metadata.create_resource() + mapper.resource("image_meta", "metadata", + controller=image_metadata_controller, parent_resource=dict(member_name='image', collection_name='images')) + mapper.connect("image_meta", controller=image_metadata_controller, + action='update_collection', condition={"method":['PUT']}) + mapper.connect("metadata", "/images/{image_id}/metadata", + controller=image_metadata_controller, + action='update_all', + conditions={"method":['PUT',]}) mapper.resource("server_meta", "meta", controller=server_metadata.create_resource(), diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index 7249f1261..49e46f663 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -282,7 +282,7 @@ class CreateInstanceHelper(object): return password -class ServerXMLDeserializer(wsgi.XMLDeserializer): +class ServerXMLDeserializer(wsgi.MetadataXMLDeserializer): """ Deserializer to handle xml-formatted server create requests. @@ -299,11 +299,12 @@ class ServerXMLDeserializer(wsgi.XMLDeserializer): def _extract_server(self, node): """Marshal the server attribute of a parsed request""" server = {} - server_node = self._find_first_child_named(node, 'server') + server_node = self.find_first_child_named(node, 'server') for attr in ["name", "imageId", "flavorId", "imageRef", "flavorRef"]: if server_node.getAttribute(attr): server[attr] = server_node.getAttribute(attr) - metadata = self._extract_metadata(server_node) + metadata_node = self.find_first_child_named(server_node, "metadata") + metadata = self.extract_metadata(metadata_node) if metadata is not None: server["metadata"] = metadata personality = self._extract_personality(server_node) @@ -311,49 +312,17 @@ class ServerXMLDeserializer(wsgi.XMLDeserializer): server["personality"] = personality return server - def _extract_metadata(self, server_node): - """Marshal the metadata attribute of a parsed request""" - metadata_node = self._find_first_child_named(server_node, "metadata") - if metadata_node is None: - return None - metadata = {} - for meta_node in self._find_children_named(metadata_node, "meta"): - key = meta_node.getAttribute("key") - metadata[key] = self._extract_text(meta_node) - return metadata - def _extract_personality(self, server_node): """Marshal the personality attribute of a parsed request""" personality_node = \ - self._find_first_child_named(server_node, "personality") + self.find_first_child_named(server_node, "personality") if personality_node is None: return None personality = [] - for file_node in self._find_children_named(personality_node, "file"): + for file_node in self.find_children_named(personality_node, "file"): item = {} if file_node.hasAttribute("path"): item["path"] = file_node.getAttribute("path") - item["contents"] = self._extract_text(file_node) + item["contents"] = self.extract_text(file_node) personality.append(item) return personality - - def _find_first_child_named(self, parent, name): - """Search a nodes children for the first child with a given name""" - for node in parent.childNodes: - if node.nodeName == name: - return node - return None - - def _find_children_named(self, parent, name): - """Return all of a nodes children who have the given name""" - for node in parent.childNodes: - if node.nodeName == name: - yield node - - def _extract_text(self, node): - """Get the text field contained by the given node""" - if len(node.childNodes) == 1: - child = node.childNodes[0] - if child.nodeType == child.TEXT_NODE: - return child.nodeValue - return "" diff --git a/nova/api/openstack/image_metadata.py b/nova/api/openstack/image_metadata.py index c0fc8c09b..ee181c924 100644 --- a/nova/api/openstack/image_metadata.py +++ b/nova/api/openstack/image_metadata.py @@ -96,8 +96,16 @@ class Controller(object): self._check_quota_limit(context, metadata) img['properties'] = metadata self.image_service.update(context, image_id, img, None) + return dict(meta=meta) - return req.body + def update_all(self, req, image_id, body): + context = req.environ['nova.context'] + img = self.image_service.show(context, image_id) + metadata = body.get('metadata', {}) + self._check_quota_limit(context, metadata) + img['properties'] = metadata + self.image_service.update(context, image_id, img, None) + return dict(metadata=metadata) def delete(self, req, image_id, id): context = req.environ['nova.context'] @@ -110,6 +118,32 @@ class Controller(object): self.image_service.update(context, image_id, img, None) +class ImageMetadataXMLDeserializer(wsgi.MetadataXMLDeserializer): + + def _extract_metadata_container(self, datastring): + dom = minidom.parseString(datastring) + metadata_node = self.find_first_child_named(dom, "metadata") + metadata = self.extract_metadata(metadata_node) + return {'body': {'metadata': metadata}} + + def create(self, datastring): + return self._extract_metadata_container(datastring) + + def update_all(self, datastring): + return self._extract_metadata_container(datastring) + + def update(self, datastring): + dom = minidom.parseString(datastring) + metadata_item = self.extract_metadata(dom) + return {'body': {'meta': metadata_item}} + + +class HeadersSerializer(wsgi.ResponseHeadersSerializer): + + def delete(self, response, data): + response.status_int = 204 + + class ImageMetadataXMLSerializer(wsgi.XMLDictSerializer): def __init__(self, xmlns=wsgi.XMLNS_V11): super(ImageMetadataXMLSerializer, self).__init__(xmlns=xmlns) @@ -143,6 +177,9 @@ class ImageMetadataXMLSerializer(wsgi.XMLDictSerializer): def create(self, metadata_dict): return self._meta_list_to_xml_string(metadata_dict) + def update_all(self, metadata_dict): + return self._meta_list_to_xml_string(metadata_dict) + def _meta_item_to_xml_string(self, meta_item_dict): xml_doc = minidom.Document() item_key, item_value = meta_item_dict.items()[0] @@ -157,11 +194,21 @@ class ImageMetadataXMLSerializer(wsgi.XMLDictSerializer): def update(self, meta_item_dict): return self._meta_item_to_xml_string(meta_item_dict['meta']) + def default(self, *args, **kwargs): + return '' + def create_resource(): + headers_serializer = HeadersSerializer() + + body_deserializers = { + 'application/xml': ImageMetadataXMLDeserializer(), + } + body_serializers = { 'application/xml': ImageMetadataXMLSerializer(), } - serializer = wsgi.ResponseSerializer(body_serializers) + serializer = wsgi.ResponseSerializer(body_serializers, headers_serializer) + deserializer = wsgi.RequestDeserializer(body_deserializers) - return wsgi.Resource(Controller(), serializer=serializer) + return wsgi.Resource(Controller(), deserializer, serializer) diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index 1e5a5143d..0463020c8 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -135,10 +135,44 @@ class XMLDeserializer(TextDeserializer): listnames) return result + def find_first_child_named(self, parent, name): + """Search a nodes children for the first child with a given name""" + for node in parent.childNodes: + if node.nodeName == name: + return node + return None + + def find_children_named(self, parent, name): + """Return all of a nodes children who have the given name""" + for node in parent.childNodes: + if node.nodeName == name: + yield node + + def extract_text(self, node): + """Get the text field contained by the given node""" + if len(node.childNodes) == 1: + child = node.childNodes[0] + if child.nodeType == child.TEXT_NODE: + return child.nodeValue + return "" + def default(self, datastring): return {'body': self._from_xml(datastring)} +class MetadataXMLDeserializer(XMLDeserializer): + + def extract_metadata(self, metadata_node): + """Marshal the metadata attribute of a parsed request""" + if metadata_node is None: + return None + metadata = {} + for meta_node in self.find_children_named(metadata_node, "meta"): + key = meta_node.getAttribute("key") + metadata[key] = self.extract_text(meta_node) + return metadata + + class RequestHeadersDeserializer(ActionDispatcher): """Default request headers deserializer""" @@ -395,9 +429,10 @@ class ResponseSerializer(object): self.headers_serializer.serialize(response, data, action) def serialize_body(self, response, data, content_type, action): - response.headers['Content-Type'] = content_type - serializer = self.get_body_serializer(content_type) - response.body = serializer.serialize(data, action) + if data is not None: + response.headers['Content-Type'] = content_type + serializer = self.get_body_serializer(content_type) + response.body = serializer.serialize(data, action) def get_body_serializer(self, content_type): try: @@ -443,7 +478,7 @@ class Resource(wsgi.Application): action, args, accept = self.deserializer.deserialize(request) except exception.InvalidContentType: msg = _("Unsupported Content-Type") - return webob.exc.HTTPBadRequest(explanation=msg) + return faults.Fault(webob.exc.HTTPBadRequest(explanation=msg)) except exception.MalformedRequestBody: msg = _("Malformed request body") return faults.Fault(webob.exc.HTTPBadRequest(explanation=msg)) @@ -452,13 +487,10 @@ class Resource(wsgi.Application): action_result = self.dispatch(request, action, args) except webob.exc.HTTPException as ex: LOG.info(_("HTTP exception thrown: %s"), unicode(ex)) - action_result = faults.Fault(ex) + return faults.Fault(ex) - #TODO(bcwaldon): find a more elegant way to pass through non-dict types - if type(action_result) is dict or action_result is None: - response = self.serializer.serialize(action_result, - accept, - action=action) + if isinstance(action_result, dict) or action_result is None: + response = self.serializer.serialize(action_result, accept, action) else: response = action_result -- cgit From bc800b16cf304811802d1e441823cffff610fc6f Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Fri, 22 Jul 2011 17:04:26 -0400 Subject: pep8 --- nova/api/openstack/__init__.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index 9242c2c29..868b98a31 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -169,12 +169,11 @@ class APIRouterV11(APIRouter): controller=image_metadata_controller, parent_resource=dict(member_name='image', collection_name='images')) - mapper.connect("image_meta", controller=image_metadata_controller, - action='update_collection', condition={"method":['PUT']}) + mapper.connect("metadata", "/images/{image_id}/metadata", controller=image_metadata_controller, action='update_all', - conditions={"method":['PUT',]}) + conditions={"method": ['PUT']}) mapper.resource("server_meta", "meta", controller=server_metadata.create_resource(), -- cgit From ccb5119280d341a2ea1b3e8352acbf32b7f243af Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Fri, 22 Jul 2011 21:36:41 +0000 Subject: clean up fake auth manager in other places --- nova/api/ec2/__init__.py | 2 -- 1 file changed, 2 deletions(-) (limited to 'nova/api') diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py index 1ea26fdeb..af232edda 100644 --- a/nova/api/ec2/__init__.py +++ b/nova/api/ec2/__init__.py @@ -175,7 +175,6 @@ class Authenticate(wsgi.Middleware): if FLAGS.use_forwarded_for: remote_address = req.headers.get('X-Forwarded-For', remote_address) roles = authman.get_active_roles(user, project) - LOG.warn(roles) ctxt = context.RequestContext(user_id=user.id, project_id=project.id, is_admin=user.is_admin(), @@ -299,7 +298,6 @@ class Authorizer(wsgi.Middleware): def _matches_any_role(self, context, roles): """Return True if any role in roles is allowed in context.""" - LOG.info(context.roles) if context.is_admin: return True if 'all' in roles: -- cgit From 164afd51017721b9cbaf2880b9dada3d4cd9b42c Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Fri, 22 Jul 2011 22:04:52 +0000 Subject: remove auth manager from instance helper --- nova/api/openstack/create_instance_helper.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index 7249f1261..2034e8ada 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -20,6 +20,7 @@ import webob from webob import exc from xml.dom import minidom +from nova import db from nova import exception from nova import flags from nova import log as logging @@ -29,7 +30,6 @@ from nova import utils from nova.compute import instance_types from nova.api.openstack import wsgi -from nova.auth import manager as auth_manager LOG = logging.getLogger('nova.api.openstack.create_instance_helper') @@ -77,7 +77,8 @@ class CreateInstanceHelper(object): key_name = None key_data = None - key_pairs = auth_manager.AuthManager.get_key_pairs(context) + key_pairs = db.key_pair_get_all_by_user(context.elevated(), + context.user_id) if key_pairs: key_pair = key_pairs[0] key_name = key_pair['name'] -- cgit From 8501cc95aa60a0a5759cf911e8adaf624fa9e547 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Mon, 25 Jul 2011 12:02:26 -0400 Subject: removing unnecessary assignments --- nova/api/openstack/servers.py | 2 -- 1 file changed, 2 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 1e09b6ea0..131937422 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -454,7 +454,6 @@ class ControllerV10(Controller): def _action_rebuild(self, info, request, instance_id): context = request.environ['nova.context'] - instance_id = instance_id try: image_id = info["rebuild"]["imageId"] @@ -569,7 +568,6 @@ class ControllerV11(Controller): def _action_rebuild(self, info, request, instance_id): context = request.environ['nova.context'] - instance_id = instance_id try: image_href = info["rebuild"]["imageRef"] -- cgit From a158166a1148a1ea35a04fb25b10361d86f36138 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Mon, 25 Jul 2011 12:08:49 -0400 Subject: Updated Faults controller to choose an xml serializer based on api version found in the request url --- nova/api/openstack/common.py | 28 ++++++++++++++++++++++++++++ nova/api/openstack/faults.py | 17 +++++++++++++---- 2 files changed, 41 insertions(+), 4 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py index 57031ebf1..97c9de167 100644 --- a/nova/api/openstack/common.py +++ b/nova/api/openstack/common.py @@ -167,3 +167,31 @@ def remove_version_from_href(href): msg = _('href does not contain version') raise ValueError(msg) return new_href + + +def get_version_from_href(href): + """Returns the api version in the href. + + Returns the api version in the href. + If no version is found, 1.0 is returned + + Given: 'http://www.nova.com/123' + Returns: '1.0' + + Given: 'http://www.nova.com/v1.1' + Returns: '1.1' + + """ + try: + #finds the first instance that matches /v#.#/ + version = re.findall(r'[/][v][0-9]+\.[0-9]+[/]', href) + #if no version was found, try finding /v#.# at the end of the string + if not version: + version = re.findall(r'[/][v][0-9]+\.[0-9]+$', href) + print href + print " " + print version + version = re.findall(r'[0-9]+\.[0-9]', version[0])[0] + except IndexError: + version = '1.0' + return version diff --git a/nova/api/openstack/faults.py b/nova/api/openstack/faults.py index 24cde69e4..839b33dfe 100644 --- a/nova/api/openstack/faults.py +++ b/nova/api/openstack/faults.py @@ -19,6 +19,7 @@ import webob.dec import webob.exc +from nova.api.openstack import common from nova.api.openstack import wsgi @@ -61,9 +62,13 @@ class Fault(webob.exc.HTTPException): content_type = req.best_match_content_type() + xml_serializer = { + '1.0': wsgi.XMLDictSerializer(metadata, wsgi.XMLNS_V10), + '1.1': wsgi.XMLDictSerializer(metadata, wsgi.XMLNS_V11), + }[common.get_version_from_href(req.url)] + serializer = { - 'application/xml': wsgi.XMLDictSerializer(metadata=metadata, - xmlns=wsgi.XMLNS_V10), + 'application/xml': xml_serializer, 'application/json': wsgi.JSONDictSerializer(), }[content_type] @@ -100,9 +105,13 @@ class OverLimitFault(webob.exc.HTTPException): content_type = request.best_match_content_type() metadata = {"attributes": {"overLimitFault": "code"}} + xml_serializer = { + '1.0': wsgi.XMLDictSerializer(metadata, wsgi.XMLNS_V10), + '1.1': wsgi.XMLDictSerializer(metadata, wsgi.XMLNS_V11), + }[common.get_version_from_href(req.url)] + serializer = { - 'application/xml': wsgi.XMLDictSerializer(metadata=metadata, - xmlns=wsgi.XMLNS_V10), + 'application/xml': xml_serializer, 'application/json': wsgi.JSONDictSerializer(), }[content_type] -- cgit From e24988c3b154d188ac69030d0b1d2811fb91d2e7 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Mon, 25 Jul 2011 13:28:22 -0400 Subject: Fixed bad test Fixed using wrong variable --- nova/api/openstack/faults.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/faults.py b/nova/api/openstack/faults.py index 839b33dfe..1ab45d4f1 100644 --- a/nova/api/openstack/faults.py +++ b/nova/api/openstack/faults.py @@ -108,7 +108,7 @@ class OverLimitFault(webob.exc.HTTPException): xml_serializer = { '1.0': wsgi.XMLDictSerializer(metadata, wsgi.XMLNS_V10), '1.1': wsgi.XMLDictSerializer(metadata, wsgi.XMLNS_V11), - }[common.get_version_from_href(req.url)] + }[common.get_version_from_href(request.url)] serializer = { 'application/xml': xml_serializer, -- cgit From 8de3c0fcaee546fae3d415ef5ddcbb51fb1db6d7 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Mon, 25 Jul 2011 17:49:09 +0000 Subject: fix for reviews --- nova/api/openstack/create_instance_helper.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'nova/api') diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index 2034e8ada..573153f68 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -77,6 +77,8 @@ class CreateInstanceHelper(object): key_name = None key_data = None + # TODO(vish): Key pair access should move into a common library + # instead of being accessed directly from the db. key_pairs = db.key_pair_get_all_by_user(context.elevated(), context.user_id) if key_pairs: -- cgit From bdd70a8c5e0aea3c1722817809c34cc78fee3ab9 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Mon, 25 Jul 2011 14:24:59 -0400 Subject: removed print lines --- nova/api/openstack/common.py | 3 --- 1 file changed, 3 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py index 97c9de167..bd14a1389 100644 --- a/nova/api/openstack/common.py +++ b/nova/api/openstack/common.py @@ -188,9 +188,6 @@ def get_version_from_href(href): #if no version was found, try finding /v#.# at the end of the string if not version: version = re.findall(r'[/][v][0-9]+\.[0-9]+$', href) - print href - print " " - print version version = re.findall(r'[0-9]+\.[0-9]', version[0])[0] except IndexError: version = '1.0' -- cgit From 28cc235edebf1986102cb51bebaacfa0c0bad984 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Mon, 25 Jul 2011 15:08:26 -0400 Subject: reverting some wsgi-related changes --- nova/api/openstack/wsgi.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index 0463020c8..7842b821a 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -487,10 +487,12 @@ class Resource(wsgi.Application): action_result = self.dispatch(request, action, args) except webob.exc.HTTPException as ex: LOG.info(_("HTTP exception thrown: %s"), unicode(ex)) - return faults.Fault(ex) + action_result = faults.Fault(ex) - if isinstance(action_result, dict) or action_result is None: - response = self.serializer.serialize(action_result, accept, action) + if type(action_result) is dict or action_result is None: + response = self.serializer.serialize(action_result, + accept, + action=action) else: response = action_result -- cgit From 2e142a02014940ebb1e775c26b60c576ad1e2bb3 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Mon, 25 Jul 2011 15:36:28 -0400 Subject: Added check to make sure there is a server entity in the create server request --- nova/api/openstack/create_instance_helper.py | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index 7249f1261..55c07ed81 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -71,9 +71,12 @@ class CreateInstanceHelper(object): if not body: raise exc.HTTPUnprocessableEntity() - context = req.environ['nova.context'] + if not 'server' in body: + raise exc.HTTPUnprocessableEntity() - password = self.controller._get_server_admin_password(body['server']) + server_dict = body['server'] + context = req.environ['nova.context'] + password = self.controller._get_server_admin_password(server_dict) key_name = None key_data = None @@ -95,7 +98,7 @@ class CreateInstanceHelper(object): locals()) raise exc.HTTPBadRequest(explanation=msg) - personality = body['server'].get('personality') + personality = server_dict.get('personality') injected_files = [] if personality: @@ -107,18 +110,18 @@ class CreateInstanceHelper(object): msg = _("Invalid flavorRef provided.") raise exc.HTTPBadRequest(explanation=msg) - if not 'name' in body['server']: + if not 'name' in server_dict: msg = _("Server name is not defined") raise exc.HTTPBadRequest(explanation=msg) - zone_blob = body['server'].get('blob') - name = body['server']['name'] + zone_blob = server_dict.get('blob') + name = server_dict['name'] self._validate_server_name(name) name = name.strip() - reservation_id = body['server'].get('reservation_id') - min_count = body['server'].get('min_count') - max_count = body['server'].get('max_count') + reservation_id = server_dict.get('reservation_id') + min_count = server_dict.get('min_count') + max_count = server_dict.get('max_count') # min_count and max_count are optional. If they exist, they come # in as strings. We want to default 'min_count' to 1, and default # 'max_count' to be 'min_count'. @@ -145,7 +148,7 @@ class CreateInstanceHelper(object): display_description=name, key_name=key_name, key_data=key_data, - metadata=body['server'].get('metadata', {}), + metadata=server_dict.get('metadata', {}), injected_files=injected_files, admin_password=password, zone_blob=zone_blob, -- cgit From 175e42fe2310a3aff69527d5623d92f3d0b19258 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Mon, 25 Jul 2011 15:56:23 -0400 Subject: adding testing to solidify handling of None in wsgi serialization --- nova/api/openstack/wsgi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index 7842b821a..a908bdb8a 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -429,8 +429,8 @@ class ResponseSerializer(object): self.headers_serializer.serialize(response, data, action) def serialize_body(self, response, data, content_type, action): + response.headers['Content-Type'] = content_type if data is not None: - response.headers['Content-Type'] = content_type serializer = self.get_body_serializer(content_type) response.body = serializer.serialize(data, action) -- cgit From b32b5571807c36f30d5541d0e284fd0e66023626 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Mon, 25 Jul 2011 17:00:19 -0400 Subject: adding xml serialization for /servers//ips and /servers//ips/ --- nova/api/openstack/ips.py | 56 ++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 46 insertions(+), 10 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/ips.py b/nova/api/openstack/ips.py index 2996b032d..a74fae487 100644 --- a/nova/api/openstack/ips.py +++ b/nova/api/openstack/ips.py @@ -16,6 +16,7 @@ # under the License. import time +from xml.dom import minidom from webob import exc @@ -100,17 +101,51 @@ class ControllerV11(Controller): return nova.api.openstack.views.addresses.ViewBuilderV11() +class IPXMLSerializer(wsgi.XMLDictSerializer): + def __init__(self, xmlns=wsgi.XMLNS_V11): + super(IPXMLSerializer, self).__init__(xmlns=xmlns) + + def _ip_to_xml(self, xml_doc, ip_dict): + ip_node = xml_doc.createElement('ip') + ip_node.setAttribute('addr', ip_dict['addr']) + ip_node.setAttribute('version', str(ip_dict['version'])) + return ip_node + + def _network_to_xml(self, xml_doc, network_id, ip_dicts): + network_node = xml_doc.createElement('network') + network_node.setAttribute('id', network_id) + + for ip_dict in ip_dicts: + ip_node = self._ip_to_xml(xml_doc, ip_dict) + network_node.appendChild(ip_node) + + return network_node + + def networks_to_xml(self, xml_doc, networks_container): + addresses_node = xml_doc.createElement('addresses') + for (network_id, ip_dicts) in networks_container.items(): + network_node = self._network_to_xml(xml_doc, network_id, ip_dicts) + addresses_node.appendChild(network_node) + return addresses_node + + def show(self, network_container): + (network_id, ip_dicts) = network_container.items()[0] + xml_doc = minidom.Document() + node = self._network_to_xml(xml_doc, network_id, ip_dicts) + return self.to_xml_string(node, False) + + def index(self, addresses_container): + xml_doc = minidom.Document() + node = self.networks_to_xml(xml_doc, addresses_container['addresses']) + return self.to_xml_string(node, False) + + def create_resource(version): controller = { '1.0': ControllerV10, '1.1': ControllerV11, }[version]() - xmlns = { - '1.0': wsgi.XMLNS_V10, - '1.1': wsgi.XMLNS_V11, - }[version] - metadata = { 'list_collections': { 'public': {'item_name': 'ip', 'item_key': 'addr'}, @@ -118,10 +153,11 @@ def create_resource(version): }, } - body_serializers = { - 'application/xml': wsgi.XMLDictSerializer(metadata=metadata, - xmlns=xmlns), - } - serializer = wsgi.ResponseSerializer(body_serializers) + xml_serializer = { + '1.0': wsgi.XMLDictSerializer(metadata=metadata, xmlns=wsgi.XMLNS_V11), + '1.1': IPXMLSerializer(), + }[version] + + serializer = wsgi.ResponseSerializer({'application/xml': xml_serializer}) return wsgi.Resource(controller, serializer=serializer) -- cgit From 422d5329276f5c2252d7328d4112be7c696a274a Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Tue, 26 Jul 2011 09:57:39 -0400 Subject: Updated ServerXMLSerializer to utilize the IPXMLSerializer --- nova/api/openstack/servers.py | 20 +++----------------- 1 file changed, 3 insertions(+), 17 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 4e6f0d7b5..11dafc272 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -28,6 +28,7 @@ from nova import log as logging from nova import utils from nova.api.openstack import common from nova.api.openstack import create_instance_helper as helper +from nova.api.openstack import ips import nova.api.openstack.views.addresses import nova.api.openstack.views.flavors import nova.api.openstack.views.images @@ -608,6 +609,7 @@ class ServerXMLSerializer(wsgi.XMLDictSerializer): def __init__(self): self.metadata_serializer = common.MetadataXMLSerializer() + self.addresses_serializer = ips.IPXMLSerializer() def _create_basic_entity_node(self, xml_doc, id, links, name): basic_node = xml_doc.createElement(name) @@ -621,23 +623,7 @@ class ServerXMLSerializer(wsgi.XMLDictSerializer): return self.metadata_serializer.meta_list_to_xml(xml_doc, metadata) def _create_addresses_node(self, xml_doc, addresses): - addresses_node = xml_doc.createElement('addresses') - for name, network_dict in addresses.items(): - network_node = self._create_network_node(xml_doc, - name, - network_dict) - addresses_node.appendChild(network_node) - return addresses_node - - def _create_network_node(self, xml_doc, network_name, network_dict): - network_node = xml_doc.createElement('network') - network_node.setAttribute('id', network_name) - for ip in network_dict: - ip_node = xml_doc.createElement('ip') - ip_node.setAttribute('version', str(ip['version'])) - ip_node.setAttribute('addr', ip['addr']) - network_node.appendChild(ip_node) - return network_node + return self.addresses_serializer.networks_to_xml(xml_doc, addresses) def _add_server_attributes(self, node, server): node.setAttribute('id', str(server['id'])) -- cgit From 1ad5f2eaf49904d8e14546d59699b1472a1a5bb2 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Tue, 26 Jul 2011 12:57:06 -0400 Subject: xml deserialization works now --- nova/api/openstack/create_instance_helper.py | 79 ++++++---------------------- nova/api/openstack/wsgi.py | 9 ++-- 2 files changed, 19 insertions(+), 69 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index 03272443a..b717b8ac0 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -303,79 +303,30 @@ class ServerXMLDeserializer(wsgi.MetadataXMLDeserializer): """Marshal the server attribute of a parsed request""" server = {} server_node = self.find_first_child_named(node, 'server') - for attr in ["name", "imageId", "flavorId"]: + + attributes = ["name", "imageId", "flavorId", "imageRef", + "flavorRef", "adminPass"] + for attr in attributes: if server_node.getAttribute(attr): server[attr] = server_node.getAttribute(attr) - image = self._extract_image(server_node) - if image is not None: - server["image"] = image - flavor = self._extract_flavor(server_node) - if flavor is not None: - server["flavor"] = flavor - metadata_node = self.find_first_child_named(server_node, "metadata") - metadata = self.extract_metadata(metadata_node) - if metadata is not None: - server["metadata"] = metadata - personality = self._extract_personality(server_node) - if personality is not None: - server["personality"] = personality - return server - - def _extract_image(self, server_node): - """Retrieve an image entity from the server node""" - image_node = self.find_first_child_named(server_node, "image") - if image_node is None: - return None - - image = {} - image_id = image_node.getAttribute('id') - if image_id is not None: - image['id'] = image_id - - image['links'] = self._extract_links_from_node(image_node) - - return image - def _extract_flavor(self, server_node): - """Retrieve a flavor entity from the server node""" - flavor_node = self.find_first_child_named(server_node, "flavor") - if flavor_node is None: - return None - - flavor = {} - flavor_id = flavor_node.getAttribute('id') - if flavor_id: - flavor['id'] = flavor_id - - flavor['links'] = self._extract_links_from_node(flavor_node) - - return flavor - - def _extract_links_from_node(self, parent_node): - """Retrieve link entities from a links container provided node""" - links = [] + metadata_node = self.find_first_child_named(server_node, "metadata") + server["metadata"] = self.extract_metadata(metadata_node) - for link_node in self.find_children_named(parent_node, 'atom:link'): - link = { - 'rel': link_node.getAttribute('rel'), - 'href': link_node.getAttribute('href'), - } - if link['rel'] is not None and link['href'] is not None: - links.append(link) + server["personality"] = self._extract_personality(server_node) - return links + return server def _extract_personality(self, server_node): """Marshal the personality attribute of a parsed request""" personality_node = \ self.find_first_child_named(server_node, "personality") - if personality_node is None: - return None personality = [] - for file_node in self.find_children_named(personality_node, "file"): - item = {} - if file_node.hasAttribute("path"): - item["path"] = file_node.getAttribute("path") - item["contents"] = self.extract_text(file_node) - personality.append(item) + if personality_node is not None: + for file_node in self.find_children_named(personality_node, "file"): + item = {} + if file_node.hasAttribute("path"): + item["path"] = file_node.getAttribute("path") + item["contents"] = self.extract_text(file_node) + personality.append(item) return personality diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index a28443d12..53dab22e8 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -165,12 +165,11 @@ class MetadataXMLDeserializer(XMLDeserializer): def extract_metadata(self, metadata_node): """Marshal the metadata attribute of a parsed request""" - if metadata_node is None: - return None metadata = {} - for meta_node in self.find_children_named(metadata_node, "meta"): - key = meta_node.getAttribute("key") - metadata[key] = self.extract_text(meta_node) + if metadata_node is not None: + for meta_node in self.find_children_named(metadata_node, "meta"): + key = meta_node.getAttribute("key") + metadata[key] = self.extract_text(meta_node) return metadata -- cgit From 3db1c53486fdb669ac2bab303335548d7a7c617d Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Tue, 26 Jul 2011 13:28:11 -0400 Subject: updating imageRef and flavorRef parsing --- nova/api/openstack/servers.py | 42 +++++------------------------------------- 1 file changed, 5 insertions(+), 37 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index e1f845f36..06112eee3 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -493,51 +493,19 @@ class ControllerV11(Controller): def _image_ref_from_req_data(self, data): try: - image = data['server']['image'] + return data['server']['imageRef'] except (TypeError, KeyError): - msg = _("Missing image entity") + msg = _("Missing imageRef attribute") raise exc.HTTPBadRequest(explanation=msg) - try: - links = image.get('links', []) - except AttributeError: - msg = _("Malformed image entity") - raise exc.HTTPBadRequest(explanation=msg) - - image_ref = self._href_from_bookmark_links(links) - - if image_ref is None: - try: - return image['id'] - except KeyError: - msg = _("Missing id attribute on image entity") - raise exc.HTTPBadRequest(explanation=msg) - else: - return image_ref - def _flavor_id_from_req_data(self, data): try: - flavor = data['server']['flavor'] + flavor_ref = data['server']['flavorRef'] except (TypeError, KeyError): - msg = _("Missing flavor entity") + msg = _("Missing flavorRef attribute") raise exc.HTTPBadRequest(explanation=msg) - try: - links = flavor.get('links', []) - except AttributeError: - msg = _("Malformed flavor entity") - raise exc.HTTPBadRequest(explanation=msg) - - flavor_ref = self._href_from_bookmark_links(links) - - if flavor_ref is None: - try: - return flavor['id'] - except (KeyError, AttributeError): - msg = _("Missing id attribute in flavor entity") - raise exc.HTTPBadRequest(explanation=msg) - else: - return common.get_id_from_href(flavor_ref) + return common.get_id_from_href(flavor_ref) def _build_view(self, req, instance, is_detail=False): base_url = req.application_url -- cgit From 94866fef798a6b72061720cb654442cd194b9f5f Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Tue, 26 Jul 2011 13:47:37 -0400 Subject: reverting tests to use imageRef, flavorRef --- nova/api/openstack/create_instance_helper.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index b717b8ac0..1342397c4 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -319,11 +319,10 @@ class ServerXMLDeserializer(wsgi.MetadataXMLDeserializer): def _extract_personality(self, server_node): """Marshal the personality attribute of a parsed request""" - personality_node = \ - self.find_first_child_named(server_node, "personality") + node = self.find_first_child_named(server_node, "personality") personality = [] - if personality_node is not None: - for file_node in self.find_children_named(personality_node, "file"): + if node is not None: + for file_node in self.find_children_named(node, "file"): item = {} if file_node.hasAttribute("path"): item["path"] = file_node.getAttribute("path") -- cgit From 7cd146e7e658c51cc94664d8da8d2bc15b0141fc Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Tue, 26 Jul 2011 13:49:04 -0400 Subject: fixed minor issues --- nova/api/openstack/servers.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 11dafc272..194ec82d5 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -630,8 +630,8 @@ class ServerXMLSerializer(wsgi.XMLDictSerializer): node.setAttribute('uuid', str(server['uuid'])) node.setAttribute('hostId', str(server['hostId'])) node.setAttribute('name', server['name']) - node.setAttribute('created', server['created']) - node.setAttribute('updated', server['updated']) + node.setAttribute('created', str(server['created'])) + node.setAttribute('updated', str(server['updated'])) node.setAttribute('status', server['status']) if 'progress' in server: node.setAttribute('progress', str(server['progress'])) @@ -655,17 +655,19 @@ class ServerXMLSerializer(wsgi.XMLDictSerializer): for link_node in link_nodes: server_node.appendChild(link_node) - image_node = self._create_basic_entity_node(xml_doc, + if 'image' in server: + image_node = self._create_basic_entity_node(xml_doc, server['image']['id'], server['image']['links'], 'image') - server_node.appendChild(image_node) + server_node.appendChild(image_node) - flavor_node = self._create_basic_entity_node(xml_doc, + if 'flavor' in server: + flavor_node = self._create_basic_entity_node(xml_doc, server['flavor']['id'], server['flavor']['links'], 'flavor') - server_node.appendChild(flavor_node) + server_node.appendChild(flavor_node) metadata = server.get('metadata', {}).items() if len(metadata) > 0: -- cgit From 24e1f23dbaf9ffd3f42fe05c24b980a6a0f09499 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Tue, 26 Jul 2011 14:06:02 -0400 Subject: removing extra function --- nova/api/openstack/servers.py | 11 ----------- 1 file changed, 11 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 06112eee3..618778ea3 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -480,17 +480,6 @@ class ControllerV11(Controller): except exception.NotFound: raise exc.HTTPNotFound() - def _href_from_bookmark_links(self, links): - for link in links: - try: - if link.get('rel') == 'bookmark': - href = link.get('href') - if href is not None: - return href - except AttributeError: - msg = _("Malformed link entity") - raise exc.HTTPBadRequest(explanation=msg) - def _image_ref_from_req_data(self, data): try: return data['server']['imageRef'] -- cgit From 2e652f4cc72976ecc471a6c6f3b48afb3eb5a420 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Tue, 26 Jul 2011 16:42:16 -0400 Subject: Updated test stubs to contain the correct data Updated created and updated in responses to use correct time format --- nova/api/openstack/views/servers.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index be25e1e40..c90801724 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -15,6 +15,7 @@ # License for the specific language governing permissions and limitations # under the License. +import datetime import hashlib import os @@ -149,8 +150,10 @@ class ViewBuilderV11(ViewBuilder): def _build_detail(self, inst): response = super(ViewBuilderV11, self)._build_detail(inst) - response['server']['created'] = inst['created_at'] - response['server']['updated'] = inst['updated_at'] + response['server']['created'] = \ + self._convert_timeformat(inst['created_at']) + response['server']['updated'] = \ + self._convert_timeformat(inst['updated_at']) if 'status' in response['server']: if response['server']['status'] == "ACTIVE": response['server']['progress'] = 100 @@ -221,3 +224,11 @@ class ViewBuilderV11(ViewBuilder): """Create an url that refers to a specific flavor id.""" return os.path.join(common.remove_version_from_href(self.base_url), "servers", str(server_id)) + + def _convert_timeformat(self, time): + """Converts the given time into the common time format + + :param time: should be a datetime + + """ + return time.strftime(utils.TIME_FORMAT) -- cgit From e4e9d7550c2eb29c5d8fed3af0b9112976d262de Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 26 Jul 2011 20:44:29 +0000 Subject: Update security gropu rules to properly support new format and boto 2.0 --- nova/api/ec2/cloud.py | 53 +++++++++++++++++++++++++++++++++------------------ 1 file changed, 34 insertions(+), 19 deletions(-) (limited to 'nova/api') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 10720a804..4b8c4cf80 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -539,15 +539,20 @@ class CloudController(object): return rules if 'ip_ranges' in kwargs: rules = self._cidr_args_split(kwargs) + else: + rules = [kwargs] finalset = [] for rule in rules: if 'groups' in rule: groups_values = self._groups_args_split(rule) for groups_value in groups_values: - finalset.append(groups_value) + final = self._rule_dict_last_step(context, **groups_value) + finalset.append(final) else: if rule: - finalset.append(rule) + final = self._rule_dict_last_step(context, **rule) + finalset.append(final) + LOG.warn(finalset) return finalset def _cidr_args_split(self, kwargs): @@ -590,6 +595,9 @@ class CloudController(object): db.security_group_get_by_name(context.elevated(), source_project_id, source_security_group_name) + notfound = exception.SecurityGroupNotFound + if not source_security_group: + raise notfound(security_group_id=source_security_group_name) values['group_id'] = source_security_group['id'] elif cidr_ip: # If this fails, it throws an exception. This is what we want. @@ -628,7 +636,7 @@ class CloudController(object): for rule in security_group.rules: if 'group_id' in values: if rule['group_id'] == values['group_id']: - return True + return rule['id'] else: is_duplicate = True for key in ('cidr', 'from_port', 'to_port', 'protocol'): @@ -636,7 +644,7 @@ class CloudController(object): is_duplicate = False break if is_duplicate: - return True + return rule['id'] return False def revoke_security_group_ingress(self, context, group_name=None, @@ -659,23 +667,30 @@ class CloudController(object): msg = "Revoke security group ingress %s" LOG.audit(_(msg), security_group['name'], context=context) + prevalues = [] + try: + prevalues = kwargs['ip_permissions'] + except KeyError: + prevalues.append(kwargs) + postvalues = [] + for values in prevalues: + rulesvalues = self._rule_args_to_dict(context, values) + if not rulesvalues: + err = "%s Not enough parameters to build a valid rule" + raise exception.ApiError(_(err % rulesvalues)) - criteria = self._rule_args_to_dict(context, kwargs)[0] - if criteria is None: - raise exception.ApiError(_("Not enough parameters to build a " - "valid rule.")) - - for rule in security_group.rules: - match = True - for (k, v) in criteria.iteritems(): - if getattr(rule, k, False) != v: - match = False - if match: - db.security_group_rule_destroy(context, rule['id']) - self.compute_api.trigger_security_group_rules_refresh(context, - security_group_id=security_group['id']) + rule_id = None + for values_for_rule in rulesvalues: + values_for_rule['parent_group_id'] = security_group.id + rule_id = self._security_group_rule_exists(security_group, + values_for_rule) + if rule_id: + db.security_group_rule_destroy(context, rule_id) + self.compute_api.trigger_security_group_rules_refresh(context, + security_group_id=security_group['id']) + if rule_id: return True - raise exception.ApiError(_("No rule for the specified parameters.")) + raise exception.ApiError(_("No rule for the specified parameters.")) # TODO(soren): This has only been tested with Boto as the client. # Unfortunately, it seems Boto is using an old API -- cgit From 5bd11c44f791cfe4c371b33cd1b1253013a8f836 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 26 Jul 2011 20:49:46 +0000 Subject: remove some logging, remove extra if --- nova/api/ec2/cloud.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'nova/api') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 4b8c4cf80..4045dfc61 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -549,10 +549,8 @@ class CloudController(object): final = self._rule_dict_last_step(context, **groups_value) finalset.append(final) else: - if rule: - final = self._rule_dict_last_step(context, **rule) - finalset.append(final) - LOG.warn(finalset) + final = self._rule_dict_last_step(context, **rule) + finalset.append(final) return finalset def _cidr_args_split(self, kwargs): -- cgit From d4b2a2b3d552103414e4052773ac97939c66fa53 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 26 Jul 2011 20:58:02 +0000 Subject: pep8 and simplify rule refresh logic --- nova/api/ec2/cloud.py | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) (limited to 'nova/api') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 4045dfc61..0294c09c5 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -670,25 +670,26 @@ class CloudController(object): prevalues = kwargs['ip_permissions'] except KeyError: prevalues.append(kwargs) - postvalues = [] + rule_id = None for values in prevalues: rulesvalues = self._rule_args_to_dict(context, values) if not rulesvalues: err = "%s Not enough parameters to build a valid rule" raise exception.ApiError(_(err % rulesvalues)) - rule_id = None for values_for_rule in rulesvalues: values_for_rule['parent_group_id'] = security_group.id rule_id = self._security_group_rule_exists(security_group, values_for_rule) if rule_id: db.security_group_rule_destroy(context, rule_id) - self.compute_api.trigger_security_group_rules_refresh(context, - security_group_id=security_group['id']) - if rule_id: - return True - raise exception.ApiError(_("No rule for the specified parameters.")) + if rule_id: + # NOTE(vish): we removed a rule, so refresh + self.compute_api.trigger_security_group_rules_refresh( + context, + security_group_id=security_group['id']) + return True + raise exception.ApiError(_("No rule for the specified parameters.")) # TODO(soren): This has only been tested with Boto as the client. # Unfortunately, it seems Boto is using an old API @@ -734,15 +735,17 @@ class CloudController(object): postvalues.append(values_for_rule) for values_for_rule in postvalues: - security_group_rule = db.security_group_rule_create(context, - values_for_rule) + security_group_rule = db.security_group_rule_create( + context, + values_for_rule) - self.compute_api.trigger_security_group_rules_refresh(context, - security_group_id=security_group['id']) + if postvalues: + self.compute_api.trigger_security_group_rules_refresh( + context, + security_group_id=security_group['id']) + return True - group = db.security_group_get_by_name(context, context.project_id, - security_group['name']) - return True + raise exception.ApiError(_("No rule for the specified parameters.")) def _get_source_project_id(self, context, source_security_group_owner_id): if source_security_group_owner_id: -- cgit From 0300c952d925ccaad2d3d4191d87c08656d4b413 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Wed, 27 Jul 2011 12:54:12 -0400 Subject: removed unused import --- nova/api/openstack/image_metadata.py | 1 - 1 file changed, 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/image_metadata.py b/nova/api/openstack/image_metadata.py index da753dee5..aaf64a123 100644 --- a/nova/api/openstack/image_metadata.py +++ b/nova/api/openstack/image_metadata.py @@ -22,7 +22,6 @@ from nova import image from nova import quota from nova import utils from nova.api.openstack import common -from nova.api.openstack import faults from nova.api.openstack import wsgi -- cgit From b8183e11e56781fce27ec1af261c5e53bca78ca5 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Wed, 27 Jul 2011 15:15:48 -0400 Subject: change local variable name --- nova/api/openstack/views/servers.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index c90801724..659a43522 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -225,10 +225,10 @@ class ViewBuilderV11(ViewBuilder): return os.path.join(common.remove_version_from_href(self.base_url), "servers", str(server_id)) - def _convert_timeformat(self, time): + def _convert_timeformat(self, date_time): """Converts the given time into the common time format - :param time: should be a datetime + :param date_time: the datetime object to convert """ - return time.strftime(utils.TIME_FORMAT) + return date_time.strftime(utils.TIME_FORMAT) -- cgit From 7a165843aa5c1a98b1dbf13dedf556878a3d0435 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Fri, 29 Jul 2011 11:06:02 -0400 Subject: Updated resize to call compute API with instance_type identifiers instead of flavor identifiers. Updated tests. --- nova/api/openstack/servers.py | 63 +++++++++++++++++++++++++++---------------- 1 file changed, 40 insertions(+), 23 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index f6841318d..096fb229c 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -17,11 +17,10 @@ import base64 import traceback from webob import exc -import webob from xml.dom import minidom +import webob from nova import compute -from nova import db from nova import exception from nova import flags from nova import log as logging @@ -29,13 +28,14 @@ from nova import utils from nova.api.openstack import common from nova.api.openstack import create_instance_helper as helper from nova.api.openstack import ips +from nova.api.openstack import wsgi +from nova.compute import instance_types +from nova.scheduler import api as scheduler_api +import nova.api.openstack 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 -import nova.api.openstack -from nova.scheduler import api as scheduler_api LOG = logging.getLogger('nova.api.openstack.servers') @@ -438,13 +438,21 @@ class ControllerV10(Controller): def _action_resize(self, input_dict, req, id): """ Resizes a given instance to the flavor size requested """ - 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 'flavorId' argument for resize")) - raise exc.HTTPUnprocessableEntity() + try: + flavor_id = input_dict["resize"]["flavorId"] + except (KeyError, TypeError): + msg = _("Resize requests require 'flavorId' attribute.") + raise exc.HTTPBadRequest(explanation=msg) + + try: + i_type = instance_types.get_instance_type_by_flavor_id(flavor_id) + except exception.FlavorNotFound: + msg = _("Unable to locate requested flavor.") + raise exc.HTTPBadRequest(explanation=msg) + + context = req.environ["nova.context"] + self.compute_api.resize(context, id, i_type["id"]) + return webob.Response(status_int=202) def _action_rebuild(self, info, request, instance_id): @@ -555,17 +563,26 @@ class ControllerV11(Controller): 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 'flavorRef' argument for resize")) - raise exc.HTTPUnprocessableEntity() - except Exception, e: - LOG.exception(_("Error in resize %s"), e) - raise exc.HTTPBadRequest() + flavor = input_dict["resize"]["flavor"] + except (KeyError, TypeError): + msg = _("Resize requests require a flavor to resize instance.") + raise exc.HTTPBadRequest(explanation=msg) + + try: + flavor_id = flavor["id"] + except (KeyError, TypeError): + msg = _("Resize flavor requires 'id' attribute.") + raise exc.HTTPBadRequest(explanation=msg) + + try: + i_type = instance_types.get_instance_type_by_flavor_id(flavor_id) + except exception.FlavorNotFound: + msg = _("Unable to locate requested flavor.") + raise exc.HTTPBadRequest(explanation=msg) + + context = req.environ["nova.context"] + self.compute_api.resize(context, id, i_type["id"]) + return webob.Response(status_int=202) def _action_rebuild(self, info, request, instance_id): -- cgit From 50abd79432ff82a23da1934cc4d297c0c5051668 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Fri, 29 Jul 2011 11:57:40 -0400 Subject: Oops, I wasn't actually being compatible with the spec here. --- nova/api/openstack/servers.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 096fb229c..30169d450 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -563,19 +563,13 @@ class ControllerV11(Controller): def _action_resize(self, input_dict, req, id): """ Resizes a given instance to the flavor size requested """ try: - flavor = input_dict["resize"]["flavor"] + flavor_ref = input_dict["resize"]["flavorRef"] except (KeyError, TypeError): - msg = _("Resize requests require a flavor to resize instance.") + msg = _("Resize requests require 'flavorRef' attribute.") raise exc.HTTPBadRequest(explanation=msg) try: - flavor_id = flavor["id"] - except (KeyError, TypeError): - msg = _("Resize flavor requires 'id' attribute.") - raise exc.HTTPBadRequest(explanation=msg) - - try: - i_type = instance_types.get_instance_type_by_flavor_id(flavor_id) + i_type = instance_types.get_instance_type_by_flavor_id(flavor_ref) except exception.FlavorNotFound: msg = _("Unable to locate requested flavor.") raise exc.HTTPBadRequest(explanation=msg) -- cgit