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