From bfefe6317fde87f8ca7a4d28bed11d99f7029186 Mon Sep 17 00:00:00 2001 From: Gaurav Gupta Date: Sat, 29 Oct 2011 17:20:08 -0700 Subject: Added support for creating nova volume snapshots using OS API. Fixes bug 883676 blueprint nova-volume-snapshot-backup-api Change-Id: Id3e1ad39ef791b93dd014cada87c2d295454701f --- nova/api/openstack/v2/contrib/volumes.py | 155 ++++++++++++++++++++++++++++++- 1 file changed, 152 insertions(+), 3 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/v2/contrib/volumes.py b/nova/api/openstack/v2/contrib/volumes.py index 75d8af803..8a5e84c0d 100644 --- a/nova/api/openstack/v2/contrib/volumes.py +++ b/nova/api/openstack/v2/contrib/volumes.py @@ -24,11 +24,9 @@ from nova.api.openstack.v2 import servers from nova.api.openstack import wsgi from nova.api.openstack import xmlutil from nova import compute -from nova import db from nova import exception from nova import flags from nova import log as logging -from nova import quota from nova import volume from nova.volume import volume_types @@ -72,6 +70,7 @@ def _translate_volume_summary_view(context, vol): else: d['volumeType'] = vol['volume_type_id'] + d['snapshotId'] = vol['snapshot_id'] LOG.audit(_("vol=%s"), vol, context=context) if vol.get('volume_metadata'): @@ -153,7 +152,8 @@ class VolumeController(object): metadata = vol.get('metadata', None) - new_volume = self.volume_api.create(context, size, None, + new_volume = self.volume_api.create(context, size, + vol.get('snapshot_id'), vol.get('display_name'), vol.get('display_description'), volume_type=vol_type, @@ -176,6 +176,7 @@ def make_volume(elem): elem.set('displayName') elem.set('displayDescription') elem.set('volumeType') + elem.set('snapshotId') attachments = xmlutil.SubTemplateElement(elem, 'attachments') attachment = xmlutil.SubTemplateElement(attachments, 'attachment', @@ -394,6 +395,143 @@ class BootFromVolumeController(servers.Controller): return data.get('block_device_mapping') +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(object): + """The Volumes 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'] + + try: + vol = self.volume_api.get_snapshot(context, id) + except exception.NotFound: + return exc.HTTPNotFound() + + return {'snapshot': _translate_snapshot_detail_view(context, vol)} + + def delete(self, req, id): + """Delete a snapshot.""" + context = req.environ['nova.context'] + + LOG.audit(_("Delete snapshot with id: %s"), id, context=context) + + try: + self.volume_api.delete_snapshot(context, snapshot_id=id) + except exception.NotFound: + return exc.HTTPNotFound() + 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'] + + 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'] + + if not body: + return exc.HTTPUnprocessableEntity() + + snapshot = body['snapshot'] + volume_id = snapshot['volume_id'] + force = snapshot.get('force', False) + LOG.audit(_("Create snapshot from volume %s"), volume_id, + context=context) + + if force: + new_snapshot = self.volume_api.create_snapshot_force(context, + volume_id, + snapshot.get('display_name'), + snapshot.get('display_description')) + else: + new_snapshot = self.volume_api.create_snapshot(context, + volume_id, + snapshot.get('display_name'), + snapshot.get('display_description')) + + retval = _translate_snapshot_detail_view(context, new_snapshot) + + return {'snapshot': retval} + + +def make_snapshot(elem): + elem.set('id') + elem.set('status') + elem.set('size') + elem.set('createdAt') + elem.set('displayName') + elem.set('displayDescription') + elem.set('volumeId') + + +class SnapshotTemplate(xmlutil.TemplateBuilder): + def construct(self): + root = xmlutil.TemplateElement('snapshot', selector='snapshot') + make_snapshot(root) + return xmlutil.MasterTemplate(root, 1) + + +class SnapshotsTemplate(xmlutil.TemplateBuilder): + def construct(self): + root = xmlutil.TemplateElement('snapshots') + elem = xmlutil.SubTemplateElement(root, 'snapshot', + selector='snapshots') + make_snapshot(elem) + return xmlutil.MasterTemplate(root, 1) + + +class SnapshotSerializer(xmlutil.XMLTemplateSerializer): + def default(self): + return SnapshotTemplate() + + def index(self): + return SnapshotsTemplate() + + def detail(self): + return SnapshotsTemplate() + + class Volumes(extensions.ExtensionDescriptor): """Volumes support""" @@ -449,4 +587,15 @@ class Volumes(extensions.ExtensionDescriptor): deserializer=deserializer) resources.append(res) + snapshot_serializers = { + 'application/xml': SnapshotSerializer(), + } + snap_serializer = wsgi.ResponseSerializer(snapshot_serializers) + + res = extensions.ResourceExtension('os-snapshots', + SnapshotController(), + serializer=snap_serializer, + collection_actions={'detail': 'GET'}) + resources.append(res) + return resources -- cgit