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:
parent
9be53e1449
commit
44ebdd2252
@ -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)
|
||||
|
@ -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.
|
||||
|
@ -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):
|
||||
|
@ -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
|
||||
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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': {
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
@ -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):
|
||||
|
@ -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(
|
||||
|
@ -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(
|
||||
|
@ -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'])
|
||||
|
||||
|
@ -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'])
|
||||
|
||||
|
@ -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']
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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')
|
||||
|
@ -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',
|
||||
|
@ -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)
|
||||
|
@ -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:
|
||||
|
@ -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)
|
||||
|
@ -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:
|
||||
|
@ -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.
|
Loading…
Reference in New Issue
Block a user