CG API changes for migrating CGs

CG APIs work as follows:
 * Create CG - Create only in groups table
 * Modify CG - Modify in CG table if CG in CG table, otherwise modify
               in groups table.
 * Delete CG - Delete from CG or groups table depending on where it is
 * List CG - Check both CG and groups tables
 * List CG snapshots - Check both CG and groups tables
 * Show CG - Check both tables
 * Show CG snapshot - Check both tables
 * Create CG snapshot - Create either in CG or groups table depending on
                        the CG.
 * Create CG from source - Create in either CG or groups table
                           depending on the source.
 * Create volume - Add volume either to CG or group

Additional notes:
 * default_cgsnapshot_type is reserved for migrating CGs.
 * Group APIs will only write/read in/from the groups table.
 * Group APIs won't work on groups with default_cgsnapshot_type.
 * Groups with default_cgsnapshot_type can only be operated by CG APIs.
 * After CG tables are removed, we'll allow default_cgsnapshot_type
   to be used by group APIs.

Partial-Implements: blueprint generic-volume-group
Change-Id: Idd88a5c9587023a56231de42ce59d672e9600770
This commit is contained in:
xing-yang 2016-11-13 20:58:15 -05:00
parent 9be53e1449
commit 44ebdd2252
27 changed files with 594 additions and 115 deletions

View File

@ -24,9 +24,14 @@ from cinder.api import common
from cinder.api import extensions
from cinder.api.openstack import wsgi
from cinder.api.views import cgsnapshots as cgsnapshot_views
from cinder import consistencygroup as consistencygroupAPI
from cinder import consistencygroup as consistencygroup_api
from cinder import exception
from cinder import group as group_api
from cinder.i18n import _, _LI
from cinder.objects import cgsnapshot as cgsnap_obj
from cinder.objects import consistencygroup as cg_obj
from cinder.objects import group as grp_obj
from cinder.objects import group_snapshot as grpsnap_obj
LOG = logging.getLogger(__name__)
@ -37,7 +42,8 @@ class CgsnapshotsController(wsgi.Controller):
_view_builder_class = cgsnapshot_views.ViewBuilder
def __init__(self):
self.cgsnapshot_api = consistencygroupAPI.API()
self.cgsnapshot_api = consistencygroup_api.API()
self.group_snapshot_api = group_api.API()
super(CgsnapshotsController, self).__init__()
def show(self, req, id):
@ -46,9 +52,7 @@ class CgsnapshotsController(wsgi.Controller):
context = req.environ['cinder.context']
# Not found exception will be handled at the wsgi level
cgsnapshot = self.cgsnapshot_api.get_cgsnapshot(
context,
cgsnapshot_id=id)
cgsnapshot = self._get_cgsnapshot(context, id)
return self._view_builder.detail(req, cgsnapshot)
@ -60,14 +64,21 @@ class CgsnapshotsController(wsgi.Controller):
LOG.info(_LI('Delete cgsnapshot with id: %s'), id)
try:
cgsnapshot = self.cgsnapshot_api.get_cgsnapshot(
context,
cgsnapshot_id=id)
self.cgsnapshot_api.delete_cgsnapshot(context, cgsnapshot)
except exception.CgSnapshotNotFound:
cgsnapshot = self._get_cgsnapshot(context, id)
if isinstance(cgsnapshot, cgsnap_obj.CGSnapshot):
self.cgsnapshot_api.delete_cgsnapshot(context, cgsnapshot)
elif isinstance(cgsnapshot, grpsnap_obj.GroupSnapshot):
self.group_snapshot_api.delete_group_snapshot(
context, cgsnapshot)
else:
msg = _("Group snapshot '%s' not found.") % id
raise exc.HTTPNotFound(explanation=msg)
except (exception.CgSnapshotNotFound,
exception.GroupSnapshotNotFound):
# Not found exception will be handled at the wsgi level
raise
except exception.InvalidCgSnapshot as e:
except (exception.InvalidCgSnapshot,
exception.InvalidGroupSnapshot) as e:
raise exc.HTTPBadRequest(explanation=six.text_type(e))
except Exception:
msg = _("Failed cgsnapshot")
@ -83,16 +94,54 @@ class CgsnapshotsController(wsgi.Controller):
"""Returns a detailed list of cgsnapshots."""
return self._get_cgsnapshots(req, is_detail=True)
def _get_cg(self, context, id):
# Not found exception will be handled at the wsgi level
try:
consistencygroup = self.cgsnapshot_api.get(
context,
group_id=id)
except exception.ConsistencyGroupNotFound:
consistencygroup = self.group_snapshot_api.get(
context, group_id=id)
return consistencygroup
def _get_cgsnapshot(self, context, id):
# Not found exception will be handled at the wsgi level
try:
cgsnapshot = self.cgsnapshot_api.get_cgsnapshot(
context,
cgsnapshot_id=id)
except exception.CgSnapshotNotFound:
cgsnapshot = self.group_snapshot_api.get_group_snapshot(
context,
group_snapshot_id=id)
return cgsnapshot
def _get_cgsnapshots(self, req, is_detail):
"""Returns a list of cgsnapshots, transformed through view builder."""
context = req.environ['cinder.context']
cgsnapshots = self.cgsnapshot_api.get_all_cgsnapshots(context)
limited_list = common.limited(cgsnapshots, req)
cgsnap_limited_list = common.limited(cgsnapshots, req)
grp_snapshots = self.group_snapshot_api.get_all_group_snapshots(
context)
grpsnap_limited_list = common.limited(grp_snapshots, req)
if is_detail:
cgsnapshots = self._view_builder.detail_list(req, limited_list)
cgsnapshots = self._view_builder.detail_list(
req, cgsnap_limited_list)
grp_snapshots = self._view_builder.detail_list(
req, grpsnap_limited_list)
else:
cgsnapshots = self._view_builder.summary_list(req, limited_list)
cgsnapshots = self._view_builder.summary_list(
req, cgsnap_limited_list)
grp_snapshots = self._view_builder.summary_list(
req, grpsnap_limited_list)
cgsnapshots['cgsnapshots'] = (cgsnapshots['cgsnapshots'] +
grp_snapshots['cgsnapshots'])
return cgsnapshots
@wsgi.response(202)
@ -112,7 +161,7 @@ class CgsnapshotsController(wsgi.Controller):
raise exc.HTTPBadRequest(explanation=msg)
# Not found exception will be handled at the wsgi level
group = self.cgsnapshot_api.get(context, group_id)
group = self._get_cg(context, group_id)
name = cgsnapshot.get('name', None)
description = cgsnapshot.get('description', None)
@ -122,10 +171,21 @@ class CgsnapshotsController(wsgi.Controller):
context=context)
try:
new_cgsnapshot = self.cgsnapshot_api.create_cgsnapshot(
context, group, name, description)
if isinstance(group, cg_obj.ConsistencyGroup):
new_cgsnapshot = self.cgsnapshot_api.create_cgsnapshot(
context, group, name, description)
elif isinstance(group, grp_obj.Group):
new_cgsnapshot = self.group_snapshot_api.create_group_snapshot(
context, group, name, description)
else:
msg = _("Group %s not found.") % group.id
raise exc.HTTPNotFound(explanation=msg)
# Not found exception will be handled at the wsgi level
except exception.InvalidCgSnapshot as error:
except (exception.InvalidCgSnapshot,
exception.InvalidConsistencyGroup,
exception.InvalidGroup,
exception.InvalidGroupSnapshot,
exception.InvalidVolume) as error:
raise exc.HTTPBadRequest(explanation=error.msg)
retval = self._view_builder.summary(req, new_cgsnapshot)

