Add group snapshots - APIs

This is the fifth patch that implements the generic-volume-group
bluerpint. It adds APIs for group snapshots and create group
from source.

This patch depends on the fourth patch which implements group
snapshots support in the volume manager:
    https://review.openstack.org/#/c/361376/

Client side patch is here:
    https://review.openstack.org/#/c/329770/

Current microversion is 3.14. The following CLI's are supported:
cinder --os-volume-api-version 3.14 group-create-from-src
    --name my_group --group-snapshot <group snapshot uuid>
cinder --os-volume-api-version 3.14 group-create-from-src
    --name my_group --source-group <source group uuid>
cinder --os-volume-api-version 3.14 group-snapshot-create
    --name <name> <group uuid>
cinder --os-volume-api-version 3.14 group-snapshot-list
cinder --os-volume-api-version 3.14 group-snapshot-show
    <group snapshot uuid>
cinder --os-volume-api-version 3.14 group-snapshot-delete
    <group snapshot uuid>

APIImpact
DocImpact
Partial-Implements: blueprint generic-volume-group

Change-Id: I2e628968afcf058113e1f1aeb851570c7f0f3a08
This commit is contained in:
xing-yang
2016-07-17 10:56:36 -04:00
parent 4a67bc8218
commit 708b9be9c0
24 changed files with 1714 additions and 34 deletions

View File

