diff options
Diffstat (limited to 'nova/api/openstack/compute/legacy_v2/contrib/volumes.py')
-rw-r--r-- | nova/api/openstack/compute/legacy_v2/contrib/volumes.py | 613 |
1 files changed, 0 insertions, 613 deletions
diff --git a/nova/api/openstack/compute/legacy_v2/contrib/volumes.py b/nova/api/openstack/compute/legacy_v2/contrib/volumes.py deleted file mode 100644 index 6b0e9966e2..0000000000 --- a/nova/api/openstack/compute/legacy_v2/contrib/volumes.py +++ /dev/null @@ -1,613 +0,0 @@ -# Copyright 2011 Justin Santa Barbara -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -"""The volumes extension.""" - -from oslo_log import log as logging -from oslo_utils import strutils -from oslo_utils import uuidutils -import webob -from webob import exc - -from nova.api.openstack import common -from nova.api.openstack import extensions -from nova.api.openstack import wsgi -from nova import compute -from nova import exception -from nova.i18n import _ -from nova.i18n import _LI -from nova import objects -from nova import volume - -LOG = logging.getLogger(__name__) -authorize = extensions.extension_authorizer('compute', 'volumes') - -authorize_attach = extensions.extension_authorizer('compute', - 'volume_attachments') - - -def _translate_volume_detail_view(context, vol): - """Maps keys for volumes details view.""" - - d = _translate_volume_summary_view(context, vol) - - # No additional data / lookups at the moment - - return d - - -def _translate_volume_summary_view(context, vol): - """Maps keys for volumes summary view.""" - d = {} - - d['id'] = vol['id'] - d['status'] = vol['status'] - d['size'] = vol['size'] - d['availabilityZone'] = vol['availability_zone'] - d['createdAt'] = vol['created_at'] - - if vol['attach_status'] == 'attached': - # NOTE(ildikov): The attachments field in the volume info that - # Cinder sends is converted to an OrderedDict with the - # instance_uuid as key to make it easier for the multiattach - # feature to check the required information. Multiattach will - # be enable in the Nova API in Newton. - # The format looks like the following: - # attachments = {'instance_uuid': { - # 'attachment_id': 'attachment_uuid', - # 'mountpoint': '/dev/sda/ - # } - # } - attachment = vol['attachments'].items()[0] - d['attachments'] = [_translate_attachment_detail_view(vol['id'], - attachment[0], - attachment[1].get('mountpoint'))] - else: - d['attachments'] = [{}] - - d['displayName'] = vol['display_name'] - d['displayDescription'] = vol['display_description'] - - if vol['volume_type_id'] and vol.get('volume_type'): - d['volumeType'] = vol['volume_type']['name'] - else: - d['volumeType'] = vol['volume_type_id'] - - d['snapshotId'] = vol['snapshot_id'] - LOG.info(_LI("vol=%s"), vol, context=context) - - if vol.get('volume_metadata'): - d['metadata'] = vol.get('volume_metadata') - else: - d['metadata'] = {} - - return d - - -class VolumeController(wsgi.Controller): - """The Volumes API controller for the OpenStack API.""" - - def __init__(self): - self.volume_api = volume.API() - super(VolumeController, self).__init__() - - def show(self, req, id): - """Return data about the given volume.""" - context = req.environ['nova.context'] - authorize(context) - - try: - vol = self.volume_api.get(context, id) - except exception.NotFound as e: - raise exc.HTTPNotFound(explanation=e.format_message()) - - return {'volume': _translate_volume_detail_view(context, vol)} - - def delete(self, req, id): - """Delete a volume.""" - context = req.environ['nova.context'] - authorize(context) - - LOG.info(_LI("Delete volume with id: %s"), id, context=context) - - try: - self.volume_api.delete(context, id) - except exception.NotFound as e: - raise exc.HTTPNotFound(explanation=e.format_message()) - return webob.Response(status_int=202) - - def index(self, req): - """Returns a summary list of volumes.""" - return self._items(req, entity_maker=_translate_volume_summary_view) - - def detail(self, req): - """Returns a detailed list of volumes.""" - return self._items(req, entity_maker=_translate_volume_detail_view) - - def _items(self, req, entity_maker): - """Returns a list of volumes, transformed through entity_maker.""" - context = req.environ['nova.context'] - authorize(context) - - volumes = self.volume_api.get_all(context) - limited_list = common.limited(volumes, req) - res = [entity_maker(context, vol) for vol in limited_list] - return {'volumes': res} - - def create(self, req, body): - """Creates a new volume.""" - context = req.environ['nova.context'] - authorize(context) - - if not self.is_valid_body(body, 'volume'): - msg = _("volume not specified") - raise exc.HTTPBadRequest(explanation=msg) - - vol = body['volume'] - - vol_type = vol.get('volume_type', None) - - metadata = vol.get('metadata', None) - - snapshot_id = vol.get('snapshot_id') - - if snapshot_id is not None: - try: - snapshot = self.volume_api.get_snapshot(context, snapshot_id) - except exception.SnapshotNotFound as e: - raise exc.HTTPNotFound(explanation=e.format_message()) - else: - snapshot = None - - size = vol.get('size', None) - if size is None and snapshot is not None: - size = snapshot['volume_size'] - - LOG.info(_LI("Create volume of %s GB"), size, context=context) - - availability_zone = vol.get('availability_zone', None) - - try: - new_volume = self.volume_api.create( - context, - size, - vol.get('display_name'), - vol.get('display_description'), - snapshot=snapshot, - volume_type=vol_type, - metadata=metadata, - availability_zone=availability_zone - ) - except exception.InvalidInput as err: - raise exc.HTTPBadRequest(explanation=err.format_message()) - except exception.OverQuota as err: - raise exc.HTTPForbidden(explanation=err.format_message()) - # TODO(vish): Instance should be None at db layer instead of - # trying to lazy load, but for now we turn it into - # a dict to avoid an error. - retval = _translate_volume_detail_view(context, dict(new_volume)) - result = {'volume': retval} - - location = '%s/%s' % (req.url, new_volume['id']) - - return wsgi.ResponseObject(result, headers=dict(location=location)) - - -def _translate_attachment_detail_view(volume_id, instance_uuid, mountpoint): - """Maps keys for attachment details view.""" - - d = _translate_attachment_summary_view(volume_id, - instance_uuid, - mountpoint) - - # No additional data / lookups at the moment - return d - - -def _translate_attachment_summary_view(volume_id, instance_uuid, mountpoint): - """Maps keys for attachment summary view.""" - d = {} - - # NOTE(justinsb): We use the volume id as the id of the attachment object - d['id'] = volume_id - - d['volumeId'] = volume_id - - d['serverId'] = instance_uuid - if mountpoint: - d['device'] = mountpoint - - return d - - -class VolumeAttachmentController(wsgi.Controller): - """The volume attachment API controller for the OpenStack API. - - A child resource of the server. Note that we use the volume id - as the ID of the attachment (though this is not guaranteed externally) - - """ - - def __init__(self, ext_mgr=None): - self.compute_api = compute.API() - self.volume_api = volume.API() - self.ext_mgr = ext_mgr - super(VolumeAttachmentController, self).__init__() - - def index(self, req, server_id): - """Returns the list of volume attachments for a given instance.""" - context = req.environ['nova.context'] - authorize_attach(context, action='index') - return self._items(req, server_id, - entity_maker=_translate_attachment_summary_view) - - def show(self, req, server_id, id): - """Return data about the given volume attachment.""" - context = req.environ['nova.context'] - authorize(context) - authorize_attach(context, action='show') - - volume_id = id - instance = common.get_instance(self.compute_api, context, server_id) - bdms = objects.BlockDeviceMappingList.get_by_instance_uuid( - context, instance.uuid) - - if not bdms: - msg = _("Instance %s is not attached.") % server_id - raise exc.HTTPNotFound(explanation=msg) - - assigned_mountpoint = None - - for bdm in bdms: - if bdm.volume_id == volume_id: - assigned_mountpoint = bdm.device_name - break - - if assigned_mountpoint is None: - msg = _("volume_id not found: %s") % volume_id - raise exc.HTTPNotFound(explanation=msg) - - return {'volumeAttachment': _translate_attachment_detail_view( - volume_id, - instance.uuid, - assigned_mountpoint)} - - def _validate_volume_id(self, volume_id): - if not uuidutils.is_uuid_like(volume_id): - msg = _("Bad volumeId format: volumeId is " - "not in proper format (%s)") % volume_id - raise exc.HTTPBadRequest(explanation=msg) - - def create(self, req, server_id, body): - """Attach a volume to an instance.""" - context = req.environ['nova.context'] - authorize(context) - authorize_attach(context, action='create') - - if not self.is_valid_body(body, 'volumeAttachment'): - msg = _("volumeAttachment not specified") - raise exc.HTTPBadRequest(explanation=msg) - try: - volume_id = body['volumeAttachment']['volumeId'] - except KeyError: - msg = _("volumeId must be specified.") - raise exc.HTTPBadRequest(explanation=msg) - device = body['volumeAttachment'].get('device') - - self._validate_volume_id(volume_id) - - LOG.info(_LI("Attach volume %(volume_id)s to instance %(server_id)s " - "at %(device)s"), - {'volume_id': volume_id, - 'device': device, - 'server_id': server_id}, - context=context) - - instance = common.get_instance(self.compute_api, context, server_id) - try: - device = self.compute_api.attach_volume(context, instance, - volume_id, device) - except exception.NotFound as e: - raise exc.HTTPNotFound(explanation=e.format_message()) - except exception.InstanceIsLocked as e: - raise exc.HTTPConflict(explanation=e.format_message()) - except exception.InstanceInvalidState as state_error: - common.raise_http_conflict_for_instance_invalid_state(state_error, - 'attach_volume', server_id) - - # The attach is async - attachment = {} - attachment['id'] = volume_id - attachment['serverId'] = server_id - attachment['volumeId'] = volume_id - attachment['device'] = device - - # NOTE(justinsb): And now, we have a problem... - # The attach is async, so there's a window in which we don't see - # the attachment (until the attachment completes). We could also - # get problems with concurrent requests. I think we need an - # attachment state, and to write to the DB here, but that's a bigger - # change. - # For now, we'll probably have to rely on libraries being smart - - # TODO(justinsb): How do I return "accepted" here? - return {'volumeAttachment': attachment} - - def update(self, req, server_id, id, body): - if (not self.ext_mgr or - not self.ext_mgr.is_loaded('os-volume-attachment-update')): - raise exc.HTTPBadRequest() - context = req.environ['nova.context'] - authorize(context) - authorize_attach(context, action='update') - - if not self.is_valid_body(body, 'volumeAttachment'): - msg = _("volumeAttachment not specified") - raise exc.HTTPBadRequest(explanation=msg) - - old_volume_id = id - old_volume = self.volume_api.get(context, old_volume_id) - - try: - new_volume_id = body['volumeAttachment']['volumeId'] - except KeyError: - msg = _("volumeId must be specified.") - raise exc.HTTPBadRequest(explanation=msg) - self._validate_volume_id(new_volume_id) - new_volume = self.volume_api.get(context, new_volume_id) - - instance = common.get_instance(self.compute_api, context, server_id) - - bdms = objects.BlockDeviceMappingList.get_by_instance_uuid( - context, instance.uuid) - found = False - try: - for bdm in bdms: - if bdm.volume_id != old_volume_id: - continue - try: - self.compute_api.swap_volume(context, instance, old_volume, - new_volume) - found = True - break - except exception.VolumeUnattached: - # The volume is not attached. Treat it as NotFound - # by falling through. - pass - except exception.InstanceIsLocked as e: - raise exc.HTTPConflict(explanation=e.format_message()) - except exception.InstanceInvalidState as state_error: - common.raise_http_conflict_for_instance_invalid_state(state_error, - 'swap_volume', server_id) - - if not found: - msg = _("volume_id not found: %s") % old_volume_id - raise exc.HTTPNotFound(explanation=msg) - else: - return webob.Response(status_int=202) - - def delete(self, req, server_id, id): - """Detach a volume from an instance.""" - context = req.environ['nova.context'] - authorize(context) - authorize_attach(context, action='delete') - - volume_id = id - LOG.info(_LI("Detach volume %s"), volume_id, context=context) - - instance = common.get_instance(self.compute_api, context, server_id) - - volume = self.volume_api.get(context, volume_id) - - bdms = objects.BlockDeviceMappingList.get_by_instance_uuid( - context, instance.uuid) - if not bdms: - msg = _("Instance %s is not attached.") % server_id - raise exc.HTTPNotFound(explanation=msg) - - found = False - try: - for bdm in bdms: - if bdm.volume_id != volume_id: - continue - if bdm.is_root: - msg = _("Can't detach root device volume") - raise exc.HTTPForbidden(explanation=msg) - try: - self.compute_api.detach_volume(context, instance, volume) - found = True - break - except exception.VolumeUnattached: - # The volume is not attached. Treat it as NotFound - # by falling through. - pass - except exception.InstanceIsLocked as e: - raise exc.HTTPConflict(explanation=e.format_message()) - except exception.InstanceInvalidState as state_error: - common.raise_http_conflict_for_instance_invalid_state(state_error, - 'detach_volume', server_id) - - if not found: - msg = _("volume_id not found: %s") % volume_id - raise exc.HTTPNotFound(explanation=msg) - else: - return webob.Response(status_int=202) - - def _items(self, req, server_id, entity_maker): - """Returns a list of attachments, transformed through entity_maker.""" - context = req.environ['nova.context'] - authorize(context) - - instance = common.get_instance(self.compute_api, context, server_id) - - bdms = objects.BlockDeviceMappingList.get_by_instance_uuid( - context, instance.uuid) - limited_list = common.limited(bdms, req) - results = [] - - for bdm in limited_list: - if bdm.volume_id: - results.append(entity_maker(bdm.volume_id, - bdm.instance_uuid, - bdm.device_name)) - - return {'volumeAttachments': results} - - -def _translate_snapshot_detail_view(context, vol): - """Maps keys for snapshots details view.""" - - d = _translate_snapshot_summary_view(context, vol) - - # NOTE(gagupta): No additional data / lookups at the moment - return d - - -def _translate_snapshot_summary_view(context, vol): - """Maps keys for snapshots summary view.""" - d = {} - - d['id'] = vol['id'] - d['volumeId'] = vol['volume_id'] - d['status'] = vol['status'] - # NOTE(gagupta): We map volume_size as the snapshot size - d['size'] = vol['volume_size'] - d['createdAt'] = vol['created_at'] - d['displayName'] = vol['display_name'] - d['displayDescription'] = vol['display_description'] - return d - - -class SnapshotController(wsgi.Controller): - """The Snapshots API controller for the OpenStack API.""" - - def __init__(self): - self.volume_api = volume.API() - super(SnapshotController, self).__init__() - - def show(self, req, id): - """Return data about the given snapshot.""" - context = req.environ['nova.context'] - authorize(context) - - try: - vol = self.volume_api.get_snapshot(context, id) - except exception.NotFound as e: - raise exc.HTTPNotFound(explanation=e.format_message()) - - return {'snapshot': _translate_snapshot_detail_view(context, vol)} - - def delete(self, req, id): - """Delete a snapshot.""" - context = req.environ['nova.context'] - authorize(context) - - LOG.info(_LI("Delete snapshot with id: %s"), id, context=context) - - try: - self.volume_api.delete_snapshot(context, id) - except exception.NotFound as e: - raise exc.HTTPNotFound(explanation=e.format_message()) - return webob.Response(status_int=202) - - def index(self, req): - """Returns a summary list of snapshots.""" - return self._items(req, entity_maker=_translate_snapshot_summary_view) - - def detail(self, req): - """Returns a detailed list of snapshots.""" - return self._items(req, entity_maker=_translate_snapshot_detail_view) - - def _items(self, req, entity_maker): - """Returns a list of snapshots, transformed through entity_maker.""" - context = req.environ['nova.context'] - authorize(context) - - snapshots = self.volume_api.get_all_snapshots(context) - limited_list = common.limited(snapshots, req) - res = [entity_maker(context, snapshot) for snapshot in limited_list] - return {'snapshots': res} - - def create(self, req, body): - """Creates a new snapshot.""" - context = req.environ['nova.context'] - authorize(context) - - if not self.is_valid_body(body, 'snapshot'): - msg = _("snapshot not specified") - raise exc.HTTPBadRequest(explanation=msg) - - snapshot = body['snapshot'] - volume_id = snapshot['volume_id'] - - LOG.info(_LI("Create snapshot from volume %s"), volume_id, - context=context) - - force = snapshot.get('force', False) - try: - force = strutils.bool_from_string(force, strict=True) - except ValueError: - msg = _("Invalid value '%s' for force.") % force - raise exc.HTTPBadRequest(explanation=msg) - - if force: - create_func = self.volume_api.create_snapshot_force - else: - create_func = self.volume_api.create_snapshot - - new_snapshot = create_func(context, volume_id, - snapshot.get('display_name'), - snapshot.get('display_description')) - - retval = _translate_snapshot_detail_view(context, new_snapshot) - return {'snapshot': retval} - - -class Volumes(extensions.ExtensionDescriptor): - """Volumes support.""" - - name = "Volumes" - alias = "os-volumes" - namespace = "http://docs.openstack.org/compute/ext/volumes/api/v1.1" - updated = "2011-03-25T00:00:00Z" - - def get_resources(self): - resources = [] - - # NOTE(justinsb): No way to provide singular name ('volume') - # Does this matter? - res = extensions.ResourceExtension('os-volumes', - VolumeController(), - collection_actions={'detail': 'GET'}) - resources.append(res) - - attachment_controller = VolumeAttachmentController(self.ext_mgr) - res = extensions.ResourceExtension('os-volume_attachments', - attachment_controller, - parent=dict( - member_name='server', - collection_name='servers')) - resources.append(res) - - res = extensions.ResourceExtension('os-volumes_boot', - inherits='servers') - resources.append(res) - - res = extensions.ResourceExtension('os-snapshots', - SnapshotController(), - collection_actions={'detail': 'GET'}) - resources.append(res) - - return resources |