View File

@ -24,9 +24,15 @@ from cinder.api import common
from cinder.api import extensions
from cinder.api.openstack import wsgi
from cinder.api.views import consistencygroups as consistencygroup_views
from cinder import consistencygroup as consistencygroupAPI
from cinder import consistencygroup as consistencygroup_api
from cinder import exception
from cinder import group as group_api
from cinder.i18n import _, _LI
from cinder.objects import cgsnapshot as cgsnap_obj
from cinder.objects import consistencygroup as cg_obj
from cinder.objects import group as grp_obj
from cinder.objects import group_snapshot as grpsnap_obj
from cinder.volume import group_types
LOG = logging.getLogger(__name__)
@ -37,7 +43,8 @@ class ConsistencyGroupsController(wsgi.Controller):
_view_builder_class = consistencygroup_views.ViewBuilder
def __init__(self):
self.consistencygroup_api = consistencygroupAPI.API()
self.consistencygroup_api = consistencygroup_api.API()
self.group_api = group_api.API()
super(ConsistencyGroupsController, self).__init__()
def show(self, req, id):
@ -46,9 +53,7 @@ class ConsistencyGroupsController(wsgi.Controller):
context = req.environ['cinder.context']
# Not found exception will be handled at the wsgi level
consistencygroup = self.consistencygroup_api.get(
context,
group_id=id)
consistencygroup = self._get(context, id)
return self._view_builder.detail(req, consistencygroup)
@ -74,8 +79,14 @@ class ConsistencyGroupsController(wsgi.Controller):
LOG.info(_LI('Delete consistency group with id: %s'), id)
try:
group = self.consistencygroup_api.get(context, id)
self.consistencygroup_api.delete(context, group, force)
group = self._get(context, id)
if isinstance(group, cg_obj.ConsistencyGroup):
self.consistencygroup_api.delete(context, group, force)
elif isinstance(group, grp_obj.Group):
self.group_api.delete(context, group, force)
else:
msg = _("Group '%s' not found.") % id
raise exc.HTTPNotFound(explanation=msg)
# Not found exception will be handled at the wsgi level
except exception.InvalidConsistencyGroup as error:
raise exc.HTTPBadRequest(explanation=error.msg)
@ -90,6 +101,30 @@ class ConsistencyGroupsController(wsgi.Controller):
"""Returns a detailed list of consistency groups."""
return self._get_consistencygroups(req, is_detail=True)
def _get(self, context, id):
# Not found exception will be handled at the wsgi level
try:
consistencygroup = self.consistencygroup_api.get(
context,
group_id=id)
except exception.ConsistencyGroupNotFound:
consistencygroup = self.group_api.get(context, group_id=id)
return consistencygroup
def _get_cgsnapshot(self, context, id):
# Not found exception will be handled at the wsgi level
try:
cgsnapshot = self.consistencygroup_api.get_cgsnapshot(
context,
cgsnapshot_id=id)
except exception.CgSnapshotNotFound:
cgsnapshot = self.group_api.get_group_snapshot(
context,
group_snapshot_id=id)
return cgsnapshot
def _get_consistencygroups(self, req, is_detail):
"""Returns a list of consistency groups through view builder."""
context = req.environ['cinder.context']
@ -101,12 +136,22 @@ class ConsistencyGroupsController(wsgi.Controller):
context, filters=filters, marker=marker, limit=limit,
offset=offset, sort_keys=sort_keys, sort_dirs=sort_dirs)
groups = self.group_api.get_all(
context, filters=filters, marker=marker, limit=limit,
offset=offset, sort_keys=sort_keys, sort_dirs=sort_dirs)
if is_detail:
consistencygroups = self._view_builder.detail_list(
req, consistencygroups)
groups = self._view_builder.detail_list(req, groups)
else:
consistencygroups = self._view_builder.summary_list(
req, consistencygroups)
groups = self._view_builder.summary_list(req, groups)
consistencygroups['consistencygroups'] = (
consistencygroups['consistencygroups'] +
groups['consistencygroups'])
return consistencygroups
@wsgi.response(202)
@ -125,20 +170,30 @@ class ConsistencyGroupsController(wsgi.Controller):
msg = _("volume_types must be provided to create "
"consistency group %(name)s.") % {'name': name}
raise exc.HTTPBadRequest(explanation=msg)
volume_types = volume_types.rstrip(',').split(',')
availability_zone = consistencygroup.get('availability_zone', None)
group_type = group_types.get_default_cgsnapshot_type()
if not group_type:
msg = (_('Group type %s not found. Rerun migration script to '
'create the default cgsnapshot type.') %
group_types.DEFAULT_CGSNAPSHOT_TYPE)
raise exc.HTTPBadRequest(explanation=msg)
LOG.info(_LI("Creating consistency group %(name)s."),
{'name': name})
try:
new_consistencygroup = self.consistencygroup_api.create(
context, name, description, volume_types,
new_consistencygroup = self.group_api.create(
context, name, description, group_type['id'], volume_types,
availability_zone=availability_zone)
# Not found exception will be handled at the wsgi level
except exception.InvalidConsistencyGroup as error:
raise exc.HTTPBadRequest(explanation=error.msg)
except exception.InvalidVolumeType as error:
except (exception.InvalidConsistencyGroup,
exception.InvalidGroup,
exception.InvalidVolumeType,
exception.ObjectActionError) as error:
raise exc.HTTPBadRequest(explanation=error.msg)
except exception.NotFound:
# Not found exception will be handled at the wsgi level
raise
retval = self._view_builder.summary(req, new_consistencygroup)
return retval
@ -183,8 +238,25 @@ class ConsistencyGroupsController(wsgi.Controller):
{'name': name, 'source_cgid': source_cgid})
try:
new_consistencygroup = self.consistencygroup_api.create_from_src(
context, name, description, cgsnapshot_id, source_cgid)
src_grp = None
src_snap = None
if source_cgid:
src_grp = self._get(context, source_cgid)
if cgsnapshot_id:
src_snap = self._get_cgsnapshot(context, cgsnapshot_id)
if (isinstance(src_grp, cg_obj.ConsistencyGroup) or
isinstance(src_snap, cgsnap_obj.CGSnapshot)):
new_group = self.consistencygroup_api.create_from_src(
context, name, description, cgsnapshot_id, source_cgid)
elif (isinstance(src_grp, grp_obj.Group) or
isinstance(src_snap, grpsnap_obj.GroupSnapshot)):
new_group = self.group_api.create_from_src(
context, name, description, cgsnapshot_id, source_cgid)
else:
msg = (_("Source CGSnapshot %(cgsnap)s or source CG %(cg)s "
"not found.") % {'cgsnap': cgsnapshot_id,
'cg': source_cgid})
raise exc.HTTPNotFound(explanation=msg)
except exception.InvalidConsistencyGroup as error:
raise exc.HTTPBadRequest(explanation=error.msg)
except exception.NotFound:
@ -193,7 +265,7 @@ class ConsistencyGroupsController(wsgi.Controller):
except exception.CinderException as error:
raise exc.HTTPBadRequest(explanation=error.msg)
retval = self._view_builder.summary(req, new_consistencygroup)
retval = self._view_builder.summary(req, new_group)
return retval
def _check_update_parameters(self, name, description, add_volumes,
@ -215,11 +287,17 @@ class ConsistencyGroupsController(wsgi.Controller):
'add_volumes': add_volumes,
'remove_volumes': remove_volumes})
# Handle relevant exceptions at wsgi level
group = self.consistencygroup_api.get(context, id)
self.consistencygroup_api.update(context, group, name, description,
add_volumes, remove_volumes,
allow_empty)
group = self._get(context, id)
if isinstance(group, cg_obj.ConsistencyGroup):
self.consistencygroup_api.update(context, group, name, description,
add_volumes, remove_volumes,
allow_empty)
elif isinstance(group, grp_obj.Group):
self.group_api.update(context, group, name, description,
add_volumes, remove_volumes)
else:
msg = _("Group '%s' not found.") % id
raise exc.HTTPNotFound(explanation=msg)
def update(self, req, id, body):
"""Update the consistency group.

View File

@ -16,7 +16,9 @@
import six
from cinder.api import common
from cinder import group as group_api
from cinder.objects import fields
from cinder.volume import group_types
class ViewBuilder(common.ViewBuilder):
@ -92,6 +94,18 @@ class ViewBuilder(common.ViewBuilder):
if request.environ['cinder.context'].is_admin:
volume_ref['volume']['migration_status'] = (
volume.get('migration_status'))
# NOTE(xyang): Display group_id as consistencygroup_id in detailed
# view of the volume if group is converted from cg.
group_id = volume.get('group_id')
if group_id is not None:
# Not found exception will be handled at the wsgi level
ctxt = request.environ['cinder.context']
grp = group_api.API().get(ctxt, group_id)
cgsnap_type = group_types.get_default_cgsnapshot_type()
if grp.group_type_id == cgsnap_type['id']:
volume_ref['volume']['consistencygroup_id'] = group_id
return volume_ref
def _is_volume_encrypted(self, volume):

View File

@ -27,6 +27,7 @@ from cinder.api.openstack import wsgi
from cinder.api.v2.views import volumes as volume_views
from cinder import consistencygroup as consistencygroupAPI
from cinder import exception
from cinder import group as group_api
from cinder.i18n import _, _LI
from cinder.image import glance
from cinder import utils
@ -47,6 +48,7 @@ class VolumeController(wsgi.Controller):
def __init__(self, ext_mgr):
self.volume_api = cinder_volume.API()
self.consistencygroup_api = consistencygroupAPI.API()
self.group_api = group_api.API()
self.ext_mgr = ext_mgr
super(VolumeController, self).__init__()
@ -236,10 +238,14 @@ class VolumeController(wsgi.Controller):
consistencygroup_id = volume.get('consistencygroup_id')
if consistencygroup_id is not None:
# Not found exception will be handled at the wsgi level
kwargs['consistencygroup'] = \
self.consistencygroup_api.get(context,
consistencygroup_id)
try:
kwargs['consistencygroup'] = (
self.consistencygroup_api.get(context,
consistencygroup_id))
except exception.ConsistencyGroupNotFound:
# Not found exception will be handled at the wsgi level
kwargs['group'] = self.group_api.get(
context, consistencygroup_id)
else:
kwargs['consistencygroup'] = None

View File

@ -27,6 +27,7 @@ from cinder import exception
from cinder import group as group_api
from cinder.i18n import _, _LI
from cinder import rpc
from cinder.volume import group_types
LOG = logging.getLogger(__name__)
@ -42,6 +43,14 @@ class GroupSnapshotsController(wsgi.Controller):
self.group_snapshot_api = group_api.API()
super(GroupSnapshotsController, self).__init__()
def _check_default_cgsnapshot_type(self, group_type_id):
if group_types.is_default_cgsnapshot_type(group_type_id):
msg = (_("Group_type %(group_type)s is reserved for migrating "
"CGs to groups. Migrated group snapshots can only be "
"operated by CG snapshot APIs.")
% {'group_type': group_type_id})
raise exc.HTTPBadRequest(explanation=msg)
@wsgi.Controller.api_version(GROUP_SNAPSHOT_API_VERSION)
def show(self, req, id):
"""Return data about the given group_snapshot."""
@ -52,6 +61,8 @@ class GroupSnapshotsController(wsgi.Controller):
context,
group_snapshot_id=id)
self._check_default_cgsnapshot_type(group_snapshot.group_type_id)
return self._view_builder.detail(req, group_snapshot)
@wsgi.Controller.api_version(GROUP_SNAPSHOT_API_VERSION)
@ -66,6 +77,7 @@ class GroupSnapshotsController(wsgi.Controller):
group_snapshot = self.group_snapshot_api.get_group_snapshot(
context,
group_snapshot_id=id)
self._check_default_cgsnapshot_type(group_snapshot.group_type_id)
self.group_snapshot_api.delete_group_snapshot(context,
group_snapshot)
except exception.InvalidGroupSnapshot as e:
@ -102,7 +114,20 @@ class GroupSnapshotsController(wsgi.Controller):
else:
group_snapshots = self._view_builder.summary_list(req,
limited_list)
return group_snapshots
new_group_snapshots = []
for grp_snap in group_snapshots['group_snapshots']:
try:
# Only show group snapshots not migrated from CG snapshots
self._check_default_cgsnapshot_type(grp_snap['group_type_id'])
if not is_detail:
grp_snap.pop('group_type_id', None)
new_group_snapshots.append(grp_snap)
except exc.HTTPBadRequest:
# Skip migrated group snapshot
pass
return {'group_snapshots': new_group_snapshots}
@wsgi.Controller.api_version(GROUP_SNAPSHOT_API_VERSION)
@wsgi.response(202)
@ -122,7 +147,7 @@ class GroupSnapshotsController(wsgi.Controller):
raise exc.HTTPBadRequest(explanation=msg)
group = self.group_snapshot_api.get(context, group_id)
self._check_default_cgsnapshot_type(group.group_type_id)
name = group_snapshot.get('name', None)
description = group_snapshot.get('description', None)

View File

@ -16,6 +16,7 @@
from oslo_log import log as logging
from oslo_utils import strutils
from oslo_utils import uuidutils
import webob
from webob import exc
@ -25,6 +26,7 @@ from cinder.api.v3.views import groups as views_groups
from cinder import exception
from cinder import group as group_api
from cinder.i18n import _, _LI
from cinder.volume import group_types
LOG = logging.getLogger(__name__)
@ -41,6 +43,13 @@ class GroupsController(wsgi.Controller):
self.group_api = group_api.API()
super(GroupsController, self).__init__()
def _check_default_cgsnapshot_type(self, group_type_id):
if group_types.is_default_cgsnapshot_type(group_type_id):
msg = _("Group_type %(group_type)s is reserved for migrating "
"CGs to groups. Migrated group can only be operated by "
"CG APIs.") % {'group_type': group_type_id}
raise exc.HTTPBadRequest(explanation=msg)
@wsgi.Controller.api_version(GROUP_API_VERSION)
def show(self, req, id):
"""Return data about the given group."""
@ -52,6 +61,8 @@ class GroupsController(wsgi.Controller):
context,
group_id=id)
self._check_default_cgsnapshot_type(group.group_type_id)
return self._view_builder.detail(req, group)
@wsgi.Controller.api_version(GROUP_API_VERSION)
@ -85,6 +96,7 @@ class GroupsController(wsgi.Controller):
try:
group = self.group_api.get(context, id)
self._check_default_cgsnapshot_type(group.group_type_id)
self.group_api.delete(context, group, del_vol)
except exception.GroupNotFound:
# Not found exception will be handled at the wsgi level
@ -115,12 +127,22 @@ class GroupsController(wsgi.Controller):
context, filters=filters, marker=marker, limit=limit,
offset=offset, sort_keys=sort_keys, sort_dirs=sort_dirs)
new_groups = []
for grp in groups:
try:
# Only show groups not migrated from CGs
self._check_default_cgsnapshot_type(grp.group_type_id)
new_groups.append(grp)
except exc.HTTPBadRequest:
# Skip migrated group
pass
if is_detail:
groups = self._view_builder.detail_list(
req, groups)
req, new_groups)
else:
groups = self._view_builder.summary_list(
req, groups)
req, new_groups)
return groups
@wsgi.Controller.api_version(GROUP_API_VERSION)
@ -140,6 +162,11 @@ class GroupsController(wsgi.Controller):
msg = _("group_type must be provided to create "
"group %(name)s.") % {'name': name}
raise exc.HTTPBadRequest(explanation=msg)
if not uuidutils.is_uuid_like(group_type):
req_group_type = group_types.get_group_type_by_name(context,
group_type)
group_type = req_group_type.id
self._check_default_cgsnapshot_type(group_type)
volume_types = group.get('volume_types')
if not volume_types:
msg = _("volume_types must be provided to create "
@ -196,16 +223,24 @@ class GroupsController(wsgi.Controller):
"source.") % {'name': name}
raise exc.HTTPBadRequest(explanation=msg)
group_type_id = None
if group_snapshot_id:
LOG.info(_LI("Creating group %(name)s from group_snapshot "
"%(snap)s."),
{'name': name, 'snap': group_snapshot_id},
context=context)
grp_snap = self.group_api.get_group_snapshot(context,
group_snapshot_id)
group_type_id = grp_snap.group_type_id
elif source_group_id:
LOG.info(_LI("Creating group %(name)s from "
"source group %(source_group_id)s."),
{'name': name, 'source_group_id': source_group_id},
context=context)
source_group = self.group_api.get(context, source_group_id)
group_type_id = source_group.group_type_id
self._check_default_cgsnapshot_type(group_type_id)
try:
new_group = self.group_api.create_from_src(
@ -274,6 +309,7 @@ class GroupsController(wsgi.Controller):
try:
group = self.group_api.get(context, id)
self._check_default_cgsnapshot_type(group.group_type_id)
self.group_api.update(
context, group, name, description,
add_volumes, remove_volumes)

View File

@ -38,7 +38,10 @@ class ViewBuilder(common.ViewBuilder):
return {
'group_snapshot': {
'id': group_snapshot.id,
'name': group_snapshot.name
'name': group_snapshot.name,
# NOTE(xyang): group_type_id is added for migrating CGs
# to generic volume groups
'group_type_id': group_snapshot.group_type_id,
}
}
@ -48,6 +51,7 @@ class ViewBuilder(common.ViewBuilder):
'group_snapshot': {
'id': group_snapshot.id,
'group_id': group_snapshot.group_id,
'group_type_id': group_snapshot.group_type_id,
'status': group_snapshot.status,
'created_at': group_snapshot.created_at,
'name': group_snapshot.name,

View File

@ -194,10 +194,14 @@ class VolumeController(volumes_v2.VolumeController):
consistencygroup_id = volume.get('consistencygroup_id')
if consistencygroup_id is not None:
# Not found exception will be handled at the wsgi level
kwargs['consistencygroup'] = (
self.consistencygroup_api.get(context,
consistencygroup_id))
try:
kwargs['consistencygroup'] = (
self.consistencygroup_api.get(context,
consistencygroup_id))
except exception.ConsistencyGroupNotFound:
# Not found exception will be handled at the wsgi level
kwargs['group'] = self.group_api.get(
context, consistencygroup_id)
else:
kwargs['consistencygroup'] = None

View File

@ -44,10 +44,20 @@ class ViewBuilder(common.ViewBuilder):
def detail(self, request, cgsnapshot):
"""Detailed view of a single cgsnapshot."""
try:
group_id = cgsnapshot.consistencygroup_id
except AttributeError:
try:
group_id = cgsnapshot.group_id
except AttributeError:
group_id = None
else:
group_id = None
return {
'cgsnapshot': {
'id': cgsnapshot.id,
'consistencygroup_id': cgsnapshot.consistencygroup_id,
'consistencygroup_id': group_id,
'status': cgsnapshot.status,
'created_at': cgsnapshot.created_at,
'name': cgsnapshot.name,

View File

@ -44,11 +44,16 @@ class ViewBuilder(common.ViewBuilder):
def detail(self, request, consistencygroup):
"""Detailed view of a single consistency group."""
if consistencygroup.volume_type_id:
volume_types = consistencygroup.volume_type_id.split(",")
try:
volume_types = (consistencygroup.volume_type_id.split(",")
if consistencygroup.volume_type_id else [])
volume_types = [type_id for type_id in volume_types if type_id]
else:
volume_types = []
except AttributeError:
try:
volume_types = [v_type.id for v_type in
consistencygroup.volume_types]
except AttributeError:
volume_types = []
return {
'consistencygroup': {

View File

@ -5502,6 +5502,8 @@ def group_create(context, values, group_snapshot_id=None,
values.pop('group_type_id', None)
values.pop('availability_zone', None)
values.pop('host', None)
# NOTE(xyang): Save volume_type_ids to update later.
volume_type_ids = values.pop('volume_type_ids', [])
sel = session.query(group_model.group_type_id,
group_model.availability_zone,
@ -5521,6 +5523,12 @@ def group_create(context, values, group_snapshot_id=None,
group_id=source_group_id)
raise exception.GroupSnapshotNotFound(
group_snapshot_id=group_snapshot_id)
for item in volume_type_ids:
mapping = models.GroupVolumeTypeMapping()
mapping['volume_type_id'] = item
mapping['group_id'] = values['id']
session.add(mapping)
else:
mappings = []
for item in values.get('volume_type_ids') or []:
@ -5528,7 +5536,6 @@ def group_create(context, values, group_snapshot_id=None,
mapping['volume_type_id'] = item
mapping['group_id'] = values['id']
mappings.append(mapping)
values['volume_types'] = mappings
group = group_model()

View File

@ -190,6 +190,19 @@ class API(base.Base):
group_snapshot_id=None, source_group_id=None):
check_policy(context, 'create')
# Populate group_type_id and volume_type_ids
group_type_id = None
volume_type_ids = []
if group_snapshot_id:
grp_snap = self.get_group_snapshot(context, group_snapshot_id)
group_type_id = grp_snap.group_type_id
grp_snap_src_grp = self.get(context, grp_snap.group_id)
volume_type_ids = [vt.id for vt in grp_snap_src_grp.volume_types]
elif source_group_id:
source_group = self.get(context, source_group_id)
group_type_id = source_group.group_type_id
volume_type_ids = [vt.id for vt in source_group.volume_types]
kwargs = {
'user_id': context.user_id,
'project_id': context.project_id,
@ -198,6 +211,8 @@ class API(base.Base):
'description': description,
'group_snapshot_id': group_snapshot_id,
'source_group_id': source_group_id,
'group_type_id': group_type_id,
'volume_type_ids': volume_type_ids,
}
group = None

View File

@ -123,6 +123,7 @@ OBJ_VERSIONS.add('1.15', {'Volume': '1.6', 'Snapshot': '1.2'})
OBJ_VERSIONS.add('1.16', {'BackupDeviceInfo': '1.0'})
OBJ_VERSIONS.add('1.17', {'VolumeAttachment': '1.1'})
OBJ_VERSIONS.add('1.18', {'Snapshot': '1.3'})
OBJ_VERSIONS.add('1.19', {'ConsistencyGroup': '1.4', 'CGSnapshot': '1.1'})
class CinderObjectRegistry(base.VersionedObjectRegistry):

View File

@ -23,7 +23,9 @@ from oslo_versionedobjects import fields
@base.CinderObjectRegistry.register
class CGSnapshot(base.CinderPersistentObject, base.CinderObject,
base.CinderObjectDictCompat):
VERSION = '1.0'
# Version 1.0: Initial version
# Version 1.1: Added from_group_snapshot
VERSION = '1.1'
OPTIONAL_FIELDS = ['consistencygroup', 'snapshots']
@ -85,6 +87,16 @@ class CGSnapshot(base.CinderPersistentObject, base.CinderObject,
db_cgsnapshots = db.cgsnapshot_create(self._context, updates)
self._from_db_object(self._context, self, db_cgsnapshots)
def from_group_snapshot(self, group_snapshot):
"""Convert a generic volume group object to a cg object."""
self.id = group_snapshot.id
self.consistencygroup_id = group_snapshot.group_id
self.user_id = group_snapshot.user_id
self.project_id = group_snapshot.project_id
self.name = group_snapshot.name
self.description = group_snapshot.description
self.status = group_snapshot.status
def obj_load_attr(self, attrname):
if attrname not in self.OPTIONAL_FIELDS:
raise exception.ObjectActionError(

View File

@ -30,7 +30,8 @@ class ConsistencyGroup(base.CinderPersistentObject, base.CinderObject,
# Version 1.1: Added cgsnapshots and volumes relationships
# Version 1.2: Changed 'status' field to use ConsistencyGroupStatusField
# Version 1.3: Added cluster fields
VERSION = '1.3'
# Version 1.4: Added from_group
VERSION = '1.4'
OPTIONAL_FIELDS = ('cgsnapshots', 'volumes', 'cluster')
@ -136,6 +137,23 @@ class ConsistencyGroup(base.CinderPersistentObject, base.CinderObject,
cg_id)
self._from_db_object(self._context, self, db_consistencygroups)
def from_group(self, group):
"""Convert a generic volume group object to a cg object."""
self.id = group.id
self.user_id = group.user_id
self.project_id = group.project_id
self.cluster_name = group.cluster_name
self.host = group.host
self.availability_zone = group.availability_zone
self.name = group.name
self.description = group.description
self.volume_type_id = ""
for v_type in group.volume_types:
self.volume_type_id += v_type.id + ","
self.status = group.status
self.cgsnapshot_id = group.group_snapshot_id
self.source_cgid = group.source_group_id
def obj_load_attr(self, attrname):
if attrname not in self.OPTIONAL_FIELDS:
raise exception.ObjectActionError(

View File

@ -85,7 +85,7 @@ class CgsnapshotsAPITestCase(test.TestCase):
self.assertEqual(404, res.status_int)
self.assertEqual(404, res_dict['itemNotFound']['code'])
self.assertEqual('CgSnapshot %s could not be found.' %
self.assertEqual('GroupSnapshot %s could not be found.' %
fake.WILL_NOT_BE_FOUND_ID,
res_dict['itemNotFound']['message'])
@ -410,7 +410,7 @@ class CgsnapshotsAPITestCase(test.TestCase):
self.assertEqual(404, res.status_int)
self.assertEqual(404, res_dict['itemNotFound']['code'])
self.assertEqual('CgSnapshot %s could not be found.' %
self.assertEqual('GroupSnapshot %s could not be found.' %
fake.WILL_NOT_BE_FOUND_ID,
res_dict['itemNotFound']['message'])

View File

@ -110,7 +110,7 @@ class ConsistencyGroupsAPITestCase(test.TestCase):
self.assertEqual(404, res.status_int)
self.assertEqual(404, res_dict['itemNotFound']['code'])
self.assertEqual('ConsistencyGroup %s could not be found.' %
self.assertEqual('Group %s could not be found.' %
fake.WILL_NOT_BE_FOUND_ID,
res_dict['itemNotFound']['message'])
@ -411,8 +411,12 @@ class ConsistencyGroupsAPITestCase(test.TestCase):
self.assertTrue(mock_validate.called)
group_id = res_dict['consistencygroup']['id']
cg = objects.ConsistencyGroup.get_by_id(self.ctxt,
group_id)
try:
cg = objects.ConsistencyGroup.get_by_id(self.ctxt,
group_id)
except exception.ConsistencyGroupNotFound:
cg = objects.Group.get_by_id(self.ctxt,
group_id)
cg.destroy()
def test_create_consistencygroup_with_no_body(self):
@ -524,7 +528,7 @@ class ConsistencyGroupsAPITestCase(test.TestCase):
self.assertEqual(404, res.status_int)
self.assertEqual(404, res_dict['itemNotFound']['code'])
self.assertEqual('ConsistencyGroup %s could not be found.' %
self.assertEqual('Group %s could not be found.' %
fake.WILL_NOT_BE_FOUND_ID,
res_dict['itemNotFound']['message'])

View File

@ -384,7 +384,7 @@ class VolumeApiTest(test.TestCase):
body = {"volume": vol}
req = fakes.HTTPRequest.blank('/v2/volumes')
# Raise 404 when consistency group is not found.
self.assertRaises(exception.ConsistencyGroupNotFound,
self.assertRaises(exception.GroupNotFound,
self.controller.create, req, body)
context = req.environ['cinder.context']

View File

@ -98,11 +98,14 @@ class GroupSnapshotsAPITestCase(test.TestCase):
group_id=group.id,
volume_type_id=fake.VOLUME_TYPE_ID)['id']
group_snapshot1 = utils.create_group_snapshot(
self.context, group_id=group.id)
self.context, group_id=group.id,
group_type_id=group.group_type_id)
group_snapshot2 = utils.create_group_snapshot(
self.context, group_id=group.id)
self.context, group_id=group.id,
group_type_id=group.group_type_id)
group_snapshot3 = utils.create_group_snapshot(
self.context, group_id=group.id)
self.context, group_id=group.id,
group_type_id=group.group_type_id)
req = fakes.HTTPRequest.blank('/v3/%s/group_snapshots' %
fake.PROJECT_ID,

View File

@ -830,17 +830,21 @@ class GroupsAPITestCase(test.TestCase):
self.mock_object(volume_api.API, "create", v3_fakes.fake_volume_create)
group = utils.create_group(self.ctxt,
group_type_id=fake.GROUP_TYPE_ID)
group_type_id=fake.GROUP_TYPE_ID,
volume_type_ids=[fake.VOLUME_TYPE_ID])
volume = utils.create_volume(
self.ctxt,
group_id=group.id)
group_id=group.id,
volume_type_id=fake.VOLUME_TYPE_ID)
group_snapshot = utils.create_group_snapshot(
self.ctxt, group_id=group.id)
self.ctxt, group_id=group.id,
group_type_id=group.group_type_id)
snapshot = utils.create_snapshot(
self.ctxt,
volume.id,
group_snapshot_id=group_snapshot.id,
status=fields.SnapshotStatus.AVAILABLE)
status=fields.SnapshotStatus.AVAILABLE,
volume_type_id=volume.volume_type_id)
test_grp_name = 'test grp'
body = {"create-from-src": {"name": test_grp_name,
@ -868,10 +872,12 @@ class GroupsAPITestCase(test.TestCase):
self.mock_object(volume_api.API, "create", v3_fakes.fake_volume_create)
source_grp = utils.create_group(self.ctxt,
group_type_id=fake.GROUP_TYPE_ID)
group_type_id=fake.GROUP_TYPE_ID,
volume_type_ids=[fake.VOLUME_TYPE_ID])
volume = utils.create_volume(
self.ctxt,
group_id=source_grp.id)
group_id=source_grp.id,
volume_type_id=fake.VOLUME_TYPE_ID)
test_grp_name = 'test cg'
body = {"create-from-src": {"name": test_grp_name,

View File

@ -493,28 +493,63 @@ class GroupAPITestCase(test.TestCase):
@mock.patch('cinder.group.api.API._create_group_from_group_snapshot')
@mock.patch('cinder.group.api.API._create_group_from_source_group')
@mock.patch('cinder.group.api.API.update_quota')
@mock.patch('cinder.objects.Group')
@mock.patch('cinder.objects.GroupSnapshot.get_by_id')
@mock.patch('cinder.objects.SnapshotList.get_all_for_group_snapshot')
@mock.patch('cinder.group.api.check_policy')
def test_create_from_src(self, mock_policy, mock_group, mock_update_quota,
mock_create_from_group, mock_create_from_snap):
def test_create_from_src(self, mock_policy, mock_snap_get_all,
mock_group_snap_get, mock_update_quota,
mock_create_from_group,
mock_create_from_snap):
name = "test_group"
description = "this is a test group"
grp = utils.create_group(self.ctxt, group_type_id = fake.GROUP_TYPE_ID,
volume_type_ids = [fake.VOLUME_TYPE_ID],
availability_zone = 'nova',
name = name, description = description,
status = fields.GroupStatus.CREATING,
group_snapshot_id = fake.GROUP_SNAPSHOT_ID,
source_group_id = fake.GROUP_ID)
mock_group.return_value = grp
status = fields.GroupStatus.AVAILABLE,)
ret_group = self.group_api.create_from_src(
self.ctxt, name, description,
group_snapshot_id = fake.GROUP_SNAPSHOT_ID,
source_group_id = None)
self.assertEqual(grp.obj_to_primitive(), ret_group.obj_to_primitive())
mock_create_from_snap.assert_called_once_with(
self.ctxt, grp, fake.GROUP_SNAPSHOT_ID)
vol1 = utils.create_volume(
self.ctxt,
availability_zone = 'nova',
volume_type_id = fake.VOLUME_TYPE_ID,
group_id = grp.id)
snap = utils.create_snapshot(self.ctxt, vol1.id,
volume_type_id = fake.VOLUME_TYPE_ID,
status = fields.SnapshotStatus.AVAILABLE)
mock_snap_get_all.return_value = [snap]
grp_snap = utils.create_group_snapshot(
self.ctxt, grp.id,
group_type_id = fake.GROUP_TYPE_ID,
status = fields.GroupStatus.AVAILABLE)
mock_group_snap_get.return_value = grp_snap
grp2 = utils.create_group(self.ctxt,
group_type_id = fake.GROUP_TYPE_ID,
volume_type_ids = [fake.VOLUME_TYPE_ID],
availability_zone = 'nova',
name = name, description = description,
status = fields.GroupStatus.CREATING,
group_snapshot_id = grp_snap.id)
with mock.patch('cinder.objects.Group') as mock_group:
mock_group.return_value = grp2
with mock.patch('cinder.objects.group.Group.create'):
ret_group = self.group_api.create_from_src(
self.ctxt, name, description,
group_snapshot_id = grp_snap.id,
source_group_id = None)
self.assertEqual(grp2.obj_to_primitive(),
ret_group.obj_to_primitive())
mock_create_from_snap.assert_called_once_with(
self.ctxt, grp2, grp_snap.id)
snap.destroy()
grp_snap.destroy()
vol1.destroy()
grp.destroy()
grp2.destroy()
@mock.patch('oslo_utils.timeutils.utcnow')
@mock.patch('cinder.objects.GroupSnapshot')

View File

@ -30,9 +30,9 @@ object_data = {
'CleanupRequest': '1.0-e7c688b893e1d5537ccf65cc3eb10a28',
'Cluster': '1.0-6f06e867c073e9d31722c53b0a9329b8',
'ClusterList': '1.0-15ecf022a68ddbb8c2a6739cfc9f8f5e',
'CGSnapshot': '1.0-3212ac2b4c2811b7134fb9ba2c49ff74',
'CGSnapshot': '1.1-3212ac2b4c2811b7134fb9ba2c49ff74',
'CGSnapshotList': '1.0-15ecf022a68ddbb8c2a6739cfc9f8f5e',
'ConsistencyGroup': '1.3-7bf01a79b82516639fc03cd3ab6d9c01',
'ConsistencyGroup': '1.4-7bf01a79b82516639fc03cd3ab6d9c01',
'ConsistencyGroupList': '1.1-15ecf022a68ddbb8c2a6739cfc9f8f5e',
'QualityOfServiceSpecs': '1.0-0b212e0a86ee99092229874e03207fe8',
'QualityOfServiceSpecsList': '1.0-1b54e51ad0fc1f3a8878f5010e7e16dc',

View File

@ -91,13 +91,13 @@ class VolumeRpcAPITestCase(test.TestCase):
generic_group = tests_utils.create_group(
self.context,
availability_zone=CONF.storage_availability_zone,
group_type_id='group_type1',
group_type_id=fake.GROUP_TYPE_ID,
host='fakehost@fakedrv#fakepool')
group_snapshot = tests_utils.create_group_snapshot(
self.context,
group_id=generic_group.id,
group_type_id='group_type1')
group_type_id=fake.GROUP_TYPE_ID)
cg = objects.ConsistencyGroup.get_by_id(self.context, cg.id)
cg2 = objects.ConsistencyGroup.get_by_id(self.context, cg2.id)

View File

@ -260,7 +260,8 @@ def create_group_snapshot(ctxt,
'status': status,
'name': name,
'description': description,
'group_id': group_id}
'group_id': group_id,
'group_type_id': group_type_id}
values.update(kwargs)
if recursive_create_if_needed and group_id:

View File

@ -157,6 +157,11 @@ def get_default_cgsnapshot_type():
return grp_type
def is_default_cgsnapshot_type(group_type_id):
cgsnap_type = get_default_cgsnapshot_type()
return group_type_id == cgsnap_type['id']
def get_group_type_specs(group_type_id, key=False):
group_type = get_group_type(context.get_admin_context(),
group_type_id)

View File

@ -68,6 +68,8 @@ from cinder.message import api as message_api
from cinder.message import defined_messages
from cinder.message import resource_types
from cinder import objects
from cinder.objects import cgsnapshot
from cinder.objects import consistencygroup
from cinder.objects import fields
from cinder import quota
from cinder import utils
@ -76,6 +78,7 @@ from cinder.volume import configuration as config
from cinder.volume.flows.manager import create_volume
from cinder.volume.flows.manager import manage_existing
from cinder.volume.flows.manager import manage_existing_snapshot
from cinder.volume import group_types
from cinder.volume import rpcapi as volume_rpcapi
from cinder.volume import utils as vol_utils
from cinder.volume import volume_types
@ -2481,8 +2484,14 @@ class VolumeManager(manager.CleanableManager,
model_update = self.driver.create_group(context,
group)
except NotImplementedError:
model_update = self._create_group_generic(context,
group)
cgsnap_type = group_types.get_default_cgsnapshot_type()
if group.group_type_id != cgsnap_type['id']:
model_update = self._create_group_generic(context,
group)
else:
cg, __ = self._convert_group_to_cg(group, [])
model_update = self.driver.create_consistencygroup(
context, cg)
else:
model_update = self.driver.create_consistencygroup(context,
group)
@ -2752,10 +2761,30 @@ class VolumeManager(manager.CleanableManager,
context, group, volumes, group_snapshot,
sorted_snapshots, source_group, sorted_source_vols))
except NotImplementedError:
model_update, volumes_model_update = (
self._create_group_from_src_generic(
context, group, volumes, group_snapshot,
sorted_snapshots, source_group, sorted_source_vols))
cgsnap_type = group_types.get_default_cgsnapshot_type()
if group.group_type_id != cgsnap_type['id']:
model_update, volumes_model_update = (
self._create_group_from_src_generic(
context, group, volumes, group_snapshot,
sorted_snapshots, source_group,
sorted_source_vols))
else:
cg, volumes = self._convert_group_to_cg(
group, volumes)
cgsnapshot, sorted_snapshots = (
self._convert_group_snapshot_to_cgsnapshot(
group_snapshot, sorted_snapshots))
source_cg, sorted_source_vols = (
self._convert_group_to_cg(source_group,
sorted_source_vols))
model_update, volumes_model_update = (
self.driver.create_consistencygroup_from_src(
context, cg, volumes, cgsnapshot,
sorted_snapshots, source_cg, sorted_source_vols))
self._remove_cgsnapshot_id_from_snapshots(sorted_snapshots)
self._remove_consistencygroup_id_from_volumes(volumes)
self._remove_consistencygroup_id_from_volumes(
sorted_source_vols)
if volumes_model_update:
for update in volumes_model_update:
@ -3100,8 +3129,17 @@ class VolumeManager(manager.CleanableManager,
model_update, volumes_model_update = (
self.driver.delete_group(context, group, volumes))
except NotImplementedError:
model_update, volumes_model_update = (
self._delete_group_generic(context, group, volumes))
cgsnap_type = group_types.get_default_cgsnapshot_type()
if group.group_type_id != cgsnap_type['id']:
model_update, volumes_model_update = (
self._delete_group_generic(context, group, volumes))
else:
cg, volumes = self._convert_group_to_cg(
group, volumes)
model_update, volumes_model_update = (
self.driver.delete_consistencygroup(context, cg,
volumes))
self._remove_consistencygroup_id_from_volumes(volumes)
if volumes_model_update:
for update in volumes_model_update:
@ -3190,6 +3228,38 @@ class VolumeManager(manager.CleanableManager,
resource={'type': 'group',
'id': group.id})
def _convert_group_to_cg(self, group, volumes):
if not group:
return None, None
cg = consistencygroup.ConsistencyGroup()
cg.from_group(group)
for vol in volumes:
vol.consistencygroup_id = vol.group_id
return group, volumes
def _remove_consistencygroup_id_from_volumes(self, volumes):
if not volumes:
return
for vol in volumes:
vol.consistencygroup_id = None
def _convert_group_snapshot_to_cgsnapshot(self, group_snapshot, snapshots):
if not group_snapshot:
return None, None
cgsnap = cgsnapshot.CGSnapshot()
cgsnap.from_group_snapshot(group_snapshot)
for snap in snapshots:
snap.cgsnapshot_id = snap.group_snapshot_id
return cgsnap, snapshots
def _remove_cgsnapshot_id_from_snapshots(self, snapshots):
if not snapshots:
return
for snap in snapshots:
snap.cgsnapshot_id = None
def _create_group_generic(self, context, group):
"""Creates a group."""
# A group entry is already created in db. Just returns a status here.
@ -3437,11 +3507,23 @@ class VolumeManager(manager.CleanableManager,
add_volumes=add_volumes_ref,
remove_volumes=remove_volumes_ref))
except NotImplementedError:
model_update, add_volumes_update, remove_volumes_update = (
self._update_group_generic(
context, group,
add_volumes=add_volumes_ref,
remove_volumes=remove_volumes_ref))
cgsnap_type = group_types.get_default_cgsnapshot_type()
if group.group_type_id != cgsnap_type['id']:
model_update, add_volumes_update, remove_volumes_update = (
self._update_group_generic(
context, group,
add_volumes=add_volumes_ref,
remove_volumes=remove_volumes_ref))
else:
cg, remove_volumes_ref = self._convert_group_to_cg(
group, remove_volumes_ref)
model_update, add_volumes_update, remove_volumes_update = (
self.driver.update_consistencygroup(
context, group,
add_volumes=add_volumes_ref,
remove_volumes=remove_volumes_ref))
self._remove_consistencygroup_id_from_volumes(
remove_volumes_ref)
if add_volumes_update:
self.db.volumes_update(context, add_volumes_update)
@ -3643,10 +3725,19 @@ class VolumeManager(manager.CleanableManager,
self.driver.create_group_snapshot(context, group_snapshot,
snapshots))
except NotImplementedError:
model_update, snapshots_model_update = (
self._create_group_snapshot_generic(
context, group_snapshot, snapshots))
cgsnap_type = group_types.get_default_cgsnapshot_type()
if group_snapshot.group_type_id != cgsnap_type['id']:
model_update, snapshots_model_update = (
self._create_group_snapshot_generic(
context, group_snapshot, snapshots))
else:
cgsnapshot, snapshots = (
self._convert_group_snapshot_to_cgsnapshot(
group_snapshot, snapshots))
model_update, snapshots_model_update = (
self.driver.create_cgsnapshot(context, cgsnapshot,
snapshots))
self._remove_cgsnapshot_id_from_snapshots(snapshots)
if snapshots_model_update:
for snap_model in snapshots_model_update:
# Update db for snapshot.
@ -3898,9 +3989,19 @@ class VolumeManager(manager.CleanableManager,
self.driver.delete_group_snapshot(context, group_snapshot,
snapshots))
except NotImplementedError:
model_update, snapshots_model_update = (
self._delete_group_snapshot_generic(
context, group_snapshot, snapshots))
cgsnap_type = group_types.get_default_cgsnapshot_type()
if group_snapshot.group_type_id != cgsnap_type['id']:
model_update, snapshots_model_update = (
self._delete_group_snapshot_generic(
context, group_snapshot, snapshots))
else:
cgsnapshot, snapshots = (
self._convert_group_snapshot_to_cgsnapshot(
group_snapshot, snapshots))
model_update, snapshots_model_update = (
self.driver.delete_cgsnapshot(context, cgsnapshot,
snapshots))
self._remove_cgsnapshot_id_from_snapshots(snapshots)
if snapshots_model_update:
for snap_model in snapshots_model_update:

View File

@ -0,0 +1,29 @@
---
upgrade:
- |
After running the migration script to migrate CGs to
generic volume groups, CG and group APIs work as follows.
* Create CG only creates in the groups table.
* Modify CG modifies in the CG table if the CG is in the
CG table, otherwise it modifies in the groups table.
* Delete CG deletes from the CG or the groups table
depending on where the CG is.
* List CG checks both CG and groups tables.
* List CG Snapshots checks both the CG and the groups
tables.
* Show CG checks both tables.
* Show CG Snapshot checks both tables.
* Create CG Snapshot creates either in the CG or the groups
table depending on where the CG is.
* Create CG from Source creates in either the CG or the
groups table depending on the source.
* Create Volume adds the volume either to the CG or the
group.
* default_cgsnapshot_type is reserved for migrating CGs.
* Group APIs will only write/read in/from the groups table.
* Group APIs will not work on groups with default_cgsnapshot_type.
* Groups with default_cgsnapshot_type can only be operated by
CG APIs.
* After CG tables are removed, we will allow default_cgsnapshot_type
to be used by group APIs.