@@ -26,9 +26,10 @@ from oslo_utils import excutils
from oslo_utils import timeutils
from cinder.common import constants
from cinder import db
from cinder.db import base
from cinder import exception
from cinder.i18n import _, _LE, _LW
from cinder.i18n import _, _LE, _LI, _LW
from cinder import objects
from cinder.objects import base as objects_base
from cinder.objects import fields as c_fields
@@ -180,6 +181,212 @@ class API(base.Base):
return group
def create_from_src(self, context, name, description=None,
group_snapshot_id=None, source_group_id=None):
check_policy(context, 'create')
kwargs = {
'user_id': context.user_id,
'project_id': context.project_id,
'status': c_fields.GroupStatus.CREATING,
'name': name,
'description': description,
'group_snapshot_id': group_snapshot_id,
'source_group_id': source_group_id,
}
group = None
try:
group = objects.Group(context=context, **kwargs)
group.create(group_snapshot_id=group_snapshot_id,
source_group_id=source_group_id)
except exception.GroupNotFound:
with excutils.save_and_reraise_exception():
LOG.error(_LE("Source Group %(source_group)s not found when "
"creating group %(group)s from "
"source."),
{'group': name, 'source_group': source_group_id})
except exception.GroupSnapshotNotFound:
with excutils.save_and_reraise_exception():
LOG.error(_LE("Group snapshot %(group_snap)s not found when "
"creating group %(group)s from source."),
{'group': name, 'group_snap': group_snapshot_id})
except Exception:
with excutils.save_and_reraise_exception():
LOG.error(_LE("Error occurred when creating group"
" %(group)s from group_snapshot %(grp_snap)s."),
{'group': name, 'grp_snap': group_snapshot_id})
# Update quota for groups
self.update_quota(context, group, 1)
if not group.host:
msg = _("No host to create group %s.") % group.id
LOG.error(msg)
raise exception.InvalidGroup(reason=msg)
if group_snapshot_id:
self._create_group_from_group_snapshot(context, group,
group_snapshot_id)
elif source_group_id:
self._create_group_from_source_group(context, group,
source_group_id)
return group
def _create_group_from_group_snapshot(self, context, group,
group_snapshot_id):
try:
group_snapshot = objects.GroupSnapshot.get_by_id(
context, group_snapshot_id)
snapshots = objects.SnapshotList.get_all_for_group_snapshot(
context, group_snapshot.id)
if not snapshots:
msg = _("Group snapshot is empty. No group will be created.")
raise exception.InvalidGroup(reason=msg)
for snapshot in snapshots:
kwargs = {}
kwargs['availability_zone'] = group.availability_zone
kwargs['group_snapshot'] = group_snapshot
kwargs['group'] = group
kwargs['snapshot'] = snapshot
volume_type_id = snapshot.volume_type_id
if volume_type_id:
kwargs['volume_type'] = volume_types.get_volume_type(
context, volume_type_id)
# Create group volume_type mapping entries
try:
db.group_volume_type_mapping_create(context, group.id,
volume_type_id)
except exception.GroupVolumeTypeMappingExists:
# Only need to create one group volume_type mapping
# entry for the same combination, skipping.
LOG.info(_LI("A mapping entry already exists for group"
" %(grp)s and volume type %(vol_type)s. "
"Do not need to create again."),
{'grp': group.id,
'vol_type': volume_type_id})
pass
# Since group snapshot is passed in, the following call will
# create a db entry for the volume, but will not call the
# volume manager to create a real volume in the backend yet.
# If error happens, taskflow will handle rollback of quota
# and removal of volume entry in the db.
try:
self.volume_api.create(context,
snapshot.volume_size,
None,
None,
**kwargs)
except exception.CinderException:
with excutils.save_and_reraise_exception():
LOG.error(_LE("Error occurred when creating volume "
"entry from snapshot in the process of "
"creating group %(group)s "
"from group snapshot %(group_snap)s."),
{'group': group.id,
'group_snap': group_snapshot.id})
except Exception:
with excutils.save_and_reraise_exception():
try:
group.destroy()
finally:
LOG.error(_LE("Error occurred when creating group "
"%(group)s from group snapshot "
"%(group_snap)s."),
{'group': group.id,
'group_snap': group_snapshot.id})
volumes = objects.VolumeList.get_all_by_generic_group(context,
group.id)
for vol in volumes:
# Update the host field for the volume.
vol.host = group.host
vol.save()
self.volume_rpcapi.create_group_from_src(
context, group, group_snapshot)
def _create_group_from_source_group(self, context, group,
source_group_id):
try:
source_group = objects.Group.get_by_id(context,
source_group_id)
source_vols = objects.VolumeList.get_all_by_generic_group(
context, source_group.id)
if not source_vols:
msg = _("Source Group is empty. No group "
"will be created.")
raise exception.InvalidGroup(reason=msg)
for source_vol in source_vols:
kwargs = {}
kwargs['availability_zone'] = group.availability_zone
kwargs['source_group'] = source_group
kwargs['group'] = group
kwargs['source_volume'] = source_vol
volume_type_id = source_vol.volume_type_id
if volume_type_id:
kwargs['volume_type'] = volume_types.get_volume_type(
context, volume_type_id)
# Create group volume_type mapping entries
try:
db.group_volume_type_mapping_create(context, group.id,
volume_type_id)
except exception.GroupVolumeTypeMappingExists:
# Only need to create one group volume_type mapping
# entry for the same combination, skipping.
LOG.info(_LI("A mapping entry already exists for group"
" %(grp)s and volume type %(vol_type)s. "
"Do not need to create again."),
{'grp': group.id,
'vol_type': volume_type_id})
pass
# Since source_group is passed in, the following call will
# create a db entry for the volume, but will not call the
# volume manager to create a real volume in the backend yet.
# If error happens, taskflow will handle rollback of quota
# and removal of volume entry in the db.
try:
self.volume_api.create(context,
source_vol.size,
None,
None,
**kwargs)
except exception.CinderException:
with excutils.save_and_reraise_exception():
LOG.error(_LE("Error occurred when creating cloned "
"volume in the process of creating "
"group %(group)s from "
"source group %(source_group)s."),
{'group': group.id,
'source_group': source_group.id})
except Exception:
with excutils.save_and_reraise_exception():
try:
group.destroy()
finally:
LOG.error(_LE("Error occurred when creating "
"group %(group)s from source group "
"%(source_group)s."),
{'group': group.id,
'source_group': source_group.id})
volumes = objects.VolumeList.get_all_by_generic_group(context,
group.id)
for vol in volumes:
# Update the host field for the volume.
vol.host = group.host
vol.save()
self.volume_rpcapi.create_group_from_src(context, group,
None, source_group)
def _cast_create_group(self, context, group,
group_spec,
request_spec_list,
@@ -542,3 +749,90 @@ class API(base.Base):
limit=limit, offset=offset, sort_keys=sort_keys,
sort_dirs=sort_dirs)
return groups
def create_group_snapshot(self, context, group, name, description):
options = {'group_id': group.id,
'user_id': context.user_id,
'project_id': context.project_id,
'status': "creating",
'name': name,
'description': description}
group_snapshot = None
group_snapshot_id = None
try:
group_snapshot = objects.GroupSnapshot(context, **options)
group_snapshot.create()
group_snapshot_id = group_snapshot.id
snap_name = group_snapshot.name
snap_desc = group_snapshot.description
with group.obj_as_admin():
self.volume_api.create_snapshots_in_db(
context, group.volumes, snap_name, snap_desc,
None, group_snapshot_id)
except Exception:
with excutils.save_and_reraise_exception():
try:
# If the group_snapshot has been created
if group_snapshot.obj_attr_is_set('id'):
group_snapshot.destroy()
finally:
LOG.error(_LE("Error occurred when creating group_snapshot"
" %s."), group_snapshot_id)
self.volume_rpcapi.create_group_snapshot(context, group_snapshot)
return group_snapshot
def delete_group_snapshot(self, context, group_snapshot, force=False):
check_policy(context, 'delete_group_snapshot')
values = {'status': 'deleting'}
expected = {'status': ('available', 'error')}
filters = [~db.group_creating_from_src(
group_snapshot_id=group_snapshot.id)]
res = group_snapshot.conditional_update(values, expected, filters)
if not res:
msg = _('GroupSnapshot status must be available or error, and no '
'Group can be currently using it as source for its '
'creation.')
raise exception.InvalidGroupSnapshot(reason=msg)
snapshots = objects.SnapshotList.get_all_for_group_snapshot(
context, group_snapshot.id)
# TODO(xyang): Add a new db API to update all snapshots statuses
# in one db API call.
for snap in snapshots:
snap.status = c_fields.SnapshotStatus.DELETING
snap.save()
self.volume_rpcapi.delete_group_snapshot(context.elevated(),
group_snapshot)
def update_group_snapshot(self, context, group_snapshot, fields):
check_policy(context, 'update_group_snapshot')
group_snapshot.update(fields)
group_snapshot.save()
def get_group_snapshot(self, context, group_snapshot_id):
check_policy(context, 'get_group_snapshot')
group_snapshots = objects.GroupSnapshot.get_by_id(context,
group_snapshot_id)
return group_snapshots
def get_all_group_snapshots(self, context, search_opts=None):
check_policy(context, 'get_all_group_snapshots')
search_opts = search_opts or {}
if context.is_admin and 'all_tenants' in search_opts:
# Need to remove all_tenants to pass the filtering below.
del search_opts['all_tenants']
group_snapshots = objects.GroupSnapshotList.get_all(context,
search_opts)
else:
group_snapshots = objects.GroupSnapshotList.get_all_by_project(
context.elevated(), context.project_id, search_opts)
return group_snapshots